diff --git a/src/nes/cartridge.rs b/src/nes/cartridge.rs index a14eeac..c81f9c9 100644 --- a/src/nes/cartridge.rs +++ b/src/nes/cartridge.rs @@ -18,19 +18,22 @@ struct Header { } impl Header { + // parse the 16 Byte header of the file fn new(f: &mut File) -> Result { 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, chr_rom: Vec, mapper: Box, + mirror: MirrorMode, } impl Cartridge { @@ -78,27 +97,29 @@ impl Cartridge { f.read_exact(&mut prg_rom)?; 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 { diff --git a/src/nes/ppubus.rs b/src/nes/ppubus.rs index a015206..d203cb8 100644 --- a/src/nes/ppubus.rs +++ b/src/nes/ppubus.rs @@ -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>> } @@ -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 { - 0 - } else if addr < 0x2800 { - 1 - } else if addr < 0x2C00 { - 2 - } else { - 3 - }; - let rel_addr = (addr - 0x2000) % 0x400; - - (table as usize, rel_addr as usize) + // 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 < 0x0800 { + 1 + } else if addr < 0x0C00 { + 2 + } else { + 3 + }; + + // 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,8 +112,9 @@ 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); - return self.nametable_memory[idx.0][idx.1] + if let Some(idx) = &self.map_nametable_addr(addr) { + return self.nametable_memory[idx.0][idx.1] + } } 0x00 @@ -114,8 +139,9 @@ 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); - self.nametable_memory[idx.0][idx.1] = data; + if let Some(idx) = &self.map_nametable_addr(addr) { + self.nametable_memory[idx.0][idx.1] = data; + } } } } @@ -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)); }