diff --git a/src/lib.rs b/src/lib.rs index 1014f82..c362941 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,6 +182,12 @@ pub enum ClockState { count: u8, addr: u16, }, + Oops { + instruction: u8, + ops: [u8; 5], + count: u8, + addr: u16, + }, HoldNmi { cycles: u8, }, @@ -224,6 +230,17 @@ impl std::fmt::Debug for ClockState { ClockState::HoldIrq { cycles } => { f.debug_struct("HoldIrq").field("cycles", cycles).finish() } + ClockState::Oops { + instruction, + ops, + count, + addr, + } => f + .debug_struct("Oops") + .field("instruction", instruction) + .field("addr", addr) + .field("ops", &&ops[..*count as usize]) + .finish(), } } } @@ -232,6 +249,7 @@ enum ExecState { Done, MoreParams, Hold(u8), + Oops, } pub enum CPUMMRegisters { @@ -436,7 +454,14 @@ impl NES { } /// Returns true if more bytes are needed - fn exec_instruction(&mut self, ins: u8, mut params: &[u8], held: bool, addr: u16) -> ExecState { + fn exec_instruction( + &mut self, + ins: u8, + mut params: &[u8], + held: bool, + oops: bool, + addr: u16, + ) -> ExecState { macro_rules! inst { ($val:expr, $hold:expr, |$($name:pat_param),*| $eval:expr) => {{ let hold_time: u8 = ($hold).into(); @@ -588,6 +613,7 @@ impl NES { ); }), 0xBD => inst!("LDA abs,x", 1, |low, high| { + if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } self.cpu.a = self.read_abs_x(low, high); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -599,6 +625,7 @@ impl NES { ); }), 0xB9 => inst!("LDA abs,y", 1, |low, high| { + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } self.cpu.a = self.read_abs_y(low, high); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -620,6 +647,7 @@ impl NES { 0xB1 => inst!("LDA (ind),y", 3, |off| { let low = self.read_abs(off, 0); let high = self.read_abs(off.wrapping_add(1), 0); + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } self.cpu.a = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -655,6 +683,7 @@ impl NES { ); }), 0xBE => inst!("LDX abs,y", 1, |low, high| { + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } self.cpu.x = self.read_abs_y(low, high); self.cpu.status.set_zero(self.cpu.x == 0); self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); @@ -695,6 +724,7 @@ impl NES { ); }), 0xBC => inst!("LDX abs,x", 1, |low, high| { + if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } self.cpu.y = self.read_abs_x(low, high); self.cpu.status.set_zero(self.cpu.y == 0); self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); @@ -956,6 +986,7 @@ impl NES { ); }), 0xDD => inst!("CMP abs,x", 1, |low, high| { + if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } let val = self.read_abs_x(low, high); let v = self.cpu.a.wrapping_sub(val); self.cpu.status.set_zero(v == 0); @@ -971,6 +1002,7 @@ impl NES { ); }), 0xD9 => inst!("CMP abs,y", 1, |low, high| { + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } let val = self.read_abs_y(low, high); let v = self.cpu.a.wrapping_sub(val); self.cpu.status.set_zero(v == 0); @@ -1003,9 +1035,9 @@ impl NES { ); }), 0xD1 => inst!("CMP (ind),y", 3, |off| { - // TODO: iymode let low = self.read_abs(off, 0); let high = self.read_abs(off.wrapping_add(1), 0); + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } let val = self.read_abs_y(low, high); let v = self.cpu.a.wrapping_sub(val); self.cpu.status.set_zero(v == 0); @@ -1163,6 +1195,7 @@ impl NES { ); }), 0x7D => inst!("ADC abs,x", 1, |low, high| { + if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } let val = self.read_abs_x(low, high); let (a, carry_1) = self.cpu.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); @@ -1181,6 +1214,7 @@ impl NES { ); }), 0x79 => inst!("ADC abs,y", 1, |low, high| { + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } let val = self.read_abs_y(low, high); let (a, carry_1) = self.cpu.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); @@ -1221,6 +1255,7 @@ impl NES { 0x71 => inst!("ADC (ind),y", 3, |off| { let low = self.read_abs(off, 0); let high = self.read_abs(off.wrapping_add(1), 0); + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); let (a, carry_1) = self.cpu.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); @@ -1295,6 +1330,7 @@ impl NES { ); }), 0xFD => inst!("SBC abs,x", 1, |low, high| { + if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } let val = self.read_abs_x(low, high); let (a, carry_1) = self.cpu.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); @@ -1313,6 +1349,7 @@ impl NES { ); }), 0xF9 => inst!("SBC abs,y", 1, |low, high| { + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } let val = self.read_abs_y(low, high); let (a, carry_1) = self.cpu.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); @@ -1353,6 +1390,7 @@ impl NES { 0xF1 => inst!("SBC (ind),y", 3, |off| { let low = self.read_abs(off, 0); let high = self.read_abs(off.wrapping_add(1), 0); + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); let (a, carry_1) = self.cpu.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); @@ -1502,7 +1540,7 @@ impl NES { ); }), 0x1D => inst!("ORA abs,x", 1, |low, high| { - // TODO: page crossing + if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } self.cpu.a |= self.read_abs_x(low, high); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -1514,6 +1552,7 @@ impl NES { ); }), 0x19 => inst!("ORA abs,y", 1, |low, high| { + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } self.cpu.a |= self.read_abs_y(low, high); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -1540,6 +1579,10 @@ impl NES { 0x11 => inst!("ORA (ind),y", 3, |off| { let low = self.read_abs(off, 0); let high = self.read_abs(off.wrapping_add(1), 0); + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } + if !oops && low.checked_add(self.cpu.y).is_none() { + return ExecState::Oops; + } self.cpu.a |= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -1580,6 +1623,7 @@ impl NES { ); }), 0x3D => inst!("AND abs,x", 1, |low, high| { + if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } self.cpu.a &= self.read_abs_x(low, high); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -1591,6 +1635,7 @@ impl NES { ); }), 0x39 => inst!("AND abs,y", 1, |low, high| { + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } self.cpu.a &= self.read_abs_y(low, high); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -1617,6 +1662,7 @@ impl NES { 0x31 => inst!("AND (ind),y", 3, |off| { let low = self.read_abs(off, 0); let high = self.read_abs(off.wrapping_add(1), 0); + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } self.cpu.a &= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -1657,6 +1703,7 @@ impl NES { ); }), 0x5D => inst!("EOR abs,x", 1, |low, high| { + if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } self.cpu.a ^= self.read_abs_x(low, high); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -1668,6 +1715,7 @@ impl NES { ); }), 0x59 => inst!("EOR abs,y", 1, |low, high| { + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } self.cpu.a ^= self.read_abs_y(low, high); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -1694,6 +1742,7 @@ impl NES { 0x51 => inst!("EOR (ind),y", 3, |off| { let low = self.read_abs(off, 0); let high = self.read_abs(off.wrapping_add(1), 0); + if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } self.cpu.a ^= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); self.cpu.status.set_zero(self.cpu.a == 0); self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); @@ -1973,39 +2022,32 @@ impl NES { } } ClockState::ReadInstruction => { - // if self.cpu.nmi_pending { - // self.cpu.nmi_pending = false; - // writeln!(self.debug_log, "NMI detected").unwrap(); - // ClockState::HoldNmi { cycles: 5 } - // } else if !self.cpu.status.interrupt_disable() - // && (self.ppu.irq_waiting() || self.apu.irq_waiting()) - // { - // // TODO: handle proper irq detection - // writeln!(self.debug_log, "IRQ detected").unwrap(); - // ClockState::HoldIrq { cycles: 6 } - // } else - { - let addr = self.cpu.pc; - let instruction = self.read(self.cpu.pc); - if instruction != 0x02 { - self.cpu.pc = self.cpu.pc.wrapping_add(1); - } - match self.exec_instruction(instruction, &[], false, addr) { - ExecState::Done => ClockState::ReadInstruction, - ExecState::MoreParams => ClockState::ReadOperands { - instruction, - ops: [0u8; 5], - count: 0, - addr, - }, - ExecState::Hold(cycles) => ClockState::Hold { - cycles, - instruction, - ops: [0u8; 5], - count: 0, - addr, - }, - } + let addr = self.cpu.pc; + let instruction = self.read(self.cpu.pc); + if instruction != 0x02 { + self.cpu.pc = self.cpu.pc.wrapping_add(1); + } + match self.exec_instruction(instruction, &[], false, false, addr) { + ExecState::Done => ClockState::ReadInstruction, + ExecState::MoreParams => ClockState::ReadOperands { + instruction, + ops: [0u8; 5], + count: 0, + addr, + }, + ExecState::Hold(cycles) => ClockState::Hold { + cycles, + instruction, + ops: [0u8; 5], + count: 0, + addr, + }, + ExecState::Oops => ClockState::Oops { + instruction, + ops: [0u8; 5], + count: 0, + addr, + }, } } ClockState::ReadOperands { @@ -2019,7 +2061,13 @@ impl NES { } ops[count as usize] = self.read(self.cpu.pc); self.cpu.pc = self.cpu.pc.wrapping_add(1); - match self.exec_instruction(instruction, &ops[..count as usize + 1], false, addr) { + match self.exec_instruction( + instruction, + &ops[..count as usize + 1], + false, + false, + addr, + ) { ExecState::Done => ClockState::ReadInstruction, ExecState::MoreParams => ClockState::ReadOperands { instruction, @@ -2034,6 +2082,12 @@ impl NES { count: count + 1, addr, }, + ExecState::Oops => ClockState::Oops { + instruction, + ops, + count: count + 1, + addr, + }, } } ClockState::Hold { @@ -2044,15 +2098,24 @@ impl NES { addr, } => { if cycles == 0 { - match self.exec_instruction(instruction, &ops[..count as usize], true, addr) { + match self.exec_instruction( + instruction, + &ops[..count as usize], + true, + false, + addr, + ) { ExecState::Done => ClockState::ReadInstruction, - ExecState::MoreParams => ClockState::ReadOperands { + ExecState::MoreParams => { + panic!("Should never return MoreParams after holding") + } + ExecState::Hold(_) => panic!("Should never return Hold after holding"), + ExecState::Oops => ClockState::Oops { instruction, ops, - count: count + 1, + count, addr, }, - ExecState::Hold(_) => panic!("Should never return Hold after holding"), } } else { ClockState::Hold { @@ -2064,6 +2127,17 @@ impl NES { } } } + ClockState::Oops { + instruction, + ops, + count, + addr, + } => { + match self.exec_instruction(instruction, &ops[..count as usize], true, true, addr) { + ExecState::Done => ClockState::ReadInstruction, + _ => panic!("Must execute after oops"), + } + } }; if self.cpu.clock_state == ClockState::ReadInstruction { if self.cpu.nmi_pending { diff --git a/src/main.rs b/src/main.rs index 66ce7f0..b3d80cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,8 +26,8 @@ use tracing_subscriber::EnvFilter; // const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_red.nes"); // const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_name_table.nes"); // const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "int_nmi_exit_timing.nes"); -const ROM_FILE: &str = "./Super Mario Bros. (World).nes"; -// const ROM_FILE: &str = "./cpu_timing_test.nes"; +// const ROM_FILE: &str = "./Super Mario Bros. (World).nes"; +const ROM_FILE: &str = "./cpu_timing_test.nes"; extern crate nes_emu;