Files
nes-emu/src/cpu.rs

2027 lines
82 KiB
Rust

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 = &params[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<u8>,
},
}
impl DmaState {
pub fn reset(&mut self) {
*self = Self::Passive;
}
pub fn cycle(self, mem: &mut CpuMem<'_>, cpu: &mut Cpu) -> Option<Self> {
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,
}