From f861f75b214bafb8e17e1d4c5ffba48b79415a11 Mon Sep 17 00:00:00 2001 From: Matthew Pomes Date: Sat, 24 Jan 2026 03:38:42 -0600 Subject: [PATCH] Major refactor - CPU is now it's own module - Memory object is now shared to support mapper chips - ROM is now stored as `Arc<[u8]>` to support mapper chips --- Cargo.lock | 36 + Cargo.toml | 1 + src/apu.rs | 58 +- src/controllers.rs | 1 + src/cpu.rs | 2005 ++++++++++++++++++++++++++++ src/debug.rs | 1 + src/debugger.rs | 51 +- src/hex_view.rs | 93 +- src/lib.rs | 2285 ++------------------------------ src/main.rs | 100 +- src/mem.rs | 664 ++++++++-- src/ppu.rs | 126 +- src/test_roms/instr_test_v3.rs | 10 +- src/test_roms/interrupts.rs | 22 +- src/test_roms/mod.rs | 48 +- src/test_roms/ppu.rs | 20 +- 16 files changed, 3071 insertions(+), 2450 deletions(-) create mode 100644 src/cpu.rs diff --git a/Cargo.lock b/Cargo.lock index b029a7f..75b5b12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -871,6 +871,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags 2.10.0", + "block2 0.6.2", + "libc", "objc2 0.6.3", ] @@ -2195,6 +2197,7 @@ version = "0.1.0" dependencies = [ "bitfield", "iced", + "rfd", "thiserror 2.0.17", "tokio", "tracing", @@ -2875,6 +2878,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -3172,6 +3181,33 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rfd" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220" +dependencies = [ + "block2 0.6.2", + "dispatch2", + "js-sys", + "libc", + "log", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "percent-encoding", + "pollster", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "web-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rgb" version = "0.8.52" diff --git a/Cargo.toml b/Cargo.toml index 3c30140..39abda1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" bitfield = "0.19.3" # iced = { version = "0.14.0", features = ["debug", "canvas", "tokio", "lazy", "image", "advanced"] } iced = { path = "../iced", features = ["debug", "canvas", "tokio", "lazy", "image", "advanced"] } +rfd = "0.17.2" # iced_graphics = { version = "0.14.0", features = ["geometry", "image"] } # iced_widget = { version = "0.13.4", features = ["canvas", "image"] } thiserror = "2.0.17" diff --git a/src/apu.rs b/src/apu.rs index 075a05f..c92a3ae 100644 --- a/src/apu.rs +++ b/src/apu.rs @@ -1,6 +1,7 @@ use tracing::debug; bitfield::bitfield! { + #[derive(Clone, Copy, PartialEq, Eq)] pub struct DutyVol(u8); impl Debug; duty, set_duty: 7, 6; @@ -10,6 +11,7 @@ bitfield::bitfield! { } bitfield::bitfield! { + #[derive(Clone, Copy, PartialEq, Eq)] pub struct Sweep(u8); impl Debug; enable, set_enable: 7; @@ -19,12 +21,14 @@ bitfield::bitfield! { } bitfield::bitfield! { + #[derive(Clone, Copy, PartialEq, Eq)] pub struct LengthTimerHigh(u8); impl Debug; length, set_length: 7, 3; timer_high, set_timer_high: 2, 0; } +#[derive(Debug, Clone)] struct PulseChannel { enabled: bool, duty_vol: DutyVol, @@ -92,12 +96,15 @@ bitfield::bitfield! { value, set_value: 6, 0; } +#[derive(Debug, Clone)] struct TriangleChannel { enabled: bool, } +#[derive(Debug, Clone)] struct NoiseChannel { enabled: bool, } +#[derive(Debug, Clone)] struct DeltaChannel { enabled: bool, } @@ -108,6 +115,7 @@ impl DeltaChannel { } } +#[derive(Debug, Clone)] struct FrameCounter { count: usize, mode_5_step: bool, @@ -115,6 +123,7 @@ struct FrameCounter { irq: bool, } +#[derive(Clone)] pub struct APU { pulse_1: PulseChannel, pulse_2: PulseChannel, @@ -185,6 +194,14 @@ impl APU { self.pulse_2.length_timer_high.0 = val; self.pulse_2.reset(); } + 0x09 => (), // Unused, technically noise channel? + 0x08 | 0x0A | 0x0B => { + // TODO: Triangle channel + } + 0x0D => (), // Unused, technically noise channel? + 0x0C | 0x0E | 0x0F => { + // TODO: Noise channel + } 0x10 => { assert_eq!(val, 0x00); // TODO: implement this value @@ -192,6 +209,12 @@ impl APU { 0x11 => { // TODO: load dmc counter with (val & 7F) } + 0x12 => { + // TODO: DMC + } + 0x13 => { + // TODO: DMC + } 0x15 => { self.dmc.enabled = val & 0b0001_0000 != 0; self.noise.enabled = val & 0b0000_1000 != 0; @@ -200,7 +223,7 @@ impl APU { self.pulse_1.enabled = val & 0b0000_0001 != 0; } 0x17 => { - self.frame_counter.mode_5_step = val & 0b1000_0000 == 0; + self.frame_counter.mode_5_step = val & 0b1000_0000 != 0; self.frame_counter.interrupt_enabled = val & 0b0100_0000 == 0; if !self.frame_counter.interrupt_enabled { self.frame_counter.irq = false; @@ -225,24 +248,23 @@ impl APU { pub fn run_one_clock_cycle(&mut self, ppu_cycle: usize) -> bool { if ppu_cycle % 6 == 1 { // APU Frame Counter clock cycle - if self.frame_counter.mode_5_step { - todo!() - } else { - if self.frame_counter.count == 3728 { - self.q_frame_clock(); - } else if self.frame_counter.count == 7456 { - self.h_frame_clock(); - } else if self.frame_counter.count == 11185 { - self.q_frame_clock(); - } else if self.frame_counter.count == 14914 { - self.frame_counter.irq = self.frame_counter.interrupt_enabled; - self.h_frame_clock(); - } else if self.frame_counter.count == 14915 { - self.frame_counter.count = 0; - self.frame_counter.irq = self.frame_counter.interrupt_enabled; - } - } self.frame_counter.count += 1; + if self.frame_counter.count == 3728 { + self.q_frame_clock(); + } else if self.frame_counter.count == 7456 { + self.h_frame_clock(); + } else if self.frame_counter.count == 11185 { + self.q_frame_clock(); + } else if self.frame_counter.count == 14914 && !self.frame_counter.mode_5_step { + self.frame_counter.irq = self.frame_counter.interrupt_enabled; + self.h_frame_clock(); + } else if self.frame_counter.count == 14915 && !self.frame_counter.mode_5_step { + self.frame_counter.count = 0; + self.frame_counter.irq = self.frame_counter.interrupt_enabled; + } else if self.frame_counter.count == 18640 { + self.h_frame_clock(); + self.frame_counter.count = 0; + } self.pulse_1.clock(); self.pulse_2.clock(); diff --git a/src/controllers.rs b/src/controllers.rs index e4b1390..34181b1 100644 --- a/src/controllers.rs +++ b/src/controllers.rs @@ -1,3 +1,4 @@ +#[derive(Debug, Clone)] pub struct Controllers {} impl Controllers { diff --git a/src/cpu.rs b/src/cpu.rs new file mode 100644 index 0000000..b586bc6 --- /dev/null +++ b/src/cpu.rs @@ -0,0 +1,2005 @@ +use std::fmt::Write; +use tracing::debug; + +use crate::{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 == 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 == 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), + } + } + + fn clock_cpu(&mut self, mem: &mut CpuMem<'_>, nmi: bool, irq: 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 => { + 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; + } + + pub fn cpu_cycle( + &mut self, + mem: &mut CpuMem<'_>, + dma: &mut DmaState, + nmi: bool, + irq: bool, + ) { + match *dma { + DmaState::Passive => self.clock_cpu(mem, nmi, irq), + // TODO: Validate that this takes the correct number of cycles (513 or 514 cycles) + DmaState::Running { + cpu_addr, + rem, + read: Some(read), + } => { + mem.write(0x2004, read); + // self.ppu.write_reg(4, read); + debug!("OAM DMA write {:02X}", read); + if rem == 0 { + *dma = DmaState::Passive; + } else { + *dma = DmaState::Running { + cpu_addr: cpu_addr + 1, + rem: rem - 1, + read: None, + }; + } + } + DmaState::Running { + cpu_addr, + rem, + read: None, + } => { + let read = mem.read(cpu_addr); + debug!("OAM DMA read {:04X} {:02X}", cpu_addr, read); + *dma = DmaState::Running { + cpu_addr, + rem, + read: Some(read), + }; + } + } + // if [0x8031, 0x8014].contains(&self.cpu.pc) { + // self.dbg_int = true; + // } + } + 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<'_>) { + *self = Self::init(); + 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, + Running { + cpu_addr: u16, + rem: u8, + read: Option, + }, +} + +impl DmaState { + pub fn merge(&mut self, other: DmaState) { + if matches!(other, DmaState::Running { .. }) { + if matches!(self, DmaState::Running { .. }) { + panic!("Merging incompatible dma states"); + } else { + *self = other; + } + } + } +} + +pub struct CycleResult { + pub cpu_exec: bool, + pub ppu_frame: bool, + pub dma: bool, + pub dbg_int: bool, +} diff --git a/src/debug.rs b/src/debug.rs index 0f413c5..f2786c3 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,5 +1,6 @@ use std::num::NonZeroUsize; +#[derive(Debug, Clone)] pub struct DebugLog { current: String, history: Vec, diff --git a/src/debugger.rs b/src/debugger.rs index 01990f8..668b48a 100644 --- a/src/debugger.rs +++ b/src/debugger.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use iced::{ Element, Length::{self, Fill}, - Point, Renderer, Size, Theme, + Point, Renderer, Size, advanced::{ Widget, layout::Node, @@ -18,14 +18,13 @@ use iced::{ canvas::{Frame, Program}, checkbox, column, container::bordered_box, - image, number_input, hex_input, row, + hex_input, image, number_input, row, rule::horizontal, scrollable, text, }, - window::Event, }; -use crate::{CycleResult, NES, PPU}; +use crate::{CycleResult, NES, PPU, mem::Mapped}; #[derive(Debug, Clone)] pub struct DebuggerState { @@ -117,7 +116,7 @@ impl DebuggerState { labelled("X:", text(format!("{:02X}", nes.cpu.x))), labelled("Y:", text(format!("{:02X}", nes.cpu.y))), labelled("PC:", text(format!("{:04X}", nes.cpu.pc))), - labelled("Cycle:", text(format!("{}", nes.cycle))), + labelled("Cycle:", text(format!("{}", nes.cpu_cycle()))), labelled("SP:", text(format!("{:02X}", nes.cpu.sp))), ] .spacing(5.), @@ -255,7 +254,7 @@ impl DebuggerState { fn run_n_clock_cycles(nes: &mut NES, n: usize) { for _ in 0..n { - if nes.run_one_clock_cycle().dbg_int || nes.halted { + if nes.run_one_clock_cycle().dbg_int || nes.halted() { break; } } @@ -269,7 +268,7 @@ impl DebuggerState { break; } } - if nes.halted { + if nes.halted() { break; } } @@ -294,8 +293,10 @@ impl DebuggerState { Self::run_until(nes, |_, n| n.ppu.scanline == self.to_scan_line, 1) } DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 262), - DebuggerMessage::RunBreakpoint => Self::run_until(nes, |_, nes| nes.cpu.pc as usize == self.breakpoint, 1), - DebuggerMessage::Run => Self::run_until(nes, |_, nes| nes.halted, 1), + DebuggerMessage::RunBreakpoint => { + Self::run_until(nes, |_, nes| nes.cpu.pc as usize == self.breakpoint, 1) + } + DebuggerMessage::Run => Self::run_until(nes, |_, nes| nes.halted(), 1), DebuggerMessage::Pause => todo!(), } } @@ -366,9 +367,9 @@ pub fn labelled_box<'a, Message: 'a>(label: &'a str, value: bool) -> Element<'a, #[derive(Clone, Copy)] pub enum DbgImage<'a> { - NameTable(&'a PPU), - PatternTable(&'a PPU), - Palette(&'a PPU), + NameTable(&'a Mapped, &'a PPU), + PatternTable(&'a Mapped, &'a PPU), + Palette(&'a Mapped, &'a PPU), } impl Program for DbgImage<'_> { @@ -388,9 +389,11 @@ impl Program for DbgImage<'_> { name_table_frame.scale(2.); // println!("Position: {:?}", cursor.position()); match self { - DbgImage::NameTable(ppu) => ppu.render_name_table(&mut name_table_frame), - DbgImage::PatternTable(ppu) => ppu.render_pattern_tables(&mut name_table_frame), - DbgImage::Palette(ppu) => ppu.render_palette(&mut name_table_frame), + DbgImage::NameTable(mem, ppu) => ppu.render_name_table(mem, &mut name_table_frame), + DbgImage::PatternTable(mem, ppu) => { + ppu.render_pattern_tables(mem, &mut name_table_frame) + } + DbgImage::Palette(_, ppu) => ppu.render_palette(&mut name_table_frame), }; vec![name_table_frame.into_geometry()] } @@ -399,23 +402,23 @@ impl Program for DbgImage<'_> { impl DbgImage<'_> { fn width(&self) -> Length { match self { - DbgImage::NameTable(_) => Length::Fixed(512. * 2.), - DbgImage::PatternTable(_) => Length::Fixed(16. * 8. * 2.), - DbgImage::Palette(_) => Length::Fixed(40. * 2.), + DbgImage::NameTable(_, _) => Length::Fixed(512. * 2.), + DbgImage::PatternTable(_, _) => Length::Fixed(16. * 8. * 2.), + DbgImage::Palette(_, _) => Length::Fixed(40. * 2.), } } fn height(&self) -> Length { match self { - DbgImage::NameTable(_) => Length::Fixed(512. * 2.), - DbgImage::PatternTable(_) => Length::Fixed(16. * 8. * 2. * 2.), - DbgImage::Palette(_) => Length::Fixed(80. * 2.), + DbgImage::NameTable(_, _) => Length::Fixed(512. * 2.), + DbgImage::PatternTable(_, _) => Length::Fixed(16. * 8. * 2. * 2.), + DbgImage::Palette(_, _) => Length::Fixed(80. * 2.), } } fn help(&self, cursor: Point) -> Option { match self { - DbgImage::NameTable(ppu) => ppu.name_cursor_info(cursor), - DbgImage::PatternTable(ppu) => ppu.pattern_cursor_info(cursor), - DbgImage::Palette(ppu) => ppu.palette_cursor_info(cursor), + DbgImage::NameTable(mem, ppu) => ppu.name_cursor_info(mem, cursor), + DbgImage::PatternTable(_, ppu) => ppu.pattern_cursor_info(cursor), + DbgImage::Palette(_, ppu) => ppu.palette_cursor_info(cursor), } } } diff --git a/src/hex_view.rs b/src/hex_view.rs index c39ef37..885245a 100644 --- a/src/hex_view.rs +++ b/src/hex_view.rs @@ -1,19 +1,48 @@ use std::fmt; -use iced::{widget::{column, lazy, row, text}, Element, Font, Length::Fill, Task}; +use iced::{ + Element, Font, + Length::Fill, + Task, + widget::{column, lazy, row, text}, +}; + +use crate::mem::Mapped; pub trait Memory { fn peek(&self, val: u16) -> Option; fn edit_ver(&self) -> usize; } +pub struct Cpu<'a>(pub &'a Mapped); + +impl Memory for Cpu<'_> { + fn peek(&self, val: u16) -> Option { + self.0.peek_cpu(val) + } + + fn edit_ver(&self) -> usize { + self.0.cpu_edit_ver() + } +} + +pub struct Ppu<'a>(pub &'a Mapped); + +impl Memory for Ppu<'_> { + fn peek(&self, val: u16) -> Option { + self.0.peek_ppu(val) + } + + fn edit_ver(&self) -> usize { + self.0.ppu_edit_ver() + } +} + #[derive(Debug, Clone)] pub enum HexEvent {} #[derive(Debug, Clone, PartialEq, Eq)] -pub struct HexView { - -} +pub struct HexView {} struct Val(Option); impl fmt::Display for Val { @@ -31,25 +60,59 @@ impl HexView { Self {} } - pub fn render<'a>(&self, mem: &'a impl Memory) -> Element<'a, HexEvent> { - struct Row<'a, M>(u16, &'a M); - impl fmt::Display for Row<'_, M> { + pub fn render<'a>(&self, mem: &'a Mapped, ppu: bool) -> Element<'a, HexEvent> { + struct Row<'a>(u16, &'a Mapped, bool); + impl fmt::Display for Row<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", Val(self.1.peek(self.0)))?; + write!( + f, + "{}", + Val(if self.2 { + self.1.peek_ppu(self.0) + } else { + self.1.peek_cpu(self.0) + }) + )?; for i in 1..16 { - write!(f, " {}", Val(self.1.peek(self.0 + i)))?; + write!( + f, + " {}", + Val(if self.2 { + self.1.peek_ppu(self.0 + i) + } else { + self.1.peek_cpu(self.0 + i) + }) + )?; } Ok(()) } } column![ text!("Hex view"), - iced::widget::scrollable(lazy(mem.edit_ver(), |_| column([ - text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F").font(Font::MONOSPACE).into() - ].into_iter().chain((0..u16::MAX).step_by(16).map(|off| { - text!(" {off:04X} | {}", Row(off, mem)).font(Font::MONOSPACE).into() - }))))).width(Fill), - ].width(Fill).into() + iced::widget::scrollable(lazy( + if ppu { + mem.ppu_edit_ver() + } else { + mem.cpu_edit_ver() + }, + move |_| column( + [ + text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F") + .font(Font::MONOSPACE) + .into() + ] + .into_iter() + .chain((0..u16::MAX).step_by(16).map(|off| { + text!(" {off:04X} | {}", Row(off, mem, ppu)) + .font(Font::MONOSPACE) + .into() + })) + ) + )) + .width(Fill), + ] + .width(Fill) + .into() } pub fn update(&mut self, ev: HexEvent) -> Task { diff --git a/src/lib.rs b/src/lib.rs index c362941..c34d87f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod apu; mod controllers; +mod cpu; pub mod debug; pub mod debugger; pub mod header_menu; @@ -11,18 +12,18 @@ pub mod resize_watcher; mod test_roms; pub use ppu::{Color, PPU, RenderBuffer}; +use tokio::io::AsyncReadExt as _; -use std::{fmt::Write as _, fs::File, io::Read, path::Path}; +use std::{fs::File, io::Read, path::Path}; use thiserror::Error; -use tracing::{debug, info}; use crate::{ apu::APU, controllers::Controllers, + cpu::{CPUMMRegisters, ClockState, Cpu, CycleResult, DmaState}, debug::DebugLog, - hex_view::Memory, - mem::{Mapper, MemoryMap, Segment}, + mem::{CpuMem, Mapped, PpuMem}, }; #[derive(Error, Debug)] @@ -34,21 +35,18 @@ pub enum NESError { } type Result = std::result::Result; +#[derive(Clone)] pub struct NES { clock_count: usize, + // memory: MemoryMap, + mapped: Mapped, cpu: Cpu, dma: DmaState, - memory: MemoryMap, - mapper: Mapper, ppu: PPU, apu: APU, controller: Controllers, - cycle: usize, - last_instruction: String, dbg_int: bool, - halted: bool, - debug_log: DebugLog, } impl std::fmt::Debug for NES { @@ -60,242 +58,61 @@ impl std::fmt::Debug for NES { self.cpu.a, self.cpu.x, self.cpu.y, self.cpu.status, self.cpu.sp, self.cpu.pc )?; writeln!(f, " Decode {:X?}", self.cpu.clock_state)?; - writeln!(f, " Last instruction: {}", self.last_instruction)?; - writeln!(f, " Cycle: CPU={}, PPU={}", self.cycle, self.ppu.cycle)?; - writeln!( - f, - "MEM: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", - self.peek(self.cpu.pc), - self.peek(self.cpu.pc + 1), - self.peek(self.cpu.pc + 2), - self.peek(self.cpu.pc + 3), - self.peek(self.cpu.pc + 4), - self.peek(self.cpu.pc + 5), - self.peek(self.cpu.pc + 6), - self.peek(self.cpu.pc + 7), - self.peek(self.cpu.pc + 8), - self.peek(self.cpu.pc + 9), - )?; - writeln!( - f, - "0x1F0: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", - self.peek(0x1F0), - self.peek(0x1F1), - self.peek(0x1F2), - self.peek(0x1F3), - self.peek(0x1F4), - self.peek(0x1F5), - self.peek(0x1F6), - self.peek(0x1F7), - self.peek(0x1F8), - self.peek(0x1F9), - self.peek(0x1FA), - self.peek(0x1FB), - self.peek(0x1FC), - self.peek(0x1FD), - self.peek(0x1FE), - self.peek(0x1FF), - )?; - writeln!( - f, - "STACK: {:02X} {:02X} | {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", - self.peek(self.cpu.sp as u16 + 0xFF), - self.peek(self.cpu.sp as u16 + 0x100), - self.peek(self.cpu.sp as u16 + 0x101), - self.peek(self.cpu.sp as u16 + 0x102), - self.peek(self.cpu.sp as u16 + 0x103), - self.peek(self.cpu.sp as u16 + 0x104), - self.peek(self.cpu.sp as u16 + 0x105), - self.peek(self.cpu.sp as u16 + 0x106), - self.peek(self.cpu.sp as u16 + 0x107), - self.peek(self.cpu.sp as u16 + 0x108), - )?; + writeln!(f, " Last instruction: {}", self.cpu.last_instruction)?; + writeln!(f, " Cycle: CPU={}, PPU={}", self.cpu.cycle, self.ppu.cycle)?; + // writeln!( + // f, + // "MEM: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", + // self.peek(self.cpu.pc), + // self.peek(self.cpu.pc + 1), + // self.peek(self.cpu.pc + 2), + // self.peek(self.cpu.pc + 3), + // self.peek(self.cpu.pc + 4), + // self.peek(self.cpu.pc + 5), + // self.peek(self.cpu.pc + 6), + // self.peek(self.cpu.pc + 7), + // self.peek(self.cpu.pc + 8), + // self.peek(self.cpu.pc + 9), + // )?; + // writeln!( + // f, + // "0x1F0: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", + // self.peek(0x1F0), + // self.peek(0x1F1), + // self.peek(0x1F2), + // self.peek(0x1F3), + // self.peek(0x1F4), + // self.peek(0x1F5), + // self.peek(0x1F6), + // self.peek(0x1F7), + // self.peek(0x1F8), + // self.peek(0x1F9), + // self.peek(0x1FA), + // self.peek(0x1FB), + // self.peek(0x1FC), + // self.peek(0x1FD), + // self.peek(0x1FE), + // self.peek(0x1FF), + // )?; + // writeln!( + // f, + // "STACK: {:02X} {:02X} | {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}", + // self.peek(self.cpu.sp as u16 + 0xFF), + // self.peek(self.cpu.sp as u16 + 0x100), + // self.peek(self.cpu.sp as u16 + 0x101), + // self.peek(self.cpu.sp as u16 + 0x102), + // self.peek(self.cpu.sp as u16 + 0x103), + // self.peek(self.cpu.sp as u16 + 0x104), + // self.peek(self.cpu.sp as u16 + 0x105), + // self.peek(self.cpu.sp as u16 + 0x106), + // self.peek(self.cpu.sp as u16 + 0x107), + // self.peek(self.cpu.sp as u16 + 0x108), + // )?; write!(f, "PPU: {:?}", self.ppu)?; Ok(()) } } -bitfield::bitfield! { - 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)] -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, -} - -#[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, - }, -} - -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(), - } - } -} - -enum ExecState { - Done, - MoreParams, - Hold(u8), - Oops, -} - -pub enum CPUMMRegisters { - PPU, - APU, -} - -impl Cpu { - fn init() -> Self { - Self { - a: 0, - x: 0, - y: 0, - pc: 0, - sp: 0, - status: CpuStatus(0), - clock_state: ClockState::Hold { - cycles: 0, - instruction: 0xEA, - ops: [0; 5], - count: 0, - addr: 0, - }, - nmi_pending: false, - irq_pending: false, - nmi_line_state: false, - } - } -} - -pub enum DmaState { - Passive, - Running { - cpu_addr: u16, - rem: u8, - read: Option, - }, -} - -pub struct CycleResult { - pub cpu_exec: bool, - pub ppu_frame: bool, - pub dma: bool, - pub dbg_int: bool, -} - impl NES { pub fn load_nes_file(file: impl AsRef) -> Result { let mut raw = Vec::new(); @@ -303,1690 +120,41 @@ impl NES { Self::load_nes_file_mem(&raw) } + pub async fn async_load_nes_file(file: impl AsRef) -> Result { + let mut raw = Vec::new(); + tokio::fs::File::open(file) + .await? + .read_to_end(&mut raw) + .await?; + Self::load_nes_file_mem(&raw) + } + pub fn load_nes_file_mem(raw: &[u8]) -> Result { if &raw[0..4] != &[0x4E, 0x45, 0x53, 0x1A] { return Err(NESError::InvalidFile); } - let prg_rom_size = raw[4]; - let chr_rom_size = raw[5]; - let mapper_flags = raw[6]; - // let nes_20 = raw[7]; - info!("PRG: {prg_rom_size}"); - info!("CHR: {chr_rom_size}"); - info!("FLAGS: {mapper_flags:b}"); - Ok(Self::from_rom( - &raw[16..][..16384 * prg_rom_size as usize], - &raw[16 + 16384 * prg_rom_size as usize..][..8192 * chr_rom_size as usize], - Mapper::from_flags(mapper_flags), - )) + let mapper = Mapped::from_rom(raw); + Ok(Self::from_mem(mapper)) } - fn from_rom(prg_rom: &[u8], chr_rom: &[u8], mapper: Mapper) -> Self { - let mut segments = vec![ - Segment::ram("Internal RAM", 0x0000, 0x0800), - Segment::mirror("Mirror of iRAM", 0x0800, 0x1800, 0), - Segment::reg("PPU registers", 0x2000, 0x0008, CPUMMRegisters::PPU), - Segment::mirror("Mirror of PPU", 0x2008, 0x1FF8, 2), - Segment::reg("APU & IO registers", 0x4000, 0x0018, CPUMMRegisters::APU), - Segment::mirror("Mirror of APU & IO", 0x4018, 0x0008, 2), - ]; - // let mut cur = 0x4020; - assert!( - prg_rom.len() <= 0x8000, - "Mappers for larger sizes not supported" - ); - segments.push(Segment::rom( - "PROG ROM", - 0x8000 + (0x8000 - prg_rom.len() as u16), - prg_rom, - )); + fn from_mem(mapped: Mapped) -> Self { Self { - cycle: 7, dbg_int: false, - halted: false, - debug_log: DebugLog::new(), clock_count: 0, - memory: MemoryMap::new(segments), - mapper, + mapped, cpu: Cpu::init(), - ppu: PPU::with_chr_rom(chr_rom, mapper), + dma: DmaState::Passive, + // ppu: PPU::with_chr_rom(chr_rom, mapper), + ppu: PPU::init(), apu: APU::init(), controller: Controllers::init(), - dma: DmaState::Passive, - last_instruction: "".into(), } } - pub fn read(&mut self, addr: u16) -> u8 { - self.memory.read(addr).reg_map(|reg, offset| match reg { - CPUMMRegisters::PPU => self.ppu.read_reg(offset), - CPUMMRegisters::APU => match offset { - 0x16 => self.controller.read_joy1(), - 0x17 => self.controller.read_joy2(), - _ => self.apu.read_reg(offset), - }, - }) - } - - pub fn peek(&self, addr: u16) -> u8 { - self.memory.read(addr).reg_map(|_, _| todo!()) - } - - pub fn write(&mut self, addr: u16, val: u8) { - self.memory.write(addr, val, |reg, offset, val| match reg { - CPUMMRegisters::PPU => self.ppu.write_reg(offset, val), - CPUMMRegisters::APU => match offset { - 0x14 => { - self.dma = DmaState::Running { - cpu_addr: u16::from_le_bytes([0, val]), - rem: 0xFF, - read: None, - } - } - 0x16 => self.controller.write_joy_strobe(val), - _ => self.apu.write_reg(offset, val), - }, - }); - } - - pub fn read_abs(&mut self, low: u8, high: u8) -> u8 { - self.read(u16::from_le_bytes([low, high])) - } - pub fn read_abs_x(&mut self, low: u8, high: u8) -> u8 { - self.read(u16::from_le_bytes([low, high]) + self.cpu.x as u16) - } - pub fn read_abs_y(&mut self, low: u8, high: u8) -> u8 { - self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16) - } - pub fn read_zp_x(&mut self, low: u8) -> u8 { - self.read(u16::from_le_bytes([low.wrapping_add(self.cpu.x), 0])) - } - pub fn read_zp_y(&mut self, low: u8) -> u8 { - self.read(u16::from_le_bytes([low.wrapping_add(self.cpu.y), 0])) - } - pub fn write_abs(&mut self, low: u8, high: u8, val: u8) { - self.write(u16::from_le_bytes([low, high]), val) - } - pub fn write_abs_x(&mut self, low: u8, high: u8, val: u8) { - self.write(u16::from_le_bytes([low, high]) + self.cpu.x as u16, val) - } - pub fn write_abs_y(&mut self, low: u8, high: u8, val: u8) { - self.write(u16::from_le_bytes([low, high]) + self.cpu.y as u16, val) - } - pub fn write_zp_x(&mut self, low: u8, val: u8) { - self.write(u16::from_le_bytes([low.wrapping_add(self.cpu.x), 0]), val) - } - pub fn write_zp_y(&mut self, low: u8, val: u8) { - self.write(u16::from_le_bytes([low.wrapping_add(self.cpu.y), 0]), val) - } - - pub fn push(&mut self, val: u8) { - self.write(self.cpu.sp as u16 + 0x100, val); - self.cpu.sp = self.cpu.sp.wrapping_sub(1); - } - pub fn pop(&mut self) -> u8 { - self.cpu.sp = self.cpu.sp.wrapping_add(1); - self.read(self.cpu.sp as u16 + 0x100) - } - pub fn push_16(&mut self, val: u16) { - debug!("Writing rc = {:04X} at {:02X}", val, self.cpu.sp); - let [low, high] = val.to_le_bytes(); - self.push(high); - self.push(low); - } - pub fn pop_16(&mut self) -> u16 { - let low = self.pop(); - let high = self.pop(); - u16::from_le_bytes([low, high]) - } - - fn jmp_rel(&mut self, off: u8) { - if off < 128 { - self.cpu.pc = self.cpu.pc.wrapping_add(off as u16); - } else { - self.cpu.pc = self.cpu.pc.wrapping_sub(0x100 - off as u16); - } - } - - fn halt(&mut self) { - self.halted = true; - } - - /// Returns true if more bytes are needed - fn exec_instruction( - &mut self, - 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.cpu.pc - (1 + params.len() as u16), $val, ins, params); - // debug!("Running 0x{:04X} {} :{:X} {:X?}", self.cpu.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.cpu.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.cpu.status.set_carry(true) - }), - 0x18 => inst!("CLC", 1, || { - log!("{addr:04X}: CLC"); - self.cpu.status.set_carry(false) - }), - 0x78 => inst!("SEI", 1, || { - log!("{addr:04X}: SEI"); - self.cpu.status.set_interrupt_disable(true) - }), - 0x58 => inst!("CLI", 1, || { - log!("{addr:04X}: CLI"); - self.cpu.status.set_interrupt_disable(false) - }), - 0xF8 => inst!("SED", 1, || { - log!("{addr:04X}: SED"); - self.cpu.status.set_decimal(true) - }), - 0xD8 => inst!("CLD", 1, || { - log!("{addr:04X}: CLD"); - self.cpu.status.set_decimal(false) - }), - 0xB8 => inst!("CLV", 1, || { - log!("{addr:04X}: CLV"); - self.cpu.status.set_overflow(false) - }), - 0x00 => inst!("BRK", 5, |_ignored| { - // TODO: this should probably just set `self.cpu.irq_pending` ... - log!("{addr:04X}: BRK #${_ignored:02X}"); - self.push_16(self.cpu.pc); - self.push(self.cpu.status.0 | 0b00110000); - self.cpu.status.set_interrupt_disable(true); - self.cpu.pc = u16::from_le_bytes([self.read(0xFFFE), self.read(0xFFFF)]); - log!(" : - JMP ${:X}", self.cpu.pc); - }), - - 0x48 => inst!("PHA", 2, || { - log!( - "{addr:04X}: PHA | {:02X} -> 01{:02X}", - self.cpu.a, - self.cpu.sp - ); - self.push(self.cpu.a); - }), - 0x08 => inst!("PHP", 2, || { - log!( - "{addr:04X}: PHP | {:02X} -> 01{:02X}", - self.cpu.status.0, - self.cpu.sp - ); - self.push(self.cpu.status.0 | 0b0011_0000); - }), - 0x68 => inst!("PLA", 3, || { - self.cpu.a = self.pop(); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: PLA | {:02X} <- 01{:02X}", - self.cpu.a, - self.cpu.sp - ); - }), - 0x28 => inst!("PLP", 3, || { - self.cpu.status.0 = self.pop() & 0b1100_1111; - log!( - "{addr:04X}: PLP | {:02X} <- 01{:02X}", - self.cpu.status.0, - self.cpu.sp - ); - }), - - // Loads - 0xA9 => inst!("LDA imm", 0, |a| { - self.cpu.a = a; - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: LDA #${:02X}", a); - }), - 0xA5 => inst!("LDA zp", 1, |off| { - self.cpu.a = self.read(off as u16); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: LDA ${:02X} | {:02X}", off, self.cpu.a); - }), - 0xB5 => inst!("LDA zp,x", 2, |off| { - self.cpu.a = self.read_zp_x(off); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: LDA ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0xAD => inst!("LDA abs", 1, |low, high| { - self.cpu.a = self.read_abs(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: LDA ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xBD => inst!("LDA abs,x", 1, |low, high| { - if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } - self.cpu.a = self.read_abs_x(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: LDA ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xB9 => inst!("LDA abs,y", 1, |low, high| { - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - self.cpu.a = self.read_abs_y(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: LDA ${:02X}{:02X},y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xA1 => inst!("LDA (ind,x)", 4, |off| { - let low = self.read_zp_x(off); - let high = self.read_zp_x(off.wrapping_add(1)); - self.cpu.a = self.read_abs(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: LDA (${:02X},x) | {:02X}", off, self.cpu.a); - }), - 0xB1 => inst!("LDA (ind),y", 3, |off| { - let low = self.read_abs(off, 0); - let high = self.read_abs(off.wrapping_add(1), 0); - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - self.cpu.a = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: LDA (${:02X}),y | {:02X}", off, self.cpu.a); - }), - 0xA2 => inst!("LDX imm", 0, |x| { - self.cpu.x = x; - self.cpu.status.set_zero(self.cpu.x == 0); - self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); - log!("{addr:04X}: LDX #${:02X}", x); - }), - 0xA6 => inst!("LDX zp", 1, |off| { - self.cpu.x = self.read(off as u16); - self.cpu.status.set_zero(self.cpu.x == 0); - self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); - log!("{addr:04X}: LDX ${:02X} | {:02X}", off, self.cpu.x); - }), - 0xB6 => inst!("LDX zp,y", 2, |off| { - self.cpu.x = self.read_zp_y(off); - self.cpu.status.set_zero(self.cpu.x == 0); - self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); - log!("{addr:04X}: LDX ${:02X},y | {:02X}", off, self.cpu.x); - }), - 0xAE => inst!("LDX abs", 1, |low, high| { - self.cpu.x = self.read_abs(low, high); - self.cpu.status.set_zero(self.cpu.x == 0); - self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); - log!( - "{addr:04X}: LDX ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.x - ); - }), - 0xBE => inst!("LDX abs,y", 1, |low, high| { - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - self.cpu.x = self.read_abs_y(low, high); - self.cpu.status.set_zero(self.cpu.x == 0); - self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); - log!( - "{addr:04X}: LDX ${:02X}{:02X},y | {:02X}", - high, - low, - self.cpu.x - ); - }), - 0xA0 => inst!("LDY imm", 0, |y| { - self.cpu.y = y; - self.cpu.status.set_zero(self.cpu.y == 0); - self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); - log!("{addr:04X}: LDY #${:02X}", y); - }), - 0xA4 => inst!("LDY zp", 1, |off| { - self.cpu.y = self.read(off as u16); - self.cpu.status.set_zero(self.cpu.y == 0); - self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); - log!("{addr:04X}: LDY ${:02X} | {:02X}", off, self.cpu.y); - }), - 0xB4 => inst!("LDX zp,x", 2, |off| { - self.cpu.y = self.read_zp_x(off); - self.cpu.status.set_zero(self.cpu.y == 0); - self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); - log!("{addr:04X}: LDY ${:02X},x | {:02X}", off, self.cpu.y); - }), - 0xAC => inst!("LDX abs", 1, |low, high| { - self.cpu.y = self.read_abs(low, high); - self.cpu.status.set_zero(self.cpu.y == 0); - self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); - log!( - "{addr:04X}: LDY ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.y - ); - }), - 0xBC => inst!("LDX abs,x", 1, |low, high| { - if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } - self.cpu.y = self.read_abs_x(low, high); - self.cpu.status.set_zero(self.cpu.y == 0); - self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); - log!( - "{addr:04X}: LDY ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.y - ); - }), - - // Stores - 0x85 => inst!("STA zp", 1, |off| { - self.write(off as u16, self.cpu.a); - log!("{addr:04X}: STA ${:02X} | {:02X}", off, self.cpu.a); - }), - 0x95 => inst!("STA zp,x", 2, |off| { - self.write_zp_x(off, self.cpu.a); - log!("{addr:04X}: STA ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0x8D => inst!("STA abs", 1, |low, high| { - self.write_abs(low, high, self.cpu.a); - log!( - "{addr:04X}: STA ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x9D => inst!("STA abs,x", 2, |low, high| { - self.write_abs_x(low, high, self.cpu.a); - log!( - "{addr:04X}: STA ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x99 => inst!("STA abs,y", 2, |low, high| { - self.write_abs_y(low, high, self.cpu.a); - log!( - "{addr:04X}: STA ${:02X}{:02X},y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x81 => inst!("STA (ind,x)", 4, |off| { - let low = self.read_zp_x(off); - let high = self.read_zp_x(off.wrapping_add(1)); - self.write_abs(low, high, self.cpu.a); - log!("{addr:04X}: STA (${:02X},x) | {:02X}", off, self.cpu.a); - }), - 0x91 => inst!("STA (ind),y", 4, |off| { - let low = self.read_abs(off, 0); - let high = self.read_abs(off.wrapping_add(1), 0); - self.write( - u16::from_le_bytes([low, high]) + self.cpu.y as u16, - self.cpu.a, - ); - log!("{addr:04X}: STA (${:02X}),y | {:02X}", off, self.cpu.a); - }), - 0x86 => inst!("STX zp", 1, |off| { - self.write(off as u16, self.cpu.x); - log!("{addr:04X}: STX ${:02X} | {:02X}", off, self.cpu.x); - }), - 0x96 => inst!("STX zp,y", 2, |off| { - self.write_zp_y(off, self.cpu.x); - log!("{addr:04X}: STX ${:02X},y | {:02X}", off, self.cpu.x); - }), - 0x8E => inst!("STX abs", 1, |low, high| { - self.write_abs(low, high, self.cpu.x); - log!( - "{addr:04X}: STX ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.x - ); - }), - 0x84 => inst!("STY zp", 1, |off| { - self.write(off as u16, self.cpu.y); - log!("{addr:04X}: STY ${:02X} | {:02X}", off, self.cpu.y); - }), - 0x94 => inst!("STY zp,x", 2, |off| { - self.write_zp_x(off, self.cpu.y); - log!("{addr:04X}: STY ${:02X},x | {:02X}", off, self.cpu.y); - }), - 0x8C => inst!("STY abs", 1, |low, high| { - self.write_abs(low, high, self.cpu.y); - log!( - "{addr:04X}: STY ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.y - ); - }), - - // Transfers - 0xAA => inst!("TAX", 1, || { - self.cpu.x = self.cpu.a; - self.cpu.status.set_zero(self.cpu.x == 0); - self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); - log!("{addr:04X}: TAX | {:02X}", self.cpu.x); - }), - 0xA8 => inst!("TAY", 1, || { - self.cpu.y = self.cpu.a; - self.cpu.status.set_zero(self.cpu.y == 0); - self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); - log!("{addr:04X}: TAY | {:02X}", self.cpu.y); - }), - 0xBA => inst!("TSX", 1, || { - self.cpu.x = self.cpu.sp; - self.cpu.status.set_zero(self.cpu.x == 0); - self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); - log!("{addr:04X}: TSX | {:02X}", self.cpu.x); - }), - 0x8A => inst!("TXA", 1, || { - self.cpu.a = self.cpu.x; - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: TXA | {:02X}", self.cpu.a); - }), - 0x9A => inst!("TXS", 1, || { - self.cpu.sp = self.cpu.x; - log!("{addr:04X}: TXS | {:02X}", self.cpu.sp); - }), - 0x98 => inst!("TYA", 1, || { - self.cpu.a = self.cpu.y; - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: TYA | {:02X}", self.cpu.a); - }), - - // Branch - 0x90 => inst!("BCC", !self.cpu.status.carry(), |off| { - log!("{addr:04X}: BCC ${:02}", off); - if !self.cpu.status.carry() { - self.jmp_rel(off); - log!(" : - JMP ${:04X}", self.cpu.pc); - } - }), - 0xB0 => inst!("BCS", self.cpu.status.carry(), |off| { - log!("{addr:04X}: BCS ${:02}", off); - if self.cpu.status.carry() { - self.jmp_rel(off); - log!(" : - JMP ${:04X}", self.cpu.pc); - } - }), - 0xF0 => inst!("BEQ", self.cpu.status.zero(), |off| { - log!("{addr:04X}: BEQ ${:02}", off); - if self.cpu.status.zero() { - self.jmp_rel(off); - log!(" : - JMP ${:04X}", self.cpu.pc); - } - }), - 0x30 => inst!("BMI", self.cpu.status.negative(), |off| { - log!("{addr:04X}: BMI ${:02}", off); - if self.cpu.status.negative() { - self.jmp_rel(off); - log!(" : - JMP ${:04X}", self.cpu.pc); - } - }), - 0xD0 => inst!("BNE", !self.cpu.status.zero(), |off| { - log!("{addr:04X}: BNE ${:02}", off); - if !self.cpu.status.zero() { - self.jmp_rel(off); - log!(" : - JMP ${:04X}", self.cpu.pc); - } - }), - 0x10 => inst!("BPL", !self.cpu.status.negative(), |off| { - log!("{addr:04X}: BPL ${:02}", off); - if !self.cpu.status.negative() { - self.jmp_rel(off); - log!(" : - JMP ${:04X}", self.cpu.pc); - } - }), - 0x4C => inst!("JMP abs", 0, |low, high| { - self.cpu.pc = u16::from_le_bytes([low, high]); - log!("{addr:04X}: JMP ${:04X}", self.cpu.pc); - }), - 0x6C => inst!("JMP abs", 2, |low, high| { - self.cpu.pc = u16::from_le_bytes([ - self.read_abs(low, high), - self.read_abs(low.wrapping_add(1), high), // Known CPU bug - ]); - log!("{addr:04X}: JMP ${:04X}", self.cpu.pc); - }), - 0x20 => inst!("JSR", 3, |low, high| { - self.push_16(self.cpu.pc - 1); - let tmp = self.cpu.pc - 1; - self.cpu.pc = u16::from_le_bytes([low, high]); - log!("{addr:04X}: JSR ${:04X} | ret ${:04X}", self.cpu.pc, tmp); - }), - 0x60 => inst!("RTS", 5, || { - self.cpu.pc = self.pop_16() + 1; - log!("{addr:04X}: RTS | ${:04X}", self.cpu.pc); - }), - 0x40 => inst!("RTI", 5, || { - self.cpu.status.0 = self.pop() & 0b1100_1111; - self.cpu.pc = self.pop_16(); - log!("{addr:04X}: RTI | ${:04X}", self.cpu.pc); - }), - - // CMP - 0xC9 => inst!("CMP imm", 0, |val| { - let v = self.cpu.a.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.a >= val); - log!( - "{addr:04X}: CMP #${:02X} | {:02X} - {:02X} -> {:?}", - val, - self.cpu.a, - val, - self.cpu.status - ); - }), - 0xC5 => inst!("CMP zp", 1, |off| { - let val = self.read_abs(off, 0); - let v = self.cpu.a.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.a >= val); - log!( - "{addr:04X}: CMP ${:02X} | {:02X} - {:02X} -> {:?}", - off, - self.cpu.a, - val, - self.cpu.status - ); - }), - 0xD5 => inst!("CMP zp,x", 2, |off| { - let val = self.read_zp_x(off); - let v = self.cpu.a.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.a >= val); - log!( - "{addr:04X}: CMP ${:02X},x | {:02X} - {:02X} -> {:?}", - off, - self.cpu.a, - val, - self.cpu.status - ); - }), - 0xCD => inst!("CMP abs", 1, |low, high| { - let val = self.read_abs(low, high); - let v = self.cpu.a.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.a >= val); - log!( - "{addr:04X}: CMP ${:02X}{:02X} | {:02X} - {:02X} -> {:?}", - high, - low, - self.cpu.a, - val, - self.cpu.status - ); - }), - 0xDD => inst!("CMP abs,x", 1, |low, high| { - if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } - let val = self.read_abs_x(low, high); - let v = self.cpu.a.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.a >= val); - log!( - "{addr:04X}: CMP ${:02X}{:02X},x | {:02X} - {:02X} -> {:?}", - high, - low, - self.cpu.a, - val, - self.cpu.status - ); - }), - 0xD9 => inst!("CMP abs,y", 1, |low, high| { - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - let val = self.read_abs_y(low, high); - let v = self.cpu.a.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.a >= val); - log!( - "{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}", - high, - low, - self.cpu.a, - val, - self.cpu.status - ); - }), - 0xC1 => inst!("CMP (ind,x)", 4, |off| { - let low = self.read_zp_x(off); - let high = self.read_zp_x(off.wrapping_add(1)); - let val = self.read_abs(low, high); - let v = self.cpu.a.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.a >= val); - log!( - "{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}", - high, - low, - self.cpu.a, - val, - self.cpu.status - ); - }), - 0xD1 => inst!("CMP (ind),y", 3, |off| { - let low = self.read_abs(off, 0); - let high = self.read_abs(off.wrapping_add(1), 0); - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - let val = self.read_abs_y(low, high); - let v = self.cpu.a.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.a >= val); - log!( - "{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}", - high, - low, - self.cpu.a, - val, - self.cpu.status - ); - }), - 0xE0 => inst!("CPX imm", 0, |val| { - let v = self.cpu.x.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.x >= val); - log!( - "{addr:04X}: CPX #${:02X} | {:02X} - {:02X} -> {:?}", - val, - self.cpu.x, - val, - self.cpu.status - ); - }), - 0xE4 => inst!("CPX zp", 1, |off| { - let val = self.read_abs(off, 0); - let v = self.cpu.x.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.x >= val); - log!( - "{addr:04X}: CPX ${:02X} | {:02X} - {:02X} -> {:?}", - off, - self.cpu.x, - val, - self.cpu.status - ); - }), - 0xEC => inst!("CPX abs", 1, |low, high| { - let val = self.read_abs(low, high); - let v = self.cpu.x.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.x >= val); - log!( - "{addr:04X}: CPX ${:02X}{:02X} | {:02X} - {:02X} -> {:?}", - high, - low, - self.cpu.x, - val, - self.cpu.status - ); - }), - 0xC0 => inst!("CPY imm", 0, |val| { - let v = self.cpu.y.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.y >= val); - log!( - "{addr:04X}: CPY #${:02X} | {:02X} - {:02X} -> {:?}", - val, - self.cpu.y, - val, - self.cpu.status - ); - }), - 0xC4 => inst!("CPY zp", 1, |off| { - let val = self.read_abs(off, 0); - let v = self.cpu.y.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.y >= val); - log!( - "{addr:04X}: CPY ${:02X} | {:02X} - {:02X} -> {:?}", - off, - self.cpu.y, - val, - self.cpu.status - ); - }), - 0xCC => inst!("CPY zp", 1, |low, high| { - let val = self.read_abs(low, high); - let v = self.cpu.y.wrapping_sub(val); - self.cpu.status.set_zero(v == 0); - self.cpu.status.set_negative(v & 0x80 == 0x80); - self.cpu.status.set_carry(self.cpu.y >= val); - log!( - "{addr:04X}: CPY ${:02X}{:02X} | {:02X} - {:02X} -> {:?}", - high, - low, - self.cpu.y, - val, - self.cpu.status - ); - }), - - // Arithmetic - 0x69 => inst!("ADC imm", 0, |val| { - let (a, carry_1) = self.cpu.a.overflowing_add(val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: ADC #${:02X} | {:02X}", val, self.cpu.a); - }), - 0x65 => inst!("ADC zp", 1, |off| { - let val = self.read_abs(off, 0); - let (a, carry_1) = self.cpu.a.overflowing_add(val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: ADC ${:02X} | {:02X}", off, self.cpu.a); - }), - 0x75 => inst!("ADC zp,x", 2, |off| { - let val = self.read_zp_x(off); - let (a, carry_1) = self.cpu.a.overflowing_add(val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: ADC ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0x6D => inst!("ADC abs", 1, |low, high| { - let val = self.read_abs(low, high); - let (a, carry_1) = self.cpu.a.overflowing_add(val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: ADC ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x7D => inst!("ADC abs,x", 1, |low, high| { - if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } - let val = self.read_abs_x(low, high); - let (a, carry_1) = self.cpu.a.overflowing_add(val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: ADC ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x79 => inst!("ADC abs,y", 1, |low, high| { - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - let val = self.read_abs_y(low, high); - let (a, carry_1) = self.cpu.a.overflowing_add(val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: ADC ${:02X}{:02X},y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x61 => inst!("ADC (ind,x)", 4, |off| { - let low = self.read_abs(off, 0); - let high = self.read_abs(off.wrapping_add(1), 0); - let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); - let (a, carry_1) = self.cpu.a.overflowing_add(val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: ADC (${:02X}{:02X},x) | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x71 => inst!("ADC (ind),y", 3, |off| { - let low = self.read_abs(off, 0); - let high = self.read_abs(off.wrapping_add(1), 0); - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); - let (a, carry_1) = self.cpu.a.overflowing_add(val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: ADC (${:02X}{:02X}),y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xE9 => inst!("SBC imm", 0, |val| { - let (a, carry_1) = self.cpu.a.overflowing_add(!val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); - self.cpu.a = a; - // TODO: I'm pretty sure the carry logic is wrong here - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: SBC #${:02X} | {:02X}", val, self.cpu.a); - }), - 0xE5 => inst!("SBC zp", 1, |off| { - let val = self.read_abs(off, 0); - let (a, carry_1) = self.cpu.a.overflowing_add(!val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - }), - 0xF5 => inst!("SBC zp,x", 2, |off| { - let val = self.read_zp_x(off); - let (a, carry_1) = self.cpu.a.overflowing_add(!val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: SBC ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0xED => inst!("SBC abs", 1, |low, high| { - let val = self.read_abs(low, high); - let (a, carry_1) = self.cpu.a.overflowing_add(!val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: SBC ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xFD => inst!("SBC abs,x", 1, |low, high| { - if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } - let val = self.read_abs_x(low, high); - let (a, carry_1) = self.cpu.a.overflowing_add(!val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: SBC ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xF9 => inst!("SBC abs,y", 1, |low, high| { - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - let val = self.read_abs_y(low, high); - let (a, carry_1) = self.cpu.a.overflowing_add(!val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: SBC ${:02X}{:02X},y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xE1 => inst!("SBC (ind,x)", 4, |off| { - let low = self.read_abs(off, 0); - let high = self.read_abs(off.wrapping_add(1), 0); - let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); - let (a, carry_1) = self.cpu.a.overflowing_add(!val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: SBC (${:02X}{:02X},x) | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xF1 => inst!("SBC (ind),y", 3, |off| { - let low = self.read_abs(off, 0); - let high = self.read_abs(off.wrapping_add(1), 0); - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); - let (a, carry_1) = self.cpu.a.overflowing_add(!val); - let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into()); - self.cpu - .status - .set_overflow((self.cpu.a ^ a) & (!val ^ a) & 0x80 != 0); - self.cpu.a = a; - self.cpu.status.set_carry(carry_1 | carry_2); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: SBC (${:02X}{:02X}),y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xC6 => inst!("DEC zp", 3, |off| { - let val = self.read_abs(off, 0).wrapping_sub(1); - self.write_abs(off, 0, val); - self.cpu.status.set_zero(val == 0); - self.cpu.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(off).wrapping_sub(1); - self.write_zp_x(off, val); - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - log!("{addr:04X}: DEC ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0xCE => inst!("DEC abs", 3, |low, high| { - let val = self.read_abs(low, high).wrapping_sub(1); - self.write_abs(low, high, val); - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - log!( - "{addr:04X}: DEC ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xDE => inst!("DEC abs,x", 4, |low, high| { - let val = self.read_abs_x(low, high).wrapping_sub(1); - self.write_abs_x(low, high, val); - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - log!( - "{addr:04X}: DEC ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xCA => inst!("DEX", 1, || { - self.cpu.x = self.cpu.x.wrapping_sub(1); - self.cpu.status.set_zero(self.cpu.x == 0); - self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); - log!("{addr:04X}: DEX | {:02X}", self.cpu.x); - }), - 0x88 => inst!("DEY", 1, || { - self.cpu.y = self.cpu.y.wrapping_sub(1); - self.cpu.status.set_zero(self.cpu.y == 0); - self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); - log!("{addr:04X}: DEY | {:02X}", self.cpu.y); - }), - 0xE6 => inst!("INC zp", 3, |off| { - let val = self.read_abs(off, 0).wrapping_add(1); - self.write_abs(off, 0, val); - self.cpu.status.set_zero(val == 0); - self.cpu.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(off).wrapping_add(1); - self.write_zp_x(off, val); - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - log!("{addr:04X}: INC ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0xEE => inst!("INC abs", 3, |low, high| { - let val = self.read_abs(low, high).wrapping_add(1); - self.write_abs(low, high, val); - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - log!( - "{addr:04X}: INC ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xFE => inst!("INC abs,x", 4, |low, high| { - let val = self.read_abs_x(low, high).wrapping_add(1); - self.write_abs_x(low, high, val); - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - log!( - "{addr:04X}: INC ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0xE8 => inst!("INX", 1, || { - self.cpu.x = self.cpu.x.wrapping_add(1); - self.cpu.status.set_zero(self.cpu.x == 0); - self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80); - log!("{addr:04X}: INX | {:02X}", self.cpu.x); - }), - 0xC8 => inst!("INY", 1, || { - self.cpu.y = self.cpu.y.wrapping_add(1); - self.cpu.status.set_zero(self.cpu.y == 0); - self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80); - log!("{addr:04X}: INY | {:02X}", self.cpu.y); - }), - - // Logical - 0x09 => inst!("ORA imm", 0, |val| { - self.cpu.a |= val; - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: ORA #${:02X} | {:02X}", val, self.cpu.a); - }), - 0x05 => inst!("ORA zp", 1, |off| { - self.cpu.a |= self.read_abs(off, 0); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: ORA ${:02X} | {:02X}", off, self.cpu.a); - }), - 0x15 => inst!("ORA zp,x", 2, |off| { - self.cpu.a |= self.read_zp_x(off); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: ORA ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0x0D => inst!("ORA abs", 1, |low, high| { - self.cpu.a |= self.read_abs(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: ORA ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x1D => inst!("ORA abs,x", 1, |low, high| { - if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } - self.cpu.a |= self.read_abs_x(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: ORA ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x19 => inst!("ORA abs,y", 1, |low, high| { - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - self.cpu.a |= self.read_abs_y(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: ORA ${:02X}{:02X},y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x01 => inst!("ORA (ind,x)", 4, |off| { - let low = self.read_zp_x(off); - let high = self.read_zp_x(off.wrapping_add(1)); - self.cpu.a |= self.read_abs(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: ORA (${:02X}{:02X},x) | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x11 => inst!("ORA (ind),y", 3, |off| { - let low = self.read_abs(off, 0); - let high = self.read_abs(off.wrapping_add(1), 0); - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - if !oops && low.checked_add(self.cpu.y).is_none() { - return ExecState::Oops; - } - self.cpu.a |= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: ORA (${:02X}{:02X}),y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x29 => inst!("AND imm", 0, |val| { - self.cpu.a &= val; - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: AND #${:02X} | {:02X}", val, self.cpu.a); - }), - 0x25 => inst!("AND zp", 1, |off| { - self.cpu.a &= self.read_abs(off, 0); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: AND ${:02X} | {:02X}", off, self.cpu.a); - }), - 0x35 => inst!("AND zp,x", 2, |off| { - self.cpu.a &= self.read_zp_x(off); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: AND ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0x2D => inst!("AND abs", 1, |low, high| { - self.cpu.a &= self.read_abs(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: AND ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x3D => inst!("AND abs,x", 1, |low, high| { - if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } - self.cpu.a &= self.read_abs_x(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: AND ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x39 => inst!("AND abs,y", 1, |low, high| { - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - self.cpu.a &= self.read_abs_y(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: AND ${:02X}{:02X},y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x21 => inst!("AND (ind,x)", 4, |off| { - let low = self.read_zp_x(off); - let high = self.read_zp_x(off.wrapping_add(1)); - self.cpu.a &= self.read_abs(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: AND (${:02X}{:02X},x) | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x31 => inst!("AND (ind),y", 3, |off| { - let low = self.read_abs(off, 0); - let high = self.read_abs(off.wrapping_add(1), 0); - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - self.cpu.a &= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: AND (${:02X}{:02X}),y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x49 => inst!("EOR imm", 0, |val| { - self.cpu.a ^= val; - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: EOR #${:02X} | {:02X}", val, self.cpu.a); - }), - 0x45 => inst!("EOR zp", 1, |off| { - self.cpu.a ^= self.read_abs(off, 0); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: EOR ${:02X} | {:02X}", off, self.cpu.a); - }), - 0x55 => inst!("EOR zp,x", 2, |off| { - self.cpu.a ^= self.read_zp_x(off); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: EOR ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0x4D => inst!("EOR abs", 1, |low, high| { - self.cpu.a ^= self.read_abs(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: EOR ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x5D => inst!("EOR abs,x", 1, |low, high| { - if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; } - self.cpu.a ^= self.read_abs_x(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: EOR ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x59 => inst!("EOR abs,y", 1, |low, high| { - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - self.cpu.a ^= self.read_abs_y(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: EOR ${:02X}{:02X},y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x41 => inst!("EOR (ind,x)", 4, |off| { - let low = self.read_zp_x(off); - let high = self.read_zp_x(off.wrapping_add(1)); - self.cpu.a ^= self.read_abs(low, high); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: EOR (${:02X}{:02X},x) | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x51 => inst!("EOR (ind),y", 3, |off| { - let low = self.read_abs(off, 0); - let high = self.read_abs(off.wrapping_add(1), 0); - if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; } - self.cpu.a ^= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16); - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!( - "{addr:04X}: EOR (${:02X}{:02X}),y | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x24 => inst!("BIT zp", 1, |off| { - let val = self.read_abs(off, 0); - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_overflow(val & 0x40 == 0x40); - self.cpu.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(low, high); - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_overflow(val & 0x40 == 0x40); - self.cpu.status.set_negative(val & 0x80 == 0x80); - log!("{addr:04X}: BIT ${:02X}{:02X} | {:02X}", high, low, val); - }), - - // Shifts - 0x4A => inst!("LSR A", 1, || { - self.cpu.status.set_carry(self.cpu.a & 0b1 == 0b1); - self.cpu.a = self.cpu.a >> 1; - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(false); - log!("{addr:04X}: LSR A | {:02X}", self.cpu.a); - }), - 0x46 => inst!("LSR zp", 3, |off| { - let val = self.read_abs(off, 0); - self.cpu.status.set_carry(val & 0b1 == 0b1); - let val = val >> 1; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(false); - self.write_abs(off, 0, val); - log!("{addr:04X}: LSR ${:02X} | {:02X}", off, self.cpu.a); - }), - 0x56 => inst!("LSR zp,x", 4, |off| { - let val = self.read_zp_x(off); - self.cpu.status.set_carry(val & 0b1 == 0b1); - let val = val >> 1; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(false); - self.write_zp_x(off, val); - log!("{addr:04X}: LSR ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0x4E => inst!("LSR abs", 3, |low, high| { - let val = self.read_abs(low, high); - self.cpu.status.set_carry(val & 0b1 == 0b1); - let val = val >> 1; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(false); - self.write_abs(low, high, val); - log!( - "{addr:04X}: LSR ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x5E => inst!("LSR abs,x", 4, |low, high| { - let val = self.read_abs_x(low, high); - self.cpu.status.set_carry(val & 0b1 == 0b1); - let val = val >> 1; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(false); - self.write_abs_x(low, high, val); - log!( - "{addr:04X}: LSR ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x0A => inst!("ASL A", 1, || { - self.cpu.status.set_carry(self.cpu.a & 0x80 == 0x80); - self.cpu.a = self.cpu.a << 1; - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(false); - log!("{addr:04X}: ASL A | {:02X}", self.cpu.a); - }), - 0x06 => inst!("ASL zp", 3, |off| { - let val = self.read_abs(off, 0); - self.cpu.status.set_carry(val & 0x80 == 0x80); - let val = val << 1; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(false); - self.write_abs(off, 0, val); - log!("{addr:04X}: ASL ${:02X} | {:02X}", off, self.cpu.a); - }), - 0x16 => inst!("ASL zp,x", 4, |off| { - let val = self.read_zp_x(off); - self.cpu.status.set_carry(val & 0x80 == 0x80); - let val = val << 1; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(false); - self.write_zp_x(off, val); - log!("{addr:04X}: ASL ${:02X},x | {:02X}", off, self.cpu.a); - }), - 0x0E => inst!("ASL abs", 3, |low, high| { - let val = self.read_abs(low, high); - self.cpu.status.set_carry(val & 0x80 == 0x80); - let val = val << 1; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(false); - self.write_abs(low, high, val); - log!( - "{addr:04X}: ASL ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x1E => inst!("ASL abs,x", 4, |low, high| { - let val = self.read_abs_x(low, high); - self.cpu.status.set_carry(val & 0x80 == 0x80); - let val = val << 1; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(false); - self.write_abs_x(low, high, val); - log!( - "{addr:04X}: ASL ${:02X}{:02X},x | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x6A => inst!("ROR A", 1, || { - let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 }; - self.cpu.status.set_carry(self.cpu.a & 0b1 == 0b1); - self.cpu.a = self.cpu.a >> 1 | old_carry; - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: ROR A | {:02X}", self.cpu.a); - }), - 0x66 => inst!("ROR zp", 3, |off| { - let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 }; - let val = self.read_abs(off, 0); - self.cpu.status.set_carry(val & 0b1 == 0b1); - let val = val >> 1 | old_carry; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - self.write_abs(off, 0, val); - log!("{addr:04X}: ROR ${:02X} | {:02X}", off, self.cpu.a); - }), - 0x76 => inst!("ROR zp,x", 4, |off| { - let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 }; - let val = self.read_zp_x(off); - self.cpu.status.set_carry(val & 0b1 == 0b1); - let val = val >> 1 | old_carry; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - self.write_zp_x(off, val); - log!("{addr:04X}: ROR ${:02X},x | {:02X}", off, val); - }), - 0x6E => inst!("ROR abs", 3, |low, high| { - let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 }; - let val = self.read_abs(low, high); - self.cpu.status.set_carry(val & 0b1 == 0b1); - let val = val >> 1 | old_carry; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - self.write_abs(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.cpu.status.carry() { 0x80 } else { 0x00 }; - let val = self.read_abs_x(low, high); - self.cpu.status.set_carry(val & 0b1 == 0b1); - let val = val >> 1 | old_carry; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - self.write_abs_x(low, high, val); - log!("{addr:04X}: ROR ${:02X}{:02X},x | {:02X}", high, low, val); - }), - 0x2A => inst!("ROL A", 1, || { - let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 }; - self.cpu.status.set_carry(self.cpu.a & 0x80 == 0x80); - self.cpu.a = self.cpu.a << 1 | old_carry; - self.cpu.status.set_zero(self.cpu.a == 0); - self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80); - log!("{addr:04X}: ROL A | {:02X}", self.cpu.a); - }), - 0x26 => inst!("ROL zp", 3, |off| { - let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 }; - let val = self.read_abs(off, 0); - self.cpu.status.set_carry(val & 0x80 == 0x80); - let val = val << 1 | old_carry; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - self.write_abs(off, 0, val); - log!("{addr:04X}: ROL ${:02X} | {:02X}", off, val); - }), - 0x36 => inst!("ROL zp,x", 4, |off| { - let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 }; - let val = self.read_zp_x(off); - self.cpu.status.set_carry(val & 0x80 == 0x80); - let val = val << 1 | old_carry; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - self.write_zp_x(off, val); - log!("{addr:04X}: ROL ${:02X},x | {:02X}", off, val); - }), - 0x2E => inst!("ROL abs", 3, |low, high| { - let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 }; - let val = self.read_abs(low, high); - self.cpu.status.set_carry(val & 0x80 == 0x80); - let val = val << 1 | old_carry; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - self.write_abs(low, high, val); - log!( - "{addr:04X}: ROL ${:02X}{:02X} | {:02X}", - high, - low, - self.cpu.a - ); - }), - 0x3E => inst!("ROL abs,x", 4, |low, high| { - let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 }; - let val = self.read_abs_x(low, high); - self.cpu.status.set_carry(val & 0x80 == 0x80); - let val = val << 1 | old_carry; - self.cpu.status.set_zero(val == 0); - self.cpu.status.set_negative(val & 0x80 == 0x80); - self.write_abs_x(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.cpu.pc - 1), - } - } - - pub fn peek_opcode(&self) -> u8 { - self.peek(self.cpu.pc) - } + // pub fn peek_opcode(&self) -> u8 { + // self.peek(self.cpu.pc) + // } pub fn peek_nmi(&self) -> bool { self.ppu.peek_nmi() || self.apu.peek_nmi() @@ -1995,216 +163,8 @@ impl NES { self.ppu.peek_irq() || self.apu.peek_irq() } - fn clock_cpu(&mut self) { - self.cpu.clock_state = match self.cpu.clock_state { - ClockState::HoldNmi { cycles } => { - if cycles == 0 { - self.push_16(self.cpu.pc); - self.push(self.cpu.status.0 | 0x20); - self.cpu.pc = u16::from_le_bytes([self.read(0xFFFA), self.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(self.cpu.pc); - self.push(self.cpu.status.0 | 0b00110000); - self.cpu.status.set_interrupt_disable(true); - self.cpu.pc = u16::from_le_bytes([self.read(0xFFFE), self.read(0xFFFF)]); - writeln!(self.debug_log, "Starting IRQ handler").unwrap(); - ClockState::ReadInstruction - } else { - ClockState::HoldIrq { cycles: cycles - 1 } - } - } - ClockState::ReadInstruction => { - let addr = self.cpu.pc; - let instruction = self.read(self.cpu.pc); - if instruction != 0x02 { - self.cpu.pc = self.cpu.pc.wrapping_add(1); - } - match self.exec_instruction(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, - }, - } - } - ClockState::ReadOperands { - instruction, - mut ops, - count, - addr, - } => { - if count == 5 { - todo!() - } - ops[count as usize] = self.read(self.cpu.pc); - self.cpu.pc = self.cpu.pc.wrapping_add(1); - match self.exec_instruction( - 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, - }, - } - } - ClockState::Hold { - cycles, - instruction, - ops, - count, - addr, - } => { - if cycles == 0 { - match self.exec_instruction( - 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, - }, - } - } else { - ClockState::Hold { - cycles: cycles - 1, - instruction, - ops, - count, - addr, - } - } - } - ClockState::Oops { - instruction, - ops, - count, - addr, - } => { - match self.exec_instruction(instruction, &ops[..count as usize], true, true, addr) { - ExecState::Done => ClockState::ReadInstruction, - _ => panic!("Must execute after oops"), - } - } - }; - if self.cpu.clock_state == ClockState::ReadInstruction { - if self.cpu.nmi_pending { - self.cpu.nmi_pending = false; - self.cpu.clock_state = ClockState::HoldNmi { cycles: 6 }; - writeln!(self.debug_log, "NMI detected").unwrap(); - } else if self.cpu.irq_pending && !self.cpu.status.interrupt_disable() { - writeln!(self.debug_log, "IRQ detected").unwrap(); - self.cpu.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 new_line_state != self.cpu.nmi_line_state { - // println!("ppu: {}, apu: {}", self.ppu.nmi_waiting(), self.apu.nmi_waiting()); - // println!("New: {new_line_state}, old: {}", self.cpu.nmi_line_state); - self.cpu.nmi_line_state = new_line_state; - self.cpu.nmi_pending |= new_line_state; - } - self.cpu.irq_pending = self.ppu.irq_waiting() || self.apu.irq_waiting(); - } - - fn cpu_cycle(&mut self) { - match self.dma { - DmaState::Passive => self.clock_cpu(), - // TODO: Validate that this takes the correct number of cycles (513 or 514 cycles) - DmaState::Running { - cpu_addr, - rem, - read: Some(read), - } => { - self.ppu.write_reg(4, read); - debug!("OAM DMA write {:02X}", read); - if rem == 0 { - self.dma = DmaState::Passive; - } else { - self.dma = DmaState::Running { - cpu_addr: cpu_addr + 1, - rem: rem - 1, - read: None, - }; - } - } - DmaState::Running { - cpu_addr, - rem, - read: None, - } => { - let read = self.read(cpu_addr); - debug!("OAM DMA read {:04X} {:02X}", cpu_addr, read); - self.dma = DmaState::Running { - cpu_addr, - rem, - read: Some(read), - }; - } - } - if !self.halted { - self.cycle += 1; - } - // if [0x8031, 0x8014].contains(&self.cpu.pc) { - // self.dbg_int = true; - // } - } - pub fn run_one_clock_cycle(&mut self) -> CycleResult { - if self.halted { + if self.cpu.halted() { return CycleResult { cpu_exec: false, ppu_frame: false, @@ -2213,17 +173,37 @@ impl NES { }; } self.clock_count += 1; - let cpu_exec = if self.clock_count % 3 == 1 { - self.cpu_cycle(); - matches!(self.cpu.clock_state, ClockState::ReadInstruction) + let cpu_exec = if self.clock_count % 3 == 0 { + let nmi = self.ppu.nmi_waiting() || self.apu.nmi_waiting(); + let irq = self.ppu.irq_waiting() || self.apu.irq_waiting(); + let mut dma = DmaState::Passive; + self.cpu.cpu_cycle( + &mut CpuMem::new( + &mut self.mapped, + &mut self.ppu, + &mut self.apu, + &mut dma, + &mut self.controller, + ), + &mut self.dma, + nmi, + irq, + ); + self.dma.merge(dma); + self.cpu.executed() } else { false }; - if !self.halted { - let apu_exec = self.apu.run_one_clock_cycle(self.clock_count); + if self.clock_count % 3 == 1 { + self.cpu.cpu_cycle_update(); } - let ppu_frame = if !self.halted { - self.ppu.run_one_clock_cycle() + // let cpu_exec = self.clock_count % 3 == CPU_CLOCK_OFFSET && self.cpu.executed(); + if !self.cpu.halted() { + let _apu_exec = self.apu.run_one_clock_cycle(self.clock_count); + } + let ppu_frame = if !self.cpu.halted() { + self.ppu + .run_one_clock_cycle(&mut PpuMem::new(&mut self.mapped)) } else { false }; @@ -2234,15 +214,19 @@ impl NES { CycleResult { cpu_exec, ppu_frame, - dma: matches!(self.dma, DmaState::Running { .. }), + dma: false, //matches!(self.dma, DmaState::Running { .. }), TODO dbg_int, } } pub fn reset(&mut self) { - self.cpu.pc = u16::from_le_bytes([self.read(0xFFFC), self.read(0xFFFD)]); - self.cpu.sp = 0xFD; - self.cpu.status.set_interrupt_disable(true); + self.cpu.reset(&mut CpuMem::new( + &mut self.mapped, + &mut self.ppu, + &mut self.apu, + &mut self.dma, + &mut self.controller, + )); self.ppu.reset(); } @@ -2250,17 +234,14 @@ impl NES { // self.memory.clear(); // self.ppu.reset(); // self.ppu.memory.clear(); - *self = Self::from_rom( - self.memory.rom(6).expect("PRG ROM"), - self.ppu.memory.rom_or_ram(0).expect("CHR ROM"), - self.mapper, - ); + *self = Self::from_mem(self.mapped.power_cylce()); + // self.memory.power_cycle(); self.reset(); } pub fn reset_and_run(&mut self) { self.reset(); - while !self.halted { + while !self.cpu.halted() { // info!("Running clock cycle: {}", self.clock_count); self.run_one_clock_cycle(); } @@ -2271,17 +252,18 @@ impl NES { /// Clocks the PPU & APU forward once if appropriate pub fn repl_nop(&mut self) { assert!( - self.halted, + self.cpu.halted(), "This method may only be called when the CPU is halted" ); assert!( // self.debug_log.pop().is_some_and(|s| s.contains("HLT")), - self.last_instruction.contains("HLT"), + self.cpu.last_instruction.contains("HLT"), "This method may only be called after a STP instruction" ); - self.halted = false; + self.cpu.unhalt(); // Turn final STP into NOP - self.ppu.run_one_clock_cycle(); + self.ppu + .run_one_clock_cycle(&mut PpuMem::new(&mut self.mapped)); self.apu.run_one_clock_cycle(self.clock_count); self.cpu.clock_state = ClockState::Hold { cycles: 0, @@ -2291,13 +273,13 @@ impl NES { addr: self.cpu.pc, }; self.cpu.pc += 1; - self.cycle += 1; + self.cpu.cycle += 1; // self.clock_cpu(); } pub fn run_with_timeout(&mut self, max_clock_cycles: usize) { let mut cur = 0; - while !self.halted && cur < max_clock_cycles { + while !self.cpu.halted() && cur < max_clock_cycles { // info!("Running clock cycle: {}", self.clock_count); self.run_one_clock_cycle(); cur += 1; @@ -2321,19 +303,26 @@ impl NES { &self.apu } - pub fn cpu_mem(&self) -> &impl Memory { - &self.memory - } - pub fn debug_log(&self) -> &DebugLog { - &self.debug_log + &self.cpu.debug_log } pub fn debug_log_mut(&mut self) -> &mut DebugLog { - &mut self.debug_log + &mut self.cpu.debug_log } pub fn halted(&self) -> bool { - self.halted + self.cpu.halted() + } + + pub fn mem(&self) -> &Mapped { + &self.mapped + } + + pub fn last_instruction(&self) -> &str { + &self.cpu.last_instruction + } + pub fn cpu_cycle(&self) -> usize { + self.cpu.cycle } } diff --git a/src/main.rs b/src/main.rs index b3d80cf..074a0a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,26 @@ use std::{collections::HashMap, fmt, time::Duration}; use iced::{ - Color, Element, Font, + Color, Element, Length::{Fill, Shrink}, - Point, Rectangle, Renderer, Size, Subscription, Task, Theme, mouse, + Point, Renderer, Size, Subscription, Task, Theme, + advanced::graphics::compositor::Display, + mouse, widget::{ - Action, Canvas, button, + Canvas, canvas::{Frame, Program}, - column, container, mouse_area, row, text, + column, container, row, }, window::{self, Id, Settings}, }; use nes_emu::{ - NES, PPU, + NES, debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image}, header_menu::header_menu, hex_view::{HexEvent, HexView}, resize_watcher::resize_watcher, }; -use tokio::runtime::Runtime; +use tokio::{io::AsyncWriteExt, runtime::Runtime}; use tracing_subscriber::EnvFilter; // const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes"); @@ -26,8 +28,9 @@ use tracing_subscriber::EnvFilter; // const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_red.nes"); // const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_name_table.nes"); // const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "int_nmi_exit_timing.nes"); -// const ROM_FILE: &str = "./Super Mario Bros. (World).nes"; -const ROM_FILE: &str = "./cpu_timing_test.nes"; +const ROM_FILE: &str = "./Super Mario Bros. (World).nes"; +// const ROM_FILE: &str = "./cpu_timing_test.nes"; +// const ROM_FILE: &str = "../nes-test-roms/instr_test-v5/official_only.nes"; extern crate nes_emu; @@ -44,12 +47,18 @@ fn main() -> Result<(), iced::Error> { .run() } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum MemoryTy { Cpu, PPU, } +impl fmt::Display for MemoryTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Export {self:?} Memory") + } +} + #[derive(Debug, Clone, PartialEq, Eq)] enum WindowType { Main, @@ -67,6 +76,7 @@ enum HeaderButton { // OpenTileViewer, // OpenDebugger, Open(WindowType), + OpenRom, Reset, PowerCycle, } @@ -83,6 +93,7 @@ impl fmt::Display for HeaderButton { Self::Open(WindowType::Main) => write!(f, "Create new Main window"), Self::Reset => write!(f, "Reset"), Self::PowerCycle => write!(f, "Power Cycle"), + Self::OpenRom => write!(f, "Open ROM file"), } } } @@ -96,6 +107,7 @@ struct Emulator { #[derive(Debug, Clone)] enum Message { + OpenRom(NES), Tick(usize), Frame, DMA, @@ -107,6 +119,7 @@ enum Message { Hex(Id, HexEvent), Debugger(DebuggerMessage), SetSize(window::Id, Size), + Export(MemoryTy), } impl Emulator { @@ -180,6 +193,22 @@ impl Emulator { Message::Header(HeaderButton::Open(w)) => { return self.open(w); } + Message::Header(HeaderButton::OpenRom) => { + return Task::future( + rfd::AsyncFileDialog::new() + .set_directory(".") + .add_filter("NES", &["nes"]) + .set_title("Open NES Rom file") + .pick_file(), + ) + .and_then(|p| { + Task::future(async move { + // println!("Opening: {}", p.path().display()); + NES::async_load_nes_file(p.path()).await.ok() + }) + }) + .and_then(|n| Task::done(Message::OpenRom(n))); + } Message::Hex(id, val) => { if let Some(WindowType::Memory(_, view)) = self.windows.get_mut(&id) { return view.update(val).map(move |e| Message::Hex(id, e)); @@ -203,6 +232,33 @@ impl Emulator { }) .then(move |_| iced::window::resize(id, size)); } + Message::OpenRom(nes) => { + self.nes = nes; + self.nes.power_cycle(); + } + Message::Export(ty) => { + let raw: Vec<_> = match ty { + MemoryTy::Cpu => (0..=0xFFFF).map(|i| self.nes.mem().peek_cpu(i).unwrap_or(0)).collect(), + MemoryTy::PPU => (0..=0xFFFF).map(|i| self.nes.mem().peek_ppu(i).unwrap_or(0)).collect(), + }; + return Task::future(async move { + if let Some(file) = rfd::AsyncFileDialog::new() + .set_directory(".") + .set_file_name("output.dmp") + .set_title("Save memory dump") + .save_file() + .await + { + tokio::fs::File::create(file.path()) + .await + .expect("Failed to save file") + .write_all(&raw) + .await + .expect("Failed to write dump"); + } + }) + .discard(); + } } // self.image.0.clone_from(self.nes.image()); Task::none() @@ -234,20 +290,26 @@ impl Emulator { Some(WindowType::Memory(ty, view)) => { let hex = match ty { MemoryTy::Cpu => view - .render(self.nes.cpu_mem()) + .render(self.nes.mem(), false) .map(move |e| Message::Hex(win, e)), MemoryTy::PPU => view - .render(self.nes.ppu().mem()) + .render(self.nes.mem(), true) .map(move |e| Message::Hex(win, e)), }; - let content = column![hex].width(Fill).height(Fill); + let content = column![row![header_menu("Export", [*ty], Message::Export)], hex] + .width(Fill) + .height(Fill); container(content).width(Fill).height(Fill).into() } - Some(WindowType::TileMap) => dbg_image(DbgImage::NameTable(self.nes.ppu())).into(), - Some(WindowType::TileViewer) => { - dbg_image(DbgImage::PatternTable(self.nes.ppu())).into() + Some(WindowType::TileMap) => { + dbg_image(DbgImage::NameTable(self.nes.mem(), self.nes.ppu())).into() + } + Some(WindowType::TileViewer) => { + dbg_image(DbgImage::PatternTable(self.nes.mem(), self.nes.ppu())).into() + } + Some(WindowType::Palette) => { + dbg_image(DbgImage::Palette(self.nes.mem(), self.nes.ppu())).into() } - Some(WindowType::Palette) => dbg_image(DbgImage::Palette(self.nes.ppu())).into(), Some(WindowType::Debugger) => { container(self.debugger.view(&self.nes).map(Message::Debugger)) .width(Fill) @@ -263,7 +325,11 @@ impl Emulator { row![ header_menu( "Console", - [HeaderButton::Reset, HeaderButton::PowerCycle,], + [ + HeaderButton::OpenRom, + HeaderButton::Reset, + HeaderButton::PowerCycle, + ], Message::Header ), header_menu( diff --git a/src/mem.rs b/src/mem.rs index ce6b193..1e60c57 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -1,5 +1,11 @@ -use crate::{hex_view::Memory, ppu::PPUMMRegisters}; +use std::{fmt, sync::Arc}; +use crate::{ + CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState, hex_view::Memory, + ppu::PPUMMRegisters, +}; + +#[derive(Debug, Clone)] pub enum Value<'a, R> { Value(u8), Register { reg: &'a R, offset: u16 }, @@ -14,23 +20,29 @@ impl Value<'_, R> { } } +#[derive(Debug, Clone)] pub enum Data { RAM(Vec), - ROM(Vec), - Mirror(usize), + ROM(Arc<[u8]>), + Mirror(u16), Reg(R), - // Disabled(), + Disabled, } +#[derive(Debug, Clone)] pub struct Segment { - name: &'static str, + name: SegmentId, position: u16, size: u16, mem: Data, } impl Segment { - pub fn ram(name: &'static str, position: u16, size: u16) -> Self { + fn is(&self, id: SegmentId) -> bool { + self.name == id + } + + fn ram(name: SegmentId, position: u16, size: u16) -> Self { Self { name, position, @@ -38,15 +50,19 @@ impl Segment { mem: Data::RAM(vec![0u8; size as usize]), } } - pub fn rom(name: &'static str, position: u16, raw: &[u8]) -> Self { + fn rom(name: SegmentId, position: u16, raw: Raw) -> Self + where + Arc<[u8]>: From, + { + let raw = Arc::from(raw); Self { name, position, size: raw.len() as u16, - mem: Data::ROM(Vec::from(raw)), + mem: Data::ROM(raw), } } - pub fn reg(name: &'static str, position: u16, size: u16, reg: R) -> Self { + fn reg(name: SegmentId, position: u16, size: u16, reg: R) -> Self { Self { name, position, @@ -54,7 +70,7 @@ impl Segment { mem: Data::Reg(reg), } } - pub fn mirror(name: &'static str, position: u16, size: u16, of: usize) -> Self { + fn mirror(name: SegmentId, position: u16, size: u16, of: u16) -> Self { Self { name, position, @@ -62,20 +78,45 @@ impl Segment { mem: Data::Mirror(of), } } + + // fn take_rom(&mut self) -> Vec { + // match std::mem::replace(&mut self.mem, Data::Disabled) { + // Data::ROM(items) => items, + // _ => panic!("Cannot take rom since memory is not rom"), + // } + // } + + // fn set_rom(&mut self, rom: Vec) { + // assert!( + // matches!(self.mem, Data::Disabled), + // "Cannot set non-disabled memory to rom" + // ); + // self.mem = Data::ROM(rom); + // } + + fn swap_rom(&mut self, rom: Arc<[u8]>) { + match &mut self.mem { + Data::ROM(items) => *items = rom, + _ => panic!("Cannot swap rom since memory is not rom"), + } + } + + pub fn power_cycle(&mut self) { + match &mut self.mem { + Data::RAM(v) => v.fill(0), + _ => (), + } + } } +#[derive(Debug, Clone)] pub struct MemoryMap { edit_ver: usize, segments: Vec>, + // map: Remapper, } -impl MemoryMap { - pub fn new(segments: Vec>) -> Self { - Self { - edit_ver: 0, - segments, - } - } +impl MemoryMap { pub fn read(&self, addr: u16) -> Value<'_, R> { // self.edit_ver += 1; for segment in &self.segments { @@ -87,11 +128,13 @@ impl MemoryMap { reg, offset: addr - segment.position, }, - Data::Mirror(index) => { + Data::Mirror(pos) => { let offset = addr - segment.position; - let s = &self.segments[*index]; - self.read(s.position + offset % s.size) - } // Data::Disabled() => todo!(), + self.read(pos + offset) + // let s = &self.segments[*index]; + // self.read(s.position + offset % s.size) + } + Data::Disabled => todo!(), }; } } @@ -100,26 +143,32 @@ impl MemoryMap { // todo!("Open bus") } - pub fn write(&mut self, addr: u16, val: u8, reg_fn: impl FnOnce(&R, u16, u8)) { + pub fn write(&mut self, addr: u16, val: u8) -> Option<(R, u16, u8)> { self.edit_ver += 1; for segment in &mut self.segments { if segment.position <= addr && addr - segment.position < segment.size { return match &mut segment.mem { Data::RAM(items) => { items[(addr - segment.position) as usize] = val; + None } - Data::ROM(_items) => (), - Data::Reg(reg) => reg_fn(reg, addr - segment.position, val), - Data::Mirror(index) => { + Data::ROM(_items) => None, + Data::Reg(reg) => Some((*reg, addr - segment.position, val)), + Data::Mirror(pos) => { + let pos = *pos; let offset = addr - segment.position; - let index = *index; - let s = &self.segments[index]; - self.write(s.position + offset % s.size, val, reg_fn) - } // Data::Disabled() => todo!(), + self.write(pos + offset, val) + // let index = *index; + // let s = &self.segments[index]; + // self.write(s.position + offset % s.size, val) + } + Data::Disabled => todo!(), }; } } - todo!("Open bus") + // iirc, open bus just drops writes + None + // todo!("Open bus (${addr:04X})") } pub fn clear(&mut self) { @@ -131,51 +180,26 @@ impl MemoryMap { } } - pub(crate) fn rom(&self, idx: usize) -> Option<&[u8]> { - if let Some(Segment { - mem: Data::ROM(val), - .. - }) = self.segments.get(idx) - { - Some(val) - } else { - None + pub fn power_cycle(&mut self) -> Self { + for seg in &mut self.segments { + seg.power_cycle(); + } + let segments = std::mem::take(&mut self.segments); + Self { + edit_ver: self.edit_ver + 1, + segments, + // map: self.map.power_cycle(), } } - pub(crate) fn rom_or_ram(&self, idx: usize) -> Option<&[u8]> { - if let Some(Segment { - mem: Data::ROM(val), - .. - }) = self.segments.get(idx) - { - Some(val) - } else if let Some(Segment { - mem: Data::RAM(_), .. - }) = self.segments.get(idx) - { - Some(&[]) - } else { - None + fn find(&mut self, id: SegmentId) -> Option<&mut Segment> { + for s in &mut self.segments { + if s.is(id) { + return Some(s); + } } + None } - - // pub fn with_peek_reg<'s>(&'s self, f: impl (Fn(&R, u16) -> Option) + 's) -> impl Memory + 's { - // struct MemImpl<'a, R>(&'a MemoryMap, Box Option + 'a>); - // impl Memory for MemImpl<'_, R> { - // fn peek(&self, val: u16) -> Option { - // match self.0.read(val) { - // Value::Value(v) => Some(v), - // Value::Register { reg, offset } => self.1(reg, offset), - // } - // } - - // fn edit_ver(&self) -> usize { - // self.0.edit_ver() - // } - // } - // MemImpl(self, Box::new(f)) - // } } impl Memory for MemoryMap { @@ -186,11 +210,11 @@ impl Memory for MemoryMap { Data::RAM(items) => Some(items[(addr - segment.position) as usize]), Data::ROM(items) => Some(items[(addr - segment.position) as usize]), Data::Reg(_) => None, - Data::Mirror(index) => { + Data::Mirror(pos) => { let offset = addr - segment.position; - let s = &self.segments[*index]; - self.peek(s.position + offset % s.size) - } // Data::Disabled() => todo!(), + self.peek(pos + offset) + } + Data::Disabled => None, }; } } @@ -203,48 +227,470 @@ impl Memory for MemoryMap { } #[derive(Clone, Copy, PartialEq, Eq)] -pub struct Mapper { +struct Mapper { horizontal_name_table: bool, + mapper: u16, + sub_mapper: u8, + prg_rom_size: u8, + chr_rom_size: u8, } -impl Mapper { - pub fn from_flags(flags: u8) -> Self { - if flags & 0b11111110 != 0 { - todo!("Support other mapper flags"); - } - Self { - horizontal_name_table: flags & (1 << 0) == 1, - } - } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum SegmentId { + #[allow(unused)] + TestRam, - pub(crate) fn ppu_map(&self, rom: &[u8]) -> MemoryMap { - let chr = if rom.len() == 0 { - Segment::ram("CHR RAM", 0x0000, 0x2000) + InternalRam, + InternalRamMirror, + PpuRegisters, + ApuIoRegisters, + PrgRom, + + Nametable0, + Nametable1, + Nametable2, + Nametable3, + VramMirror, + PaletteControl, + PaletteMirror, + PpuRom, + PpuRam, + + // CpuBank(u32), + PrgBank0, + PrgBank1, + ChrBank0, + ChrBank1, +} + +impl fmt::Display for SegmentId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +#[derive(Clone)] +pub struct Mapped { + cpu: MemoryMap, + ppu: MemoryMap, + mapper: Remapper, +} + +impl Mapped { + pub fn from_rom(rom: &[u8]) -> Self { + let (header, rom) = rom.split_at(0x10); + let nes_20 = header[7] & 0x0C == 0x08; + // assert!(nes_20, "Only supports nes 2.0 format"); + // if header[6] & 0b11111110 != 0 { + // todo!("Support other mapper flags, {:08b}", header[6]); + // } + let mapper = if nes_20 { + assert_eq!(header[9], 0, "No support for larger PRG/CHR roms"); + assert_eq!(header[10], 0, "No support for PRG-RAM/EEPROM"); + assert_eq!(header[11], 0, "No support for CHR-RAM"); + assert!(header[12] == 0 || header[12] == 2, "Only support NTSC NES"); + Mapper { + horizontal_name_table: header[6] & (1 << 0) == 1, + mapper: ((header[6] as u16 & 0xF0) >> 4) + | ((header[7] as u16 & 0xF0) >> 0) + | ((header[8] as u16 & 0x0F) << 8), + sub_mapper: (header[8] & 0xF0) >> 4, + prg_rom_size: header[4], + chr_rom_size: header[5], + } } else { - Segment::rom("CHR ROM", 0x0000, rom) + Mapper { + horizontal_name_table: header[6] & (1 << 0) == 1, + mapper: ((header[6] as u16 & 0xF0) >> 4) | ((header[7] as u16 & 0xF0) >> 0), + sub_mapper: 0, + prg_rom_size: header[4], + chr_rom_size: header[5], + } }; - if self.horizontal_name_table { - MemoryMap::new(vec![ - chr, - Segment::ram("Internal VRAM", 0x2000, 0x400), - Segment::ram("Internal VRAM", 0x2400, 0x400), - Segment::mirror("Internal VRAM", 0x2800, 0x400, 1), - Segment::mirror("Internal VRAM", 0x2C00, 0x400, 2), - Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1), - Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette), - Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 6), - ]) + let (prg_rom, rom) = rom.split_at(mapper.prg_rom_size as usize * 0x4000); + let (chr_rom, rom) = rom.split_at(mapper.chr_rom_size as usize * 0x2000); + // assert_eq!(rom.len(), 0); + // let prg_rom = &file[self.cpu_offset()..self.ppu_offset()]; + let mut cpu_segments = vec![ + Segment::ram(SegmentId::InternalRam, 0x0000, 0x0800), + Segment::mirror(SegmentId::InternalRamMirror, 0x0800, 0x1800, 0x0000), + Segment::reg(SegmentId::PpuRegisters, 0x2000, 0x0008, CPUMMRegisters::PPU), + Segment::reg( + SegmentId::ApuIoRegisters, + 0x4000, + 0x0018, + CPUMMRegisters::APU, + ), + ]; + let mut ppu_segments = vec![ + Segment::mirror(SegmentId::VramMirror, 0x3000, 0x0F00, 0x0000), + Segment::reg( + SegmentId::PaletteControl, + 0x3F00, + 0x0020, + PPUMMRegisters::Palette, + ), + Segment::mirror(SegmentId::PaletteMirror, 0x3F20, 0x00E0, 0x3F00), + ]; + if mapper.horizontal_name_table { + ppu_segments.push(Segment::ram(SegmentId::Nametable0, 0x2000, 0x400)); + ppu_segments.push(Segment::ram(SegmentId::Nametable1, 0x2400, 0x400)); + ppu_segments.push(Segment::mirror( + SegmentId::Nametable2, + 0x2800, + 0x400, + 0x2000, + )); + ppu_segments.push(Segment::mirror( + SegmentId::Nametable3, + 0x2C00, + 0x400, + 0x2400, + )); } else { - MemoryMap::new(vec![ - chr, - Segment::ram("Internal VRAM", 0x2000, 0x400), - Segment::mirror("Internal VRAM", 0x2400, 0x400, 1), - Segment::ram("Internal VRAM", 0x2800, 0x400), - Segment::mirror("Internal VRAM", 0x2C00, 0x400, 3), - Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1), - Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette), - Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 6), - ]) + ppu_segments.push(Segment::ram(SegmentId::Nametable0, 0x2000, 0x400)); + ppu_segments.push(Segment::mirror( + SegmentId::Nametable1, + 0x2400, + 0x400, + 0x2000, + )); + ppu_segments.push(Segment::ram(SegmentId::Nametable2, 0x2800, 0x400)); + ppu_segments.push(Segment::mirror( + SegmentId::Nametable3, + 0x2C00, + 0x400, + 0x2800, + )); + } + let remap = if mapper.mapper == 0 { + cpu_segments.push(Segment::rom( + SegmentId::PrgRom, + 0x8000 + (0x8000 - prg_rom.len() as u16), + prg_rom, + )); + if chr_rom.len() == 0 { + ppu_segments.push(Segment::ram(SegmentId::PpuRam, 0, 0x2000)); + } else { + ppu_segments.push(Segment::rom(SegmentId::PpuRom, 0, chr_rom)); + } + Remapper::None + } else if mapper.mapper == 1 && mapper.sub_mapper == 0 { + let prg_banks: Vec> = + prg_rom.chunks(0x4000).map(|ch| Arc::from(ch)).collect(); + for (i, b) in prg_banks.iter().enumerate() { + println!("{i}: {:X?}", &b[0x3FF0..]); + } + cpu_segments.push(Segment::rom( + SegmentId::PrgBank0, + 0x8000, + prg_banks.first().unwrap().clone(), + )); + cpu_segments.push(Segment::rom( + SegmentId::PrgBank1, + 0xC000, + prg_banks.last().unwrap().clone(), + )); + println!("CHR_ROM: {}", chr_rom.len()); + let chr_banks: Vec> = + chr_rom.chunks(0x1000).map(|ch| Arc::from(ch)).collect(); + if chr_rom.len() == 0 { + ppu_segments.push(Segment::ram(SegmentId::PpuRam, 0, 0x2000)); + } else { + ppu_segments.push(Segment::rom( + SegmentId::ChrBank0, + 0x0000, + chr_banks[0].clone(), + )); + ppu_segments.push(Segment::rom( + SegmentId::ChrBank1, + 0x1000, + chr_banks[1].clone(), + )); + } + Remapper::MMC1 { + shift_reg: 0, + count: 0, + prg_banks, + chr_banks, + mode: MMC1Mode::LastFixed, + cur_prg_bank: 0, + } + } else { + todo!() + }; + Self { + cpu: MemoryMap { + edit_ver: 0, + segments: cpu_segments, + }, + ppu: MemoryMap { + edit_ver: 0, + segments: ppu_segments, + }, + mapper: remap, + } + } + + pub fn power_cylce(&mut self) -> Self { + // TODO: mapper needs to reset cpu and ppu mem maps + let mut cpu = self.cpu.power_cycle(); + let mut ppu = self.ppu.power_cycle(); + Self { + mapper: self.mapper.power_cycle(&mut cpu, &mut ppu), + cpu, + ppu, + } + } + + pub fn peek_ppu(&self, addr: u16) -> Option { + self.ppu.peek(addr) + } + pub fn ppu_edit_ver(&self) -> usize { + self.ppu.edit_ver + } + pub fn peek_cpu(&self, addr: u16) -> Option { + self.cpu.peek(addr) + } + pub fn cpu_edit_ver(&self) -> usize { + self.cpu.edit_ver + } + + #[cfg(test)] + pub fn test_ram() -> Self { + Self { + cpu: MemoryMap { + edit_ver: 0, + segments: vec![Segment::ram(SegmentId::TestRam, 0, 0x8000)], + }, + ppu: MemoryMap { + edit_ver: 0, + segments: vec![Segment::ram(SegmentId::TestRam, 0, 0x8000)], + }, + mapper: Remapper::None, + } + } +} + +pub struct CpuMem<'a> { + mem: &'a mut Mapped, + ppu: &'a mut PPU, + apu: &'a mut APU, + dma: &'a mut DmaState, + controllers: &'a mut Controllers, +} + +impl<'a> CpuMem<'a> { + pub fn new( + mem: &'a mut Mapped, + ppu: &'a mut PPU, + apu: &'a mut APU, + dma: &'a mut DmaState, + controllers: &'a mut Controllers, + ) -> Self { + Self { + mem, + ppu, + apu, + dma, + controllers, + } + } + pub fn read(&mut self, addr: u16) -> u8 { + match self.mem.mapper { + _ => (), + } + match self.mem.cpu.read(addr) { + Value::Value(v) => v, + Value::Register { reg, offset } => match reg { + CPUMMRegisters::PPU => self.ppu.read_reg(&mut PpuMem::new(self.mem), offset), + CPUMMRegisters::APU => { + if offset == 0x014 { + todo!("OAM DMA") + } else if offset == 0x16 { + self.controllers.read_joy1() + } else if offset == 0x17 { + self.controllers.read_joy2() + } else { + self.apu.read_reg(offset) + } + } + }, + } + } + pub fn write(&mut self, addr: u16, val: u8) { + match &mut self.mem.mapper { + Remapper::MMC1 { + shift_reg, + count, + mode, + prg_banks, + chr_banks, + cur_prg_bank, + } if addr & 0x8000 != 0 => { + if val & 0x80 != 0 { + *shift_reg = 0; + *count = 0; + } else if *count == 4 { + let val = (*shift_reg << 1) | (val & 0x01); + if (addr & 0x6000) >> 13 == 0 { + // TODO: fix mem layout if it's changed + if val & 0b01100 == 0b01000 { + *mode = MMC1Mode::FirstFixed; + let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap(); + bank0.swap_rom(prg_banks[0].clone()); + let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap(); + bank1.swap_rom(prg_banks[*cur_prg_bank].clone()); + } else if val & 0b01100 == 0b01100 { + *mode = MMC1Mode::LastFixed; + let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap(); + bank0.swap_rom(prg_banks[*cur_prg_bank].clone()); + let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap(); + bank1.swap_rom(prg_banks.last().unwrap().clone()); + } else { + *mode = MMC1Mode::Full; + let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap(); + bank0.swap_rom(prg_banks[*cur_prg_bank & !1].clone()); + let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap(); + bank1.swap_rom(prg_banks[(*cur_prg_bank & !1) + 1].clone()); + } + if val & 0b10000 == 0b10000 { + // TODO: CHR-ROM mode + } + // TODO: Set name table mirroring + if val & 0b00011 == 0b00000 { + } else if val & 0b00011 == 0b00001 { + } else if val & 0b00011 == 0b00010 { + } else { + } + } else if (addr & 0x6000) >> 13 == 1 { + if chr_banks.len() != 0 { + todo!("Swap CHR bank 0") + } + } else if (addr & 0x6000) >> 13 == 2 { + if chr_banks.len() != 0 { + todo!("Swap CHR bank 1") + } + } else if (addr & 0x6000) >> 13 == 3 { + *cur_prg_bank = (val & 0x0F) as usize; + match mode { + MMC1Mode::Full => { + let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap(); + bank0.swap_rom(prg_banks[*cur_prg_bank & !1].clone()); + let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap(); + bank1.swap_rom(prg_banks[(*cur_prg_bank & !1) + 1].clone()); + } + MMC1Mode::FirstFixed => { + let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap(); + bank1.swap_rom(prg_banks[(val & 0x0F) as usize].clone()); + } + MMC1Mode::LastFixed => { + let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap(); + bank0.swap_rom(prg_banks[(val & 0x0F) as usize].clone()); + } + } + // TODO: handle MSB, changes some stuff... + } else { + todo!("Handle reg write {} => {}", val, (addr & 0x6000) >> 13,) + } + *shift_reg = 0; + *count = 0; + } else { + *shift_reg = (*shift_reg << 1) | (val & 0x01); + *count += 1; + } + } + _ => (), + } + match self.mem.cpu.write(addr, val) { + Some((CPUMMRegisters::PPU, offset, val)) => { + self.ppu.write_reg(&mut PpuMem::new(self.mem), offset, val) + } + Some((CPUMMRegisters::APU, offset, val)) => { + if offset == 0x014 { + *self.dma = DmaState::Running { + cpu_addr: (val as u16) << 8, + rem: 0xFF, + read: None, + }; + } else if offset == 0x16 { + self.controllers.write_joy_strobe(val); + } else { + self.apu.write_reg(offset, val); + } + } + _ => (), + } + } +} + +pub struct PpuMem<'a>(&'a mut Mapped); + +impl<'a> PpuMem<'a> { + pub fn new(mem: &'a mut Mapped) -> Self { + Self(mem) + } + pub fn read(&mut self, addr: u16) -> Value<'_, PPUMMRegisters> { + self.0.ppu.read(addr) + } + pub fn write(&mut self, addr: u16, val: u8, reg_fn: impl FnOnce(&PPUMMRegisters, u16, u8)) { + match self.0.ppu.write(addr, val) { + Some((r, o, v)) => reg_fn(&r, o, v), + None => (), + } + } +} + +#[derive(Debug, Clone)] +enum MMC1Mode { + Full, + FirstFixed, + LastFixed, +} + +#[derive(Debug, Clone)] +enum Remapper { + None, + MMC1 { + shift_reg: u8, + count: u8, + prg_banks: Vec>, + chr_banks: Vec>, + mode: MMC1Mode, + cur_prg_bank: usize, + }, +} + +impl Remapper { + fn power_cycle( + &mut self, + cpu: &mut MemoryMap, + _ppu: &mut MemoryMap, + ) -> Self { + match self { + Remapper::None => Remapper::None, + Remapper::MMC1 { + prg_banks, + chr_banks, + .. + } => { + let prg_banks = std::mem::take(prg_banks); + let chr_banks = std::mem::take(chr_banks); + cpu.find(SegmentId::PrgBank0) + .unwrap() + .swap_rom(prg_banks[0].clone()); + cpu.find(SegmentId::PrgBank1) + .unwrap() + .swap_rom(prg_banks.last().unwrap().clone()); + Remapper::MMC1 { + shift_reg: 0, + count: 0, + prg_banks, + chr_banks, + mode: MMC1Mode::LastFixed, + cur_prg_bank: 0, + } + } } } } diff --git a/src/ppu.rs b/src/ppu.rs index ba48ebb..d0cbaf0 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -8,7 +8,7 @@ use iced::{ use crate::{ hex_view::Memory, - mem::{Mapper, MemoryMap, Segment}, + mem::{Mapped, PpuMem}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -72,15 +72,18 @@ impl RenderBuffer { } } -pub(crate) enum PPUMMRegisters { +#[derive(Debug, Clone, Copy)] +pub enum PPUMMRegisters { Palette, } +#[derive(Debug, Clone)] pub struct OAM { mem: Vec, addr: u8, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum BackgroundState { NameTableBytePre, NameTableByte, @@ -92,6 +95,7 @@ enum BackgroundState { PatternTableTileHigh, } +#[derive(Debug, Clone)] pub struct Background { /// Current vram address, 15 bits pub v: u16, @@ -119,6 +123,7 @@ pub struct Background { pub cur_shift_low: u32, } +#[derive(Debug, Clone, Copy)] pub struct Mask { pub grayscale: bool, pub background_on_left_edge: bool, @@ -453,6 +458,7 @@ const COLORS: &'static [Color; 0b100_0000] = &[ }, // 3F ]; +#[derive(Debug, Clone)] pub struct Palette { colors: &'static [Color; 0x40], ram: [u8; 0x20], @@ -465,6 +471,7 @@ impl Palette { } } +#[derive(Clone)] pub struct PPU { // registers: PPURegisters, pub frame_count: usize, @@ -477,7 +484,6 @@ pub struct PPU { pub mask: Mask, pub vblank: bool, - pub(crate) memory: MemoryMap, palette: Palette, pub background: Background, oam: OAM, @@ -505,7 +511,10 @@ impl std::fmt::Debug for PPU { } impl PPU { - pub fn with_chr_rom(rom: &[u8], mapper: Mapper) -> Self { + // pub fn with_chr_rom(rom: &[u8], mapper: Mapper) -> Self { + // todo!() + // } + pub fn init() -> Self { Self { cycle: 25, dbg_int: false, @@ -536,7 +545,6 @@ impl PPU { scanline: 0, pixel: 25, render_buffer: RenderBuffer::empty(), - memory: mapper.ppu_map(rom), background: Background { v: 0, t: 0, @@ -561,17 +569,14 @@ impl PPU { } } pub fn reset(&mut self) { - *self = Self { - memory: std::mem::replace(&mut self.memory, MemoryMap::new(vec![])), - ..Self::with_chr_rom(&[], Mapper::from_flags(0)) - }; + *self = Self::init(); } pub fn rendering_enabled(&self) -> bool { self.mask.enable_background || self.mask.enable_sprites } - pub fn read_reg(&mut self, offset: u16) -> u8 { + pub fn read_reg(&mut self, mem: &mut PpuMem<'_>, offset: u16) -> u8 { match offset { 0 => panic!("ppuctrl is write-only"), 1 => panic!("ppumask is write-only"), @@ -587,12 +592,9 @@ impl PPU { 6 => panic!("ppuaddr is write-only"), 7 => { // println!("Updating v for ppudata read"); - let val = self - .memory - .read(self.background.v) - .reg_map(|a, off| match a { - PPUMMRegisters::Palette => self.palette.ram[off as usize], - }); + let val = mem.read(self.background.v).reg_map(|a, off| match a { + PPUMMRegisters::Palette => self.palette.ram[off as usize], + }); // if self.background self.increment_v(); val @@ -601,7 +603,7 @@ impl PPU { _ => panic!("No register at {:02X}", offset), } } - pub fn write_reg(&mut self, offset: u16, val: u8) { + pub fn write_reg(&mut self, mem: &mut PpuMem, offset: u16, val: u8) { match offset { 0x00 => { self.nmi_enabled = val & 0b1000_0000 != 0; @@ -664,13 +666,12 @@ impl PPU { } 0x07 => { // println!("Writing: {:02X}, @{:04X}", val, self.background.v); - self.memory - .write(self.background.v, val, |r, o, v| match r { - PPUMMRegisters::Palette => { - // println!("Writing {:02X} to {:02X}", v & 0x3F, o); - self.palette.ram[o as usize] = v & 0x3F; - } - }); + mem.write(self.background.v, val, |r, o, v| match r { + PPUMMRegisters::Palette => { + // println!("Writing {:02X} to {:02X}", v & 0x3F, o); + self.palette.ram[o as usize] = v & 0x3F; + } + }); self.increment_v(); // self.background.v += 1; // TODO: implement inc behavior } @@ -694,7 +695,7 @@ impl PPU { // // TODO: OAM high addr // } - pub fn run_one_clock_cycle(&mut self) -> bool { + pub fn run_one_clock_cycle(&mut self, mem: &mut PpuMem<'_>) -> bool { self.cycle += 1; self.pixel += 1; if self.scanline == 261 @@ -761,7 +762,7 @@ impl PPU { // let addr = 0x2000 + self.pixel / 8 + self.scanline / 8; let addr = 0x2000 | (self.background.v & 0x0FFF); // println!("Cur: {:04X}, comp: {:04X}", addr, addr_2); - let val = self.memory.read(addr).reg_map(|_, _| 0); + let val = mem.read(addr).reg_map(|_, _| 0); self.background.cur_nametable = val; } else if self.pixel % 8 == 4 { // Attr table fetch @@ -772,7 +773,7 @@ impl PPU { | ((self.background.v >> 2) & 0x07); // println!("Cur: {:04X}, comp: {:04X}", addr, addr_2); // assert_eq!(addr, addr_2); - let val = self.memory.read(addr).reg_map(|_, _| 0); // TODO: handle reg reads + let val = mem.read(addr).reg_map(|_, _| 0); // TODO: handle reg reads self.background.next_attr_2 = val; } else if self.pixel % 8 == 6 { // BG pattern low @@ -784,7 +785,7 @@ impl PPU { } else { 0 }; - let val = self.memory.read(addr).reg_map(|_, _| 0); + let val = mem.read(addr).reg_map(|_, _| 0); self.background.cur_low = val; } else if self.pixel % 8 == 0 && self.pixel > 0 { let addr = self.background.cur_nametable as u16 * 16 @@ -796,7 +797,7 @@ impl PPU { } else { 0 }; - let val = self.memory.read(addr).reg_map(|_, _| todo!()); + let val = mem.read(addr).reg_map(|_, _| todo!()); self.background.cur_high = val; self.background.cur_shift_low |= self.background.cur_low as u32; self.background.cur_shift_high |= self.background.cur_high as u32; @@ -842,7 +843,6 @@ impl PPU { } if self.scanline == 241 && self.pixel == 1 { self.vblank = true; - // self.nmi_waiting = self.nmi_enabled; self.frame_count += 1; self.background.state = BackgroundState::NameTableBytePre; return true; @@ -858,12 +858,6 @@ impl PPU { } pub fn nmi_waiting(&mut self) -> bool { self.vblank && self.nmi_enabled - // if self.nmi_waiting { - // self.nmi_waiting = false; - // return true; - // } else { - // return false; - // } } pub fn peek_irq(&self) -> bool { false @@ -872,30 +866,29 @@ impl PPU { false } - pub fn render_name_table(&self, frame: &mut Frame) { + pub fn render_name_table(&self, mem: &Mapped, frame: &mut Frame) { for y in 0..60 { for x in 0..64 { let row = y % 30; let col = x % 32; let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32); - let name = self.memory.peek((off + col + row * 32) as u16).unwrap() as u16 * 16 + let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as u16 * 16 + if self.background.second_pattern { 0x1000 } else { 0 }; - let attr = self - .memory - .peek((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16) + let attr = mem + .peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16) .unwrap(); // attr << (((col & 1) << 1) | ((row & 1) << 0)) * 2 // let h_off = ((self.pixel - 1) / 16) % 2; // let v_off = (self.scanline / 16) % 2; // let off = v_off * 4 + h_off * 2; - let palette = (attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3; + let palette = (attr >> (((col & 2) << 0) | ((row & 2) << 1))) & 0x3; for y_off in 0..8 { - let low = self.memory.peek(name + y_off).unwrap(); - let high = self.memory.peek(name + y_off + 8).unwrap(); + let low = mem.peek_ppu(name + y_off).unwrap(); + let high = mem.peek_ppu(name + y_off + 8).unwrap(); for bit in 0..8 { frame.fill_rectangle( Point::new( @@ -929,21 +922,20 @@ impl PPU { } } // for - // let pat = self.memory.peek(); + // let pat = mem.peek(); } } } - pub fn name_cursor_info(&self, cursor: Point) -> Option { + pub fn name_cursor_info(&self, mem: &Mapped, cursor: Point) -> Option { let x = (cursor.x / 8.) as usize; let y = (cursor.y / 8.) as usize; if x < 64 && y < 60 { let row = y % 30; let col = x % 32; let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32); - let name = self.memory.peek((off + col + row * 32) as u16).unwrap() as usize; - let attr = self - .memory - .peek((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16) + let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as usize; + let attr = mem + .peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16) .unwrap(); Some(format!( "Row, Column: {}, {} @@ -988,13 +980,13 @@ Attribute data: ${:02X} } } - pub fn render_pattern_tables(&self, frame: &mut Frame) { + pub fn render_pattern_tables(&self, mem: &Mapped, frame: &mut Frame) { for y in 0..16 { for x in 0..16 { let name = (y * 16 + x) * 16; for y_off in 0..8 { - let low = self.memory.peek(name + y_off).unwrap(); - let high = self.memory.peek(name + y_off + 8).unwrap(); + let low = mem.peek_ppu(name + y_off).unwrap(); + let high = mem.peek_ppu(name + y_off + 8).unwrap(); for bit in 0..8 { frame.fill_rectangle( Point::new( @@ -1030,8 +1022,8 @@ Attribute data: ${:02X} for x in 0..16 { let name = (y * 16 + x) * 16; for y_off in 0..8 { - let low = self.memory.peek(name + y_off + 0x1000).unwrap(); - let high = self.memory.peek(name + y_off + 8 + 0x1000).unwrap(); + let low = mem.peek_ppu(name + y_off + 0x1000).unwrap(); + let high = mem.peek_ppu(name + y_off + 8 + 0x1000).unwrap(); for bit in 0..8 { frame.fill_rectangle( Point::new( @@ -1061,7 +1053,7 @@ Attribute data: ${:02X} } } // for - // let pat = self.memory.peek(); + // let pat = mem.peek(); } } } @@ -1105,10 +1097,6 @@ Attribute data: ${:02X} None } } - - pub fn mem(&self) -> &impl Memory { - &self.memory - } } #[cfg(test)] @@ -1117,14 +1105,18 @@ mod tests { #[test] fn ppu_registers() { - let mut ppu = PPU::with_chr_rom(&[0u8; 8192], Mapper::from_flags(0)); + // let mut ppu = PPU::with_chr_rom(&[0u8; 8192], Mapper::from_header(0)); + let mut ppu = PPU::init(); + // let mut mem = MemoryMap::new(vec![Segment::ram("")]); + let mut mem = Mapped::test_ram(); + let mut mem = PpuMem::new(&mut mem); assert_eq!(ppu.background.v, 0); assert_eq!(ppu.background.t, 0); assert_eq!(ppu.background.x, 0); assert_eq!(ppu.background.w, false); - ppu.write_reg(0, 0); + ppu.write_reg(&mut mem, 0, 0); assert_eq!(ppu.background.v, 0); - ppu.write_reg(0, 0b11); + ppu.write_reg(&mut mem, 0, 0b11); assert_eq!( ppu.background.t, 0b0001100_00000000, "Actual: {:016b}", @@ -1132,7 +1124,7 @@ mod tests { ); assert_eq!(ppu.background.w, false); - ppu.write_reg(5, 0x7D); + ppu.write_reg(&mut mem, 5, 0x7D); assert_eq!( ppu.background.t, 0b0001100_00001111, "Actual: {:016b}", @@ -1140,7 +1132,7 @@ mod tests { ); assert_eq!(ppu.background.x, 0b101); assert_eq!(ppu.background.w, true); - ppu.write_reg(5, 0x5E); + ppu.write_reg(&mut mem, 5, 0x5E); assert_eq!( ppu.background.t, 0b1101101_01101111, "Actual: {:016b}", @@ -1149,7 +1141,7 @@ mod tests { assert_eq!(ppu.background.x, 0b101); assert_eq!(ppu.background.w, false); - ppu.write_reg(5, 0x7D); + ppu.write_reg(&mut mem, 5, 0x7D); assert_eq!( ppu.background.t, 0b1101101_01101111, "Actual: {:016b}", @@ -1157,7 +1149,7 @@ mod tests { ); assert_eq!(ppu.background.x, 0b101); assert_eq!(ppu.background.w, true); - ppu.read_reg(2); + ppu.read_reg(&mut mem, 2); assert_eq!(ppu.background.w, false); } } diff --git a/src/test_roms/instr_test_v3.rs b/src/test_roms/instr_test_v3.rs index c1ab77e..a7eae5c 100644 --- a/src/test_roms/instr_test_v3.rs +++ b/src/test_roms/instr_test_v3.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::{NES, hex_view::Memory}; +use crate::NES; use super::rom_test; @@ -13,7 +13,7 @@ impl HexDump<'_> { fn get(&self, off: usize) -> Option { match self { Self::NES(_, _, len) if off >= *len => None, - Self::NES(nes, addr, _) => nes.cpu_mem().peek(addr + off as u16), + Self::NES(nes, addr, _) => nes.mem().peek_cpu(addr + off as u16), Self::Lit(items) => items.get(off).copied(), } } @@ -35,7 +35,7 @@ impl fmt::Display for HexDump<'_> { fn mem_cmp(nes: &NES, addr: u16, vals: &[u8]) { for (i, v) in vals.iter().enumerate() { - if nes.cpu_mem().peek(addr + i as u16) != Some(*v) { + if nes.mem().peek_cpu(addr + i as u16) != Some(*v) { panic!( "memcmp assertion failed:\nNES:{}\nTest:{}", HexDump::NES(nes, addr, vals.len()), @@ -46,7 +46,7 @@ fn mem_cmp(nes: &NES, addr: u16, vals: &[u8]) { } rom_test!(crc_check, "crc_check.nes", |nes| { - assert_eq!(nes.last_instruction, "0x8026 HLT :2 []"); + assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []"); assert_eq!(nes.cpu.a, 0x00); mem_cmp(&nes, 0x20, &[0xFF, 0xFF, 0xFF, 0xFF]); mem_cmp(&nes, 0x24, &[0x69, 0xCF, 0xF8, 0x88]); @@ -58,6 +58,6 @@ rom_test!(crc_check, "crc_check.nes", |nes| { }); rom_test!(implied_instructions, "implied_instrs.nes", |nes| { - assert_eq!(nes.last_instruction, "0x8026 HLT :2 []"); + assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []"); assert_eq!(nes.cpu.a, 0x00); }); diff --git a/src/test_roms/interrupts.rs b/src/test_roms/interrupts.rs index ab9a9ea..8e5e07f 100644 --- a/src/test_roms/interrupts.rs +++ b/src/test_roms/interrupts.rs @@ -1,27 +1,25 @@ -use crate::hex_view::Memory; - use super::rom_test; rom_test!(int_nmi_timing, "int_nmi_timing.nes", |nes| { - assert_eq!(nes.last_instruction, "0x801B HLT :2 []"); + assert_eq!(nes.last_instruction(), "0x801B HLT :2 []"); assert_eq!(nes.clock_count, 260881); - assert_eq!(nes.cycle, 86967); + assert_eq!(nes.cpu_cycle(), 86967); assert_eq!(nes.ppu().pixel, 40); assert_eq!(nes.ppu().scanline, 241); assert_eq!(nes.ppu().cycle, 260905); - assert_eq!(nes.cpu_mem().peek(0x1FF), Some(0x80)); - assert_eq!(nes.cpu_mem().peek(0x1FE), Some(0x17)); - assert_eq!(nes.cpu_mem().peek(0x1FD), Some(0xA4)); + assert_eq!(nes.mem().peek_cpu(0x1FF), Some(0x80)); + assert_eq!(nes.mem().peek_cpu(0x1FE), Some(0x17)); + assert_eq!(nes.mem().peek_cpu(0x1FD), Some(0xA4)); }); rom_test!(int_nmi_exit_timing, "int_nmi_exit_timing.nes", |nes| { - assert_eq!(nes.last_instruction, "0x801F HLT :2 []"); + assert_eq!(nes.last_instruction(), "0x801F HLT :2 []"); // assert_eq!(nes.clock_count, 260881); - assert_eq!(nes.cycle, 86980); + assert_eq!(nes.cpu_cycle(), 86980); assert_eq!(nes.ppu().pixel, 79); assert_eq!(nes.ppu().scanline, 241); assert_eq!(nes.ppu().cycle, 260905 - 40 + 79); - assert_eq!(nes.cpu_mem().peek(0x1FF), Some(0x80)); - assert_eq!(nes.cpu_mem().peek(0x1FE), Some(0x1B)); - assert_eq!(nes.cpu_mem().peek(0x1FD), Some(0x26)); + assert_eq!(nes.mem().peek_cpu(0x1FF), Some(0x80)); + assert_eq!(nes.mem().peek_cpu(0x1FE), Some(0x1B)); + assert_eq!(nes.mem().peek_cpu(0x1FD), Some(0x26)); }); diff --git a/src/test_roms/mod.rs b/src/test_roms/mod.rs index dd38002..f623860 100644 --- a/src/test_roms/mod.rs +++ b/src/test_roms/mod.rs @@ -3,8 +3,6 @@ mod instr_test_v3; mod ppu; mod interrupts; -use crate::hex_view::Memory; - macro_rules! rom_test { ($name:ident, $rom:literal, |$nes:ident| $eval:expr) => { rom_test!($name, $rom, timeout = 10000000, |$nes| $eval); @@ -39,8 +37,8 @@ macro_rules! rom_test { pub(crate) use rom_test; rom_test!(basic_cpu, "basic-cpu.nes", |nes| { - assert_eq!(nes.last_instruction, "0x8001 HLT :2 []"); - assert_eq!(nes.cycle, 10); + assert_eq!(nes.last_instruction(), "0x8001 HLT :2 []"); + assert_eq!(nes.cpu_cycle(), 10); assert_eq!(nes.cpu.pc, 0x8001); assert_eq!(nes.ppu.pixel, 34); @@ -48,8 +46,8 @@ rom_test!(basic_cpu, "basic-cpu.nes", |nes| { nes.repl_nop(); nes.run_with_timeout(200); - assert_eq!(nes.last_instruction, "0x8002 HLT :2 []"); - assert_eq!(nes.cycle, 12); + assert_eq!(nes.last_instruction(), "0x8002 HLT :2 []"); + assert_eq!(nes.cpu_cycle(), 12); assert_eq!(nes.cpu.pc, 0x8002); assert_eq!(nes.ppu.pixel, 40); @@ -57,8 +55,8 @@ rom_test!(basic_cpu, "basic-cpu.nes", |nes| { }); rom_test!(basic_cpu_with_nop, "basic-cpu-nop.nes", |nes| { - assert_eq!(nes.last_instruction, "0x8002 HLT :2 []"); - assert_eq!(nes.cycle, 12); + assert_eq!(nes.last_instruction(), "0x8002 HLT :2 []"); + assert_eq!(nes.cpu_cycle(), 12); assert_eq!(nes.cpu.pc, 0x8002); assert_eq!(nes.ppu.pixel, 40); @@ -66,22 +64,22 @@ rom_test!(basic_cpu_with_nop, "basic-cpu-nop.nes", |nes| { }); rom_test!(read_write, "read_write.nes", |nes| { - assert_eq!(nes.last_instruction, "0x800C HLT :2 []"); - assert_eq!(nes.cycle, 25); + assert_eq!(nes.last_instruction(), "0x800C HLT :2 []"); + assert_eq!(nes.cpu_cycle(), 25); assert_eq!(nes.cpu.pc, 0x800C); assert_eq!(nes.cpu.sp, 0xFD); assert_eq!(nes.cpu.a, 0xAA); assert_eq!(nes.cpu.x, 0xAA); assert_eq!(nes.cpu.y, 0xAA); - assert_eq!(nes.cpu_mem().peek(0x0000).unwrap(), 0xAA); - assert_eq!(nes.cpu_mem().peek(0x0001).unwrap(), 0xAA); - assert_eq!(nes.cpu_mem().peek(0x0002).unwrap(), 0xAA); + assert_eq!(nes.mem().peek_cpu(0x0000).unwrap(), 0xAA); + assert_eq!(nes.mem().peek_cpu(0x0001).unwrap(), 0xAA); + assert_eq!(nes.mem().peek_cpu(0x0002).unwrap(), 0xAA); }); rom_test!(basic_init_0, "basic_init_0.nes", |nes| { - assert_eq!(nes.last_instruction, "0x8017 HLT :2 []"); - assert_eq!(nes.cycle, 40); + assert_eq!(nes.last_instruction(), "0x8017 HLT :2 []"); + assert_eq!(nes.cpu_cycle(), 40); assert_eq!(nes.cpu.pc, 0x8017); assert_eq!(nes.cpu.sp, 0xFF); @@ -91,8 +89,8 @@ rom_test!(basic_init_0, "basic_init_0.nes", |nes| { }); rom_test!(basic_init_1, "basic_init_1.nes", |nes| { - assert_eq!(nes.last_instruction, "0x801C HLT :2 []"); - assert_eq!(nes.cycle, 27402); + assert_eq!(nes.last_instruction(), "0x801C HLT :2 []"); + assert_eq!(nes.cpu_cycle(), 27402); assert_eq!(nes.cpu.pc, 0x801C); assert_eq!(nes.ppu.pixel, 29); @@ -103,8 +101,8 @@ rom_test!(basic_init_1, "basic_init_1.nes", |nes| { }); rom_test!(basic_init_2, "basic_init_2.nes", |nes| { - assert_eq!(nes.last_instruction, "0x8021 HLT :2 []"); - assert_eq!(nes.cycle, 57179); + assert_eq!(nes.last_instruction(), "0x8021 HLT :2 []"); + assert_eq!(nes.cpu_cycle(), 57179); assert_eq!(nes.cpu.pc, 0x8021); assert_eq!(nes.ppu.pixel, 18); @@ -115,8 +113,8 @@ rom_test!(basic_init_2, "basic_init_2.nes", |nes| { }); rom_test!(basic_init_3, "basic_init_3.nes", |nes| { - assert_eq!(nes.last_instruction, "0x8026 HLT :2 []"); - assert_eq!(nes.cycle, 86963); + assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []"); + assert_eq!(nes.cpu_cycle(), 86963); assert_eq!(nes.cpu.pc, 0x8026); assert_eq!(nes.ppu.pixel, 28); @@ -127,8 +125,8 @@ rom_test!(basic_init_3, "basic_init_3.nes", |nes| { }); rom_test!(even_odd, "even_odd.nes", |nes| { - assert_eq!(nes.last_instruction, "0x8023 HLT :2 []"); - assert_eq!(nes.cycle, 57181); + assert_eq!(nes.last_instruction(), "0x8023 HLT :2 []"); + assert_eq!(nes.cpu_cycle(), 57181); assert_eq!(nes.cpu.pc, 0x8023); assert_eq!(nes.ppu.pixel, 24); @@ -139,8 +137,8 @@ rom_test!(even_odd, "even_odd.nes", |nes| { }); // rom_test!(even_odd, "even_odd.nes", |nes| { -// assert_eq!(nes.last_instruction, "0x8023 HLT :2 []"); -// assert_eq!(nes.cycle, 57182); +// assert_eq!(nes.last_instruction(), "0x8023 HLT :2 []"); +// assert_eq!(nes.cpu_cycle(), 57182); // assert_eq!(nes.cpu.pc, 0x8024); // assert_eq!(nes.ppu.pixel, 25); diff --git a/src/test_roms/ppu.rs b/src/test_roms/ppu.rs index 8a74da5..f1b2b71 100644 --- a/src/test_roms/ppu.rs +++ b/src/test_roms/ppu.rs @@ -1,4 +1,4 @@ -use crate::{hex_view::Memory, Color, RenderBuffer}; +use crate::{Color, RenderBuffer}; use super::rom_test; @@ -19,7 +19,7 @@ const COLOR_0B: Color = Color { }; rom_test!(ppu_fill, "ppu_fill.nes", |nes| { - assert_eq!(nes.last_instruction, "0x802B HLT :2 []"); + assert_eq!(nes.last_instruction(), "0x802B HLT :2 []"); assert_eq!(nes.cpu.a, 0x01); let mut buffer = RenderBuffer::empty(); @@ -32,7 +32,7 @@ rom_test!(ppu_fill, "ppu_fill.nes", |nes| { }); rom_test!(ppu_fill_red, "ppu_fill_red.nes", |nes| { - assert_eq!(nes.last_instruction, "0x803A HLT :2 []"); + assert_eq!(nes.last_instruction(), "0x803A HLT :2 []"); assert_eq!(nes.cpu.a, 0x01); let mut buffer = RenderBuffer::empty(); @@ -45,7 +45,7 @@ rom_test!(ppu_fill_red, "ppu_fill_red.nes", |nes| { }); rom_test!(ppu_fill_palette_1, "ppu_fill_palette_1.nes", |nes| { - assert_eq!(nes.last_instruction, "0x8064 HLT :2 []"); + assert_eq!(nes.last_instruction(), "0x8064 HLT :2 []"); assert_eq!(nes.cpu.a, 0x01); let mut buffer = RenderBuffer::empty(); @@ -62,7 +62,7 @@ rom_test!(ppu_fill_palette_1, "ppu_fill_palette_1.nes", |nes| { }); rom_test!(ppu_fill_name_table, "ppu_fill_name_table.nes", |nes| { - assert_eq!(nes.last_instruction, "0x80B3 HLT :2 []"); + assert_eq!(nes.last_instruction(), "0x80B3 HLT :2 []"); assert_eq!(nes.cpu.a, 0x01); let mut buffer = RenderBuffer::empty(); @@ -81,10 +81,10 @@ rom_test!(ppu_fill_name_table, "ppu_fill_name_table.nes", |nes| { }); rom_test!(ppu_vertical_write, "ppu_vertical_write.nes", |nes| { - assert_eq!(nes.last_instruction, "0x8040 HLT :2 []"); + assert_eq!(nes.last_instruction(), "0x8040 HLT :2 []"); - assert_eq!(nes.ppu().mem().peek(0x2000), Some(1)); - assert_eq!(nes.ppu().mem().peek(0x2020), Some(1)); - assert_eq!(nes.ppu().mem().peek(0x2400), Some(2)); - assert_eq!(nes.ppu().mem().peek(0x2401), Some(2)); + assert_eq!(nes.mem().peek_ppu(0x2000), Some(1)); + assert_eq!(nes.mem().peek_ppu(0x2020), Some(1)); + assert_eq!(nes.mem().peek_ppu(0x2400), Some(2)); + assert_eq!(nes.mem().peek_ppu(0x2401), Some(2)); });