use super::master_clock::MasterClock;
use super::opcode::*;
use crate::apu::Apu;
use crate::bus::Bus;
use crate::console::TimingMode;
use crate::ppu::Ppu;
use crate::trace_cpu;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CpuState {
pub a: u8,
pub x: u8,
pub y: u8,
pub sp: u8,
pub pc: u16,
pub p: u8,
pub total_cycles: u64,
pub halted: bool,
pub nmi_pending: bool,
pub irq_pending: bool,
pub prev_need_nmi: bool,
pub prev_run_irq: bool,
pub run_irq: bool,
pub delayed_i_flag: Option<bool>,
pub forced_irq_pending: bool,
pub skip_interrupt_latch_this_cycle: bool,
pub master_clock: u64,
pub master_clock_ppu: u64,
pub dmc_dma_phase: DmcDmaPhase,
pub interrupt_stack: Vec<crate::cpu::InterruptKind>,
pub current_tick_info: Option<(u8, u8)>,
}
pub struct Cpu {
a: u8,
x: u8,
y: u8,
sp: u8,
pc: u16,
p: u8,
bus: Rc<RefCell<Bus>>,
ppu: Rc<RefCell<Ppu>>,
#[allow(dead_code)]
apu: Rc<RefCell<Apu>>,
halted: bool,
total_cycles: u64,
delayed_i_flag: Option<bool>,
nmi_pending: bool,
prev_need_nmi: bool,
prev_run_irq: bool,
run_irq: bool,
irq_pending: bool,
forced_irq_pending: bool,
master_clock: MasterClock,
skip_interrupt_latch_this_cycle: bool,
dmc_dma_phase: DmcDmaPhase,
interrupt_stack: Vec<InterruptKind>,
current_tick_info: Option<(u8, u8)>,
last_cpu_write_addr: Option<u16>,
}
const FLAG_CARRY: u8 = 0b0000_0001;
const FLAG_ZERO: u8 = 0b0000_0010;
const FLAG_INTERRUPT: u8 = 0b0000_0100;
const FLAG_DECIMAL: u8 = 0b0000_1000;
const FLAG_BREAK: u8 = 0b0001_0000;
const FLAG_UNUSED: u8 = 0b0010_0000;
const FLAG_OVERFLOW: u8 = 0b0100_0000;
const FLAG_NEGATIVE: u8 = 0b1000_0000;
const NMI_VECTOR: u16 = 0xFFFA;
const RESET_VECTOR: u16 = 0xFFFC;
const IRQ_VECTOR: u16 = 0xFFFE;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterruptKind {
Nmi,
Irq,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DmaReadOutcome {
NoDma,
RetryRead,
ReturnValue(u8),
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DmcDmaPhase {
#[default]
Idle,
Halt,
Dummy,
Aligning,
Reading,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
pub struct CpuRegisters {
pub a: u8,
pub x: u8,
pub y: u8,
pub sp: u8,
pub pc: u16,
pub p: u8,
}
impl Cpu {
fn is_controller_port2_read(addr: u16) -> bool {
addr == 0x4017
}
fn should_skip_first_input_clock(read_address: u16, dmc_address: u16) -> bool {
let is_controller_read = matches!(read_address, 0x4016 | 0x4017);
is_controller_read && (dmc_address & 0x1F) == (read_address & 0x1F)
}
fn dmc_pending_single_byte_fetch(&self) -> bool {
let mut apu = self.apu.borrow_mut();
let dmc = apu.dmc_mut().capture_state();
dmc.sample_length == 1 && dmc.bytes_remaining == 1
}
pub fn current_interrupt(&self) -> Option<InterruptKind> {
self.interrupt_stack.last().copied()
}
#[allow(dead_code)]
pub fn state(&self) -> CpuRegisters {
CpuRegisters {
a: self.a,
x: self.x,
y: self.y,
sp: self.sp,
pc: self.pc,
p: self.p,
}
}
pub fn new(
tv_system: TimingMode,
memory: Rc<RefCell<Bus>>,
ppu: Rc<RefCell<Ppu>>,
apu: Rc<RefCell<Apu>>,
) -> Self {
Self {
a: 0,
x: 0,
y: 0,
sp: 0x00, pc: 0, p: 0x20, bus: memory,
ppu,
apu,
halted: false,
total_cycles: 0,
delayed_i_flag: None,
nmi_pending: false,
prev_need_nmi: false,
prev_run_irq: false,
run_irq: false,
irq_pending: false,
forced_irq_pending: false,
master_clock: MasterClock::new(tv_system),
skip_interrupt_latch_this_cycle: false,
dmc_dma_phase: DmcDmaPhase::Idle,
interrupt_stack: Vec::with_capacity(2),
current_tick_info: None,
last_cpu_write_addr: None,
}
}
pub fn is_halted(&self) -> bool {
self.halted
}
pub fn a(&self) -> u8 {
self.a
}
pub fn x(&self) -> u8 {
self.x
}
pub fn y(&self) -> u8 {
self.y
}
pub fn sp(&self) -> u8 {
self.sp
}
pub fn pc(&self) -> u16 {
self.pc
}
pub fn p(&self) -> u8 {
self.p
}
pub fn get_total_cycles(&self) -> u64 {
self.total_cycles
}
pub fn last_cpu_write_addr(&self) -> Option<u16> {
self.last_cpu_write_addr
}
#[cfg(test)]
pub fn set_total_cycles(&mut self, cycles: u64) {
self.total_cycles = cycles;
}
#[cfg(test)]
pub fn set_a_register(&mut self, value: u8) {
self.a = value;
}
#[cfg(test)]
pub fn set_x(&mut self, value: u8) {
self.x = value;
}
#[cfg(test)]
pub fn set_y(&mut self, value: u8) {
self.y = value;
}
#[cfg(test)]
pub fn set_sp(&mut self, value: u8) {
self.sp = value;
}
#[cfg(test)]
pub fn set_pc(&mut self, value: u16) {
self.pc = value;
}
#[cfg(test)]
pub fn set_p(&mut self, value: u8) {
self.p = value;
}
pub fn divert_to_trainer(&mut self, game_vector: u16) {
let return_addr = game_vector.wrapping_sub(1);
let hi = (return_addr >> 8) as u8;
let lo = return_addr as u8;
let addr_hi = 0x0100 | self.sp as u16;
self.bus.borrow_mut().write(addr_hi, hi, false);
self.sp = self.sp.wrapping_sub(1);
let addr_lo = 0x0100 | self.sp as u16;
self.bus.borrow_mut().write(addr_lo, lo, false);
self.sp = self.sp.wrapping_sub(1);
self.pc = 0x7003;
}
pub fn add_cycles(&mut self, cycles: u64) {
self.total_cycles += cycles;
}
pub fn capture_state(&self) -> CpuState {
CpuState {
a: self.a,
x: self.x,
y: self.y,
sp: self.sp,
pc: self.pc,
p: self.p,
total_cycles: self.total_cycles,
halted: self.halted,
nmi_pending: self.nmi_pending,
irq_pending: self.irq_pending,
prev_need_nmi: self.prev_need_nmi,
prev_run_irq: self.prev_run_irq,
run_irq: self.run_irq,
delayed_i_flag: self.delayed_i_flag,
forced_irq_pending: self.forced_irq_pending,
skip_interrupt_latch_this_cycle: self.skip_interrupt_latch_this_cycle,
master_clock: self.master_clock.master_cycles(),
master_clock_ppu: self.master_clock.ppu_cycles(),
dmc_dma_phase: self.dmc_dma_phase,
interrupt_stack: self.interrupt_stack.clone(),
current_tick_info: self.current_tick_info,
}
}
pub fn restore_state(&mut self, state: &CpuState) {
self.a = state.a;
self.x = state.x;
self.y = state.y;
self.sp = state.sp;
self.pc = state.pc;
self.p = state.p;
self.total_cycles = state.total_cycles;
self.halted = state.halted;
self.nmi_pending = state.nmi_pending;
self.irq_pending = state.irq_pending;
self.prev_need_nmi = state.prev_need_nmi;
self.prev_run_irq = state.prev_run_irq;
self.run_irq = state.run_irq;
self.delayed_i_flag = state.delayed_i_flag;
self.forced_irq_pending = state.forced_irq_pending;
self.skip_interrupt_latch_this_cycle = state.skip_interrupt_latch_this_cycle;
self.master_clock.set_master_cycles(state.master_clock);
self.master_clock.set_ppu_cycles(state.master_clock_ppu);
self.dmc_dma_phase = state.dmc_dma_phase;
self.interrupt_stack = state.interrupt_stack.clone();
self.current_tick_info = state.current_tick_info;
}
fn end_cpu_cycle_latch_interrupt_lines(&mut self) {
self.prev_need_nmi = self.nmi_pending;
if self.ppu.borrow_mut().poll_nmi() {
self.nmi_pending = true;
}
self.prev_run_irq = self.run_irq;
let irq_asserted_from_apu = self.apu.borrow().poll_irq();
let irq_asserted_from_mapper = self.bus.borrow().mapper_irq_pending();
self.irq_pending =
irq_asserted_from_apu || irq_asserted_from_mapper || self.forced_irq_pending;
self.run_irq = self.should_poll_irq();
}
fn service_irq_or_nmi_sequence(&mut self) {
let mut nmi_hijack = self.nmi_pending;
let pc = self.pc;
let _sp = self.sp;
let _p = self.p;
let _cycle = self.total_cycles;
let _frame = self.ppu.borrow().timing().frame_count();
let _scanline = self.ppu.borrow().timing().scanline();
let _pixel = self.ppu.borrow().timing().pixel();
self.dummy_read(pc);
nmi_hijack |= self.nmi_pending;
self.dummy_read(pc);
nmi_hijack |= self.nmi_pending;
self.push_byte((self.pc >> 8) as u8);
nmi_hijack |= self.nmi_pending;
self.push_byte(self.pc as u8);
nmi_hijack |= self.nmi_pending;
let flags = (self.p & !FLAG_BREAK) | FLAG_UNUSED;
self.push_byte(flags);
self.p |= FLAG_INTERRUPT;
self.delayed_i_flag = None;
if nmi_hijack {
self.nmi_pending = false;
self.interrupt_stack.push(InterruptKind::Nmi);
self.pc = self.read_u16(NMI_VECTOR);
} else {
self.forced_irq_pending = false;
self.interrupt_stack.push(InterruptKind::Irq);
self.pc = self.read_u16(IRQ_VECTOR);
}
trace_cpu!(1;
"{} PC={:04X} A={:02X} X={:02X} Y={:02X} P={:02X} SP={:02X} cyc={:<3} F/S/P={}/{:03}/{:03}",
if nmi_hijack { "NMI " } else { "IRQ " },
pc,
self.a,
self.x,
self.y,
_p,
_sp,
_cycle,
_frame,
_scanline,
_pixel
);
}
pub fn handle_oam_dma_if_pending(&mut self) -> Option<u16> {
let page = self.bus.borrow_mut().take_oam_dma_page()?;
let cycles_before = self.total_cycles;
self.tick_single_dma_cycle();
self.run_oam_dma_internal_no_nmi(page);
let dma_cycles = (self.total_cycles - cycles_before) as u16;
if self.ppu.borrow_mut().poll_nmi() {
self.trigger_nmi_without_bus_cycles();
self.tick_ppu_apu_for_cpu_cycles(7);
self.add_cycles(7);
}
Some(dma_cycles)
}
fn tick_single_dma_cycle(&mut self) {
self.master_clock.advance_cpu_cycles(1);
let ppu_cycles = self.master_clock.ppu_cycles_since_last();
self.ppu.borrow_mut().run_ppu_cycles(ppu_cycles);
let expansion = self.bus.borrow().mapper_expansion_audio_sample();
self.apu.borrow_mut().clock_with_expansion(expansion);
self.bus.borrow_mut().mapper_cpu_cycle();
self.end_cpu_cycle_latch_irq_line_only();
self.total_cycles += 1;
}
fn tick_ppu_apu_for_cpu_cycles(&mut self, cpu_cycles: u16) {
self.master_clock.advance_cpu_cycles(cpu_cycles as u64);
let ppu_cycles = self.master_clock.ppu_cycles_since_last();
self.ppu.borrow_mut().run_ppu_cycles(ppu_cycles);
for _ in 0..cpu_cycles {
let expansion = self.bus.borrow().mapper_expansion_audio_sample();
self.apu.borrow_mut().clock_with_expansion(expansion);
self.bus.borrow_mut().mapper_cpu_cycle();
self.end_cpu_cycle_latch_irq_line_only();
}
}
fn end_cpu_cycle_latch_irq_line_only(&mut self) {
self.prev_run_irq = self.run_irq;
let irq_asserted_from_apu = self.apu.borrow().poll_irq();
let irq_asserted_from_mapper = self.bus.borrow().mapper_irq_pending();
self.irq_pending =
irq_asserted_from_apu || irq_asserted_from_mapper || self.forced_irq_pending;
self.run_irq = self.should_poll_irq();
}
fn trigger_nmi_without_bus_cycles(&mut self) {
self.delayed_i_flag = None;
let pc = self.pc;
let addr = 0x0100 | (self.sp as u16);
self.bus.borrow_mut().write(addr, (pc >> 8) as u8, false);
self.sp = self.sp.wrapping_sub(1);
let addr = 0x0100 | (self.sp as u16);
self.bus.borrow_mut().write(addr, (pc & 0xFF) as u8, false);
self.sp = self.sp.wrapping_sub(1);
let mut p_with_break = self.p & !FLAG_BREAK;
p_with_break |= FLAG_UNUSED;
let addr = 0x0100 | (self.sp as u16);
self.bus.borrow_mut().write(addr, p_with_break, false);
self.sp = self.sp.wrapping_sub(1);
let lo = self.bus.borrow_mut().read(NMI_VECTOR, false) as u16;
let hi = self.bus.borrow_mut().read(NMI_VECTOR + 1, false) as u16;
self.pc = (hi << 8) | lo;
self.interrupt_stack.push(InterruptKind::Nmi);
self.p |= FLAG_INTERRUPT;
}
pub fn reset(&mut self, soft_reset: bool) {
if !soft_reset {
self.a = 0;
self.x = 0;
self.y = 0;
self.sp = 0x00;
self.p = FLAG_UNUSED;
}
self.p |= FLAG_INTERRUPT | FLAG_UNUSED;
self.sp = self.sp.wrapping_sub(3);
self.halted = false;
self.delayed_i_flag = None;
self.nmi_pending = false;
self.irq_pending = false;
self.forced_irq_pending = false;
self.prev_need_nmi = false;
self.prev_run_irq = false;
self.run_irq = false;
self.skip_interrupt_latch_this_cycle = false;
self.interrupt_stack.clear();
self.total_cycles = 0;
self.master_clock.reset();
for _ in 0..5 {
self.internal_cycle();
}
self.pc = self.read_reset_vector();
}
fn internal_cycle(&mut self) {
if let Some((ref mut tick, total)) = self.current_tick_info {
trace_cpu!(2;
"tick ({}/{}) cyc={} [internal]",
*tick,
total,
self.total_cycles
);
let _ = total;
*tick += 1;
} else {
trace_cpu!(2; "tick cyc={} [internal]", self.total_cycles);
}
self.before_cpu_cycle(false);
self.after_cpu_cycle(false);
}
fn page_crossed(addr1: u16, addr2: u16) -> bool {
(addr1 & 0xFF00) != (addr2 & 0xFF00)
}
fn before_cpu_cycle(&mut self, is_write: bool) {
self.master_clock.before_cpu_cycle(is_write);
self.total_cycles += 1;
let ppu_cycles = self.master_clock.ppu_cycles_since_last();
self.ppu.borrow_mut().run_ppu_cycles(ppu_cycles);
let expansion = self.bus.borrow().mapper_expansion_audio_sample();
self.apu.borrow_mut().clock_with_expansion(expansion);
}
fn after_cpu_cycle(&mut self, is_write: bool) {
self.master_clock.after_cpu_cycle(is_write);
let ppu_cycles = self.master_clock.ppu_cycles_since_last();
self.ppu.borrow_mut().run_ppu_cycles(ppu_cycles);
self.bus.borrow_mut().mapper_cpu_cycle();
if self.skip_interrupt_latch_this_cycle {
self.skip_interrupt_latch_this_cycle = false;
} else {
self.end_cpu_cycle_latch_interrupt_lines();
}
}
fn start_dmc_dma(&mut self) {
self.dmc_dma_phase = DmcDmaPhase::Halt;
}
fn cpu_visible_dmc_dma_pending(&self) -> bool {
matches!(self.dmc_dma_phase, DmcDmaPhase::Idle) && {
let mut apu = self.apu.borrow_mut();
apu.dmc_mut().cpu_dma_pending()
}
}
fn process_pending_dmc_dma(&mut self, read_address: u16) -> Option<u8> {
let mut observed_bus_value = None;
while !matches!(self.dmc_dma_phase, DmcDmaPhase::Idle) {
match self.dmc_dma_phase {
DmcDmaPhase::Idle => break,
DmcDmaPhase::Halt => {
self.before_cpu_cycle(false);
let _ = self.bus.borrow_mut().read(read_address, true);
self.after_cpu_cycle(false);
self.dmc_dma_phase = DmcDmaPhase::Dummy;
}
DmcDmaPhase::Dummy => {
self.before_cpu_cycle(false);
let _ = self.bus.borrow_mut().read(read_address, true);
self.after_cpu_cycle(false);
self.dmc_dma_phase = DmcDmaPhase::Aligning;
}
DmcDmaPhase::Aligning => {
if !self.total_cycles.is_multiple_of(2) {
self.before_cpu_cycle(false);
let _ = self.bus.borrow_mut().read(read_address, true);
self.after_cpu_cycle(false);
}
self.dmc_dma_phase = DmcDmaPhase::Reading;
}
DmcDmaPhase::Reading => {
let dma_addr = {
let mut apu = self.apu.borrow_mut();
apu.dmc_mut().dma_address()
};
if let Some(addr) = dma_addr {
self.before_cpu_cycle(false);
let value = self.bus.borrow_mut().read(addr, false);
self.after_cpu_cycle(false);
self.apu.borrow_mut().dmc_mut().complete_dma_read(value);
if Self::is_controller_port2_read(read_address) {
observed_bus_value = Some(value);
}
}
self.dmc_dma_phase = DmcDmaPhase::Idle;
}
}
}
observed_bus_value
}
fn process_pending_dma(&mut self, read_address: u16) -> DmaReadOutcome {
let oam_dma_pending = self.bus.borrow().oam_dma_pending();
let dmc_dma_pending = self.cpu_visible_dmc_dma_pending();
if !oam_dma_pending && !dmc_dma_pending {
return DmaReadOutcome::NoDma;
}
if !oam_dma_pending && dmc_dma_pending {
self.start_dmc_dma();
let dmc_dma_address = {
let mut apu = self.apu.borrow_mut();
apu.dmc_mut().dma_address()
};
let is_controller_read = matches!(read_address, 0x4016 | 0x4017);
let single_byte_dmc_fetch = self.dmc_pending_single_byte_fetch();
let skip_first_input_clock = dmc_dma_address
.map(|address| Self::should_skip_first_input_clock(read_address, address))
.unwrap_or(false);
let use_dummy_halt_read =
is_controller_read && (!single_byte_dmc_fetch || skip_first_input_clock);
let halted_read_value = self
.bus
.borrow_mut()
.read(read_address, use_dummy_halt_read);
self.after_cpu_cycle(false);
self.dmc_dma_phase = DmcDmaPhase::Dummy;
let observed_bus_value = self.process_pending_dmc_dma(read_address);
if read_address == 0x4016 {
if observed_bus_value.is_none() {
return DmaReadOutcome::RetryRead;
}
return DmaReadOutcome::ReturnValue(halted_read_value);
}
if let Some(value) = observed_bus_value {
return DmaReadOutcome::ReturnValue(value);
}
return DmaReadOutcome::RetryRead;
}
let _ = self.bus.borrow_mut().read(read_address, false);
self.after_cpu_cycle(false);
let page = self.bus.borrow_mut().take_oam_dma_page();
if let Some(page) = page {
self.run_oam_dma_internal(page);
}
DmaReadOutcome::RetryRead
}
fn run_oam_dma_internal_impl(&mut self, page: u8, handle_nmi: bool) {
let source_base = (page as u16) << 8;
let dmc_pending_at_start = self.cpu_visible_dmc_dma_pending();
const DMC_IDLE: u8 = 0;
const DMC_HALT_DONE: u8 = 1;
const DMC_READY_TO_READ: u8 = 2;
const OAM_SIZE: u16 = 256;
let mut dmc_progress: u8 = if dmc_pending_at_start {
DMC_HALT_DONE
} else {
DMC_IDLE
};
let mut sprite_dma_value: Option<u8> = None;
let mut sprite_offset: u16 = 0;
let mut sprite_dma_done = false;
loop {
let dmc_pending = self.cpu_visible_dmc_dma_pending();
if dmc_pending && dmc_progress == DMC_IDLE {
dmc_progress = DMC_HALT_DONE; }
let on_get_phase = self.total_cycles.is_multiple_of(2);
let on_put_phase = !on_get_phase;
let dmc_ready_to_read =
dmc_pending && dmc_progress >= DMC_READY_TO_READ && on_get_phase;
let oam_ready_to_read = !sprite_dma_done && sprite_dma_value.is_none() && on_get_phase;
let oam_ready_to_write = !sprite_dma_done && sprite_dma_value.is_some() && on_put_phase;
if dmc_ready_to_read {
let dma_addr = {
let mut apu = self.apu.borrow_mut();
apu.dmc_mut().dma_address()
};
if let Some(addr) = dma_addr {
let value = self.bus.borrow_mut().read(addr, false);
self.apu.borrow_mut().dmc_mut().complete_dma_read(value);
}
self.tick_single_dma_cycle();
dmc_progress = DMC_IDLE; } else if oam_ready_to_read {
if dmc_pending && dmc_progress == DMC_HALT_DONE {
dmc_progress = DMC_READY_TO_READ; }
let addr = source_base.wrapping_add(sprite_offset);
let value = self.bus.borrow_mut().read(addr, false);
sprite_dma_value = Some(value);
self.tick_single_dma_cycle();
} else if oam_ready_to_write {
if dmc_pending && dmc_progress == DMC_HALT_DONE {
dmc_progress = DMC_READY_TO_READ; }
self.ppu
.borrow_mut()
.write_oam_data_dma(sprite_dma_value.take().unwrap());
self.tick_single_dma_cycle();
sprite_offset += 1;
if sprite_offset >= OAM_SIZE {
sprite_dma_done = true;
}
} else if dmc_pending || !sprite_dma_done {
if dmc_pending && dmc_progress == DMC_HALT_DONE {
dmc_progress = DMC_READY_TO_READ;
}
self.tick_single_dma_cycle();
} else {
break;
}
}
if handle_nmi && self.ppu.borrow_mut().poll_nmi() {
self.trigger_nmi_without_bus_cycles();
self.tick_ppu_apu_for_cpu_cycles(7);
self.add_cycles(7);
}
}
fn run_oam_dma_internal_no_nmi(&mut self, page: u8) {
self.run_oam_dma_internal_impl(page, false);
}
fn run_oam_dma_internal(&mut self, page: u8) {
self.run_oam_dma_internal_impl(page, true);
}
fn read(&mut self, addr: u16) -> u8 {
self.read_with_dummy_flag(addr, false)
}
fn dummy_read(&mut self, addr: u16) -> u8 {
self.read_with_dummy_flag(addr, true)
}
fn read_with_dummy_flag(&mut self, addr: u16, is_dummy_read: bool) -> u8 {
loop {
if let Some((ref mut tick, total)) = self.current_tick_info {
trace_cpu!(2;
"tick ({}/{}) cyc={} [read] addr=0x{:04X}",
*tick,
total,
self.total_cycles,
addr
);
let _ = total;
*tick += 1;
} else {
trace_cpu!(2; "tick cyc={} [read] addr=0x{:04X}", self.total_cycles, addr);
}
self.before_cpu_cycle(false);
match self.process_pending_dma(addr) {
DmaReadOutcome::NoDma => {}
DmaReadOutcome::RetryRead => {
continue;
}
DmaReadOutcome::ReturnValue(value) => return value,
}
let value = self.bus.borrow_mut().read(addr, is_dummy_read);
self.after_cpu_cycle(false);
return value;
}
}
fn read_u16(&mut self, addr: u16) -> u16 {
self.read(addr) as u16 | ((self.read(addr + 1) as u16) << 8)
}
fn write(&mut self, addr: u16, value: u8, dummy: bool) {
if let Some((ref mut tick, total)) = self.current_tick_info {
trace_cpu!(2;
"tick ({}/{}) cyc={} [write{}] addr=0x{:04X} value=0x{:02X}",
*tick,
total,
self.total_cycles,
if dummy { " (dummy)" } else { "" },
addr,
value
);
let _ = total;
*tick += 1;
} else {
trace_cpu!(2;
"tick cyc={} [write{}] addr=0x{:04X} value=0x{:02X}",
self.total_cycles,
if dummy { " (dummy)" } else { "" },
addr,
value
);
}
self.before_cpu_cycle(true);
self.bus.borrow_mut().write(addr, value, dummy);
if !dummy {
self.last_cpu_write_addr = Some(addr);
}
self.after_cpu_cycle(true);
}
fn dummy_write(&mut self, addr: u16, value: u8) {
self.write(addr, value, true);
}
fn read_byte_from_pc(&mut self) -> u8 {
let value = self.read(self.pc);
self.pc = self.pc.wrapping_add(1);
value
}
fn read_word_from_pc(&mut self) -> u16 {
let lo = self.read_byte_from_pc() as u16;
let hi = self.read_byte_from_pc() as u16;
(hi << 8) | lo
}
fn read_reset_vector(&mut self) -> u16 {
self.read_u16(RESET_VECTOR)
}
fn read_word_from_zp(&mut self, addr: u8) -> u16 {
let lo = self.read(addr as u16) as u16;
let hi = self.read(addr.wrapping_add(1) as u16) as u16;
(hi << 8) | lo
}
fn read_word_indirect(&mut self, addr: u16) -> u16 {
let lo = self.read(addr) as u16;
let hi_addr = if addr & 0xFF == 0xFF {
addr & 0xFF00
} else {
addr + 1
};
let hi = self.read(hi_addr) as u16;
(hi << 8) | lo
}
fn push_byte(&mut self, value: u8) {
let addr = 0x0100 | (self.sp as u16);
self.write(addr, value, false);
self.sp = self.sp.wrapping_sub(1);
}
fn push_word(&mut self, value: u16) {
self.push_byte((value >> 8) as u8); self.push_byte(value as u8); }
fn pop_byte(&mut self) -> u8 {
self.sp = self.sp.wrapping_add(1);
let addr = 0x0100 | (self.sp as u16);
self.read(addr)
}
fn pop_word(&mut self) -> u16 {
let lo = self.pop_byte() as u16; let hi = self.pop_byte() as u16; (hi << 8) | lo
}
fn update_zero_and_negative_flags(&mut self, value: u8) {
self.p &= !(FLAG_ZERO | FLAG_NEGATIVE);
if value == 0 {
self.p |= FLAG_ZERO;
}
if value & 0x80 != 0 {
self.p |= FLAG_NEGATIVE;
}
}
fn adc(&mut self, value: u8) {
let carry = if self.p & FLAG_CARRY != 0 { 1 } else { 0 };
let sum = self.a as u16 + value as u16 + carry as u16;
if sum > 0xFF {
self.p |= FLAG_CARRY;
} else {
self.p &= !FLAG_CARRY;
}
let result = sum as u8;
let overflow = (self.a ^ result) & (value ^ result) & 0x80;
if overflow != 0 {
self.p |= FLAG_OVERFLOW;
} else {
self.p &= !FLAG_OVERFLOW;
}
self.a = result;
self.update_zero_and_negative_flags(self.a);
}
fn and(&mut self, value: u8) {
self.a &= value;
self.update_zero_and_negative_flags(self.a);
}
fn asl(&mut self, value: u8) -> u8 {
let carry = if value & 0x80 != 0 { FLAG_CARRY } else { 0 };
let result = value << 1;
self.p = (self.p & !FLAG_CARRY) | carry;
self.update_zero_and_negative_flags(result);
result
}
fn bit(&mut self, value: u8) {
let result = self.a & value;
if result == 0 {
self.p |= FLAG_ZERO;
} else {
self.p &= !FLAG_ZERO;
}
if value & 0x80 != 0 {
self.p |= FLAG_NEGATIVE;
} else {
self.p &= !FLAG_NEGATIVE;
}
if value & 0x40 != 0 {
self.p |= FLAG_OVERFLOW;
} else {
self.p &= !FLAG_OVERFLOW;
}
}
fn compare(&mut self, register_value: u8, value: u8) {
let result = register_value.wrapping_sub(value);
if register_value >= value {
self.p |= FLAG_CARRY;
} else {
self.p &= !FLAG_CARRY;
}
if register_value == value {
self.p |= FLAG_ZERO;
} else {
self.p &= !FLAG_ZERO;
}
if result & 0x80 != 0 {
self.p |= FLAG_NEGATIVE;
} else {
self.p &= !FLAG_NEGATIVE;
}
}
fn cmp(&mut self, value: u8) {
self.compare(self.a, value);
}
fn cpx(&mut self, value: u8) {
self.compare(self.x, value);
}
fn cpy(&mut self, value: u8) {
self.compare(self.y, value);
}
fn dec(&mut self, value: u8) -> u8 {
let result = value.wrapping_sub(1);
self.update_zero_and_negative_flags(result);
result
}
fn dcp(&mut self, addr: u16) {
let value = self.read(addr);
self.dummy_write(addr, value);
let result = self.dec(value);
self.write(addr, result, false);
self.cmp(result);
}
fn lar(&mut self, value: u8) {
let result = self.sp & value;
self.a = result;
self.x = result;
self.sp = result;
self.update_zero_and_negative_flags(result);
}
fn axs(&mut self, value: u8) {
let and_result = self.a & self.x;
let (result, borrow) = and_result.overflowing_sub(value);
self.x = result;
self.p = (self.p & !FLAG_CARRY) | if !borrow { FLAG_CARRY } else { 0 };
self.update_zero_and_negative_flags(self.x);
}
fn isb(&mut self, addr: u16) {
let value = self.read(addr);
self.dummy_write(addr, value);
let result = value.wrapping_add(1);
self.write(addr, result, false);
self.sbc(result);
}
fn eor(&mut self, value: u8) {
self.a ^= value;
self.update_zero_and_negative_flags(self.a);
}
fn inc(&mut self, value: u8) -> u8 {
let result = value.wrapping_add(1);
self.update_zero_and_negative_flags(result);
result
}
fn rla(&mut self, addr: u16) {
let value = self.read(addr);
self.dummy_write(addr, value);
let rotated = self.rol(value);
self.write(addr, rotated, false);
self.a &= rotated;
self.update_zero_and_negative_flags(self.a);
}
fn rra(&mut self, addr: u16) {
let value = self.read(addr);
self.dummy_write(addr, value);
let rotated = self.ror(value);
self.write(addr, rotated, false);
self.adc(rotated);
}
fn slo(&mut self, addr: u16) {
let value = self.read(addr);
self.dummy_write(addr, value);
let shifted = self.asl(value);
self.write(addr, shifted, false);
self.ora(shifted);
}
fn sre(&mut self, addr: u16) {
let value = self.read(addr);
self.dummy_write(addr, value);
let shifted = self.lsr(value);
self.write(addr, shifted, false);
self.eor(shifted);
}
fn lda(&mut self, value: u8) {
self.a = value;
self.update_zero_and_negative_flags(self.a);
}
fn ldx(&mut self, value: u8) {
self.x = value;
self.update_zero_and_negative_flags(self.x);
}
fn ldy(&mut self, value: u8) {
self.y = value;
self.update_zero_and_negative_flags(self.y);
}
fn lsr(&mut self, value: u8) -> u8 {
if value & 0b00000001 != 0 {
self.p |= FLAG_CARRY;
} else {
self.p &= !FLAG_CARRY;
}
let result = value >> 1;
self.update_zero_and_negative_flags(result);
result
}
fn ora(&mut self, value: u8) {
self.set_a(self.a | value);
}
fn dex(&mut self) {
self.x = self.x.wrapping_sub(1);
self.update_zero_and_negative_flags(self.x);
}
fn dey(&mut self) {
self.y = self.y.wrapping_sub(1);
self.update_zero_and_negative_flags(self.y);
}
fn iny(&mut self) {
self.y = self.y.wrapping_add(1);
self.update_zero_and_negative_flags(self.y);
}
fn inx(&mut self) {
self.x = self.x.wrapping_add(1);
self.update_zero_and_negative_flags(self.x);
}
fn tax(&mut self) {
self.x = self.a;
self.update_zero_and_negative_flags(self.x);
}
fn tay(&mut self) {
self.y = self.a;
self.update_zero_and_negative_flags(self.y);
}
fn txa(&mut self) {
self.a = self.x;
self.update_zero_and_negative_flags(self.a);
}
fn tya(&mut self) {
self.a = self.y;
self.update_zero_and_negative_flags(self.a);
}
fn rol(&mut self, value: u8) -> u8 {
let old_carry = if self.p & FLAG_CARRY != 0 { 1 } else { 0 };
if value & 0b10000000 != 0 {
self.p |= FLAG_CARRY;
} else {
self.p &= !FLAG_CARRY;
}
let result = (value << 1) | old_carry;
self.update_zero_and_negative_flags(result);
result
}
fn ror(&mut self, value: u8) -> u8 {
let old_carry = if self.p & FLAG_CARRY != 0 {
0b10000000
} else {
0
};
if value & 0b00000001 != 0 {
self.p |= FLAG_CARRY;
} else {
self.p &= !FLAG_CARRY;
}
let result = (value >> 1) | old_carry;
self.update_zero_and_negative_flags(result);
result
}
fn sbc(&mut self, value: u8) {
let carry_in = if self.p & FLAG_CARRY != 0 { 1 } else { 0 };
let inverted_value = !value;
let result = self.a as u16 + inverted_value as u16 + carry_in;
if result >= 0x100 {
self.p |= FLAG_CARRY;
} else {
self.p &= !FLAG_CARRY;
}
let a_sign = self.a & 0x80;
let m_sign = inverted_value & 0x80;
let result_sign = (result as u8) & 0x80;
if a_sign == m_sign && a_sign != result_sign {
self.p |= FLAG_OVERFLOW;
} else {
self.p &= !FLAG_OVERFLOW;
}
self.a = result as u8;
self.update_zero_and_negative_flags(self.a);
}
fn get_effective_i_flag(&self) -> bool {
self.delayed_i_flag
.unwrap_or((self.p & FLAG_INTERRUPT) != 0)
}
pub fn should_poll_irq(&self) -> bool {
self.irq_pending && !self.get_effective_i_flag()
}
#[cfg(test)]
pub(crate) fn set_nmi_pending(&mut self, pending: bool) {
self.nmi_pending = pending;
}
#[cfg(test)]
pub(crate) fn set_irq_pending(&mut self, pending: bool) {
self.forced_irq_pending = pending;
self.irq_pending = pending;
}
fn get_operand_value(&mut self, op: &OpCode, operand: u16) -> u8 {
match op.mode {
"IMM" => operand as u8,
"ZP" | "ZPX" | "ZPY" | "ABS" | "ABSX" | "ABSY" | "IND" | "INDX" | "INDY" => {
self.read(operand)
}
"IMP" | "ACC" | "REL" => operand as u8,
_ => panic!("Unhandled addressing mode: {}", op.mode),
}
}
fn set_a(&mut self, value: u8) {
self.a = value;
self.update_zero_and_negative_flags(self.a);
}
fn exec_arr_illegal(&mut self, imm: u8) {
self.a &= imm;
let old_carry = if self.p & FLAG_CARRY != 0 { 1 } else { 0 };
self.a = (self.a >> 1) | (old_carry << 7);
self.update_zero_and_negative_flags(self.a);
if (self.a & 0x40) != 0 {
self.p |= FLAG_CARRY;
} else {
self.p &= !FLAG_CARRY;
}
let bit6 = (self.a >> 6) & 1;
let bit5 = (self.a >> 5) & 1;
if (bit6 ^ bit5) != 0 {
self.p |= FLAG_OVERFLOW;
} else {
self.p &= !FLAG_OVERFLOW;
}
}
fn exec_sya_illegal(&mut self, addr: u16) {
let base_addr = addr.wrapping_sub(self.x as u16);
let base_high_byte = (base_addr >> 8) as u8;
let value = self.y & base_high_byte.wrapping_add(1);
let page_crossed = Self::page_crossed(base_addr, addr);
let final_addr = if page_crossed {
let modified_high = ((addr >> 8) as u8) & self.y;
((modified_high as u16) << 8) | (addr & 0x00FF)
} else {
addr
};
self.write(final_addr, value, false);
}
fn exec_sxa_illegal(&mut self, addr: u16) {
let base_addr = addr.wrapping_sub(self.y as u16);
let base_high_byte = (base_addr >> 8) as u8;
let value = self.x & base_high_byte.wrapping_add(1);
let page_crossed = Self::page_crossed(base_addr, addr);
let final_addr = if page_crossed {
let modified_high = ((addr >> 8) as u8) & self.x;
((modified_high as u16) << 8) | (addr & 0x00FF)
} else {
addr
};
self.write(final_addr, value, false);
}
pub fn execute(&mut self) {
if self.halted {
return;
}
self.last_cpu_write_addr = None;
let had_delayed_i_flag = self.delayed_i_flag.is_some();
let mut new_delayed_i_flag: Option<bool> = None;
#[cfg(debug_assertions)]
if crate::debugging::is_cpu_tracing_enabled() {
let pc = self.pc;
let mut memory = self.bus.borrow_mut();
let opcode_byte = memory.read_for_testing(pc);
let op = super::opcode::lookup(opcode_byte);
let byte1 = if op.bytes() > 1 {
memory.read_for_testing(pc.wrapping_add(1))
} else {
0
};
let byte2 = if op.bytes() > 2 {
memory.read_for_testing(pc.wrapping_add(2))
} else {
0
};
drop(memory); let hex_dump = match op.bytes() {
1 => format!("{:02X}", opcode_byte),
2 => format!("{:02X} {:02X}", opcode_byte, byte1),
_ => format!("{:02X} {:02X} {:02X}", opcode_byte, byte1, byte2),
};
let asm = match op.mode {
"IMP" => op.mnemonic.to_string(),
"ACC" => format!("{} A", op.mnemonic),
"IMM" => format!("{} #${:02X}", op.mnemonic, byte1),
"ZP" => format!("{} ${:02X}", op.mnemonic, byte1),
"ZPX" => format!("{} ${:02X},X", op.mnemonic, byte1),
"ZPY" => format!("{} ${:02X},Y", op.mnemonic, byte1),
"ABS" => format!(
"{} ${:04X}",
op.mnemonic,
u16::from_le_bytes([byte1, byte2])
),
"ABSX" | "ABSXW" => format!(
"{} ${:04X},X",
op.mnemonic,
u16::from_le_bytes([byte1, byte2])
),
"ABSY" | "ABSYW" => format!(
"{} ${:04X},Y",
op.mnemonic,
u16::from_le_bytes([byte1, byte2])
),
"IND" => format!(
"{} (${:04X})",
op.mnemonic,
u16::from_le_bytes([byte1, byte2])
),
"INDX" => format!("{} (${:02X},X)", op.mnemonic, byte1),
"INDY" | "INDYW" => format!("{} (${:02X}),Y", op.mnemonic, byte1),
"REL" => {
let offset = byte1 as i8;
let target = pc.wrapping_add(2).wrapping_add(offset as u16);
format!("{} ${:04X}", op.mnemonic, target)
}
_ => op.mnemonic.to_string(),
};
self.current_tick_info = Some((1, op.cycles));
trace_cpu!(1;
"exec PC={:04X} {:08} {:14} A={:02X} X={:02X} Y={:02X} P={:02X} SP={:02X} cyc={:<3} F/S/P={}/{:03}/{:03}",
pc,
hex_dump,
asm,
self.a,
self.x,
self.y,
self.p,
self.sp,
self.total_cycles,
self.ppu.borrow().timing().frame_count(),
self.ppu.borrow().timing().scanline(),
self.ppu.borrow().timing().pixel()
);
}
let opcode = self.read_byte_from_pc();
let op = super::opcode::lookup(opcode);
let operand = self.get_operand(*op);
match op.mnemonic {
"BRK" => {
self.push_word(self.pc.wrapping_add(1));
let flags = self.p | FLAG_BREAK | FLAG_UNUSED;
if self.nmi_pending {
self.nmi_pending = false;
self.push_byte(flags);
self.p |= FLAG_INTERRUPT;
self.pc = self.read_u16(NMI_VECTOR);
} else {
self.push_byte(flags);
self.p |= FLAG_INTERRUPT;
self.pc = self.read_u16(IRQ_VECTOR);
}
self.prev_need_nmi = false;
}
"ORA" => {
let value = self.get_operand_value(op, operand);
self.ora(value);
}
"HLT" | "KIL" => {
self.halted = true;
self.pc -= 1;
}
"*SLO" => {
self.slo(operand);
}
"NOP" | "*NOP" => {
self.get_operand_value(op, operand);
}
"ASL" => {
match op.mode {
"ACC" => {
self.a = self.asl(self.a);
}
_ => {
let value = self.read(operand);
self.dummy_write(operand, value);
let result = self.asl(value);
self.write(operand, result, false); }
}
}
"PHP" => {
let flags = self.p | FLAG_BREAK | FLAG_UNUSED;
self.push_byte(flags);
}
"*AAC" => {
let value = self.get_operand_value(op, operand);
self.a &= value;
self.update_zero_and_negative_flags(self.a);
let carry = if self.a & 0x80 != 0 { FLAG_CARRY } else { 0 };
self.p = (self.p & !FLAG_CARRY) | carry;
}
"BPL" => {
let offset = operand as i8;
if self.p & FLAG_NEGATIVE == 0 {
let old_pc = self.pc;
self.pc = self.pc.wrapping_add(offset as u16);
let page_crossed = Self::page_crossed(old_pc, self.pc);
if page_crossed {
self.dummy_read(self.pc);
self.dummy_read(self.pc);
} else {
self.skip_interrupt_latch_this_cycle = true;
self.dummy_read(self.pc);
}
}
}
"CLC" => {
self.p &= !FLAG_CARRY;
}
"JSR" => {
self.dummy_read(0x0100 | (self.sp as u16));
let return_addr = self.pc.wrapping_sub(1);
self.push_word(return_addr);
self.pc = operand;
}
"AND" => {
let value = self.get_operand_value(op, operand);
self.and(value);
}
"*RLA" => {
self.rla(operand);
}
"BIT" => {
let value = self.read(operand);
self.bit(value);
}
"ROL" => {
match op.mode {
"ACC" => {
self.a = self.rol(self.a);
}
_ => {
let value = self.read(operand);
self.dummy_write(operand, value);
let result = self.rol(value);
self.write(operand, result, false); }
}
}
"PLP" => {
self.dummy_read(0x0100 | (self.sp as u16));
let status = self.pop_byte();
let old_i_flag = (self.p & FLAG_INTERRUPT) != 0;
self.p = (status & !FLAG_BREAK) | FLAG_UNUSED;
let new_i_flag = (self.p & FLAG_INTERRUPT) != 0;
if old_i_flag != new_i_flag {
new_delayed_i_flag = Some(old_i_flag);
}
}
"BMI" => {
let offset = operand as i8;
if self.p & FLAG_NEGATIVE != 0 {
let old_pc = self.pc;
self.pc = self.pc.wrapping_add(offset as u16);
let page_crossed = Self::page_crossed(old_pc, self.pc);
if page_crossed {
self.dummy_read(self.pc);
self.dummy_read(self.pc);
} else {
self.skip_interrupt_latch_this_cycle = true;
self.dummy_read(self.pc);
}
}
}
"SEC" => {
self.p |= FLAG_CARRY;
}
"RTI" => {
self.dummy_read(self.pc);
let status = self.pop_byte();
self.p = (status & !FLAG_BREAK) | FLAG_UNUSED;
self.delayed_i_flag = None;
self.pc = self.pop_word();
let _ = self.interrupt_stack.pop();
}
"EOR" => {
let value = self.get_operand_value(op, operand);
self.eor(value);
}
"*SRE" => {
self.sre(operand);
}
"LSR" => {
match op.mode {
"ACC" => {
self.a = self.lsr(self.a);
}
_ => {
let value = self.read(operand);
self.dummy_write(operand, value);
let result = self.lsr(value);
self.write(operand, result, false); }
}
}
"PHA" => {
self.push_byte(self.a);
}
"*ASR" => {
let value = self.get_operand_value(op, operand);
self.a &= value;
self.a = self.lsr(self.a);
}
"JMP" => {
self.pc = operand;
}
"BVC" => {
let offset = operand as i8;
if (self.p & FLAG_OVERFLOW) == 0 {
let old_pc = self.pc;
self.pc = self.pc.wrapping_add(offset as u16);
let page_crossed = Self::page_crossed(old_pc, self.pc);
if page_crossed {
self.dummy_read(self.pc);
self.dummy_read(self.pc);
} else {
self.skip_interrupt_latch_this_cycle = true;
self.dummy_read(self.pc);
}
}
}
"CLI" => {
let old_i_flag = (self.p & FLAG_INTERRUPT) != 0;
self.p &= !FLAG_INTERRUPT;
new_delayed_i_flag = Some(old_i_flag);
}
"RTS" => {
self.dummy_read(0x0100 | (self.sp as u16));
let addr = self.pop_word();
self.pc = addr.wrapping_add(1);
self.dummy_read(self.pc);
}
"ADC" => {
let value = self.get_operand_value(op, operand);
self.adc(value);
}
"*RRA" => {
self.rra(operand);
}
"ROR" => {
match op.mode {
"ACC" => {
self.a = self.ror(self.a);
}
_ => {
let value = self.read(operand);
self.dummy_write(operand, value);
let result = self.ror(value);
self.write(operand, result, false); }
}
}
"PLA" => {
self.dummy_read(self.pc);
self.a = self.pop_byte();
self.update_zero_and_negative_flags(self.a);
}
"*ARR" => {
let value = self.get_operand_value(op, operand);
self.exec_arr_illegal(value);
}
"BVS" => {
let offset = operand as i8;
if (self.p & FLAG_OVERFLOW) != 0 {
let old_pc = self.pc;
self.pc = self.pc.wrapping_add(offset as u16);
let page_crossed = Self::page_crossed(old_pc, self.pc);
if page_crossed {
self.dummy_read(self.pc);
self.dummy_read(self.pc);
} else {
self.skip_interrupt_latch_this_cycle = true;
self.dummy_read(self.pc);
}
}
}
"SEI" => {
let old_i_flag = (self.p & FLAG_INTERRUPT) != 0;
self.p |= FLAG_INTERRUPT;
new_delayed_i_flag = Some(old_i_flag);
}
"STA" => {
self.write(operand, self.a, false);
}
"*SAX" => {
let value = self.a & self.x;
self.write(operand, value, false);
}
"STY" => {
self.write(operand, self.y, false);
}
"STX" => {
self.write(operand, self.x, false);
}
"DEY" => {
self.dey();
}
"TXA" => {
self.txa();
}
"ANE" | "*XAA" => {
self.a = self.x;
let value = self.get_operand_value(op, operand);
self.a &= value;
self.update_zero_and_negative_flags(self.a);
}
"BCC" => {
let offset = operand as i8;
if self.p & FLAG_CARRY == 0 {
let old_pc = self.pc;
self.pc = self.pc.wrapping_add(offset as u16);
let page_crossed = Self::page_crossed(old_pc, self.pc);
if page_crossed {
self.dummy_read(self.pc);
self.dummy_read(self.pc);
} else {
self.skip_interrupt_latch_this_cycle = true;
self.dummy_read(self.pc);
}
}
}
"SHAZ" | "*XAS" => {
self.sp = self.a & self.x;
let high_byte = (operand >> 8) as u8;
let value = self.sp & high_byte.wrapping_add(1);
self.write(operand, value, false);
}
"TYA" => {
self.tya();
}
"TXS" => {
self.sp = self.x;
}
"TAS" => {
self.sp = self.a & self.x;
let high_byte = (operand >> 8) as u8;
let value = self.sp & high_byte.wrapping_add(1);
self.write(operand, value, false);
}
"SHY" | "*SYA" => {
self.exec_sya_illegal(operand);
}
"SHX" | "*SXA" => {
self.exec_sxa_illegal(operand);
}
"SHAA" | "*AXA" => {
let high_byte = (operand >> 8) as u8;
let value = self.a & self.x & high_byte.wrapping_add(1);
self.write(operand, value, false);
}
"LDY" => {
let value = self.get_operand_value(op, operand);
self.ldy(value);
}
"LDA" => {
let value = self.get_operand_value(op, operand);
self.lda(value);
}
"LDX" => {
let value = self.get_operand_value(op, operand);
self.ldx(value);
}
"*LAX" => {
let value = self.get_operand_value(op, operand);
self.lda(value);
self.ldx(value);
}
"TAY" => {
self.tay();
}
"TAX" => {
self.tax();
}
"*ATX" => {
let value = self.get_operand_value(op, operand);
self.a = value;
self.x = value;
self.update_zero_and_negative_flags(self.a);
}
"BCS" => {
let offset = operand as i8;
if self.p & FLAG_CARRY != 0 {
let old_pc = self.pc;
self.pc = self.pc.wrapping_add(offset as u16);
let page_crossed = Self::page_crossed(old_pc, self.pc);
if page_crossed {
self.dummy_read(self.pc);
self.dummy_read(self.pc);
} else {
self.skip_interrupt_latch_this_cycle = true;
self.dummy_read(self.pc);
}
}
}
"CLV" => {
self.p &= !FLAG_OVERFLOW;
}
"TSX" => {
self.x = self.sp;
self.update_zero_and_negative_flags(self.x);
}
"*LAR" => {
let value = self.get_operand_value(op, operand);
self.lar(value);
}
"CPY" => {
let value = self.get_operand_value(op, operand);
self.cpy(value);
}
"CMP" => {
let value = self.get_operand_value(op, operand);
self.cmp(value);
}
"*DCP" => {
self.dcp(operand);
}
"INY" => {
self.iny();
}
"DEX" => {
self.dex();
}
"*AXS" => {
let value = self.get_operand_value(op, operand);
self.axs(value);
}
"BNE" => {
let offset = operand as i8;
if self.p & FLAG_ZERO == 0 {
let old_pc = self.pc;
self.pc = self.pc.wrapping_add(offset as u16);
let page_crossed = Self::page_crossed(old_pc, self.pc);
if page_crossed {
self.dummy_read(self.pc);
self.dummy_read(self.pc);
} else {
self.skip_interrupt_latch_this_cycle = true;
self.dummy_read(self.pc);
}
}
}
"CLD" => {
self.p &= !FLAG_DECIMAL;
}
"CPX" => {
let value = self.get_operand_value(op, operand);
self.cpx(value);
}
"SBC" | "*SBC" => {
let value = self.get_operand_value(op, operand);
self.sbc(value);
}
"*ISB" => {
self.isb(operand);
}
"INX" => {
self.inx();
}
"BEQ" => {
let offset = operand as i8;
if self.p & FLAG_ZERO != 0 {
let old_pc = self.pc;
self.pc = self.pc.wrapping_add(offset as u16);
let page_crossed = Self::page_crossed(old_pc, self.pc);
if page_crossed {
self.dummy_read(self.pc);
self.dummy_read(self.pc);
} else {
self.skip_interrupt_latch_this_cycle = true;
self.dummy_read(self.pc);
}
}
}
"SED" => {
self.p |= FLAG_DECIMAL;
}
"INC" => {
let value = self.read(operand);
self.dummy_write(operand, value);
let result = self.inc(value);
self.write(operand, result, false);
}
"DEC" => {
let value = self.read(operand);
self.dummy_write(operand, value);
let result = self.dec(value);
self.write(operand, result, false);
}
_ => {}
}
self.current_tick_info = None;
let cleared_delayed_i_flag_this_instruction = had_delayed_i_flag;
if had_delayed_i_flag {
self.delayed_i_flag = None;
}
if new_delayed_i_flag.is_some() {
self.delayed_i_flag = new_delayed_i_flag;
}
let irq_after_delayed_i_expires =
cleared_delayed_i_flag_this_instruction && self.should_poll_irq();
if self.prev_need_nmi || self.prev_run_irq || irq_after_delayed_i_expires {
self.service_irq_or_nmi_sequence();
}
}
pub fn get_operand(&mut self, op: OpCode) -> u16 {
match op.mode {
"IMP" | "ACC" => {
self.dummy_read(self.pc);
0
}
"IMM" | "REL" | "ZP" => self.read_byte_from_pc() as u16,
"ZPX" => {
let base = self.read_byte_from_pc();
self.dummy_read(base as u16);
base.wrapping_add(self.x) as u16
}
"ZPY" => {
let base = self.read_byte_from_pc();
self.dummy_read(base as u16);
base.wrapping_add(self.y) as u16
}
"ABS" => self.read_word_from_pc(),
"ABSX" => {
let base = self.read_word_from_pc();
let addr = base.wrapping_add(self.x as u16);
if Self::page_crossed(base, addr) {
let dummy_addr = (base & 0xFF00) | (addr & 0x00FF);
self.dummy_read(dummy_addr);
}
addr
}
"ABSXW" => {
let base = self.read_word_from_pc();
let addr = base.wrapping_add(self.x as u16);
let dummy_addr = (base & 0xFF00) | (addr & 0x00FF);
self.dummy_read(dummy_addr);
addr
}
"ABSY" => {
let base = self.read_word_from_pc();
let addr = base.wrapping_add(self.y as u16);
if Self::page_crossed(base, addr) {
let dummy_addr = (base & 0xFF00) | (addr & 0x00FF);
self.dummy_read(dummy_addr);
}
addr
}
"ABSYW" => {
let base = self.read_word_from_pc();
let addr = base.wrapping_add(self.y as u16);
let page_crossed = Self::page_crossed(base, addr);
let dummy_addr = if page_crossed { addr - 0x100 } else { addr };
self.dummy_read(dummy_addr);
addr
}
"IND" => {
let ptr = self.read_word_from_pc();
self.read_word_indirect(ptr)
}
"INDX" => {
let base = self.read_byte_from_pc();
self.dummy_read(base as u16);
let ptr = base.wrapping_add(self.x);
self.read_word_from_zp(ptr)
}
"INDY" => {
let ptr = self.read_byte_from_pc();
let base = self.read_word_from_zp(ptr);
let addr = base.wrapping_add(self.y as u16);
if Self::page_crossed(base, addr) {
let dummy_addr = (base & 0xFF00) | (addr & 0x00FF);
self.dummy_read(dummy_addr);
}
addr
}
"INDYW" => {
let ptr = self.read_byte_from_pc();
let base = self.read_word_from_zp(ptr);
let addr = base.wrapping_add(self.y as u16);
let dummy_addr = if Self::page_crossed(base, addr) {
(base & 0xFF00) | (addr & 0x00FF)
} else {
addr
};
self.dummy_read(dummy_addr);
addr
}
_ => 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cartridge::{Cartridge, NametableLayout};
use crate::cpu::opcode;
use std::cell::RefCell;
use std::rc::Rc;
fn map_minimal_cartridge_for_reset_vector(cpu: &mut Cpu) {
let mut prg_rom = vec![0; 0x4000];
prg_rom[0x3FFC] = 0x00;
prg_rom[0x3FFD] = 0x80;
prg_rom[0x0000] = 0xEA; let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
}
#[test]
fn test_read_byte_from_pc_wraps_program_counter_at_ffff() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
map_minimal_cartridge_for_reset_vector(&mut cpu);
cpu.pc = 0xFFFF;
let value = cpu.read_byte_from_pc();
assert_eq!(value, 0x00);
assert_eq!(cpu.pc, 0x0000);
}
#[test]
fn test_reset_ticks_apu_for_7_cpu_cycles() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
map_minimal_cartridge_for_reset_vector(&mut cpu);
let apu_before = apu.borrow().frame_counter().get_cycle_counter();
cpu.reset(true);
let apu_after = apu.borrow().frame_counter().get_cycle_counter();
assert_eq!(
apu_after - apu_before,
7,
"CPU reset should tick the APU for 7 cycles"
);
}
#[test]
fn test_soft_reset_preserves_registers_but_sets_i_and_adjusts_sp() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
map_minimal_cartridge_for_reset_vector(&mut cpu);
cpu.a = 0x12;
cpu.x = 0x34;
cpu.y = 0x56;
cpu.sp = 0x80;
cpu.p = 0x00;
cpu.reset(true);
assert_eq!(cpu.a, 0x12);
assert_eq!(cpu.x, 0x34);
assert_eq!(cpu.y, 0x56);
assert_eq!(cpu.sp, 0x7D);
assert_ne!(cpu.p & FLAG_INTERRUPT, 0);
}
#[test]
fn test_cpu_state_snapshot() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
cpu.a = 0x11;
cpu.x = 0x22;
cpu.y = 0x33;
cpu.sp = 0x44;
cpu.pc = 0x5566;
cpu.p = 0x77;
let state = cpu.state();
assert_eq!(state.a, 0x11);
assert_eq!(state.x, 0x22);
assert_eq!(state.y, 0x33);
assert_eq!(state.sp, 0x44);
assert_eq!(state.pc, 0x5566);
assert_eq!(state.p, 0x77);
}
#[test]
fn test_cpu_save_state_roundtrip_includes_internal_flags() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
cpu.a = 0x11;
cpu.x = 0x22;
cpu.y = 0x33;
cpu.sp = 0x44;
cpu.pc = 0x5566;
cpu.p = 0x77;
cpu.halted = true;
cpu.set_total_cycles(1234);
cpu.delayed_i_flag = Some(true);
cpu.nmi_pending = true;
cpu.prev_need_nmi = true;
cpu.prev_run_irq = true;
cpu.run_irq = true;
cpu.irq_pending = true;
cpu.forced_irq_pending = true;
cpu.skip_interrupt_latch_this_cycle = true;
cpu.master_clock.set_master_cycles(111);
cpu.master_clock.set_ppu_cycles(222);
cpu.dmc_dma_phase = DmcDmaPhase::Halt;
cpu.interrupt_stack = vec![InterruptKind::Irq, InterruptKind::Nmi];
cpu.current_tick_info = Some((2, 5));
let state = cpu.capture_state();
let mut restored = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
restored.restore_state(&state);
assert_eq!(restored.a, cpu.a);
assert_eq!(restored.x, cpu.x);
assert_eq!(restored.y, cpu.y);
assert_eq!(restored.sp, cpu.sp);
assert_eq!(restored.pc, cpu.pc);
assert_eq!(restored.p, cpu.p);
assert_eq!(restored.halted, cpu.halted);
assert_eq!(restored.total_cycles, cpu.total_cycles);
assert_eq!(restored.delayed_i_flag, cpu.delayed_i_flag);
assert_eq!(restored.nmi_pending, cpu.nmi_pending);
assert_eq!(restored.prev_need_nmi, cpu.prev_need_nmi);
assert_eq!(restored.prev_run_irq, cpu.prev_run_irq);
assert_eq!(restored.run_irq, cpu.run_irq);
assert_eq!(restored.irq_pending, cpu.irq_pending);
assert_eq!(restored.forced_irq_pending, cpu.forced_irq_pending);
assert_eq!(
restored.skip_interrupt_latch_this_cycle,
cpu.skip_interrupt_latch_this_cycle
);
assert_eq!(
restored.master_clock.master_cycles(),
cpu.master_clock.master_cycles()
);
assert_eq!(
restored.master_clock.ppu_cycles(),
cpu.master_clock.ppu_cycles()
);
assert_eq!(restored.dmc_dma_phase, cpu.dmc_dma_phase);
assert_eq!(restored.interrupt_stack, cpu.interrupt_stack);
assert_eq!(restored.current_tick_info, cpu.current_tick_info);
}
#[test]
fn test_set_pc_for_tests() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
cpu.set_pc(0xC0DE);
assert_eq!(cpu.pc, 0xC0DE);
}
#[test]
fn test_hard_reset_restores_power_on_register_defaults() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
map_minimal_cartridge_for_reset_vector(&mut cpu);
cpu.a = 0xFF;
cpu.x = 0xEE;
cpu.y = 0xDD;
cpu.sp = 0x10;
cpu.p = 0x00;
cpu.reset(false);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.x, 0x00);
assert_eq!(cpu.y, 0x00);
assert_eq!(cpu.sp, 0xFD);
assert_eq!(cpu.p & FLAG_UNUSED, FLAG_UNUSED);
assert_ne!(cpu.p & FLAG_INTERRUPT, 0);
}
#[test]
fn test_soft_reset_sets_i_without_changing_other_flags() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
map_minimal_cartridge_for_reset_vector(&mut cpu);
cpu.a = 0x34;
cpu.x = 0x56;
cpu.y = 0x78;
cpu.sp = 0x12;
cpu.p = 0xFB;
cpu.reset(true);
assert_eq!(cpu.a, 0x34);
assert_eq!(cpu.x, 0x56);
assert_eq!(cpu.y, 0x78);
assert_eq!(cpu.sp, 0x0F);
assert_eq!(cpu.p, 0xFF);
}
#[test]
fn test_execute_captures_pending_ppu_nmi_edge() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
let mut prg_rom = vec![0; 0x4000];
prg_rom[0x3FFA] = 0x00;
prg_rom[0x3FFB] = 0x90;
prg_rom[0x3FFC] = 0x00;
prg_rom[0x3FFD] = 0x80;
prg_rom[0x0000] = 0xEA; prg_rom[0x1000] = 0xEA; let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
cpu.reset(true);
ppu.borrow_mut().write_control(0x80);
ppu.borrow_mut().run_ppu_cycles(241 * 341 + 1);
cpu.execute();
assert_eq!(cpu.pc, 0x9000, "CPU should service NMI after execute()");
assert_eq!(
cpu.current_interrupt(),
Some(InterruptKind::Nmi),
"CPU should report being in NMI handler after vectoring"
);
assert!(
!ppu.borrow_mut().poll_nmi(),
"PPU NMI flag should be cleared once CPU polls it"
);
}
#[test]
fn test_rti_clears_interrupt_indicator_after_nmi() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
let mut prg_rom = vec![0; 0x4000];
prg_rom[0x3FFA] = 0x00;
prg_rom[0x3FFB] = 0x90;
prg_rom[0x3FFC] = 0x00;
prg_rom[0x3FFD] = 0x80;
prg_rom[0x0000] = 0xEA; prg_rom[0x1000] = 0x40; let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
cpu.reset(true);
ppu.borrow_mut().write_control(0x80);
ppu.borrow_mut().run_ppu_cycles(241 * 341 + 1);
cpu.execute();
assert_eq!(cpu.pc, 0x9000);
assert_eq!(cpu.current_interrupt(), Some(InterruptKind::Nmi));
cpu.execute();
assert_eq!(
cpu.current_interrupt(),
None,
"RTI should clear the interrupt indicator"
);
assert_eq!(
cpu.pc, 0x8001,
"RTI should return to the instruction after the interrupted one"
);
}
#[test]
fn test_cpu_new_stores_provided_ppu_and_apu_instances() {
let ppu = Rc::new(RefCell::new(crate::ppu::Ppu::new_for_testing(
TimingMode::Ntsc,
)));
let apu = Rc::new(RefCell::new(crate::apu::Apu::new()));
let app_context = Rc::new(RefCell::new(
crate::app_context::AppContext::new_with_config(crate::console::Config::default()),
));
let memory = Rc::new(RefCell::new(Bus::new(
Rc::clone(&ppu),
Rc::clone(&apu),
app_context,
)));
let cpu = Cpu::new(TimingMode::Ntsc, memory, Rc::clone(&ppu), Rc::clone(&apu));
assert!(Rc::ptr_eq(&cpu.ppu, &ppu));
assert!(Rc::ptr_eq(&cpu.apu, &apu));
}
#[test]
fn test_oam_dma_even_cycle_costs_513_cycles() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, Rc::clone(&memory), ppu, apu);
cpu.set_total_cycles(8);
assert!(
cpu.get_total_cycles().is_multiple_of(2),
"Should start on even cycle"
);
memory.borrow_mut().write(0x4014, 0x02, false);
let cycles_before = cpu.get_total_cycles();
let dma_cycles = cpu
.handle_oam_dma_if_pending()
.expect("DMA should be pending");
assert_eq!(dma_cycles, 514);
assert_eq!(cpu.get_total_cycles() - cycles_before, 514);
}
#[test]
fn test_oam_dma_odd_cycle_costs_514_cycles() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, Rc::clone(&memory), ppu, apu);
cpu.set_total_cycles(7);
assert!(
!cpu.get_total_cycles().is_multiple_of(2),
"Should start on odd cycle"
);
memory.borrow_mut().write(0x4014, 0x02, false);
let cycles_before = cpu.get_total_cycles();
let dma_cycles = cpu
.handle_oam_dma_if_pending()
.expect("DMA should be pending");
assert_eq!(dma_cycles, 513);
assert_eq!(cpu.get_total_cycles() - cycles_before, 513);
}
#[test]
fn test_oam_dma_transfers_256_bytes_from_requested_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, Rc::clone(&memory), ppu, apu);
for i in 0..256u16 {
memory
.borrow_mut()
.write(0x0200 + i, (i & 0xFF) as u8, false);
}
memory.borrow_mut().write(0x4014, 0x02, false);
cpu.handle_oam_dma_if_pending()
.expect("DMA should be pending");
for i in 0..256u16 {
memory.borrow_mut().write(0x2003, i as u8, false);
let oam_byte = memory.borrow_mut().read(0x2004, false);
let expected = if (i & 0x03) == 2 {
((i & 0xFF) as u8) & 0xE3
} else {
(i & 0xFF) as u8
};
assert_eq!(
oam_byte, expected,
"OAM byte {} should match source data (with attribute masking)",
i
);
}
}
#[test]
fn test_oam_dma_ticks_mapper_expansion_audio() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, Rc::clone(&memory), ppu, apu);
let mut rom = vec![
b'N', b'E', b'S', 0x1A, 2, 1, 0x80, 0x10, 0, 0, 0, 0, 0, 0, 0, 0,
];
rom.extend(vec![0u8; 2 * 16 * 1024]);
rom.extend(vec![0u8; 8 * 1024]);
let cart = crate::cartridge::Cartridge::load_from_file(
&rom,
"cpu-reset-test.nes",
crate::app_context::AppContext::new(),
)
.expect("cartridge should parse");
memory.borrow_mut().map_cartridge(cart);
memory.borrow_mut().write(0xB000, 0b0000_1000, false);
memory.borrow_mut().write(0xB001, 0x00, false);
memory.borrow_mut().write(0xB002, 0b1000_0000, false);
let before = memory.borrow().mapper_expansion_audio_sample();
assert_eq!(before, 0.0);
memory.borrow_mut().write(0x4014, 0x02, false);
cpu.handle_oam_dma_if_pending()
.expect("DMA should be pending");
let after = memory.borrow().mapper_expansion_audio_sample();
assert!(
after > 0.0,
"expected expansion audio to advance during DMA"
);
}
#[test]
fn test_oam_dma_returns_none_when_not_pending() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
assert_eq!(cpu.handle_oam_dma_if_pending(), None);
}
#[test]
fn test_cancelled_dmc_dma_after_halt_does_not_complete_sample_read() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
setup_pending_dmc_dma_with_sample(&mut cpu, &apu, 0xA5);
cpu.set_total_cycles(1);
cpu.start_dmc_dma();
let halted_read_addr = 0x1234;
let cycles_before_halt = cpu.get_total_cycles();
cpu.before_cpu_cycle(false);
let _ = cpu.bus.borrow_mut().read(halted_read_addr, false);
cpu.after_cpu_cycle(false);
assert_eq!(cpu.get_total_cycles(), cycles_before_halt + 1);
apu.borrow_mut().write_enable(0);
cpu.process_pending_dmc_dma(halted_read_addr);
let dmc_state = apu.borrow().dmc().capture_state();
assert!(
dmc_state.sample_buffer.is_none(),
"cancelled DMC DMA should discard the queued sample fetch"
);
assert!(
!dmc_state.dma_pending,
"cancelled DMC DMA should not remain pending after the discard"
);
}
#[test]
fn test_dmc_dma_overlap_4016_exercises_halt_and_dummy_cycles() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
setup_pending_dmc_dma_with_sample(&mut cpu, &apu, 0xA5);
cpu.set_total_cycles(0);
let before = cpu.get_total_cycles();
let _ = cpu.read(0x4016);
let after = cpu.get_total_cycles();
assert_eq!(
after - before,
4,
"DMC overlap on $4016 should consume halt + dummy + get + retried CPU read"
);
}
#[test]
fn test_dmc_dma_overlap_4017_get_cycle_returns_dmc_sample_value() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
setup_pending_dmc_dma_with_sample(&mut cpu, &apu, 0xA5);
cpu.set_total_cycles(0);
let value = cpu.read(0x4017);
assert_eq!(
value, 0xA5,
"On the DMC get cycle, $4017 should observe the DMC sample byte on the bus"
);
}
#[test]
fn test_should_skip_first_input_clock_for_aliasing_4016_read() {
assert!(Cpu::should_skip_first_input_clock(0x4016, 0xC016));
}
#[test]
fn test_should_not_skip_first_input_clock_for_non_aliasing_4016_read() {
assert!(!Cpu::should_skip_first_input_clock(0x4016, 0xC000));
}
#[test]
fn test_execute_does_not_service_irq_when_not_asserted() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
let mut prg_rom = vec![0; 0x4000];
prg_rom[0x3FFC] = 0x00;
prg_rom[0x3FFD] = 0x80;
prg_rom[0x3FFE] = 0x00;
prg_rom[0x3FFF] = 0x90;
prg_rom[0x0000] = 0xEA; let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
cpu.reset(true);
cpu.p &= !FLAG_INTERRUPT;
let pc_before = cpu.pc;
let sp_before = cpu.sp;
let cycles_before = cpu.get_total_cycles();
cpu.execute();
assert_eq!(cpu.get_total_cycles(), cycles_before + 2);
assert_eq!(cpu.pc, pc_before + 1);
assert_eq!(cpu.sp, sp_before);
}
#[test]
fn test_execute_services_irq_after_instruction_and_sets_pc_and_stack() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
let mut prg_rom = vec![0; 0x4000];
prg_rom[0x3FFE] = 0x00;
prg_rom[0x3FFF] = 0x90;
prg_rom[0x3FFC] = 0x00;
prg_rom[0x3FFD] = 0x80;
prg_rom[0x0000] = 0xEA; prg_rom[0x1000] = 0xEA; let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
cpu.reset(true);
cpu.p &= !FLAG_INTERRUPT;
cpu.set_irq_pending(true);
let pc_before = cpu.pc;
let sp_before = cpu.sp;
let p_before = cpu.p;
let cycles_before = cpu.get_total_cycles();
cpu.execute();
assert_eq!(cpu.get_total_cycles(), cycles_before + 9);
assert_eq!(cpu.pc, 0x9000);
assert_eq!(
cpu.current_interrupt(),
Some(InterruptKind::Irq),
"CPU should report being in IRQ handler after vectoring"
);
assert_ne!(cpu.p & FLAG_INTERRUPT, 0);
assert_eq!(cpu.sp, sp_before.wrapping_sub(3));
let pch_addr = 0x0100 | (sp_before as u16);
let pcl_addr = 0x0100 | (sp_before.wrapping_sub(1) as u16);
let p_addr = 0x0100 | (sp_before.wrapping_sub(2) as u16);
let pch = memory.borrow_mut().read(pch_addr, false);
let pcl = memory.borrow_mut().read(pcl_addr, false);
let pushed_p = memory.borrow_mut().read(p_addr, false);
let expected_return_pc = pc_before.wrapping_add(1);
assert_eq!(pch, (expected_return_pc >> 8) as u8);
assert_eq!(pcl, (expected_return_pc & 0xFF) as u8);
let expected_pushed_p = (p_before & !FLAG_BREAK) | FLAG_UNUSED;
assert_eq!(pushed_p, expected_pushed_p);
}
#[test]
fn test_execute_services_mapper_irq_after_instruction() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
let mut prg_rom = vec![0; 0x4000];
prg_rom[0x3FFC] = 0x00;
prg_rom[0x3FFD] = 0x80;
prg_rom[0x3FFE] = 0x00;
prg_rom[0x3FFF] = 0x90;
prg_rom[0x0000] = 0xEA; prg_rom[0x1000] = 0xEA; let chr_rom = vec![0; 0x2000];
let flags6 = 0x40; let mut rom = vec![
b'N', b'E', b'S', 0x1A, 1, 1, flags6, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
rom.extend_from_slice(&prg_rom);
rom.extend_from_slice(&chr_rom);
let cartridge = Cartridge::load_from_file(
&rom,
"cpu-mmc3-test.nes",
crate::app_context::AppContext::new(),
)
.expect("MMC3 iNES ROM should parse");
cpu.bus.borrow_mut().map_cartridge(cartridge);
cpu.reset(true);
cpu.p &= !FLAG_INTERRUPT;
memory.borrow_mut().write_for_testing(0xC000, 1);
memory.borrow_mut().write_for_testing(0xC001, 0);
memory.borrow_mut().write_for_testing(0xE001, 0);
for _ in 0..8 {
memory
.borrow_mut()
.mapper_ppu_address_changed_for_test(0x0FFF);
}
memory
.borrow_mut()
.mapper_ppu_address_changed_for_test(0x1000);
for _ in 0..8 {
memory
.borrow_mut()
.mapper_ppu_address_changed_for_test(0x0FFF);
}
memory
.borrow_mut()
.mapper_ppu_address_changed_for_test(0x1000);
assert!(
memory.borrow().mapper_irq_pending(),
"Mapper should have asserted IRQ before CPU executes"
);
cpu.execute();
assert_eq!(cpu.pc, 0x9000);
}
#[test]
fn test_execute_ticks_full_instruction_cycles_for_nop_ntsc() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
let mut prg_rom = vec![0; 0x4000];
prg_rom[0x3FFC] = 0x00;
prg_rom[0x3FFD] = 0x80;
prg_rom[0x0000] = 0xEA; let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
cpu.reset(true);
let cpu_cycles_before = cpu.get_total_cycles();
let ppu_before = ppu_dot(&ppu.borrow());
let apu_before = apu.borrow().frame_counter().get_cycle_counter();
cpu.execute();
let cpu_cycles_after = cpu.get_total_cycles();
let ppu_after = ppu_dot(&ppu.borrow());
let apu_after = apu.borrow().frame_counter().get_cycle_counter();
assert_eq!(cpu_cycles_after - cpu_cycles_before, 2);
assert_eq!(ppu_after - ppu_before, 6);
assert_eq!(apu_after - apu_before, 2);
}
#[test]
fn test_oam_dma_ticks_ppu_and_apu_for_dma_cycles_ntsc() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
cpu.set_total_cycles(8); memory.borrow_mut().write(0x4014, 0x02, false);
let ppu_before = ppu_dot(&ppu.borrow());
let apu_before = apu.borrow().frame_counter().get_cycle_counter();
let dma_cycles = cpu
.handle_oam_dma_if_pending()
.expect("DMA should be pending");
let ppu_after = ppu_dot(&ppu.borrow());
let apu_after = apu.borrow().frame_counter().get_cycle_counter();
assert_eq!(ppu_after - ppu_before, dma_cycles as u64 * 3);
assert_eq!(apu_after - apu_before, dma_cycles as u32);
}
#[test]
fn test_oam_dma_advances_master_clock_for_synthetic_cycles() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
cpu.set_total_cycles(8); cpu.master_clock.set_master_cycles(0);
cpu.master_clock.set_ppu_cycles(0);
memory.borrow_mut().write(0x4014, 0x02, false);
let dma_cycles = cpu
.handle_oam_dma_if_pending()
.expect("DMA should be pending");
let expected_master_ticks = cpu.master_clock.cpu_divider() * dma_cycles as u64;
assert_eq!(cpu.master_clock.master_cycles(), expected_master_ticks);
}
#[test]
fn test_oam_dma_pal_ticks_ppu_and_tracks_fractional_cycles() {
let (ppu, apu, memory) = create_test_memory_for(TimingMode::Pal);
let mut cpu = Cpu::new(
TimingMode::Pal,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
cpu.set_total_cycles(8); memory.borrow_mut().write(0x4014, 0x02, false);
let ppu_before = ppu_dot(&ppu.borrow());
let apu_before = apu.borrow().frame_counter().get_cycle_counter();
let dma_cycles = cpu
.handle_oam_dma_if_pending()
.expect("DMA should be pending");
let expected_total_ppu = dma_cycles as f64 * TimingMode::Pal.ppu_cycles_per_cpu_cycle();
let expected_run_ppu = expected_total_ppu.floor() as u64;
let ppu_after = ppu_dot(&ppu.borrow());
let apu_after = apu.borrow().frame_counter().get_cycle_counter();
assert_eq!(ppu_after - ppu_before, expected_run_ppu);
assert_eq!(apu_after - apu_before, dma_cycles as u32);
}
#[test]
fn test_oam_dma_services_nmi_after_dma_and_ticks_ppu_apu_for_nmi_cycles() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
let mut prg_rom = vec![0; 0x4000];
prg_rom[0x3FFA] = 0x00;
prg_rom[0x3FFB] = 0x80;
prg_rom[0x3FFC] = 0x00;
prg_rom[0x3FFD] = 0x80;
let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
memory.borrow_mut().write(0x2000, 0x80, false);
let vblank_start_minus_one = 241u64 * 341u64;
ppu.borrow_mut().run_ppu_cycles(vblank_start_minus_one);
assert_eq!(ppu.borrow().scanline(), 241);
assert_eq!(ppu.borrow().pixel(), 0);
assert!(!ppu.borrow_mut().poll_nmi());
cpu.set_total_cycles(8); memory.borrow_mut().write(0x4014, 0x02, false);
let cpu_before = cpu.get_total_cycles();
let ppu_before = ppu_dot(&ppu.borrow());
let apu_before = apu.borrow().frame_counter().get_cycle_counter();
let dma_cycles = cpu
.handle_oam_dma_if_pending()
.expect("DMA should be pending");
let cpu_after = cpu.get_total_cycles();
let ppu_after = ppu_dot(&ppu.borrow());
let apu_after = apu.borrow().frame_counter().get_cycle_counter();
assert_eq!(cpu_after - cpu_before, dma_cycles as u64 + 7);
assert_eq!(ppu_after - ppu_before, dma_cycles as u64 * 3 + 7 * 3);
assert_eq!(apu_after - apu_before, dma_cycles as u32 + 7);
}
type TestMemory = (Rc<RefCell<Ppu>>, Rc<RefCell<Apu>>, Rc<RefCell<Bus>>);
fn create_test_memory() -> TestMemory {
let ppu = Rc::new(RefCell::new(crate::ppu::Ppu::new_for_testing(
TimingMode::Ntsc,
)));
let apu = Rc::new(RefCell::new(crate::apu::Apu::new()));
let config = crate::console::Config {
ram_init_mode: crate::console::RamInitMode::Zero,
..Default::default()
};
let app_context = Rc::new(RefCell::new(
crate::app_context::AppContext::new_with_config(config),
));
let memory = Rc::new(RefCell::new(Bus::new(
Rc::clone(&ppu),
Rc::clone(&apu),
app_context,
)));
(ppu, apu, memory)
}
fn create_test_memory_for(tv_system: TimingMode) -> TestMemory {
let ppu = Rc::new(RefCell::new(crate::ppu::Ppu::new_for_testing(tv_system)));
let apu = Rc::new(RefCell::new(crate::apu::Apu::new()));
let config = crate::console::Config {
ram_init_mode: crate::console::RamInitMode::Zero,
..Default::default()
};
let app_context = Rc::new(RefCell::new(
crate::app_context::AppContext::new_with_config(config),
));
let memory = Rc::new(RefCell::new(Bus::new(
Rc::clone(&ppu),
Rc::clone(&apu),
app_context,
)));
(ppu, apu, memory)
}
fn ppu_dot(ppu: &Ppu) -> u64 {
(ppu.scanline() as u64) * 341 + (ppu.pixel() as u64)
}
fn run(cpu: &mut Cpu) {
while !cpu.halted {
cpu.execute();
}
}
fn fake_cartridge(cpu: &mut Cpu, program: &[u8]) {
let mut prg_rom = vec![0; 0x4000];
for (i, &byte) in program.iter().enumerate() {
prg_rom[i] = byte;
}
prg_rom[0x3FFC] = 0x00; prg_rom[0x3FFD] = 0x80;
let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
}
fn setup_pending_dmc_dma_with_sample(
cpu: &mut Cpu,
apu: &Rc<RefCell<crate::apu::Apu>>,
sample_byte: u8,
) {
fake_cartridge(cpu, &[sample_byte]);
let mut apu = apu.borrow_mut();
apu.dmc_mut().write_sample_address(0x00);
apu.dmc_mut().write_sample_length(0x00);
apu.write_enable(0b0001_0000);
apu.dmc_mut().debug_set_transfer_start_delay(0);
apu.dmc_mut().debug_set_dma_pending(true);
}
#[test]
fn test_cpu_new() {
let (ppu, apu, memory) = create_test_memory();
let cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
assert_eq!(cpu.a, 0);
assert_eq!(cpu.x, 0);
assert_eq!(cpu.y, 0);
assert_eq!(cpu.sp, 0x00); assert_eq!(cpu.pc, 0);
assert_eq!(cpu.p, 0x20); }
#[test]
fn test_cpu_reset() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![KIL];
fake_cartridge(&mut cpu, &program);
cpu.a = 0xFF;
cpu.x = 0xFF;
cpu.y = 0xFF;
cpu.sp = 0x00;
cpu.p = 0xFF;
cpu.reset(true);
assert_eq!(cpu.a, 0xFF);
assert_eq!(cpu.x, 0xFF);
assert_eq!(cpu.y, 0xFF);
assert_eq!(cpu.sp, 0xFD);
assert_eq!(cpu.p, 0xFF);
}
#[test]
fn test_execute_kil_halts_cpu() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert!(cpu.halted, "KIL should halt the CPU when executed");
assert_eq!(cpu.pc, 0x8000, "KIL should not advance PC");
}
#[test]
fn test_adc_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x10;
run(&mut cpu);
assert_eq!(cpu.a, 0x30);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_ZERO, 0); assert_eq!(cpu.p & FLAG_OVERFLOW, 0); assert_eq!(cpu.p & FLAG_NEGATIVE, 0); }
#[test]
fn test_adc_immediate_with_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x10;
cpu.p |= FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.a, 0x31); assert_eq!(cpu.p & FLAG_CARRY, 0); }
#[test]
fn test_adc_immediate_carry_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0x01, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
run(&mut cpu);
assert_eq!(cpu.a, 0x00); assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO); }
#[test]
fn test_adc_immediate_overflow_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0x50, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50; run(&mut cpu);
assert_eq!(cpu.a, 0xA0); assert_eq!(cpu.p & FLAG_OVERFLOW, FLAG_OVERFLOW); assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE); }
#[test]
fn test_adc_immediate_negative_overflow() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0x80, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x80; run(&mut cpu);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.p & FLAG_OVERFLOW, FLAG_OVERFLOW); assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO); }
#[test]
fn test_adc_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x10;
cpu.bus.borrow_mut().write(0x42, 0x33, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x43);
}
#[test]
fn test_adc_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_ABS, 0x34, 0x12, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x20;
cpu.bus.borrow_mut().write(0x1234, 0x55, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x75);
}
#[test]
fn test_adc_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_ABSX, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x10;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x1239, 0x44, false); run(&mut cpu);
assert_eq!(cpu.a, 0x54);
}
#[test]
fn test_adc_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x15;
cpu.x = 0x03;
cpu.bus.borrow_mut().write(0x45, 0x22, false); run(&mut cpu);
assert_eq!(cpu.a, 0x37);
}
#[test]
fn test_adc_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_ABSY, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x08;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x1010, 0x17, false); run(&mut cpu);
assert_eq!(cpu.a, 0x1F);
}
#[test]
fn test_adc_indirect_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_INDX, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x05;
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x24, 0x74, false); cpu.bus.borrow_mut().write(0x25, 0x10, false); cpu.bus.borrow_mut().write(0x1074, 0x33, false); run(&mut cpu);
assert_eq!(cpu.a, 0x38);
}
#[test]
fn test_adc_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_INDY, 0x86, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x0A;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x86, 0x28, false); cpu.bus.borrow_mut().write(0x87, 0x10, false); cpu.bus.borrow_mut().write(0x1038, 0x06, false); run(&mut cpu);
assert_eq!(cpu.a, 0x10);
}
#[test]
fn test_and_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AND_IMM, 0b1010_1010, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1111_0000;
run(&mut cpu);
assert_eq!(cpu.a, 0b1010_0000);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_and_immediate_zero_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AND_IMM, 0b0000_1111, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1111_0000;
run(&mut cpu);
assert_eq!(cpu.a, 0);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_and_immediate_clears_negative_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AND_IMM, 0b0111_1111, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1111_1111;
cpu.p = FLAG_NEGATIVE; run(&mut cpu);
assert_eq!(cpu.a, 0b0111_1111);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_and_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AND_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1100_1100;
cpu.bus.borrow_mut().write(0x42, 0b1010_1010, false);
run(&mut cpu);
assert_eq!(cpu.a, 0b1000_1000);
}
#[test]
fn test_and_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AND_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1111_0000;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0b0011_1111, false); run(&mut cpu);
assert_eq!(cpu.a, 0b0011_0000);
}
#[test]
fn test_and_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AND_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1010_1010;
cpu.bus.borrow_mut().write(0x1234, 0b1100_1100, false);
run(&mut cpu);
assert_eq!(cpu.a, 0b1000_1000);
}
#[test]
fn test_and_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AND_ABSX, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1111_1111;
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0b0101_0101, false); run(&mut cpu);
assert_eq!(cpu.a, 0b0101_0101);
}
#[test]
fn test_and_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AND_ABSY, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1100_0011;
cpu.y = 0x20;
cpu.bus.borrow_mut().write(0x1020, 0b0011_1100, false); run(&mut cpu);
assert_eq!(cpu.a, 0b0000_0000);
}
#[test]
fn test_and_indirect_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AND_INDX, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1111_0000;
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x24, 0x74, false); cpu.bus.borrow_mut().write(0x25, 0x10, false); cpu.bus.borrow_mut().write(0x1074, 0b0000_1111, false); run(&mut cpu);
assert_eq!(cpu.a, 0b0000_0000);
}
#[test]
fn test_and_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AND_INDY, 0x86, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1010_1010;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x86, 0x28, false); cpu.bus.borrow_mut().write(0x87, 0x10, false); cpu.bus.borrow_mut().write(0x1038, 0b1111_0000, false); run(&mut cpu);
assert_eq!(cpu.a, 0b1010_0000);
}
#[test]
fn test_asl_accumulator() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASL_A, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b0100_0010;
run(&mut cpu);
assert_eq!(cpu.a, 0b1000_0100);
assert_eq!(cpu.p & FLAG_CARRY, 0);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_asl_accumulator_sets_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASL_A, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1000_0001;
run(&mut cpu);
assert_eq!(cpu.a, 0b0000_0010);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_asl_accumulator_sets_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASL_A, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1000_0000;
run(&mut cpu);
assert_eq!(cpu.a, 0);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_asl_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASL_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0b0011_0011, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0b0110_0110);
assert_eq!(cpu.p & FLAG_CARRY, 0);
}
#[test]
fn test_asl_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASL_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0b1010_0101, false); run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x47, false), 0b0100_1010);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_asl_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASL_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0b0100_0001, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1234, false), 0b1000_0010);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_asl_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASL_ABSXW, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0b0000_0001, false); run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1244, false), 0b0000_0010);
assert_eq!(cpu.p & FLAG_CARRY, 0);
assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_bit_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BIT_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1111_0000;
cpu.bus.borrow_mut().write(0x42, 0b1100_0011, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
assert_eq!(cpu.p & FLAG_OVERFLOW, FLAG_OVERFLOW);
}
#[test]
fn test_bit_zero_page_sets_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BIT_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b0000_1111;
cpu.bus.borrow_mut().write(0x42, 0b1111_0000, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
assert_eq!(cpu.p & FLAG_OVERFLOW, FLAG_OVERFLOW);
}
#[test]
fn test_bit_zero_page_clears_flags() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BIT_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1111_1111;
cpu.bus.borrow_mut().write(0x42, 0b0011_1111, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
assert_eq!(cpu.p & FLAG_OVERFLOW, 0);
}
#[test]
fn test_bit_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BIT_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1010_1010;
cpu.bus.borrow_mut().write(0x1234, 0b0101_1010, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
assert_eq!(cpu.p & FLAG_OVERFLOW, FLAG_OVERFLOW);
}
#[test]
fn test_bcc_branch_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BCC, 0x02, 0x00, 0x00, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.pc, 0x8004);
}
#[test]
fn test_bcc_branch_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BCC, 0x05, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.pc, 0x8002);
}
#[test]
fn test_bcc_branch_backward() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![KIL, 0x00, 0x00, BCC, 0xFB];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_CARRY; cpu.pc = 0x8003; run(&mut cpu);
assert_eq!(cpu.pc, 0x8000);
}
#[test]
fn test_bcs_branch_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BCS, 0x01, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.pc, 0x8003);
}
#[test]
fn test_bcs_branch_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BCS, 0x03, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.pc, 0x8002);
}
#[test]
fn test_beq_branch_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BEQ, 0x01, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_ZERO; run(&mut cpu);
assert_eq!(cpu.pc, 0x8003);
}
#[test]
fn test_beq_branch_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BEQ, 0x02, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_ZERO; run(&mut cpu);
assert_eq!(cpu.pc, 0x8002);
}
#[test]
fn test_bmi_branch_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BMI, 0x01, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_NEGATIVE; run(&mut cpu);
assert_eq!(cpu.pc, 0x8003);
}
#[test]
fn test_bmi_branch_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BMI, 0x04, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_NEGATIVE; run(&mut cpu);
assert_eq!(cpu.pc, 0x8002);
}
#[test]
fn test_bne_branch_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BNE, 0x01, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_ZERO; run(&mut cpu);
assert_eq!(cpu.pc, 0x8003);
}
#[test]
fn test_bne_branch_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BNE, 0x06, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_ZERO; run(&mut cpu);
assert_eq!(cpu.pc, 0x8002);
}
#[test]
fn test_bpl_branch_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BPL, 0x01, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_NEGATIVE; run(&mut cpu);
assert_eq!(cpu.pc, 0x8003);
}
#[test]
fn test_bpl_branch_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BPL, 0x07, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_NEGATIVE; run(&mut cpu);
assert_eq!(cpu.pc, 0x8002);
}
#[test]
fn test_bvc_branch_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BVC, 0x01, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_OVERFLOW; run(&mut cpu);
assert_eq!(cpu.pc, 0x8003);
}
#[test]
fn test_bvc_branch_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BVC, 0x05, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_OVERFLOW; run(&mut cpu);
assert_eq!(cpu.pc, 0x8002);
}
#[test]
fn test_bvs_branch_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BVS, 0x01, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_OVERFLOW; run(&mut cpu);
assert_eq!(cpu.pc, 0x8003);
}
#[test]
fn test_bvs_branch_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BVS, 0x08, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_OVERFLOW; run(&mut cpu);
assert_eq!(cpu.pc, 0x8002);
}
#[test]
fn test_cmp_immediate_equal() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_IMM, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO); assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_NEGATIVE, 0); }
#[test]
fn test_cmp_immediate_greater() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_IMM, 0x30, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, 0); assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_NEGATIVE, 0); }
#[test]
fn test_cmp_immediate_less() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_IMM, 0x50, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x30;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, 0); assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE); }
#[test]
fn test_cmp_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x80;
cpu.bus.borrow_mut().write(0x42, 0x80, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_cmp_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x10;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0x05, false); run(&mut cpu);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); }
#[test]
fn test_cmp_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x20;
cpu.bus.borrow_mut().write(0x1234, 0x30, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_CARRY, 0); }
#[test]
fn test_cmp_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_ABSX, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0xFF, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_cmp_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_ABSY, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x55;
cpu.y = 0x20;
cpu.bus.borrow_mut().write(0x1020, 0x44, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); }
#[test]
fn test_cmp_indirect_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_INDX, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x33;
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x24, 0x74, false);
cpu.bus.borrow_mut().write(0x25, 0x10, false);
cpu.bus.borrow_mut().write(0x1074, 0x33, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_cmp_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_INDY, 0x86, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x77;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x86, 0x28, false);
cpu.bus.borrow_mut().write(0x87, 0x10, false);
cpu.bus.borrow_mut().write(0x1038, 0x88, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_cpx_immediate_equal() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPX_IMM, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_cpx_immediate_greater() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPX_IMM, 0x30, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x50;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_cpx_immediate_less() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPX_IMM, 0x50, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x30;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_CARRY, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_cpx_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPX_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x80;
cpu.bus.borrow_mut().write(0x42, 0x80, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_cpx_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPX_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x20;
cpu.bus.borrow_mut().write(0x1234, 0x30, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_cpy_immediate_equal() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPY_IMM, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_cpy_immediate_greater() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPY_IMM, 0x30, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x50;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_cpy_immediate_less() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPY_IMM, 0x50, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x30;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_CARRY, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_cpy_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPY_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x80;
cpu.bus.borrow_mut().write(0x42, 0x80, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_cpy_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPY_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x20;
cpu.bus.borrow_mut().write(0x1234, 0x30, false);
run(&mut cpu);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_dec_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x50, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0x4F);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_dec_zero_page_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x01, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_dec_zero_page_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x00, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0xFF);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_dec_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0x80, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x47, false), 0x7F);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_dec_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0x30, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1234, false), 0x2F);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_dec_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ABSXW, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0x90, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1244, false), 0x8F);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_eor_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_IMM, 0b1111_0000, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1010_1010;
run(&mut cpu);
assert_eq!(cpu.a, 0b0101_1010);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_eor_immediate_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_IMM, 0b1010_1010, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1010_1010;
run(&mut cpu);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_eor_immediate_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_IMM, 0b1111_0000, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b0101_0101;
run(&mut cpu);
assert_eq!(cpu.a, 0b1010_0101);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_eor_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.bus.borrow_mut().write(0x42, 0x0F, false);
run(&mut cpu);
assert_eq!(cpu.a, 0xF0);
}
#[test]
fn test_eor_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0x55, false);
run(&mut cpu);
assert_eq!(cpu.a, 0xAA);
}
#[test]
fn test_eor_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x12;
cpu.bus.borrow_mut().write(0x1234, 0x34, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x26);
}
#[test]
fn test_eor_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_ABSX, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xAA;
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0x55, false);
run(&mut cpu);
assert_eq!(cpu.a, 0xFF);
}
#[test]
fn test_eor_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_ABSY, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xF0;
cpu.y = 0x20;
cpu.bus.borrow_mut().write(0x1254, 0x0F, false);
run(&mut cpu);
assert_eq!(cpu.a, 0xFF);
}
#[test]
fn test_eor_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_INDX, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1100_0011;
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x24, 0x74, false);
cpu.bus.borrow_mut().write(0x25, 0x10, false);
cpu.bus.borrow_mut().write(0x1074, 0b0011_1100, false);
run(&mut cpu);
assert_eq!(cpu.a, 0b1111_1111);
}
#[test]
fn test_eor_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_INDY, 0x86, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b1010_0101;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x86, 0x28, false);
cpu.bus.borrow_mut().write(0x87, 0x10, false);
cpu.bus.borrow_mut().write(0x1038, 0b0101_1010, false);
run(&mut cpu);
assert_eq!(cpu.a, 0xFF);
}
#[test]
fn test_clc() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CLC, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = FLAG_CARRY;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_CARRY, 0);
}
#[test]
fn test_cld() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CLD, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = FLAG_DECIMAL;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_DECIMAL, 0);
}
#[test]
fn test_cli() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CLI, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = FLAG_INTERRUPT;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_INTERRUPT, 0);
}
#[test]
fn test_clv() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CLV, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = FLAG_OVERFLOW;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_OVERFLOW, 0);
}
#[test]
fn test_sec() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SEC, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = 0;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_sed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SED, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = 0;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_DECIMAL, FLAG_DECIMAL);
}
#[test]
fn test_sei() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SEI, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = 0;
run(&mut cpu);
assert_eq!(cpu.p & FLAG_INTERRUPT, FLAG_INTERRUPT);
}
#[test]
fn test_inc_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x50, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0x51);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_inc_zero_page_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0xFF, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_inc_zero_page_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x7F, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0x80);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_inc_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0x20, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x47, false), 0x21);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_inc_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0x30, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1234, false), 0x31);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_inc_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ABSXW, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0x8F, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1244, false), 0x90);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_jmp_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
fake_cartridge(&mut cpu, &[]);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0600, JMP_ABS, false);
cpu.bus.borrow_mut().write(0x0601, 0x34, false);
cpu.bus.borrow_mut().write(0x0602, 0x12, false);
cpu.bus.borrow_mut().write(0x1234, KIL, false);
cpu.pc = 0x0600;
run(&mut cpu);
assert_eq!(cpu.pc, 0x1234); }
#[test]
fn test_jmp_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
fake_cartridge(&mut cpu, &[]);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0600, JMP_IND, false);
cpu.bus.borrow_mut().write(0x0601, 0x20, false);
cpu.bus.borrow_mut().write(0x0602, 0x10, false);
cpu.bus.borrow_mut().write(0x1020, 0x56, false);
cpu.bus.borrow_mut().write(0x1021, 0x18, false);
cpu.bus.borrow_mut().write(0x1856, KIL, false);
cpu.pc = 0x0600;
run(&mut cpu);
assert_eq!(cpu.pc, 0x1856); }
#[test]
fn test_jmp_indirect_page_boundary_bug() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
fake_cartridge(&mut cpu, &[]);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0600, JMP_IND, false);
cpu.bus.borrow_mut().write(0x0601, 0xFF, false);
cpu.bus.borrow_mut().write(0x0602, 0x10, false);
cpu.bus.borrow_mut().write(0x10FF, 0x34, false);
cpu.bus.borrow_mut().write(0x1000, 0x12, false); cpu.bus.borrow_mut().write(0x1234, KIL, false);
cpu.pc = 0x0600;
run(&mut cpu);
assert_eq!(cpu.pc, 0x1234); }
#[test]
fn test_jsr() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
fake_cartridge(&mut cpu, &[]);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0600, JSR, false);
cpu.bus.borrow_mut().write(0x0601, 0x34, false);
cpu.bus.borrow_mut().write(0x0602, 0x12, false);
cpu.bus.borrow_mut().write(0x1234, KIL, false);
cpu.pc = 0x0600;
cpu.sp = 0xFF;
run(&mut cpu);
assert_eq!(cpu.pc, 0x1234); assert_eq!(cpu.sp, 0xFD); assert_eq!(cpu.bus.borrow_mut().read(0x01FF, false), 0x06); assert_eq!(cpu.bus.borrow_mut().read(0x01FE, false), 0x02); }
#[test]
fn test_lda_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_IMM, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
run(&mut cpu);
assert_eq!(cpu.a, 0x42);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_lda_immediate_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_IMM, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
run(&mut cpu);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_lda_immediate_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_IMM, 0x80, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
run(&mut cpu);
assert_eq!(cpu.a, 0x80);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_lda_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x55, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x55);
}
#[test]
fn test_lda_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0xAA, false);
run(&mut cpu);
assert_eq!(cpu.a, 0xAA);
}
#[test]
fn test_lda_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0x77, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x77);
}
#[test]
fn test_lda_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ABSX, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0x88, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x88);
}
#[test]
fn test_lda_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ABSY, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x20;
cpu.bus.borrow_mut().write(0x1254, 0x99, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x99);
}
#[test]
fn test_lda_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_INDX, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x24, 0x74, false);
cpu.bus.borrow_mut().write(0x25, 0x10, false);
cpu.bus.borrow_mut().write(0x1074, 0xCC, false);
run(&mut cpu);
assert_eq!(cpu.a, 0xCC);
}
#[test]
fn test_lda_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_INDY, 0x86, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x86, 0x28, false);
cpu.bus.borrow_mut().write(0x87, 0x10, false);
cpu.bus.borrow_mut().write(0x1038, 0xDD, false);
run(&mut cpu);
assert_eq!(cpu.a, 0xDD);
}
#[test]
fn test_ldx_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_IMM, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
run(&mut cpu);
assert_eq!(cpu.x, 0x42);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_ldx_immediate_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_IMM, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
run(&mut cpu);
assert_eq!(cpu.x, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_ldx_immediate_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_IMM, 0x80, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
run(&mut cpu);
assert_eq!(cpu.x, 0x80);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_ldx_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x55, false);
run(&mut cpu);
assert_eq!(cpu.x, 0x55);
}
#[test]
fn test_ldx_zero_page_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_ZPY, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x47, 0xAA, false);
run(&mut cpu);
assert_eq!(cpu.x, 0xAA);
}
#[test]
fn test_ldx_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0x77, false);
run(&mut cpu);
assert_eq!(cpu.x, 0x77);
}
#[test]
fn test_ldx_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_ABSY, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x20;
cpu.bus.borrow_mut().write(0x1254, 0x99, false);
run(&mut cpu);
assert_eq!(cpu.x, 0x99);
}
#[test]
fn test_ldy_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_IMM, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
run(&mut cpu);
assert_eq!(cpu.y, 0x42);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_ldy_immediate_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_IMM, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
run(&mut cpu);
assert_eq!(cpu.y, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_ldy_immediate_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_IMM, 0x80, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
run(&mut cpu);
assert_eq!(cpu.y, 0x80);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_ldy_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x55, false);
run(&mut cpu);
assert_eq!(cpu.y, 0x55);
}
#[test]
fn test_ldy_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0xAA, false);
run(&mut cpu);
assert_eq!(cpu.y, 0xAA);
}
#[test]
fn test_ldy_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0x77, false);
run(&mut cpu);
assert_eq!(cpu.y, 0x77);
}
#[test]
fn test_ldy_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_ABSX, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0x88, false);
run(&mut cpu);
assert_eq!(cpu.y, 0x88);
}
#[test]
fn test_lsr_accumulator() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ACC, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10110101;
run(&mut cpu);
assert_eq!(cpu.a, 0b01011010);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_lsr_accumulator_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ACC, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b00000001;
run(&mut cpu);
assert_eq!(cpu.a, 0);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_lsr_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0b11001100, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0b01100110);
assert_eq!(cpu.p & FLAG_CARRY, 0);
}
#[test]
fn test_lsr_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0b10101011, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x47, false), 0b01010101);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_lsr_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0b01010100, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1234, false), 0b00101010);
assert_eq!(cpu.p & FLAG_CARRY, 0);
}
#[test]
fn test_lsr_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ABSXW, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0b00000011, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1244, false), 0b00000001);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_nop() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![NOP, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.x = 0x33;
cpu.y = 0x24;
cpu.p = 0xFF;
run(&mut cpu);
assert_eq!(cpu.a, 0x42);
assert_eq!(cpu.x, 0x33);
assert_eq!(cpu.y, 0x24);
assert_eq!(cpu.p, 0xFF);
}
#[test]
fn test_ora_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ORA_IMM, 0b01010101, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10101010;
run(&mut cpu);
assert_eq!(cpu.a, 0b11111111);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_ora_immediate_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ORA_IMM, 0x00, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x00;
run(&mut cpu);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_ora_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ORA_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
cpu.bus.borrow_mut().write(0x42, 0b00001111, false);
run(&mut cpu);
assert_eq!(cpu.a, 0b11111111);
}
#[test]
fn test_ora_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ORA_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10000000;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0b01000000, false);
run(&mut cpu);
assert_eq!(cpu.a, 0b11000000);
}
#[test]
fn test_ora_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ORA_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b00110011;
cpu.bus.borrow_mut().write(0x1234, 0b11001100, false);
run(&mut cpu);
assert_eq!(cpu.a, 0b11111111);
}
#[test]
fn test_ora_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ORA_ABSX, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b00001111;
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0b11110000, false);
run(&mut cpu);
assert_eq!(cpu.a, 0b11111111);
}
#[test]
fn test_ora_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ORA_ABSY, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b01010101;
cpu.y = 0x20;
cpu.bus.borrow_mut().write(0x1254, 0b10101010, false);
run(&mut cpu);
assert_eq!(cpu.a, 0b11111111);
}
#[test]
fn test_ora_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ORA_INDX, 0x82, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b00110011;
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x86, 0x34, false);
cpu.bus.borrow_mut().write(0x87, 0x12, false);
cpu.bus.borrow_mut().write(0x1234, 0b11001100, false);
run(&mut cpu);
assert_eq!(cpu.a, 0b11111111);
}
#[test]
fn test_ora_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ORA_INDY, 0x86, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10101010;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x86, 0x28, false);
cpu.bus.borrow_mut().write(0x87, 0x10, false);
cpu.bus.borrow_mut().write(0x1038, 0b01010101, false);
run(&mut cpu);
assert_eq!(cpu.a, 0b11111111);
}
#[test]
fn test_ora_negative_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ORA_IMM, 0x80, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x00;
run(&mut cpu);
assert_eq!(cpu.a, 0x80);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_dex() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEX, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
run(&mut cpu);
assert_eq!(cpu.x, 0x41);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_dex_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEX, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x01;
run(&mut cpu);
assert_eq!(cpu.x, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_dex_wrap() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEX, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x00;
run(&mut cpu);
assert_eq!(cpu.x, 0xFF);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_dey() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEY, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
run(&mut cpu);
assert_eq!(cpu.y, 0x41);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_inx() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INX, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
run(&mut cpu);
assert_eq!(cpu.x, 0x43);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_inx_wrap() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INX, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0xFF;
run(&mut cpu);
assert_eq!(cpu.x, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_iny() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INY, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
run(&mut cpu);
assert_eq!(cpu.y, 0x43);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_tax() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TAX, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
run(&mut cpu);
assert_eq!(cpu.x, 0x42);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_tax_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TAX, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x00;
run(&mut cpu);
assert_eq!(cpu.x, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_tax_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TAX, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x80;
run(&mut cpu);
assert_eq!(cpu.x, 0x80);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_tay() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TAY, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
run(&mut cpu);
assert_eq!(cpu.y, 0x42);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_txa() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TXA, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
run(&mut cpu);
assert_eq!(cpu.a, 0x42);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_tya() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TYA, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
run(&mut cpu);
assert_eq!(cpu.a, 0x42);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_rol_accumulator() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ACC, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10110101;
cpu.p = 0; run(&mut cpu);
assert_eq!(cpu.a, 0b01101010);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_rol_accumulator_with_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ACC, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b01010101;
cpu.p = FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.a, 0b10101011);
assert_eq!(cpu.p & FLAG_CARRY, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_rol_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0b11001100, false);
cpu.p = 0;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0b10011000);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_rol_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0b10101011, false);
cpu.p = FLAG_CARRY;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x47, false), 0b01010111);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_rol_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0b01010100, false);
cpu.p = 0;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1234, false), 0b10101000);
assert_eq!(cpu.p & FLAG_CARRY, 0);
}
#[test]
fn test_rol_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ABSXW, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0b00000011, false);
cpu.p = 0;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1244, false), 0b00000110);
assert_eq!(cpu.p & FLAG_CARRY, 0);
}
#[test]
fn test_ror_accumulator() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ACC, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10110101;
cpu.p = 0; run(&mut cpu);
assert_eq!(cpu.a, 0b01011010);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_ror_accumulator_with_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ACC, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b01010101;
cpu.p = FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.a, 0b10101010);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_ror_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0b11001100, false);
cpu.p = 0;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0b01100110);
assert_eq!(cpu.p & FLAG_CARRY, 0);
}
#[test]
fn test_ror_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x47, 0b10101011, false);
cpu.p = FLAG_CARRY;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x47, false), 0b11010101);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_ror_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0b01010100, false);
cpu.p = 0;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1234, false), 0b00101010);
assert_eq!(cpu.p & FLAG_CARRY, 0);
}
#[test]
fn test_ror_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ABSXW, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1244, 0b00000011, false);
cpu.p = 0;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1244, false), 0b00000001);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_rti() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RTI, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFC;
cpu.bus.borrow_mut().write(0x01FD, 0b11010011, false); cpu.bus.borrow_mut().write(0x01FE, 0x34, false); cpu.bus.borrow_mut().write(0x01FF, 0x12, false); cpu.bus.borrow_mut().write(0x1234, KIL, false); run(&mut cpu);
assert_eq!(cpu.p, 0b11100011);
assert_eq!(cpu.pc, 0x1234); assert_eq!(cpu.sp, 0xFF);
}
#[test]
fn test_rts() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RTS, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFD;
cpu.bus.borrow_mut().write(0x01FE, 0x33, false); cpu.bus.borrow_mut().write(0x01FF, 0x12, false); cpu.bus.borrow_mut().write(0x1234, KIL, false); run(&mut cpu);
assert_eq!(cpu.pc, 0x1234); assert_eq!(cpu.sp, 0xFF);
}
#[test]
fn test_sbc_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_IMM, 0x30, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.p |= FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.a, 0x20);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_sbc_immediate_with_borrow() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_IMM, 0x30, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.p &= !FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.a, 0x1F);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
}
#[test]
fn test_sbc_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x80;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x42, 0x40, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x40);
}
#[test]
fn test_sbc_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ZPX, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x47, 0x10, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x40);
}
#[test]
fn test_sbc_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ABS, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x60;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x1234, 0x20, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x40);
}
#[test]
fn test_sbc_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ABSX, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x70;
cpu.x = 0x10;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x1244, 0x30, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x40);
}
#[test]
fn test_sbc_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ABSY, 0x34, 0x12, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x90;
cpu.y = 0x20;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x1254, 0x50, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x40);
}
#[test]
fn test_sbc_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_INDX, 0x82, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xA0;
cpu.x = 0x04;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x86, 0x34, false);
cpu.bus.borrow_mut().write(0x87, 0x12, false);
cpu.bus.borrow_mut().write(0x1234, 0x60, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x40);
}
#[test]
fn test_sbc_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_INDY, 0x86, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xB0;
cpu.y = 0x10;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x86, 0x28, false);
cpu.bus.borrow_mut().write(0x87, 0x10, false);
cpu.bus.borrow_mut().write(0x1038, 0x70, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x40);
}
#[test]
fn test_sbc_overflow() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_IMM, 0xB0, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.p |= FLAG_CARRY;
run(&mut cpu);
assert_eq!(cpu.a, 0xA0);
assert_eq!(cpu.p & FLAG_OVERFLOW, FLAG_OVERFLOW);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_sta_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ZP, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x10, false), 0x42);
}
#[test]
fn test_sta_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ZPX, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.x = 0x05;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x15, false), 0x42);
}
#[test]
fn test_sta_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ABS, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1000, false), 0x42);
}
#[test]
fn test_sta_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ABSXW, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.x = 0x05;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1005, false), 0x42);
}
#[test]
fn test_sta_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ABSYW, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.y = 0x05;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1005, false), 0x42);
}
#[test]
fn test_sta_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_INDX, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x15, 0x00, false);
cpu.bus.borrow_mut().write(0x16, 0x10, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1000, false), 0x42);
}
#[test]
fn test_sta_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_INDYW, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x10, 0x00, false);
cpu.bus.borrow_mut().write(0x11, 0x10, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1005, false), 0x42);
}
#[test]
fn test_txs() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TXS, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0xFF;
run(&mut cpu);
assert_eq!(cpu.sp, 0xFF);
}
#[test]
fn test_tsx() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TSX, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xAB;
run(&mut cpu);
assert_eq!(cpu.x, 0xAB);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_tsx_zero_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TSX, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0x00;
run(&mut cpu);
assert_eq!(cpu.x, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_pha() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PHA, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.sp = 0xFD;
run(&mut cpu);
assert_eq!(cpu.sp, 0xFC);
assert_eq!(cpu.bus.borrow_mut().read(0x01FD, false), 0x42);
}
#[test]
fn test_pla() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLA, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFC;
cpu.bus.borrow_mut().write(0x01FD, 0x42, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x42);
assert_eq!(cpu.sp, 0xFD);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_pla_zero_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLA, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFC;
cpu.bus.borrow_mut().write(0x01FD, 0x00, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
}
#[test]
fn test_pla_negative_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLA, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFC;
cpu.bus.borrow_mut().write(0x01FD, 0x80, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x80);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_php() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PHP, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = 0xFF;
cpu.sp = 0xFD;
run(&mut cpu);
assert_eq!(cpu.sp, 0xFC);
assert_eq!(cpu.bus.borrow_mut().read(0x01FD, false), 0xFF);
}
#[test]
fn test_php_sets_break_and_unused_bits() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PHP, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = 0xC0;
cpu.sp = 0xFD;
run(&mut cpu);
assert_eq!(cpu.sp, 0xFC);
assert_eq!(cpu.bus.borrow_mut().read(0x01FD, false), 0xF0);
}
#[test]
fn test_plp() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLP, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFC;
cpu.bus.borrow_mut().write(0x01FD, 0xC3, false);
run(&mut cpu);
assert_eq!(cpu.p, 0xE3);
assert_eq!(cpu.sp, 0xFD);
}
#[test]
fn test_plp_ignores_break_and_unused_bits() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLP, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFC;
cpu.bus.borrow_mut().write(0x01FD, 0xFF, false);
cpu.p = 0xC0; run(&mut cpu);
assert_eq!(cpu.p, 0xEF);
assert_eq!(cpu.sp, 0xFD);
}
#[test]
fn test_stx_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STX_ZP, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x10, false), 0x42);
}
#[test]
fn test_stx_zero_page_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STX_ZPY, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
cpu.y = 0x05;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x15, false), 0x42);
}
#[test]
fn test_stx_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STX_ABS, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1000, false), 0x42);
}
#[test]
fn test_sty_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STY_ZP, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x10, false), 0x42);
}
#[test]
fn test_sty_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STY_ZPX, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
cpu.x = 0x05;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x15, false), 0x42);
}
#[test]
fn test_sty_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STY_ABS, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1000, false), 0x42);
}
#[test]
fn test_load_program_at_custom_address() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_IMM, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
run(&mut cpu);
assert_eq!(cpu.a, 0x42);
assert_eq!(cpu.bus.borrow_mut().read(0x8000, false), LDA_IMM);
assert_eq!(cpu.bus.borrow_mut().read(0x8001, false), 0x42);
assert_eq!(cpu.bus.borrow_mut().read(0x8002, false), KIL);
}
#[test]
fn test_aac_sets_carry_when_bit7_set() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AAC_IMM, 0b11000000, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11111111;
cpu.p = 0x00;
run(&mut cpu);
assert_eq!(cpu.a, 0b11000000);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_aac_clears_carry_when_bit7_clear() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AAC_IMM, 0b01000000, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11111111;
cpu.p = FLAG_CARRY;
run(&mut cpu);
assert_eq!(cpu.a, 0b01000000);
assert_eq!(cpu.p & FLAG_CARRY, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_sax_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SAX_ZP, 0x50, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
cpu.x = 0b10101010;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x0050, false), 0b10100000);
}
#[test]
fn test_sax_zero_page_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SAX_ZPY, 0x50, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
cpu.x = 0b10101010;
cpu.y = 0x05;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x0055, false), 0b10100000);
}
#[test]
fn test_sax_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SAX_ABS, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
cpu.x = 0b10101010;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1000, false), 0b10100000);
}
#[test]
fn test_sax_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SAX_INDX, 0x40, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11111111;
cpu.x = 0b10101010;
cpu.bus.borrow_mut().write(0x00EA, 0x00, false);
cpu.bus.borrow_mut().write(0x00EB, 0x10, false);
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1000, false), 0b10101010);
}
#[test]
fn test_arr_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ARR_IMM, 0b11110000, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11111111;
cpu.p = 0x00; run(&mut cpu);
assert_eq!(cpu.a, 0b01111000);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0); assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_arr_with_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ARR_IMM, 0b11110000, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11111111;
cpu.p = FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.a, 0b11111000);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE); }
#[test]
fn test_arr_sets_carry_and_overflow() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ARR_IMM, 0b01100001, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11111111;
cpu.p = 0x00;
run(&mut cpu);
assert_eq!(cpu.a, 0b00110000);
assert_eq!(cpu.p & FLAG_CARRY, 0);
assert_eq!(cpu.p & FLAG_OVERFLOW, FLAG_OVERFLOW);
}
#[test]
fn test_asr_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASR_IMM, 0b11110000, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11111111;
cpu.p = FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.a, 0b01111000);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0); assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_CARRY, 0); }
#[test]
fn test_asr_sets_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASR_IMM, 0b11110001, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11111111;
cpu.p = 0x00;
run(&mut cpu);
assert_eq!(cpu.a, 0b01111000);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_asr_zero_result() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASR_IMM, 0b00000001, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b00000001;
cpu.p = 0x00;
run(&mut cpu);
assert_eq!(cpu.a, 0b00000000);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); }
#[test]
fn test_atx_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ATX_IMM, 0b11110000, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11111111;
cpu.x = 0x00;
run(&mut cpu);
assert_eq!(cpu.a, 0b11110000);
assert_eq!(cpu.x, 0b11110000);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_atx_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ATX_IMM, 0b00001111, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
cpu.x = 0xFF;
run(&mut cpu);
assert_eq!(cpu.a, 0b00001111);
assert_eq!(cpu.x, 0b00001111);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_atx_preserves_result() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ATX_IMM, 0b10101010, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11001100;
cpu.x = 0x33;
run(&mut cpu);
assert_eq!(cpu.a, 0b10101010);
assert_eq!(cpu.x, 0b10101010);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
}
#[test]
fn test_axa_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x20, 0x00, false); cpu.bus.borrow_mut().write(0x21, 0x10, false); let program = vec![AXA_INDY, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0x7F;
cpu.y = 0x05; run(&mut cpu);
let stored_value = cpu.bus.borrow_mut().read(0x1005, false);
assert_eq!(stored_value, 0x11);
}
#[test]
fn test_axa_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AXA_ABSY, 0x00, 0x10, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0x3F;
cpu.y = 0x10; run(&mut cpu);
let stored_value = cpu.bus.borrow_mut().read(0x1010, false);
assert_eq!(stored_value, 0x11);
}
#[test]
fn test_axa_page_boundary() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AXA_ABSY, 0xFF, 0x10, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0xFF;
cpu.y = 0x01; run(&mut cpu);
let stored_value = cpu.bus.borrow_mut().read(0x1100, false);
assert_eq!(stored_value, 0x12);
}
#[test]
fn test_axs_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AXS_IMM, 0x05, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x0F;
cpu.x = 0xFF;
run(&mut cpu);
assert_eq!(cpu.x, 0x0A);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); }
#[test]
fn test_axs_with_borrow() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AXS_IMM, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x0F;
cpu.x = 0x0F;
run(&mut cpu);
assert_eq!(cpu.x, 0xFF);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_CARRY, 0); }
#[test]
fn test_axs_zero_result() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AXS_IMM, 0x08, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x0F;
cpu.x = 0xF8;
run(&mut cpu);
assert_eq!(cpu.x, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); }
#[test]
fn test_dcp_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DCP_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x10, false);
cpu.a = 0x0F;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0x0F);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO); assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_dcp_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DCP_ABSXW, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x1005, 0x20, false);
cpu.a = 0x30;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1005, false), 0x1F);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_dcp_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x20, 0x00, false);
cpu.bus.borrow_mut().write(0x21, 0x10, false);
let program = vec![DCP_INDYW, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x1010, 0x05, false);
cpu.a = 0x03;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1010, false), 0x04);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE); }
#[test]
fn test_dop_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DOP_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0xFF, false);
cpu.a = 0x10;
cpu.x = 0x20;
cpu.y = 0x30;
let saved_status = cpu.p;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0xFF); assert_eq!(cpu.a, 0x10); assert_eq!(cpu.x, 0x20); assert_eq!(cpu.y, 0x30); assert_eq!(cpu.p, saved_status); }
#[test]
fn test_dop_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DOP_ZPX, 0x40, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x45, 0xAA, false);
cpu.a = 0x10;
cpu.y = 0x30;
let saved_status = cpu.p;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x45, false), 0xAA); assert_eq!(cpu.a, 0x10); assert_eq!(cpu.x, 0x05); assert_eq!(cpu.y, 0x30); assert_eq!(cpu.p, saved_status); }
#[test]
fn test_dop_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DOP_IMM, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x10;
cpu.x = 0x20;
cpu.y = 0x30;
let saved_status = cpu.p;
run(&mut cpu);
assert_eq!(cpu.a, 0x10); assert_eq!(cpu.x, 0x20); assert_eq!(cpu.y, 0x30); assert_eq!(cpu.p, saved_status); }
#[test]
fn test_isb_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ISB_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x10, false);
cpu.a = 0x50;
cpu.p |= FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0x11);
assert_eq!(cpu.a, 0x3F);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_isb_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ISB_ABSXW, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x1005, 0xFF, false);
cpu.a = 0x00;
cpu.p |= FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1005, false), 0x00);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); }
#[test]
fn test_isb_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x20, 0x00, false);
cpu.bus.borrow_mut().write(0x21, 0x10, false);
let program = vec![ISB_INDYW, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x1010, 0x05, false);
cpu.a = 0x10;
cpu.p |= FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1010, false), 0x06);
assert_eq!(cpu.a, 0x0A);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY);
assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_kil_opcode_0x02() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert!(cpu.halted);
}
#[test]
fn test_kil_opcode_0x12() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![KIL2];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert!(cpu.halted);
}
#[test]
fn test_kil_opcode_0xf2() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![KIL12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert!(cpu.halted);
}
#[test]
fn test_kil_halts_until_reset() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![KIL, NOP, NOP];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert!(cpu.halted);
cpu.execute();
assert!(cpu.halted);
cpu.reset(true);
assert!(!cpu.halted);
let program2 = vec![NOP];
fake_cartridge(&mut cpu, &program2);
cpu.reset(true);
cpu.execute();
assert!(!cpu.halted);
}
#[test]
fn test_lar_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAR_ABSY, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.sp = 0xFD;
cpu.bus.borrow_mut().write(0x1005, 0xAB, false);
run(&mut cpu);
assert_eq!(cpu.a, 0xA9);
assert_eq!(cpu.x, 0xA9);
assert_eq!(cpu.sp, 0xA9);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE); assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_lax_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0x55, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x55);
assert_eq!(cpu.x, 0x55);
assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_lax_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_ABSY, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x1010, 0x80, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x80);
assert_eq!(cpu.x, 0x80);
assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE); assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_lax_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x20, 0x00, false);
cpu.bus.borrow_mut().write(0x21, 0x10, false);
let program = vec![LAX_INDY, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x1005, 0x00, false);
run(&mut cpu);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.x, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO); assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_nop_undocumented_0x1a() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![NOP_IMP, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let a_before = cpu.a;
let x_before = cpu.x;
let y_before = cpu.y;
let p_before = cpu.p;
run(&mut cpu);
assert_eq!(cpu.a, a_before);
assert_eq!(cpu.x, x_before);
assert_eq!(cpu.y, y_before);
assert_eq!(cpu.p, p_before);
}
#[test]
fn test_nop_undocumented_0xda() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![NOP_IMP5, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.x = 0x55;
let a_before = cpu.a;
let x_before = cpu.x;
run(&mut cpu);
assert_eq!(cpu.a, a_before);
assert_eq!(cpu.x, x_before);
}
#[test]
fn test_rla_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RLA_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x42, 0b0110_1010, false); cpu.a = 0b1111_0000; cpu.p &= !FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0xD4);
assert_eq!(cpu.a, 0xD0);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE); assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_rla_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RLA_ABSXW, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x1005, 0b1000_0001, false); cpu.a = 0xFF;
cpu.p |= FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1005, false), 0x03);
assert_eq!(cpu.a, 0x03);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_rla_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x20, 0x00, false);
cpu.bus.borrow_mut().write(0x21, 0x10, false);
let program = vec![RLA_INDYW, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x1010, 0x01, false);
cpu.a = 0x01;
cpu.p &= !FLAG_CARRY;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1010, false), 0x02);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO); assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_rra_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RRA_ZP, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x10, 0b1010_1010, false); cpu.a = 0x10;
cpu.p &= !FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x10, false), 0x55);
assert_eq!(cpu.a, 0x65);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_rra_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RRA_ABSXW, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x1005, 0b0000_0001, false); cpu.a = 0xFF;
cpu.p |= FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1005, false), 0x80);
assert_eq!(cpu.a, 0x80);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE); }
#[test]
fn test_rra_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x20, 0x00, false);
cpu.bus.borrow_mut().write(0x21, 0x10, false);
let program = vec![RRA_INDYW, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x1010, 0b0000_0010, false); cpu.a = 0x00;
cpu.p &= !FLAG_CARRY;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1010, false), 0x01);
assert_eq!(cpu.a, 0x01);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_sbc_immediate_undocumented() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_IMM2, 0x01, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x05;
cpu.p |= FLAG_CARRY; run(&mut cpu);
assert_eq!(cpu.a, 0x04);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_ZERO, 0);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_slo_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SLO_ZP, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x10, 0b0101_0101, false); cpu.a = 0b0000_1111; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x10, false), 0xAA);
assert_eq!(cpu.a, 0xAF);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_NEGATIVE, FLAG_NEGATIVE); }
#[test]
fn test_slo_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SLO_ABSXW, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x1005, 0b1000_0001, false); cpu.a = 0b0000_0010; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1005, false), 0x02);
assert_eq!(cpu.a, 0x02);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_slo_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x20, 0x00, false);
cpu.bus.borrow_mut().write(0x21, 0x10, false);
let program = vec![SLO_INDYW, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x1010, 0b0000_0001, false); cpu.a = 0b0000_0000; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1010, false), 0x02);
assert_eq!(cpu.a, 0x02);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_sre_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x42, 0b0000_0110, false); let program = vec![SRE_ZP, 0x42, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b0000_0001; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x42, false), 0x03);
assert_eq!(cpu.a, 0x02);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_sre_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x20, 0x00, false);
cpu.bus.borrow_mut().write(0x21, 0x10, false);
let program = vec![SRE_ABSXW, 0x00, 0x10, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1010, 0b0000_0101, false); cpu.a = 0b0000_0011; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1010, false), 0x02);
assert_eq!(cpu.a, 0x01);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY); assert_eq!(cpu.p & FLAG_ZERO, 0);
}
#[test]
fn test_sre_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x20, 0x00, false);
cpu.bus.borrow_mut().write(0x21, 0x10, false);
let program = vec![SRE_INDYW, 0x20, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x1010, 0b0000_1000, false); cpu.a = 0b0000_0100; run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1010, false), 0x04);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.p & FLAG_CARRY, 0); assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO); }
#[test]
fn test_sxa_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SXA_ABSY, 0x00, 0x10, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0xFF;
cpu.y = 0x10;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1010, false), 0x11); }
#[test]
fn test_sya_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SYA_ABSX, 0x00, 0x10, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0xFF;
cpu.x = 0x10;
run(&mut cpu);
assert_eq!(cpu.bus.borrow_mut().read(0x1010, false), 0x11); }
#[test]
fn test_top_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TOP_ABS, 0x00, 0x30, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
run(&mut cpu);
assert_eq!(cpu.a, 0x42);
}
#[test]
fn test_top_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TOP_ABSX, 0x00, 0x30, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.a = 0x42;
run(&mut cpu);
assert_eq!(cpu.a, 0x42);
assert_eq!(cpu.x, 0x10);
}
#[test]
fn test_xaa_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![XAA_IMM, 0x0F, KIL];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0xF0;
run(&mut cpu);
assert_eq!(cpu.a, 0x00);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0);
}
#[test]
fn test_xas_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![XAS_ABSY, 0x00, 0x10, KIL]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0xF0;
cpu.y = 0x10;
run(&mut cpu);
assert_eq!(cpu.sp, 0xF0);
assert_eq!(cpu.bus.borrow_mut().read(0x1010, false), 0x10);
}
#[test]
fn test_cycle_counter_starts_at_zero() {
let (ppu, apu, memory) = create_test_memory();
let cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
assert_eq!(cpu.get_total_cycles(), 0);
}
#[test]
fn test_master_clock_ntsc_read_cycle_ticks_master_clock() {
let mut clock = crate::cpu::MasterClock::new(TimingMode::Ntsc);
assert_eq!(clock.master_cycles(), 0);
clock.before_cpu_cycle(false);
clock.after_cpu_cycle(false);
assert_eq!(clock.master_cycles(), 12);
}
#[test]
fn test_master_clock_ntsc_write_cycle_ticks_master_clock() {
let mut clock = crate::cpu::MasterClock::new(TimingMode::Ntsc);
assert_eq!(clock.master_cycles(), 0);
clock.before_cpu_cycle(true);
clock.after_cpu_cycle(true);
assert_eq!(clock.master_cycles(), 12);
}
#[test]
fn test_rmw_on_ppu_register_preserves_w_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
cpu.bus.borrow_mut().write(0x2006, 0x20, false);
cpu.bus.borrow_mut().write(0x2006, 0x00, false);
let program = vec![0xEE, 0x06, 0x20, 0x02]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x2006, 0x20, false);
cpu.bus.borrow_mut().write(0x2006, 0x00, false);
cpu.execute();
cpu.bus.borrow_mut().write(0x2006, 0x30, false);
cpu.bus.borrow_mut().write(0x2006, 0x40, false);
cpu.bus.borrow_mut().write(0x2007, 0xAB, false);
cpu.bus.borrow_mut().write(0x2006, 0x30, false);
cpu.bus.borrow_mut().write(0x2006, 0x40, false);
let _ = cpu.bus.borrow_mut().read(0x2007, false); let value = cpu.bus.borrow_mut().read(0x2007, false);
assert_eq!(
value, 0xAB,
"Value at PPU $3040 should be $AB - dummy writes do affect PPU state"
);
}
#[test]
fn test_get_operand_implied() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let op = opcode::lookup(0xEA);
assert_eq!(cpu.get_operand(*op), 0, "Implied mode should return 0");
let op = opcode::lookup(0xE8);
assert_eq!(cpu.get_operand(*op), 0, "Implied mode should return 0");
}
#[test]
fn test_get_operand_accumulator() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let op = opcode::lookup(0x0A);
assert_eq!(cpu.get_operand(*op), 0, "Accumulator mode should return 0");
let op = opcode::lookup(0x4A);
assert_eq!(cpu.get_operand(*op), 0, "Accumulator mode should return 0");
}
#[test]
fn test_get_operand_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.bus.borrow_mut().write(0x0100, 0x42, false);
let op = opcode::lookup(0xA9);
assert_eq!(
cpu.get_operand(*op),
0x42,
"Immediate mode should return the byte at PC"
);
}
#[test]
fn test_get_operand_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.bus.borrow_mut().write(0x0100, 0x80, false);
let op = opcode::lookup(0xA5);
assert_eq!(
cpu.get_operand(*op),
0x80,
"Zero Page mode should return ZP address"
);
}
#[test]
fn test_get_operand_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0100, 0x80, false);
let op = opcode::lookup(0xB5);
assert_eq!(
cpu.get_operand(*op),
0x85,
"Zero Page,X mode should return (ZP + X) & 0xFF"
);
}
#[test]
fn test_get_operand_zero_page_x_wrapping() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.x = 0xFF;
cpu.bus.borrow_mut().write(0x0100, 0x80, false);
let op = opcode::lookup(0xB5);
assert_eq!(
cpu.get_operand(*op),
0x7F,
"Zero Page,X mode should wrap within zero page"
);
}
#[test]
fn test_get_operand_zero_page_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x0100, 0x20, false);
let op = opcode::lookup(0xB6);
assert_eq!(
cpu.get_operand(*op),
0x30,
"Zero Page,Y mode should return (ZP + Y) & 0xFF"
);
}
#[test]
fn test_get_operand_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.bus.borrow_mut().write(0x0100, 0x34, false); cpu.bus.borrow_mut().write(0x0101, 0x12, false);
let op = opcode::lookup(0xAD);
assert_eq!(
cpu.get_operand(*op),
0x1234,
"Absolute mode should return 16-bit address"
);
}
#[test]
fn test_get_operand_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x0100, 0x00, false); cpu.bus.borrow_mut().write(0x0101, 0x20, false);
let op = opcode::lookup(0xBD);
assert_eq!(
cpu.get_operand(*op),
0x2010,
"Absolute,X mode should return base + X"
);
}
#[test]
fn test_get_operand_absolute_x_page_crossing() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.x = 0xFF;
cpu.bus.borrow_mut().write(0x0100, 0x80, false); cpu.bus.borrow_mut().write(0x0101, 0x20, false);
let op = opcode::lookup(0xBD);
assert_eq!(
cpu.get_operand(*op),
0x217F,
"Absolute,X mode should handle page crossing"
);
}
#[test]
fn test_get_operand_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x0100, 0x00, false); cpu.bus.borrow_mut().write(0x0101, 0x30, false);
let op = opcode::lookup(0xB9);
assert_eq!(
cpu.get_operand(*op),
0x3005,
"Absolute,Y mode should return base + Y"
);
}
#[test]
fn test_get_operand_relative_positive() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.bus.borrow_mut().write(0x0100, 0x10, false);
let op = opcode::lookup(0xD0);
assert_eq!(
cpu.get_operand(*op),
0x10,
"Relative mode should return the immediate offset byte"
);
assert_eq!(cpu.pc, 0x0101, "PC should advance after reading offset");
}
#[test]
fn test_get_operand_relative_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.bus.borrow_mut().write(0x0100, 0xF0, false);
let op = opcode::lookup(0xF0);
assert_eq!(
cpu.get_operand(*op),
0xF0,
"Relative mode should return the immediate offset byte"
);
assert_eq!(cpu.pc, 0x0101, "PC should advance after reading offset");
}
#[test]
fn test_get_operand_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.bus.borrow_mut().write(0x0100, 0x34, false); cpu.bus.borrow_mut().write(0x0101, 0x12, false);
cpu.bus.borrow_mut().write(0x1234, 0x00, false); cpu.bus.borrow_mut().write(0x1235, 0x80, false);
let op = opcode::lookup(0x6C);
assert_eq!(
cpu.get_operand(*op),
0x8000,
"Indirect mode should return address at pointer"
);
}
#[test]
fn test_get_operand_indirect_page_boundary_bug() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.bus.borrow_mut().write(0x0100, 0xFF, false); cpu.bus.borrow_mut().write(0x0101, 0x02, false);
cpu.bus.borrow_mut().write(0x02FF, 0x34, false); cpu.bus.borrow_mut().write(0x0200, 0x12, false); cpu.bus.borrow_mut().write(0x0300, 0x56, false);
let op = opcode::lookup(0x6C);
assert_eq!(
cpu.get_operand(*op),
0x1234,
"Indirect mode should exhibit page boundary bug"
);
}
#[test]
fn test_get_operand_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x0100, 0x20, false);
cpu.bus.borrow_mut().write(0x0024, 0x00, false); cpu.bus.borrow_mut().write(0x0025, 0x30, false);
let op = opcode::lookup(0xA1);
assert_eq!(
cpu.get_operand(*op),
0x3000,
"Indexed Indirect mode should return address from (ZP+X)"
);
}
#[test]
fn test_get_operand_indexed_indirect_wrapping() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.x = 0xFF;
cpu.bus.borrow_mut().write(0x0100, 0x80, false);
cpu.bus.borrow_mut().write(0x007F, 0x34, false); cpu.bus.borrow_mut().write(0x0080, 0x12, false);
let op = opcode::lookup(0xA1);
assert_eq!(
cpu.get_operand(*op),
0x1234,
"Indexed Indirect should wrap pointer address in zero page"
);
}
#[test]
fn test_get_operand_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x0100, 0x20, false);
cpu.bus.borrow_mut().write(0x0020, 0x00, false); cpu.bus.borrow_mut().write(0x0021, 0x30, false);
let op = opcode::lookup(0xB1);
assert_eq!(
cpu.get_operand(*op),
0x3010,
"Indirect Indexed mode should return (ZP)+Y"
);
}
#[test]
fn test_get_operand_indirect_indexed_page_crossing() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.y = 0xFF;
cpu.bus.borrow_mut().write(0x0100, 0x20, false);
cpu.bus.borrow_mut().write(0x0020, 0x80, false); cpu.bus.borrow_mut().write(0x0021, 0x20, false);
let op = opcode::lookup(0xB1);
assert_eq!(
cpu.get_operand(*op),
0x217F,
"Indirect Indexed mode should handle page crossing"
);
}
#[test]
fn test_get_operand_indirect_indexed_zp_wrapping() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
cpu.pc = 0x0100;
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x0100, 0xFF, false);
cpu.bus.borrow_mut().write(0x00FF, 0x00, false); cpu.bus.borrow_mut().write(0x0000, 0x40, false);
let op = opcode::lookup(0xB1);
assert_eq!(
cpu.get_operand(*op),
0x4005,
"Indirect Indexed should wrap pointer in zero page"
);
}
#[test]
fn test_get_operand_invalid_opcode() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let op = opcode::lookup(0xFF); let result = cpu.get_operand(*op);
let _ = result; }
#[test]
fn test_execute_brk() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let mut prg_rom = vec![0; 0x4000];
prg_rom[0] = BRK;
prg_rom[1] = 0x00;
prg_rom[0x3FFC] = 0x00; prg_rom[0x3FFD] = 0x80;
prg_rom[0x3FFE] = 0x00; prg_rom[0x3FFF] = 0x80;
let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
cpu.reset(true);
cpu.sp = 0xFF;
cpu.p = 0x00;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, 0x8000, "PC should point to IRQ vector address");
assert_eq!(
cpu.p & FLAG_INTERRUPT,
FLAG_INTERRUPT,
"I flag should be set"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"BRK should take 7 cycles"
);
}
#[test]
fn test_execute_ora_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ORA_IMM, 0x0F];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xF0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xF0 OR 0x0F = 0xFF");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"ORA immediate should take 2 cycles"
);
}
#[test]
fn test_execute_ora_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ORA_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0x0F, false); cpu.a = 0xF0;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xF0 OR 0x0F = 0xFF");
}
#[test]
fn test_execute_ora_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ORA_ZPX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0x0F, false); cpu.a = 0xF0;
cpu.x = 0x02;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xF0 OR 0x0F = 0xFF");
}
#[test]
fn test_execute_ora_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ORA_ABS, 0x34, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0x0F, false); cpu.a = 0xF0;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xF0 OR 0x0F = 0xFF");
}
#[test]
fn test_execute_ora_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ORA_ABSX, 0x34, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1236, 0x0F, false); cpu.a = 0xF0;
cpu.x = 0x02;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xF0 OR 0x0F = 0xFF");
}
#[test]
fn test_execute_ora_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ORA_ABSY, 0x34, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1237, 0x0F, false); cpu.a = 0xF0;
cpu.y = 0x03;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xF0 OR 0x0F = 0xFF");
}
#[test]
fn test_execute_ora_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ORA_INDX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0x34, false); cpu.bus.borrow_mut().write(0x0043, 0x12, false); cpu.bus.borrow_mut().write(0x1234, 0x0F, false); cpu.a = 0xF0;
cpu.x = 0x02;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xF0 OR 0x0F = 0xFF");
}
#[test]
fn test_execute_ora_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ORA_INDY, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0040, 0x34, false); cpu.bus.borrow_mut().write(0x0041, 0x12, false); cpu.bus.borrow_mut().write(0x1237, 0x0F, false); cpu.a = 0xF0;
cpu.y = 0x03;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xF0 OR 0x0F = 0xFF");
}
#[test]
fn test_execute_ora_zero_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ORA_IMM, 0x00];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x00;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should remain 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_slo_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![SLO_INDX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0x34, false); cpu.bus.borrow_mut().write(0x0043, 0x12, false); cpu.bus.borrow_mut().write(0x1234, 0b00000101, false);
cpu.a = 0b11110000; cpu.x = 0x02;
cpu.execute();
assert_eq!(cpu.a, 0b11111010, "A should be 0xF0 | (5 << 1)");
assert_eq!(
cpu.bus.borrow_mut().read(0x1234, false),
0b00001010,
"Memory should contain shifted value"
);
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_slo_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![SLO_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b01000000, false); cpu.a = 0b00001111;
cpu.execute();
assert_eq!(cpu.a, 0b10001111, "A should be 15 | (64 << 1)");
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b10000000,
"Memory should contain shifted value"
);
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_slo_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![SLO_ABS, 0x34, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0b10000001, false); cpu.a = 0b00001111;
cpu.execute();
assert_eq!(cpu.a, 0b00001111, "A should be 15 | (129 << 1)");
assert_eq!(
cpu.bus.borrow_mut().read(0x1234, false),
0b00000010,
"Memory should contain shifted value"
);
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be set (bit 7 was 1)"
);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_slo_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![SLO_INDYW, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0040, 0x34, false); cpu.bus.borrow_mut().write(0x0041, 0x12, false); cpu.bus.borrow_mut().write(0x1237, 0b00000011, false);
cpu.a = 0b11000000; cpu.y = 0x03;
cpu.execute();
assert_eq!(cpu.a, 0b11000110, "A should be 192 | (3 << 1)");
assert_eq!(
cpu.bus.borrow_mut().read(0x1237, false),
0b00000110,
"Memory should contain shifted value"
);
}
#[test]
fn test_execute_slo_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![SLO_ZPX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b00010000, false); cpu.a = 0b00000001; cpu.x = 0x02;
cpu.execute();
assert_eq!(cpu.a, 0b00100001, "A should be 1 | (16 << 1)");
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b00100000,
"Memory should contain shifted value"
);
}
#[test]
fn test_execute_slo_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![SLO_ABSYW, 0x34, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1237, 0b00000010, false); cpu.a = 0b01010101; cpu.y = 0x03;
cpu.execute();
assert_eq!(cpu.a, 0b01010101, "A should be 85 | (2 << 1)");
assert_eq!(
cpu.bus.borrow_mut().read(0x1237, false),
0b00000100,
"Memory should contain shifted value"
);
}
#[test]
fn test_execute_slo_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![SLO_ABSXW, 0x34, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1236, 0b00001000, false); cpu.a = 0b00000000; cpu.x = 0x02;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b00010000, "A should be 0 | (8 << 1)");
assert_eq!(
cpu.bus.borrow_mut().read(0x1236, false),
0b00010000,
"Memory should contain shifted value"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"SLO absolute,X should take 7 cycles"
);
}
#[test]
fn test_execute_slo_zero_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![SLO_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b00000000, false); cpu.a = 0b00000000; let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"SLO zero page should take 5 cycles"
);
}
#[test]
fn test_execute_nop() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![NOP];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.x = 0x13;
cpu.y = 0x37;
cpu.p = 0b11001010;
let initial_sp = cpu.sp;
let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x42, "A should remain unchanged");
assert_eq!(cpu.x, 0x13, "X should remain unchanged");
assert_eq!(cpu.y, 0x37, "Y should remain unchanged");
assert_eq!(cpu.p, 0b11001010, "Status flags should remain unchanged");
assert_eq!(cpu.sp, initial_sp, "Stack pointer should remain unchanged");
assert_eq!(cpu.pc, initial_pc + 1, "PC should advance by 1 byte");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"NOP should take 2 cycles"
);
}
#[test]
fn test_execute_nop_undocumented_0x1a() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![NOP_IMP];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x55;
cpu.x = 0xAA;
cpu.y = 0xFF;
cpu.p = 0b10101010;
let initial_sp = cpu.sp;
let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x55, "A should remain unchanged");
assert_eq!(cpu.x, 0xAA, "X should remain unchanged");
assert_eq!(cpu.y, 0xFF, "Y should remain unchanged");
assert_eq!(cpu.p, 0b10101010, "Status flags should remain unchanged");
assert_eq!(cpu.sp, initial_sp, "Stack pointer should remain unchanged");
assert_eq!(cpu.pc, initial_pc + 1, "PC should advance by 1 byte");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"*NOP (0x1A) should take 2 cycles"
);
}
#[test]
fn test_execute_nop_undocumented_0x3a() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![NOP_IMP2];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x11;
cpu.x = 0x22;
cpu.y = 0x33;
cpu.p = 0b00110011;
let initial_sp = cpu.sp;
let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x11, "A should remain unchanged");
assert_eq!(cpu.x, 0x22, "X should remain unchanged");
assert_eq!(cpu.y, 0x33, "Y should remain unchanged");
assert_eq!(cpu.p, 0b00110011, "Status flags should remain unchanged");
assert_eq!(cpu.sp, initial_sp, "Stack pointer should remain unchanged");
assert_eq!(cpu.pc, initial_pc + 1, "PC should advance by 1 byte");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"*NOP (0x3A) should take 2 cycles"
);
}
#[test]
fn test_execute_nop_undocumented_0x5a() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![NOP_IMP3];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xCC;
cpu.x = 0xDD;
cpu.y = 0xEE;
cpu.p = 0b11110000;
let initial_sp = cpu.sp;
let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0xCC, "A should remain unchanged");
assert_eq!(cpu.x, 0xDD, "X should remain unchanged");
assert_eq!(cpu.y, 0xEE, "Y should remain unchanged");
assert_eq!(cpu.p, 0b11110000, "Status flags should remain unchanged");
assert_eq!(cpu.sp, initial_sp, "Stack pointer should remain unchanged");
assert_eq!(cpu.pc, initial_pc + 1, "PC should advance by 1 byte");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"*NOP (0x5A) should take 2 cycles"
);
}
#[test]
fn test_execute_nop_undocumented_0x7a() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![NOP_IMP4];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x01;
cpu.x = 0x02;
cpu.y = 0x03;
cpu.p = 0b00001111;
let initial_sp = cpu.sp;
let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x01, "A should remain unchanged");
assert_eq!(cpu.x, 0x02, "X should remain unchanged");
assert_eq!(cpu.y, 0x03, "Y should remain unchanged");
assert_eq!(cpu.p, 0b00001111, "Status flags should remain unchanged");
assert_eq!(cpu.sp, initial_sp, "Stack pointer should remain unchanged");
assert_eq!(cpu.pc, initial_pc + 1, "PC should advance by 1 byte");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"*NOP (0x7A) should take 2 cycles"
);
}
#[test]
fn test_execute_nop_undocumented_0xda() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![NOP_IMP5];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x7F;
cpu.x = 0x80;
cpu.y = 0x81;
cpu.p = 0b01010101;
let initial_sp = cpu.sp;
let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x7F, "A should remain unchanged");
assert_eq!(cpu.x, 0x80, "X should remain unchanged");
assert_eq!(cpu.y, 0x81, "Y should remain unchanged");
assert_eq!(cpu.p, 0b01010101, "Status flags should remain unchanged");
assert_eq!(cpu.sp, initial_sp, "Stack pointer should remain unchanged");
assert_eq!(cpu.pc, initial_pc + 1, "PC should advance by 1 byte");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"*NOP (0xDA) should take 2 cycles"
);
}
#[test]
fn test_execute_nop_undocumented_0xfa() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![NOP_IMP6];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFE;
cpu.x = 0xFD;
cpu.y = 0xFC;
cpu.p = 0b10011001;
let initial_sp = cpu.sp;
let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0xFE, "A should remain unchanged");
assert_eq!(cpu.x, 0xFD, "X should remain unchanged");
assert_eq!(cpu.y, 0xFC, "Y should remain unchanged");
assert_eq!(cpu.p, 0b10011001, "Status flags should remain unchanged");
assert_eq!(cpu.sp, initial_sp, "Stack pointer should remain unchanged");
assert_eq!(cpu.pc, initial_pc + 1, "PC should advance by 1 byte");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"*NOP (0xFA) should take 2 cycles"
);
}
#[test]
fn test_execute_asl_accumulator() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ASL_A];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b01010101; let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.a, 0b10101010,
"A should be shifted left: 0b01010101 << 1 = 0b10101010"
);
assert_eq!(
cpu.p & FLAG_CARRY,
0,
"Carry flag should not be set (bit 7 was 0)"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set (bit 7 is 1)"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"ASL accumulator should take 2 cycles"
);
}
#[test]
fn test_execute_asl_accumulator_with_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ASL_A];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10000001;
cpu.execute();
assert_eq!(
cpu.a, 0b00000010,
"A should be shifted left with bit 7 moved to carry"
);
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be set (bit 7 was 1)"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_asl_accumulator_zero_result() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ASL_A];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b00000000;
cpu.execute();
assert_eq!(cpu.a, 0, "A should be 0");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should not be set");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_asl_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ASL_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b01010101, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b10101010,
"Memory should be shifted left"
);
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should not be set");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"ASL zero page should take 5 cycles"
);
}
#[test]
fn test_execute_asl_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ASL_ZPX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0b11000000, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0045, false),
0b10000000,
"Memory at $45 should be shifted left"
);
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be set (bit 7 was 1)"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"ASL zero page,X should take 6 cycles"
);
}
#[test]
fn test_execute_asl_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ASL_ABS, 0x34, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0b00100000, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1234, false),
0b01000000,
"Memory at $1234 should be shifted left"
);
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should not be set");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"ASL absolute should take 6 cycles"
);
}
#[test]
fn test_execute_asl_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![ASL_ABSXW, 0x30, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x1234, 0b10000000, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1234, false),
0b00000000,
"Memory at $1234 should be shifted left to 0"
);
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be set (bit 7 was 1)"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"ASL absolute,X should take 7 cycles"
);
}
#[test]
fn test_execute_php() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![PHP];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = 0b10110101; cpu.sp = 0xFF; let initial_cycles = cpu.total_cycles;
cpu.execute();
let pushed_value = cpu.bus.borrow_mut().read(0x01FF, false);
assert_eq!(
pushed_value,
0b10110101 | FLAG_BREAK | FLAG_UNUSED,
"PHP should push P with BREAK and UNUSED flags set"
);
assert_eq!(cpu.sp, 0xFE, "Stack pointer should be decremented by 1");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"PHP should take 3 cycles"
);
}
#[test]
fn test_execute_php_preserves_flags() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![PHP];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = 0b11001010;
let initial_p = cpu.p;
cpu.execute();
assert_eq!(cpu.p, initial_p, "PHP should not modify the P register");
}
#[test]
fn test_execute_aac_immediate_0x0b() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AAC_IMM, 0xF0];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b11110000, "A should be ANDed with 0xF0");
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be set (bit 7 is 1)"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"AAC should take 2 cycles"
);
}
#[test]
fn test_execute_aac_immediate_0x2b() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AAC_IMM2, 0x7F];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b01110000, "A should be ANDed with 0x7F");
assert_eq!(
cpu.p & FLAG_CARRY,
0,
"Carry flag should not be set (bit 7 is 0)"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
0,
"Negative flag should not be set (bit 7 is 0)"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"AAC should take 2 cycles"
);
}
#[test]
fn test_execute_aac_zero_result() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AAC_IMM, 0x00];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should not be set");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_bpl_branch_taken_positive() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![BPL, 0x05];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_NEGATIVE; let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, initial_pc + 2 + 5, "PC should branch forward by 5");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"BPL with branch taken (no page cross) should take 3 cycles"
);
}
#[test]
fn test_execute_bpl_branch_not_taken_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![BPL, 0x05];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_NEGATIVE; let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.pc,
initial_pc + 2,
"PC should advance by 2 (instruction length)"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"BPL with branch not taken should take 2 cycles"
);
}
#[test]
fn test_execute_bpl_branch_backward() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![BPL, 0xF6];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_NEGATIVE; let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.pc,
initial_pc.wrapping_add(2).wrapping_sub(10),
"PC should branch backward by 10"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"BPL with branch taken crossing page should take 4 cycles"
);
}
#[test]
fn test_execute_bpl_branch_page_crossing() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let mut program = vec![0xEA; 0x90]; program.push(BPL); program.push(0x70);
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
for _ in 0..0x90 {
cpu.execute();
}
cpu.p &= !FLAG_NEGATIVE; let initial_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.pc,
initial_pc + 2 + 0x70,
"PC should branch forward crossing page"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"BPL with branch taken and page cross should take 4 cycles"
);
}
#[test]
fn test_execute_clc() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![CLC];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = 0xFF; let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should be cleared");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Other flags should remain unchanged"
);
assert_eq!(
cpu.p & FLAG_ZERO,
FLAG_ZERO,
"Other flags should remain unchanged"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"CLC should take 2 cycles"
);
}
#[test]
fn test_execute_clc_already_clear() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![CLC];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_CARRY;
cpu.execute();
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should remain cleared");
}
#[test]
fn test_execute_and_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AND_IMM, 0x0F];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x0F, "A should be ANDed with 0x0F");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"AND immediate should take 2 cycles"
);
}
#[test]
fn test_execute_and_zero_result() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AND_IMM, 0x00];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_and_negative_result() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AND_IMM, 0x80];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_and_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AND_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0xF0, false);
cpu.a = 0x55;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x50, "A should be ANDed with memory value");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"AND zero page should take 3 cycles"
);
}
#[test]
fn test_execute_and_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AND_ZPX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0x0F, false);
cpu.a = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x0F, "A should be ANDed with memory at $45");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"AND zero page,X should take 4 cycles"
);
}
#[test]
fn test_execute_and_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AND_ABS, 0x34, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0xAA, false);
cpu.a = 0x55;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0 (0x55 & 0xAA)");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"AND absolute should take 4 cycles"
);
}
#[test]
fn test_execute_and_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AND_ABSX, 0x30, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x1234, 0xCC, false);
cpu.a = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0xCC, "A should be ANDed with memory at $1234");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"AND absolute,X should take 4 cycles (no page cross)"
);
}
#[test]
fn test_execute_and_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AND_ABSY, 0x30, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x04;
cpu.bus.borrow_mut().write(0x1234, 0x3C, false);
cpu.a = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x3C, "A should be ANDed with memory at $1234");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"AND absolute,Y should take 4 cycles (no page cross)"
);
}
#[test]
fn test_execute_and_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AND_INDX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0x34, false);
cpu.bus.borrow_mut().write(0x0046, 0x12, false);
cpu.bus.borrow_mut().write(0x1234, 0x77, false);
cpu.a = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x77, "A should be ANDed with memory at ($45)");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"AND indexed indirect should take 6 cycles"
);
}
#[test]
fn test_execute_and_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![AND_INDY, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x0040, 0x30, false);
cpu.bus.borrow_mut().write(0x0041, 0x12, false);
cpu.bus.borrow_mut().write(0x1235, 0x88, false);
cpu.a = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x88, "A should be ANDed with memory at ($40),Y");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"AND indirect indexed should take 5 cycles (no page cross)"
);
}
#[test]
fn test_execute_rla_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![RLA_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b01010101, false);
cpu.a = 0xFF;
cpu.p &= !FLAG_CARRY; let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b10101010,
"Memory should be rotated left"
);
assert_eq!(cpu.a, 0b10101010, "A should be ANDed with rotated value");
assert_eq!(
cpu.p & FLAG_CARRY,
0,
"Carry should not be set (bit 7 was 0)"
);
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"RLA zero page should take 5 cycles"
);
}
#[test]
fn test_execute_rla_zero_page_with_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![RLA_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b10000001, false);
cpu.a = 0xFF;
cpu.p |= FLAG_CARRY;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b00000011,
"Memory should be rotated left with carry in"
);
assert_eq!(cpu.a, 0b00000011, "A should be ANDed with rotated value");
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry should be set (bit 7 was 1)"
);
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_rla_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![RLA_ZPX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0b00110011, false);
cpu.a = 0b11110000;
cpu.p &= !FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b01100000, "A should be ANDed with rotated value");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"RLA zero page,X should take 6 cycles"
);
}
#[test]
fn test_execute_rla_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![RLA_ABS, 0x34, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0b01000000, false);
cpu.a = 0b11111111;
cpu.p &= !FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b10000000, "A should be ANDed with rotated value");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"RLA absolute should take 6 cycles"
);
}
#[test]
fn test_execute_rla_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![RLA_ABSXW, 0x30, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x1234, 0b00000001, false);
cpu.a = 0b11111111;
cpu.p &= !FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b00000010, "A should be ANDed with rotated value");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"RLA absolute,X should take 7 cycles"
);
}
#[test]
fn test_execute_rla_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![RLA_ABSYW, 0x30, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x04;
cpu.bus.borrow_mut().write(0x1234, 0b11000000, false);
cpu.a = 0b11111111;
cpu.p &= !FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b10000000, "A should be ANDed with rotated value");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"RLA absolute,Y should take 7 cycles"
);
}
#[test]
fn test_execute_rla_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![RLA_INDX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0x34, false);
cpu.bus.borrow_mut().write(0x0046, 0x12, false);
cpu.bus.borrow_mut().write(0x1234, 0b00001111, false);
cpu.a = 0b11110000;
cpu.p &= !FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b00010000, "A should be ANDed with rotated value");
assert_eq!(
cpu.total_cycles,
initial_cycles + 8,
"RLA indexed indirect should take 8 cycles"
);
}
#[test]
fn test_execute_rla_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory.clone(), ppu.clone(), apu.clone());
let program = vec![RLA_INDYW, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x0040, 0x30, false);
cpu.bus.borrow_mut().write(0x0041, 0x12, false);
cpu.bus.borrow_mut().write(0x1235, 0b10101010, false);
cpu.a = 0b11111111;
cpu.p &= !FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b01010100, "A should be ANDed with rotated value");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 8,
"RLA indirect indexed should take 8 cycles"
);
}
#[test]
fn test_execute_jsr_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![JSR, 0x34, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, 0x1234, "PC should be set to target address");
assert_eq!(cpu.sp, 0xFD, "SP should have decremented by 2");
assert_eq!(
cpu.bus.borrow_mut().read(0x01FF, false),
0x80,
"High byte of return address on stack"
);
assert_eq!(
cpu.bus.borrow_mut().read(0x01FE, false),
0x02,
"Low byte of return address on stack"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"JSR should take 6 cycles"
);
}
#[test]
fn test_execute_jsr_stack_wrapping() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![JSR, 0x78, 0x56]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0x01;
cpu.execute();
assert_eq!(cpu.sp, 0xFF, "SP should wrap around");
assert_eq!(
cpu.bus.borrow_mut().read(0x0101, false),
0x80,
"High byte pushed at correct location"
);
assert_eq!(
cpu.bus.borrow_mut().read(0x0100, false),
0x02,
"Low byte pushed at correct location"
);
assert_eq!(cpu.pc, 0x5678, "PC set to target");
}
#[test]
fn test_execute_bit_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BIT_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b11000000, false); cpu.a = 0b00001111;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
assert_eq!(
cpu.p & FLAG_OVERFLOW,
FLAG_OVERFLOW,
"Overflow flag should be set"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"BIT ZP takes 3 cycles"
);
}
#[test]
fn test_execute_bit_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BIT_ABS, 0x34, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0b01110000, false); cpu.a = 0b00110000;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.p & FLAG_OVERFLOW,
FLAG_OVERFLOW,
"Overflow flag should be set"
);
}
#[test]
fn test_execute_rol_accumulator() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ACC];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10000001;
cpu.p |= FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b00000011, "A should be rotated left with carry in");
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry should be set from bit 7"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"ROL ACC takes 2 cycles"
);
}
#[test]
fn test_execute_rol_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b01000000, false);
cpu.p &= !FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b10000000,
"Memory should be rotated left"
);
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"ROL ZP takes 5 cycles"
);
}
#[test]
fn test_execute_rol_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ZPX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x02;
cpu.bus.borrow_mut().write(0x0042, 0b11111111, false);
cpu.p |= FLAG_CARRY;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b11111111,
"Memory at ZP+X should be rotated"
);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry should be set");
}
#[test]
fn test_execute_rol_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ABS, 0x34, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0b00000001, false);
cpu.p &= !FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1234, false),
0b00000010,
"Memory should be rotated left"
);
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"ROL ABS takes 6 cycles"
);
}
#[test]
fn test_execute_rol_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROL_ABSXW, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x34;
cpu.bus.borrow_mut().write(0x1234, 0b10000000, false);
cpu.p &= !FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1234, false),
0b00000000,
"Memory at ABS+X should be rotated"
);
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry should be set from bit 7"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"ROL ABSXW takes 7 cycles"
);
}
#[test]
fn test_execute_plp() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLP];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFF;
cpu.bus.borrow_mut().write(0x01FE, 0b11001010, false); cpu.sp = 0xFD;
cpu.p = 0b00000000;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.sp, 0xFE, "SP should increment");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be restored"
);
assert_eq!(
cpu.p & FLAG_OVERFLOW,
FLAG_OVERFLOW,
"Overflow flag should be restored"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be restored");
assert_eq!(
cpu.p & FLAG_INTERRUPT,
0,
"Interrupt flag should be restored"
);
assert_eq!(
cpu.p & FLAG_BREAK,
0,
"BREAK flag should be ignored/cleared"
);
assert_eq!(
cpu.p & FLAG_UNUSED,
FLAG_UNUSED,
"UNUSED flag should be set"
);
assert_eq!(cpu.total_cycles, initial_cycles + 4, "PLP takes 4 cycles");
}
#[test]
fn test_execute_plp_preserves_break_behavior() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLP];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x01FF, 0b00010000, false); cpu.sp = 0xFE;
cpu.execute();
assert_eq!(cpu.p & FLAG_BREAK, 0, "BREAK flag should be cleared");
assert_eq!(
cpu.p & FLAG_UNUSED,
FLAG_UNUSED,
"UNUSED should always be set"
);
}
#[test]
fn test_execute_bmi_branch_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BMI, 0x10]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_NEGATIVE;
let initial_cycles = cpu.total_cycles;
let initial_pc = cpu.pc;
cpu.execute();
assert_eq!(cpu.pc, initial_pc + 2 + 0x10, "PC should branch forward");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"BMI taken without page cross takes 3 cycles"
);
}
#[test]
fn test_execute_bmi_branch_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BMI, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_NEGATIVE;
let initial_cycles = cpu.total_cycles;
let initial_pc = cpu.pc;
cpu.execute();
assert_eq!(cpu.pc, initial_pc + 2, "PC should not branch");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"BMI not taken takes 2 cycles"
);
}
#[test]
fn test_execute_bmi_branch_backward() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BMI, 0xFE]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_NEGATIVE;
let initial_pc = cpu.pc;
cpu.execute();
assert_eq!(
cpu.pc,
initial_pc.wrapping_add(2u16.wrapping_add(0xFEu8 as i8 as u16)),
"PC should branch backward"
);
}
#[test]
fn test_execute_bmi_page_crossing() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA,
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA,
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA,
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA,
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA,
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA,
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA,
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA,
0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, BMI,
0x7F, ];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
for _ in 0..128 {
cpu.execute();
}
cpu.p |= FLAG_NEGATIVE;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"BMI with page crossing takes 4 cycles"
);
}
#[test]
fn test_execute_sec() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SEC];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "SEC takes 2 cycles");
}
#[test]
fn test_execute_sec_already_set() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SEC];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_CARRY;
cpu.execute();
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should remain set"
);
}
#[test]
fn test_execute_rti() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RTI];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFC;
cpu.bus.borrow_mut().write(0x01FD, 0b11001010, false); cpu.bus.borrow_mut().write(0x01FE, 0x34, false); cpu.bus.borrow_mut().write(0x01FF, 0x12, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, 0x1234, "PC should be restored from stack");
assert_eq!(cpu.sp, 0xFF, "SP should be incremented by 3");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be restored"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be restored");
assert_eq!(cpu.total_cycles, initial_cycles + 6, "RTI takes 6 cycles");
}
#[test]
fn test_execute_rti_clears_delayed_i_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RTI];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.delayed_i_flag = Some(true);
cpu.sp = 0xFC;
cpu.bus.borrow_mut().write(0x01FD, 0b00000000, false); cpu.bus.borrow_mut().write(0x01FE, 0x00, false);
cpu.bus.borrow_mut().write(0x01FF, 0x80, false);
cpu.execute();
assert_eq!(cpu.delayed_i_flag, None, "RTI should clear delayed I flag");
}
#[test]
fn test_execute_rti_restores_break_and_unused() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RTI];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFC;
cpu.bus.borrow_mut().write(0x01FD, 0b00110000, false); cpu.bus.borrow_mut().write(0x01FE, 0x00, false);
cpu.bus.borrow_mut().write(0x01FF, 0x80, false);
cpu.execute();
assert_eq!(cpu.p & FLAG_BREAK, 0, "BREAK flag should be ignored");
assert_eq!(cpu.p & FLAG_UNUSED, FLAG_UNUSED, "UNUSED should be set");
}
#[test]
fn test_execute_eor_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_IMM, 0b11110000];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10101010;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b01011010, "A should be XORed");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"EOR IMM takes 2 cycles"
);
}
#[test]
fn test_execute_eor_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0xFF, false);
cpu.a = 0xFF;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_eor_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_ZPX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x02;
cpu.bus.borrow_mut().write(0x0042, 0b10000000, false);
cpu.a = 0b00000000;
cpu.execute();
assert_eq!(cpu.a, 0b10000000, "A should have bit 7 set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_eor_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_ABS, 0x34, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0x0F, false);
cpu.a = 0xF0;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xFF");
}
#[test]
fn test_execute_eor_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_ABSX, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x34;
cpu.bus.borrow_mut().write(0x1234, 0xAA, false);
cpu.a = 0x55;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xFF");
}
#[test]
fn test_execute_eor_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_ABSY, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x34;
cpu.bus.borrow_mut().write(0x1234, 0b00110011, false);
cpu.a = 0b11001100;
cpu.execute();
assert_eq!(cpu.a, 0xFF, "A should be 0xFF");
}
#[test]
fn test_execute_eor_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_INDX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x02;
cpu.bus.borrow_mut().write(0x0042, 0x34, false); cpu.bus.borrow_mut().write(0x0043, 0x12, false); cpu.bus.borrow_mut().write(0x1234, 0x01, false);
cpu.a = 0x00;
cpu.execute();
assert_eq!(cpu.a, 0x01, "A should be 0x01");
}
#[test]
fn test_execute_eor_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![EOR_INDY, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x03;
cpu.bus.borrow_mut().write(0x0040, 0x31, false); cpu.bus.borrow_mut().write(0x0041, 0x12, false); cpu.bus.borrow_mut().write(0x1234, 0xFF, false);
cpu.a = 0xAA;
cpu.execute();
assert_eq!(cpu.a, 0x55, "A should be 0x55");
}
#[test]
fn test_execute_lsr_accumulator() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ACC];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10000001;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b01000000, "A should be shifted right");
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry should be set from bit 0"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"LSR ACC takes 2 cycles"
);
}
#[test]
fn test_execute_lsr_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b00000010, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b00000001,
"Memory should be shifted right"
);
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"LSR ZP takes 5 cycles"
);
}
#[test]
fn test_execute_lsr_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ZPX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x02;
cpu.bus.borrow_mut().write(0x0042, 0b00000001, false);
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b00000000,
"Memory should be 0"
);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry should be set");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_lsr_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ABS, 0x34, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0xFF, false);
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1234, false),
0x7F,
"Memory should be 0x7F"
);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry should be set");
}
#[test]
fn test_execute_lsr_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LSR_ABSXW, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x34;
cpu.bus.borrow_mut().write(0x1234, 0b10101010, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1234, false),
0b01010101,
"Memory should be shifted"
);
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"LSR ABSXW takes 7 cycles"
);
}
#[test]
fn test_execute_sre_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SRE_ZP, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b00001111, false);
cpu.a = 0b11110000;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b11110111, "A should be XORed with shifted value");
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b00000111,
"Memory should be shifted"
);
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry should be set from bit 0"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"SRE ZP takes 5 cycles"
);
}
#[test]
fn test_execute_sre_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SRE_ZPX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x02;
cpu.bus.borrow_mut().write(0x0042, 0b11111110, false);
cpu.a = 0xFF;
cpu.execute();
assert_eq!(cpu.a, 0b10000000, "A should be XORed with shifted value");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_sre_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SRE_ABS, 0x34, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1234, 0b10101010, false);
cpu.a = 0b01010101;
cpu.execute();
assert_eq!(cpu.a, 0b00000000, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_sre_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SRE_ABSXW, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x34;
cpu.bus.borrow_mut().write(0x1234, 0xFF, false);
cpu.a = 0x00;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x7F, "A should be 0x7F");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"SRE ABSXW takes 7 cycles"
);
}
#[test]
fn test_execute_sre_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SRE_ABSYW, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x34;
cpu.bus.borrow_mut().write(0x1234, 0b00000010, false);
cpu.a = 0xFF;
cpu.execute();
assert_eq!(cpu.a, 0b11111110, "A should be XORed with shifted value");
}
#[test]
fn test_execute_sre_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SRE_INDX, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x02;
cpu.bus.borrow_mut().write(0x0042, 0x34, false);
cpu.bus.borrow_mut().write(0x0043, 0x12, false);
cpu.bus.borrow_mut().write(0x1234, 0b11000000, false);
cpu.a = 0b01100000;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b00000000, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 8,
"SRE INDX takes 8 cycles"
);
}
#[test]
fn test_execute_sre_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SRE_INDYW, 0x40];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x03;
cpu.bus.borrow_mut().write(0x0040, 0x31, false);
cpu.bus.borrow_mut().write(0x0041, 0x12, false);
cpu.bus.borrow_mut().write(0x1234, 0b10101010, false);
cpu.a = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b10101010, "A should be XORed with shifted value");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 8,
"SRE INDYW takes 8 cycles"
);
}
#[test]
fn test_execute_pha() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PHA];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.sp = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.sp, 0xFE, "SP should decrement");
assert_eq!(
cpu.bus.borrow_mut().read(0x01FF, false),
0x42,
"A should be pushed to stack"
);
assert_eq!(cpu.total_cycles, initial_cycles + 3, "PHA takes 3 cycles");
}
#[test]
fn test_execute_pha_stack_wrapping() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PHA];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xAB;
cpu.sp = 0x00;
cpu.execute();
assert_eq!(cpu.sp, 0xFF, "SP should wrap around");
assert_eq!(
cpu.bus.borrow_mut().read(0x0100, false),
0xAB,
"A should be pushed to correct location"
);
}
#[test]
fn test_execute_asr_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASR_IMM, 0b11110000];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11111111;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b01111000, "A should be ANDed then shifted");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry should not be set");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "ASR takes 2 cycles");
}
#[test]
fn test_execute_asr_with_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASR_IMM, 0b00001111];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10101111;
cpu.execute();
assert_eq!(cpu.a, 0b00000111, "A should be ANDed then shifted");
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry should be set from bit 0"
);
}
#[test]
fn test_execute_asr_zero_result() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ASR_IMM, 0x00];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry should not be set");
}
#[test]
fn test_execute_jmp_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![JMP_ABS, 0x34, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, 0x1234, "PC should jump to target address");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"JMP ABS takes 3 cycles"
);
}
#[test]
fn test_execute_jmp_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![JMP_IND, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1200, 0x34, false); cpu.bus.borrow_mut().write(0x1201, 0x56, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, 0x5634, "PC should jump to indirect address");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"JMP IND takes 5 cycles"
);
}
#[test]
fn test_execute_jmp_indirect_page_boundary_bug() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![JMP_IND, 0xFF, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x12FF, 0x34, false); cpu.bus.borrow_mut().write(0x1200, 0x56, false); cpu.bus.borrow_mut().write(0x1300, 0x78, false);
cpu.execute();
assert_eq!(cpu.pc, 0x5634, "PC should use page boundary bug behavior");
}
#[test]
fn test_execute_bvc_not_taken_overflow_set() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BVC, 0x10]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let start_pc = cpu.pc;
cpu.p = FLAG_OVERFLOW;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, start_pc + 2, "Branch not taken, PC += 2");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"Branch not taken takes 2 cycles"
);
}
#[test]
fn test_execute_bvc_taken_same_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BVC, 0x10]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let start_pc = cpu.pc;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.pc,
start_pc.wrapping_add(2).wrapping_add(0x10),
"Branch taken, PC += 2 + offset"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"Branch taken same page takes 3 cycles"
);
}
#[test]
fn test_execute_bvc_taken_cross_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let mut program = vec![0xEA; 0x90]; program.push(BVC); program.push(0x70);
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
for _ in 0..0x90 {
cpu.execute();
}
cpu.p = 0;
let start_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
let target_pc = start_pc.wrapping_add(2).wrapping_add(0x70);
assert_eq!(cpu.pc, target_pc, "Branch to new page");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"Branch taken cross page takes 4 cycles"
);
}
#[test]
fn test_execute_bvc_backward_branch() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BVC, 0xFE]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let start_pc = cpu.pc;
cpu.p = 0;
cpu.execute();
let offset = -2_i16; let target_pc = start_pc.wrapping_add(2).wrapping_add_signed(offset);
assert_eq!(cpu.pc, target_pc, "Branch backward");
}
#[test]
fn test_execute_cli_clears_interrupt_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CLI];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = FLAG_INTERRUPT;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.p & FLAG_INTERRUPT,
0,
"Interrupt flag should be cleared"
);
assert_eq!(cpu.total_cycles, initial_cycles + 2, "CLI takes 2 cycles");
}
#[test]
fn test_execute_cli_doesnt_affect_other_flags() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CLI];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = FLAG_CARRY | FLAG_ZERO | FLAG_NEGATIVE | FLAG_INTERRUPT;
cpu.execute();
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be preserved"
);
assert_eq!(
cpu.p & FLAG_ZERO,
FLAG_ZERO,
"Zero flag should be preserved"
);
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be preserved"
);
assert_eq!(
cpu.p & FLAG_INTERRUPT,
0,
"Interrupt flag should be cleared"
);
}
#[test]
fn test_execute_cli_delays_irq_for_one_instruction() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
let mut prg_rom = vec![0; 0x4000];
prg_rom[0x3FFC] = 0x00;
prg_rom[0x3FFD] = 0x80;
prg_rom[0x3FFE] = 0x00;
prg_rom[0x3FFF] = 0x90;
prg_rom[0x0000] = CLI;
prg_rom[0x0001] = NOP;
let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
cpu.reset(true);
cpu.p |= FLAG_INTERRUPT;
cpu.set_irq_pending(true);
cpu.execute();
assert_eq!(cpu.p & FLAG_INTERRUPT, 0, "CLI should clear I");
assert_ne!(
cpu.pc, 0x9000,
"IRQ must not be taken immediately after CLI"
);
cpu.execute();
assert_eq!(cpu.pc, 0x9000, "IRQ should be taken after one instruction");
}
#[test]
fn test_execute_cli_irq_taken_after_one_following_instruction() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(
TimingMode::Ntsc,
Rc::clone(&memory),
Rc::clone(&ppu),
Rc::clone(&apu),
);
let mut prg_rom = vec![0; 0x4000];
prg_rom[0x3FFC] = 0x00;
prg_rom[0x3FFD] = 0x80;
prg_rom[0x3FFE] = 0x00;
prg_rom[0x3FFF] = 0x90;
prg_rom[0x0000] = CLI;
prg_rom[0x0001] = NOP;
prg_rom[0x0002] = NOP;
let chr_rom = vec![0; 0x2000];
let cartridge = Cartridge::from_parts(prg_rom, chr_rom, NametableLayout::Horizontal);
cpu.bus.borrow_mut().map_cartridge(cartridge);
cpu.reset(true);
cpu.p |= FLAG_INTERRUPT;
cpu.set_irq_pending(true);
cpu.execute();
assert_eq!(cpu.p & FLAG_INTERRUPT, 0, "CLI should clear I");
assert_ne!(
cpu.pc, 0x9000,
"IRQ must not be taken immediately after CLI"
);
cpu.execute();
assert_eq!(
cpu.pc, 0x9000,
"IRQ should be taken after one following instruction"
);
}
#[test]
fn test_execute_rts_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RTS];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let return_address = 0x1234_u16;
cpu.push_word(return_address - 1);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.pc, return_address,
"PC should be set to return address (popped value + 1)"
);
assert_eq!(cpu.total_cycles, initial_cycles + 6, "RTS takes 6 cycles");
}
#[test]
fn test_execute_rts_stack_pointer() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RTS];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let initial_sp = cpu.sp;
cpu.push_word(0x5678);
cpu.execute();
assert_eq!(
cpu.sp, initial_sp,
"Stack pointer should be restored after RTS"
);
}
#[test]
fn test_execute_rts_doesnt_affect_flags() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RTS];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = FLAG_CARRY | FLAG_ZERO | FLAG_NEGATIVE;
cpu.push_word(0x1234);
cpu.execute();
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be preserved"
);
assert_eq!(
cpu.p & FLAG_ZERO,
FLAG_ZERO,
"Zero flag should be preserved"
);
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be preserved"
);
}
#[test]
fn test_execute_adc_immediate_no_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0x10]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x05;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x15, "A should be 0x05 + 0x10");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should be clear");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should be clear");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should be clear");
assert_eq!(cpu.p & FLAG_OVERFLOW, 0, "Overflow flag should be clear");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"ADC IMM takes 2 cycles"
);
}
#[test]
fn test_execute_adc_with_carry_in() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0x10]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x05;
cpu.p = FLAG_CARRY;
cpu.execute();
assert_eq!(cpu.a, 0x16, "A should be 0x05 + 0x10 + 1");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should be clear");
}
#[test]
fn test_execute_adc_with_carry_out() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0xFF]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x02;
cpu.p = 0;
cpu.execute();
assert_eq!(cpu.a, 0x01, "A should wrap to 0x01");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
}
#[test]
fn test_execute_adc_zero_result() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0xFF]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x01;
cpu.p = 0;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
}
#[test]
fn test_execute_adc_negative_result() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0x80]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x00;
cpu.p = 0;
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_adc_overflow_positive() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0x7F]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x01; cpu.p = 0;
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(
cpu.p & FLAG_OVERFLOW,
FLAG_OVERFLOW,
"Overflow flag should be set"
);
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_adc_overflow_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_IMM, 0x80]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x80; cpu.p = 0;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should wrap to 0");
assert_eq!(
cpu.p & FLAG_OVERFLOW,
FLAG_OVERFLOW,
"Overflow flag should be set"
);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
}
#[test]
fn test_execute_adc_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_ZP, 0x42]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0x33, false);
cpu.a = 0x10;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x43, "A should be 0x10 + 0x33");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"ADC ZP takes 3 cycles"
);
}
#[test]
fn test_execute_adc_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_ZPX, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0x25, false);
cpu.a = 0x10;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x35, "A should be 0x10 + 0x25");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"ADC ZPX takes 4 cycles"
);
}
#[test]
fn test_execute_adc_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_ABS, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1200, 0x44, false);
cpu.a = 0x11;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x55, "A should be 0x11 + 0x44");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"ADC ABS takes 4 cycles"
);
}
#[test]
fn test_execute_adc_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_ABSX, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x08;
cpu.bus.borrow_mut().write(0x1208, 0x22, false);
cpu.a = 0x10;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x32, "A should be 0x10 + 0x22");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"ADC ABSX (no page cross) takes 4 cycles"
);
}
#[test]
fn test_execute_adc_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_ABSY, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x03;
cpu.bus.borrow_mut().write(0x1203, 0x15, false);
cpu.a = 0x20;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x35, "A should be 0x20 + 0x15");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"ADC ABSY (no page cross) takes 4 cycles"
);
}
#[test]
fn test_execute_adc_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_INDX, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0x34, false);
cpu.bus.borrow_mut().write(0x0046, 0x12, false);
cpu.bus.borrow_mut().write(0x1234, 0x50, false);
cpu.a = 0x10;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x60, "A should be 0x10 + 0x50");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"ADC INDX takes 6 cycles"
);
}
#[test]
fn test_execute_adc_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ADC_INDY, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x08;
cpu.bus.borrow_mut().write(0x0040, 0x00, false);
cpu.bus.borrow_mut().write(0x0041, 0x12, false);
cpu.bus.borrow_mut().write(0x1208, 0x33, false);
cpu.a = 0x11;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x44, "A should be 0x11 + 0x33");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"ADC INDY (no page cross) takes 5 cycles"
);
}
#[test]
fn test_execute_ror_accumulator_no_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ACC]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10110110;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b01011011, "A should be rotated right");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should be clear");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should be clear");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"ROR ACC takes 2 cycles"
);
}
#[test]
fn test_execute_ror_accumulator_with_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ACC]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b00110110;
cpu.p = FLAG_CARRY;
cpu.execute();
assert_eq!(cpu.a, 0b10011011, "A should be rotated right with carry");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should be clear");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_ror_accumulator_sets_carry() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ACC]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b00110111; cpu.p = 0;
cpu.execute();
assert_eq!(cpu.a, 0b00011011, "A should be rotated right");
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be set from bit 0"
);
}
#[test]
fn test_execute_ror_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ZP, 0x42]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b11001100, false);
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b01100110,
"Memory should be rotated right"
);
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should be clear");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"ROR ZP takes 5 cycles"
);
}
#[test]
fn test_execute_ror_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ZPX, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0b10101010, false);
cpu.p = FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0045, false),
0b11010101,
"Memory should be rotated right with carry in"
);
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should be clear");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"ROR ZPX takes 6 cycles"
);
}
#[test]
fn test_execute_ror_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ABS, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1200, 0b00110011, false);
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0b00011001,
"Memory should be rotated right"
);
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be set from bit 0"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"ROR ABS takes 6 cycles"
);
}
#[test]
fn test_execute_ror_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ROR_ABSXW, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x08;
cpu.bus.borrow_mut().write(0x1208, 0b11110000, false);
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1208, false),
0b01111000,
"Memory should be rotated right"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"ROR ABSXW takes 7 cycles"
);
}
#[test]
fn test_execute_rra_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RRA_ZP, 0x42]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0b10000000, false);
cpu.a = 0x10;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0x40,
"Memory should be rotated right"
);
assert_eq!(cpu.a, 0x50, "A should be updated with ADC");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should be clear");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"RRA ZP takes 5 cycles"
);
}
#[test]
fn test_execute_rra_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RRA_ZPX, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0b00000011, false); cpu.a = 0x05;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0045, false),
0x01,
"Memory should be rotated right"
);
assert_eq!(cpu.a, 0x07, "A should be updated with ADC including carry");
assert_eq!(
cpu.p & FLAG_CARRY,
0,
"Carry flag should be clear after ADC"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"RRA ZPX takes 6 cycles"
);
}
#[test]
fn test_execute_rra_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RRA_ABS, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1200, 0x02, false);
cpu.a = 0xFF;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x01,
"Memory should be rotated right"
);
assert_eq!(cpu.a, 0x00, "A should wrap with carry");
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be set from ADC"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"RRA ABS takes 6 cycles"
);
}
#[test]
fn test_execute_rra_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RRA_ABSXW, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x1210, 0x20, false);
cpu.a = 0x05;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1210, false),
0x10,
"Memory should be rotated right"
);
assert_eq!(cpu.a, 0x15, "A should be updated with ADC");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"RRA ABSXW takes 7 cycles"
);
}
#[test]
fn test_execute_rra_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RRA_ABSYW, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x08;
cpu.bus.borrow_mut().write(0x1208, 0x04, false);
cpu.a = 0x01;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1208, false),
0x02,
"Memory should be rotated right"
);
assert_eq!(cpu.a, 0x03, "A should be updated with ADC");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"RRA ABSYW takes 7 cycles"
);
}
#[test]
fn test_execute_rra_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RRA_INDX, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0x00, false);
cpu.bus.borrow_mut().write(0x0046, 0x12, false);
cpu.bus.borrow_mut().write(0x1200, 0x08, false);
cpu.a = 0x01;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x04,
"Memory should be rotated right"
);
assert_eq!(cpu.a, 0x05, "A should be updated with ADC");
assert_eq!(
cpu.total_cycles,
initial_cycles + 8,
"RRA INDX takes 8 cycles"
);
}
#[test]
fn test_execute_rra_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![RRA_INDYW, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x08;
cpu.bus.borrow_mut().write(0x0040, 0x00, false);
cpu.bus.borrow_mut().write(0x0041, 0x12, false);
cpu.bus.borrow_mut().write(0x1208, 0x10, false);
cpu.a = 0x0F;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1208, false),
0x08,
"Memory should be rotated right"
);
assert_eq!(cpu.a, 0x17, "A should be updated with ADC");
assert_eq!(
cpu.total_cycles,
initial_cycles + 8,
"RRA INDYW takes 8 cycles"
);
}
#[test]
fn test_execute_pla_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLA]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.push_byte(0x42);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x42, "A should be pulled from stack");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should be clear");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should be clear");
assert_eq!(cpu.total_cycles, initial_cycles + 4, "PLA takes 4 cycles");
}
#[test]
fn test_execute_pla_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLA]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.push_byte(0x00);
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should be clear");
}
#[test]
fn test_execute_pla_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLA]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.push_byte(0x80);
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should be clear");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_pla_stack_pointer() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![PLA]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let initial_sp = cpu.sp;
cpu.push_byte(0x55);
cpu.execute();
assert_eq!(cpu.sp, initial_sp, "Stack pointer should be restored");
}
#[test]
fn test_execute_arr_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ARR_IMM, 0b11001100]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b01100000, "A should be ANDed then rotated");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry should be bit 6");
assert_eq!(cpu.p & FLAG_OVERFLOW, 0, "Overflow should be bit6^bit5");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "ARR takes 2 cycles");
}
#[test]
fn test_execute_arr_with_carry_in() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ARR_IMM, 0b11001100]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
cpu.p = FLAG_CARRY;
cpu.execute();
assert_eq!(
cpu.a, 0b11100000,
"A should be ANDed then rotated with carry"
);
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry should be bit 6");
assert_eq!(cpu.p & FLAG_OVERFLOW, 0, "Overflow should be bit6^bit5");
}
#[test]
fn test_execute_arr_sets_carry_and_overflow_from_bits_6_and_5() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ARR_IMM, 0xFF]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10000000;
cpu.p = 0;
cpu.execute();
assert_eq!(cpu.a, 0b01000000, "A should be rotated");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry should be bit 6");
assert_eq!(
cpu.p & FLAG_OVERFLOW,
FLAG_OVERFLOW,
"Overflow should be bit6^bit5"
);
}
#[test]
fn test_execute_bvs_not_taken_overflow_clear() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BVS, 0x10]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let start_pc = cpu.pc;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, start_pc + 2, "Branch not taken, PC += 2");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"Branch not taken takes 2 cycles"
);
}
#[test]
fn test_execute_bvs_taken_same_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BVS, 0x10]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let start_pc = cpu.pc;
cpu.p = FLAG_OVERFLOW;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.pc,
start_pc.wrapping_add(2).wrapping_add(0x10),
"Branch taken, PC += 2 + offset"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"Branch taken same page takes 3 cycles"
);
}
#[test]
fn test_execute_bvs_taken_cross_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let mut program = vec![0xEA; 0x90]; program.push(BVS);
program.push(0x70);
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
for _ in 0..0x90 {
cpu.execute();
}
cpu.p = FLAG_OVERFLOW;
let start_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
let target_pc = start_pc.wrapping_add(2).wrapping_add(0x70);
assert_eq!(cpu.pc, target_pc, "Branch to new page");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"Branch taken cross page takes 4 cycles"
);
}
#[test]
fn test_execute_bvs_backward_branch() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BVS, 0xFE]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let start_pc = cpu.pc;
cpu.p = FLAG_OVERFLOW;
cpu.execute();
let offset = -2_i16;
let target_pc = start_pc.wrapping_add(2).wrapping_add_signed(offset);
assert_eq!(cpu.pc, target_pc, "Branch backward");
}
#[test]
fn test_execute_sei_sets_interrupt_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SEI];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.p & FLAG_INTERRUPT,
FLAG_INTERRUPT,
"Interrupt flag should be set"
);
assert_eq!(cpu.total_cycles, initial_cycles + 2, "SEI takes 2 cycles");
}
#[test]
fn test_execute_sei_doesnt_affect_other_flags() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SEI];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p = FLAG_CARRY | FLAG_ZERO | FLAG_NEGATIVE;
cpu.execute();
assert_eq!(
cpu.p & FLAG_CARRY,
FLAG_CARRY,
"Carry flag should be preserved"
);
assert_eq!(
cpu.p & FLAG_ZERO,
FLAG_ZERO,
"Zero flag should be preserved"
);
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be preserved"
);
assert_eq!(
cpu.p & FLAG_INTERRUPT,
FLAG_INTERRUPT,
"Interrupt flag should be set"
);
}
#[test]
fn test_execute_sta_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ZP, 0x42]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x55;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0x55,
"Memory should contain A"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"STA ZP takes 3 cycles"
);
}
#[test]
fn test_execute_sta_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ZPX, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xAA;
cpu.x = 0x05;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0045, false),
0xAA,
"Memory should contain A"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"STA ZPX takes 4 cycles"
);
}
#[test]
fn test_execute_sta_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ABS, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x77;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x77,
"Memory should contain A"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"STA ABS takes 4 cycles"
);
}
#[test]
fn test_execute_sta_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ABSXW, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x88;
cpu.x = 0x10;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1210, false),
0x88,
"Memory should contain A"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"STA ABSXW takes 5 cycles"
);
}
#[test]
fn test_execute_sta_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ABSYW, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x99;
cpu.y = 0x08;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1208, false),
0x99,
"Memory should contain A"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"STA ABSYW takes 5 cycles"
);
}
#[test]
fn test_execute_sta_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_INDX, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xCC;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0x00, false);
cpu.bus.borrow_mut().write(0x0046, 0x12, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0xCC,
"Memory should contain A"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"STA INDX takes 6 cycles"
);
}
#[test]
fn test_execute_sta_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_INDYW, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xDD;
cpu.y = 0x08;
cpu.bus.borrow_mut().write(0x0040, 0x00, false);
cpu.bus.borrow_mut().write(0x0041, 0x12, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1208, false),
0xDD,
"Memory should contain A"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"STA INDYW takes 6 cycles"
);
}
#[test]
fn test_execute_sax_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SAX_ZP, 0x42]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
cpu.x = 0b11001100;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0b11000000,
"Memory should contain A AND X"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"SAX ZP takes 3 cycles"
);
}
#[test]
fn test_execute_sax_zero_page_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SAX_ZPY, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0x55;
cpu.y = 0x05;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0045, false),
0x55,
"Memory should contain A AND X"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"SAX ZPY takes 4 cycles"
);
}
#[test]
fn test_execute_sax_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SAX_ABS, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b10101010;
cpu.x = 0b01010101;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x00,
"Memory should contain A AND X (0x00)"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"SAX ABS takes 4 cycles"
);
}
#[test]
fn test_execute_sax_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SAX_INDX, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0x00, false);
cpu.bus.borrow_mut().write(0x0046, 0x12, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x05,
"Memory should contain A AND X"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"SAX INDX takes 6 cycles"
);
}
#[test]
fn test_execute_sty_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STY_ZP, 0x42]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x66;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0x66,
"Memory should contain Y"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"STY ZP takes 3 cycles"
);
}
#[test]
fn test_execute_sty_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STY_ZPX, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x77;
cpu.x = 0x05;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0045, false),
0x77,
"Memory should contain Y"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"STY ZPX takes 4 cycles"
);
}
#[test]
fn test_execute_sty_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STY_ABS, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x88;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x88,
"Memory should contain Y"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"STY ABS takes 4 cycles"
);
}
#[test]
fn test_execute_stx_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STX_ZP, 0x42]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x99;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0042, false),
0x99,
"Memory should contain X"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"STX ZP takes 3 cycles"
);
}
#[test]
fn test_execute_stx_zero_page_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STX_ZPY, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0xAA;
cpu.y = 0x05;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0045, false),
0xAA,
"Memory should contain X"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"STX ZPY takes 4 cycles"
);
}
#[test]
fn test_execute_stx_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STX_ABS, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0xBB;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0xBB,
"Memory should contain X"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"STX ABS takes 4 cycles"
);
}
#[test]
fn test_execute_dey_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEY]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.y, 0x04, "Y should be decremented");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should be clear");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should be clear");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "DEY takes 2 cycles");
}
#[test]
fn test_execute_dey_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEY]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x01;
cpu.execute();
assert_eq!(cpu.y, 0x00, "Y should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should be clear");
}
#[test]
fn test_execute_dey_wrap() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEY]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x00;
cpu.execute();
assert_eq!(cpu.y, 0xFF, "Y should wrap to 0xFF");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should be clear");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_txa_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TXA]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
cpu.a = 0x00;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x42, "A should be set to X");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should be clear");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should be clear");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "TXA takes 2 cycles");
}
#[test]
fn test_execute_txa_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TXA]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x00;
cpu.a = 0xFF;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_txa_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TXA]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x80;
cpu.a = 0x00;
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_xaa_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![XAA_IMM, 0b11110000]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0b11001100;
cpu.a = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0b11000000, "A should be X AND immediate");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should be clear");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "XAA takes 2 cycles");
}
#[test]
fn test_execute_xaa_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![XAA_IMM, 0x00]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0xFF;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_xaa_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![XAA_IMM, 0xFF]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x80;
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_bcc_not_taken_carry_set() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BCC, 0x10]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let start_pc = cpu.pc;
cpu.p = FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, start_pc + 2, "Branch not taken, PC += 2");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"Branch not taken takes 2 cycles"
);
}
#[test]
fn test_execute_bcc_taken_same_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BCC, 0x10]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let start_pc = cpu.pc;
cpu.p = 0;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.pc,
start_pc.wrapping_add(2).wrapping_add(0x10),
"Branch taken, PC += 2 + offset"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"Branch taken same page takes 3 cycles"
);
}
#[test]
fn test_execute_bcc_taken_cross_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let mut program = vec![0xEA; 0x90]; program.push(BCC);
program.push(0x70);
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
for _ in 0..0x90 {
cpu.execute();
}
cpu.p = 0;
let start_pc = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
let target_pc = start_pc.wrapping_add(2).wrapping_add(0x70);
assert_eq!(cpu.pc, target_pc, "Branch to new page");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"Branch taken cross page takes 4 cycles"
);
}
#[test]
fn test_execute_bcc_backward_branch() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BCC, 0xFE]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let start_pc = cpu.pc;
cpu.p = 0;
cpu.execute();
let offset = -2_i16;
let target_pc = start_pc.wrapping_add(2).wrapping_add_signed(offset);
assert_eq!(cpu.pc, target_pc, "Branch backward");
}
#[test]
fn test_execute_xas_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![XAS_ABSY, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0xFF;
cpu.y = 0x00;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x13,
"Memory should contain A & X & (H+1)"
);
assert_eq!(cpu.total_cycles, initial_cycles + 5, "XAS takes 5 cycles");
}
#[test]
fn test_execute_xas_with_offset() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![XAS_ABSY, 0xF0, 0x11]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0xFF;
cpu.y = 0x08;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x11F8, false),
0x12,
"Memory should contain A & X & (H+1)"
);
}
#[test]
fn test_execute_xas_masking() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![XAS_ABSY, 0x00, 0x02]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0b11110000;
cpu.x = 0b11001100;
cpu.y = 0x00;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0200, false),
0x00,
"Memory should contain masked value"
);
}
#[test]
fn test_execute_tya_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TYA]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
cpu.a = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x42, "A should be set to Y");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should be clear");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should be clear");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "TYA takes 2 cycles");
}
#[test]
fn test_execute_tya_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TYA]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x00;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_tya_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TYA]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x80;
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_txs_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TXS]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
cpu.sp = 0xFF;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.sp, 0x42, "SP should be set to X");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "TXS takes 2 cycles");
}
#[test]
fn test_execute_txs_no_flags() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TXS]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x00;
cpu.p = 0xFF;
let initial_flags = cpu.p;
cpu.execute();
assert_eq!(cpu.sp, 0x00, "SP should be 0");
assert_eq!(cpu.p, initial_flags, "Flags should not change");
}
#[test]
fn test_execute_sya_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SYA_ABSX, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0xFF;
cpu.x = 0x00;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x13,
"Memory should contain Y & (H+1)"
);
assert_eq!(cpu.total_cycles, initial_cycles + 5, "SYA takes 5 cycles");
}
#[test]
fn test_execute_sya_masking() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SYA_ABSX, 0x00, 0x03]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0b11110000;
cpu.x = 0x00;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0300, false),
0x00,
"Memory should contain masked value"
);
}
#[test]
fn test_execute_sya_page_crossing_uses_base_high_plus1_and_modifies_high_byte() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SYA_ABSX, 0xFF, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x01;
cpu.y = 0x0F;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0300, false),
0x03,
"SYA should use base high byte and apply page-crossing high-byte quirk"
);
}
#[test]
fn test_execute_sxa_basic() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SXA_ABSY, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0xFF;
cpu.y = 0x00;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x13,
"Memory should contain X & (H+1)"
);
assert_eq!(cpu.total_cycles, initial_cycles + 5, "SXA takes 5 cycles");
}
#[test]
fn test_execute_sxa_masking() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SXA_ABSY, 0x00, 0x03]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0b11110000;
cpu.y = 0x00;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0300, false),
0x00,
"Memory should contain masked value"
);
}
#[test]
fn test_execute_sxa_page_crossing_uses_base_high_plus1_and_modifies_high_byte() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SXA_ABSY, 0xFF, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x01;
cpu.x = 0x0F;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0300, false),
0x03,
"SXA should use base high byte and apply page-crossing high-byte quirk"
);
}
#[test]
fn test_execute_axa_indirect_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AXA_INDY, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0040, 0x00, false);
cpu.bus.borrow_mut().write(0x0041, 0x12, false);
cpu.a = 0xFF;
cpu.x = 0xFF;
cpu.y = 0x00;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x13,
"Memory should contain A & X & (H+1)"
);
}
#[test]
fn test_execute_axa_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AXA_ABSY, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0xFF;
cpu.y = 0x00;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x1200, false),
0x13,
"Memory should contain A & X & (H+1)"
);
}
#[test]
fn test_execute_ldy_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_IMM, 0x42]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.y, 0x42, "Y should be 0x42");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should be clear");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should be clear");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"LDY IMM takes 2 cycles"
);
}
#[test]
fn test_execute_ldy_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_ZP, 0x42]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0042, 0x99, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.y, 0x99, "Y should be 0x99");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"LDY ZP takes 3 cycles"
);
}
#[test]
fn test_execute_ldy_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_ZPX, 0x40]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0045, 0xAA, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.y, 0xAA, "Y should be 0xAA");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LDY ZPX takes 4 cycles"
);
}
#[test]
fn test_execute_ldy_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_ABS, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1200, 0xBB, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.y, 0xBB, "Y should be 0xBB");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LDY ABS takes 4 cycles"
);
}
#[test]
fn test_execute_ldy_absolute_x_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_ABSX, 0x00, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x1205, 0xCC, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.y, 0xCC, "Y should be 0xCC");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LDY ABSX no page cross takes 4 cycles"
);
}
#[test]
fn test_execute_ldy_absolute_x_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_ABSX, 0xFF, 0x11]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x02; cpu.bus.borrow_mut().write(0x1201, 0xDD, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.y, 0xDD, "Y should be 0xDD");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"LDY ABSX with page cross takes 5 cycles"
);
}
#[test]
fn test_execute_ldy_zero_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_IMM, 0x00]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert_eq!(cpu.y, 0x00, "Y should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_ldy_negative_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDY_IMM, 0x80]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert_eq!(cpu.y, 0x80, "Y should be 0x80");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_tay_positive() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TAY]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.y = 0x00;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.y, 0x42, "Y should equal A");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "TAY takes 2 cycles");
}
#[test]
fn test_execute_tay_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TAY]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x00;
cpu.y = 0xFF;
cpu.execute();
assert_eq!(cpu.y, 0x00, "Y should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_tay_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TAY]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x80;
cpu.y = 0x00;
cpu.execute();
assert_eq!(cpu.y, 0x80, "Y should be 0x80");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_tax_positive() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TAX]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.x = 0x00;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x42, "X should equal A");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "TAX takes 2 cycles");
}
#[test]
fn test_execute_tax_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TAX]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x00;
cpu.x = 0xFF;
cpu.execute();
assert_eq!(cpu.x, 0x00, "X should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_tax_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TAX]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x80;
cpu.x = 0x00;
cpu.execute();
assert_eq!(cpu.x, 0x80, "X should be 0x80");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_lda_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_IMM, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x42, "A should be 0x42");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"LDA IMM takes 2 cycles"
);
}
#[test]
fn test_execute_lda_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0x55, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x55, "A should be 0x55");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"LDA ZP takes 3 cycles"
);
}
#[test]
fn test_execute_lda_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ZPX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0015, 0x66, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x66, "A should be 0x66");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LDA ZPX takes 4 cycles"
);
}
#[test]
fn test_execute_lda_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ABS, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1200, 0x77, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x77, "A should be 0x77");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LDA ABS takes 4 cycles"
);
}
#[test]
fn test_execute_lda_absolute_x_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ABSX, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x1205, 0x88, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x88, "A should be 0x88");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LDA ABSX no page cross takes 4 cycles"
);
}
#[test]
fn test_execute_lda_absolute_x_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ABSX, 0xFF, 0x11];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x1204, 0x99, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x99, "A should be 0x99");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"LDA ABSX page cross takes 5 cycles"
);
}
#[test]
fn test_execute_lda_absolute_y_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ABSY, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x1205, 0xAA, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0xAA, "A should be 0xAA");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LDA ABSY no page cross takes 4 cycles"
);
}
#[test]
fn test_execute_lda_absolute_y_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_ABSY, 0xFF, 0x11];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x1204, 0xBB, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0xBB, "A should be 0xBB");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"LDA ABSY page cross takes 5 cycles"
);
}
#[test]
fn test_execute_lda_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_INDX, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x0024, 0x00, false); cpu.bus.borrow_mut().write(0x0025, 0x13, false); cpu.bus.borrow_mut().write(0x1300, 0xCC, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0xCC, "A should be 0xCC");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"LDA INDX takes 6 cycles"
);
}
#[test]
fn test_execute_lda_indirect_indexed_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_INDY, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x04;
cpu.bus.borrow_mut().write(0x0020, 0x00, false); cpu.bus.borrow_mut().write(0x0021, 0x13, false); cpu.bus.borrow_mut().write(0x1304, 0xDD, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0xDD, "A should be 0xDD");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"LDA INDY no page cross takes 5 cycles"
);
}
#[test]
fn test_execute_lda_indirect_indexed_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_INDY, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0xFF;
cpu.bus.borrow_mut().write(0x0020, 0x10, false); cpu.bus.borrow_mut().write(0x0021, 0x12, false); cpu.bus.borrow_mut().write(0x130F, 0xEE, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0xEE, "A should be 0xEE");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"LDA INDY page cross takes 6 cycles"
);
}
#[test]
fn test_execute_lda_zero_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_IMM, 0x00];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_lda_negative_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDA_IMM, 0x80];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_ldx_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_IMM, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x42, "X should be 0x42");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"LDX IMM takes 2 cycles"
);
}
#[test]
fn test_execute_ldx_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0x55, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x55, "X should be 0x55");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"LDX ZP takes 3 cycles"
);
}
#[test]
fn test_execute_ldx_zero_page_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_ZPY, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x0015, 0x66, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x66, "X should be 0x66");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LDX ZPY takes 4 cycles"
);
}
#[test]
fn test_execute_ldx_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_ABS, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1200, 0x77, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x77, "X should be 0x77");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LDX ABS takes 4 cycles"
);
}
#[test]
fn test_execute_ldx_absolute_y_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_ABSY, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x1205, 0x88, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x88, "X should be 0x88");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LDX ABSY no page cross takes 4 cycles"
);
}
#[test]
fn test_execute_ldx_absolute_y_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_ABSY, 0xFF, 0x11];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x1204, 0x99, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x99, "X should be 0x99");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"LDX ABSY page cross takes 5 cycles"
);
}
#[test]
fn test_execute_ldx_zero_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_IMM, 0x00];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert_eq!(cpu.x, 0x00, "X should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_ldx_negative_flag() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LDX_IMM, 0x80];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert_eq!(cpu.x, 0x80, "X should be 0x80");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_lax_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x42, "A should be 0x42");
assert_eq!(cpu.x, 0x42, "X should be 0x42");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"LAX ZP takes 3 cycles"
);
}
#[test]
fn test_execute_lax_zero_page_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_ZPY, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x0015, 0x55, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x55, "A should be 0x55");
assert_eq!(cpu.x, 0x55, "X should be 0x55");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LAX ZPY takes 4 cycles"
);
}
#[test]
fn test_execute_lax_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_ABS, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x1200, 0x66, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x66, "A should be 0x66");
assert_eq!(cpu.x, 0x66, "X should be 0x66");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LAX ABS takes 4 cycles"
);
}
#[test]
fn test_execute_lax_absolute_y_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_ABSY, 0x00, 0x12];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x1205, 0x77, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x77, "A should be 0x77");
assert_eq!(cpu.x, 0x77, "X should be 0x77");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LAX ABSY no page cross takes 4 cycles"
);
}
#[test]
fn test_execute_lax_absolute_y_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_ABSY, 0xFF, 0x11];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x05;
cpu.bus.borrow_mut().write(0x1204, 0x88, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x88, "A should be 0x88");
assert_eq!(cpu.x, 0x88, "X should be 0x88");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"LAX ABSY page cross takes 5 cycles"
);
}
#[test]
fn test_execute_lax_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_INDX, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x04;
cpu.bus.borrow_mut().write(0x0024, 0x00, false); cpu.bus.borrow_mut().write(0x0025, 0x13, false); cpu.bus.borrow_mut().write(0x1300, 0x99, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x99, "A should be 0x99");
assert_eq!(cpu.x, 0x99, "X should be 0x99");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"LAX INDX takes 6 cycles"
);
}
#[test]
fn test_execute_lax_indirect_indexed_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_INDY, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x04;
cpu.bus.borrow_mut().write(0x0020, 0x00, false); cpu.bus.borrow_mut().write(0x0021, 0x13, false); cpu.bus.borrow_mut().write(0x1304, 0xAA, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0xAA, "A should be 0xAA");
assert_eq!(cpu.x, 0xAA, "X should be 0xAA");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"LAX INDY no page cross takes 5 cycles"
);
}
#[test]
fn test_execute_lax_flags_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0x00, false);
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.x, 0x00, "X should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_lax_flags_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAX_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0x80, false);
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(cpu.x, 0x80, "X should be 0x80");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_clv() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CLV];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_OVERFLOW;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_OVERFLOW, 0, "Overflow flag should be cleared");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "CLV takes 2 cycles");
}
#[test]
fn test_execute_clv_already_clear() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CLV];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_OVERFLOW;
cpu.execute();
assert_eq!(
cpu.p & FLAG_OVERFLOW,
0,
"Overflow flag should remain clear"
);
}
#[test]
fn test_execute_tsx_positive() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TSX];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0x42;
cpu.x = 0x00;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x42, "X should equal SP");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "TSX takes 2 cycles");
}
#[test]
fn test_execute_tsx_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TSX];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0x00;
cpu.x = 0xFF;
cpu.execute();
assert_eq!(cpu.x, 0x00, "X should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_tsx_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![TSX];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0x80;
cpu.x = 0x00;
cpu.execute();
assert_eq!(cpu.x, 0x80, "X should be 0x80");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_bcs_taken_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BCS, 0x05]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_CARRY;
let pc_before = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.pc,
pc_before + 2 + 0x05,
"PC should advance past instruction then branch forward 5 bytes"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"BCS taken no page cross takes 3 cycles"
);
}
#[test]
fn test_execute_bcs_taken_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let mut program = vec![0xEA; 128]; program.push(BCS); program.push(0x7F);
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
for _ in 0..128 {
cpu.execute();
}
cpu.p |= FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"BCS taken with page cross takes 4 cycles"
);
}
#[test]
fn test_execute_bcs_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BCS, 0x05];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_CARRY;
let pc_before = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, pc_before + 2, "PC should advance past instruction");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"BCS not taken takes 2 cycles"
);
}
#[test]
fn test_execute_bcs_backward() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BCS, 0xFE]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_CARRY;
let pc_before = cpu.pc;
cpu.execute();
assert_eq!(
cpu.pc,
pc_before.wrapping_add(2).wrapping_add((-2i8) as u16),
"PC should branch backward 2 bytes"
);
}
#[test]
fn test_execute_atx_positive() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ATX_IMM, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF; cpu.x = 0x00;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x42, "A should be 0x42");
assert_eq!(cpu.x, 0x42, "X should be 0x42");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"ATX IMM takes 2 cycles"
);
}
#[test]
fn test_execute_atx_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ATX_IMM, 0x00];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0xFF;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.x, 0x00, "X should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_atx_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ATX_IMM, 0x80];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0x00;
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(cpu.x, 0x80, "X should be 0x80");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_iny_normal() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INY];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.y, 0x43, "Y should be incremented to 0x43");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "INY takes 2 cycles");
}
#[test]
fn test_execute_iny_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INY];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0xFF;
cpu.execute();
assert_eq!(cpu.y, 0x00, "Y should wrap to 0x00");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_iny_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INY];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x7F;
cpu.execute();
assert_eq!(cpu.y, 0x80, "Y should be 0x80");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_cpy_immediate_equal() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPY_IMM, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"CPY IMM takes 2 cycles"
);
}
#[test]
fn test_execute_cpy_immediate_greater() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPY_IMM, 0x30];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_cpy_immediate_less() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPY_IMM, 0x50];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_cpy_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPY_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
cpu.bus.borrow_mut().write(0x0010, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"CPY ZP takes 3 cycles"
);
}
#[test]
fn test_execute_cpy_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPY_ABS, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.y = 0x42;
cpu.bus.borrow_mut().write(0x2000, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"CPY ABS takes 4 cycles"
);
}
#[test]
fn test_execute_cmp_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_IMM, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"CMP IMM takes 2 cycles"
);
}
#[test]
fn test_execute_cmp_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.bus.borrow_mut().write(0x0010, 0x40, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"CMP ZP takes 3 cycles"
);
}
#[test]
fn test_execute_cmp_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_ZPX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0015, 0x40, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"CMP ZPX takes 4 cycles"
);
}
#[test]
fn test_execute_cmp_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_ABS, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.bus.borrow_mut().write(0x2000, 0x40, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"CMP ABS takes 4 cycles"
);
}
#[test]
fn test_execute_cmp_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_ABSX, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x2010, 0x40, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"CMP ABSX no page cross takes 4 cycles"
);
}
#[test]
fn test_execute_cmp_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_ABSY, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x2010, 0x40, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"CMP ABSY no page cross takes 4 cycles"
);
}
#[test]
fn test_execute_cmp_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_INDX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0015, 0x00, false);
cpu.bus.borrow_mut().write(0x0016, 0x30, false);
cpu.bus.borrow_mut().write(0x3000, 0x40, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"CMP INDX takes 6 cycles"
);
}
#[test]
fn test_execute_cmp_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CMP_INDY, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x0010, 0x00, false);
cpu.bus.borrow_mut().write(0x0011, 0x30, false);
cpu.bus.borrow_mut().write(0x3010, 0x40, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"CMP INDY no page cross takes 5 cycles"
);
}
#[test]
fn test_execute_dcp_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DCP_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.bus.borrow_mut().write(0x0010, 0x43, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0010, false),
0x42,
"Memory should be decremented to 0x42"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"DCP ZP takes 5 cycles"
);
}
#[test]
fn test_execute_dcp_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DCP_ZPX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0015, 0x43, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0015, false),
0x42,
"Memory should be decremented to 0x42"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"DCP ZPX takes 6 cycles"
);
}
#[test]
fn test_execute_dcp_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DCP_ABS, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.bus.borrow_mut().write(0x2000, 0x43, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x2000, false),
0x42,
"Memory should be decremented to 0x42"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"DCP ABS takes 6 cycles"
);
}
#[test]
fn test_execute_dcp_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DCP_ABSXW, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.x = 0x10;
cpu.bus.borrow_mut().write(0x2010, 0x43, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x2010, false),
0x42,
"Memory should be decremented to 0x42"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"DCP ABSXW takes 7 cycles"
);
}
#[test]
fn test_execute_dcp_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DCP_ABSYW, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x2010, 0x43, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x2010, false),
0x42,
"Memory should be decremented to 0x42"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"DCP ABSYW takes 7 cycles"
);
}
#[test]
fn test_execute_dcp_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DCP_INDX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0015, 0x00, false);
cpu.bus.borrow_mut().write(0x0016, 0x30, false);
cpu.bus.borrow_mut().write(0x3000, 0x43, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x3000, false),
0x42,
"Memory should be decremented to 0x42"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 8,
"DCP INDX takes 8 cycles"
);
}
#[test]
fn test_execute_dcp_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DCP_INDYW, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x0010, 0x00, false);
cpu.bus.borrow_mut().write(0x0011, 0x30, false);
cpu.bus.borrow_mut().write(0x3010, 0x43, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x3010, false),
0x42,
"Memory should be decremented to 0x42"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 8,
"DCP INDYW takes 8 cycles"
);
}
#[test]
fn test_execute_lar_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAR_ABSY, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFF;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x2010, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
let expected = 0x42;
assert_eq!(cpu.a, expected, "A should be SP & memory");
assert_eq!(cpu.x, expected, "X should be SP & memory");
assert_eq!(cpu.sp, expected, "SP should be SP & memory");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"LAR ABSY no page cross takes 4 cycles"
);
}
#[test]
fn test_execute_lar_absolute_y_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAR_ABSY, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFF;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x2010, 0x00, false);
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0");
assert_eq!(cpu.x, 0x00, "X should be 0");
assert_eq!(cpu.sp, 0x00, "SP should be 0");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
}
#[test]
fn test_execute_lar_absolute_y_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![LAR_ABSY, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.sp = 0xFF;
cpu.y = 0x10;
cpu.bus.borrow_mut().write(0x2010, 0x80, false);
cpu.execute();
assert_eq!(cpu.a, 0x80, "A should be 0x80");
assert_eq!(cpu.x, 0x80, "X should be 0x80");
assert_eq!(cpu.sp, 0x80, "SP should be 0x80");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_dex_normal() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEX];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x41, "X should be decremented to 0x41");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "DEX takes 2 cycles");
}
#[test]
fn test_execute_dex_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEX];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x01;
cpu.execute();
assert_eq!(cpu.x, 0x00, "X should be 0x00");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_dex_wrap() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEX];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x00;
cpu.execute();
assert_eq!(cpu.x, 0xFF, "X should wrap to 0xFF");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_cpx_immediate_equal() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPX_IMM, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"CPX IMM takes 2 cycles"
);
}
#[test]
fn test_execute_cpx_immediate_greater() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPX_IMM, 0x30];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_cpx_immediate_less() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPX_IMM, 0x50];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_cpx_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPX_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
cpu.bus.borrow_mut().write(0x0010, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"CPX ZP takes 3 cycles"
);
}
#[test]
fn test_execute_cpx_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CPX_ABS, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
cpu.bus.borrow_mut().write(0x2000, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"CPX ABS takes 4 cycles"
);
}
#[test]
fn test_execute_cld() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CLD];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_DECIMAL;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.p & FLAG_DECIMAL, 0, "Decimal flag should be cleared");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "CLD takes 2 cycles");
}
#[test]
fn test_execute_cld_already_clear() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![CLD];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_DECIMAL;
cpu.execute();
assert_eq!(
cpu.p & FLAG_DECIMAL,
0,
"Decimal flag should remain cleared"
);
}
#[test]
fn test_execute_bne_taken_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BNE, 0x05]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_ZERO;
let pc_before = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.pc,
pc_before + 2 + 0x05,
"PC should advance past instruction then branch forward 5 bytes"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"BNE taken no page cross takes 3 cycles"
);
}
#[test]
fn test_execute_bne_taken_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let mut program = vec![0xEA; 128]; program.push(BNE); program.push(0x7F);
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
for _ in 0..128 {
cpu.execute();
}
cpu.p &= !FLAG_ZERO;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"BNE taken with page cross takes 4 cycles"
);
}
#[test]
fn test_execute_bne_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BNE, 0x05];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_ZERO;
let pc_before = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, pc_before + 2, "PC should advance past instruction");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"BNE not taken takes 2 cycles"
);
}
#[test]
fn test_execute_bne_backward() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BNE, 0xFE]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_ZERO;
let pc_before = cpu.pc;
cpu.execute();
assert_eq!(
cpu.pc,
pc_before.wrapping_add(2).wrapping_add((-2i8) as u16),
"PC should branch backward 2 bytes"
);
}
#[test]
fn test_execute_axs_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AXS_IMM, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0x50;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x40, "X should be 0x40");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"AXS IMM takes 2 cycles"
);
}
#[test]
fn test_execute_axs_immediate_borrow() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AXS_IMM, 0x50];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0x30;
cpu.execute();
assert_eq!(cpu.x, 0xE0, "X should wrap to 0xE0");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should be clear (borrow)");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_axs_immediate_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![AXS_IMM, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xFF;
cpu.x = 0x42;
cpu.execute();
assert_eq!(cpu.x, 0x00, "X should be 0x00");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
}
#[test]
fn test_execute_inx_normal() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INX];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x42;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.x, 0x43, "X should be incremented to 0x43");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(cpu.total_cycles, initial_cycles + 2, "INX takes 2 cycles");
}
#[test]
fn test_execute_inx_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INX];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0xFF;
cpu.execute();
assert_eq!(cpu.x, 0x00, "X should wrap to 0x00");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_inx_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INX];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x7F;
cpu.execute();
assert_eq!(cpu.x, 0x80, "X should be 0x80");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_beq_taken_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BEQ, 0x05]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_ZERO;
let pc_before = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.pc,
pc_before + 2 + 0x05,
"PC should advance past instruction then branch forward 5 bytes"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"BEQ taken no page cross takes 3 cycles"
);
}
#[test]
fn test_execute_beq_taken_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let mut program = vec![0xEA; 128]; program.push(BEQ); program.push(0x7F);
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
for _ in 0..128 {
cpu.execute();
}
cpu.p |= FLAG_ZERO;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"BEQ taken with page cross takes 4 cycles"
);
}
#[test]
fn test_execute_beq_not_taken() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BEQ, 0x05];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_ZERO;
let pc_before = cpu.pc;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.pc, pc_before + 2, "PC should advance past instruction");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"BEQ not taken takes 2 cycles"
);
}
#[test]
fn test_execute_beq_backward() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![BEQ, 0xFE]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_ZERO;
let pc_before = cpu.pc;
cpu.execute();
assert_eq!(
cpu.pc,
pc_before.wrapping_add(2).wrapping_add((-2i8) as u16),
"PC should branch backward 2 bytes"
);
}
#[test]
fn test_execute_sbc_immediate_no_borrow() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_IMM, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.p |= FLAG_CARRY;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(cpu.p & FLAG_OVERFLOW, 0, "Overflow flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 2,
"SBC IMM takes 2 cycles"
);
}
#[test]
fn test_execute_sbc_immediate_with_borrow() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_IMM, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.p &= !FLAG_CARRY;
cpu.execute();
assert_eq!(cpu.a, 0x3F, "A should be 0x3F");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
}
#[test]
fn test_execute_sbc_immediate_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_IMM, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x42;
cpu.p |= FLAG_CARRY;
cpu.execute();
assert_eq!(cpu.a, 0x00, "A should be 0x00");
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_CARRY, FLAG_CARRY, "Carry flag should be set");
}
#[test]
fn test_execute_sbc_immediate_underflow() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_IMM, 0x50];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x30;
cpu.p |= FLAG_CARRY;
cpu.execute();
assert_eq!(cpu.a, 0xE0, "A should wrap to 0xE0");
assert_eq!(cpu.p & FLAG_CARRY, 0, "Carry flag should be clear");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_sbc_immediate_overflow_positive() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_IMM, 0x80];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.p |= FLAG_CARRY;
cpu.execute();
assert_eq!(cpu.a, 0xD0, "A should be 0xD0");
assert_eq!(
cpu.p & FLAG_OVERFLOW,
FLAG_OVERFLOW,
"Overflow flag should be set"
);
}
#[test]
fn test_execute_sbc_immediate_overflow_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_IMM, 0x01];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x80;
cpu.p |= FLAG_CARRY;
cpu.execute();
assert_eq!(cpu.a, 0x7F, "A should be 0x7F");
assert_eq!(
cpu.p & FLAG_OVERFLOW,
FLAG_OVERFLOW,
"Overflow flag should be set"
);
}
#[test]
fn test_execute_sbc_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0010, 0x10, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 3,
"SBC ZP takes 3 cycles"
);
}
#[test]
fn test_execute_sbc_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ZPX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0015, 0x10, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"SBC ZPX takes 4 cycles"
);
}
#[test]
fn test_execute_sbc_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ABS, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x2000, 0x10, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"SBC ABS takes 4 cycles"
);
}
#[test]
fn test_execute_sbc_absolute_x_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ABSX, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x2005, 0x10, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"SBC ABSX no page cross takes 4 cycles"
);
}
#[test]
fn test_execute_sbc_absolute_x_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ABSX, 0xFF, 0x01];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0204, 0x10, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"SBC ABSX with page cross takes 5 cycles"
);
}
#[test]
fn test_execute_sbc_absolute_y_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ABSY, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.y = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x2005, 0x10, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 4,
"SBC ABSY no page cross takes 4 cycles"
);
}
#[test]
fn test_execute_sbc_absolute_y_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_ABSY, 0xFF, 0x01];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.y = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0204, 0x10, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"SBC ABSY with page cross takes 5 cycles"
);
}
#[test]
fn test_execute_sbc_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_INDX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0015, 0x00, false);
cpu.bus.borrow_mut().write(0x0016, 0x20, false);
cpu.bus.borrow_mut().write(0x2000, 0x10, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"SBC INDX takes 6 cycles"
);
}
#[test]
fn test_execute_sbc_indirect_indexed_no_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_INDY, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.y = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0010, 0x00, false);
cpu.bus.borrow_mut().write(0x0011, 0x20, false);
cpu.bus.borrow_mut().write(0x2005, 0x10, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"SBC INDY no page cross takes 5 cycles"
);
}
#[test]
fn test_execute_sbc_indirect_indexed_page_cross() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SBC_INDY, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.y = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0010, 0xFF, false);
cpu.bus.borrow_mut().write(0x0011, 0x01, false);
cpu.bus.borrow_mut().write(0x0204, 0x10, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"SBC INDY with page cross takes 6 cycles"
);
}
#[test]
fn test_execute_isb_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ISB_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0010, 0x0F, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0010, false),
0x10,
"Memory should be incremented to 0x10"
);
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"ISB ZP takes 5 cycles"
);
}
#[test]
fn test_execute_isb_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ISB_ZPX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0015, 0x0F, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0015, false),
0x10,
"Memory should be incremented to 0x10"
);
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"ISB ZPX takes 6 cycles"
);
}
#[test]
fn test_execute_isb_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ISB_ABS, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x2000, 0x0F, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x2000, false),
0x10,
"Memory should be incremented to 0x10"
);
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"ISB ABS takes 6 cycles"
);
}
#[test]
fn test_execute_isb_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ISB_ABSXW, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x2005, 0x0F, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x2005, false),
0x10,
"Memory should be incremented to 0x10"
);
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"ISB ABSXW takes 7 cycles"
);
}
#[test]
fn test_execute_isb_absolute_y() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ISB_ABSYW, 0x00, 0x20];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.y = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x2005, 0x0F, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x2005, false),
0x10,
"Memory should be incremented to 0x10"
);
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"ISB ABSYW takes 7 cycles"
);
}
#[test]
fn test_execute_isb_indexed_indirect() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ISB_INDX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.x = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0015, 0x00, false);
cpu.bus.borrow_mut().write(0x0016, 0x20, false);
cpu.bus.borrow_mut().write(0x2000, 0x0F, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x2000, false),
0x10,
"Memory should be incremented to 0x10"
);
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 8,
"ISB INDX takes 8 cycles"
);
}
#[test]
fn test_execute_isb_indirect_indexed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![ISB_INDYW, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0x50;
cpu.y = 0x05;
cpu.p |= FLAG_CARRY;
cpu.bus.borrow_mut().write(0x0010, 0x00, false);
cpu.bus.borrow_mut().write(0x0011, 0x20, false);
cpu.bus.borrow_mut().write(0x2005, 0x0F, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x2005, false),
0x10,
"Memory should be incremented to 0x10"
);
assert_eq!(cpu.a, 0x40, "A should be 0x40");
assert_eq!(
cpu.total_cycles,
initial_cycles + 8,
"ISB INDYW takes 8 cycles"
);
}
#[test]
fn test_execute_sed() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SED];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p &= !FLAG_DECIMAL;
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.p & FLAG_DECIMAL,
FLAG_DECIMAL,
"Decimal flag should be set"
);
assert_eq!(cpu.total_cycles, initial_cycles + 2, "SED takes 2 cycles");
}
#[test]
fn test_execute_sed_already_set() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![SED];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.p |= FLAG_DECIMAL;
cpu.execute();
assert_eq!(
cpu.p & FLAG_DECIMAL,
FLAG_DECIMAL,
"Decimal flag should remain set"
);
}
#[test]
fn test_execute_inc_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0010, false),
0x43,
"Memory should be incremented to 0x43"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"INC ZP takes 5 cycles"
);
}
#[test]
fn test_execute_inc_zero_page_wrap() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0xFF, false);
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0010, false),
0x00,
"Memory should wrap to 0x00"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_inc_zero_page_negative() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0x7F, false);
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0010, false),
0x80,
"Memory should be 0x80"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_inc_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ZPX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0015, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0015, false),
0x43,
"Memory should be incremented to 0x43"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"INC ZPX takes 6 cycles"
);
}
#[test]
fn test_execute_inc_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ABS, 0x00, 0x02];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0200, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0200, false),
0x43,
"Memory should be incremented to 0x43"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"INC ABS takes 6 cycles"
);
}
#[test]
fn test_execute_inc_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![INC_ABSXW, 0x00, 0x02];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0205, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0205, false),
0x43,
"Memory should be incremented to 0x43"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"INC ABSXW takes 7 cycles"
);
}
#[test]
fn test_execute_dec_zero_page() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0010, false),
0x41,
"Memory should be decremented to 0x41"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
assert_eq!(
cpu.total_cycles,
initial_cycles + 5,
"DEC ZP takes 5 cycles"
);
}
#[test]
fn test_execute_dec_zero_page_zero() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0x01, false);
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0010, false),
0x00,
"Memory should be 0x00"
);
assert_eq!(cpu.p & FLAG_ZERO, FLAG_ZERO, "Zero flag should be set");
assert_eq!(cpu.p & FLAG_NEGATIVE, 0, "Negative flag should not be set");
}
#[test]
fn test_execute_dec_zero_page_wrap() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ZP, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0010, 0x00, false);
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0010, false),
0xFF,
"Memory should wrap to 0xFF"
);
assert_eq!(cpu.p & FLAG_ZERO, 0, "Zero flag should not be set");
assert_eq!(
cpu.p & FLAG_NEGATIVE,
FLAG_NEGATIVE,
"Negative flag should be set"
);
}
#[test]
fn test_execute_dec_zero_page_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ZPX, 0x10];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0015, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0015, false),
0x41,
"Memory should be decremented to 0x41"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"DEC ZPX takes 6 cycles"
);
}
#[test]
fn test_execute_dec_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ABS, 0x00, 0x02];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.bus.borrow_mut().write(0x0200, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0200, false),
0x41,
"Memory should be decremented to 0x41"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 6,
"DEC ABS takes 6 cycles"
);
}
#[test]
fn test_execute_dec_absolute_x() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![DEC_ABSXW, 0x00, 0x02];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.x = 0x05;
cpu.bus.borrow_mut().write(0x0205, 0x42, false);
let initial_cycles = cpu.total_cycles;
cpu.execute();
assert_eq!(
cpu.bus.borrow_mut().read(0x0205, false),
0x41,
"Memory should be decremented to 0x41"
);
assert_eq!(
cpu.total_cycles,
initial_cycles + 7,
"DEC ABSXW takes 7 cycles"
);
}
#[test]
fn test_last_cpu_write_addr_is_none_initially() {
let (ppu, apu, memory) = create_test_memory();
let cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
assert_eq!(cpu.last_cpu_write_addr(), None);
}
#[test]
fn test_last_cpu_write_addr_is_set_after_sta_absolute() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ABS, 0x34, 0x12]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xAB;
cpu.execute();
assert_eq!(cpu.last_cpu_write_addr(), Some(0x1234));
}
#[test]
fn test_last_cpu_write_addr_is_none_after_lda_immediate() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![opcode::LDA_IMM, 0x42]; fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.execute();
assert_eq!(cpu.last_cpu_write_addr(), None);
}
#[test]
fn test_last_cpu_write_addr_is_cleared_before_next_instruction() {
let (ppu, apu, memory) = create_test_memory();
let mut cpu = Cpu::new(TimingMode::Ntsc, memory, ppu, apu);
let program = vec![STA_ABS, 0x34, 0x12, opcode::LDA_IMM, 0x42];
fake_cartridge(&mut cpu, &program);
cpu.reset(true);
cpu.a = 0xAB;
cpu.execute(); assert_eq!(cpu.last_cpu_write_addr(), Some(0x1234));
cpu.execute(); assert_eq!(cpu.last_cpu_write_addr(), None);
}
}