use std::fmt::Write; use tracing::debug; use crate::{Break, debug::DebugLog, mem::CpuMem}; bitfield::bitfield! { #[derive(Clone, Copy, PartialEq, Eq)] pub struct CpuStatus(u8); pub carry, set_carry: 0; pub zero, set_zero: 1; pub interrupt_disable, set_interrupt_disable: 2; pub decimal, set_decimal: 3; pub brk, set_brk: 4; pub php, set_php: 5; pub overflow, set_overflow: 6; pub negative, set_negative: 7; } impl std::fmt::Debug for CpuStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.negative() { write!(f, "N")?; } if self.overflow() { write!(f, "V")?; } if self.decimal() { write!(f, "D")?; } if self.interrupt_disable() { write!(f, "I")?; } if self.zero() { write!(f, "Z")?; } if self.carry() { write!(f, "C")?; } Ok(()) } } #[derive(Debug, Clone)] pub struct Cpu { pub a: u8, pub x: u8, pub y: u8, pub pc: u16, pub sp: u8, pub status: CpuStatus, pub clock_state: ClockState, pub nmi_line_state: bool, pub nmi_pending: bool, pub irq_pending: bool, pub(crate) last_instruction: String, pub(crate) debug_log: DebugLog, halted: bool, pub(crate) cycle: usize, } #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum ClockState { ReadInstruction, ReadOperands { instruction: u8, ops: [u8; 5], count: u8, addr: u16, }, Hold { cycles: u8, instruction: u8, ops: [u8; 5], count: u8, addr: u16, }, Oops { instruction: u8, ops: [u8; 5], count: u8, addr: u16, }, HoldNmi { cycles: u8, }, HoldIrq { cycles: u8, }, PageCross, } impl std::fmt::Debug for ClockState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::ReadInstruction => write!(f, "ReadInstruction"), Self::ReadOperands { instruction, ops, count, addr, } => f .debug_struct("ReadOperands") .field("instruction", instruction) .field("addr", addr) .field("ops", &&ops[..*count as usize]) .finish(), Self::Hold { cycles, instruction, ops, count, addr, } => f .debug_struct("Hold") .field("cycles", cycles) .field("instruction", instruction) .field("addr", addr) .field("ops", &&ops[..*count as usize]) .finish(), ClockState::HoldNmi { cycles } => { f.debug_struct("HoldNmi").field("cycles", cycles).finish() } 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(), ClockState::PageCross => write!(f, "PageCross"), } } } enum ExecState { Done, MoreParams, Hold(u8), Oops, PageCross, } #[derive(Debug, Clone, Copy)] pub enum CPUMMRegisters { PPU, APU, } impl Cpu { pub fn init() -> Self { Self { a: 0, x: 0, y: 0, pc: 0, sp: 0, status: CpuStatus(0), clock_state: ClockState::ReadInstruction, nmi_pending: false, irq_pending: false, nmi_line_state: false, cycle: 7, last_instruction: String::new(), debug_log: DebugLog::new(), halted: false, } } pub fn read_abs(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8) -> u8 { mem.read(u16::from_le_bytes([low, high])) } pub fn read_abs_x(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8) -> u8 { mem.read(u16::from_le_bytes([low, high]) + self.x as u16) } pub fn read_abs_y(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8) -> u8 { mem.read(u16::from_le_bytes([low, high]) + self.y as u16) } pub fn read_zp_x(&mut self, mem: &mut CpuMem<'_>, low: u8) -> u8 { mem.read(u16::from_le_bytes([low.wrapping_add(self.x), 0])) } pub fn read_zp_y(&mut self, mem: &mut CpuMem<'_>, low: u8) -> u8 { mem.read(u16::from_le_bytes([low.wrapping_add(self.y), 0])) } pub fn write_abs(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8, val: u8) { mem.write(u16::from_le_bytes([low, high]), val) } pub fn write_abs_x(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8, val: u8) { mem.write(u16::from_le_bytes([low, high]) + self.x as u16, val) } pub fn write_abs_y(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8, val: u8) { mem.write(u16::from_le_bytes([low, high]) + self.y as u16, val) } pub fn write_zp_x(&mut self, mem: &mut CpuMem<'_>, low: u8, val: u8) { mem.write(u16::from_le_bytes([low.wrapping_add(self.x), 0]), val) } pub fn write_zp_y(&mut self, mem: &mut CpuMem<'_>, low: u8, val: u8) { mem.write(u16::from_le_bytes([low.wrapping_add(self.y), 0]), val) } pub fn push(&mut self, mem: &mut CpuMem<'_>, val: u8) { mem.write(self.sp as u16 + 0x100, val); self.sp = self.sp.wrapping_sub(1); } pub fn pop(&mut self, mem: &mut CpuMem<'_>) -> u8 { self.sp = self.sp.wrapping_add(1); mem.read(self.sp as u16 + 0x100) } pub fn push_16(&mut self, mem: &mut CpuMem<'_>, val: u16) { debug!("Writing rc = {:04X} at {:02X}", val, self.sp); let [low, high] = val.to_le_bytes(); self.push(mem, high); self.push(mem, low); } pub fn pop_16(&mut self, mem: &mut CpuMem<'_>) -> u16 { let low = self.pop(mem); let high = self.pop(mem); u16::from_le_bytes([low, high]) } #[must_use] fn jmp_rel(&mut self, off: u8) -> bool { let pc = if off < 128 { self.pc.wrapping_add(off as u16) } else { self.pc.wrapping_sub(0x100 - off as u16) }; let page_cross = pc & 0xFF00 != self.pc & 0xFF00; self.pc = pc; page_cross } fn halt(&mut self) { self.halted = true; } /// Returns true if more bytes are needed fn exec_instruction( &mut self, mem: &mut CpuMem<'_>, 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(); if params.len() < ([$(inst!(@EMPTY $name)),*] as [(); _]).len() { ExecState::MoreParams } else if !held && hold_time != 0 { ExecState::Hold(hold_time - 1) } else { // debug!("Running 0x{:04X} {} :{:X} {:X?}", self.pc - (1 + params.len() as u16), $val, ins, params); // debug!("Running 0x{:04X} {} :{:X} {:X?}", self.pc - (1 + params.len() as u16), $val, ins, params); self.last_instruction = format!("0x{:04X} {} :{:X} {:X?}", addr, $val, ins, params); $( let $name = params[0]; #[allow(unused_assignments)] { params = ¶ms[1..]; } )* {$eval} // self.status.set_interrupt_disable(true); ExecState::Done } }}; ($name:expr, $hold:expr, || $eval:expr) => { inst!($name, $hold, | | $eval) }; (@EMPTY $n:pat_param) => { () }; } macro_rules! log { ($($t:tt)*) => { let args = format_args!($($t)*); self.debug_log.write_fmt(args).expect("Failed to write debug log"); self.debug_log.write_char('\n').expect("Failed to write debug log"); }; } match ins { 0x02 => inst!("HLT", 0, || { log!("{addr:04X}: HLT"); self.halt() }), 0x12 => inst!("DBG", 0, || { log!("{addr:04X}: DBG"); self.halt() }), 0x22 | 0x32 | 0x42 | 0x52 | 0x62 | 0x72 | 0x92 | 0xB2 | 0xD3 | 0xF2 => { panic!("Game crash by executing {ins:02X} at {addr:04X}") } 0x38 => inst!("SEC", 1, || { log!("{addr:04X}: SEC"); self.status.set_carry(true) }), 0x18 => inst!("CLC", 1, || { log!("{addr:04X}: CLC"); self.status.set_carry(false) }), 0x78 => inst!("SEI", 1, || { log!("{addr:04X}: SEI"); self.status.set_interrupt_disable(true) }), 0x58 => inst!("CLI", 1, || { log!("{addr:04X}: CLI"); self.status.set_interrupt_disable(false) }), 0xF8 => inst!("SED", 1, || { log!("{addr:04X}: SED"); self.status.set_decimal(true) }), 0xD8 => inst!("CLD", 1, || { log!("{addr:04X}: CLD"); self.status.set_decimal(false) }), 0xB8 => inst!("CLV", 1, || { log!("{addr:04X}: CLV"); self.status.set_overflow(false) }), 0x00 => inst!("BRK", 5, |_ignored| { // TODO: this should probably just set `self.irq_pending` ... log!("{addr:04X}: BRK #${_ignored:02X}"); self.push_16(mem, self.pc); self.push(mem, self.status.0 | 0b00110000); self.status.set_interrupt_disable(true); self.pc = u16::from_le_bytes([mem.read(0xFFFE), mem.read(0xFFFF)]); log!(" : - JMP ${:X}", self.pc); }), 0x48 => inst!("PHA", 2, || { log!("{addr:04X}: PHA | {:02X} -> 01{:02X}", self.a, self.sp); self.push(mem, self.a); }), 0x08 => inst!("PHP", 2, || { log!( "{addr:04X}: PHP | {:02X} -> 01{:02X}", self.status.0, self.sp ); self.push(mem, self.status.0 | 0b0011_0000); }), 0x68 => inst!("PLA", 3, || { self.a = self.pop(mem); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: PLA | {:02X} <- 01{:02X}", self.a, self.sp); }), 0x28 => inst!("PLP", 3, || { self.status.0 = self.pop(mem) & 0b1100_1111; log!( "{addr:04X}: PLP | {:02X} <- 01{:02X}", self.status.0, self.sp ); }), // Loads 0xA9 => inst!("LDA imm", 0, |a| { self.a = a; self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: LDA #${:02X}", a); }), 0xA5 => inst!("LDA zp", 1, |off| { self.a = mem.read(off as u16); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: LDA ${:02X} | {:02X}", off, self.a); }), 0xB5 => inst!("LDA zp,x", 2, |off| { self.a = self.read_zp_x(mem, off); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: LDA ${:02X},x | {:02X}", off, self.a); }), 0xAD => inst!("LDA abs", 1, |low, high| { self.a = self.read_abs(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: LDA ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0xBD => inst!("LDA abs,x", 1, |low, high| { if !oops && low.checked_add(self.x).is_none() { return ExecState::Oops; } self.a = self.read_abs_x(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: LDA ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0xB9 => inst!("LDA abs,y", 1, |low, high| { if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } self.a = self.read_abs_y(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: LDA ${:02X}{:02X},y | {:02X}", high, low, self.a ); }), 0xA1 => inst!("LDA (ind,x)", 4, |off| { let low = self.read_zp_x(mem, off); let high = self.read_zp_x(mem, off.wrapping_add(1)); self.a = self.read_abs(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: LDA (${:02X},x) | {:02X}", off, self.a); }), 0xB1 => inst!("LDA (ind),y", 3, |off| { let low = self.read_abs(mem, off, 0); let high = self.read_abs(mem, off.wrapping_add(1), 0); if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } self.a = mem.read(u16::from_le_bytes([low, high]) + self.y as u16); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: LDA (${:02X}),y | {:02X}", off, self.a); }), 0xA2 => inst!("LDX imm", 0, |x| { self.x = x; self.status.set_zero(self.x == 0); self.status.set_negative(self.x & 0x80 == 0x80); log!("{addr:04X}: LDX #${:02X}", x); }), 0xA6 => inst!("LDX zp", 1, |off| { self.x = mem.read(off as u16); self.status.set_zero(self.x == 0); self.status.set_negative(self.x & 0x80 == 0x80); log!("{addr:04X}: LDX ${:02X} | {:02X}", off, self.x); }), 0xB6 => inst!("LDX zp,y", 2, |off| { self.x = self.read_zp_y(mem, off); self.status.set_zero(self.x == 0); self.status.set_negative(self.x & 0x80 == 0x80); log!("{addr:04X}: LDX ${:02X},y | {:02X}", off, self.x); }), 0xAE => inst!("LDX abs", 1, |low, high| { self.x = self.read_abs(mem, low, high); self.status.set_zero(self.x == 0); self.status.set_negative(self.x & 0x80 == 0x80); log!("{addr:04X}: LDX ${:02X}{:02X} | {:02X}", high, low, self.x); }), 0xBE => inst!("LDX abs,y", 1, |low, high| { if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } self.x = self.read_abs_y(mem, low, high); self.status.set_zero(self.x == 0); self.status.set_negative(self.x & 0x80 == 0x80); log!( "{addr:04X}: LDX ${:02X}{:02X},y | {:02X}", high, low, self.x ); }), 0xA0 => inst!("LDY imm", 0, |y| { self.y = y; self.status.set_zero(self.y == 0); self.status.set_negative(self.y & 0x80 == 0x80); log!("{addr:04X}: LDY #${:02X}", y); }), 0xA4 => inst!("LDY zp", 1, |off| { self.y = mem.read(off as u16); self.status.set_zero(self.y == 0); self.status.set_negative(self.y & 0x80 == 0x80); log!("{addr:04X}: LDY ${:02X} | {:02X}", off, self.y); }), 0xB4 => inst!("LDX zp,x", 2, |off| { self.y = self.read_zp_x(mem, off); self.status.set_zero(self.y == 0); self.status.set_negative(self.y & 0x80 == 0x80); log!("{addr:04X}: LDY ${:02X},x | {:02X}", off, self.y); }), 0xAC => inst!("LDX abs", 1, |low, high| { self.y = self.read_abs(mem, low, high); self.status.set_zero(self.y == 0); self.status.set_negative(self.y & 0x80 == 0x80); log!("{addr:04X}: LDY ${:02X}{:02X} | {:02X}", high, low, self.y); }), 0xBC => inst!("LDX abs,x", 1, |low, high| { if !oops && low.checked_add(self.x).is_none() { return ExecState::Oops; } self.y = self.read_abs_x(mem, low, high); self.status.set_zero(self.y == 0); self.status.set_negative(self.y & 0x80 == 0x80); log!( "{addr:04X}: LDY ${:02X}{:02X},x | {:02X}", high, low, self.y ); }), // Stores 0x85 => inst!("STA zp", 1, |off| { mem.write(off as u16, self.a); log!("{addr:04X}: STA ${:02X} | {:02X}", off, self.a); }), 0x95 => inst!("STA zp,x", 2, |off| { self.write_zp_x(mem, off, self.a); log!("{addr:04X}: STA ${:02X},x | {:02X}", off, self.a); }), 0x8D => inst!("STA abs", 1, |low, high| { self.write_abs(mem, low, high, self.a); log!("{addr:04X}: STA ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0x9D => inst!("STA abs,x", 2, |low, high| { self.write_abs_x(mem, low, high, self.a); log!( "{addr:04X}: STA ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0x99 => inst!("STA abs,y", 2, |low, high| { self.write_abs_y(mem, low, high, self.a); log!( "{addr:04X}: STA ${:02X}{:02X},y | {:02X}", high, low, self.a ); }), 0x81 => inst!("STA (ind,x)", 4, |off| { let low = self.read_zp_x(mem, off); let high = self.read_zp_x(mem, off.wrapping_add(1)); self.write_abs(mem, low, high, self.a); log!("{addr:04X}: STA (${:02X},x) | {:02X}", off, self.a); }), 0x91 => inst!("STA (ind),y", 4, |off| { let low = self.read_abs(mem, off, 0); let high = self.read_abs(mem, off.wrapping_add(1), 0); mem.write(u16::from_le_bytes([low, high]) + self.y as u16, self.a); log!("{addr:04X}: STA (${:02X}),y | {:02X}", off, self.a); }), 0x86 => inst!("STX zp", 1, |off| { mem.write(off as u16, self.x); log!("{addr:04X}: STX ${:02X} | {:02X}", off, self.x); }), 0x96 => inst!("STX zp,y", 2, |off| { self.write_zp_y(mem, off, self.x); log!("{addr:04X}: STX ${:02X},y | {:02X}", off, self.x); }), 0x8E => inst!("STX abs", 1, |low, high| { self.write_abs(mem, low, high, self.x); log!("{addr:04X}: STX ${:02X}{:02X} | {:02X}", high, low, self.x); }), 0x84 => inst!("STY zp", 1, |off| { mem.write(off as u16, self.y); log!("{addr:04X}: STY ${:02X} | {:02X}", off, self.y); }), 0x94 => inst!("STY zp,x", 2, |off| { self.write_zp_x(mem, off, self.y); log!("{addr:04X}: STY ${:02X},x | {:02X}", off, self.y); }), 0x8C => inst!("STY abs", 1, |low, high| { self.write_abs(mem, low, high, self.y); log!("{addr:04X}: STY ${:02X}{:02X} | {:02X}", high, low, self.y); }), // Transfers 0xAA => inst!("TAX", 1, || { self.x = self.a; self.status.set_zero(self.x == 0); self.status.set_negative(self.x & 0x80 == 0x80); log!("{addr:04X}: TAX | {:02X}", self.x); }), 0xA8 => inst!("TAY", 1, || { self.y = self.a; self.status.set_zero(self.y == 0); self.status.set_negative(self.y & 0x80 == 0x80); log!("{addr:04X}: TAY | {:02X}", self.y); }), 0xBA => inst!("TSX", 1, || { self.x = self.sp; self.status.set_zero(self.x == 0); self.status.set_negative(self.x & 0x80 == 0x80); log!("{addr:04X}: TSX | {:02X}", self.x); }), 0x8A => inst!("TXA", 1, || { self.a = self.x; self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: TXA | {:02X}", self.a); }), 0x9A => inst!("TXS", 1, || { self.sp = self.x; log!("{addr:04X}: TXS | {:02X}", self.sp); }), 0x98 => inst!("TYA", 1, || { self.a = self.y; self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: TYA | {:02X}", self.a); }), // Branch 0x90 => inst!("BCC", !self.status.carry(), |off| { log!("{addr:04X}: BCC ${:02}", off); if !self.status.carry() { let cross = self.jmp_rel(off); log!(" : - JMP ${:04X}", self.pc); if cross { return ExecState::PageCross; } } }), 0xB0 => inst!("BCS", self.status.carry(), |off| { log!("{addr:04X}: BCS ${:02}", off); if self.status.carry() { let page = self.jmp_rel(off); log!(" : - JMP ${:04X}", self.pc); if page { return ExecState::PageCross; }; } }), 0xF0 => inst!("BEQ", self.status.zero(), |off| { log!("{addr:04X}: BEQ ${:02}", off); if self.status.zero() { let page = self.jmp_rel(off); log!(" : - JMP ${:04X}", self.pc); if page { return ExecState::PageCross; }; } }), 0x30 => inst!("BMI", self.status.negative(), |off| { log!("{addr:04X}: BMI ${:02}", off); if self.status.negative() { let page = self.jmp_rel(off); log!(" : - JMP ${:04X}", self.pc); if page { return ExecState::PageCross; }; } }), 0xD0 => inst!("BNE", !self.status.zero(), |off| { log!("{addr:04X}: BNE {:02}", off as i8); if !self.status.zero() { let page = self.jmp_rel(off); log!(" : - JMP ${:04X}", self.pc); if page { return ExecState::PageCross; }; } }), 0x10 => inst!("BPL", !self.status.negative(), |off| { log!("{addr:04X}: BPL ${:02}", off); if !self.status.negative() { let page = self.jmp_rel(off); log!(" : - JMP ${:04X}", self.pc); if page { return ExecState::PageCross; }; } }), 0x4C => inst!("JMP abs", 0, |low, high| { self.pc = u16::from_le_bytes([low, high]); log!("{addr:04X}: JMP ${:04X}", self.pc); }), 0x6C => inst!("JMP abs", 2, |low, high| { self.pc = u16::from_le_bytes([ self.read_abs(mem, low, high), self.read_abs(mem, low.wrapping_add(1), high), // Known CPU bug ]); log!("{addr:04X}: JMP (${:02X}{:02X})", high, low); log!(" : - JMP ${:04X}", self.pc); }), 0x20 => inst!("JSR", 3, |low, high| { self.push_16(mem, self.pc - 1); let tmp = self.pc - 1; self.pc = u16::from_le_bytes([low, high]); log!("{addr:04X}: JSR ${:04X} | ret ${:04X}", self.pc, tmp); }), 0x60 => inst!("RTS", 5, || { self.pc = self.pop_16(mem) + 1; log!("{addr:04X}: RTS | ${:04X}", self.pc); }), 0x40 => inst!("RTI", 5, || { self.status.0 = self.pop(mem) & 0b1100_1111; self.pc = self.pop_16(mem); log!("{addr:04X}: RTI | ${:04X}", self.pc); }), // CMP 0xC9 => inst!("CMP imm", 0, |val| { let v = self.a.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.a >= val); log!( "{addr:04X}: CMP #${:02X} | {:02X} - {:02X} -> {:?}", val, self.a, val, self.status ); }), 0xC5 => inst!("CMP zp", 1, |off| { let val = self.read_abs(mem, off, 0); let v = self.a.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.a >= val); log!( "{addr:04X}: CMP ${:02X} | {:02X} - {:02X} -> {:?}", off, self.a, val, self.status ); }), 0xD5 => inst!("CMP zp,x", 2, |off| { let val = self.read_zp_x(mem, off); let v = self.a.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.a >= val); log!( "{addr:04X}: CMP ${:02X},x | {:02X} - {:02X} -> {:?}", off, self.a, val, self.status ); }), 0xCD => inst!("CMP abs", 1, |low, high| { let val = self.read_abs(mem, low, high); let v = self.a.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.a >= val); log!( "{addr:04X}: CMP ${:02X}{:02X} | {:02X} - {:02X} -> {:?}", high, low, self.a, val, self.status ); }), 0xDD => inst!("CMP abs,x", 1, |low, high| { if !oops && low.checked_add(self.x).is_none() { return ExecState::Oops; } let val = self.read_abs_x(mem, low, high); let v = self.a.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.a >= val); log!( "{addr:04X}: CMP ${:02X}{:02X},x | {:02X} - {:02X} -> {:?}", high, low, self.a, val, self.status ); }), 0xD9 => inst!("CMP abs,y", 1, |low, high| { if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } let val = self.read_abs_y(mem, low, high); let v = self.a.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.a >= val); log!( "{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}", high, low, self.a, val, self.status ); }), 0xC1 => inst!("CMP (ind,x)", 4, |off| { let low = self.read_zp_x(mem, off); let high = self.read_zp_x(mem, off.wrapping_add(1)); let val = self.read_abs(mem, low, high); let v = self.a.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.a >= val); log!( "{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}", high, low, self.a, val, self.status ); }), 0xD1 => inst!("CMP (ind),y", 3, |off| { let low = self.read_abs(mem, off, 0); let high = self.read_abs(mem, off.wrapping_add(1), 0); if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } let val = self.read_abs_y(mem, low, high); let v = self.a.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.a >= val); log!( "{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}", high, low, self.a, val, self.status ); }), 0xE0 => inst!("CPX imm", 0, |val| { let v = self.x.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.x >= val); log!( "{addr:04X}: CPX #${:02X} | {:02X} - {:02X} -> {:?}", val, self.x, val, self.status ); }), 0xE4 => inst!("CPX zp", 1, |off| { let val = self.read_abs(mem, off, 0); let v = self.x.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.x >= val); log!( "{addr:04X}: CPX ${:02X} | {:02X} - {:02X} -> {:?}", off, self.x, val, self.status ); }), 0xEC => inst!("CPX abs", 1, |low, high| { let val = self.read_abs(mem, low, high); let v = self.x.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.x >= val); log!( "{addr:04X}: CPX ${:02X}{:02X} | {:02X} - {:02X} -> {:?}", high, low, self.x, val, self.status ); }), 0xC0 => inst!("CPY imm", 0, |val| { let v = self.y.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.y >= val); log!( "{addr:04X}: CPY #${:02X} | {:02X} - {:02X} -> {:?}", val, self.y, val, self.status ); }), 0xC4 => inst!("CPY zp", 1, |off| { let val = self.read_abs(mem, off, 0); let v = self.y.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.y >= val); log!( "{addr:04X}: CPY ${:02X} | {:02X} - {:02X} -> {:?}", off, self.y, val, self.status ); }), 0xCC => inst!("CPY zp", 1, |low, high| { let val = self.read_abs(mem, low, high); let v = self.y.wrapping_sub(val); self.status.set_zero(v == 0); self.status.set_negative(v & 0x80 == 0x80); self.status.set_carry(self.y >= val); log!( "{addr:04X}: CPY ${:02X}{:02X} | {:02X} - {:02X} -> {:?}", high, low, self.y, val, self.status ); }), // Arithmetic 0x69 => inst!("ADC imm", 0, |val| { let (a, carry_1) = self.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ADC #${:02X} | {:02X}", val, self.a); }), 0x65 => inst!("ADC zp", 1, |off| { let val = self.read_abs(mem, off, 0); let (a, carry_1) = self.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ADC ${:02X} | {:02X}", off, self.a); }), 0x75 => inst!("ADC zp,x", 2, |off| { let val = self.read_zp_x(mem, off); let (a, carry_1) = self.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ADC ${:02X},x | {:02X}", off, self.a); }), 0x6D => inst!("ADC abs", 1, |low, high| { let val = self.read_abs(mem, low, high); let (a, carry_1) = self.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ADC ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0x7D => inst!("ADC abs,x", 1, |low, high| { if !oops && low.checked_add(self.x).is_none() { return ExecState::Oops; } let val = self.read_abs_x(mem, low, high); let (a, carry_1) = self.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: ADC ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0x79 => inst!("ADC abs,y", 1, |low, high| { if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } let val = self.read_abs_y(mem, low, high); let (a, carry_1) = self.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: ADC ${:02X}{:02X},y | {:02X}", high, low, self.a ); }), 0x61 => inst!("ADC (ind,x)", 4, |off| { let low = self.read_abs(mem, off, 0); let high = self.read_abs(mem, off.wrapping_add(1), 0); let val = mem.read(u16::from_le_bytes([low, high]) + self.y as u16); let (a, carry_1) = self.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: ADC (${:02X}{:02X},x) | {:02X}", high, low, self.a ); }), 0x71 => inst!("ADC (ind),y", 3, |off| { let low = self.read_abs(mem, off, 0); let high = self.read_abs(mem, off.wrapping_add(1), 0); if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } let val = mem.read(u16::from_le_bytes([low, high]) + self.y as u16); let (a, carry_1) = self.a.overflowing_add(val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: ADC (${:02X}{:02X}),y | {:02X}", high, low, self.a ); }), 0xE9 => inst!("SBC imm", 0, |val| { let (a, carry_1) = self.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0); self.a = a; // TODO: I'm pretty sure the carry logic is wrong here self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: SBC #${:02X} | {:02X}", val, self.a); }), 0xE5 => inst!("SBC zp", 1, |off| { let val = self.read_abs(mem, off, 0); let (a, carry_1) = self.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); }), 0xF5 => inst!("SBC zp,x", 2, |off| { let val = self.read_zp_x(mem, off); let (a, carry_1) = self.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: SBC ${:02X},x | {:02X}", off, self.a); }), 0xED => inst!("SBC abs", 1, |low, high| { let val = self.read_abs(mem, low, high); let (a, carry_1) = self.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: SBC ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0xFD => inst!("SBC abs,x", 1, |low, high| { if !oops && low.checked_add(self.x).is_none() { return ExecState::Oops; } let val = self.read_abs_x(mem, low, high); let (a, carry_1) = self.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: SBC ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0xF9 => inst!("SBC abs,y", 1, |low, high| { if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } let val = self.read_abs_y(mem, low, high); let (a, carry_1) = self.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: SBC ${:02X}{:02X},y | {:02X}", high, low, self.a ); }), 0xE1 => inst!("SBC (ind,x)", 4, |off| { let low = self.read_abs(mem, off, 0); let high = self.read_abs(mem, off.wrapping_add(1), 0); let val = mem.read(u16::from_le_bytes([low, high]) + self.y as u16); let (a, carry_1) = self.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: SBC (${:02X}{:02X},x) | {:02X}", high, low, self.a ); }), 0xF1 => inst!("SBC (ind),y", 3, |off| { let low = self.read_abs(mem, off, 0); let high = self.read_abs(mem, off.wrapping_add(1), 0); if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } let val = mem.read(u16::from_le_bytes([low, high]) + self.y as u16); let (a, carry_1) = self.a.overflowing_add(!val); let (a, carry_2) = a.overflowing_add(self.status.carry().into()); self.status .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0); self.a = a; self.status.set_carry(carry_1 | carry_2); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: SBC (${:02X}{:02X}),y | {:02X}", high, low, self.a ); }), 0xC6 => inst!("DEC zp", 3, |off| { let val = self.read_abs(mem, off, 0).wrapping_sub(1); self.write_abs(mem, off, 0, val); self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); log!("{addr:04X}: DEC ${:02X} | {:02X}", off, val); }), 0xD6 => inst!("DEC zp,x", 4, |off| { let val = self.read_zp_x(mem, off).wrapping_sub(1); self.write_zp_x(mem, off, val); self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); log!("{addr:04X}: DEC ${:02X},x | {:02X}", off, self.a); }), 0xCE => inst!("DEC abs", 3, |low, high| { let val = self.read_abs(mem, low, high).wrapping_sub(1); self.write_abs(mem, low, high, val); self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); log!("{addr:04X}: DEC ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0xDE => inst!("DEC abs,x", 4, |low, high| { let val = self.read_abs_x(mem, low, high).wrapping_sub(1); self.write_abs_x(mem, low, high, val); self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); log!( "{addr:04X}: DEC ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0xCA => inst!("DEX", 1, || { self.x = self.x.wrapping_sub(1); self.status.set_zero(self.x == 0); self.status.set_negative(self.x & 0x80 == 0x80); log!("{addr:04X}: DEX | {:02X}", self.x); }), 0x88 => inst!("DEY", 1, || { self.y = self.y.wrapping_sub(1); self.status.set_zero(self.y == 0); self.status.set_negative(self.y & 0x80 == 0x80); log!("{addr:04X}: DEY | {:02X}", self.y); }), 0xE6 => inst!("INC zp", 3, |off| { let val = self.read_abs(mem, off, 0).wrapping_add(1); self.write_abs(mem, off, 0, val); self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); log!("{addr:04X}: INC ${:02X} | {:02X}", off, val); }), 0xF6 => inst!("INC zp,x", 4, |off| { let val = self.read_zp_x(mem, off).wrapping_add(1); self.write_zp_x(mem, off, val); self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); log!("{addr:04X}: INC ${:02X},x | {:02X}", off, self.a); }), 0xEE => inst!("INC abs", 3, |low, high| { let val = self.read_abs(mem, low, high).wrapping_add(1); self.write_abs(mem, low, high, val); self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); log!("{addr:04X}: INC ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0xFE => inst!("INC abs,x", 4, |low, high| { let val = self.read_abs_x(mem, low, high).wrapping_add(1); self.write_abs_x(mem, low, high, val); self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); log!( "{addr:04X}: INC ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0xE8 => inst!("INX", 1, || { self.x = self.x.wrapping_add(1); self.status.set_zero(self.x == 0); self.status.set_negative(self.x & 0x80 == 0x80); log!("{addr:04X}: INX | {:02X}", self.x); }), 0xC8 => inst!("INY", 1, || { self.y = self.y.wrapping_add(1); self.status.set_zero(self.y == 0); self.status.set_negative(self.y & 0x80 == 0x80); log!("{addr:04X}: INY | {:02X}", self.y); }), // Logical 0x09 => inst!("ORA imm", 0, |val| { self.a |= val; self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ORA #${:02X} | {:02X}", val, self.a); }), 0x05 => inst!("ORA zp", 1, |off| { self.a |= self.read_abs(mem, off, 0); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ORA ${:02X} | {:02X}", off, self.a); }), 0x15 => inst!("ORA zp,x", 2, |off| { self.a |= self.read_zp_x(mem, off); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ORA ${:02X},x | {:02X}", off, self.a); }), 0x0D => inst!("ORA abs", 1, |low, high| { self.a |= self.read_abs(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ORA ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0x1D => inst!("ORA abs,x", 1, |low, high| { if !oops && low.checked_add(self.x).is_none() { return ExecState::Oops; } self.a |= self.read_abs_x(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: ORA ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0x19 => inst!("ORA abs,y", 1, |low, high| { if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } self.a |= self.read_abs_y(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: ORA ${:02X}{:02X},y | {:02X}", high, low, self.a ); }), 0x01 => inst!("ORA (ind,x)", 4, |off| { let low = self.read_zp_x(mem, off); let high = self.read_zp_x(mem, off.wrapping_add(1)); self.a |= self.read_abs(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: ORA (${:02X}{:02X},x) | {:02X}", high, low, self.a ); }), 0x11 => inst!("ORA (ind),y", 3, |off| { let low = self.read_abs(mem, off, 0); let high = self.read_abs(mem, off.wrapping_add(1), 0); if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } self.a |= mem.read(u16::from_le_bytes([low, high]) + self.y as u16); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: ORA (${:02X}{:02X}),y | {:02X}", high, low, self.a ); }), 0x29 => inst!("AND imm", 0, |val| { self.a &= val; self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: AND #${:02X} | {:02X}", val, self.a); }), 0x25 => inst!("AND zp", 1, |off| { self.a &= self.read_abs(mem, off, 0); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: AND ${:02X} | {:02X}", off, self.a); }), 0x35 => inst!("AND zp,x", 2, |off| { self.a &= self.read_zp_x(mem, off); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: AND ${:02X},x | {:02X}", off, self.a); }), 0x2D => inst!("AND abs", 1, |low, high| { self.a &= self.read_abs(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: AND ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0x3D => inst!("AND abs,x", 1, |low, high| { if !oops && low.checked_add(self.x).is_none() { return ExecState::Oops; } self.a &= self.read_abs_x(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: AND ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0x39 => inst!("AND abs,y", 1, |low, high| { if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } self.a &= self.read_abs_y(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: AND ${:02X}{:02X},y | {:02X}", high, low, self.a ); }), 0x21 => inst!("AND (ind,x)", 4, |off| { let low = self.read_zp_x(mem, off); let high = self.read_zp_x(mem, off.wrapping_add(1)); self.a &= self.read_abs(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: AND (${:02X}{:02X},x) | {:02X}", high, low, self.a ); }), 0x31 => inst!("AND (ind),y", 3, |off| { let low = self.read_abs(mem, off, 0); let high = self.read_abs(mem, off.wrapping_add(1), 0); if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } self.a &= mem.read(u16::from_le_bytes([low, high]) + self.y as u16); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: AND (${:02X}{:02X}),y | {:02X}", high, low, self.a ); }), 0x49 => inst!("EOR imm", 0, |val| { self.a ^= val; self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: EOR #${:02X} | {:02X}", val, self.a); }), 0x45 => inst!("EOR zp", 1, |off| { self.a ^= self.read_abs(mem, off, 0); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: EOR ${:02X} | {:02X}", off, self.a); }), 0x55 => inst!("EOR zp,x", 2, |off| { self.a ^= self.read_zp_x(mem, off); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: EOR ${:02X},x | {:02X}", off, self.a); }), 0x4D => inst!("EOR abs", 1, |low, high| { self.a ^= self.read_abs(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: EOR ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0x5D => inst!("EOR abs,x", 1, |low, high| { if !oops && low.checked_add(self.x).is_none() { return ExecState::Oops; } self.a ^= self.read_abs_x(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: EOR ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0x59 => inst!("EOR abs,y", 1, |low, high| { if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } self.a ^= self.read_abs_y(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: EOR ${:02X}{:02X},y | {:02X}", high, low, self.a ); }), 0x41 => inst!("EOR (ind,x)", 4, |off| { let low = self.read_zp_x(mem, off); let high = self.read_zp_x(mem, off.wrapping_add(1)); self.a ^= self.read_abs(mem, low, high); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: EOR (${:02X}{:02X},x) | {:02X}", high, low, self.a ); }), 0x51 => inst!("EOR (ind),y", 3, |off| { let low = self.read_abs(mem, off, 0); let high = self.read_abs(mem, off.wrapping_add(1), 0); if !oops && low.checked_add(self.y).is_none() { return ExecState::Oops; } self.a ^= mem.read(u16::from_le_bytes([low, high]) + self.y as u16); self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!( "{addr:04X}: EOR (${:02X}{:02X}),y | {:02X}", high, low, self.a ); }), 0x24 => inst!("BIT zp", 1, |off| { let val = self.read_abs(mem, off, 0); self.status.set_zero(val & self.a == 0); self.status.set_overflow(val & 0x40 == 0x40); self.status.set_negative(val & 0x80 == 0x80); log!("{addr:04X}: BIT ${:02X} | {:02X}", off, val); }), 0x2C => inst!("BIT abs", 1, |low, high| { let val = self.read_abs(mem, low, high); self.status.set_zero(val & self.a == 0); self.status.set_overflow(val & 0x40 == 0x40); self.status.set_negative(val & 0x80 == 0x80); log!("{addr:04X}: BIT ${:02X}{:02X} | {:02X}", high, low, val); }), // Shifts 0x4A => inst!("LSR A", 1, || { self.status.set_carry(self.a & 0b1 == 0b1); self.a = self.a >> 1; self.status.set_zero(self.a == 0); self.status.set_negative(false); log!("{addr:04X}: LSR A | {:02X}", self.a); }), 0x46 => inst!("LSR zp", 3, |off| { let val = self.read_abs(mem, off, 0); self.status.set_carry(val & 0b1 == 0b1); let val = val >> 1; self.status.set_zero(val == 0); self.status.set_negative(false); self.write_abs(mem, off, 0, val); log!("{addr:04X}: LSR ${:02X} | {:02X}", off, self.a); }), 0x56 => inst!("LSR zp,x", 4, |off| { let val = self.read_zp_x(mem, off); self.status.set_carry(val & 0b1 == 0b1); let val = val >> 1; self.status.set_zero(val == 0); self.status.set_negative(false); self.write_zp_x(mem, off, val); log!("{addr:04X}: LSR ${:02X},x | {:02X}", off, self.a); }), 0x4E => inst!("LSR abs", 3, |low, high| { let val = self.read_abs(mem, low, high); self.status.set_carry(val & 0b1 == 0b1); let val = val >> 1; self.status.set_zero(val == 0); self.status.set_negative(false); self.write_abs(mem, low, high, val); log!("{addr:04X}: LSR ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0x5E => inst!("LSR abs,x", 4, |low, high| { let val = self.read_abs_x(mem, low, high); self.status.set_carry(val & 0b1 == 0b1); let val = val >> 1; self.status.set_zero(val == 0); self.status.set_negative(false); self.write_abs_x(mem, low, high, val); log!( "{addr:04X}: LSR ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0x0A => inst!("ASL A", 1, || { self.status.set_carry(self.a & 0x80 == 0x80); self.a = self.a << 1; self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ASL A | {:02X}", self.a); }), 0x06 => inst!("ASL zp", 3, |off| { let val = self.read_abs(mem, off, 0); self.status.set_carry(val & 0x80 == 0x80); let val = val << 1; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_abs(mem, off, 0, val); log!("{addr:04X}: ASL ${:02X} | {:02X}", off, self.a); }), 0x16 => inst!("ASL zp,x", 4, |off| { let val = self.read_zp_x(mem, off); self.status.set_carry(val & 0x80 == 0x80); let val = val << 1; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_zp_x(mem, off, val); log!("{addr:04X}: ASL ${:02X},x | {:02X}", off, self.a); }), 0x0E => inst!("ASL abs", 3, |low, high| { let val = self.read_abs(mem, low, high); self.status.set_carry(val & 0x80 == 0x80); let val = val << 1; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_abs(mem, low, high, val); log!("{addr:04X}: ASL ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0x1E => inst!("ASL abs,x", 4, |low, high| { let val = self.read_abs_x(mem, low, high); self.status.set_carry(val & 0x80 == 0x80); let val = val << 1; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_abs_x(mem, low, high, val); log!( "{addr:04X}: ASL ${:02X}{:02X},x | {:02X}", high, low, self.a ); }), 0x6A => inst!("ROR A", 1, || { let old_carry = if self.status.carry() { 0x80 } else { 0x00 }; self.status.set_carry(self.a & 0b1 == 0b1); self.a = self.a >> 1 | old_carry; self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ROR A | {:02X}", self.a); }), 0x66 => inst!("ROR zp", 3, |off| { let old_carry = if self.status.carry() { 0x80 } else { 0x00 }; let val = self.read_abs(mem, off, 0); self.status.set_carry(val & 0b1 == 0b1); let val = val >> 1 | old_carry; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_abs(mem, off, 0, val); log!("{addr:04X}: ROR ${:02X} | {:02X}", off, self.a); }), 0x76 => inst!("ROR zp,x", 4, |off| { let old_carry = if self.status.carry() { 0x80 } else { 0x00 }; let val = self.read_zp_x(mem, off); self.status.set_carry(val & 0b1 == 0b1); let val = val >> 1 | old_carry; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_zp_x(mem, off, val); log!("{addr:04X}: ROR ${:02X},x | {:02X}", off, val); }), 0x6E => inst!("ROR abs", 3, |low, high| { let old_carry = if self.status.carry() { 0x80 } else { 0x00 }; let val = self.read_abs(mem, low, high); self.status.set_carry(val & 0b1 == 0b1); let val = val >> 1 | old_carry; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_abs(mem, low, high, val); log!("{addr:04X}: ROR ${:02X}{:02X} | {:02X}", high, low, val); }), 0x7E => inst!("ROR abs,x", 4, |low, high| { let old_carry = if self.status.carry() { 0x80 } else { 0x00 }; let val = self.read_abs_x(mem, low, high); self.status.set_carry(val & 0b1 == 0b1); let val = val >> 1 | old_carry; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_abs_x(mem, low, high, val); log!("{addr:04X}: ROR ${:02X}{:02X},x | {:02X}", high, low, val); }), 0x2A => inst!("ROL A", 1, || { let old_carry = if self.status.carry() { 0x01 } else { 0x00 }; self.status.set_carry(self.a & 0x80 == 0x80); self.a = self.a << 1 | old_carry; self.status.set_zero(self.a == 0); self.status.set_negative(self.a & 0x80 == 0x80); log!("{addr:04X}: ROL A | {:02X}", self.a); }), 0x26 => inst!("ROL zp", 3, |off| { let old_carry = if self.status.carry() { 0x01 } else { 0x00 }; let val = self.read_abs(mem, off, 0); self.status.set_carry(val & 0x80 == 0x80); let val = val << 1 | old_carry; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_abs(mem, off, 0, val); log!("{addr:04X}: ROL ${:02X} | {:02X}", off, val); }), 0x36 => inst!("ROL zp,x", 4, |off| { let old_carry = if self.status.carry() { 0x01 } else { 0x00 }; let val = self.read_zp_x(mem, off); self.status.set_carry(val & 0x80 == 0x80); let val = val << 1 | old_carry; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_zp_x(mem, off, val); log!("{addr:04X}: ROL ${:02X},x | {:02X}", off, val); }), 0x2E => inst!("ROL abs", 3, |low, high| { let old_carry = if self.status.carry() { 0x01 } else { 0x00 }; let val = self.read_abs(mem, low, high); self.status.set_carry(val & 0x80 == 0x80); let val = val << 1 | old_carry; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_abs(mem, low, high, val); log!("{addr:04X}: ROL ${:02X}{:02X} | {:02X}", high, low, self.a); }), 0x3E => inst!("ROL abs,x", 4, |low, high| { let old_carry = if self.status.carry() { 0x01 } else { 0x00 }; let val = self.read_abs_x(mem, low, high); self.status.set_carry(val & 0x80 == 0x80); let val = val << 1 | old_carry; self.status.set_zero(val == 0); self.status.set_negative(val & 0x80 == 0x80); self.write_abs_x(mem, low, high, val); log!("{addr:04X}: ROL ${:02X}{:02X},x | {:02X}", high, low, val); }), 0xEA => inst!("NOP", 1, || { log!("{addr:04X}: NOP"); }), // _ => todo!("ins: 0x{:04X}: 0x{ins:X}, {params:X?}", self.pc - 1), _ => { println!("ins: 0x{:04X}: 0x{ins:X}, {params:X?}", self.pc - 1); self.halt(); ExecState::Done } } } pub fn clock_cpu(&mut self, mem: &mut CpuMem<'_>, nmi: bool, irq: bool, br: &Break) -> bool { self.clock_state = match self.clock_state { ClockState::HoldNmi { cycles } => { if cycles == 0 { self.push_16(mem, self.pc); self.push(mem, self.status.0 | 0x20); self.pc = u16::from_le_bytes([mem.read(0xFFFA), mem.read(0xFFFB)]); writeln!(self.debug_log, "Starting NMI Handler").unwrap(); ClockState::ReadInstruction } else { ClockState::HoldNmi { cycles: cycles - 1 } } } ClockState::HoldIrq { cycles } => { if cycles == 0 { // todo!("Run IRQ"); self.push_16(mem, self.pc); self.push(mem, self.status.0 | 0b00110000); self.status.set_interrupt_disable(true); self.pc = u16::from_le_bytes([mem.read(0xFFFE), mem.read(0xFFFF)]); writeln!(self.debug_log, "Starting IRQ handler").unwrap(); ClockState::ReadInstruction } else { ClockState::HoldIrq { cycles: cycles - 1 } } } ClockState::ReadInstruction => { if br.cpu_exec { println!("Returning early from clock_cpu"); return true; } let addr = self.pc; let instruction = mem.read(self.pc); if instruction != 0x02 { self.pc = self.pc.wrapping_add(1); } match self.exec_instruction(mem, instruction, &[], false, false, addr) { ExecState::Done => ClockState::ReadInstruction, ExecState::MoreParams => ClockState::ReadOperands { instruction, ops: [0u8; 5], count: 0, addr, }, ExecState::Hold(cycles) => ClockState::Hold { cycles, instruction, ops: [0u8; 5], count: 0, addr, }, ExecState::Oops => ClockState::Oops { instruction, ops: [0u8; 5], count: 0, addr, }, ExecState::PageCross => ClockState::PageCross, } } ClockState::ReadOperands { instruction, mut ops, count, addr, } => { if count == 5 { todo!() } ops[count as usize] = mem.read(self.pc); self.pc = self.pc.wrapping_add(1); match self.exec_instruction( mem, instruction, &ops[..count as usize + 1], false, false, addr, ) { ExecState::Done => ClockState::ReadInstruction, ExecState::MoreParams => ClockState::ReadOperands { instruction, ops, count: count + 1, addr, }, ExecState::Hold(cycles) => ClockState::Hold { cycles, instruction, ops, count: count + 1, addr, }, ExecState::Oops => ClockState::Oops { instruction, ops, count: count + 1, addr, }, ExecState::PageCross => ClockState::PageCross, } } ClockState::Hold { cycles, instruction, ops, count, addr, } => { if cycles == 0 { match self.exec_instruction( mem, instruction, &ops[..count as usize], true, false, addr, ) { ExecState::Done => ClockState::ReadInstruction, 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, addr, }, ExecState::PageCross => ClockState::PageCross, } } else { ClockState::Hold { cycles: cycles - 1, instruction, ops, count, addr, } } } ClockState::Oops { instruction, ops, count, addr, } => { match self.exec_instruction( mem, instruction, &ops[..count as usize], true, true, addr, ) { ExecState::Done => ClockState::ReadInstruction, _ => panic!("Must execute after oops"), } } ClockState::PageCross => ClockState::ReadInstruction, }; if self.clock_state == ClockState::ReadInstruction { if self.nmi_pending { self.nmi_pending = false; self.clock_state = ClockState::HoldNmi { cycles: 6 }; writeln!(self.debug_log, "NMI detected").unwrap(); } else if self.irq_pending && !self.status.interrupt_disable() { writeln!(self.debug_log, "IRQ detected").unwrap(); self.clock_state = ClockState::HoldIrq { cycles: 6 }; } } // Check NMI and IRQ line state. This happens in phi2, i.e. the second half of the instruction // let new_line_state = self.ppu.nmi_waiting() || self.apu.nmi_waiting(); if nmi != self.nmi_line_state { // println!("ppu: {}, apu: {}", self.ppu.nmi_waiting(), self.apu.nmi_waiting()); // println!("New: {new_line_state}, old: {}", self.nmi_line_state); self.nmi_line_state = nmi; self.nmi_pending |= nmi; } // self.irq_pending = self.ppu.irq_waiting() || self.apu.irq_waiting(); self.irq_pending = irq; false } pub fn cpu_cycle_update(&mut self) { if !self.halted { self.cycle += 1; } } pub fn executed(&self) -> bool { matches!(self.clock_state, ClockState::ReadInstruction) } pub fn halted(&self) -> bool { self.halted } pub(crate) fn unhalt(&mut self) { self.halted = false; } pub fn reset(&mut self, mem: &mut CpuMem<'_>) { let debug_log = self.debug_log.enabled(); *self = Self::init(); if debug_log { self.debug_log.enable(); } self.pc = u16::from_le_bytes([mem.read(0xFFFC), mem.read(0xFFFD)]); self.sp = 0xFD; self.status.set_interrupt_disable(true); } } #[derive(Debug, Clone, Copy)] pub enum DmaState { Passive, Idle(u16), Running { cpu_addr: u16, rem: u8, read: Option, }, } impl DmaState { pub fn reset(&mut self) { *self = Self::Passive; } pub fn cycle(self, mem: &mut CpuMem<'_>, cpu: &mut Cpu) -> Option { match self { DmaState::Passive => None, DmaState::Idle(cpu_addr) => Some(DmaState::Running { cpu_addr, rem: 0xFF, read: None, }), DmaState::Running { cpu_addr, rem, read: Some(read), } => { mem.write(0x2004, read); // self.ppu.write_reg(4, read); writeln!(&mut cpu.debug_log, "OAM DMA write {:02X}", read).unwrap(); if rem == 0 { Some(DmaState::Passive) } else { Some(DmaState::Running { cpu_addr: cpu_addr + 1, rem: rem - 1, read: None, }) } } DmaState::Running { cpu_addr, rem, read: None, } => { if cpu.cycle % 2 == 0 { writeln!(&mut cpu.debug_log, "DMA: Waiting for cycle").unwrap(); return Some(self); } let read = mem.read(cpu_addr); writeln!( &mut cpu.debug_log, "OAM DMA read {:04X} {:02X}", cpu_addr, read ) .unwrap(); Some(DmaState::Running { cpu_addr, rem, read: Some(read), }) } } } } pub struct CycleResult { pub cpu_exec: bool, pub ppu_frame: bool, pub dma: bool, pub dbg_int: bool, }