added the STx family of instructions
This commit is contained in:
@@ -24,7 +24,8 @@
|
|||||||
- 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 22 (C754 A9, CYC:61), field-for-field (PC/A/X/Y/P/SP/CYC verified by label-comparison script). Next panic is on A9 (LDA immediate) at line 22, top of Batch 3
|
- 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
|
||||||
|
- 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
|
||||||
- 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
|
||||||
@@ -35,8 +36,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 - Loads: LDA 8 modes, LDX 5 modes, LDY 5 modes (share load + set_zp_flags). Next up
|
- Batch 3 - DONE (loads, see Done section)
|
||||||
- Batch 4 - Stores: STA 7 modes, STX 3, STY 3 (share store core, no flag changes)
|
- Batch 4 - Stores: STA 7 modes, STX 3, STY 3 (share store core, no flag changes). Next up
|
||||||
- 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)
|
||||||
- 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)
|
||||||
|
|||||||
@@ -34,4 +34,5 @@ C774 D0 03 A:00 X:00 Y:00 P:26 SP:FB CYC:86
|
|||||||
C776 4C 7D C7 A:00 X:00 Y:00 P:26 SP:FB CYC:88
|
C776 4C 7D C7 A:00 X:00 Y:00 P:26 SP:FB CYC:88
|
||||||
C77D EA A:00 X:00 Y:00 P:26 SP:FB CYC:91
|
C77D EA A:00 X:00 Y:00 P:26 SP:FB CYC:91
|
||||||
C77E A9 FF A:00 X:00 Y:00 P:26 SP:FB CYC:93
|
C77E A9 FF A:00 X:00 Y:00 P:26 SP:FB CYC:93
|
||||||
C780 85 A:FF X:00 Y:00 P:A4 SP:FB CYC:95
|
C780 85 01 A:FF X:00 Y:00 P:A4 SP:FB CYC:95
|
||||||
|
C782 24 A:FF X:00 Y:00 P:A4 SP:FB CYC:98
|
||||||
|
|||||||
+108
-17
@@ -131,15 +131,27 @@ impl Cpu {
|
|||||||
0xB9 => self.lda_aby(bus),
|
0xB9 => self.lda_aby(bus),
|
||||||
0xA1 => self.lda_izx(bus),
|
0xA1 => self.lda_izx(bus),
|
||||||
0xB1 => self.lda_izy(bus),
|
0xB1 => self.lda_izy(bus),
|
||||||
0xA6 => self.ldx_zp(bus), // LDX zp
|
0xA6 => self.ldx_zp(bus),
|
||||||
0xB6 => self.ldx_zpy(bus), // LDX zpy
|
0xB6 => self.ldx_zpy(bus),
|
||||||
0xAE => self.ldx_abs(bus), // LDX abs
|
0xAE => self.ldx_abs(bus),
|
||||||
0xBE => self.ldx_aby(bus), // LDX aby
|
0xBE => self.ldx_aby(bus),
|
||||||
0xA0 => self.ldy_imm(bus), // LDY imm
|
0xA0 => self.ldy_imm(bus),
|
||||||
0xA4 => self.ldy_zp(bus), // LDY zp
|
0xA4 => self.ldy_zp(bus),
|
||||||
0xB4 => self.ldy_zpx(bus), // LDY zpx
|
0xB4 => self.ldy_zpx(bus),
|
||||||
0xAC => self.ldy_abs(bus), // LDY abs
|
0xAC => self.ldy_abs(bus),
|
||||||
0xBC => self.ldy_abx(bus), // LDY abx
|
0xBC => self.ldy_abx(bus),
|
||||||
|
0x85 => self.sta_zp(bus),
|
||||||
|
0x95 => self.sta_zpx(bus),
|
||||||
|
0x8D => self.sta_abs(bus),
|
||||||
|
0x9D => self.sta_abx(bus),
|
||||||
|
0x99 => self.sta_aby(bus),
|
||||||
|
0x81 => self.sta_izx(bus),
|
||||||
|
0x91 => self.sta_izy(bus),
|
||||||
|
0x96 => self.stx_zpy(bus),
|
||||||
|
0x8E => self.stx_abs(bus),
|
||||||
|
0x84 => self.sty_zp(bus),
|
||||||
|
0x94 => self.sty_zpx(bus),
|
||||||
|
0x8C => self.sty_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;
|
||||||
@@ -256,16 +268,26 @@ impl Cpu {
|
|||||||
base + extra
|
base + extra
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared cores for STx instructions
|
||||||
|
fn sta(&mut self, bus: &mut impl Bus, (address, extra): (u16, u8), base: u8) -> u8 {
|
||||||
|
self.write(bus, address, self.a);
|
||||||
|
base + extra
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stx(&mut self, bus: &mut impl Bus, (address, extra): (u16, u8), base: u8) -> u8 {
|
||||||
|
self.write(bus, address, self.x);
|
||||||
|
base + extra
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sty(&mut self, bus: &mut impl Bus, (address, extra): (u16, u8), base: u8) -> u8 {
|
||||||
|
self.write(bus, address, self.y);
|
||||||
|
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);
|
||||||
3
|
3
|
||||||
}
|
|
||||||
|
|
||||||
fn stx_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0x86, 2 bytes, 3 cycles
|
|
||||||
let (addr, _) = self.address_zp(bus);
|
|
||||||
self.write(bus, addr, self.x);
|
|
||||||
3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn jsr_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x20, 3 bytes, 6 cycles
|
fn jsr_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x20, 3 bytes, 6 cycles
|
||||||
@@ -427,7 +449,7 @@ impl Cpu {
|
|||||||
self.ldy(bus, (addr, extra), 4)
|
self.ldy(bus, (addr, extra), 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// imm variants
|
// imm variants for LDx
|
||||||
fn lda_imm(&mut self, bus: &mut impl Bus) -> u8 { // 0xA9, 2 bytes, 2 cycles
|
fn lda_imm(&mut self, bus: &mut impl Bus) -> u8 { // 0xA9, 2 bytes, 2 cycles
|
||||||
self.a = self.fetch(bus);
|
self.a = self.fetch(bus);
|
||||||
self.set_zp_flags(self.a);
|
self.set_zp_flags(self.a);
|
||||||
@@ -446,6 +468,63 @@ impl Cpu {
|
|||||||
2
|
2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// STA variants
|
||||||
|
fn sta_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0x85, 2 bytes, 3 cycles
|
||||||
|
let (address, extra) = self.address_zp(bus);
|
||||||
|
self.sta(bus, (address, extra), 3)
|
||||||
|
}
|
||||||
|
fn sta_zpx(&mut self, bus: &mut impl Bus) -> u8 { // 0x95, 2 bytes, 4 cycles
|
||||||
|
let (address, extra) = self.address_zpx(bus);
|
||||||
|
self.sta(bus, (address, extra), 4)
|
||||||
|
}
|
||||||
|
fn sta_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x8D, 3 bytes, 4 cycles
|
||||||
|
let (address, extra) = self.address_abs(bus);
|
||||||
|
self.sta(bus, (address, extra), 4)
|
||||||
|
}
|
||||||
|
fn sta_abx(&mut self, bus: &mut impl Bus) -> u8 { // 0x9D, 3 bytes, 4 cycles
|
||||||
|
let (address, extra) = self.address_abx(bus);
|
||||||
|
self.sta(bus, (address, extra), 4)
|
||||||
|
}
|
||||||
|
fn sta_aby(&mut self, bus: &mut impl Bus) -> u8 { // 0x99, 3 bytes, 4 cycles
|
||||||
|
let (address, extra) = self.address_aby(bus);
|
||||||
|
self.sta(bus, (address, extra), 4)
|
||||||
|
}
|
||||||
|
fn sta_izx(&mut self, bus: &mut impl Bus) -> u8 { // 0x81, 2 bytes, 6 cycles
|
||||||
|
let (address, extra) = self.address_izx(bus);
|
||||||
|
self.sta(bus, (address, extra), 6)
|
||||||
|
}
|
||||||
|
fn sta_izy(&mut self, bus: &mut impl Bus) -> u8 { // 0x91, 2 bytes, 5 cycles
|
||||||
|
let (address, extra) = self.address_izy(bus);
|
||||||
|
self.sta(bus, (address, extra), 5)
|
||||||
|
}
|
||||||
|
// STX variants
|
||||||
|
fn stx_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0x86, 2 bytes, 3 cycles
|
||||||
|
let (address, extra) = self.address_zp(bus);
|
||||||
|
self.stx(bus, (address, extra), 3)
|
||||||
|
}
|
||||||
|
fn stx_zpy(&mut self, bus: &mut impl Bus) -> u8 { // 0x96, 2 bytes, 4 cycles
|
||||||
|
let (address, extra) = self.address_zpy(bus);
|
||||||
|
self.stx(bus, (address, extra), 4)
|
||||||
|
}
|
||||||
|
fn stx_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x8E, 3 bytes, 4 cycles
|
||||||
|
let (address, extra) = self.address_abs(bus);
|
||||||
|
self.stx(bus, (address, extra), 4)
|
||||||
|
}
|
||||||
|
// STY variants
|
||||||
|
fn sty_zp(&mut self, bus: &mut impl Bus) -> u8 { // 0x84, 2 bytes, 3 cycles
|
||||||
|
let (addr, extra) = self.address_zp(bus);
|
||||||
|
self.sty(bus, (addr, extra), 3)
|
||||||
|
}
|
||||||
|
fn sty_zpx(&mut self, bus: &mut impl Bus) -> u8 { // 0x94, 2 bytes, 4 cycles
|
||||||
|
let (addr, extra) = self.address_zpx(bus);
|
||||||
|
self.sty(bus, (addr, extra), 4)
|
||||||
|
}
|
||||||
|
fn sty_abs(&mut self, bus: &mut impl Bus) -> u8 { // 0x8C, 3 bytes, 4 cycles
|
||||||
|
let (addr, extra) = self.address_abs(bus);
|
||||||
|
self.sty(bus, (addr, extra), 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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
|
||||||
fn bcc_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, !self.flag(FLAG_CARRY)) } // 0x90
|
fn bcc_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, !self.flag(FLAG_CARRY)) } // 0x90
|
||||||
@@ -457,7 +536,7 @@ impl Cpu {
|
|||||||
fn bvs_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, self.flag(FLAG_OVERFLOW)) } //0x70
|
fn bvs_rel(&mut self, bus: &mut impl Bus) -> u8 { self.branch(bus, self.flag(FLAG_OVERFLOW)) } //0x70
|
||||||
|
|
||||||
// NES 6502 opcode length table/matcher
|
// NES 6502 opcode length table/matcher
|
||||||
fn opcode_len(opcode: u8) -> u8 {
|
pub fn opcode_len(opcode: u8) -> u8 {
|
||||||
match opcode {
|
match opcode {
|
||||||
0x4C => 3, // JMP abs
|
0x4C => 3, // JMP abs
|
||||||
0xA2 => 2, // LDX imm
|
0xA2 => 2, // LDX imm
|
||||||
@@ -503,6 +582,18 @@ impl Cpu {
|
|||||||
0xB4 => 2, // LDY zpx
|
0xB4 => 2, // LDY zpx
|
||||||
0xAC => 3, // LDY abs
|
0xAC => 3, // LDY abs
|
||||||
0xBC => 3, // LDY abx
|
0xBC => 3, // LDY abx
|
||||||
|
0x85 => 2, // STA zp
|
||||||
|
0x95 => 2, // STA zpx
|
||||||
|
0x8D => 3, // STA abs
|
||||||
|
0x9D => 3, // STA abx
|
||||||
|
0x99 => 3, // STA aby
|
||||||
|
0x81 => 2, // STA izx
|
||||||
|
0x91 => 2, // STA izy
|
||||||
|
0x96 => 2, // STX zpy
|
||||||
|
0x8E => 3, // STX abs
|
||||||
|
0x84 => 2, // STY zp
|
||||||
|
0x94 => 2, // STY zpx
|
||||||
|
0x8C => 3, // STY 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user