Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 26s
2141 lines
84 KiB
Rust
2141 lines
84 KiB
Rust
mod apu;
|
|
mod controllers;
|
|
pub mod debug;
|
|
pub mod debugger;
|
|
pub mod header_menu;
|
|
pub mod hex_view;
|
|
mod mem;
|
|
mod ppu;
|
|
#[cfg(test)]
|
|
mod test_roms;
|
|
pub mod resize_watcher;
|
|
|
|
pub use ppu::{Color, PPU, RenderBuffer};
|
|
|
|
use std::{fmt::Write as _, fs::File, io::Read, path::Path};
|
|
|
|
use thiserror::Error;
|
|
use tracing::{debug, info};
|
|
|
|
use crate::{
|
|
apu::APU,
|
|
controllers::Controllers,
|
|
debug::DebugLog,
|
|
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,
|
|
debug_log: DebugLog,
|
|
}
|
|
|
|
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);
|
|
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 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,
|
|
},
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
addr: 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,
|
|
debug_log: DebugLog::new(),
|
|
|
|
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, 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?}", self.cpu.pc - (1 + params.len() as u16), $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()
|
|
}),
|
|
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", 7, |_ignored| {
|
|
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| {
|
|
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| {
|
|
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);
|
|
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", 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);
|
|
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| {
|
|
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", 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);
|
|
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| {
|
|
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", 1, |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", 1, |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", 3, |low, high| {
|
|
self.cpu.pc = u16::from_le_bytes([low, high]);
|
|
log!("{addr:04X}: JMP ${:04X}", self.cpu.pc);
|
|
}),
|
|
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
|
|
]);
|
|
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| {
|
|
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| {
|
|
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", 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);
|
|
log!("{addr:04X}: ADC ${:02X} | {:02X}", off, self.cpu.a);
|
|
}),
|
|
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);
|
|
log!("{addr:04X}: ADC ${:02X},x | {:02X}", off, self.cpu.a);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: ADC ${:02X}{:02X} | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: ADC ${:02X}{:02X},x | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: ADC ${:02X}{:02X},y | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: ADC (${:02X}{:02X},x) | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
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", 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);
|
|
log!("{addr:04X}: SBC ${:02X},x | {:02X}", off, self.cpu.a);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: SBC ${:02X}{:02X} | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: SBC ${:02X}{:02X},x | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: SBC ${:02X}{:02X},y | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: SBC (${:02X}{:02X},x) | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
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", 3, |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", 3, |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", 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);
|
|
log!(
|
|
"{addr:04X}: ORA ${:02X}{:02X} | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: ORA ${:02X}{:02X},x | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
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", 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);
|
|
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", 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);
|
|
log!(
|
|
"{addr:04X}: AND ${:02X}{:02X} | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: AND ${:02X}{:02X},x | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
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", 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);
|
|
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| {
|
|
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| {
|
|
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);
|
|
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(false);
|
|
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(false);
|
|
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(false);
|
|
self.write_zp_x(off, val);
|
|
log!("{addr:04X}: ROR ${:02X},x | {:02X}", off, self.cpu.a);
|
|
}),
|
|
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);
|
|
log!(
|
|
"{addr:04X}: ROR ${:02X}{:02X} | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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(false);
|
|
self.write_abs_x(low, high, val);
|
|
log!(
|
|
"{addr:04X}: ROR ${:02X}{:02X},x | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
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);
|
|
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(false);
|
|
self.write_abs(off, 0, val);
|
|
log!("{addr:04X}: ROL ${:02X} | {:02X}", off, self.cpu.a);
|
|
}),
|
|
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);
|
|
log!("{addr:04X}: ROL ${:02X},x | {:02X}", off, self.cpu.a);
|
|
}),
|
|
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);
|
|
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(false);
|
|
self.write_abs_x(low, high, val);
|
|
log!(
|
|
"{addr:04X}: ROL ${:02X}{:02X},x | {:02X}",
|
|
high,
|
|
low,
|
|
self.cpu.a
|
|
);
|
|
}),
|
|
|
|
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_nmi(&self) -> bool {
|
|
self.ppu.peek_nmi() || self.apu.peek_nmi()
|
|
}
|
|
pub fn peek_irq(&self) -> bool {
|
|
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);
|
|
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 addr = self.cpu.pc;
|
|
let instruction = self.read(self.cpu.pc);
|
|
self.cpu.pc = self.cpu.pc.wrapping_add(1);
|
|
match self.exec_instruction(instruction, &[], 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,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
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, 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,
|
|
},
|
|
}
|
|
}
|
|
ClockState::Hold {
|
|
cycles,
|
|
instruction,
|
|
ops,
|
|
count,
|
|
addr,
|
|
} => {
|
|
if cycles == 0 {
|
|
match self.exec_instruction(instruction, &ops[..count as usize], true, addr) {
|
|
ExecState::Done => ClockState::ReadInstruction,
|
|
ExecState::MoreParams => ClockState::ReadOperands {
|
|
instruction,
|
|
ops,
|
|
count: count + 1,
|
|
addr,
|
|
},
|
|
ExecState::Hold(_) => panic!("Should never return Hold after holding"),
|
|
}
|
|
} else {
|
|
ClockState::Hold {
|
|
cycles: cycles - 1,
|
|
instruction,
|
|
ops,
|
|
count,
|
|
addr,
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
pub fn debug_log(&self) -> &DebugLog {
|
|
&self.debug_log
|
|
}
|
|
|
|
pub fn debug_log_mut(&mut self) -> &mut DebugLog {
|
|
&mut self.debug_log
|
|
}
|
|
}
|