implement nametable mirroring
This commit is contained in:
@@ -18,19 +18,22 @@ struct Header {
|
||||
}
|
||||
|
||||
impl Header {
|
||||
// parse the 16 Byte header of the file
|
||||
fn new(f: &mut File) -> Result<Self, Error> {
|
||||
f.seek(SeekFrom::Start(0))?;
|
||||
|
||||
// assert NES "NES"
|
||||
// Byte 0-3 are the "NES" format header and just say NES
|
||||
f.seek(SeekFrom::Start(4))?;
|
||||
|
||||
// Byte 4 and 5 are prg and chr rom sizes
|
||||
let mut rom_sizes = [0; 2]; // prg size in 16K and chr size in 8K
|
||||
f.read_exact(&mut rom_sizes)?;
|
||||
|
||||
// Byte 6-10 are various flags
|
||||
let mut flags = [0; 5];
|
||||
f.read_exact(&mut flags)?;
|
||||
|
||||
// Unused
|
||||
// Byte 11-15 are unused
|
||||
f.seek(SeekFrom::Current(5))?;
|
||||
|
||||
Ok(Header {
|
||||
@@ -54,12 +57,28 @@ impl Header {
|
||||
hi | lo
|
||||
}
|
||||
|
||||
pub fn get_mirror_mode(&self) -> MirrorMode {
|
||||
if (self.mapper1 & 0x01) == 0 {
|
||||
MirrorMode::HORIZONTAL
|
||||
} else {
|
||||
MirrorMode::VERTICAL
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Nametable mirroring mode
|
||||
#[derive(PartialEq,Debug,Copy,Clone)]
|
||||
pub enum MirrorMode {
|
||||
HORIZONTAL,
|
||||
VERTICAL,
|
||||
}
|
||||
|
||||
pub struct Cartridge {
|
||||
prg_rom: Vec<Byte>,
|
||||
chr_rom: Vec<Byte>,
|
||||
mapper: Box<dyn Mapper>,
|
||||
mirror: MirrorMode,
|
||||
}
|
||||
|
||||
impl Cartridge {
|
||||
@@ -79,26 +98,28 @@ impl Cartridge {
|
||||
let mut chr_rom = vec!(0; header.chr_rom_chunks as usize * 8192);
|
||||
f.read_exact(&mut chr_rom)?;
|
||||
|
||||
|
||||
let mapper = match header.get_mapper_id() {
|
||||
0 => { Mapper0::new(header.prg_rom_chunks, header.chr_rom_chunks) }
|
||||
id => bail!("Mapper {:04} not supported", id)
|
||||
};
|
||||
|
||||
let mirror = header.get_mirror_mode();
|
||||
|
||||
debug!("Cartrige loaded. mapper: {:?}", &mapper);
|
||||
Ok(Cartridge {
|
||||
prg_rom: prg_rom,
|
||||
chr_rom: chr_rom,
|
||||
mapper: Box::new(mapper)
|
||||
mapper: Box::new(mapper),
|
||||
mirror: mirror,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dummy() -> Self {
|
||||
pub fn dummy(mirror: MirrorMode) -> Self {
|
||||
Cartridge {
|
||||
prg_rom: vec![0; 16384],
|
||||
chr_rom: vec![0; 8192],
|
||||
mapper: Box::new(Mapper0::new(1, 1))
|
||||
mapper: Box::new(Mapper0::new(1, 1)),
|
||||
mirror: mirror,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +155,12 @@ impl Cartridge {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// get cartrige mirror mode
|
||||
// TODO can be changed by mapper
|
||||
pub fn get_mirror_mode(&self) -> MirrorMode {
|
||||
self.mirror
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -179,6 +206,31 @@ mod tests {
|
||||
assert_eq!(255, header.get_mapper_id());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_get_mirror_mode() {
|
||||
let header = Header {
|
||||
prg_rom_chunks: 1,
|
||||
chr_rom_chunks: 1,
|
||||
mapper1: 0x00,
|
||||
mapper2: 0x00,
|
||||
prg_ram_size: 0x00,
|
||||
tv1: 0x00,
|
||||
tv2: 0x00
|
||||
};
|
||||
assert_eq!(header.get_mirror_mode(), MirrorMode::HORIZONTAL);
|
||||
let header = Header {
|
||||
prg_rom_chunks: 1,
|
||||
chr_rom_chunks: 1,
|
||||
mapper1: 0x01,
|
||||
mapper2: 0x00,
|
||||
prg_ram_size: 0x00,
|
||||
tv1: 0x00,
|
||||
tv2: 0x00
|
||||
};
|
||||
assert_eq!(header.get_mirror_mode(), MirrorMode::VERTICAL);
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header_has_trainer() {
|
||||
let header = Header {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::nes::ppu::PPU;
|
||||
use std::rc::Rc;
|
||||
use core::cell::RefCell;
|
||||
use crate::nes::cartridge::Cartridge;
|
||||
use crate::nes::types::*;
|
||||
use crate::nes::cartridge::MirrorMode;
|
||||
|
||||
pub const PATTERN_MEMORY_SIZE: usize = 4096;
|
||||
pub const PATTERN_ADDR_RANGE: [Addr; 2] = [0x000, 0x1FFF];
|
||||
@@ -13,7 +13,7 @@ pub const PALETTE_ADDR_RANGE: [Addr; 2] = [0x3F00, 0x3FFF];
|
||||
|
||||
pub struct PPUBus {
|
||||
pattern_memory: [[Byte; PATTERN_MEMORY_SIZE]; 2], // 8kb pattern memory
|
||||
nametable_memory: [[Byte; NAMETABLE_MEMORY_SIZE] ;4], // 2kb nametables
|
||||
nametable_memory: [[Byte; NAMETABLE_MEMORY_SIZE] ;2], // 2kb nametables
|
||||
palette_memory: [Byte; PALETTE_MEMORY_SIZE], // palettes
|
||||
cartridge: Option<Rc<RefCell<Cartridge>>>
|
||||
}
|
||||
@@ -22,7 +22,7 @@ impl PPUBus {
|
||||
pub fn new() -> Self {
|
||||
PPUBus {
|
||||
pattern_memory: [[0; PATTERN_MEMORY_SIZE]; 2],
|
||||
nametable_memory: [[0; NAMETABLE_MEMORY_SIZE] ;4],
|
||||
nametable_memory: [[0; NAMETABLE_MEMORY_SIZE] ;2],
|
||||
palette_memory: [0; PALETTE_MEMORY_SIZE],
|
||||
cartridge: None,
|
||||
}
|
||||
@@ -51,20 +51,44 @@ impl PPUBus {
|
||||
}
|
||||
|
||||
// maps a nametable address to the corresponding index in the memory
|
||||
// array
|
||||
fn map_nametable_addr(&self, addr: Addr) -> (usize, usize) {
|
||||
let table = if addr < 0x2400 {
|
||||
// array. Method required an inserted cartrige to read the mirroring mode.
|
||||
// Returns none if no cartrige is inserted
|
||||
fn map_nametable_addr(&self, addr: Addr) -> Option<(usize, usize)> {
|
||||
if let Some(cartridge) = &self.cartridge {
|
||||
// sovle mirroring
|
||||
let addr = addr % 0x1000;
|
||||
|
||||
// There are four theoretical nametables
|
||||
let nametable_idx = if addr < 0x0400 {
|
||||
0
|
||||
} else if addr < 0x2800 {
|
||||
} else if addr < 0x0800 {
|
||||
1
|
||||
} else if addr < 0x2C00 {
|
||||
} else if addr < 0x0C00 {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
};
|
||||
let rel_addr = (addr - 0x2000) % 0x400;
|
||||
|
||||
(table as usize, rel_addr as usize)
|
||||
// nametable 2,3 are actually mirrored depending on the cartriges
|
||||
// mirror mode
|
||||
let mirror_mode = cartridge.borrow().get_mirror_mode();
|
||||
let table_id = if mirror_mode == MirrorMode::VERTICAL {
|
||||
match nametable_idx {
|
||||
0 | 2 => 0,
|
||||
1 | 3 => 1,
|
||||
_ => unreachable!()
|
||||
}
|
||||
} else {
|
||||
match nametable_idx {
|
||||
0 | 1 => 0,
|
||||
2 | 3 => 1,
|
||||
_ => unreachable!()
|
||||
}
|
||||
};
|
||||
let rel_addr = addr % 0x400;
|
||||
return Some((table_id as usize, rel_addr as usize))
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +112,10 @@ impl PPUMemory for PPUBus {
|
||||
return self.pattern_memory[idx.0][idx.1]
|
||||
}
|
||||
if NAMETABLE_ADDR_RANGE[0] <= addr && addr <= NAMETABLE_ADDR_RANGE[1] {
|
||||
let idx = &self.map_nametable_addr(addr);
|
||||
if let Some(idx) = &self.map_nametable_addr(addr) {
|
||||
return self.nametable_memory[idx.0][idx.1]
|
||||
}
|
||||
}
|
||||
|
||||
0x00
|
||||
}
|
||||
@@ -114,10 +139,11 @@ impl PPUMemory for PPUBus {
|
||||
self.pattern_memory[idx.0][idx.1] = data;
|
||||
}
|
||||
if NAMETABLE_ADDR_RANGE[0] <= addr && addr <= NAMETABLE_ADDR_RANGE[1] {
|
||||
let idx = &self.map_nametable_addr(addr);
|
||||
if let Some(idx) = &self.map_nametable_addr(addr) {
|
||||
self.nametable_memory[idx.0][idx.1] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PPU interface to allow read/write of memory
|
||||
@@ -191,13 +217,19 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn dummy_ppu_bus(mirror: MirrorMode) -> PPUBus {
|
||||
let mut mem = PPUBus::new();
|
||||
let mut cart = Cartridge::dummy(mirror);
|
||||
mem.insert_cartridge(Rc::new(RefCell::new(cart)));
|
||||
mem
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_nametable_rw() {
|
||||
let mut mem = PPUBus::new();
|
||||
let mut mem = dummy_ppu_bus(MirrorMode::VERTICAL);
|
||||
|
||||
// read/write something to nametable memory
|
||||
for (idx, addr) in (0x2000 .. 0x2FFF + 1).enumerate() {
|
||||
assert_eq!(0, mem.readb_ppu(addr));
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
@@ -210,7 +242,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_nametable_not_overwritten() {
|
||||
let mut mem = PPUBus::new();
|
||||
let mut mem = dummy_ppu_bus(MirrorMode::VERTICAL);
|
||||
|
||||
// write some value to whole nametable space
|
||||
for addr in 0x2000 .. 0x3EFF + 1 {
|
||||
@@ -227,33 +259,103 @@ mod tests {
|
||||
// Namestables should not have changed
|
||||
for addr in 0x2000 .. 0x3EFF + 1 {
|
||||
assert_eq!(0x1, mem.readb_ppu(addr),
|
||||
"Nametable changed unxexpected at position {:#08x}", addr);
|
||||
"Nametable changed unxexpected at position {:#06x}", addr);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_nametable_mirrormode_vertical() {
|
||||
let mut mem = dummy_ppu_bus(MirrorMode::VERTICAL);
|
||||
// write something to nametable memory and check if vertical mirror
|
||||
// has the same data
|
||||
|
||||
// vertical left
|
||||
for (idx, addr) in (0x2000 .. 0x23FF + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr+0x800));
|
||||
}
|
||||
|
||||
for (idx, addr) in (0x2800 .. 0x2BFF + 1).enumerate() {
|
||||
let val = (idx as Byte).wrapping_add(1);
|
||||
mem.writeb_ppu(addr, val);
|
||||
assert_eq!(val, mem.readb_ppu(addr));
|
||||
assert_eq!(val, mem.readb_ppu(addr-0x800));
|
||||
}
|
||||
|
||||
// vertical right
|
||||
for (idx, addr) in (0x2400 .. 0x27FF + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr+0x800));
|
||||
}
|
||||
|
||||
for (idx, addr) in (0x2C00 .. 0x2EFF + 1).enumerate() {
|
||||
let val = (idx as Byte).wrapping_add(1);
|
||||
mem.writeb_ppu(addr, val);
|
||||
assert_eq!(val, mem.readb_ppu(addr));
|
||||
assert_eq!(val, mem.readb_ppu(addr-0x800));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_nametable_mirrormode_horizontal() {
|
||||
let mut mem = dummy_ppu_bus(MirrorMode::HORIZONTAL);
|
||||
// write something to nametable memory and check if horizontal mirror
|
||||
// has the same data
|
||||
|
||||
// horizontal top
|
||||
for (idx, addr) in (0x2000 .. 0x23FF + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr+0x400));
|
||||
}
|
||||
|
||||
for (idx, addr) in (0x2400 .. 0x27FF + 1).enumerate() {
|
||||
let val = (idx as Byte).wrapping_add(1);
|
||||
mem.writeb_ppu(addr, val);
|
||||
assert_eq!(val, mem.readb_ppu(addr));
|
||||
assert_eq!(val, mem.readb_ppu(addr-0x400));
|
||||
}
|
||||
|
||||
// horizontal bottom
|
||||
for (idx, addr) in (0x2800 .. 0x2BFF + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr+0x400));
|
||||
}
|
||||
|
||||
for (idx, addr) in (0x2C00 .. 0x2EFF + 1).enumerate() {
|
||||
let val = (idx as Byte).wrapping_add(1);
|
||||
mem.writeb_ppu(addr, val);
|
||||
assert_eq!(val, mem.readb_ppu(addr));
|
||||
assert_eq!(val, mem.readb_ppu(addr-0x400));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_nametable_mirroring() {
|
||||
let mut mem = PPUBus::new();
|
||||
let mut mem = dummy_ppu_bus(MirrorMode::VERTICAL);
|
||||
|
||||
// read/write something to nametable memory
|
||||
for (idx, addr) in (0x2000 .. 0x2FFF + 1).enumerate() {
|
||||
for (idx, addr) in (0x2000 .. 0x27FF + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// mirror memory should have the same data
|
||||
for (idx, addr) in (0x3000 .. 0x3EFF + 1).enumerate() {
|
||||
for (idx, addr) in (0x3000 .. 0x37FF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// write data to mirrored addr range
|
||||
for (idx, addr) in (0x3000 .. 0x3EFF + 1).enumerate() {
|
||||
for (idx, addr) in (0x3000 .. 0x37FF + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// start memory should have the same data
|
||||
for (idx, addr) in (0x2000 .. 0x2FFF + 1).enumerate() {
|
||||
for (idx, addr) in (0x2000 .. 0x27FF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
}
|
||||
@@ -265,7 +367,6 @@ mod tests {
|
||||
// read/write something to palette memory
|
||||
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
println!("Written {} to {:#08x}", idx, addr);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
@@ -340,7 +441,6 @@ mod tests {
|
||||
// read/write something to palette memory
|
||||
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
println!("Written {} to {:#08x}", idx, addr);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user