diff --git a/src/main.rs b/src/main.rs index 80a6bb0..3c549ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,7 @@ fn main() -> Result<(), Error> { } // disassemble instructions - let disasm = Disasm::disassemble(&nes.memory, 0xC000, 0xFFFF).unwrap(); + let disasm = Disasm::disassemble(&nes.bus, 0xC000, 0xFFFF).unwrap(); // Prepare window and drawing resources @@ -340,7 +340,7 @@ fn render_memory(glyphs: &mut GlyphBrush, nes: &NES, offset: position_y.1 += FT_LINE_DISTANCE + FT_SIZE_PX; let mut line = format!("{:#06x}:", page); (0u16..16u16).map(|offset| offset + page) - .map(|addr| nes.memory.readb(addr)) + .map(|addr| nes.bus.readb(addr)) .map(|val| format!(" {:02x}", val)) .for_each(|s| line.push_str(&s)); glyphs.queue(Section { @@ -357,7 +357,7 @@ fn render_memory(glyphs: &mut GlyphBrush, nes: &NES, offset: position_y.1 += FT_LINE_DISTANCE + FT_SIZE_PX; let mut line = format!("{:#06x}:", page); (0u16..16u16).map(|offset| offset + page) - .map(|addr| nes.memory.readb(addr)) + .map(|addr| nes.bus.readb(addr)) .map(|val| format!(" {:02x}", val)) .for_each(|s| line.push_str(&s)); glyphs.queue(Section { diff --git a/src/nes.rs b/src/nes.rs index 9b5037b..b1c5ae0 100644 --- a/src/nes.rs +++ b/src/nes.rs @@ -19,42 +19,48 @@ pub mod ppu; // as the mediator between the different components and hold the RAM pub struct NES { pub cpu: CPU, + pub bus: Bus, pub ppu: Rc>, - pub memory: NESMemory, + pub ppu_bus: PPUBus, pub clock_count: u64, } impl NES { pub fn new() -> Self { - // The memory module needs access to some parts of the nes like the ppu - // to forward cpu reads to it. Because the NES also requires access - // to the PPU for clocking, we use shared ownership. Otherwise, all - // components except the CPU would have to live inside the memory - // instance, which is conceptually not what we are looking for. + // The bus module needs access to the ppu to forward cpu reads to it. + // Because the NES also requires access to the PPU for clocking, + // shared ownership is required. Otherwise, all + // components except the CPU would have to live inside the bus + // instance, which is conceptually not nice. let ppu = Rc::new(RefCell::new(PPU::new())); + let ppu_bus = Rc::new(RefCell::new(PPUBus::new())); NES { cpu: CPU::new(), + bus: Bus::new(ppu.clone(), ppu_bus.clone()), ppu: ppu.clone(), - memory: NESMemory::new(ppu.clone()), + ppu_bus: PPUBus::new(), clock_count: 0, } } - // Insert a cartridge into the NES. This inserts the cartridge memory + // Insert a cartridge into the NES. This inserts the cartridge bus // into the NES address range pub fn insert_cartridge(&mut self, cartridge: Cartridge) { - self.memory.insert_cartridge(cartridge); + // both buses need to be connected to the cartridge + let cart = Rc::new(RefCell::new(cartridge)); + self.bus.insert_cartridge(cart.clone()); + self.ppu_bus.insert_cartridge(cart.clone()) } // Initializes the NES CPU programm pointer pub fn start(&mut self) { - self.cpu.find_pc_addr(&self.memory); + self.cpu.find_pc_addr(&self.bus); } // Reset the CPU pub fn reset(&mut self) { self.clock_count = 0; - self.cpu.reset(&self.memory); + self.cpu.reset(&self.bus); self.ppu.borrow_mut().reset(); } @@ -62,9 +68,9 @@ impl NES { pub fn clock(&mut self) { self.clock_count += 1; if self.clock_count % 3 == 0 { - self.cpu.clock(&mut self.memory); + self.cpu.clock(&mut self.bus); } - self.ppu.borrow_mut().clock(&mut self.memory); + self.ppu.borrow_mut().clock(&mut self.ppu_bus); if self.clock_count % 100000 == 0 { info!("clock {}", self.clock_count); } diff --git a/src/nes/memory.rs b/src/nes/memory.rs index c92acbc..76abea2 100644 --- a/src/nes/memory.rs +++ b/src/nes/memory.rs @@ -12,22 +12,24 @@ pub const PPU_PHYS_RANGE: [Addr; 2] = [0x2000, 0x2007]; pub const CART_ADDR_RANGE: [Addr; 2] = [0x4020, 0xffff]; // NES memory: Contains data from RAM, cartridge... -pub struct NESMemory { +pub struct Bus { ram: [Byte; RAM_SIZE], // 2kb - cartridge: Option, + cartridge: Option>>, ppu: Rc>, + ppu_bus: Rc>, } -impl NESMemory { - pub fn new(ppu: Rc>) -> Self { - NESMemory { +impl Bus { + pub fn new(ppu: Rc>, ppu_bus: Rc>) -> Self { + Bus { ram: [0; RAM_SIZE], cartridge: None, ppu: ppu, + ppu_bus: ppu_bus, } } - pub fn insert_cartridge(&mut self, c: Cartridge) { + pub fn insert_cartridge(&mut self, c: Rc>) { self.cartridge = Some(c); } } @@ -46,11 +48,11 @@ pub trait Memory { } } -impl Memory for NESMemory { +impl Memory for Bus { fn readb(&self, addr: Addr) -> Byte { if let Some(cartridge) = &self.cartridge { if CART_ADDR_RANGE[0] <= addr && addr <= CART_ADDR_RANGE[1] { - return cartridge.readb(addr) + return cartridge.borrow().readb(addr) } } if RAM_ADDR_RANGE[0] <= addr && addr <= RAM_ADDR_RANGE[1] { @@ -59,7 +61,8 @@ impl Memory for NESMemory { } if PPU_ADDR_RANGE[0] <= addr && addr <= PPU_ADDR_RANGE[1] { let mut ppu = self.ppu.borrow_mut(); - return ppu.readb(addr & PPU_PHYS_RANGE[1]); + let ppu_bus = self.ppu_bus.borrow(); + return ppu.readb(&*ppu_bus, addr & PPU_PHYS_RANGE[1]); } 0x0000 // generic response } @@ -67,7 +70,7 @@ impl Memory for NESMemory { fn writeb(&mut self, addr: Addr, data: Byte) { if let Some(cartridge) = &mut self.cartridge { if CART_ADDR_RANGE[0] <= addr && addr <= CART_ADDR_RANGE[1] { - cartridge.writeb(addr, data) + cartridge.borrow_mut().writeb(addr, data) } } if RAM_ADDR_RANGE[0] <= addr && addr <= RAM_ADDR_RANGE[1] { @@ -76,20 +79,12 @@ impl Memory for NESMemory { } if PPU_ADDR_RANGE[0] <= addr && addr <= PPU_ADDR_RANGE[1] { let mut ppu = self.ppu.borrow_mut(); - ppu.writeb(addr & PPU_PHYS_RANGE[1], data); + let mut ppu_bus = self.ppu_bus.borrow_mut(); + ppu.writeb(&mut *ppu_bus, addr & PPU_PHYS_RANGE[1], data); } } } -// impl PPUMemory for NESMemory { -// fn readb_ppu(&self, addr: Addr) -> Byte { - -// } -// fn writeb_ppu(&mut self, addr: Addr, data: Byte) { - -// } -// } - pub trait MemoryReader { fn readb(&self, mem: &T, addr: Addr) -> Byte { mem.readb(addr) @@ -104,18 +99,316 @@ pub trait MemoryReader { } } + +pub const PATTERN_MEMORY_SIZE: usize = 4096; +pub const PATTERN_ADDR_RANGE: [Addr; 2] = [0x000, 0x1FFF]; +pub const NAMETABLE_MEMORY_SIZE: usize = 1024; +pub const NAMETABLE_ADDR_RANGE: [Addr; 2] = [0x2000, 0x3EFF]; +pub const PALETTE_MEMORY_SIZE: usize = 32; +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 + palette_memory: [Byte; PALETTE_MEMORY_SIZE], // palettes + cartridge: Option>> +} + +impl PPUBus { + pub fn new() -> Self { + PPUBus { + pattern_memory: [[0; PATTERN_MEMORY_SIZE]; 2], + nametable_memory: [[0; NAMETABLE_MEMORY_SIZE] ;4], + palette_memory: [0; PALETTE_MEMORY_SIZE], + cartridge: None, + } + } + + pub fn insert_cartridge(&mut self, c: Rc>) { + self.cartridge = Some(c); + } +} + +impl PPUMemory for PPUBus { + fn readb_ppu(&self, addr: Addr) -> Byte { + if PATTERN_ADDR_RANGE[0] <= addr && addr <= PATTERN_ADDR_RANGE[1] { + let table = if addr < 0x1000 { 0 } else { 1 }; + return self.pattern_memory[table as usize][(addr % 0x1000) as usize] + } + if NAMETABLE_ADDR_RANGE[0] <= addr && addr <= NAMETABLE_ADDR_RANGE[1] { + let table = if addr < 0x2400 { + 0 + } else if addr < 0x2800 { + 1 + } else if addr < 0x2C00 { + 2 + } else { + 3 + }; + let rel_addr = addr - 0x2000; + return self.nametable_memory[table][(rel_addr % 0x400) as usize] + } + if PALETTE_ADDR_RANGE[0] <= addr && addr <= PALETTE_ADDR_RANGE[1] { + let rel_addr = addr - 0x3F00; + return self.palette_memory[(rel_addr % 0x0020) as usize] + } + + 0x00 + } + fn writeb_ppu(&mut self, addr: Addr, data: Byte) { + if PATTERN_ADDR_RANGE[0] <= addr && addr <= PATTERN_ADDR_RANGE[1] { + let table = if addr < 0x1000 { 0 } else { 1 }; + self.pattern_memory[table as usize][(addr % 0x1000) as usize] = data; + } + if NAMETABLE_ADDR_RANGE[0] <= addr && addr <= NAMETABLE_ADDR_RANGE[1] { + let table = if addr < 0x2400 { + 0 + } else if addr < 0x2800 { + 1 + } else if addr < 0x2C00 { + 2 + } else { + 3 + }; + let rel_addr = addr - 0x2000; + self.nametable_memory[table][(rel_addr % 0x400) as usize] = data; + } + if PALETTE_ADDR_RANGE[0] <= addr && addr <= PALETTE_ADDR_RANGE[1] { + let rel_addr = addr - 0x3F00; + self.palette_memory[(rel_addr % 0x0020) as usize] = data; + } + } +} + // PPU interface to allow read/write of memory pub trait PPUMemory { fn readb_ppu(&self, addr: Addr) -> Byte; fn writeb_ppu(&mut self, addr: Addr, data: Byte); } -// pub trait PPUMemoryReader { -// fn readb_ppu(&self, mem: &T, addr: Addr) -> Byte { -// mem.readb_ppu(addr) -// } +pub trait PPUMemoryReader { + fn readb_ppu(&self, mem: &T, addr: Addr) -> Byte { + mem.readb_ppu(addr) + } -// fn writeb_ppu(&mut self, mem: &mut T, addr: Addr, data: Byte) { -// mem.writeb_ppu(addr, data) -// } -// } + fn writeb_ppu(&mut self, mem: &mut T, addr: Addr, data: Byte) { + mem.writeb_ppu(addr, data) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ppu_memory_pattern() { + let mut mem = PPUBus::new(); + + // table/position 0,0 + assert_eq!(mem.readb_ppu(0x0), 0); + mem.writeb_ppu(0x0, 100); + assert_eq!(mem.readb_ppu(0x0), 100); + + // table/position 0,1 + assert_eq!(mem.readb_ppu(0x1), 0); + mem.writeb_ppu(0x1, 101); + assert_eq!(mem.readb_ppu(0x1), 101); + + // table/position 0,1000 + assert_eq!(mem.readb_ppu(0x0FFF), 0); + mem.writeb_ppu(0x0FFF, 102); + assert_eq!(mem.readb_ppu(0x0FFF), 102); + + // table/position 1,0 + assert_eq!(mem.readb_ppu(0x1000), 0); + mem.writeb_ppu(0x1000, 103); + assert_eq!(mem.readb_ppu(0x1000), 103); + + // table/position 1,1 + assert_eq!(mem.readb_ppu(0x1001), 0); + mem.writeb_ppu(0x1001, 104); + assert_eq!(mem.readb_ppu(0x1001), 104); + + // table/position 1,1000 + assert_eq!(mem.readb_ppu(0x1FFF), 0); + mem.writeb_ppu(0x1FFF, 105); + assert_eq!(mem.readb_ppu(0x1FFF), 105); + + // not pattern + assert_eq!(mem.readb_ppu(0x2000), 0); + } + + #[test] + fn test_ppu_memory_pattern_no_overwrite() { + let mut mem = PPUBus::new(); + + // fill pattern + for addr in 0x0 .. 0x1FFF + 1 { + mem.writeb_ppu(addr, 0x1); + } + + // fill remaining addr space + for addr in 0x2000 .. 0x3EFF + 1 { + mem.writeb_ppu(addr, 0x2); + } + for addr in 0x3F00 .. 0x3FFF + 1 { + mem.writeb_ppu(addr, 0x3); + } + + // pattern should not have changed + for addr in 0x0 .. 0x1FFF + 1 { + assert_eq!(0x1, mem.readb_ppu(addr)); + } + } + + #[test] + fn test_ppu_memory_nametable_rw() { + let mut mem = PPUBus::new(); + + // 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)); + } + + // assert nametables are initialized correctly + for (idx, addr) in (0x2000 .. 0x2FFF + 1).enumerate() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + } + + #[test] + fn test_ppu_memory_nametable_not_overwritten() { + let mut mem = PPUBus::new(); + + // write some value to whole nametable space + for addr in 0x2000 .. 0x3EFF + 1 { + mem.writeb_ppu(addr, 0x1); + } + // write something else to the remaining address space + for addr in 0x0 .. 0x1FFF + 1 { + mem.writeb_ppu(addr, 0x2); + } + for addr in 0x3F00 .. 0x3FFF + 1 { + mem.writeb_ppu(addr, 0x3); + } + + // 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); + } + } + + #[test] + fn test_ppu_memory_nametable_mirroring() { + let mut mem = PPUBus::new(); + + // read/write something to nametable memory + for (idx, addr) in (0x2000 .. 0x2FFF + 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() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + + // write data to mirrored addr range + for (idx, addr) in (0x3000 .. 0x3EFF + 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() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + } + + #[test] + fn test_ppu_memory_palette_rw() { + let mut mem = PPUBus::new(); + + // read/write something to palette memory + for (idx, addr) in (0x3F00 .. 0x3F1F + 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)); + } + + // assert palette are initialized correctly + for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + } + + #[test] + fn test_ppu_memory_palette_not_overwritten() { + let mut mem = PPUBus::new(); + + // write some value to whole palette space + for addr in 0x3F00 .. 0x3F1F + 1 { + mem.writeb_ppu(addr, 0x1); + } + // write something else to the remaining address space + for addr in 0x0 .. 0x1FFF + 1 { + mem.writeb_ppu(addr, 0x2); + } + for addr in 0x2000 .. 0x3EFF + 1 { + mem.writeb_ppu(addr, 0x3); + } + + // palette should not have changed + for addr in 0x3F00 .. 0x3F1F + 1 { + assert_eq!(0x1, mem.readb_ppu(addr), + "Palette changed unxexpected at position {:#08x}", addr); + } + } + + #[test] + fn test_ppu_memory_palette_mirroring() { + let mut mem = PPUBus::new(); + + // read/write something to palette memory + for (idx, addr) in (0x3F00 .. 0x3F1F + 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 (0x3F20 .. 0x3F3F + 1).enumerate() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + for (idx, addr) in (0x3F40 .. 0x3F5F + 1).enumerate() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + for (idx, addr) in (0x3F60 .. 0x3F7F + 1).enumerate() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + for (idx, addr) in (0x3F80 .. 0x3F9F + 1).enumerate() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + for (idx, addr) in (0x3FA0 .. 0x3FBF + 1).enumerate() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + for (idx, addr) in (0x3FC0 .. 0x3FDF + 1).enumerate() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + for (idx, addr) in (0x3FE0 .. 0x3FFF + 1).enumerate() { + assert_eq!(idx as Byte, mem.readb_ppu(addr)); + } + + // write data to mirrored addr range + for (idx, addr) in (0x3FC0 .. 0x3FDF + 1).enumerate() { + mem.writeb_ppu(addr, idx as Byte + 1); + } + + // start memory should have the same data + for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() { + assert_eq!(idx as Byte + 1, mem.readb_ppu(addr)); + } + } +} diff --git a/src/nes/ppu.rs b/src/nes/ppu.rs index 7da43e9..418e760 100644 --- a/src/nes/ppu.rs +++ b/src/nes/ppu.rs @@ -1,7 +1,6 @@ -use crate::nes::memory::{Memory,PPUMemory}; +use crate::nes::memory::PPUMemory; use crate::nes::types::*; use image::{ImageBuffer, Rgba}; -use rand::Rng; use palette::PALETTE; pub mod palette; @@ -94,6 +93,7 @@ impl Registers { } } + pub struct PPU { pub regs: Registers, pub cycle: u16, @@ -105,15 +105,13 @@ pub struct PPU { data_buffer: Byte, } -// impl PPUMemoryReader for PPU {} - impl PPU { pub fn new() -> Self { PPU { regs: Registers::new(), cycle: 0, scanline: 0, - canvas_main: ImageBuffer::new(256, 240), + canvas_main: ImageBuffer::from_pixel(256, 240, PALETTE[&0x00]), pattern_table: [ImageBuffer::new(128, 128), ImageBuffer::new(128, 128)], frame_ready: false, addr_latch_set: false, @@ -162,7 +160,7 @@ impl PPU { // Scanline 240: PPU idle // Scanline 241-260: Vblack. Flag is set during second clock of 241 together // with NMI - pub fn clock(&mut self, _mem: &mut T) { + pub fn clock(&mut self, _mem: &mut T) { if self.cycle == 340 { self.cycle = 0; if self.scanline == 261 { @@ -184,8 +182,8 @@ impl PPU { } } - // CPU can write certain registers of the PPU through the bus - pub fn readb(&mut self, addr: Addr) -> Byte { + // read from the main bus + pub fn readb(&mut self, mem: &T, addr: Addr) -> Byte { // Only certain registers of the PPU can actually by read // remaining registers and read attemps will return garbage match addr { @@ -208,7 +206,7 @@ impl PPU { // because the PPU is weird, this does not apply for the // palette memory let data = self.data_buffer; - self.data_buffer = self.readb_ppu(addr); + self.data_buffer = mem.readb_ppu(addr); if addr > 0x3F00 { // everything above 0x3F00 is palette self.data_buffer @@ -220,8 +218,8 @@ impl PPU { } } - // CPU can write certain registers of the PPU through the bus - pub fn writeb(&mut self, addr: Addr, data: Byte) { + // // write to the main bus + pub fn writeb(&mut self, mem: &mut T, addr: Addr, data: Byte) { // Only some of the PPU regs can be written to match addr { // Control @@ -253,7 +251,7 @@ impl PPU { } // write data to the ppu addr bus 0x2007 => { - self.writeb_ppu(self.regs.addr, data); + mem.writeb_ppu(self.regs.addr, data); // after write, increment vram addr for next write. // The increment value is determined by the vertical mode // flag of the status reg 0: +1, 1: +32 @@ -267,15 +265,6 @@ impl PPU { } } - // Some read/writes are nt - fn writeb_ppu(&mut self, addr: Addr, data: Byte) { - // TODO - } - - fn readb_ppu(&self, addr: Addr) -> Byte { - unimplemented!() - } - // Get the correct color for the pixel from the given palette fn get_color(&self, pixel: Byte, _palette: Byte) -> Pixel { // TODO: not implemented yet. returns some black and white color @@ -327,6 +316,7 @@ impl PPU { #[cfg(test)] mod tests { use super::*; + use crate::nes::memory::PPUBus; #[test] fn test_set_flags() { @@ -372,14 +362,13 @@ mod tests { #[test] fn test_write_addr() { let mut ppu = PPU::new(); - // TODO unit test + let mut ppu_bus = PPUBus::new(); assert_eq!(ppu.regs.addr, 0x0000); - - ppu.writeb(0x2006, 0x12); + ppu.writeb(&mut ppu_bus, 0x2006, 0x12); assert_eq!(ppu.regs.addr, 0x1200); - ppu.writeb(0x2006, 0x34); + ppu.writeb(&mut ppu_bus, 0x2006, 0x34); assert_eq!(ppu.regs.addr, 0x1234); - ppu.writeb(0x2006, 0x56); + ppu.writeb(&mut ppu_bus, 0x2006, 0x56); assert_eq!(ppu.regs.addr, 0x5634); } }