implemented the CMP and CPx family of instructions but these are untested

This commit is contained in:
2026-08-08 21:25:10 -05:00
parent a2ceb1dea8
commit c2fd920dae
2 changed files with 180 additions and 25 deletions
+4 -3
View File
@@ -24,8 +24,9 @@
- Batch 1 done (subroutines + stack): JSR, RTS, PHA, PLA, PHP, PLP, RTI, BRK, plus NOP and SEC as quick wins. Verified SP:FD->FB across JSR (push high-then-low works) - Batch 1 done (subroutines + stack): JSR, RTS, PHA, PLA, PHP, PLP, RTI, BRK, plus NOP and SEC as quick wins. Verified SP:FD->FB across JSR (push high-then-low works)
- Batch 2 done (branches): all 8 branches (BEQ/BNE/BCC/BCS/BPL/BMI/BVC/BVS) via one shared branch() routine with condition param. Taken = +3 cycles, page-cross = +1 more. Verified both taken and not-taken paths in the log - Batch 2 done (branches): all 8 branches (BEQ/BNE/BCC/BCS/BPL/BMI/BVC/BVS) via one shared branch() routine with condition param. Taken = +3 cycles, page-cross = +1 more. Verified both taken and not-taken paths in the log
- Flag set/clear ops done (part of Batch 10): CLC, CLI, CLV, CLD, SEI, SED (SEC done in Batch 1). SEI/SED set, the rest clear. Fixed a real bug where SEI/SED were clearing instead of setting - Flag set/clear ops done (part of Batch 10): CLC, CLI, CLV, CLD, SEI, SED (SEC done in Batch 1). SEI/SED set, the rest clear. Fixed a real bug where SEI/SED were clearing instead of setting
- Trace now matches the nestest reference log through line 37 (C780 STA, CYC:95), field-for-field (PC/A/X/Y/P/SP/CYC verified by label-comparison script). Next panic is on 0x85 (STA zp) at line 37, top of Batch 4 - Trace now matches the nestest reference log through line 38 (C782 BIT, CYC:98), field-for-field (PC/A/X/Y/P/SP/CYC verified by label-comparison script). Next panic is on 0x24 (BIT zp) at line 38, top of Batch 6
- Batch 3 done (loads): LDA (8 modes), LDX (5 modes), LDY (5 modes) via three shared cores (lda/ldx/ldy) + thin wrappers. Caught two real bugs in review: all 4 LDY wrappers called self.ldx (would write to X instead of Y), and 6 of 8 LDX/LDY wrappers had wrong base cycles (3 instead of 4). Both fixed - Batch 3 done (loads): LDA (8 modes), LDX (5 modes), LDY (5 modes) via three shared cores (lda/ldx/ldy) + thin wrappers. Caught two real bugs in review: all 4 LDY wrappers called self.ldx (would write to X instead of Y), and 6 of 8 LDX/LDY wrappers had wrong base cycles (3 instead of 4). Both fixed
- Batch 4 done (stores): STA (7 modes), STX (3 modes), STY (3 modes) via three shared cores (sta/stx/sty) + thin wrappers. No flag changes. Refactored stx_zp to use the shared stx core for consistency. Fixed a comment typo (sta_abx was labeled 0x95 instead of 0x9D)
- trace bytes are length-aware via opcode_len (fixed-width byte field, columns align for awk diff) - trace bytes are length-aware via opcode_len (fixed-width byte field, columns align for awk diff)
## Next ## Next
@@ -37,8 +38,8 @@ CPU implementation batches (by family, ordered by nestest log frequency):
- Batch 1 - DONE (see Done section) - Batch 1 - DONE (see Done section)
- Batch 2 - DONE (branches, see Done section) - Batch 2 - DONE (branches, see Done section)
- Batch 3 - DONE (loads, see Done section) - Batch 3 - DONE (loads, see Done section)
- Batch 4 - Stores: STA 7 modes, STX 3, STY 3 (share store core, no flag changes). Next up - Batch 4 - DONE (stores, see Done section)
- Batch 5 - Compares: CMP 8 modes, CPX 3, CPY 3 (set C/Z/N, no store) - Batch 5 - Compares: CMP 8 modes, CPX 3, CPY 3 (set C/Z/N, no store). Next up. Note: keeping family order on purpose, no diff progress until the log reaches these opcodes
- Batch 6 - Arithmetic: ADC 8 modes, SBC 8 modes (N/V/C/Z, decimal flag inert), BIT 2 modes - Batch 6 - Arithmetic: ADC 8 modes, SBC 8 modes (N/V/C/Z, decimal flag inert), BIT 2 modes
- Batch 7 - Logic: AND, ORA, EOR (8 modes each) - Batch 7 - Logic: AND, ORA, EOR (8 modes each)
- Batch 8 - Inc/Dec: INC/DEC 4 modes each (RMW), INX, INY, DEX, DEY - Batch 8 - Inc/Dec: INC/DEC 4 modes each (RMW), INX, INY, DEX, DEY
+176 -22
View File
@@ -152,6 +152,20 @@ impl Cpu {
0x84 => self.sty_zp(bus), 0x84 => self.sty_zp(bus),
0x94 => self.sty_zpx(bus), 0x94 => self.sty_zpx(bus),
0x8C => self.sty_abs(bus), 0x8C => self.sty_abs(bus),
0xC9 => self.cmp_imm(bus),
0xC5 => self.cmp_zp(bus),
0xD5 => self.cmp_zpx(bus),
0xCD => self.cmp_abs(bus),
0xDD => self.cmp_abx(bus),
0xD9 => self.cmp_aby(bus),
0xC1 => self.cmp_izx(bus),
0xD1 => self.cmp_izy(bus),
0xE0 => self.cpx_imm(bus),
0xE4 => self.cpx_zp(bus),
0xEC => self.cpx_abs(bus),
0xC0 => self.cpy_imm(bus),
0xC4 => self.cpy_zp(bus),
0xCC => self.cpy_abs(bus),
_ => panic!("illegal/unkown opcode {opcode:#04X} at {:#06X}", self.pc.wrapping_sub(1)), _ => panic!("illegal/unkown opcode {opcode:#04X} at {:#06X}", self.pc.wrapping_sub(1)),
}; };
self.cycles += cycles as u64; self.cycles += cycles as u64;
@@ -284,6 +298,33 @@ impl Cpu {
base + extra base + extra
} }
// shared cores for CMP and CPx instructions
fn cmp(&mut self, bus: &mut impl Bus, (address, extra): (u16, u8), base: u8) -> u8 {
let value = self.read(bus, address);
let result = self.a.wrapping_sub(value);
if self.a >= value { self.set_flag(FLAG_CARRY); }
else { self.clear_flag(FLAG_CARRY); }
self.set_zp_flags(result);
base + extra
}
fn cpx(&mut self, bus: &mut impl Bus, (address, extra): (u16, u8), base: u8) -> u8 {
let value = self.read(bus, address);
let result = self.x.wrapping_sub(value);
if self.x >= value { self.set_flag(FLAG_CARRY); }
else { self.clear_flag(FLAG_CARRY); }
self.set_zp_flags(result);
base + extra
}
fn cpy(&mut self, bus: &mut impl Bus, (address, extra): (u16, u8), base: u8) -> u8 {
let value = self.read(bus, address);
let result = self.y.wrapping_sub(value);
if self.y >= value { self.set_flag(FLAG_CARRY); }
else { self.clear_flag(FLAG_CARRY); }
self.set_zp_flags(result);
base + extra
}
// NES 6502 opcode -> functions // NES 6502 opcode -> functions
fn jmp_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x4C, 3 bytes, 3 cycles fn jmp_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x4C, 3 bytes, 3 cycles
self.pc = self.fetch16(bus); self.pc = self.fetch16(bus);
@@ -416,37 +457,37 @@ impl Cpu {
} }
// LDX alternatives // LDX alternatives
fn ldx_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0xA6, 2 bytes fn ldx_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0xA6, 2 bytes
let (addr, extra) = self.address_zp(bus); let (address, extra) = self.address_zp(bus);
self.ldx(bus, (addr, extra), 3) self.ldx(bus, (address, extra), 3)
} }
fn ldx_zpy(&mut self, bus: &mut impl Bus) -> u8 { // 0xB6, 2 bytes fn ldx_zpy(&mut self, bus: &mut impl Bus) -> u8 { // 0xB6, 2 bytes
let (addr, extra) = self.address_zpy(bus); let (address, extra) = self.address_zpy(bus);
self.ldx(bus, (addr, extra), 4) self.ldx(bus, (address, extra), 4)
} }
fn ldx_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0xAE, 3 bytes fn ldx_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0xAE, 3 bytes
let (addr, extra) = self.address_abs(bus); let (address, extra) = self.address_abs(bus);
self.ldx(bus, (addr, extra), 4) self.ldx(bus, (address, extra), 4)
} }
fn ldx_aby(&mut self, bus: &mut impl Bus) -> u8 { // 0xBE, 3 bytes fn ldx_aby(&mut self, bus: &mut impl Bus) -> u8 { // 0xBE, 3 bytes
let (addr, extra) = self.address_aby(bus); let (address, extra) = self.address_aby(bus);
self.ldx(bus, (addr, extra), 4) self.ldx(bus, (address, extra), 4)
} }
// LDY alternatives // LDY alternatives
fn ldy_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0xA4, 2 bytes fn ldy_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0xA4, 2 bytes
let (addr, extra) = self.address_zp(bus); let (address, extra) = self.address_zp(bus);
self.ldy(bus, (addr, extra), 3) self.ldy(bus, (address, extra), 3)
} }
fn ldy_zpx(&mut self, bus: &mut impl Bus) -> u8 { // 0xB4, 2 bytes fn ldy_zpx(&mut self, bus: &mut impl Bus) -> u8 { // 0xB4, 2 bytes
let (addr, extra) = self.address_zpx(bus); let (address, extra) = self.address_zpx(bus);
self.ldy(bus, (addr, extra), 4) self.ldy(bus, (address, extra), 4)
} }
fn ldy_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0xAC, 3 bytes fn ldy_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0xAC, 3 bytes
let (addr, extra) = self.address_abs(bus); let (address, extra) = self.address_abs(bus);
self.ldy(bus, (addr, extra), 4) self.ldy(bus, (address, extra), 4)
} }
fn ldy_abx(&mut self, bus: &mut impl Bus) -> u8 { // 0xBC, 3 bytes fn ldy_abx(&mut self, bus: &mut impl Bus) -> u8 { // 0xBC, 3 bytes
let (addr, extra) = self.address_abx(bus); let (address, extra) = self.address_abx(bus);
self.ldy(bus, (addr, extra), 4) self.ldy(bus, (address, extra), 4)
} }
// imm variants for LDx // imm variants for LDx
@@ -473,57 +514,156 @@ impl Cpu {
let (address, extra) = self.address_zp(bus); let (address, extra) = self.address_zp(bus);
self.sta(bus, (address, extra), 3) self.sta(bus, (address, extra), 3)
} }
fn sta_zpx(&mut self, bus: &mut impl Bus) -> u8 { // 0x95, 2 bytes, 4 cycles fn sta_zpx(&mut self, bus: &mut impl Bus) -> u8 { // 0x95, 2 bytes, 4 cycles
let (address, extra) = self.address_zpx(bus); let (address, extra) = self.address_zpx(bus);
self.sta(bus, (address, extra), 4) self.sta(bus, (address, extra), 4)
} }
fn sta_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x8D, 3 bytes, 4 cycles fn sta_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x8D, 3 bytes, 4 cycles
let (address, extra) = self.address_abs(bus); let (address, extra) = self.address_abs(bus);
self.sta(bus, (address, extra), 4) self.sta(bus, (address, extra), 4)
} }
fn sta_abx(&mut self, bus: &mut impl Bus) -> u8 { // 0x9D, 3 bytes, 4 cycles fn sta_abx(&mut self, bus: &mut impl Bus) -> u8 { // 0x9D, 3 bytes, 4 cycles
let (address, extra) = self.address_abx(bus); let (address, extra) = self.address_abx(bus);
self.sta(bus, (address, extra), 4) self.sta(bus, (address, extra), 4)
} }
fn sta_aby(&mut self, bus: &mut impl Bus) -> u8 { // 0x99, 3 bytes, 4 cycles fn sta_aby(&mut self, bus: &mut impl Bus) -> u8 { // 0x99, 3 bytes, 4 cycles
let (address, extra) = self.address_aby(bus); let (address, extra) = self.address_aby(bus);
self.sta(bus, (address, extra), 4) self.sta(bus, (address, extra), 4)
} }
fn sta_izx(&mut self, bus: &mut impl Bus) -> u8 { // 0x81, 2 bytes, 6 cycles fn sta_izx(&mut self, bus: &mut impl Bus) -> u8 { // 0x81, 2 bytes, 6 cycles
let (address, extra) = self.address_izx(bus); let (address, extra) = self.address_izx(bus);
self.sta(bus, (address, extra), 6) self.sta(bus, (address, extra), 6)
} }
fn sta_izy(&mut self, bus: &mut impl Bus) -> u8 { // 0x91, 2 bytes, 5 cycles fn sta_izy(&mut self, bus: &mut impl Bus) -> u8 { // 0x91, 2 bytes, 5 cycles
let (address, extra) = self.address_izy(bus); let (address, extra) = self.address_izy(bus);
self.sta(bus, (address, extra), 5) self.sta(bus, (address, extra), 5)
} }
// STX variants // STX variants
fn stx_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0x86, 2 bytes, 3 cycles fn stx_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0x86, 2 bytes, 3 cycles
let (address, extra) = self.address_zp(bus); let (address, extra) = self.address_zp(bus);
self.stx(bus, (address, extra), 3) self.stx(bus, (address, extra), 3)
} }
fn stx_zpy(&mut self, bus: &mut impl Bus) -> u8 { // 0x96, 2 bytes, 4 cycles fn stx_zpy(&mut self, bus: &mut impl Bus) -> u8 { // 0x96, 2 bytes, 4 cycles
let (address, extra) = self.address_zpy(bus); let (address, extra) = self.address_zpy(bus);
self.stx(bus, (address, extra), 4) self.stx(bus, (address, extra), 4)
} }
fn stx_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x8E, 3 bytes, 4 cycles fn stx_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x8E, 3 bytes, 4 cycles
let (address, extra) = self.address_abs(bus); let (address, extra) = self.address_abs(bus);
self.stx(bus, (address, extra), 4) self.stx(bus, (address, extra), 4)
} }
// STY variants // STY variants
fn sty_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0x84, 2 bytes, 3 cycles fn sty_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0x84, 2 bytes, 3 cycles
let (addr, extra) = self.address_zp(bus); let (address, extra) = self.address_zp(bus);
self.sty(bus, (addr, extra), 3) self.sty(bus, (address, extra), 3)
} }
fn sty_zpx(&mut self, bus: &mut impl Bus) -> u8 { // 0x94, 2 bytes, 4 cycles fn sty_zpx(&mut self, bus: &mut impl Bus) -> u8 { // 0x94, 2 bytes, 4 cycles
let (addr, extra) = self.address_zpx(bus); let (address, extra) = self.address_zpx(bus);
self.sty(bus, (addr, extra), 4) self.sty(bus, (address, extra), 4)
} }
fn sty_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x8C, 3 bytes, 4 cycles fn sty_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x8C, 3 bytes, 4 cycles
let (addr, extra) = self.address_abs(bus); let (address, extra) = self.address_abs(bus);
self.sty(bus, (addr, extra), 4) self.sty(bus, (address, extra), 4)
} }
// CMP variants
fn cmp_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0xC5, 2 bytes, 3 cycles
let (address, extra) = self.address_zp(bus);
self.cmp(bus, (address, extra), 3)
}
fn cmp_zpx(&mut self, bus: &mut impl Bus) -> u8 { // 0xD5, 2 bytes, 4 cycles
let (address, extra) = self.address_zpx(bus);
self.cmp(bus, (address, extra), 4)
}
fn cmp_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0xCD, 3 bytes, 4 cycles
let (address, extra) = self.address_abs(bus);
self.cmp(bus, (address, extra), 4)
}
fn cmp_abx(&mut self, bus: &mut impl Bus) -> u8 { // 0xDD, 3 bytes, 4 cycles
let (address, extra) = self.address_abx(bus);
self.cmp(bus, (address, extra), 4)
}
fn cmp_aby(&mut self, bus: &mut impl Bus) -> u8 { // 0xD9, 3 bytes, 4 cycles
let (address, extra) = self.address_aby(bus);
self.cmp(bus, (address, extra), 4)
}
fn cmp_izx(&mut self, bus: &mut impl Bus) -> u8 { // 0xC1, 2 bytes, 6 cycles
let (address, extra) = self.address_izx(bus);
self.cmp(bus, (address, extra), 6)
}
fn cmp_izy(&mut self, bus: &mut impl Bus) -> u8 { // 0xD1, 2 bytes, 5 cycles
let (address, extra) = self.address_izy(bus);
self.cmp(bus, (address, extra), 5)
}
// CPX variants
fn cpx_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0xE4, 2 bytes, 3 cycles
let (address, extra) = self.address_zp(bus);
self.cpx(bus, (address, extra), 3)
}
fn cpx_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0xEC, 3 bytes, 4 cycles
let (address, extra) = self.address_abs(bus);
self.cpx(bus, (address, extra), 4)
}
// CPY variants
fn cpy_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0xC4, 2 bytes, 3 cycles
let (address, extra) = self.address_zp(bus);
self.cpy(bus, (address, extra), 3)
}
fn cpy_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0xCC, 3 bytes, 4 cycles
let (address, extra) = self.address_abs(bus);
self.cpy(bus, (address, extra), 4)
}
// CMP / CPx immediate mode variants
fn cmp_imm(&mut self, bus: &mut impl Bus) -> u8 { // 0xC9, 2 bytes, 2 cycles
let value = self.fetch(bus);
let result = self.a.wrapping_sub(value);
if self.a >= value { self.set_flag(FLAG_CARRY); }
else { self.clear_flag(FLAG_CARRY); }
self.set_zp_flags(result);
2
}
fn cpx_imm(&mut self, bus: &mut impl Bus) -> u8 { // 0xE0, 2 bytes, 2 cycles
let value = self.fetch(bus);
let result = self.x.wrapping_sub(value);
if self.x >= value { self.set_flag(FLAG_CARRY); }
else { self.clear_flag(FLAG_CARRY); }
self.set_zp_flags(result);
2
}
fn cpy_imm(&mut self, bus: &mut impl Bus) -> u8 { // 0xC0, 2 bytes, 2 cycles
let value = self.fetch(bus);
let result = self.y.wrapping_sub(value);
if self.y >= value { self.set_flag(FLAG_CARRY); }
else { self.clear_flag(FLAG_CARRY); }
self.set_zp_flags(result);
2
}
// all branching alternatives here all 1 bytes // all branching alternatives here all 1 bytes
fn bcs_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, self.flag(FLAG_CARRY)) } // 0xB0 fn bcs_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, self.flag(FLAG_CARRY)) } // 0xB0
@@ -594,6 +734,20 @@ impl Cpu {
0x84 => 2, // STY zp 0x84 => 2, // STY zp
0x94 => 2, // STY zpx 0x94 => 2, // STY zpx
0x8C => 3, // STY abs 0x8C => 3, // STY abs
0xC9 => 2, // CMP imm
0xC5 => 2, // CMP zp
0xD5 => 2, // CMP zpx
0xCD => 3, // CMP abs
0xDD => 3, // CMP abx
0xD9 => 3, // CMP aby
0xC1 => 2, // CMP izx
0xD1 => 2, // CMP izy
0xE0 => 2, // CPX imm
0xE4 => 2, // CPX zp
0xEC => 3, // CPX abs
0xC0 => 2, // CPY imm
0xC4 => 2, // CPY zp
0xCC => 3, // CPY abs
_ => 1, // fallback, would show the invalid/unknown opcode only not its parameters _ => 1, // fallback, would show the invalid/unknown opcode only not its parameters
} }
} }