From 3e598ec9e62e8699314e52ec2c157580078efca2 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Thu, 16 Jan 2020 20:57:17 -0600 Subject: [PATCH] fixed bug with mmc1, working on mmc3 --- src/cartridge/cnrom.rs | 7 +- src/cartridge/mmc1.rs | 29 +++--- src/cartridge/mmc3.rs | 214 ++++++++++++++++++++++++++++++----------- src/cartridge/mod.rs | 8 +- src/cartridge/nrom.rs | 2 + src/cartridge/uxrom.rs | 2 + src/cpu/mod.rs | 7 +- src/ppu/mod.rs | 21 ++++ 8 files changed, 215 insertions(+), 75 deletions(-) diff --git a/src/cartridge/cnrom.rs b/src/cartridge/cnrom.rs index 99d7cb8..9e0f165 100644 --- a/src/cartridge/cnrom.rs +++ b/src/cartridge/cnrom.rs @@ -36,13 +36,10 @@ impl Mapper for Cnrom { fn get_mirroring(&mut self) -> Mirror { self.cart.mirroring - // if self.cart.four_screen_vram { - // Mirror::FourScreen - // } else { - // self.cart.mirroring - // } } fn load_battery_backed_ram(&mut self) {} fn save_battery_backed_ram(&self) {} + fn clock(&mut self) {} + fn check_irq(&mut self) -> bool {false} } diff --git a/src/cartridge/mmc1.rs b/src/cartridge/mmc1.rs index dde05d1..cbb07c0 100644 --- a/src/cartridge/mmc1.rs +++ b/src/cartridge/mmc1.rs @@ -113,18 +113,22 @@ impl Mmc1 { impl Mapper for Mmc1 { fn read(&mut self, address: usize) -> u8 { match address { - 0x0000..=0x0FFF => { - if self.cart.chr_rom_size > self.chr_low_bank { - self.cart.chr_rom[self.chr_low_bank][address % 0x1000] + 0x0000..=0x1FFF => { + let offset = address % 0x1000; + if self.chr_bank_mode { + // if 4K bank mode, $0000-$0FFF will be a 4K-indexed section of some CHR-ROM chunk referred to by chr_low_bank + // and $1000-$1FFF will be the one referred to by chr_high_bank + let bank = match address { + 0x0000..=0x0FFF => self.chr_low_bank, + 0x1000..=0x1FFF => self.chr_high_bank, + _ => panic!("bad address read from MMC1: 0x{:X}", address), + }; + let chunk_num = bank / 2; + let chunk_half = if bank % 2 == 0 {0x0} else {0x1000}; + self.cart.chr_rom[chunk_num][chunk_half + offset] } else { - self.chr_ram_bank[address] - } - }, - 0x1000..=0x1FFF => { - if self.cart.chr_rom_size > self.chr_high_bank { - self.cart.chr_rom[self.chr_high_bank][address % 0x1000] - } else { - self.chr_ram_bank[address] + // if we're in 8K bank mode, the whole $0000-$1FFF region will be the 8K range referred to by chr_low_bank + self.cart.chr_rom[self.chr_low_bank][address] } }, 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000], @@ -197,4 +201,7 @@ impl Mapper for Mmc1 { .expect("could not create output file for battery-backed RAM"); f.write_all(&self.prg_ram_bank).expect("could not write battery-backed RAM to file"); } + + fn clock(&mut self) {} + fn check_irq(&mut self) -> bool {false} } diff --git a/src/cartridge/mmc3.rs b/src/cartridge/mmc3.rs index 388dc8b..bb6a1dc 100644 --- a/src/cartridge/mmc3.rs +++ b/src/cartridge/mmc3.rs @@ -2,95 +2,199 @@ use super::{Cartridge, Mapper, Mirror}; pub struct Mmc3 { cart: Cartridge, + mirroring: Mirror, - reg0: u8, - reg1: u8, - reg2: u8, - reg3: u8, - reg4: u8, - reg5: u8, - reg6: u8, - reg7: u8, + bank_registers: Vec, + next_bank: u8, - irq_counter: u8, irq_latch: u8, + irq_counter: u8, + irq_enable: bool, + trigger_irq: bool, // signal to send to CPU + + prg_ram_bank: Vec, // CPU $6000-$7FFF + // 0: $8000-$9FFF swappable, $C000-$DFFF fixed to second-last bank + // 1: $C000-$DFFF swappable, $8000-$9FFF fixed to second-last bank + prg_rom_bank_mode: bool, + // 0: two 2 KB banks at $0000-$0FFF, four 1 KB banks at $1000-$1FFF + // 1: two 2 KB banks at $1000-$1FFF, four 1 KB banks at $0000-$0FFF + chr_rom_bank_mode: bool, } impl Mmc3 { pub fn new(cart: Cartridge) -> Self { + let m = cart.mirroring; + // let num_banks = cart.prg_rom_size Mmc3{ cart: cart, - - reg0: 0, - reg1: 0, - reg2: 0, - reg3: 0, - reg4: 0, - reg5: 0, - reg6: 0, - reg7: 0, + mirroring: m, + bank_registers: vec![0, 0, 0, 0, 0, 0, 0, 0], + next_bank: 0, + irq_latch: 0, + irq_counter: 0, + irq_enable: false, + trigger_irq: false, + prg_ram_bank: vec![0; 0x2000], + prg_rom_bank_mode: false, + chr_rom_bank_mode: false, } } -} -impl Mmc3 { + fn bank_select(&mut self, value: u8) { + self.next_bank = value & 0b111; + // ?? = value & (1<<5); // Nothing on the MMC3, see MMC6 + self.prg_rom_bank_mode = value & (1<<6) != 0; + self.chr_rom_bank_mode = value & (1<<7) != 0; + } + fn bank_data(&mut self, value: u8) { + // R6 and R7 will ignore the top two bits, as the MMC3 has only 6 PRG ROM address lines. + // R0 and R1 ignore the bottom bit, as the value written still counts banks in 1KB units but odd numbered banks can't be selected. + self.bank_registers[self.next_bank as usize] = match self.next_bank { + 0 | 1 => value & 0b0011_1111, + 6 | 7 => value & 0b1111_1110, + _ => value, + } as usize; + } + + fn prg_ram_protect(&mut self) {} } impl Mapper for Mmc3 { fn read(&mut self, address: usize) -> u8 { - match address { - 0x0000..=0x07FF => , - 0x0800..=0x0FFF => , - 0x1000..=0x13FF => , - 0x1400..=0x17FF => , - 0x1800..=0x1BFF => , - 0x1C00..=0x1FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , - } + let val = match address { + 0x0000..=0x1FFF => { // reading from CHR-ROM + let offset_1k = address % 0x400; + let offset_2k = address % 0x800; + let bank_reg_num = match self.chr_rom_bank_mode { + // look at the value in the bank reg, then depending whether that's a + true => { + match address { + 0x0000..=0x03FF => 2, + 0x0400..=0x07FF => 3, + 0x0800..=0x0BFF => 4, + 0x0C00..=0x0FFF => 5, + 0x1000..=0x17FF => 0, + 0x1800..=0x1FFF => 1, + _ => panic!("oh no"), + } + }, + false => { + match address { + 0x0000..=0x07FF => 0, + 0x0800..=0x0FFF => 1, + 0x1000..=0x13FF => 2, + 0x1400..=0x17FF => 3, + 0x1800..=0x1BFF => 4, + 0x1C00..=0x1FFF => 5, + _ => panic!("oh no"), + } + }, + }; + let bank_num = self.bank_registers[bank_reg_num]; + if bank_reg_num == 0 || bank_reg_num == 1 { // dealing with 2K banks of 8K chunks + let chunk_num = bank_num / 4; + let chunk_quarter = (bank_num % 4) * 0x800; + self.cart.chr_rom[chunk_num][chunk_quarter + offset_2k] + } else { // dealing with 1K banks of 8K chunks + let chunk_num = bank_num / 8; + let chunk_eighth = (bank_num % 8) * 0x400; + self.cart.chr_rom[chunk_num][chunk_eighth + offset_1k] + } + }, + + 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000], // PRG-RAM + + 0x8000..=0xFFFF => { // reading from PRG ROM, dealing with 8K banks of 16K chunks + let offset_8k = address % 0x2000; + let num_banks = self.cart.prg_rom_size * 2; + let bank_num = match self.chr_rom_bank_mode { + true => { + match address { + 0x8000..=0x9FFF => num_banks - 2, + 0xA000..=0xBFFF => self.bank_registers[7], + 0xC000..=0xDFFF => self.bank_registers[6], + 0xE000..=0xFFFF => num_banks - 1, + _ => panic!("oh no"), + } + }, + false => { + match address { + 0x8000..=0x9FFF => self.bank_registers[6], + 0xA000..=0xBFFF => self.bank_registers[7], + 0xC000..=0xDFFF => num_banks - 2, + 0xE000..=0xFFFF => num_banks - 1, + _ => panic!("oh no"), + } + }, + }; + let chunk_num = bank_num / 2; + let chunk_half = (bank_num % 2) * 0x1000; + self.cart.prg_rom[chunk_num][chunk_half + offset_8k] + + }, + _ => { + println!("bad address read from MMC3: 0x{:X}", address); + 0 + }, + }; + println!("reading 0x{:X} from 0x{:X}", val, address); + val } fn write(&mut self, address: usize, value: u8) { match address % 2 == 0 { true => { // even match address { - 0x8000..=0x9FFF => self.bank_select(address), + 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, // PRG-RAM + 0x8000..=0x9FFF => self.bank_select(value), 0xA000..=0xBFFF => self.mirroring = if value & 1 == 0 {Mirror::Vertical} else {Mirror::Horizontal}, - 0xC000..=0xDFFF => , - 0x1400..=0x17FF => , - 0x1800..=0x1BFF => , - 0x1C00..=0x1FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , + 0xC000..=0xDFFF => self.irq_latch = value, + 0xE000..=0xFFFF => self.irq_enable = false, + _ => panic!("oh no"), } }, false => { // odd match address { + 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, // PRG-RAM 0x8000..=0x9FFF => self.bank_data(value), - 0xA000..=0xBFFF => self.prg_ram_protect(value), - 0xC000..=0xDFFF => , - 0x1400..=0x17FF => , - 0x1800..=0x1BFF => , - 0x1C00..=0x1FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , - 0x6000..=0x7FFF => , + 0xA000..=0xBFFF => self.prg_ram_protect(), + 0xC000..=0xDFFF => self.irq_counter = 0, // Writing any value to this register reloads the MMC3 IRQ counter at the NEXT rising edge of the PPU address, presumably at PPU cycle 260 of the current scanline. + 0xE000..=0xFFFF => self.irq_enable = true, + _ => panic!("oh no"), } }, + } } - fn get_mirroring(&mut self) -> Mirror {} + fn get_mirroring(&mut self) -> Mirror { + if self.cart.four_screen_vram { + Mirror::FourScreen + } else { + self.mirroring + } + } fn load_battery_backed_ram(&mut self) {} - fn save_battery_backed_ram(&self) {} + + fn clock(&mut self) { + if self.irq_counter == 0 { + self.irq_counter = self.irq_latch; + } else { + self.irq_counter -= 1; + } + if self.irq_counter == 0 && self.irq_enable { + self.trigger_irq = true; + } + } + + fn check_irq(&mut self) -> bool { + if self.trigger_irq { + self.trigger_irq = false; + true + } else { + false + } + } } diff --git a/src/cartridge/mod.rs b/src/cartridge/mod.rs index d8ad17f..9020bff 100644 --- a/src/cartridge/mod.rs +++ b/src/cartridge/mod.rs @@ -2,13 +2,13 @@ mod nrom; mod mmc1; mod uxrom; mod cnrom; -// mod mmc3; +mod mmc3; use nrom::Nrom; use mmc1::Mmc1; use uxrom::Uxrom; use cnrom::Cnrom; -// use mmc3::Mmc3; +use mmc3::Mmc3; use std::cell::RefCell; use std::rc::Rc; @@ -20,6 +20,8 @@ pub trait Mapper { fn get_mirroring(&mut self) -> Mirror; fn load_battery_backed_ram(&mut self); fn save_battery_backed_ram(&self); + fn clock(&mut self); + fn check_irq(&mut self) -> bool; } #[derive(Copy, Clone, Debug, PartialEq)] @@ -39,7 +41,7 @@ pub fn get_mapper() -> Rc> { 1 => Rc::new(RefCell::new(Mmc1::new(cart))), 2 => Rc::new(RefCell::new(Uxrom::new(cart))), 3 => Rc::new(RefCell::new(Cnrom::new(cart))), - // 4 => Rc::new(RefCell::new(Mmc3::new(cart))), + 4 => Rc::new(RefCell::new(Mmc3::new(cart))), _ => panic!("unimplemented mapper: {}", num), } } diff --git a/src/cartridge/nrom.rs b/src/cartridge/nrom.rs index 4cbc6c6..a32d894 100644 --- a/src/cartridge/nrom.rs +++ b/src/cartridge/nrom.rs @@ -57,4 +57,6 @@ impl Mapper for Nrom { fn load_battery_backed_ram(&mut self) {} fn save_battery_backed_ram(&self) {} + fn clock(&mut self) {} + fn check_irq(&mut self) -> bool {false} } diff --git a/src/cartridge/uxrom.rs b/src/cartridge/uxrom.rs index dc62e15..e410a06 100644 --- a/src/cartridge/uxrom.rs +++ b/src/cartridge/uxrom.rs @@ -50,4 +50,6 @@ impl Mapper for Uxrom { fn load_battery_backed_ram(&mut self) {} fn save_battery_backed_ram(&self) {} + fn clock(&mut self) {} + fn check_irq(&mut self) -> bool {false} } diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 8ac0ac8..c7d1327 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -144,15 +144,20 @@ impl Cpu { return 1; } - // handle interrupts + // handle interrupts from ppu if self.ppu.trigger_nmi { self.nmi(); } self.ppu.trigger_nmi = false; + // and apu if self.apu.trigger_irq && (self.P & INTERRUPT_DISABLE_FLAG == 0) { self.irq(); } self.apu.trigger_irq = false; + // and mapper MMC3 + if self.mapper.borrow_mut().check_irq() { + self.irq(); + } // back up clock so we know how many cycles we complete let clock = self.clock; diff --git a/src/ppu/mod.rs b/src/ppu/mod.rs index 3e92f1b..5d6e569 100644 --- a/src/ppu/mod.rs +++ b/src/ppu/mod.rs @@ -241,6 +241,27 @@ impl Ppu { } else { self.line_cycle += 1; } + + // deal with mapper MMC3 + if self.rendering() && ( + self.line_cycle == 260 + && self.sprite_size == 8 + && self.background_pattern_table_base == 0x0000 + && self.sprite_pattern_table_base == 0x1000 + ) || ( + self.line_cycle == 324 + && self.sprite_size == 8 + && self.background_pattern_table_base == 0x1000 + && self.sprite_pattern_table_base == 0x0000 + ) || ( + self.line_cycle == 260 + && self.sprite_size == 16 + // TODO: figure out exact conditions here + ) + { + self.mapper.borrow_mut().clock() + } + (pixel, end_of_frame) } }