From f7110dcd50da9680d79d7253c436989818c39609 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 22 Nov 2021 22:44:32 -0800 Subject: [PATCH] Emulation Fixes --- README.md | 22 +++++++++++ src/cartridge/cnrom.rs | 2 +- src/cpu/mod.rs | 84 ++++++++++++++++++++++------------------ src/cpu/opcodes.rs | 20 +++++++++- src/cpu/utility.rs | 6 +-- src/input.rs | 9 +++++ src/main.rs | 9 +++-- src/ppu/cpu_registers.rs | 14 ++++++- src/ppu/mod.rs | 11 +++++- src/ppu/rendering.rs | 8 +++- 10 files changed, 133 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index d045d88..7705fe6 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,25 @@ There are also a number of possible extensions in the replacement algorithm. Rig attempt to "lock-in" good paths and so the engine is likely to reconsider all past values. This leads to the queue of inputs growing without bound (eventually causing the application itself to be refused memory from the kernel). + +## Emulator Tests + +### CPU Timing Test + +Passess + +### Sprite Hit + +| Test Name | Current Status | +|-----------|----------------| +| 01.basics.nes| Pass | +| 02.alignment.nes| Pass| +| 03.corners.nes | Pass| +| 04.flip.nes| Pass| +| 05.left_clip.nes| Pass| +| 06.right_edge.nes| Pass| +| 07.screen_bottom.nes| Fail #4 - `y=255 should not hit`| +| 08.double height.nes| Pass | +| 09.timing basics.nes| Pass| +| 10.timing order.nes| Pass| +| 11.edge_timing.nes| Pass| \ No newline at end of file diff --git a/src/cartridge/cnrom.rs b/src/cartridge/cnrom.rs index 2e564e7..2239015 100644 --- a/src/cartridge/cnrom.rs +++ b/src/cartridge/cnrom.rs @@ -32,7 +32,7 @@ impl Mapper for Cnrom { fn write(&mut self, address: usize, value: u8) { match address { 0x8000..=0xFFFF => self.chr_bank_select = (value & 0b11) as usize, - _ => println!("bad address written to CNROM mapper: 0x{:X}", address), + _ => {}//println!("bad address written to CNROM mapper: 0x{:X}", address), } } diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 7e8f0a9..0fd49fb 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -24,21 +24,21 @@ const DECIMAL_FLAG: u8 = 1 << 3; const OVERFLOW_FLAG: u8 = 1 << 6; const NEGATIVE_FLAG: u8 = 1 << 7; -#[derive(Clone, Copy, Debug, Serialize, Deserialize, Hash)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)] pub enum Mode { - ABS, - ABX, - ABY, - ACC, - IMM, - IMP, + ABS, // Absolute + ABX, // Absolute X + ABY, // Absolute Y + ACC, // Accumulator + IMM, // Immediate + IMP, // Implied IDX, - IND, - INX, - REL, - ZPG, - ZPX, - ZPY, + IND, // Indirect + INY, + REL, // Relative + ZPG, // Zero PAge + ZPX, // Zero Page X + ZPY, // Xer Page Y } type AddressingFunction = fn(&mut Cpu) -> usize; @@ -55,7 +55,7 @@ impl Mode { Mode::IMP => (Cpu::implied, 1), Mode::IDX => (Cpu::indexed_indirect, 2), Mode::IND => (Cpu::indirect, 3), - Mode::INX => (Cpu::indirect_indexed, 2), + Mode::INY => (Cpu::indirect_indexed, 2), Mode::REL => (Cpu::relative, 2), Mode::ZPG => (Cpu::zero_page, 2), Mode::ZPX => (Cpu::zero_page_x, 2), @@ -74,6 +74,7 @@ pub struct Cpu { s: u8, // stack pointer p: u8, // status pub addresses_fetched: HashSet, + before_clock: u64, clock: u64, // number of ticks in current cycle delay: usize, // for skipping cycles during OAM DMA @@ -102,6 +103,7 @@ impl Cpu { s: 0xFD, p: 0x24, clock: 0, + before_clock:0, delay: 0, mapper: mapper, ppu: ppu, @@ -392,9 +394,9 @@ impl Cpu { Mode::ABS, Mode::ABS, /*00*/ /*10*/ Mode::REL, - Mode::INX, + Mode::INY, Mode::IMP, - Mode::INX, + Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, @@ -424,9 +426,9 @@ impl Cpu { Mode::ABS, Mode::ABS, /*20*/ /*30*/ Mode::REL, - Mode::INX, + Mode::INY, Mode::IMP, - Mode::INX, + Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, @@ -456,9 +458,9 @@ impl Cpu { Mode::ABS, Mode::ABS, /*40*/ /*50*/ Mode::REL, - Mode::INX, + Mode::INY, Mode::IMP, - Mode::INX, + Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, @@ -488,9 +490,9 @@ impl Cpu { Mode::ABS, Mode::ABS, /*60*/ /*70*/ Mode::REL, - Mode::INX, + Mode::INY, Mode::IMP, - Mode::INX, + Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, @@ -520,9 +522,9 @@ impl Cpu { Mode::ABS, Mode::ABS, /*80*/ /*90*/ Mode::REL, - Mode::INX, + Mode::INY, Mode::IMP, - Mode::INX, + Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPY, @@ -552,9 +554,9 @@ impl Cpu { Mode::ABS, Mode::ABS, /*A0*/ /*B0*/ Mode::REL, - Mode::INX, + Mode::INY, Mode::IMP, - Mode::INX, + Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPY, @@ -584,9 +586,9 @@ impl Cpu { Mode::ABS, Mode::ABS, /*C0*/ /*D0*/ Mode::REL, - Mode::INX, + Mode::INY, Mode::IMP, - Mode::INX, + Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, @@ -616,9 +618,9 @@ impl Cpu { Mode::ABS, Mode::ABS, /*E0*/ /*F0*/ Mode::REL, - Mode::INX, + Mode::INY, Mode::IMP, - Mode::INX, + Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, @@ -652,9 +654,10 @@ impl Cpu { // 1 cycle if it occurs on the second-last OAM DMA cycle. // 3 cycles if it occurs on the last OAM DMA cycle. if self.apu.dmc.cpu_stall { - self.delay = 3; // TODO: not correct + panic!(); + self.delay = 4; // TODO: not correct self.apu.dmc.cpu_stall = false; - } + }; // skip cycles from OAM DMA if necessary if self.delay > 0 { @@ -666,7 +669,7 @@ impl Cpu { // back up clock so we know how many cycles we complete let clock = self.clock; - + self.before_clock = self.clock; let opcode = ::from(self.read(self.pc)); self.addresses_fetched.insert(self.pc); // get addressing mode @@ -751,12 +754,15 @@ impl Cpu { } fn read_ppu_reg(&mut self, reg_num: usize) -> u8 { - match reg_num { + let val = match reg_num { 2 => self.ppu.read_status(), 4 => self.ppu.read_oam_data(), 7 => self.ppu.read_data(), - _ => 0, - } + 8 => self.ppu.read_oam_data(), + _ => self.ppu.recent_bits + }; + + return val } fn write_ppu_reg(&mut self, reg_num: usize, val: u8) { @@ -772,14 +778,16 @@ impl Cpu { 8 => { let page = (val as usize) << 8; let mut data = vec![]; - for i in 0..=255 { + for i in 0..256 { data.push(self.read(page + i)); } + self.ppu.write_oam_dma(data); + let is_odd = self.clock % 2 != 0; self.delay = 513 + if is_odd { 1 } else { 0 }; } - _ => panic!("wrote to bad ppu reg: {}", reg_num), + _ => print!("wrote to bad ppu reg: {}", reg_num), } } diff --git a/src/cpu/opcodes.rs b/src/cpu/opcodes.rs index d193433..39b3820 100644 --- a/src/cpu/opcodes.rs +++ b/src/cpu/opcodes.rs @@ -2,6 +2,7 @@ use super::{ Mode, CARRY_FLAG, DECIMAL_FLAG, INTERRUPT_DISABLE_FLAG, IRQ_VECTOR, NEGATIVE_FLAG, NMI_VECTOR, OVERFLOW_FLAG, ZERO_FLAG, }; +use crate::cpu::Mode::{ABS, ABY}; // TODO: check unofficial opcodes for page crosses @@ -246,6 +247,9 @@ impl super::Cpu { } pub fn jmp(&mut self, _address: usize, _mode: Mode) { + if _mode == ABS { + self.clock -=1; // this only takes 3.. + } self.pc = _address; } @@ -254,6 +258,7 @@ impl super::Cpu { let minus1 = self.pc - 1; // so m1 is the last _byte of the jsr instruction. second _byte of the operand. self.push((minus1 >> 8) as u8); self.push((minus1 & 0xFF) as u8); + self.clock+=2; //(+4 from absolute) self.pc = _address; } @@ -316,6 +321,7 @@ impl super::Cpu { self.a |= self.read(_address); self.set_zero_flag(self.a); self.set_negative_flag(self.a); + } pub fn pha(&mut self, _address: usize, _mode: Mode) { @@ -424,7 +430,7 @@ impl super::Cpu { self.plp(_address, _mode); // pull and set status reg (2 clock cycles) self.pc = self.pop() as usize; // low byte self.pc += (self.pop() as usize) << 8; // high byte - self.clock += 4; + self.clock += 2; // +2 from implied } pub fn rts(&mut self, _address: usize, _mode: Mode) { @@ -490,6 +496,18 @@ impl super::Cpu { } pub fn sta(&mut self, _address: usize, _mode: Mode) { + // PPU Test 17 + // STA, $2000,Y **must** issue a dummy read to 2007 + if _address == 0x2007 && _mode == ABY && self.y == 7 { + self.read(0x2007); + } + + if _mode == Mode::INY { + self.clock = self.before_clock+6; // Special + } else if _mode == Mode::ABY { + self.clock = self.before_clock+5; // Specia + } + self.write(_address, self.a); } diff --git a/src/cpu/utility.rs b/src/cpu/utility.rs index fd61b21..207d3a7 100644 --- a/src/cpu/utility.rs +++ b/src/cpu/utility.rs @@ -11,7 +11,7 @@ impl super::Cpu { Mode::IMP => 1, Mode::IDX => 2, Mode::IND => 3, - Mode::INX => 2, + Mode::INY => 2, Mode::REL => 2, Mode::ZPG => 2, Mode::ZPX => 2, @@ -35,13 +35,13 @@ impl super::Cpu { } pub fn address_page_cross(&mut self, old_address: usize, new_address: usize) { - if old_address / 0xFF != new_address / 0xFF { + if old_address >> 8 != new_address >> 8 { self.clock += 1; } } pub fn branch_page_cross(&mut self, old_address: usize, new_address: usize) { - if old_address / 0xFF != new_address / 0xFF { + if old_address >> 8 != new_address >> 8 { self.clock += 2; } } diff --git a/src/input.rs b/src/input.rs index 99ee331..cb03904 100644 --- a/src/input.rs +++ b/src/input.rs @@ -127,6 +127,15 @@ impl FuzzingInput { // Hacky code to parse an fm2 movie/input file into our initial fuzzing input pub fn load(fm2: &str) -> FuzzingInput { + + if fm2 == "testrom" { + return FuzzingInput{ + frames: vec![], + disable_start_after: 0, + mutated: false + } + } + let file = File::open(fm2).unwrap(); let mut frames = vec![]; let lines = io::BufReader::new(file).lines(); diff --git a/src/main.rs b/src/main.rs index 491a7f3..d3e37a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ use std::sync::{mpsc, Arc, Mutex}; use std::{fs, thread}; // The number of cpu instances to spawn.. -const NUM_THREADS: usize = 28; +const NUM_THREADS: usize = 1; // The number of frames to fuzz and process // A small number exploits the current point more at the expense of @@ -43,10 +43,10 @@ const RNG_SEED: u32 = 0x35234623; const DISABLE_START_PRESSES_AFTER: usize = 500; // The rate at which seed inputs become corrupted.. -const MUTATION_RATE: f64 = 0.25; +const MUTATION_RATE: f64 = 0.025; // The rate at which seed inputs may become soft resets.. -const MUTATION_RATE_SOFT_RESET: f64 = 0.0000; +const MUTATION_RATE_SOFT_RESET: f64 = 0.00; // The number of cases to fuzz from a given seed input at each consideration point.. const SEED_CASES: usize = 500; @@ -334,7 +334,9 @@ fn run_game( match input.console_action { ConsoleAction::None => {} ConsoleAction::SoftReset => { + println!("soft reset"); cpu.soft_reset(); + frames+=1; } } if cpu.strobe & 0x01 == 0x01 { @@ -383,6 +385,7 @@ fn play_frames(cpu: &mut Cpu, num_frames: usize, fuzzing_input: &FuzzingInput) { } if cpu.strobe & 0x01 == 0x01 { cpu.button_states = input.player_1_input; + // FIXME PLayer 2 doesn't play nicely with some games (e.g. mario) // So to enable player 2 controls you also have to uncomment the // bus in cpu/mod.rs diff --git a/src/ppu/cpu_registers.rs b/src/ppu/cpu_registers.rs index 7d1f2ee..0625978 100644 --- a/src/ppu/cpu_registers.rs +++ b/src/ppu/cpu_registers.rs @@ -48,6 +48,7 @@ impl super::Ppu { self.w = 0; self.vertical_blank = false; self.nmi_change(); + self.recent_bits = byte; byte } @@ -58,7 +59,8 @@ impl super::Ppu { // cpu reads from 0x2004, OAMDATA pub fn read_oam_data(&mut self) -> u8 { - self.primary_oam[self.oam_address] + self.recent_bits = self.primary_oam[self.oam_address]; + return self.recent_bits } // cpu writes to 0x2004, OAMDATA @@ -66,6 +68,10 @@ impl super::Ppu { // Writes will increment OAMADDR after the write self.primary_oam[self.oam_address] = val; self.oam_address += 1; + // TODO.. + if self.oam_address > 255 { + self.oam_address = 0; + } } // cpu writes to 0x2005, PPUSCROLL @@ -183,6 +189,7 @@ impl super::Ppu { // Outside of rendering, reads from or writes to $2007 will add either 1 or 32 to v depending on the VRAM increment bit set via $2000. self.v += self.address_increment; } + self.recent_bits = ret_val; ret_val } @@ -202,7 +209,10 @@ impl super::Ppu { // cpu writes to 0x4014, OAMDATA pub fn write_oam_dma(&mut self, data: Vec) { - self.primary_oam = data; + let start = self.oam_address; + for (i, v) in data.iter().enumerate() { + self.primary_oam[(start+i) % 256] = data[i]; + } } } diff --git a/src/ppu/mod.rs b/src/ppu/mod.rs index 364014c..f3ae9fd 100644 --- a/src/ppu/mod.rs +++ b/src/ppu/mod.rs @@ -67,7 +67,7 @@ pub struct Ppu { address_increment: u16, sprite_pattern_table_base: usize, background_pattern_table_base: usize, - oam_address: usize, + pub(crate) oam_address: usize, sprite_size: u8, grayscale: bool, show_background_left: bool, // 1: Show background in leftmost 8 pixels of screen, 0: Hide @@ -90,7 +90,7 @@ pub struct Ppu { read_buffer: u8, // used with PPUDATA register pub recent_bits: u8, // Least significant bits previously written into a PPU register - + pub open_bus: u8, previous_a12: u8, } @@ -104,6 +104,7 @@ impl Ppu { t: 0, x: 0, w: 0, + open_bus: 0, mapper: mapper, nametable_a: vec![0u8; 0x0400], nametable_b: vec![0u8; 0x0400], @@ -160,6 +161,7 @@ impl Ppu { } } + let mut pixel: Option<(usize, usize, (u8, u8, u8))> = None; let rendering = self.rendering(); @@ -217,6 +219,11 @@ impl Ppu { self.vertical_blank = true; self.nmi_change(); } + + if self.scanline == 261 { + self.vertical_blank = false; + } + if self.scanline == 261 && self.line_cycle == 1 { self.vertical_blank = false; self.nmi_change(); diff --git a/src/ppu/rendering.rs b/src/ppu/rendering.rs index f878abf..247aab9 100644 --- a/src/ppu/rendering.rs +++ b/src/ppu/rendering.rs @@ -106,8 +106,12 @@ impl super::Ppu { palette_address += background_pixel; // Pixel value from tile data } else if background_pixel != 0 && sprite_pixel != 0 { if self.sprite_indexes[current_sprite] == 0 { - // don't access index current_sprite. need to know which sprite we're on horizontally. - self.sprite_zero_hit = true; + if x != 255 { // Sprite 0 does not hit At x=255, for an obscure reason related to the pixel pipeline. + // don't access index current_sprite. need to know which sprite we're on horizontally. + self.sprite_zero_hit = true; + } + // TODO sprite zero should not hit when y == 255 (+1) == 0... + // } if self.sprite_attribute_latches[current_sprite] & (1 << 5) == 0 { // sprite has high priority