diff --git a/src/cpu/mappers/mmc3.rs b/src/cpu/mappers/mmc3.rs index 666105e..9c5ecaa 100644 --- a/src/cpu/mappers/mmc3.rs +++ b/src/cpu/mappers/mmc3.rs @@ -147,6 +147,8 @@ impl Mapper for MMC3 { } fn step_scanline(&mut self) { + // MMC3 IRQ should be triggered on A12 toggles, not just once per scanline + // This is called when A12 transitions from 0 to 1 during background tile fetching if self.irq_counter == 0 { self.irq_counter = self.irq_reload; } else { diff --git a/src/cpu/mappers/mmc3_test.rs b/src/cpu/mappers/mmc3_test.rs new file mode 100644 index 0000000..c07bf82 --- /dev/null +++ b/src/cpu/mappers/mmc3_test.rs @@ -0,0 +1,95 @@ +#[cfg(test)] +mod tests { + use crate::cpu::mappers::mmc3::MMC3; + use crate::cpu::mappers::Mapper; + use crate::cpu::rom::{Cart, Mirroring}; + + fn create_test_cart() -> Cart { + Cart { + bytes: vec![0; 0x10000], // 64KB ROM + hash: [0; 32], + prg_rom_size: 2, // 32KB + chr_rom_size: 1, // 8KB + mirroring: Mirroring::Horizontal, + mapper_id: 4, + battery: false, + trainer: false, + prg_rom_start: 16, + chr_rom_start: 0x8000, + } + } + + #[test] + fn test_mmc3_initialization() { + let cart = create_test_cart(); + let mut mmc3 = MMC3::new(&cart); + + // Test that it doesn't panic and can be created + assert!(!mmc3.is_asserting_irq()); + } + + #[test] + fn test_mmc3_irq_assertions() { + let cart = create_test_cart(); + let mut mmc3 = MMC3::new(&cart); + + // Initially should not assert IRQ + assert!(!mmc3.is_asserting_irq()); + + // Test IRQ enable/disable + mmc3.write(&mut create_test_cart(), 0xE001, 0x01); // Enable IRQ + mmc3.write(&mut create_test_cart(), 0xE000, 0x00); // Disable IRQ + + assert!(!mmc3.is_asserting_irq()); + } + + #[test] + fn test_mmc3_prg_ram_access() { + let cart = create_test_cart(); + let mut mmc3 = MMC3::new(&cart); + let mut cart = create_test_cart(); + + // Test PRG RAM write/read + mmc3.write(&mut cart, 0x6000, 0x42); + assert_eq!(mmc3.read(&mut cart, 0x6000), 0x42); + + mmc3.write(&mut cart, 0x7FFF, 0x84); + assert_eq!(mmc3.read(&mut cart, 0x7FFF), 0x84); + } + + #[test] + fn test_mmc3_bank_register_writes() { + let cart = create_test_cart(); + let mut mmc3 = MMC3::new(&cart); + let mut cart = create_test_cart(); + + // Test bank select register + mmc3.write(&mut cart, 0x8000, 0x06); // Select PRG bank 0 + mmc3.write(&mut cart, 0x8001, 0x02); // Set bank to 2 + + // Test mirroring + mmc3.write(&mut cart, 0xA000, 0x01); // Horizontal mirroring + assert_eq!(cart.mirroring, Mirroring::Horizontal); + + mmc3.write(&mut cart, 0xA000, 0x00); // Vertical mirroring + assert_eq!(cart.mirroring, Mirroring::Vertical); + } + + #[test] + fn test_mmc3_irq_registers() { + let cart = create_test_cart(); + let mut mmc3 = MMC3::new(&cart); + let mut cart = create_test_cart(); + + // Test IRQ reload register + mmc3.write(&mut cart, 0xC000, 0x05); // Set reload value + mmc3.write(&mut cart, 0xC001, 0x00); // Clear counter + + // Test IRQ enable/disable + mmc3.write(&mut cart, 0xE001, 0x01); // Enable IRQ + mmc3.write(&mut cart, 0xE000, 0x00); // Disable IRQ + + // Should not assert IRQ when disabled + assert!(!mmc3.is_asserting_irq()); + } +} \ No newline at end of file diff --git a/src/cpu/mappers/mod.rs b/src/cpu/mappers/mod.rs index 30a0f83..c75e7f2 100644 --- a/src/cpu/mappers/mod.rs +++ b/src/cpu/mappers/mod.rs @@ -7,6 +7,9 @@ pub mod mmc3; pub mod nrom; pub mod unrom; +#[cfg(test)] +mod mmc3_test; + pub trait Mapper: savestate::Save { fn read(&mut self, cart: &mut Cart, addr: u16) -> u8; fn write(&mut self, cart: &mut Cart, addr: u16, val: u8); diff --git a/src/ppu/mod.rs b/src/ppu/mod.rs index 933536f..7754f20 100644 --- a/src/ppu/mod.rs +++ b/src/ppu/mod.rs @@ -62,6 +62,7 @@ pub struct PPU { visible_sprites_count: u8, frame_buffer: [u8; WIDTH * HEIGHT * 3], frame_buffer_complete: Box<[u8; WIDTH * HEIGHT * 3]>, // avoid stack overflow in WASM + last_a12: bool, // Track A12 state for MMC3 IRQ timing } impl PPU { @@ -97,6 +98,7 @@ impl PPU { visible_sprites_count: 0, frame_buffer: [0; WIDTH * HEIGHT * 3], frame_buffer_complete: Box::new([0; WIDTH * HEIGHT * 3]), + last_a12: false, }; ppu.reset(); @@ -136,10 +138,6 @@ impl PPU { self.frame += 1; } } - - if self.regs.rendering_enabled() && self.cycle == 260 && self.scanline < 240 { - self.rom.mapper.step_scanline(); - } } pub fn step(&mut self) { @@ -501,6 +499,20 @@ impl PPU { } fn read_chr(&mut self, addr: u16) -> u8 { + // Track A12 toggles for MMC3 IRQ timing + // A12 is bit 12 of the address (0x1000) + // Only track A12 toggles during rendering periods + if self.regs.rendering_enabled() && (self.scanline < 240 || self.scanline == 261) { + let new_a12 = (addr & 0x1000) != 0; + + // Check if this is a rising edge (0 to 1 transition) + if new_a12 && !self.last_a12 { + self.rom.mapper.step_scanline(); + } + + self.last_a12 = new_a12; + } + self.rom.mapper.read(&mut self.rom.cart, addr) } @@ -654,6 +666,7 @@ impl savestate::Save for PPU { s.data.write_u8(self.pattern_table_low_byte); s.data.write_u8(self.pattern_table_high_byte); s.data.write_u8(self.visible_sprites_count); + s.data.write_bool(self.last_a12); s.write_all(&self.scanline_sprites); self.regs.save(s); @@ -681,6 +694,7 @@ impl savestate::Save for PPU { self.pattern_table_low_byte = s.data.read_u8()?; self.pattern_table_high_byte = s.data.read_u8()?; self.visible_sprites_count = s.data.read_u8()?; + self.last_a12 = s.data.read_bool()?; s.read_all(&mut self.scanline_sprites)?; self.regs.load(s)?; diff --git a/web/Cargo.lock b/web/Cargo.lock index 3ffd1b6..7b3e46e 100644 --- a/web/Cargo.lock +++ b/web/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "bitflags" @@ -131,6 +131,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "sha2" version = "0.10.7" @@ -173,23 +179,24 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -198,9 +205,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -208,9 +215,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -221,6 +228,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +]