Files
nes-emu/src/lib.rs
2025-12-07 11:34:37 -06:00

1575 lines
64 KiB
Rust

mod apu;
mod controllers;
pub mod header_menu;
pub mod hex_view;
mod mem;
mod ppu;
#[cfg(test)]
mod test_roms;
pub use ppu::{Color, PPU, RenderBuffer};
use std::{fs::File, io::Read, path::Path};
use thiserror::Error;
use tracing::{debug, info};
use crate::{
apu::APU,
controllers::Controllers,
hex_view::Memory,
mem::{MemoryMap, Segment},
};
#[derive(Error, Debug)]
pub enum NESError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("File loaded was invalid")]
InvalidFile,
}
type Result<T> = std::result::Result<T, NESError>;
pub struct NES {
clock_count: usize,
cpu: Cpu,
dma: DmaState,
memory: MemoryMap<CPUMMRegisters>,
ppu: PPU,
apu: APU,
controller: Controllers,
cycle: usize,
last_instruction: String,
dbg_int: bool,
halted: bool,
}
impl std::fmt::Debug for NES {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "NES Emulator")?;
writeln!(
f,
"CPU: {{ a: {:02X}, x: {:02X}, y: {:02X}, flags: {:?}, sp: {:02X}, pc: {:04X} }}",
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),
)?;
write!(f, "PPU: {:?}", self.ppu)?;
Ok(())
}
}
bitfield::bitfield! {
pub struct CpuStatus(u8);
carry, set_carry: 0;
zero, set_zero: 1;
interrupt_disable, set_interrupt_disable: 2;
decimal, set_decimal: 3;
brk, set_brk: 4;
php, set_php: 5;
overflow, set_overflow: 6;
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 {
a: u8,
x: u8,
y: u8,
pc: u16,
sp: u8,
status: CpuStatus,
clock_state: ClockState,
}
enum ClockState {
ReadInstruction,
ReadOperands {
instruction: u8,
ops: [u8; 5],
count: u8,
},
Hold {
cycles: u8,
instruction: u8,
ops: [u8; 5],
count: u8,
},
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,
} => f
.debug_struct("ReadOperands")
.field("instruction", instruction)
.field("ops", &&ops[..*count as usize])
.finish(),
Self::Hold {
cycles,
instruction,
ops,
count,
} => f
.debug_struct("Hold")
.field("cycles", cycles)
.field("instruction", instruction)
.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()
}
}
}
}
enum ExecState {
Done,
MoreParams,
Hold(u8),
}
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 },
}
}
}
pub enum DmaState {
Passive,
Running {
cpu_addr: u16,
rem: u8,
read: Option<u8>,
},
}
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<Path>) -> Result<Self> {
let mut raw = Vec::new();
File::open(file)?.read_to_end(&mut raw)?;
Self::load_nes_file_mem(&raw)
}
pub fn load_nes_file_mem(raw: &[u8]) -> Result<Self> {
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}");
if mapper_flags & 0b11111110 != 0 {
todo!("Support other mapper flags");
}
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],
))
}
fn from_rom(prg_rom: &[u8], chr_rom: &[u8]) -> 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;
segments.push(Segment::rom("PROG ROM", 0x8000, prg_rom));
Self {
cycle: 7,
dbg_int: false,
halted: false,
clock_count: 0,
memory: MemoryMap::new(segments),
cpu: Cpu::init(),
ppu: PPU::with_chr_rom(chr_rom),
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),
0x17 => (), // TODO: frame counter control
_ => 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) -> 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?}", self.cpu.pc - (1 + params.len() as u16), $val, ins, params);
$(
let $name = params[0];
#[allow(unused_assignments)]
{ params = &params[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) => {
()
};
}
match ins {
0x02 => inst!("HLT", 0, || self.halt()),
0x38 => inst!("SEC", 1, || self.cpu.status.set_carry(true)),
0x18 => inst!("CLC", 1, || self.cpu.status.set_carry(false)),
0x78 => inst!("SEI", 1, || self.cpu.status.set_interrupt_disable(true)),
0x58 => inst!("CLI", 1, || self.cpu.status.set_interrupt_disable(false)),
0xF8 => inst!("SED", 1, || self.cpu.status.set_decimal(true)),
0xD8 => inst!("CLD", 1, || self.cpu.status.set_decimal(false)),
0xB8 => inst!("CLV", 1, || self.cpu.status.set_overflow(false)),
0x00 => inst!("BRK", 7, |_ignored| {
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)]);
}),
0x48 => inst!("PHA", 2, || {
self.push(self.cpu.a);
}),
0x08 => inst!("PHP", 2, || {
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);
}),
0x28 => inst!("PLP", 3, || {
self.cpu.status.0 = self.pop() & 0b1100_1111;
}),
// 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);
}),
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);
}),
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);
}),
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);
}),
0xBD => inst!("LDA abs,x", 1, |low, high| {
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);
}),
0xB9 => inst!("LDA abs,y", 1, |low, high| {
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);
}),
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);
}),
0xB1 => inst!("LDA (ind),y", 3, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
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);
}),
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);
}),
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);
}),
0xB6 => inst!("LDX zp,y", 1, |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);
}),
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);
}),
0xBE => inst!("LDX abs,y", 1, |low, high| {
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);
}),
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);
}),
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);
}),
0xB4 => inst!("LDX zp,x", 1, |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);
}),
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);
}),
0xBC => inst!("LDX abs,x", 1, |low, high| {
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);
}),
// Stores
0x85 => inst!("STA zp", 1, |off| {
self.write(off as u16, self.cpu.a);
}),
0x95 => inst!("STA zp,x", 2, |off| {
self.write_zp_x(off, self.cpu.a);
}),
0x8D => inst!("STA abs", 1, |low, high| {
self.write_abs(low, high, self.cpu.a);
}),
0x9D => inst!("STA abs,x", 1, |low, high| {
self.write_abs_x(low, high, self.cpu.a);
}),
0x99 => inst!("STA abs,y", 1, |low, high| {
self.write_abs_y(low, high, 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);
}),
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,
);
}),
0x86 => inst!("STX zp", 1, |off| {
self.write(off as u16, self.cpu.x);
}),
0x96 => inst!("STX zp,y", 2, |off| {
self.write_zp_y(off, self.cpu.x);
}),
0x8E => inst!("STX abs", 1, |low, high| {
self.write_abs(low, high, self.cpu.x);
}),
0x84 => inst!("STY zp", 1, |off| {
self.write(off as u16, self.cpu.y);
}),
0x94 => inst!("STY zp,x", 2, |off| {
self.write_zp_x(off, self.cpu.y);
}),
0x8C => inst!("STY abs", 1, |low, high| {
self.write_abs(low, high, 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);
}),
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);
}),
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);
}),
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);
}),
0x9A => inst!("TXS", 1, || {
self.cpu.sp = self.cpu.x;
}),
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);
}),
// Branch
0x90 => inst!("BCC", !self.cpu.status.carry(), |off| {
if !self.cpu.status.carry() {
self.jmp_rel(off);
}
}),
0xB0 => inst!("BCS", self.cpu.status.carry(), |off| {
if self.cpu.status.carry() {
self.jmp_rel(off);
}
}),
0xF0 => inst!("BEQ", self.cpu.status.zero(), |off| {
if self.cpu.status.zero() {
self.jmp_rel(off);
}
}),
0x30 => inst!("BMI", self.cpu.status.negative(), |off| {
if self.cpu.status.negative() {
self.jmp_rel(off);
}
}),
0xD0 => inst!("BNE", !self.cpu.status.zero(), |off| {
if !self.cpu.status.zero() {
self.jmp_rel(off);
}
}),
0x10 => inst!("BPL", !self.cpu.status.negative(), |off| {
if !self.cpu.status.negative() {
self.jmp_rel(off);
}
}),
0x4C => inst!("JMP abs", 3, |low, high| {
self.cpu.pc = u16::from_le_bytes([low, high]);
}),
0x6C => inst!("JMP abs", 3, |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
]);
}),
0x20 => inst!("JSR", 3, |low, high| {
self.push_16(self.cpu.pc - 1);
self.cpu.pc = u16::from_le_bytes([low, high]);
}),
0x60 => inst!("RTS", 5, || {
self.cpu.pc = self.pop_16() + 1;
}),
0x40 => inst!("RTI", 5, || {
self.cpu.status.0 = self.pop() & 0b1100_1111;
self.cpu.pc = self.pop_16();
}),
// 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);
}),
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);
}),
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);
}),
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);
}),
0xDD => inst!("CMP abs,x", 1, |low, high| {
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);
}),
0xD9 => inst!("CMP abs,y", 1, |low, high| {
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);
}),
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);
}),
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);
}),
0xEC => inst!("CPX zp", 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);
}),
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);
}),
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);
}),
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);
}),
// 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);
}),
0x65 => inst!("ADC zp", 0, |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);
}),
0x75 => inst!("ADC zp,x", 0, |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);
}),
0x6D => inst!("ADC abs", 0, |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);
}),
0x7D => inst!("ADC abs,x", 0, |low, high| {
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);
}),
0x79 => inst!("ADC abs,y", 0, |low, high| {
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);
}),
0x61 => inst!("ADC (ind,x)", 0, |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);
}),
0x71 => inst!("ADC (ind),y", 0, |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);
}),
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);
}),
0xE5 => inst!("SBC zp", 0, |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", 0, |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);
}),
0xED => inst!("SBC abs", 0, |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);
}),
0xFD => inst!("SBC abs,x", 0, |low, high| {
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);
}),
0xF9 => inst!("SBC abs,y", 0, |low, high| {
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);
}),
0xE1 => inst!("SBC (ind,x)", 0, |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);
}),
0xF1 => inst!("SBC (ind),y", 0, |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);
}),
0xC6 => inst!("DEC zp", 3, |off| {
let val = self.read_abs(off, 0);
self.write_abs(off, 0, val.wrapping_sub(1));
self.cpu.status.set_zero(val == 0);
self.cpu.status.set_negative(val & 0x80 == 0x80);
}),
0xD6 => inst!("DEC zp,x", 4, |off| {
let val = self.read_zp_x(off);
self.write_zp_x(off, val.wrapping_sub(1));
self.cpu.status.set_zero(val == 0);
self.cpu.status.set_negative(val & 0x80 == 0x80);
}),
0xCE => inst!("DEC abs", 3, |low, high| {
let val = self.read_abs(low, high);
self.write_abs(low, high, val.wrapping_sub(1));
self.cpu.status.set_zero(val == 0);
self.cpu.status.set_negative(val & 0x80 == 0x80);
}),
0xDE => inst!("DEC abs,x", 3, |low, high| {
let val = self.read_abs_x(low, high);
self.write_abs_x(low, high, val.wrapping_sub(1));
self.cpu.status.set_zero(val == 0);
self.cpu.status.set_negative(val & 0x80 == 0x80);
}),
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);
}),
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);
}),
0xE6 => inst!("INC zp", 3, |off| {
let val = self.read_abs(off, 0);
self.write_abs(off, 0, val.wrapping_add(1));
self.cpu.status.set_zero(val == 0);
self.cpu.status.set_negative(val & 0x80 == 0x80);
}),
0xF6 => inst!("INC zp,x", 4, |off| {
let val = self.read_zp_x(off);
self.write_zp_x(off, val.wrapping_add(1));
self.cpu.status.set_zero(val == 0);
self.cpu.status.set_negative(val & 0x80 == 0x80);
}),
0xEE => inst!("INC abs", 3, |low, high| {
let val = self.read_abs(low, high);
self.write_abs(low, high, val.wrapping_add(1));
self.cpu.status.set_zero(val == 0);
self.cpu.status.set_negative(val & 0x80 == 0x80);
}),
0xFE => inst!("INC abs,x", 3, |low, high| {
let val = self.read_abs_x(low, high);
self.write_abs_x(low, high, val.wrapping_add(1));
self.cpu.status.set_zero(val == 0);
self.cpu.status.set_negative(val & 0x80 == 0x80);
}),
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);
}),
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);
}),
// 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);
}),
0x05 => inst!("ORA zp", 1, |val| {
self.cpu.a |= self.read_abs(val, 0);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
}),
0x15 => inst!("ORA zp,x", 2, |val| {
self.cpu.a |= self.read_zp_x(val);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
}),
0x0D => inst!("ORA abs", 2, |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);
}),
0x1D => inst!("ORA abs,x", 2, |low, high| {
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);
}),
0x19 => inst!("ORA abs,y", 1, |low, high| {
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);
}),
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);
}),
0x11 => inst!("ORA (ind),y", 4, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
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);
}),
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);
}),
0x25 => inst!("AND zp", 1, |val| {
self.cpu.a &= self.read_abs(val, 0);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
}),
0x35 => inst!("AND zp,x", 2, |val| {
self.cpu.a &= self.read_zp_x(val);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
}),
0x2D => inst!("AND abs", 2, |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);
}),
0x3D => inst!("AND abs,x", 2, |low, high| {
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);
}),
0x39 => inst!("AND abs,y", 1, |low, high| {
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);
}),
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);
}),
0x31 => inst!("AND (ind),y", 4, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
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);
}),
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);
}),
0x45 => inst!("EOR zp", 1, |val| {
self.cpu.a ^= self.read_abs(val, 0);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
}),
0x55 => inst!("EOR zp,x", 2, |val| {
self.cpu.a ^= self.read_zp_x(val);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
}),
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);
}),
0x5D => inst!("EOR abs,x", 1, |low, high| {
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);
}),
0x59 => inst!("EOR abs,y", 1, |low, high| {
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);
}),
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);
}),
0x51 => inst!("EOR (ind),y", 3, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
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);
}),
0x24 => inst!("BIT zp", 1, |val| {
let val = self.cpu.a & self.read_abs(val, 0);
self.cpu.status.set_zero(val == 0);
self.cpu.status.set_overflow(val & 0x40 == 0x40);
self.cpu.status.set_negative(val & 0x80 == 0x80);
}),
0x2C => inst!("BIT abs", 1, |low, high| {
let val = self.cpu.a & 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);
}),
// 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);
}),
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);
}),
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);
}),
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);
}),
0x5E => inst!("LSR zp,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);
}),
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);
}),
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);
}),
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);
}),
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);
}),
0x1E => inst!("ASL zp,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);
}),
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(false);
}),
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(false);
self.write_abs(off, 0, val);
}),
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(false);
self.write_zp_x(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(false);
self.write_abs(low, high, val);
}),
0x7E => inst!("ROR zp,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(false);
self.write_abs_x(low, high, 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(false);
}),
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(false);
self.write_abs(off, 0, 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(false);
self.write_zp_x(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(false);
self.write_abs(low, high, val);
}),
0x3E => inst!("ROL zp,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(false);
self.write_abs_x(low, high, val);
}),
0xEA => inst!("NOP", 1, || {}),
_ => todo!("ins: 0x{:04X}: 0x{ins:X}, {params:X?}", self.cpu.pc - 1),
}
}
pub fn peek_opcode(&self) -> u8 {
self.peek(self.cpu.pc)
}
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);
self.cpu.pc = u16::from_le_bytes([self.read(0xFFFA), self.read(0xFFFB)]);
ClockState::ReadInstruction
} else {
ClockState::HoldNmi { cycles: cycles - 1 }
}
}
ClockState::HoldIrq { cycles } => {
if cycles == 0 {
todo!("Run NMI");
} else {
ClockState::HoldIrq { cycles: cycles - 1 }
}
}
ClockState::ReadInstruction => {
if self.ppu.nmi_waiting() || self.apu.nmi_waiting() {
ClockState::HoldNmi { cycles: 6 }
} else if self.ppu.irq_waiting() || self.apu.irq_waiting() {
ClockState::HoldIrq { cycles: 6 }
} else {
let instruction = self.read(self.cpu.pc);
self.cpu.pc = self.cpu.pc.wrapping_add(1);
match self.exec_instruction(instruction, &[], false) {
ExecState::Done => ClockState::ReadInstruction,
ExecState::MoreParams => ClockState::ReadOperands {
instruction,
ops: [0u8; 5],
count: 0,
},
ExecState::Hold(cycles) => ClockState::Hold {
cycles,
instruction,
ops: [0u8; 5],
count: 0,
},
}
}
}
ClockState::ReadOperands {
instruction,
mut ops,
count,
} => {
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) {
ExecState::Done => ClockState::ReadInstruction,
ExecState::MoreParams => ClockState::ReadOperands {
instruction,
ops,
count: count + 1,
},
ExecState::Hold(cycles) => ClockState::Hold {
cycles,
instruction,
ops,
count: count + 1,
},
}
}
ClockState::Hold {
cycles,
instruction,
ops,
count,
} => {
if cycles == 0 {
match self.exec_instruction(instruction, &ops[..count as usize], true) {
ExecState::Done => ClockState::ReadInstruction,
ExecState::MoreParams => ClockState::ReadOperands {
instruction,
ops,
count: count + 1,
},
ExecState::Hold(_) => panic!("Should never return Hold after holding"),
}
} else {
ClockState::Hold {
cycles: cycles - 1,
instruction,
ops,
count,
}
}
}
};
}
fn cpu_cycle(&mut self) {
self.cycle += 1;
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 [0x8031, 0x8014].contains(&self.cpu.pc) {
self.dbg_int = true;
}
}
pub fn run_one_clock_cycle(&mut self) -> CycleResult {
if self.halted {
return CycleResult {
cpu_exec: false,
ppu_frame: false,
dma: false,
dbg_int: true,
};
}
self.clock_count += 1;
let cpu_exec = if self.clock_count % 3 == 1 {
self.cpu_cycle();
matches!(self.cpu.clock_state, ClockState::ReadInstruction)
} else {
false
};
// 3 PPU clock cycles for each CPU clock cycle
let ppu_frame = self.ppu.run_one_clock_cycle();
let dbg_int = self.dbg_int | self.ppu.dbg_int;
self.dbg_int = false;
self.ppu.dbg_int = false;
CycleResult {
cpu_exec,
ppu_frame,
dma: matches!(self.dma, DmaState::Running { .. }),
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.ppu.reset();
}
pub fn power_cycle(&mut self) {
// 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(0).expect("CHR ROM"));
self.reset();
}
pub fn reset_and_run(&mut self) {
self.reset();
while !self.halted {
// info!("Running clock cycle: {}", self.clock_count);
self.run_one_clock_cycle();
}
}
pub fn reset_and_run_with_timeout(&mut self, max_clock_cycles: usize) {
self.reset();
let mut cur = 0;
while !self.halted && cur < max_clock_cycles {
// info!("Running clock cycle: {}", self.clock_count);
self.run_one_clock_cycle();
cur += 1;
}
}
pub fn image(&self) -> &RenderBuffer<256, 240> {
&self.ppu.render_buffer
}
pub fn ppu(&self) -> &PPU {
&self.ppu
}
pub fn cpu_mem(&self) -> &impl Memory {
&self.memory
}
}