Fix 'oops cycle' timing, to pass timing test rom
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 25s

This commit is contained in:
2026-01-20 00:14:17 -06:00
parent 2e5e2ed1e7
commit b5e1d1a4c3
2 changed files with 117 additions and 43 deletions

View File

@@ -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,24 +2022,12 @@ 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) {
match self.exec_instruction(instruction, &[], false, false, addr) {
ExecState::Done => ClockState::ReadInstruction,
ExecState::MoreParams => ClockState::ReadOperands {
instruction,
@@ -2005,7 +2042,12 @@ impl NES {
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 {

View File

@@ -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;