implement nametable mirroring

This commit is contained in:
Daniel Bauer
2020-01-22 11:47:01 +01:00
parent b3efd0363d
commit e054472e65
2 changed files with 190 additions and 38 deletions

View File

@@ -18,19 +18,22 @@ struct Header {
} }
impl Header { impl Header {
// parse the 16 Byte header of the file
fn new(f: &mut File) -> Result<Self, Error> { fn new(f: &mut File) -> Result<Self, Error> {
f.seek(SeekFrom::Start(0))?; 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))?; 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 let mut rom_sizes = [0; 2]; // prg size in 16K and chr size in 8K
f.read_exact(&mut rom_sizes)?; f.read_exact(&mut rom_sizes)?;
// Byte 6-10 are various flags
let mut flags = [0; 5]; let mut flags = [0; 5];
f.read_exact(&mut flags)?; f.read_exact(&mut flags)?;
// Unused // Byte 11-15 are unused
f.seek(SeekFrom::Current(5))?; f.seek(SeekFrom::Current(5))?;
Ok(Header { Ok(Header {
@@ -54,12 +57,28 @@ impl Header {
hi | lo 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 { pub struct Cartridge {
prg_rom: Vec<Byte>, prg_rom: Vec<Byte>,
chr_rom: Vec<Byte>, chr_rom: Vec<Byte>,
mapper: Box<dyn Mapper>, mapper: Box<dyn Mapper>,
mirror: MirrorMode,
} }
impl Cartridge { impl Cartridge {
@@ -78,27 +97,29 @@ impl Cartridge {
f.read_exact(&mut prg_rom)?; f.read_exact(&mut prg_rom)?;
let mut chr_rom = vec!(0; header.chr_rom_chunks as usize * 8192); let mut chr_rom = vec!(0; header.chr_rom_chunks as usize * 8192);
f.read_exact(&mut chr_rom)?; f.read_exact(&mut chr_rom)?;
let mapper = match header.get_mapper_id() { let mapper = match header.get_mapper_id() {
0 => { Mapper0::new(header.prg_rom_chunks, header.chr_rom_chunks) } 0 => { Mapper0::new(header.prg_rom_chunks, header.chr_rom_chunks) }
id => bail!("Mapper {:04} not supported", id) id => bail!("Mapper {:04} not supported", id)
}; };
let mirror = header.get_mirror_mode();
debug!("Cartrige loaded. mapper: {:?}", &mapper); debug!("Cartrige loaded. mapper: {:?}", &mapper);
Ok(Cartridge { Ok(Cartridge {
prg_rom: prg_rom, prg_rom: prg_rom,
chr_rom: chr_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 { Cartridge {
prg_rom: vec![0; 16384], prg_rom: vec![0; 16384],
chr_rom: vec![0; 8192], 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 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()); 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] #[test]
fn test_header_has_trainer() { fn test_header_has_trainer() {
let header = Header { let header = Header {

View File

@@ -1,8 +1,8 @@
use crate::nes::ppu::PPU;
use std::rc::Rc; use std::rc::Rc;
use core::cell::RefCell; use core::cell::RefCell;
use crate::nes::cartridge::Cartridge; use crate::nes::cartridge::Cartridge;
use crate::nes::types::*; use crate::nes::types::*;
use crate::nes::cartridge::MirrorMode;
pub const PATTERN_MEMORY_SIZE: usize = 4096; pub const PATTERN_MEMORY_SIZE: usize = 4096;
pub const PATTERN_ADDR_RANGE: [Addr; 2] = [0x000, 0x1FFF]; 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 { pub struct PPUBus {
pattern_memory: [[Byte; PATTERN_MEMORY_SIZE]; 2], // 8kb pattern memory 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 palette_memory: [Byte; PALETTE_MEMORY_SIZE], // palettes
cartridge: Option<Rc<RefCell<Cartridge>>> cartridge: Option<Rc<RefCell<Cartridge>>>
} }
@@ -22,7 +22,7 @@ impl PPUBus {
pub fn new() -> Self { pub fn new() -> Self {
PPUBus { PPUBus {
pattern_memory: [[0; PATTERN_MEMORY_SIZE]; 2], 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], palette_memory: [0; PALETTE_MEMORY_SIZE],
cartridge: None, cartridge: None,
} }
@@ -51,20 +51,44 @@ impl PPUBus {
} }
// maps a nametable address to the corresponding index in the memory // maps a nametable address to the corresponding index in the memory
// array // array. Method required an inserted cartrige to read the mirroring mode.
fn map_nametable_addr(&self, addr: Addr) -> (usize, usize) { // Returns none if no cartrige is inserted
let table = if addr < 0x2400 { fn map_nametable_addr(&self, addr: Addr) -> Option<(usize, usize)> {
0 if let Some(cartridge) = &self.cartridge {
} else if addr < 0x2800 { // sovle mirroring
1 let addr = addr % 0x1000;
} else if addr < 0x2C00 {
2 // There are four theoretical nametables
} else { let nametable_idx = if addr < 0x0400 {
3 0
}; } else if addr < 0x0800 {
let rel_addr = (addr - 0x2000) % 0x400; 1
} else if addr < 0x0C00 {
(table as usize, rel_addr as usize) 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] return self.pattern_memory[idx.0][idx.1]
} }
if NAMETABLE_ADDR_RANGE[0] <= addr && addr <= NAMETABLE_ADDR_RANGE[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] return self.nametable_memory[idx.0][idx.1]
}
} }
0x00 0x00
@@ -114,8 +139,9 @@ impl PPUMemory for PPUBus {
self.pattern_memory[idx.0][idx.1] = data; self.pattern_memory[idx.0][idx.1] = data;
} }
if NAMETABLE_ADDR_RANGE[0] <= addr && addr <= NAMETABLE_ADDR_RANGE[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) {
self.nametable_memory[idx.0][idx.1] = data; 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] #[test]
fn test_ppu_memory_nametable_rw() { 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 // read/write something to nametable memory
for (idx, addr) in (0x2000 .. 0x2FFF + 1).enumerate() { for (idx, addr) in (0x2000 .. 0x2FFF + 1).enumerate() {
assert_eq!(0, mem.readb_ppu(addr));
mem.writeb_ppu(addr, idx as Byte); 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));
} }
@@ -210,7 +242,7 @@ mod tests {
#[test] #[test]
fn test_ppu_memory_nametable_not_overwritten() { 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 // write some value to whole nametable space
for addr in 0x2000 .. 0x3EFF + 1 { for addr in 0x2000 .. 0x3EFF + 1 {
@@ -227,33 +259,103 @@ mod tests {
// Namestables should not have changed // Namestables should not have changed
for addr in 0x2000 .. 0x3EFF + 1 { for addr in 0x2000 .. 0x3EFF + 1 {
assert_eq!(0x1, mem.readb_ppu(addr), 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] #[test]
fn test_ppu_memory_nametable_mirroring() { 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 // 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); 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));
} }
// mirror memory should have the same data // 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)); assert_eq!(idx as Byte, mem.readb_ppu(addr));
} }
// write data to mirrored addr range // 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); 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));
} }
// start memory should have the same data // 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)); assert_eq!(idx as Byte, mem.readb_ppu(addr));
} }
} }
@@ -265,7 +367,6 @@ mod tests {
// read/write something to palette memory // read/write something to palette memory
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() { for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
mem.writeb_ppu(addr, idx as Byte); mem.writeb_ppu(addr, idx as Byte);
println!("Written {} to {:#08x}", idx, addr);
assert_eq!(idx as Byte, mem.readb_ppu(addr)); assert_eq!(idx as Byte, mem.readb_ppu(addr));
} }
@@ -340,7 +441,6 @@ mod tests {
// read/write something to palette memory // read/write something to palette memory
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() { for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
mem.writeb_ppu(addr, idx as Byte); mem.writeb_ppu(addr, idx as Byte);
println!("Written {} to {:#08x}", idx, addr);
assert_eq!(idx as Byte, mem.readb_ppu(addr)); assert_eq!(idx as Byte, mem.readb_ppu(addr));
} }