From 5f54792589c36f8090c39569ee2480af4ba1de62 Mon Sep 17 00:00:00 2001 From: omarandlorraine <64254276+omarandlorraine@users.noreply.github.com> Date: Mon, 19 Jan 2026 20:40:25 +0000 Subject: [PATCH 1/8] sax --- src/cpu.rs | 5 ++++- src/instruction.rs | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 51290a0..9c82ed1 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -818,7 +818,10 @@ impl CPU { (Instruction::SEI, OpInput::UseImplied) => { self.set_flag(Status::PS_DISABLE_INTERRUPTS); } - + (Instruction::SAX, OpInput::UseAddress { address: addr, .. }) => { + self.memory + .set_byte(addr, self.registers.accumulator & self.registers.index_x); + } (Instruction::STA, OpInput::UseAddress { address: addr, .. }) => { self.memory.set_byte(addr, self.registers.accumulator); } diff --git a/src/instruction.rs b/src/instruction.rs index 969b443..9a965ec 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -208,6 +208,9 @@ pub enum Instruction { // STore Zero STZ, + // STore A & X + SAX, + // Transfer Accumulator to X TAX, @@ -747,11 +750,11 @@ impl crate::Variant for Nmos6502 { 0x80 => None, 0x81 => Some((Instruction::STA, AddressingMode::IndexedIndirectX)), 0x82 => None, - 0x83 => None, + 0x83 => Some((Instruction::SAX, AddressingMode::ZeroPage)), 0x84 => Some((Instruction::STY, AddressingMode::ZeroPage)), 0x85 => Some((Instruction::STA, AddressingMode::ZeroPage)), 0x86 => Some((Instruction::STX, AddressingMode::ZeroPage)), - 0x87 => None, + 0x87 => Some((Instruction::SAX, AddressingMode::ZeroPage)), 0x88 => Some((Instruction::DEY, AddressingMode::Implied)), 0x89 => None, 0x8a => Some((Instruction::TXA, AddressingMode::Implied)), @@ -759,7 +762,7 @@ impl crate::Variant for Nmos6502 { 0x8c => Some((Instruction::STY, AddressingMode::Absolute)), 0x8d => Some((Instruction::STA, AddressingMode::Absolute)), 0x8e => Some((Instruction::STX, AddressingMode::Absolute)), - 0x8f => None, + 0x8f => Some((Instruction::SAX, AddressingMode::Absolute)), 0x90 => Some((Instruction::BCC, AddressingMode::Relative)), 0x91 => Some((Instruction::STA, AddressingMode::IndirectIndexedY)), 0x92 => None, @@ -767,7 +770,7 @@ impl crate::Variant for Nmos6502 { 0x94 => Some((Instruction::STY, AddressingMode::ZeroPageX)), 0x95 => Some((Instruction::STA, AddressingMode::ZeroPageX)), 0x96 => Some((Instruction::STX, AddressingMode::ZeroPageY)), - 0x97 => None, + 0x97 => Some((Instruction::SAX, AddressingMode::ZeroPageY)), 0x98 => Some((Instruction::TYA, AddressingMode::Implied)), 0x99 => Some((Instruction::STA, AddressingMode::AbsoluteY)), 0x9a => Some((Instruction::TXS, AddressingMode::Implied)), From cb336aed1726ae405d0ec20f2584ab286c430d9b Mon Sep 17 00:00:00 2001 From: omarandlorraine <64254276+omarandlorraine@users.noreply.github.com> Date: Mon, 19 Jan 2026 20:53:49 +0000 Subject: [PATCH 2/8] xaa --- src/cpu.rs | 4 ++++ src/instruction.rs | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/cpu.rs b/src/cpu.rs index 9c82ed1..a410a5b 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -894,6 +894,10 @@ impl CPU { let val = self.registers.index_y; self.load_accumulator(val); } + (Instruction::XAA, OpInput::UseImmediate(val)) => { + self.load_accumulator(self.registers.index_x); + self.and(val); + } (Instruction::WAI, OpInput::UseImplied) => { // Wait for Interrupt (65C02) diff --git a/src/instruction.rs b/src/instruction.rs index 9a965ec..9de001a 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -240,6 +240,9 @@ pub enum Instruction { // SToP processor (65C02 only) STP, + + // XAA, (transfer X to A, and then ANDs the accumulator with an immediate value) + XAA, } impl Instruction { @@ -758,7 +761,7 @@ impl crate::Variant for Nmos6502 { 0x88 => Some((Instruction::DEY, AddressingMode::Implied)), 0x89 => None, 0x8a => Some((Instruction::TXA, AddressingMode::Implied)), - 0x8b => None, + 0x8b => Some((Instruction::XAA, AddressingMode::Immediate)), 0x8c => Some((Instruction::STY, AddressingMode::Absolute)), 0x8d => Some((Instruction::STA, AddressingMode::Absolute)), 0x8e => Some((Instruction::STX, AddressingMode::Absolute)), From 747e70c5b0b93a38d2d2c2e76922b030e1fecf59 Mon Sep 17 00:00:00 2001 From: omarandlorraine <64254276+omarandlorraine@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:59:54 +0000 Subject: [PATCH 3/8] alr --- src/cpu.rs | 8 ++++++++ src/instruction.rs | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/cpu.rs b/src/cpu.rs index a410a5b..baa5306 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -916,6 +916,14 @@ impl CPU { (Instruction::NOP, OpInput::UseImplied) => { log::debug!("NOP instruction"); } + + (Instruction::ALR, OpInput::UseImmediate(val)) => { + self.and(val); + let mut val = self.registers.accumulator; + CPU::::shift_right_with_flags(&mut val, &mut self.registers.status); + self.registers.accumulator = val; + } + (_, _) => { log::debug!( "attempting to execute unimplemented or invalid \ diff --git a/src/instruction.rs b/src/instruction.rs index 9de001a..f018ed9 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -243,6 +243,9 @@ pub enum Instruction { // XAA, (transfer X to A, and then ANDs the accumulator with an immediate value) XAA, + + // ALR, (ANDs the accumulator with an immediate value, and then does LSR) + ALR, } impl Instruction { @@ -697,7 +700,7 @@ impl crate::Variant for Nmos6502 { 0x48 => Some((Instruction::PHA, AddressingMode::Implied)), 0x49 => Some((Instruction::EOR, AddressingMode::Immediate)), 0x4a => Some((Instruction::LSR, AddressingMode::Accumulator)), - 0x4b => None, + 0x4b => Some((Instruction::ALR, AddressingMode::Immediate)), 0x4c => Some((Instruction::JMP, AddressingMode::Absolute)), 0x4d => Some((Instruction::EOR, AddressingMode::Absolute)), 0x4e => Some((Instruction::LSR, AddressingMode::Absolute)), From f088898c3ac7f51f4e53fff0bb3ffb97425a6398 Mon Sep 17 00:00:00 2001 From: omarandlorraine <64254276+omarandlorraine@users.noreply.github.com> Date: Sat, 24 Jan 2026 22:04:14 +0000 Subject: [PATCH 4/8] anc --- src/cpu.rs | 9 +++++++++ src/instruction.rs | 7 +++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index baa5306..4129b49 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -924,6 +924,15 @@ impl CPU { self.registers.accumulator = val; } + (Instruction::ANC, OpInput::UseImmediate(val)) => { + self.and(val); + if self.registers.accumulator & 0x80 != 0 { + self.set_flag(Status::PS_CARRY); + } else { + self.unset_flag(Status::PS_CARRY); + } + } + (_, _) => { log::debug!( "attempting to execute unimplemented or invalid \ diff --git a/src/instruction.rs b/src/instruction.rs index f018ed9..499db42 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -246,6 +246,9 @@ pub enum Instruction { // ALR, (ANDs the accumulator with an immediate value, and then does LSR) ALR, + + // ANC, (ANDs the accumulator and then copies bit 7 into carry flag) + ANC, } impl Instruction { @@ -636,7 +639,7 @@ impl crate::Variant for Nmos6502 { 0x08 => Some((Instruction::PHP, AddressingMode::Implied)), 0x09 => Some((Instruction::ORA, AddressingMode::Immediate)), 0x0a => Some((Instruction::ASL, AddressingMode::Accumulator)), - 0x0b => None, + 0x0b => Some((Instruction::ANC, AddressingMode::Immediate)), 0x0c => None, 0x0d => Some((Instruction::ORA, AddressingMode::Absolute)), 0x0e => Some((Instruction::ASL, AddressingMode::Absolute)), @@ -668,7 +671,7 @@ impl crate::Variant for Nmos6502 { 0x28 => Some((Instruction::PLP, AddressingMode::Implied)), 0x29 => Some((Instruction::AND, AddressingMode::Immediate)), 0x2a => Some((Instruction::ROL, AddressingMode::Accumulator)), - 0x2b => None, + 0x2b => Some((Instruction::ANC, AddressingMode::Immediate)), 0x2c => Some((Instruction::BIT, AddressingMode::Absolute)), 0x2d => Some((Instruction::AND, AddressingMode::Absolute)), 0x2e => Some((Instruction::ROL, AddressingMode::Absolute)), From 3e5faf9ccb9a64934fc55188b0a8bff72b9133b5 Mon Sep 17 00:00:00 2001 From: omarandlorraine <64254276+omarandlorraine@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:45:27 +0000 Subject: [PATCH 5/8] bump version number --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f24d4bb..8e99928 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ name = "mos6502" description = "A MOS 6502 Emulator" license = "BSD-3-Clause" -version = "0.6.2" +version = "0.6.3" authors = ["The 6502-rs Developers"] exclude = ["examples/**"] edition = "2024" From eb1d6f78e49bbbf422578a7b82c423f0ac312d55 Mon Sep 17 00:00:00 2001 From: mlund Date: Mon, 26 Jan 2026 08:02:57 +0100 Subject: [PATCH 6/8] illegal opcodes: lax, dcp, isc, slo, rla, sre, rra, arr, sbx, las, usbc, jam, nops --- src/cpu.rs | 511 +++++++++++++++++++++++++++++++++++++++++++++ src/instruction.rs | 343 +++++++++++++++++++++--------- 2 files changed, 756 insertions(+), 98 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 4129b49..e866e41 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -933,6 +933,134 @@ impl CPU { } } + // ARR - AND with immediate, then ROR with special flag handling. + // Unlike normal ROR, ARR sets C and V flags based on result bits, + // not from the shift operation. This quirk stems from how the 6502's + // internal buses interact during this undocumented instruction. + (Instruction::ARR, OpInput::UseImmediate(val)) => { + self.and(val); + let a = self.registers.accumulator; + let carry = self.registers.status.contains(Status::PS_CARRY); + + // ROR the accumulator + let result = (a >> 1) | (if carry { 0x80 } else { 0 }); + self.registers.accumulator = result; + + // Set N and Z flags from result + CPU::::set_flags_from_u8(&mut self.registers.status, result); + + // Carry is set from bit 6 (not bit 0 as in normal ROR) + if result & 0x40 != 0 { + self.set_flag(Status::PS_CARRY); + } else { + self.unset_flag(Status::PS_CARRY); + } + + // Overflow is set when bits 6 and 5 differ. This unusual behavior + // detects a "sign change" between the two highest result bits, + // useful for BCD fixup in some algorithms. + if ((result >> 6) ^ (result >> 5)) & 1 != 0 { + self.set_flag(Status::PS_OVERFLOW); + } else { + self.unset_flag(Status::PS_OVERFLOW); + } + } + + // DCP - Decrement memory, then compare with accumulator + (Instruction::DCP, OpInput::UseAddress { address: addr, .. }) => { + let val = self.memory.get_byte(addr).wrapping_sub(1); + self.memory.set_byte(addr, val); + self.compare_with_a_register(val); + } + + // ISC - Increment memory, then SBC from accumulator + (Instruction::ISC, OpInput::UseAddress { address: addr, .. }) => { + let val = self.memory.get_byte(addr).wrapping_add(1); + self.memory.set_byte(addr, val); + self.subtract_with_carry(val); + } + + // JAM - Halt the CPU (requires reset) + (Instruction::JAM, OpInput::UseImplied) => { + self.halted = true; + } + + // LAS - AND memory with SP, load to A, X, SP + (Instruction::LAS, OpInput::UseAddress { address: addr, .. }) => { + let val = self.memory.get_byte(addr) & self.registers.stack_pointer.0; + self.registers.accumulator = val; + self.registers.index_x = val; + self.registers.stack_pointer = StackPointer(val); + CPU::::set_flags_from_u8(&mut self.registers.status, val); + } + + // LAX - Load A and X with the same value from memory + (Instruction::LAX, OpInput::UseAddress { address: addr, .. }) => { + let val = self.memory.get_byte(addr); + self.load_accumulator(val); + self.load_x_register(val); + } + + // NOP variants - read memory but do nothing + (Instruction::NOPI, OpInput::UseImmediate(_)) => {} + (Instruction::NOPZ, OpInput::UseAddress { .. }) => {} + (Instruction::NOPZX, OpInput::UseAddress { .. }) => {} + (Instruction::NOPA, OpInput::UseAddress { .. }) => {} + (Instruction::NOPAX, OpInput::UseAddress { .. }) => {} + + // RLA - Rotate left memory, then AND with accumulator + (Instruction::RLA, OpInput::UseAddress { address: addr, .. }) => { + let mut val = self.memory.get_byte(addr); + CPU::::rotate_left_with_flags(&mut val, &mut self.registers.status); + self.memory.set_byte(addr, val); + self.and(val); + } + + // RRA - Rotate right memory, then ADC with accumulator + (Instruction::RRA, OpInput::UseAddress { address: addr, .. }) => { + let mut val = self.memory.get_byte(addr); + CPU::::rotate_right_with_flags(&mut val, &mut self.registers.status); + self.memory.set_byte(addr, val); + self.add_with_carry(val); + } + + // SBX - (A AND X) - immediate -> X, flags like CMP + (Instruction::SBX, OpInput::UseImmediate(val)) => { + let ax = self.registers.accumulator & self.registers.index_x; + let result = ax.wrapping_sub(val); + self.registers.index_x = result; + + // Set carry if no borrow (ax >= val) + if ax >= val { + self.set_flag(Status::PS_CARRY); + } else { + self.unset_flag(Status::PS_CARRY); + } + + CPU::::set_flags_from_u8(&mut self.registers.status, result); + } + + // SLO - Shift left memory, then OR with accumulator + (Instruction::SLO, OpInput::UseAddress { address: addr, .. }) => { + let mut val = self.memory.get_byte(addr); + CPU::::shift_left_with_flags(&mut val, &mut self.registers.status); + self.memory.set_byte(addr, val); + self.inclusive_or(val); + } + + // SRE - Shift right memory, then EOR with accumulator + (Instruction::SRE, OpInput::UseAddress { address: addr, .. }) => { + let mut val = self.memory.get_byte(addr); + CPU::::shift_right_with_flags(&mut val, &mut self.registers.status); + self.memory.set_byte(addr, val); + self.exclusive_or(val); + } + + // USBC - Same as SBC immediate + (Instruction::USBC, OpInput::UseImmediate(val)) => { + self.subtract_with_carry(val); + } + (_, _) => { log::debug!( "attempting to execute unimplemented or invalid \ @@ -2757,6 +2885,389 @@ mod tests { cpu.single_step(); // Execute LDA assert_eq!(cpu.registers.accumulator, 0x99); } + + // ==================== Illegal Opcode Tests ==================== + + /// Execute instruction with zero-page addressing + macro_rules! exec_zp { + ($cpu:expr, $instr:ident, $addr:expr) => { + $cpu.execute_instruction(( + Instruction::$instr, + AddressingMode::ZeroPage, + OpInput::UseAddress { + address: $addr, + page_crossed: false, + }, + )) + }; + } + + /// Execute instruction with immediate addressing + macro_rules! exec_imm { + ($cpu:expr, $instr:ident, $val:expr) => { + $cpu.execute_instruction(( + Instruction::$instr, + AddressingMode::Immediate, + OpInput::UseImmediate($val), + )) + }; + } + + /// Execute instruction with implied addressing + macro_rules! exec_impl { + ($cpu:expr, $instr:ident) => { + $cpu.execute_instruction(( + Instruction::$instr, + AddressingMode::Implied, + OpInput::UseImplied, + )) + }; + } + + /// Execute instruction with absolute,Y addressing + macro_rules! exec_aby { + ($cpu:expr, $instr:ident, $addr:expr) => { + $cpu.execute_instruction(( + Instruction::$instr, + AddressingMode::AbsoluteY, + OpInput::UseAddress { + address: $addr, + page_crossed: false, + }, + )) + }; + } + + #[test] + fn lax_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.memory.set_byte(0x42, 0x55); + exec_zp!(cpu, LAX, 0x42); + assert_eq!(cpu.registers.accumulator, 0x55); + assert_eq!(cpu.registers.index_x, 0x55); + assert!(!cpu.registers.status.contains(Status::PS_ZERO)); + assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); + + cpu.memory.set_byte(0x10, 0x00); + exec_zp!(cpu, LAX, 0x10); + assert_eq!(cpu.registers.accumulator, 0x00); + assert_eq!(cpu.registers.index_x, 0x00); + assert!(cpu.registers.status.contains(Status::PS_ZERO)); + + cpu.memory.set_byte(0x20, 0x80); + exec_zp!(cpu, LAX, 0x20); + assert_eq!(cpu.registers.accumulator, 0x80); + assert_eq!(cpu.registers.index_x, 0x80); + assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); + } + + #[test] + fn sax_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.registers.accumulator = 0xFF; + cpu.registers.index_x = 0x0F; + exec_zp!(cpu, SAX, 0x42); + assert_eq!(cpu.memory.get_byte(0x42), 0x0F); + + cpu.registers.accumulator = 0xAA; + cpu.registers.index_x = 0x55; + exec_zp!(cpu, SAX, 0x43); + assert_eq!(cpu.memory.get_byte(0x43), 0x00); + } + + #[test] + fn dcp_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.memory.set_byte(0x42, 0x10); + cpu.registers.accumulator = 0x0F; + exec_zp!(cpu, DCP, 0x42); + assert_eq!(cpu.memory.get_byte(0x42), 0x0F); + assert!(cpu.registers.status.contains(Status::PS_ZERO)); + assert!(cpu.registers.status.contains(Status::PS_CARRY)); + + cpu.memory.set_byte(0x43, 0x05); + cpu.registers.accumulator = 0x10; + exec_zp!(cpu, DCP, 0x43); + assert_eq!(cpu.memory.get_byte(0x43), 0x04); + assert!(!cpu.registers.status.contains(Status::PS_ZERO)); + assert!(cpu.registers.status.contains(Status::PS_CARRY)); + + cpu.memory.set_byte(0x44, 0x00); + cpu.registers.accumulator = 0x00; + exec_zp!(cpu, DCP, 0x44); + assert_eq!(cpu.memory.get_byte(0x44), 0xFF); + assert!(!cpu.registers.status.contains(Status::PS_CARRY)); + } + + #[test] + fn isc_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + cpu.registers.status.remove(Status::PS_DECIMAL_MODE); + + cpu.memory.set_byte(0x42, 0x09); + cpu.registers.accumulator = 0x20; + cpu.registers.status.insert(Status::PS_CARRY); + exec_zp!(cpu, ISC, 0x42); + assert_eq!(cpu.memory.get_byte(0x42), 0x0A); + assert_eq!(cpu.registers.accumulator, 0x16); + assert!(cpu.registers.status.contains(Status::PS_CARRY)); + + cpu.memory.set_byte(0x43, 0xFF); + cpu.registers.accumulator = 0x10; + cpu.registers.status.insert(Status::PS_CARRY); + exec_zp!(cpu, ISC, 0x43); + assert_eq!(cpu.memory.get_byte(0x43), 0x00); + assert_eq!(cpu.registers.accumulator, 0x10); + } + + #[test] + fn slo_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.memory.set_byte(0x42, 0x40); + cpu.registers.accumulator = 0x01; + exec_zp!(cpu, SLO, 0x42); + assert_eq!(cpu.memory.get_byte(0x42), 0x80); + assert_eq!(cpu.registers.accumulator, 0x81); + assert!(!cpu.registers.status.contains(Status::PS_CARRY)); + assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); + + cpu.memory.set_byte(0x43, 0x80); + cpu.registers.accumulator = 0x00; + exec_zp!(cpu, SLO, 0x43); + assert_eq!(cpu.memory.get_byte(0x43), 0x00); + assert_eq!(cpu.registers.accumulator, 0x00); + assert!(cpu.registers.status.contains(Status::PS_CARRY)); + assert!(cpu.registers.status.contains(Status::PS_ZERO)); + } + + #[test] + fn rla_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.registers.status.remove(Status::PS_CARRY); + cpu.memory.set_byte(0x42, 0x40); + cpu.registers.accumulator = 0xFF; + exec_zp!(cpu, RLA, 0x42); + assert_eq!(cpu.memory.get_byte(0x42), 0x80); + assert_eq!(cpu.registers.accumulator, 0x80); + assert!(!cpu.registers.status.contains(Status::PS_CARRY)); + + cpu.registers.status.insert(Status::PS_CARRY); + cpu.memory.set_byte(0x43, 0x40); + cpu.registers.accumulator = 0xFF; + exec_zp!(cpu, RLA, 0x43); + assert_eq!(cpu.memory.get_byte(0x43), 0x81); + assert_eq!(cpu.registers.accumulator, 0x81); + } + + #[test] + fn sre_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.memory.set_byte(0x42, 0x02); + cpu.registers.accumulator = 0xFF; + exec_zp!(cpu, SRE, 0x42); + assert_eq!(cpu.memory.get_byte(0x42), 0x01); + assert_eq!(cpu.registers.accumulator, 0xFE); + assert!(!cpu.registers.status.contains(Status::PS_CARRY)); + + cpu.memory.set_byte(0x43, 0x01); + cpu.registers.accumulator = 0x00; + exec_zp!(cpu, SRE, 0x43); + assert_eq!(cpu.memory.get_byte(0x43), 0x00); + assert_eq!(cpu.registers.accumulator, 0x00); + assert!(cpu.registers.status.contains(Status::PS_CARRY)); + } + + #[test] + fn rra_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + cpu.registers.status.remove(Status::PS_DECIMAL_MODE); + + cpu.registers.status.remove(Status::PS_CARRY); + cpu.memory.set_byte(0x42, 0x02); + cpu.registers.accumulator = 0x10; + exec_zp!(cpu, RRA, 0x42); + assert_eq!(cpu.memory.get_byte(0x42), 0x01); + assert_eq!(cpu.registers.accumulator, 0x11); + + cpu.registers.status.insert(Status::PS_CARRY); + cpu.memory.set_byte(0x43, 0x02); + cpu.registers.accumulator = 0x00; + exec_zp!(cpu, RRA, 0x43); + assert_eq!(cpu.memory.get_byte(0x43), 0x81); + assert_eq!(cpu.registers.accumulator, 0x81); + } + + #[test] + fn arr_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.registers.accumulator = 0xFF; + cpu.registers.status.remove(Status::PS_CARRY); + exec_imm!(cpu, ARR, 0x55); + assert_eq!(cpu.registers.accumulator, 0x2A); + assert!(!cpu.registers.status.contains(Status::PS_CARRY)); + + cpu.registers.accumulator = 0xFF; + cpu.registers.status.insert(Status::PS_CARRY); + exec_imm!(cpu, ARR, 0x55); + assert_eq!(cpu.registers.accumulator, 0xAA); + assert!(!cpu.registers.status.contains(Status::PS_CARRY)); + } + + #[test] + fn sbx_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.registers.accumulator = 0xFF; + cpu.registers.index_x = 0x0F; + exec_imm!(cpu, SBX, 0x05); + assert_eq!(cpu.registers.index_x, 0x0A); + assert!(cpu.registers.status.contains(Status::PS_CARRY)); + + cpu.registers.accumulator = 0xFF; + cpu.registers.index_x = 0x0F; + exec_imm!(cpu, SBX, 0x10); + assert_eq!(cpu.registers.index_x, 0xFF); + assert!(!cpu.registers.status.contains(Status::PS_CARRY)); + assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); + } + + #[test] + fn alr_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.registers.accumulator = 0xFF; + exec_imm!(cpu, ALR, 0xAA); + assert_eq!(cpu.registers.accumulator, 0x55); + assert!(!cpu.registers.status.contains(Status::PS_CARRY)); + + cpu.registers.accumulator = 0xFF; + exec_imm!(cpu, ALR, 0x55); + assert_eq!(cpu.registers.accumulator, 0x2A); + assert!(cpu.registers.status.contains(Status::PS_CARRY)); + } + + #[test] + fn anc_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.registers.accumulator = 0xFF; + exec_imm!(cpu, ANC, 0x80); + assert_eq!(cpu.registers.accumulator, 0x80); + assert!(cpu.registers.status.contains(Status::PS_CARRY)); + assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); + + cpu.registers.accumulator = 0xFF; + exec_imm!(cpu, ANC, 0x7F); + assert_eq!(cpu.registers.accumulator, 0x7F); + assert!(!cpu.registers.status.contains(Status::PS_CARRY)); + assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); + } + + #[test] + fn xaa_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + // XAA transfers X to A, then ANDs with immediate + cpu.registers.index_x = 0xFF; + cpu.registers.accumulator = 0x00; + exec_imm!(cpu, XAA, 0x0F); + assert_eq!(cpu.registers.accumulator, 0x0F); + assert!(!cpu.registers.status.contains(Status::PS_ZERO)); + assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); + + cpu.registers.index_x = 0xAA; + exec_imm!(cpu, XAA, 0xF0); + assert_eq!(cpu.registers.accumulator, 0xA0); + assert!(cpu.registers.status.contains(Status::PS_NEGATIVE)); + + cpu.registers.index_x = 0x55; + exec_imm!(cpu, XAA, 0xAA); + assert_eq!(cpu.registers.accumulator, 0x00); + assert!(cpu.registers.status.contains(Status::PS_ZERO)); + } + + #[test] + fn las_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + cpu.registers.stack_pointer = StackPointer(0xFF); + cpu.memory.set_byte(0x1000, 0x0F); + exec_aby!(cpu, LAS, 0x1000); + assert_eq!(cpu.registers.accumulator, 0x0F); + assert_eq!(cpu.registers.index_x, 0x0F); + assert_eq!(cpu.registers.stack_pointer.0, 0x0F); + assert!(!cpu.registers.status.contains(Status::PS_ZERO)); + assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE)); + } + + #[test] + fn usbc_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + cpu.registers.status.remove(Status::PS_DECIMAL_MODE); + + cpu.registers.accumulator = 0x20; + cpu.registers.status.insert(Status::PS_CARRY); + exec_imm!(cpu, USBC, 0x10); + assert_eq!(cpu.registers.accumulator, 0x10); + assert!(cpu.registers.status.contains(Status::PS_CARRY)); + } + + #[test] + fn jam_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + assert!(!cpu.halted); + exec_impl!(cpu, JAM); + assert!(cpu.halted); + assert!(!cpu.single_step()); + } + + #[test] + fn nop_variants_test() { + let mut cpu = CPU::new(Ram::new(), Nmos6502); + + let initial_a = 0x42; + let initial_x = 0x13; + let initial_y = 0x37; + cpu.registers.accumulator = initial_a; + cpu.registers.index_x = initial_x; + cpu.registers.index_y = initial_y; + + exec_imm!(cpu, NOPI, 0xFF); + assert_eq!(cpu.registers.accumulator, initial_a); + assert_eq!(cpu.registers.index_x, initial_x); + assert_eq!(cpu.registers.index_y, initial_y); + + exec_zp!(cpu, NOPZ, 0x42); + assert_eq!(cpu.registers.accumulator, initial_a); + + cpu.execute_instruction(( + Instruction::NOPA, + AddressingMode::Absolute, + OpInput::UseAddress { + address: 0x1234, + page_crossed: false, + }, + )); + assert_eq!(cpu.registers.accumulator, initial_a); + + cpu.execute_instruction(( + Instruction::NOPAX, + AddressingMode::AbsoluteX, + OpInput::UseAddress { + address: 0x1234, + page_crossed: false, + }, + )); + assert_eq!(cpu.registers.accumulator, initial_a); + } } #[cfg(test)] diff --git a/src/instruction.rs b/src/instruction.rs index 499db42..e460b3e 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -249,6 +249,54 @@ pub enum Instruction { // ANC, (ANDs the accumulator and then copies bit 7 into carry flag) ANC, + + // ARR, (ANDs the accumulator with an immediate value, then RORs with special flag handling) + ARR, + + // DCP (DCM), (Decrements memory, then compares with accumulator) + DCP, + + // ISC (ISB/INS), (Increments memory, then SBCs from accumulator) + ISC, + + // JAM (KIL/HLT), (Halts the CPU - requires reset) + JAM, + + // LAS (LAR), (ANDs memory with SP, loads result into A, X, and SP) + LAS, + + // LAX, (Loads both A and X with the same value from memory) + LAX, + + // NOP with addressing modes (reads memory but does nothing) + // Immediate NOP + NOPI, + // Zero Page NOP + NOPZ, + // Zero Page,X NOP + NOPZX, + // Absolute NOP + NOPA, + // Absolute,X NOP + NOPAX, + + // RLA, (Rotates memory left, then ANDs with accumulator) + RLA, + + // RRA, (Rotates memory right, then ADCs with accumulator) + RRA, + + // SBX (AXS), ((A AND X) - immediate -> X, sets flags like CMP) + SBX, + + // SLO (ASO), (Shifts memory left, then ORs with accumulator) + SLO, + + // SRE (LSE), (Shifts memory right, then EORs with accumulator) + SRE, + + // USBC, (Same as SBC immediate) + USBC, } impl Instruction { @@ -503,6 +551,105 @@ impl Instruction { // WAI - Wait for Interrupt (65C02 only, 3 cycles before waiting) (WAI, Implied) => 3, + // Illegal opcodes - SAX (Store A AND X) + (SAX, ZeroPage) => 3, + (SAX, ZeroPageY) => 4, + (SAX, Absolute) => 4, + (SAX, IndexedIndirectX) => 6, + + // Illegal opcodes - XAA (Transfer X to A, then AND) + (XAA, Immediate) => 2, + + // Illegal opcodes - ALR (AND then LSR) + (ALR, Immediate) => 2, + + // Illegal opcodes - ANC (AND then copy bit 7 to carry) + (ANC, Immediate) => 2, + + // Illegal opcodes - ARR (AND then ROR with special flags) + (ARR, Immediate) => 2, + + // Illegal opcodes - DCP (Decrement then Compare) + (DCP, ZeroPage) => 5, + (DCP, ZeroPageX) => 6, + (DCP, Absolute) => 6, + (DCP, AbsoluteX) => 7, + (DCP, AbsoluteY) => 7, + (DCP, IndexedIndirectX) => 8, + (DCP, IndirectIndexedY) => 8, + + // Illegal opcodes - ISC (Increment then SBC) + (ISC, ZeroPage) => 5, + (ISC, ZeroPageX) => 6, + (ISC, Absolute) => 6, + (ISC, AbsoluteX) => 7, + (ISC, AbsoluteY) => 7, + (ISC, IndexedIndirectX) => 8, + (ISC, IndirectIndexedY) => 8, + + // Illegal opcodes - JAM (Halt CPU) + (JAM, Implied) => 2, + + // Illegal opcodes - LAS (AND with SP, load to A, X, SP) + (LAS, AbsoluteY) => 4, // +1 if page crossed + + // Illegal opcodes - LAX (Load A and X) + (LAX, ZeroPage) => 3, + (LAX, ZeroPageY) => 4, + (LAX, Absolute) => 4, + (LAX, AbsoluteY) => 4, // +1 if page crossed + (LAX, IndexedIndirectX) => 6, + (LAX, IndirectIndexedY) => 5, // +1 if page crossed + + // Illegal opcodes - NOP variants + (NOPI, Immediate) => 2, + (NOPZ, ZeroPage) => 3, + (NOPZX, ZeroPageX) => 4, + (NOPA, Absolute) => 4, + (NOPAX, AbsoluteX) => 4, // +1 if page crossed + + // Illegal opcodes - RLA (Rotate Left then AND) + (RLA, ZeroPage) => 5, + (RLA, ZeroPageX) => 6, + (RLA, Absolute) => 6, + (RLA, AbsoluteX) => 7, + (RLA, AbsoluteY) => 7, + (RLA, IndexedIndirectX) => 8, + (RLA, IndirectIndexedY) => 8, + + // Illegal opcodes - RRA (Rotate Right then ADC) + (RRA, ZeroPage) => 5, + (RRA, ZeroPageX) => 6, + (RRA, Absolute) => 6, + (RRA, AbsoluteX) => 7, + (RRA, AbsoluteY) => 7, + (RRA, IndexedIndirectX) => 8, + (RRA, IndirectIndexedY) => 8, + + // Illegal opcodes - SBX (A AND X - immediate -> X) + (SBX, Immediate) => 2, + + // Illegal opcodes - SLO (Shift Left then OR) + (SLO, ZeroPage) => 5, + (SLO, ZeroPageX) => 6, + (SLO, Absolute) => 6, + (SLO, AbsoluteX) => 7, + (SLO, AbsoluteY) => 7, + (SLO, IndexedIndirectX) => 8, + (SLO, IndirectIndexedY) => 8, + + // Illegal opcodes - SRE (Shift Right then EOR) + (SRE, ZeroPage) => 5, + (SRE, ZeroPageX) => 6, + (SRE, Absolute) => 6, + (SRE, AbsoluteX) => 7, + (SRE, AbsoluteY) => 7, + (SRE, IndexedIndirectX) => 8, + (SRE, IndirectIndexedY) => 8, + + // Illegal opcodes - USBC (Same as SBC) + (USBC, Immediate) => 2, + // Invalid combinations cause a panic to indicate a bug in the decoder _ => unreachable!("undecoded instruction"), } @@ -630,44 +777,44 @@ impl crate::Variant for Nmos6502 { match opcode { 0x00 => Some((Instruction::BRK, AddressingMode::Implied)), 0x01 => Some((Instruction::ORA, AddressingMode::IndexedIndirectX)), - 0x02 => None, - 0x03 => None, - 0x04 => None, + 0x02 => Some((Instruction::JAM, AddressingMode::Implied)), + 0x03 => Some((Instruction::SLO, AddressingMode::IndexedIndirectX)), + 0x04 => Some((Instruction::NOPZ, AddressingMode::ZeroPage)), 0x05 => Some((Instruction::ORA, AddressingMode::ZeroPage)), 0x06 => Some((Instruction::ASL, AddressingMode::ZeroPage)), - 0x07 => None, + 0x07 => Some((Instruction::SLO, AddressingMode::ZeroPage)), 0x08 => Some((Instruction::PHP, AddressingMode::Implied)), 0x09 => Some((Instruction::ORA, AddressingMode::Immediate)), 0x0a => Some((Instruction::ASL, AddressingMode::Accumulator)), 0x0b => Some((Instruction::ANC, AddressingMode::Immediate)), - 0x0c => None, + 0x0c => Some((Instruction::NOPA, AddressingMode::Absolute)), 0x0d => Some((Instruction::ORA, AddressingMode::Absolute)), 0x0e => Some((Instruction::ASL, AddressingMode::Absolute)), - 0x0f => None, + 0x0f => Some((Instruction::SLO, AddressingMode::Absolute)), 0x10 => Some((Instruction::BPL, AddressingMode::Relative)), 0x11 => Some((Instruction::ORA, AddressingMode::IndirectIndexedY)), - 0x12 => None, - 0x13 => None, - 0x14 => None, + 0x12 => Some((Instruction::JAM, AddressingMode::Implied)), + 0x13 => Some((Instruction::SLO, AddressingMode::IndirectIndexedY)), + 0x14 => Some((Instruction::NOPZX, AddressingMode::ZeroPageX)), 0x15 => Some((Instruction::ORA, AddressingMode::ZeroPageX)), 0x16 => Some((Instruction::ASL, AddressingMode::ZeroPageX)), - 0x17 => None, + 0x17 => Some((Instruction::SLO, AddressingMode::ZeroPageX)), 0x18 => Some((Instruction::CLC, AddressingMode::Implied)), 0x19 => Some((Instruction::ORA, AddressingMode::AbsoluteY)), - 0x1a => None, - 0x1b => None, - 0x1c => None, + 0x1a => Some((Instruction::NOP, AddressingMode::Implied)), + 0x1b => Some((Instruction::SLO, AddressingMode::AbsoluteY)), + 0x1c => Some((Instruction::NOPAX, AddressingMode::AbsoluteX)), 0x1d => Some((Instruction::ORA, AddressingMode::AbsoluteX)), 0x1e => Some((Instruction::ASL, AddressingMode::AbsoluteX)), - 0x1f => None, + 0x1f => Some((Instruction::SLO, AddressingMode::AbsoluteX)), 0x20 => Some((Instruction::JSR, AddressingMode::Absolute)), 0x21 => Some((Instruction::AND, AddressingMode::IndexedIndirectX)), - 0x22 => None, - 0x23 => None, + 0x22 => Some((Instruction::JAM, AddressingMode::Implied)), + 0x23 => Some((Instruction::RLA, AddressingMode::IndexedIndirectX)), 0x24 => Some((Instruction::BIT, AddressingMode::ZeroPage)), 0x25 => Some((Instruction::AND, AddressingMode::ZeroPage)), 0x26 => Some((Instruction::ROL, AddressingMode::ZeroPage)), - 0x27 => None, + 0x27 => Some((Instruction::RLA, AddressingMode::ZeroPage)), 0x28 => Some((Instruction::PLP, AddressingMode::Implied)), 0x29 => Some((Instruction::AND, AddressingMode::Immediate)), 0x2a => Some((Instruction::ROL, AddressingMode::Accumulator)), @@ -675,31 +822,31 @@ impl crate::Variant for Nmos6502 { 0x2c => Some((Instruction::BIT, AddressingMode::Absolute)), 0x2d => Some((Instruction::AND, AddressingMode::Absolute)), 0x2e => Some((Instruction::ROL, AddressingMode::Absolute)), - 0x2f => None, + 0x2f => Some((Instruction::RLA, AddressingMode::Absolute)), 0x30 => Some((Instruction::BMI, AddressingMode::Relative)), 0x31 => Some((Instruction::AND, AddressingMode::IndirectIndexedY)), - 0x32 => None, - 0x33 => None, - 0x34 => None, + 0x32 => Some((Instruction::JAM, AddressingMode::Implied)), + 0x33 => Some((Instruction::RLA, AddressingMode::IndirectIndexedY)), + 0x34 => Some((Instruction::NOPZX, AddressingMode::ZeroPageX)), 0x35 => Some((Instruction::AND, AddressingMode::ZeroPageX)), 0x36 => Some((Instruction::ROL, AddressingMode::ZeroPageX)), - 0x37 => None, + 0x37 => Some((Instruction::RLA, AddressingMode::ZeroPageX)), 0x38 => Some((Instruction::SEC, AddressingMode::Implied)), 0x39 => Some((Instruction::AND, AddressingMode::AbsoluteY)), - 0x3a => None, - 0x3b => None, - 0x3c => None, + 0x3a => Some((Instruction::NOP, AddressingMode::Implied)), + 0x3b => Some((Instruction::RLA, AddressingMode::AbsoluteY)), + 0x3c => Some((Instruction::NOPAX, AddressingMode::AbsoluteX)), 0x3d => Some((Instruction::AND, AddressingMode::AbsoluteX)), 0x3e => Some((Instruction::ROL, AddressingMode::AbsoluteX)), - 0x3f => None, + 0x3f => Some((Instruction::RLA, AddressingMode::AbsoluteX)), 0x40 => Some((Instruction::RTI, AddressingMode::Implied)), 0x41 => Some((Instruction::EOR, AddressingMode::IndexedIndirectX)), - 0x42 => None, - 0x43 => None, - 0x44 => None, + 0x42 => Some((Instruction::JAM, AddressingMode::Implied)), + 0x43 => Some((Instruction::SRE, AddressingMode::IndexedIndirectX)), + 0x44 => Some((Instruction::NOPZ, AddressingMode::ZeroPage)), 0x45 => Some((Instruction::EOR, AddressingMode::ZeroPage)), 0x46 => Some((Instruction::LSR, AddressingMode::ZeroPage)), - 0x47 => None, + 0x47 => Some((Instruction::SRE, AddressingMode::ZeroPage)), 0x48 => Some((Instruction::PHA, AddressingMode::Implied)), 0x49 => Some((Instruction::EOR, AddressingMode::Immediate)), 0x4a => Some((Instruction::LSR, AddressingMode::Accumulator)), @@ -707,65 +854,65 @@ impl crate::Variant for Nmos6502 { 0x4c => Some((Instruction::JMP, AddressingMode::Absolute)), 0x4d => Some((Instruction::EOR, AddressingMode::Absolute)), 0x4e => Some((Instruction::LSR, AddressingMode::Absolute)), - 0x4f => None, + 0x4f => Some((Instruction::SRE, AddressingMode::Absolute)), 0x50 => Some((Instruction::BVC, AddressingMode::Relative)), 0x51 => Some((Instruction::EOR, AddressingMode::IndirectIndexedY)), - 0x52 => None, - 0x53 => None, - 0x54 => None, + 0x52 => Some((Instruction::JAM, AddressingMode::Implied)), + 0x53 => Some((Instruction::SRE, AddressingMode::IndirectIndexedY)), + 0x54 => Some((Instruction::NOPZX, AddressingMode::ZeroPageX)), 0x55 => Some((Instruction::EOR, AddressingMode::ZeroPageX)), 0x56 => Some((Instruction::LSR, AddressingMode::ZeroPageX)), - 0x57 => None, + 0x57 => Some((Instruction::SRE, AddressingMode::ZeroPageX)), 0x58 => Some((Instruction::CLI, AddressingMode::Implied)), 0x59 => Some((Instruction::EOR, AddressingMode::AbsoluteY)), - 0x5a => None, - 0x5b => None, - 0x5c => None, + 0x5a => Some((Instruction::NOP, AddressingMode::Implied)), + 0x5b => Some((Instruction::SRE, AddressingMode::AbsoluteY)), + 0x5c => Some((Instruction::NOPAX, AddressingMode::AbsoluteX)), 0x5d => Some((Instruction::EOR, AddressingMode::AbsoluteX)), 0x5e => Some((Instruction::LSR, AddressingMode::AbsoluteX)), - 0x5f => None, + 0x5f => Some((Instruction::SRE, AddressingMode::AbsoluteX)), 0x60 => Some((Instruction::RTS, AddressingMode::Implied)), 0x61 => Some((Instruction::ADC, AddressingMode::IndexedIndirectX)), - 0x62 => None, - 0x63 => None, - 0x64 => None, + 0x62 => Some((Instruction::JAM, AddressingMode::Implied)), + 0x63 => Some((Instruction::RRA, AddressingMode::IndexedIndirectX)), + 0x64 => Some((Instruction::NOPZ, AddressingMode::ZeroPage)), 0x65 => Some((Instruction::ADC, AddressingMode::ZeroPage)), 0x66 => Some((Instruction::ROR, AddressingMode::ZeroPage)), - 0x67 => None, + 0x67 => Some((Instruction::RRA, AddressingMode::ZeroPage)), 0x68 => Some((Instruction::PLA, AddressingMode::Implied)), 0x69 => Some((Instruction::ADC, AddressingMode::Immediate)), 0x6a => Some((Instruction::ROR, AddressingMode::Accumulator)), - 0x6b => None, + 0x6b => Some((Instruction::ARR, AddressingMode::Immediate)), 0x6c => Some((Instruction::JMP, AddressingMode::BuggyIndirect)), 0x6d => Some((Instruction::ADC, AddressingMode::Absolute)), 0x6e => Some((Instruction::ROR, AddressingMode::Absolute)), - 0x6f => None, + 0x6f => Some((Instruction::RRA, AddressingMode::Absolute)), 0x70 => Some((Instruction::BVS, AddressingMode::Relative)), 0x71 => Some((Instruction::ADC, AddressingMode::IndirectIndexedY)), - 0x72 => None, - 0x73 => None, - 0x74 => None, + 0x72 => Some((Instruction::JAM, AddressingMode::Implied)), + 0x73 => Some((Instruction::RRA, AddressingMode::IndirectIndexedY)), + 0x74 => Some((Instruction::NOPZX, AddressingMode::ZeroPageX)), 0x75 => Some((Instruction::ADC, AddressingMode::ZeroPageX)), 0x76 => Some((Instruction::ROR, AddressingMode::ZeroPageX)), - 0x77 => None, + 0x77 => Some((Instruction::RRA, AddressingMode::ZeroPageX)), 0x78 => Some((Instruction::SEI, AddressingMode::Implied)), 0x79 => Some((Instruction::ADC, AddressingMode::AbsoluteY)), - 0x7a => None, - 0x7b => None, - 0x7c => None, + 0x7a => Some((Instruction::NOP, AddressingMode::Implied)), + 0x7b => Some((Instruction::RRA, AddressingMode::AbsoluteY)), + 0x7c => Some((Instruction::NOPAX, AddressingMode::AbsoluteX)), 0x7d => Some((Instruction::ADC, AddressingMode::AbsoluteX)), 0x7e => Some((Instruction::ROR, AddressingMode::AbsoluteX)), - 0x7f => None, - 0x80 => None, + 0x7f => Some((Instruction::RRA, AddressingMode::AbsoluteX)), + 0x80 => Some((Instruction::NOPI, AddressingMode::Immediate)), 0x81 => Some((Instruction::STA, AddressingMode::IndexedIndirectX)), - 0x82 => None, - 0x83 => Some((Instruction::SAX, AddressingMode::ZeroPage)), + 0x82 => Some((Instruction::NOPI, AddressingMode::Immediate)), + 0x83 => Some((Instruction::SAX, AddressingMode::IndexedIndirectX)), 0x84 => Some((Instruction::STY, AddressingMode::ZeroPage)), 0x85 => Some((Instruction::STA, AddressingMode::ZeroPage)), 0x86 => Some((Instruction::STX, AddressingMode::ZeroPage)), 0x87 => Some((Instruction::SAX, AddressingMode::ZeroPage)), 0x88 => Some((Instruction::DEY, AddressingMode::Implied)), - 0x89 => None, + 0x89 => Some((Instruction::NOPI, AddressingMode::Immediate)), 0x8a => Some((Instruction::TXA, AddressingMode::Implied)), 0x8b => Some((Instruction::XAA, AddressingMode::Immediate)), 0x8c => Some((Instruction::STY, AddressingMode::Absolute)), @@ -774,8 +921,8 @@ impl crate::Variant for Nmos6502 { 0x8f => Some((Instruction::SAX, AddressingMode::Absolute)), 0x90 => Some((Instruction::BCC, AddressingMode::Relative)), 0x91 => Some((Instruction::STA, AddressingMode::IndirectIndexedY)), - 0x92 => None, - 0x93 => None, + 0x92 => Some((Instruction::JAM, AddressingMode::Implied)), + 0x93 => None, // SHA (zp),Y - unstable, skip 0x94 => Some((Instruction::STY, AddressingMode::ZeroPageX)), 0x95 => Some((Instruction::STA, AddressingMode::ZeroPageX)), 0x96 => Some((Instruction::STX, AddressingMode::ZeroPageY)), @@ -783,107 +930,107 @@ impl crate::Variant for Nmos6502 { 0x98 => Some((Instruction::TYA, AddressingMode::Implied)), 0x99 => Some((Instruction::STA, AddressingMode::AbsoluteY)), 0x9a => Some((Instruction::TXS, AddressingMode::Implied)), - 0x9b => None, - 0x9c => None, + 0x9b => None, // TAS abs,Y - unstable, skip + 0x9c => None, // SHY abs,X - unstable, skip 0x9d => Some((Instruction::STA, AddressingMode::AbsoluteX)), - 0x9e => None, - 0x9f => None, + 0x9e => None, // SHX abs,Y - unstable, skip + 0x9f => None, // SHA abs,Y - unstable, skip 0xa0 => Some((Instruction::LDY, AddressingMode::Immediate)), 0xa1 => Some((Instruction::LDA, AddressingMode::IndexedIndirectX)), 0xa2 => Some((Instruction::LDX, AddressingMode::Immediate)), - 0xa3 => None, + 0xa3 => Some((Instruction::LAX, AddressingMode::IndexedIndirectX)), 0xa4 => Some((Instruction::LDY, AddressingMode::ZeroPage)), 0xa5 => Some((Instruction::LDA, AddressingMode::ZeroPage)), 0xa6 => Some((Instruction::LDX, AddressingMode::ZeroPage)), - 0xa7 => None, + 0xa7 => Some((Instruction::LAX, AddressingMode::ZeroPage)), 0xa8 => Some((Instruction::TAY, AddressingMode::Implied)), 0xa9 => Some((Instruction::LDA, AddressingMode::Immediate)), 0xaa => Some((Instruction::TAX, AddressingMode::Implied)), - 0xab => None, + 0xab => None, // LXA #imm - unstable, skip 0xac => Some((Instruction::LDY, AddressingMode::Absolute)), 0xad => Some((Instruction::LDA, AddressingMode::Absolute)), 0xae => Some((Instruction::LDX, AddressingMode::Absolute)), - 0xaf => None, + 0xaf => Some((Instruction::LAX, AddressingMode::Absolute)), 0xb0 => Some((Instruction::BCS, AddressingMode::Relative)), 0xb1 => Some((Instruction::LDA, AddressingMode::IndirectIndexedY)), - 0xb2 => None, - 0xb3 => None, + 0xb2 => Some((Instruction::JAM, AddressingMode::Implied)), + 0xb3 => Some((Instruction::LAX, AddressingMode::IndirectIndexedY)), 0xb4 => Some((Instruction::LDY, AddressingMode::ZeroPageX)), 0xb5 => Some((Instruction::LDA, AddressingMode::ZeroPageX)), 0xb6 => Some((Instruction::LDX, AddressingMode::ZeroPageY)), - 0xb7 => None, + 0xb7 => Some((Instruction::LAX, AddressingMode::ZeroPageY)), 0xb8 => Some((Instruction::CLV, AddressingMode::Implied)), 0xb9 => Some((Instruction::LDA, AddressingMode::AbsoluteY)), 0xba => Some((Instruction::TSX, AddressingMode::Implied)), - 0xbb => None, + 0xbb => Some((Instruction::LAS, AddressingMode::AbsoluteY)), 0xbc => Some((Instruction::LDY, AddressingMode::AbsoluteX)), 0xbd => Some((Instruction::LDA, AddressingMode::AbsoluteX)), 0xbe => Some((Instruction::LDX, AddressingMode::AbsoluteY)), - 0xbf => None, + 0xbf => Some((Instruction::LAX, AddressingMode::AbsoluteY)), 0xc0 => Some((Instruction::CPY, AddressingMode::Immediate)), 0xc1 => Some((Instruction::CMP, AddressingMode::IndexedIndirectX)), - 0xc2 => None, - 0xc3 => None, + 0xc2 => Some((Instruction::NOPI, AddressingMode::Immediate)), + 0xc3 => Some((Instruction::DCP, AddressingMode::IndexedIndirectX)), 0xc4 => Some((Instruction::CPY, AddressingMode::ZeroPage)), 0xc5 => Some((Instruction::CMP, AddressingMode::ZeroPage)), 0xc6 => Some((Instruction::DEC, AddressingMode::ZeroPage)), - 0xc7 => None, + 0xc7 => Some((Instruction::DCP, AddressingMode::ZeroPage)), 0xc8 => Some((Instruction::INY, AddressingMode::Implied)), 0xc9 => Some((Instruction::CMP, AddressingMode::Immediate)), 0xca => Some((Instruction::DEX, AddressingMode::Implied)), - 0xcb => None, + 0xcb => Some((Instruction::SBX, AddressingMode::Immediate)), 0xcc => Some((Instruction::CPY, AddressingMode::Absolute)), 0xcd => Some((Instruction::CMP, AddressingMode::Absolute)), 0xce => Some((Instruction::DEC, AddressingMode::Absolute)), - 0xcf => None, + 0xcf => Some((Instruction::DCP, AddressingMode::Absolute)), 0xd0 => Some((Instruction::BNE, AddressingMode::Relative)), 0xd1 => Some((Instruction::CMP, AddressingMode::IndirectIndexedY)), - 0xd2 => None, - 0xd3 => None, - 0xd4 => None, + 0xd2 => Some((Instruction::JAM, AddressingMode::Implied)), + 0xd3 => Some((Instruction::DCP, AddressingMode::IndirectIndexedY)), + 0xd4 => Some((Instruction::NOPZX, AddressingMode::ZeroPageX)), 0xd5 => Some((Instruction::CMP, AddressingMode::ZeroPageX)), 0xd6 => Some((Instruction::DEC, AddressingMode::ZeroPageX)), - 0xd7 => None, + 0xd7 => Some((Instruction::DCP, AddressingMode::ZeroPageX)), 0xd8 => Some((Instruction::CLD, AddressingMode::Implied)), 0xd9 => Some((Instruction::CMP, AddressingMode::AbsoluteY)), - 0xda => None, - 0xdb => None, - 0xdc => None, + 0xda => Some((Instruction::NOP, AddressingMode::Implied)), + 0xdb => Some((Instruction::DCP, AddressingMode::AbsoluteY)), + 0xdc => Some((Instruction::NOPAX, AddressingMode::AbsoluteX)), 0xdd => Some((Instruction::CMP, AddressingMode::AbsoluteX)), 0xde => Some((Instruction::DEC, AddressingMode::AbsoluteX)), - 0xdf => None, + 0xdf => Some((Instruction::DCP, AddressingMode::AbsoluteX)), 0xe0 => Some((Instruction::CPX, AddressingMode::Immediate)), 0xe1 => Some((Instruction::SBC, AddressingMode::IndexedIndirectX)), - 0xe2 => None, - 0xe3 => None, + 0xe2 => Some((Instruction::NOPI, AddressingMode::Immediate)), + 0xe3 => Some((Instruction::ISC, AddressingMode::IndexedIndirectX)), 0xe4 => Some((Instruction::CPX, AddressingMode::ZeroPage)), 0xe5 => Some((Instruction::SBC, AddressingMode::ZeroPage)), 0xe6 => Some((Instruction::INC, AddressingMode::ZeroPage)), - 0xe7 => None, + 0xe7 => Some((Instruction::ISC, AddressingMode::ZeroPage)), 0xe8 => Some((Instruction::INX, AddressingMode::Implied)), 0xe9 => Some((Instruction::SBC, AddressingMode::Immediate)), 0xea => Some((Instruction::NOP, AddressingMode::Implied)), - 0xeb => None, + 0xeb => Some((Instruction::USBC, AddressingMode::Immediate)), 0xec => Some((Instruction::CPX, AddressingMode::Absolute)), 0xed => Some((Instruction::SBC, AddressingMode::Absolute)), 0xee => Some((Instruction::INC, AddressingMode::Absolute)), - 0xef => None, + 0xef => Some((Instruction::ISC, AddressingMode::Absolute)), 0xf0 => Some((Instruction::BEQ, AddressingMode::Relative)), 0xf1 => Some((Instruction::SBC, AddressingMode::IndirectIndexedY)), - 0xf2 => None, - 0xf3 => None, - 0xf4 => None, + 0xf2 => Some((Instruction::JAM, AddressingMode::Implied)), + 0xf3 => Some((Instruction::ISC, AddressingMode::IndirectIndexedY)), + 0xf4 => Some((Instruction::NOPZX, AddressingMode::ZeroPageX)), 0xf5 => Some((Instruction::SBC, AddressingMode::ZeroPageX)), 0xf6 => Some((Instruction::INC, AddressingMode::ZeroPageX)), - 0xf7 => None, + 0xf7 => Some((Instruction::ISC, AddressingMode::ZeroPageX)), 0xf8 => Some((Instruction::SED, AddressingMode::Implied)), 0xf9 => Some((Instruction::SBC, AddressingMode::AbsoluteY)), - 0xfa => None, - 0xfb => None, - 0xfc => None, + 0xfa => Some((Instruction::NOP, AddressingMode::Implied)), + 0xfb => Some((Instruction::ISC, AddressingMode::AbsoluteY)), + 0xfc => Some((Instruction::NOPAX, AddressingMode::AbsoluteX)), 0xfd => Some((Instruction::SBC, AddressingMode::AbsoluteX)), 0xfe => Some((Instruction::INC, AddressingMode::AbsoluteX)), - 0xff => None, + 0xff => Some((Instruction::ISC, AddressingMode::AbsoluteX)), } } From 204fc8b5c7882bba74a4beef3dedbafdecab8215 Mon Sep 17 00:00:00 2001 From: mlund Date: Mon, 26 Jan 2026 08:23:15 +0100 Subject: [PATCH 7/8] fix: README doc tests hanging after illegal opcodes implementation The illegal opcodes implementation caused README doc tests to hang: 1. First example used 0xff as program terminator - Previously 0xff was unimplemented (returned None), stopping execution - Now 0xff is ISC (INC+SBC) AbsoluteX, causing infinite loop - Fix: Use 0x02 (JAM) which explicitly halts the CPU 2. Second example loads euclid.bin which uses BRK (0x00) to terminate - BRK jumps to IRQ vector at $FFFE/$FFFF - Uninitialized memory contains 0x00, jumping to address $0000 - This causes infinite loop executing whatever is in low memory - Fix: Mark as no_run since it depends on external file anyway --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4dfcc20..abe86ae 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ fn main() { 0x4c, 0x12, 0x00, // Jump to .algo_ // .end 0xa5, 0x00, // Load from S to A - 0xff, + 0x02, // JAM - halt CPU to end program // .swap 0xa6, 0x00, // load F to X 0xa4, 0x01, // load S to Y @@ -89,7 +89,7 @@ ld65 -C ../linker.cfg -o euclid.bin euclid.o This will create a binary file `euclid.bin` that you can load into the emulator: -```rust +```rust,no_run use mos6502::memory::Bus; use mos6502::memory::Memory; use mos6502::instruction::Nmos6502; From cb29892c4897369d607a08b71417bcf5b720e4cd Mon Sep 17 00:00:00 2001 From: mlund Date: Tue, 27 Jan 2026 12:58:44 +0100 Subject: [PATCH 8/8] fix: make euclid.bin example runnable as doc test - Replace BRK with JAM ($02) to halt CPU after illegal opcodes impl - Fix assembly label structure (algo/algo_ were dead infinite loops) - Simplify linker.cfg to output raw binary at $0010 (was 64KB image) - Enable README doc test (remove no_run marker) The old linker config produced a 64KB memory image with code at $0400, but README loaded at $0010 causing address mismatch. New config outputs only the code segment at the correct load address. --- README.md | 2 +- examples/asm/euclid/euclid.bin | Bin 34 -> 28 bytes examples/asm/euclid/euclid.ca65 | 42 +++++++++++++------------------- examples/asm/linker.cfg | 36 +++++++++------------------ 4 files changed, 29 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index abe86ae..1df4d01 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ ld65 -C ../linker.cfg -o euclid.bin euclid.o This will create a binary file `euclid.bin` that you can load into the emulator: -```rust,no_run +```rust use mos6502::memory::Bus; use mos6502::memory::Memory; use mos6502::instruction::Nmos6502; diff --git a/examples/asm/euclid/euclid.bin b/examples/asm/euclid/euclid.bin index 0c957020e5fe177cf5965ac0de3daf7535b8709d..994ebb8d799e0f2e2467618ba99be932e14242d4 100644 GIT binary patch literal 28 jcmZ3=VDXgk1G@o7D}#>^!%_yOWeiIg+ZbCId;}N(c4h}J literal 34 ncmZ3=VDXgk1G@o7D}#@`z)}W=WeiIg+ZbCId}IVbNL~N{s9*?L diff --git a/examples/asm/euclid/euclid.ca65 b/examples/asm/euclid/euclid.ca65 index 8e678c9..c5c9228 100644 --- a/examples/asm/euclid/euclid.ca65 +++ b/examples/asm/euclid/euclid.ca65 @@ -1,33 +1,25 @@ ; euclid.ca65 ; A program to find the greatest common divisor of two numbers -.ORG $1000 +.segment "CODE" -; .algo -LDA $00 ; Load from F to A -; .algo_ -SEC ; Set carry flag -SBC $01 ; Subtract S from the number in A (from F) -BEQ end ; Jump to .end if the difference is zero -BMI swap ; Jump to .swap if the difference is negative -STA $00 ; Load A to F -JMP algo_ ; Jump to .algo_ +algo: + LDA $00 ; Load from F to A +algo_: + SEC ; Set carry flag + SBC $01 ; Subtract S from A + BEQ end ; Jump to end if zero + BMI swap ; Jump to swap if negative + STA $00 ; Store A to F + JMP algo_ ; Continue algorithm -; .end end: -LDA $00 ; Load from F to A -BRK ; Break (end program) + LDA $00 ; Load result to A + .byte $02 ; JAM - halt CPU -; .swap swap: -LDX $00 ; Load F to X -LDY $01 ; Load S to Y -STX $01 ; Store X to S -STY $00 ; Store Y to F -JMP algo ; Jump to .algo - -algo: -JMP algo ; Infinite loop to prevent program from ending - -algo_: -JMP algo_ ; Infinite loop to prevent program from ending + LDX $00 ; Load F to X + LDY $01 ; Load S to Y + STX $01 ; Store X to S + STY $00 ; Store Y to F + JMP algo ; Restart algorithm diff --git a/examples/asm/linker.cfg b/examples/asm/linker.cfg index 9618ef3..fd36679 100644 --- a/examples/asm/linker.cfg +++ b/examples/asm/linker.cfg @@ -1,32 +1,18 @@ # Linker configuration for cc65 (ld65) -# Defines memory layout and segment placement for 6502 programs +# Outputs raw binary containing only the code segment MEMORY { - # RAM: 32KB from $0000-$7FFF - # Used for zero page, stack, data, and code in this emulator config - RAM: start = $0000, size=$8000, type = rw, fill = yes, fillval = $FF, file = %O; - - # ROM: Almost 32KB from $8000-$FFF9 - # Defined but not used in examples (code runs from RAM instead) - ROM: start = $8000, size=$7FFA, type = ro, fill = yes, fillval = $FF, file = %O; - - # ROM_VECTORS: 6 bytes at $FFFA-$FFFF - # Holds the three 16-bit interrupt vectors: NMI, RESET, IRQ - ROM_VECTORS: start = $FFFA, size=6, type = ro, fill = yes, fillval = $FF, file = %O; + # Code region starting at $0010, matching README examples + CODE: start = $0010, size = $1000, type = rw, file = %O; } SEGMENTS { - # Zero page ($00-$FF): Fast access variables - ZEROPAGE: load=RAM, type=rw; - - # Data segment: Starts at $0200 (page 2) - # Page 1 ($0100-$01FF) is reserved for the stack - DATA: load=RAM, type=rw, offset=$0200; + # Code segment: Contains the program + CODE: load = CODE, type = ro; - # Code segment: Starts at $0400 (page 4) - # Leaves room for zero page, stack, and data - CODE: load=RAM, type=rw, offset=$0400; - - # Interrupt vectors at top of memory - VECTORS: load=ROM_VECTORS, type=ro; -} \ No newline at end of file + # Optional segments + ZEROPAGE: load = CODE, type = rw, optional = yes; + DATA: load = CODE, type = rw, optional = yes; + RODATA: load = CODE, type = rw, optional = yes; + BSS: load = CODE, type = rw, optional = yes; +}