use crate::Variant;
use crate::instruction::{AddressingMode, DecodedInstr, Instruction, OpInput};
use crate::memory::{
Bus, IRQ_INTERRUPT_VECTOR_LO, NMI_INTERRUPT_VECTOR_LO, RESET_VECTOR_HI, RESET_VECTOR_LO,
};
use crate::registers::{Registers, StackPointer, Status, StatusArgs};
fn address_from_bytes(lo: u8, hi: u8) -> u16 {
u16::from(lo) + (u16::from(hi) << 8usize)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
enum WaitState {
#[default]
Running,
WaitingForInterrupt,
WaitingForReset,
}
#[derive(Clone, Default)]
pub struct CPU<M, V>
where
M: Bus,
V: Variant,
{
pub registers: Registers,
pub memory: M,
pub cycles: u64,
wait_state: WaitState,
last_nmi_state: bool,
variant: core::marker::PhantomData<V>,
}
impl<M: Bus, V: Variant> CPU<M, V> {
#[allow(clippy::needless_pass_by_value)]
pub fn new(memory: M, _variant: V) -> CPU<M, V> {
CPU {
registers: Registers::new(),
memory,
cycles: 0,
variant: core::marker::PhantomData::<V>,
wait_state: WaitState::Running,
last_nmi_state: false,
}
}
pub fn reset(&mut self) {
self.wait_state = WaitState::Running;
self.last_nmi_state = false;
self.registers.stack_pointer.decrement();
self.registers.stack_pointer.decrement();
self.registers.stack_pointer.decrement();
self.registers.status.insert(Status::PS_DISABLE_INTERRUPTS);
let reset_vector_low = self.memory.get_byte(RESET_VECTOR_LO);
let reset_vector_high = self.memory.get_byte(RESET_VECTOR_HI);
self.registers.program_counter = u16::from_le_bytes([reset_vector_low, reset_vector_high]);
}
pub fn fetch_next_and_decode(&mut self) -> Option<DecodedInstr> {
fn read_address<M: Bus>(mem: &mut M, addr: u16) -> [u8; 2] {
let lo = mem.get_byte(addr);
let hi = mem.get_byte(addr.wrapping_add(1));
[lo, hi]
}
let x: u8 = self.memory.get_byte(self.registers.program_counter);
match V::decode(x) {
Some((instr, am)) => {
let extra_bytes = am.extra_bytes();
let num_bytes = extra_bytes + 1;
let data_start = self.registers.program_counter.wrapping_add(1);
let slice = if extra_bytes == 0 {
[0, 0]
} else if extra_bytes == 1 {
[self.memory.get_byte(data_start), 0]
} else if extra_bytes == 2 {
[
self.memory.get_byte(data_start),
self.memory.get_byte(data_start.wrapping_add(1)),
]
} else {
panic!()
};
let x = self.registers.index_x;
let y = self.registers.index_y;
let memory = &mut self.memory;
let am_out = match am {
AddressingMode::Accumulator | AddressingMode::Implied => {
OpInput::UseImplied
}
AddressingMode::Immediate => {
OpInput::UseImmediate(slice[0])
}
AddressingMode::ZeroPage => {
OpInput::UseAddress {
address: u16::from(slice[0]),
page_crossed: false,
}
}
AddressingMode::ZeroPageX => {
OpInput::UseAddress {
address: u16::from(slice[0].wrapping_add(x)),
page_crossed: false,
}
}
AddressingMode::ZeroPageY => {
OpInput::UseAddress {
address: u16::from(slice[0].wrapping_add(y)),
page_crossed: false,
}
}
AddressingMode::Relative => {
let offset = slice[0];
let sign_extend = if offset & 0x80 == 0x80 { 0xffu8 } else { 0x0 };
let rel = u16::from_le_bytes([offset, sign_extend]);
OpInput::UseRelative(rel)
}
AddressingMode::Absolute => {
OpInput::UseAddress {
address: address_from_bytes(slice[0], slice[1]),
page_crossed: false,
}
}
AddressingMode::AbsoluteX => {
let base = address_from_bytes(slice[0], slice[1]);
let final_addr = base.wrapping_add(x.into());
let crossed = (base & 0xFF00) != (final_addr & 0xFF00);
OpInput::UseAddress {
address: final_addr,
page_crossed: crossed,
}
}
AddressingMode::AbsoluteY => {
let base = address_from_bytes(slice[0], slice[1]);
let final_addr = base.wrapping_add(y.into());
let crossed = (base & 0xFF00) != (final_addr & 0xFF00);
OpInput::UseAddress {
address: final_addr,
page_crossed: crossed,
}
}
AddressingMode::Indirect => {
let slice = read_address(memory, address_from_bytes(slice[0], slice[1]));
OpInput::UseAddress {
address: address_from_bytes(slice[0], slice[1]),
page_crossed: false,
}
}
AddressingMode::BuggyIndirect => {
let pointer = address_from_bytes(slice[0], slice[1]);
let low_byte_of_target = memory.get_byte(pointer);
let low_byte_of_incremented_pointer =
pointer.to_le_bytes()[0].wrapping_add(1);
let incremented_pointer = u16::from_le_bytes([
low_byte_of_incremented_pointer,
pointer.to_le_bytes()[1],
]);
let high_byte_of_target = memory.get_byte(incremented_pointer);
OpInput::UseAddress {
address: address_from_bytes(low_byte_of_target, high_byte_of_target),
page_crossed: false,
}
}
AddressingMode::AbsoluteIndexedIndirect => {
let x: u8 = self.registers.index_x;
let base_addr = address_from_bytes(slice[0], slice[1]);
let pointer = base_addr.wrapping_add(u16::from(x));
let slice = read_address(memory, pointer);
OpInput::UseAddress {
address: address_from_bytes(slice[0], slice[1]),
page_crossed: false,
}
}
AddressingMode::IndexedIndirectX => {
let start = slice[0].wrapping_add(x);
let slice = read_address(memory, u16::from(start));
OpInput::UseAddress {
address: address_from_bytes(slice[0], slice[1]),
page_crossed: false,
}
}
AddressingMode::IndirectIndexedY => {
let start = slice[0];
let slice = read_address(memory, u16::from(start));
let base = address_from_bytes(slice[0], slice[1]);
let final_addr = base.wrapping_add(y.into());
let crossed = (base & 0xFF00) != (final_addr & 0xFF00);
OpInput::UseAddress {
address: final_addr,
page_crossed: crossed,
}
}
AddressingMode::ZeroPageIndirect => {
let start = slice[0];
let slice = read_address(memory, u16::from(start));
OpInput::UseAddress {
address: address_from_bytes(slice[0], slice[1]),
page_crossed: false,
}
}
AddressingMode::ZeroPageRelative => {
let zp_address = slice[0];
let offset = slice[1];
let sign_extend = if offset & 0x80 == 0x80 { 0xffu8 } else { 0x00 };
let relative = u16::from_le_bytes([offset, sign_extend]);
OpInput::UseBitBranch {
zp_address,
relative,
}
}
};
self.registers.program_counter =
self.registers.program_counter.wrapping_add(num_bytes);
Some((instr, am, am_out))
}
_ => None,
}
}
fn calculate_instruction_cycles(
instr: Instruction,
mode: AddressingMode,
page_crossed: bool,
registers: Registers,
) -> u8 {
use Instruction::{STA, STX, STY, STZ};
let base_cycles = instr.base_cycles(mode);
let page_penalty = u8::from(page_crossed && !matches!(instr, STA | STX | STY | STZ));
let decimal_penalty = Self::decimal_mode_penalty_for_variant(instr, registers);
base_cycles + page_penalty + decimal_penalty
}
fn decimal_mode_penalty_for_variant(instr: Instruction, registers: Registers) -> u8 {
use Instruction::{ADC, SBC};
if !matches!(instr, ADC | SBC) {
return 0;
}
if !registers.status.contains(Status::PS_DECIMAL_MODE) {
return 0;
}
V::penalty_cycles_for_decimal_mode()
}
#[allow(clippy::too_many_lines)]
pub fn execute_instruction(&mut self, decoded_instr: DecodedInstr) {
let (instr, mode, operand) = decoded_instr;
let total_cycles =
Self::calculate_instruction_cycles(instr, mode, operand.page_crossed(), self.registers);
self.cycles = self.cycles.wrapping_add(u64::from(total_cycles));
match (instr, operand) {
(Instruction::ADC, OpInput::UseImmediate(val)) => {
log::debug!("add with carry immediate: {val}");
self.add_with_carry(val);
}
(Instruction::ADC, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
log::debug!("add with carry. address: {addr:?}. value: {val}");
self.add_with_carry(val);
}
(Instruction::ADCnd, OpInput::UseImmediate(val)) => {
log::debug!("add with carry immediate: {val}");
self.add_with_no_decimal(val);
}
(Instruction::ADCnd, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
log::debug!("add with carry. address: {addr:?}. value: {val}");
self.add_with_no_decimal(val);
}
(Instruction::AND, OpInput::UseImmediate(val)) => {
self.and(val);
}
(Instruction::AND, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.and(val);
}
(Instruction::ASL, OpInput::UseImplied) => {
let mut val = self.registers.accumulator;
CPU::<M, V>::shift_left_with_flags(&mut val, &mut self.registers.status);
self.registers.accumulator = val;
}
(Instruction::ASL, OpInput::UseAddress { address: addr, .. }) => {
let mut operand: u8 = self.memory.get_byte(addr);
CPU::<M, V>::shift_left_with_flags(&mut operand, &mut self.registers.status);
self.memory.set_byte(addr, operand);
}
(Instruction::BCC, OpInput::UseRelative(rel)) => {
let addr = self.registers.program_counter.wrapping_add(rel);
self.branch_if_carry_clear(addr);
}
(Instruction::BCS, OpInput::UseRelative(rel)) => {
let addr = self.registers.program_counter.wrapping_add(rel);
self.branch_if_carry_set(addr);
}
(Instruction::BEQ, OpInput::UseRelative(rel)) => {
let addr = self.registers.program_counter.wrapping_add(rel);
self.branch_if_equal(addr);
}
(Instruction::BNE, OpInput::UseRelative(rel)) => {
let addr = self.registers.program_counter.wrapping_add(rel);
self.branch_if_not_equal(addr);
}
(Instruction::BIT, OpInput::UseImmediate(val)) => {
self.registers.status.set_with_mask(
Status::PS_ZERO,
Status::new(StatusArgs {
zero: 0 == (self.registers.accumulator & val),
..StatusArgs::none()
}),
);
}
(Instruction::BIT, OpInput::UseAddress { address: addr, .. }) => {
let a: u8 = self.registers.accumulator;
let m: u8 = self.memory.get_byte(addr);
let res = a & m;
let is_zero = 0 == res;
let bit7 = 0 != (0x80 & m);
let bit6 = 0 != (0x40 & m);
self.registers.status.set_with_mask(
Status::PS_ZERO | Status::PS_NEGATIVE | Status::PS_OVERFLOW,
Status::new(StatusArgs {
zero: is_zero,
negative: bit7,
overflow: bit6,
..StatusArgs::none()
}),
);
}
(Instruction::BMI, OpInput::UseRelative(rel)) => {
let addr = self.registers.program_counter.wrapping_add(rel);
log::debug!("branch if minus relative. address: {addr:?}");
self.branch_if_minus(addr);
}
(Instruction::BPL, OpInput::UseRelative(rel)) => {
let addr = self.registers.program_counter.wrapping_add(rel);
self.branch_if_positive(addr);
}
(Instruction::BRA, OpInput::UseRelative(rel)) => {
let addr = self.registers.program_counter.wrapping_add(rel);
self.branch(addr);
}
(
Instruction::BBR(bit),
OpInput::UseBitBranch {
zp_address,
relative,
},
) => {
let val = self.memory.get_byte(u16::from(zp_address));
if val & (1 << bit) == 0 {
let addr = self.registers.program_counter.wrapping_add(relative);
self.registers.program_counter = addr;
}
}
(
Instruction::BBS(bit),
OpInput::UseBitBranch {
zp_address,
relative,
},
) => {
let val = self.memory.get_byte(u16::from(zp_address));
if val & (1 << bit) != 0 {
let addr = self.registers.program_counter.wrapping_add(relative);
self.registers.program_counter = addr;
}
}
(Instruction::RMB(bit), OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.memory.set_byte(addr, val & !(1 << bit));
}
(Instruction::SMB(bit), OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.memory.set_byte(addr, val | (1 << bit));
}
(Instruction::BRK, OpInput::UseImplied) => {
let return_addr = self.registers.program_counter.wrapping_add(1);
self.push_address(return_addr);
self.push_on_stack(self.registers.status.bits() | 0x30);
let pcl = self.memory.get_byte(0xfffe);
let pch = self.memory.get_byte(0xffff);
self.jump((u16::from(pch) << 8) | u16::from(pcl));
self.set_flag(Status::PS_DISABLE_INTERRUPTS);
}
(Instruction::BRKcld, OpInput::UseImplied) => {
let return_addr = self.registers.program_counter.wrapping_add(1);
for b in return_addr.to_be_bytes() {
self.push_on_stack(b);
}
self.push_on_stack(self.registers.status.bits() | 0x30);
let pcl = self.memory.get_byte(0xfffe);
let pch = self.memory.get_byte(0xffff);
self.jump((u16::from(pch) << 8) | u16::from(pcl));
self.set_flag(Status::PS_DISABLE_INTERRUPTS);
self.unset_flag(Status::PS_DECIMAL_MODE);
}
(Instruction::BVC, OpInput::UseRelative(rel)) => {
let addr = self.registers.program_counter.wrapping_add(rel);
self.branch_if_overflow_clear(addr);
}
(Instruction::BVS, OpInput::UseRelative(rel)) => {
let addr = self.registers.program_counter.wrapping_add(rel);
self.branch_if_overflow_set(addr);
}
(Instruction::CLC, OpInput::UseImplied) => {
self.unset_flag(Status::PS_CARRY);
}
(Instruction::CLD, OpInput::UseImplied) => {
self.unset_flag(Status::PS_DECIMAL_MODE);
}
(Instruction::CLI, OpInput::UseImplied) => {
self.unset_flag(Status::PS_DISABLE_INTERRUPTS);
}
(Instruction::CLV, OpInput::UseImplied) => {
self.unset_flag(Status::PS_OVERFLOW);
}
(Instruction::CMP, OpInput::UseImmediate(val)) => {
self.compare_with_a_register(val);
}
(Instruction::CMP, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.compare_with_a_register(val);
}
(Instruction::CPX, OpInput::UseImmediate(val)) => {
self.compare_with_x_register(val);
}
(Instruction::CPX, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.compare_with_x_register(val);
}
(Instruction::CPY, OpInput::UseImmediate(val)) => {
self.compare_with_y_register(val);
}
(Instruction::CPY, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.compare_with_y_register(val);
}
(Instruction::DEC, OpInput::UseAddress { address: addr, .. }) => {
let mut operand: u8 = self.memory.get_byte(addr);
CPU::<M, V>::decrement(&mut operand, &mut self.registers.status);
self.memory.set_byte(addr, operand);
}
(Instruction::DEC, OpInput::UseImplied) => {
CPU::<M, V>::decrement(&mut self.registers.accumulator, &mut self.registers.status);
}
(Instruction::DEY, OpInput::UseImplied) => {
CPU::<M, V>::decrement(&mut self.registers.index_y, &mut self.registers.status);
}
(Instruction::DEX, OpInput::UseImplied) => {
CPU::<M, V>::decrement(&mut self.registers.index_x, &mut self.registers.status);
}
(Instruction::EOR, OpInput::UseImmediate(val)) => {
self.exclusive_or(val);
}
(Instruction::EOR, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.exclusive_or(val);
}
(Instruction::INC, OpInput::UseAddress { address: addr, .. }) => {
let mut operand: u8 = self.memory.get_byte(addr);
CPU::<M, V>::increment(&mut operand, &mut self.registers.status);
self.memory.set_byte(addr, operand);
}
(Instruction::INC, OpInput::UseImplied) => {
CPU::<M, V>::increment(&mut self.registers.accumulator, &mut self.registers.status);
}
(Instruction::INX, OpInput::UseImplied) => {
CPU::<M, V>::increment(&mut self.registers.index_x, &mut self.registers.status);
}
(Instruction::INY, OpInput::UseImplied) => {
CPU::<M, V>::increment(&mut self.registers.index_y, &mut self.registers.status);
}
(Instruction::JMP, OpInput::UseAddress { address: addr, .. }) => self.jump(addr),
(Instruction::JSR, OpInput::UseAddress { address: addr, .. }) => {
for b in self.registers.program_counter.wrapping_sub(1).to_be_bytes() {
self.push_on_stack(b);
}
self.jump(addr);
}
(Instruction::LDA, OpInput::UseImmediate(val)) => {
log::debug!("load A immediate: {val}");
self.load_accumulator(val);
}
(Instruction::LDA, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
log::debug!("load A. address: {addr:?}. value: {val}");
self.load_accumulator(val);
}
(Instruction::LDX, OpInput::UseImmediate(val)) => {
log::debug!("load X immediate: {val}");
self.load_x_register(val);
}
(Instruction::LDX, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
log::debug!("load X. address: {addr:?}. value: {val}");
self.load_x_register(val);
}
(Instruction::LDY, OpInput::UseImmediate(val)) => {
log::debug!("load Y immediate: {val}");
self.load_y_register(val);
}
(Instruction::LDY, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
log::debug!("load Y. address: {addr:?}. value: {val}");
self.load_y_register(val);
}
(Instruction::LSR, OpInput::UseImplied) => {
let mut val = self.registers.accumulator;
CPU::<M, V>::shift_right_with_flags(&mut val, &mut self.registers.status);
self.registers.accumulator = val;
}
(Instruction::LSR, OpInput::UseAddress { address: addr, .. }) => {
let mut operand: u8 = self.memory.get_byte(addr);
CPU::<M, V>::shift_right_with_flags(&mut operand, &mut self.registers.status);
self.memory.set_byte(addr, operand);
}
(Instruction::ORA, OpInput::UseImmediate(val)) => {
self.inclusive_or(val);
}
(Instruction::ORA, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.inclusive_or(val);
}
(Instruction::PHA, OpInput::UseImplied) => {
let val = self.registers.accumulator;
self.push_on_stack(val);
}
(Instruction::PHX, OpInput::UseImplied) => {
self.push_on_stack(self.registers.index_x);
}
(Instruction::PHY, OpInput::UseImplied) => {
self.push_on_stack(self.registers.index_y);
}
(Instruction::PHP, OpInput::UseImplied) => {
let val = self.registers.status.bits() | 0x30;
self.push_on_stack(val);
}
(Instruction::PLX, OpInput::UseImplied) => {
let val: u8 = self.pull_from_stack();
self.registers.index_x = val;
self.registers.status.set_with_mask(
Status::PS_ZERO | Status::PS_NEGATIVE,
Status::new(StatusArgs {
zero: val == 0,
negative: val > 127,
..StatusArgs::none()
}),
);
}
(Instruction::PLY, OpInput::UseImplied) => {
let val: u8 = self.pull_from_stack();
self.registers.index_y = val;
self.registers.status.set_with_mask(
Status::PS_ZERO | Status::PS_NEGATIVE,
Status::new(StatusArgs {
zero: val == 0,
negative: val > 127,
..StatusArgs::none()
}),
);
}
(Instruction::PLA, OpInput::UseImplied) => {
let val: u8 = self.pull_from_stack();
self.registers.accumulator = val;
self.registers.status.set_with_mask(
Status::PS_ZERO | Status::PS_NEGATIVE,
Status::new(StatusArgs {
zero: val == 0,
negative: self.registers.accumulator > 127,
..StatusArgs::none()
}),
);
}
(Instruction::PLP, OpInput::UseImplied) => {
let val: u8 = self.pull_from_stack();
self.registers.status = Status::from_bits_truncate(val);
}
(Instruction::ROL, OpInput::UseImplied) => {
let mut val = self.registers.accumulator;
CPU::<M, V>::rotate_left_with_flags(&mut val, &mut self.registers.status);
self.registers.accumulator = val;
}
(Instruction::ROL, OpInput::UseAddress { address: addr, .. }) => {
let mut operand: u8 = self.memory.get_byte(addr);
CPU::<M, V>::rotate_left_with_flags(&mut operand, &mut self.registers.status);
self.memory.set_byte(addr, operand);
}
(Instruction::ROR, OpInput::UseImplied) => {
let mut val = self.registers.accumulator;
CPU::<M, V>::rotate_right_with_flags(&mut val, &mut self.registers.status);
self.registers.accumulator = val;
}
(Instruction::ROR, OpInput::UseAddress { address: addr, .. }) => {
let mut operand: u8 = self.memory.get_byte(addr);
CPU::<M, V>::rotate_right_with_flags(&mut operand, &mut self.registers.status);
self.memory.set_byte(addr, operand);
}
(Instruction::RTI, OpInput::UseImplied) => {
let val: u8 = self.pull_from_stack();
self.registers.status = Status::from_bits_truncate(val);
let pcl: u8 = self.pull_from_stack();
let pch: u8 = self.pull_from_stack();
self.registers.program_counter = (u16::from(pch) << 8) | u16::from(pcl);
}
(Instruction::RTS, OpInput::UseImplied) => {
let pcl: u8 = self.pull_from_stack();
let pch: u8 = self.pull_from_stack();
self.registers.program_counter =
((u16::from(pch) << 8) | u16::from(pcl)).wrapping_add(1);
}
(Instruction::SBC, OpInput::UseImmediate(val)) => {
log::debug!("subtract with carry immediate: {val}");
self.subtract_with_carry(val);
}
(Instruction::SBC, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
log::debug!("subtract with carry. address: {addr:?}. value: {val}");
self.subtract_with_carry(val);
}
(Instruction::SBCnd, OpInput::UseImmediate(val)) => {
log::debug!("subtract with carry immediate: {val}");
self.subtract_with_no_decimal(val);
}
(Instruction::SBCnd, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
log::debug!("subtract with carry. address: {addr:?}. value: {val}");
self.subtract_with_no_decimal(val);
}
(Instruction::SEC, OpInput::UseImplied) => {
self.set_flag(Status::PS_CARRY);
}
(Instruction::SED, OpInput::UseImplied) => {
self.set_flag(Status::PS_DECIMAL_MODE);
}
(Instruction::SEI, OpInput::UseImplied) => {
self.set_flag(Status::PS_DISABLE_INTERRUPTS);
}
(Instruction::SAX, OpInput::UseAddress { address: addr, .. }) => {
self.memory
.set_byte(addr, self.registers.accumulator & self.registers.index_x);
}
(Instruction::STA, OpInput::UseAddress { address: addr, .. }) => {
self.memory.set_byte(addr, self.registers.accumulator);
}
(Instruction::STX, OpInput::UseAddress { address: addr, .. }) => {
self.memory.set_byte(addr, self.registers.index_x);
}
(Instruction::STY, OpInput::UseAddress { address: addr, .. }) => {
self.memory.set_byte(addr, self.registers.index_y);
}
(Instruction::STZ, OpInput::UseAddress { address: addr, .. }) => {
self.memory.set_byte(addr, 0);
}
(Instruction::TAX, OpInput::UseImplied) => {
let val = self.registers.accumulator;
self.load_x_register(val);
}
(Instruction::TAY, OpInput::UseImplied) => {
let val = self.registers.accumulator;
self.load_y_register(val);
}
(Instruction::TRB, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.registers.status.set_with_mask(
Status::PS_ZERO,
Status::new(StatusArgs {
zero: 0 == (self.registers.accumulator & val),
..StatusArgs::none()
}),
);
let res = val & !self.registers.accumulator;
self.memory.set_byte(addr, res);
}
(Instruction::TSB, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.registers.status.set_with_mask(
Status::PS_ZERO,
Status::new(StatusArgs {
zero: 0 == (self.registers.accumulator & val),
..StatusArgs::none()
}),
);
let res = val | self.registers.accumulator;
self.memory.set_byte(addr, res);
}
(Instruction::TSX, OpInput::UseImplied) => {
let StackPointer(val) = self.registers.stack_pointer;
self.load_x_register(val);
}
(Instruction::TXA, OpInput::UseImplied) => {
let val = self.registers.index_x;
self.load_accumulator(val);
}
(Instruction::TXS, OpInput::UseImplied) => {
let val = self.registers.index_x;
self.registers.stack_pointer = StackPointer(val);
}
(Instruction::TYA, OpInput::UseImplied) => {
let val = self.registers.index_y;
self.load_accumulator(val);
}
(Instruction::XAA, OpInput::UseImmediate(val)) => {
self.load_accumulator(self.registers.index_x);
self.and(val);
}
(Instruction::WAI, OpInput::UseImplied) => {
log::debug!("WAI instruction - waiting for interrupt");
self.wait_state = WaitState::WaitingForInterrupt;
}
(Instruction::STP, OpInput::UseImplied) => {
log::debug!("STP instruction - processor stopped");
self.wait_state = WaitState::WaitingForReset;
}
(Instruction::NOP, OpInput::UseImplied) => {
log::debug!("NOP instruction");
}
(Instruction::ALR, OpInput::UseImmediate(val)) => {
self.and(val);
let mut val = self.registers.accumulator;
CPU::<M, V>::shift_right_with_flags(&mut val, &mut self.registers.status);
self.registers.accumulator = val;
}
(Instruction::ANC, OpInput::UseImmediate(val)) => {
self.and(val);
if self.registers.accumulator & 0x80 != 0 {
self.set_flag(Status::PS_CARRY);
} else {
self.unset_flag(Status::PS_CARRY);
}
}
(Instruction::ARR, OpInput::UseImmediate(val)) => {
self.and(val);
let a = self.registers.accumulator;
let carry = self.registers.status.contains(Status::PS_CARRY);
let result = (a >> 1) | (if carry { 0x80 } else { 0 });
self.registers.accumulator = result;
CPU::<M, V>::set_flags_from_u8(&mut self.registers.status, result);
if result & 0x40 != 0 {
self.set_flag(Status::PS_CARRY);
} else {
self.unset_flag(Status::PS_CARRY);
}
if ((result >> 6) ^ (result >> 5)) & 1 != 0 {
self.set_flag(Status::PS_OVERFLOW);
} else {
self.unset_flag(Status::PS_OVERFLOW);
}
}
(Instruction::DCP, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr).wrapping_sub(1);
self.memory.set_byte(addr, val);
self.compare_with_a_register(val);
}
(Instruction::ISC, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr).wrapping_add(1);
self.memory.set_byte(addr, val);
self.subtract_with_carry(val);
}
(Instruction::JAM, OpInput::UseImplied) => {
self.wait_state = WaitState::WaitingForReset;
}
(Instruction::LAS, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr) & self.registers.stack_pointer.0;
self.registers.accumulator = val;
self.registers.index_x = val;
self.registers.stack_pointer = StackPointer(val);
CPU::<M, V>::set_flags_from_u8(&mut self.registers.status, val);
}
(Instruction::LAX, OpInput::UseAddress { address: addr, .. }) => {
let val = self.memory.get_byte(addr);
self.load_accumulator(val);
self.load_x_register(val);
}
(Instruction::NOP1, OpInput::UseImplied) => {}
(Instruction::NOPI, OpInput::UseImmediate(_)) => {}
(Instruction::NOPZ, OpInput::UseAddress { .. }) => {}
(Instruction::NOPZX, OpInput::UseAddress { .. }) => {}
(Instruction::NOPA, OpInput::UseAddress { .. }) => {}
(Instruction::NOPAX, OpInput::UseAddress { .. }) => {}
(Instruction::NOPAX8, OpInput::UseAddress { .. }) => {}
(Instruction::RLA, OpInput::UseAddress { address: addr, .. }) => {
let mut val = self.memory.get_byte(addr);
CPU::<M, V>::rotate_left_with_flags(&mut val, &mut self.registers.status);
self.memory.set_byte(addr, val);
self.and(val);
}
(Instruction::RRA, OpInput::UseAddress { address: addr, .. }) => {
let mut val = self.memory.get_byte(addr);
CPU::<M, V>::rotate_right_with_flags(&mut val, &mut self.registers.status);
self.memory.set_byte(addr, val);
self.add_with_carry(val);
}
(Instruction::SBX, OpInput::UseImmediate(val)) => {
let ax = self.registers.accumulator & self.registers.index_x;
let result = ax.wrapping_sub(val);
self.registers.index_x = result;
if ax >= val {
self.set_flag(Status::PS_CARRY);
} else {
self.unset_flag(Status::PS_CARRY);
}
CPU::<M, V>::set_flags_from_u8(&mut self.registers.status, result);
}
(Instruction::SLO, OpInput::UseAddress { address: addr, .. }) => {
let mut val = self.memory.get_byte(addr);
CPU::<M, V>::shift_left_with_flags(&mut val, &mut self.registers.status);
self.memory.set_byte(addr, val);
self.inclusive_or(val);
}
(Instruction::SRE, OpInput::UseAddress { address: addr, .. }) => {
let mut val = self.memory.get_byte(addr);
CPU::<M, V>::shift_right_with_flags(&mut val, &mut self.registers.status);
self.memory.set_byte(addr, val);
self.exclusive_or(val);
}
(Instruction::USBC, OpInput::UseImmediate(val)) => {
self.subtract_with_carry(val);
}
(instr, input) => {
panic!("unimplemented or invalid instruction: {instr:?} with input {input:?}");
}
}
}
pub fn single_step(&mut self) -> bool {
match self.wait_state {
WaitState::Running => {
if let Some(decoded_instr) = self.fetch_next_and_decode() {
self.execute_instruction(decoded_instr);
self.check_interrupts();
true
} else {
self.check_interrupts();
false
}
}
WaitState::WaitingForInterrupt => {
self.check_interrupts();
false
}
WaitState::WaitingForReset => {
false
}
}
}
pub fn run(&mut self) {
while self.wait_state != WaitState::WaitingForReset
&& let Some(decoded_instr) = self.fetch_next_and_decode()
{
self.execute_instruction(decoded_instr);
}
}
const fn value_is_negative(value: u8) -> bool {
value > 127
}
fn set_flags_from_u8(status: &mut Status, value: u8) {
let is_zero = value == 0;
let is_negative = Self::value_is_negative(value);
status.set_with_mask(
Status::PS_ZERO | Status::PS_NEGATIVE,
Status::new(StatusArgs {
zero: is_zero,
negative: is_negative,
..StatusArgs::none()
}),
);
}
fn shift_left_with_flags(p_val: &mut u8, status: &mut Status) {
let mask = 1 << 7;
let is_bit_7_set = (*p_val & mask) == mask;
let shifted = (*p_val & !(1 << 7)) << 1;
*p_val = shifted;
status.set_with_mask(
Status::PS_CARRY,
Status::new(StatusArgs {
carry: is_bit_7_set,
..StatusArgs::none()
}),
);
CPU::<M, V>::set_flags_from_u8(status, *p_val);
}
fn shift_right_with_flags(p_val: &mut u8, status: &mut Status) {
let mask = 1;
let is_bit_0_set = (*p_val & mask) == mask;
*p_val >>= 1;
status.set_with_mask(
Status::PS_CARRY,
Status::new(StatusArgs {
carry: is_bit_0_set,
..StatusArgs::none()
}),
);
CPU::<M, V>::set_flags_from_u8(status, *p_val);
}
fn rotate_left_with_flags(p_val: &mut u8, status: &mut Status) {
let is_carry_set = status.contains(Status::PS_CARRY);
let mask = 1 << 7;
let is_bit_7_set = (*p_val & mask) == mask;
let shifted = (*p_val & !(1 << 7)) << 1;
*p_val = shifted + u8::from(is_carry_set);
status.set_with_mask(
Status::PS_CARRY,
Status::new(StatusArgs {
carry: is_bit_7_set,
..StatusArgs::none()
}),
);
CPU::<M, V>::set_flags_from_u8(status, *p_val);
}
fn rotate_right_with_flags(p_val: &mut u8, status: &mut Status) {
let is_carry_set = status.contains(Status::PS_CARRY);
let mask = 1;
let is_bit_0_set = (*p_val & mask) == mask;
let shifted = *p_val >> 1;
*p_val = shifted + if is_carry_set { 1 << 7 } else { 0 };
status.set_with_mask(
Status::PS_CARRY,
Status::new(StatusArgs {
carry: is_bit_0_set,
..StatusArgs::none()
}),
);
CPU::<M, V>::set_flags_from_u8(status, *p_val);
}
fn set_u8_with_flags(mem: &mut u8, status: &mut Status, value: u8) {
*mem = value;
CPU::<M, V>::set_flags_from_u8(status, value);
}
fn load_x_register(&mut self, value: u8) {
CPU::<M, V>::set_u8_with_flags(
&mut self.registers.index_x,
&mut self.registers.status,
value,
);
}
fn load_y_register(&mut self, value: u8) {
CPU::<M, V>::set_u8_with_flags(
&mut self.registers.index_y,
&mut self.registers.status,
value,
);
}
fn load_accumulator(&mut self, value: u8) {
CPU::<M, V>::set_u8_with_flags(
&mut self.registers.accumulator,
&mut self.registers.status,
value,
);
}
#[inline]
const fn get_flag(&self, flag: Status) -> bool {
self.registers.status.contains(flag)
}
#[inline]
fn set_flag(&mut self, flag: Status) {
self.registers.status.or(flag);
}
#[inline]
fn unset_flag(&mut self, flag: Status) {
self.registers.status.and(!flag);
}
fn add_with_carry(&mut self, value: u8) {
let carry_set = self.get_flag(Status::PS_CARRY);
let decimal_mode = self.get_flag(Status::PS_DECIMAL_MODE);
let output = if decimal_mode {
V::adc_decimal(self.registers.accumulator, value, carry_set)
} else {
V::adc_binary(self.registers.accumulator, value, carry_set)
};
self.registers.status.set_with_mask(
Status::PS_CARRY | Status::PS_OVERFLOW | Status::PS_ZERO | Status::PS_NEGATIVE,
Status::new(StatusArgs {
carry: output.did_carry,
overflow: output.overflow,
zero: output.zero,
negative: output.negative,
..StatusArgs::none()
}),
);
self.registers.accumulator = output.result;
}
fn add_with_no_decimal(&mut self, value: u8) {
let carry_set = self.get_flag(Status::PS_CARRY);
let output = V::adc_binary(self.registers.accumulator, value, carry_set);
self.registers.status.set_with_mask(
Status::PS_CARRY | Status::PS_OVERFLOW | Status::PS_ZERO | Status::PS_NEGATIVE,
Status::new(StatusArgs {
carry: output.did_carry,
overflow: output.overflow,
zero: output.zero,
negative: output.negative,
..StatusArgs::none()
}),
);
self.registers.accumulator = output.result;
}
fn and(&mut self, value: u8) {
let a_after = self.registers.accumulator & value;
self.load_accumulator(a_after);
}
fn subtract_with_no_decimal(&mut self, value: u8) {
let nc: u8 = u8::from(!self.registers.status.contains(Status::PS_CARRY));
let a_before = self.registers.accumulator;
let a_after = a_before.wrapping_sub(value).wrapping_sub(nc);
let over = (nc == 0 && value > 127) && a_before < 128 && a_after > 127;
let under =
(a_before > 127) && (0u8.wrapping_sub(value).wrapping_sub(nc) > 127) && a_after < 128;
let did_overflow = over || under;
let mask = Status::PS_CARRY | Status::PS_OVERFLOW;
let result = a_after;
let did_carry = (result) > (a_before);
self.registers.status.set_with_mask(
mask,
Status::new(StatusArgs {
carry: did_carry,
overflow: did_overflow,
..StatusArgs::none()
}),
);
self.load_accumulator(result);
}
fn subtract_with_carry(&mut self, value: u8) {
let carry_set = self.get_flag(Status::PS_CARRY);
let decimal_mode = self.get_flag(Status::PS_DECIMAL_MODE);
let output = if decimal_mode {
V::sbc_decimal(self.registers.accumulator, value, carry_set)
} else {
V::sbc_binary(self.registers.accumulator, value, carry_set)
};
self.registers.status.set_with_mask(
Status::PS_CARRY | Status::PS_OVERFLOW | Status::PS_ZERO | Status::PS_NEGATIVE,
Status::new(StatusArgs {
carry: output.did_carry,
overflow: output.overflow,
zero: output.zero,
negative: output.negative,
..StatusArgs::none()
}),
);
self.registers.accumulator = output.result;
}
fn increment(val: &mut u8, flags: &mut Status) {
let value_new = val.wrapping_add(1);
*val = value_new;
let is_zero = value_new == 0;
flags.set_with_mask(
Status::PS_NEGATIVE | Status::PS_ZERO,
Status::new(StatusArgs {
negative: Self::value_is_negative(value_new),
zero: is_zero,
..StatusArgs::none()
}),
);
}
fn decrement(val: &mut u8, flags: &mut Status) {
let value_new = val.wrapping_sub(1);
*val = value_new;
let is_zero = value_new == 0;
let is_negative = Self::value_is_negative(value_new);
flags.set_with_mask(
Status::PS_NEGATIVE | Status::PS_ZERO,
Status::new(StatusArgs {
zero: is_zero,
negative: is_negative,
..StatusArgs::none()
}),
);
}
const fn jump(&mut self, addr: u16) {
self.registers.program_counter = addr;
}
fn branch_if_carry_clear(&mut self, addr: u16) {
if !self.registers.status.contains(Status::PS_CARRY) {
self.branch(addr);
}
}
fn branch_if_carry_set(&mut self, addr: u16) {
if self.registers.status.contains(Status::PS_CARRY) {
self.branch(addr);
}
}
fn branch_if_equal(&mut self, addr: u16) {
if self.registers.status.contains(Status::PS_ZERO) {
self.branch(addr);
}
}
fn branch_if_not_equal(&mut self, addr: u16) {
if !self.registers.status.contains(Status::PS_ZERO) {
self.branch(addr);
}
}
fn branch_if_minus(&mut self, addr: u16) {
if self.registers.status.contains(Status::PS_NEGATIVE) {
self.branch(addr);
}
}
fn branch(&mut self, addr: u16) {
let page_crossed = (self.registers.program_counter ^ addr) & 0xFF00 != 0;
self.cycles += 1 + u64::from(page_crossed);
self.registers.program_counter = addr;
}
fn branch_if_positive(&mut self, addr: u16) {
if !self.registers.status.contains(Status::PS_NEGATIVE) {
self.branch(addr);
}
}
fn branch_if_overflow_clear(&mut self, addr: u16) {
if !self.registers.status.contains(Status::PS_OVERFLOW) {
self.branch(addr);
}
}
fn branch_if_overflow_set(&mut self, addr: u16) {
if self.registers.status.contains(Status::PS_OVERFLOW) {
self.branch(addr);
}
}
fn compare(&mut self, r: u8, val: u8) {
if r >= val {
self.registers.status.insert(Status::PS_CARRY);
} else {
self.registers.status.remove(Status::PS_CARRY);
}
if r == val {
self.registers.status.insert(Status::PS_ZERO);
} else {
self.registers.status.remove(Status::PS_ZERO);
}
let diff = r.wrapping_sub(val);
if Self::value_is_negative(diff) {
self.registers.status.insert(Status::PS_NEGATIVE);
} else {
self.registers.status.remove(Status::PS_NEGATIVE);
}
}
fn compare_with_a_register(&mut self, val: u8) {
let a = self.registers.accumulator;
self.compare(a, val);
}
fn compare_with_x_register(&mut self, val: u8) {
log::debug!("compare_with_x_register");
let x = self.registers.index_x;
self.compare(x, val);
}
fn compare_with_y_register(&mut self, val: u8) {
let y = self.registers.index_y;
self.compare(y, val);
}
fn exclusive_or(&mut self, val: u8) {
let a_after = self.registers.accumulator ^ val;
self.load_accumulator(a_after);
}
fn inclusive_or(&mut self, val: u8) {
let a_after = self.registers.accumulator | val;
self.load_accumulator(a_after);
}
fn push_on_stack(&mut self, val: u8) {
let addr = self.registers.stack_pointer.to_u16();
self.memory.set_byte(addr, val);
self.registers.stack_pointer.decrement();
}
fn push_address(&mut self, addr: u16) {
let bytes = addr.to_be_bytes();
self.push_on_stack(bytes[0]); self.push_on_stack(bytes[1]); }
fn pull_from_stack(&mut self) -> u8 {
self.registers.stack_pointer.increment();
let addr = self.registers.stack_pointer.to_u16();
self.memory.get_byte(addr)
}
fn service_interrupt(&mut self, vector_addr: u16) {
self.push_address(self.registers.program_counter);
self.push_on_stack((self.registers.status.bits() | 0x20) & !0x10);
self.registers.status.insert(Status::PS_DISABLE_INTERRUPTS);
let pcl = self.memory.get_byte(vector_addr);
let pch = self.memory.get_byte(vector_addr.wrapping_add(1));
self.registers.program_counter = u16::from_le_bytes([pcl, pch]);
}
fn is_nmi_triggered(&mut self) -> bool {
let nmi_current = self.memory.nmi_pending();
let nmi_triggered = !self.last_nmi_state && nmi_current;
self.last_nmi_state = nmi_current;
nmi_triggered
}
fn is_irq_triggered(&mut self) -> bool {
let irq_pending = self.memory.irq_pending();
let irq_enabled = !self
.registers
.status
.contains(Status::PS_DISABLE_INTERRUPTS);
irq_pending && irq_enabled
}
fn check_interrupts(&mut self) -> bool {
if self.is_nmi_triggered() {
log::debug!("NMI triggered");
self.wait_state = WaitState::Running; self.service_interrupt(NMI_INTERRUPT_VECTOR_LO);
return true;
}
if self.is_irq_triggered() {
log::debug!("IRQ triggered");
self.wait_state = WaitState::Running; self.service_interrupt(IRQ_INTERRUPT_VECTOR_LO);
return true;
}
false
}
}
impl<M: Bus, V: Variant> core::fmt::Debug for CPU<M, V> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "CPU {{ registers: {:?}", self.registers)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_wrap)]
use super::*;
use crate::instruction::Nmos6502;
use crate::memory::{Memory as Ram, RESET_VECTOR_LO};
#[test]
fn dont_panic_for_overflow() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.add_with_carry(0x80);
assert_eq!(cpu.registers.accumulator, 0x80);
cpu.add_with_carry(0x80);
assert_eq!(cpu.registers.accumulator, 0);
cpu.registers.status.insert(Status::PS_CARRY);
cpu.subtract_with_carry(0x80);
assert_eq!(cpu.registers.accumulator, 0x80);
cpu.registers.status.insert(Status::PS_CARRY);
cpu.subtract_with_carry(0x80);
assert_eq!(cpu.registers.accumulator, 0);
}
#[test]
fn decimal_add_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.status.insert(Status::PS_DECIMAL_MODE);
cpu.add_with_carry(0x09);
assert_eq!(cpu.registers.accumulator, 0x09);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.add_with_carry(0x43);
assert_eq!(cpu.registers.accumulator, 0x52);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.add_with_carry(0x48);
assert_eq!(cpu.registers.accumulator, 0x00);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(cpu.registers.status.contains(Status::PS_OVERFLOW));
}
#[test]
fn decimal_subtract_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers
.status
.insert(Status::PS_DECIMAL_MODE | Status::PS_CARRY);
cpu.registers.accumulator = 0;
cpu.subtract_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x99);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.registers.status.insert(Status::PS_CARRY);
cpu.registers.accumulator = 0x50;
cpu.subtract_with_carry(0x25);
assert_eq!(cpu.registers.accumulator, 0x25);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
}
#[test]
fn add_with_carry_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.status.remove(Status::PS_DECIMAL_MODE);
cpu.registers.accumulator = 0;
cpu.registers.status.remove(Status::PS_CARRY);
cpu.add_with_carry(0);
assert_eq!(cpu.registers.accumulator, 0);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.registers.accumulator = 0;
cpu.registers.status.insert(Status::PS_CARRY);
cpu.add_with_carry(1);
assert_eq!(cpu.registers.accumulator, 2);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.registers.accumulator = 0x7F;
cpu.registers.status.remove(Status::PS_CARRY);
cpu.add_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x80);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.registers.status.insert(Status::PS_DECIMAL_MODE);
cpu.registers.accumulator = 0x09;
cpu.registers.status.remove(Status::PS_CARRY);
cpu.add_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x10);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.accumulator = 0x50;
cpu.registers.status.remove(Status::PS_CARRY);
cpu.add_with_carry(0x50);
assert_eq!(cpu.registers.accumulator, 0x00);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.accumulator = 0x99;
cpu.registers.status.insert(Status::PS_CARRY);
cpu.add_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x01);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.status.remove(Status::PS_DECIMAL_MODE);
cpu.registers.accumulator = 0xFF;
cpu.registers.status.remove(Status::PS_CARRY);
cpu.add_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x00);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.registers.accumulator = 0xFF;
cpu.registers.status.insert(Status::PS_CARRY);
cpu.add_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x01);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.registers.accumulator = 0x80;
cpu.registers.status.remove(Status::PS_CARRY);
cpu.add_with_carry(0x80);
assert_eq!(cpu.registers.accumulator, 0x00);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(cpu.registers.status.contains(Status::PS_OVERFLOW));
}
#[test]
fn solid65_adc_immediate() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::Immediate,
OpInput::UseImmediate(0x9c),
));
cpu.execute_instruction((
Instruction::SEC,
AddressingMode::Implied,
OpInput::UseImplied,
));
cpu.execute_instruction((
Instruction::ADC,
AddressingMode::Immediate,
OpInput::UseImmediate(0xff),
));
assert_eq!(cpu.registers.accumulator, 0x9c);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
}
#[test]
fn php_sets_bits_4_and_5() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.execute_instruction((
Instruction::PHP,
AddressingMode::Implied,
OpInput::UseImplied,
));
cpu.execute_instruction((
Instruction::PLA,
AddressingMode::Implied,
OpInput::UseImplied,
));
cpu.execute_instruction((
Instruction::AND,
AddressingMode::Immediate,
OpInput::UseImmediate(0x30),
));
assert_eq!(cpu.registers.accumulator, 0x30);
}
#[test]
fn and_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.accumulator = 0;
cpu.and(0xff);
assert_eq!(cpu.registers.accumulator, 0);
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.accumulator = 0xff;
cpu.and(0);
assert_eq!(cpu.registers.accumulator, 0);
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.accumulator = 0xff;
cpu.and(0x0f);
assert_eq!(cpu.registers.accumulator, 0x0f);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.accumulator = 0xff;
cpu.and(0x80);
assert_eq!(cpu.registers.accumulator, 0x80);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn subtract_with_carry_comprehensive_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.status.remove(Status::PS_DECIMAL_MODE);
cpu.registers.accumulator = 0;
cpu.registers.status.insert(Status::PS_CARRY);
cpu.subtract_with_carry(0);
assert_eq!(cpu.registers.accumulator, 0);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.registers.accumulator = 0;
cpu.registers.status.insert(Status::PS_CARRY);
cpu.subtract_with_carry(1);
assert_eq!(cpu.registers.accumulator, 0xFF);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.registers.accumulator = 0x80;
cpu.registers.status.insert(Status::PS_CARRY);
cpu.subtract_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x7F);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.registers.status.insert(Status::PS_DECIMAL_MODE);
cpu.registers.accumulator = 0x10;
cpu.registers.status.insert(Status::PS_CARRY);
cpu.subtract_with_carry(0x05);
assert_eq!(cpu.registers.accumulator, 0x05);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.accumulator = 0x20;
cpu.registers.status.remove(Status::PS_CARRY);
cpu.subtract_with_carry(0x10);
assert_eq!(cpu.registers.accumulator, 0x09);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.accumulator = 0x00;
cpu.registers.status.insert(Status::PS_CARRY);
cpu.subtract_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x99);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.accumulator = 0x99;
cpu.registers.status.insert(Status::PS_CARRY);
cpu.subtract_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x98);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn decrement_memory_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
let addr: u16 = 0xA1B2;
cpu.memory.set_byte(addr, 5);
cpu.execute_instruction((
Instruction::DEC,
AddressingMode::Absolute,
OpInput::UseAddress {
address: addr,
page_crossed: false,
},
));
assert_eq!(cpu.memory.get_byte(addr), 4);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
Instruction::DEC,
AddressingMode::Absolute,
OpInput::UseAddress {
address: addr,
page_crossed: false,
},
));
assert_eq!(cpu.memory.get_byte(addr), 3);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
Instruction::DEC,
AddressingMode::Absolute,
OpInput::UseAddress {
address: addr,
page_crossed: false,
},
));
cpu.execute_instruction((
Instruction::DEC,
AddressingMode::Absolute,
OpInput::UseAddress {
address: addr,
page_crossed: false,
},
));
cpu.execute_instruction((
Instruction::DEC,
AddressingMode::Absolute,
OpInput::UseAddress {
address: addr,
page_crossed: false,
},
));
assert_eq!(cpu.memory.get_byte(addr), 0);
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
Instruction::DEC,
AddressingMode::Absolute,
OpInput::UseAddress {
address: addr,
page_crossed: false,
},
));
assert_eq!(cpu.memory.get_byte(addr) as i8, -1);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.memory.set_byte(addr, 0);
cpu.execute_instruction((
Instruction::DEC,
AddressingMode::Absolute,
OpInput::UseAddress {
address: addr,
page_crossed: false,
},
));
assert_eq!(cpu.memory.get_byte(addr), 0xff);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn decrement_x_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.index_x = 0x80;
cpu.execute_instruction((
Instruction::DEX,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.index_x, 127);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn decrement_y_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.index_y = 0x80;
cpu.execute_instruction((
Instruction::DEY,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.index_y, 127);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn logical_shift_right_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::Immediate,
OpInput::UseImmediate(0),
));
cpu.execute_instruction((
Instruction::LSR,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::Immediate,
OpInput::UseImmediate(1),
));
cpu.execute_instruction((
Instruction::LSR,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::Immediate,
OpInput::UseImmediate(255),
));
cpu.execute_instruction((
Instruction::LSR,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0x7F);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::Immediate,
OpInput::UseImmediate(254),
));
cpu.execute_instruction((
Instruction::LSR,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0x7F);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
}
#[test]
fn dec_x_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.execute_instruction((
Instruction::DEX,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.index_x, 0xff);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.execute_instruction((
Instruction::DEX,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.index_x, 0xfe);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.load_x_register(5);
cpu.execute_instruction((
Instruction::DEX,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.index_x, 4);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.execute_instruction((
Instruction::DEX,
AddressingMode::Implied,
OpInput::UseImplied,
));
cpu.execute_instruction((
Instruction::DEX,
AddressingMode::Implied,
OpInput::UseImplied,
));
cpu.execute_instruction((
Instruction::DEX,
AddressingMode::Implied,
OpInput::UseImplied,
));
cpu.execute_instruction((
Instruction::DEX,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.index_x, 0);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
cpu.execute_instruction((
Instruction::DEX,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.index_x, 0xff);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_OVERFLOW));
}
#[test]
fn jump_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
let addr: u16 = 0xA1B1;
cpu.jump(addr);
assert_eq!(cpu.registers.program_counter, addr);
}
#[test]
fn branch_if_carry_clear_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.execute_instruction((
Instruction::SEC,
AddressingMode::Implied,
OpInput::UseImplied,
));
cpu.branch_if_carry_clear(0xABCD);
assert_eq!(cpu.registers.program_counter, (0));
cpu.execute_instruction((
Instruction::CLC,
AddressingMode::Implied,
OpInput::UseImplied,
));
cpu.branch_if_carry_clear(0xABCD);
assert_eq!(cpu.registers.program_counter, (0xABCD));
}
#[test]
fn branch_if_carry_set_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.execute_instruction((
Instruction::CLC,
AddressingMode::Implied,
OpInput::UseImplied,
));
cpu.branch_if_carry_set(0xABCD);
assert_eq!(cpu.registers.program_counter, (0));
cpu.execute_instruction((
Instruction::SEC,
AddressingMode::Implied,
OpInput::UseImplied,
));
cpu.branch_if_carry_set(0xABCD);
assert_eq!(cpu.registers.program_counter, (0xABCD));
}
#[test]
fn branch_if_equal_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.branch_if_equal(0xABCD);
assert_eq!(cpu.registers.program_counter, (0));
cpu.set_flag(Status::PS_ZERO);
cpu.branch_if_equal(0xABCD);
assert_eq!(cpu.registers.program_counter, (0xABCD));
}
#[test]
fn branch_if_minus_test() {
{
let mut cpu = CPU::new(Ram::new(), Nmos6502);
let registers_before = cpu.registers;
cpu.branch_if_minus(0xABCD);
assert_eq!(cpu.registers, registers_before);
assert_eq!(cpu.registers.program_counter, (0));
}
{
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.set_flag(Status::PS_NEGATIVE);
let registers_before = cpu.registers;
cpu.branch_if_minus(0xABCD);
assert_eq!(cpu.registers.status, registers_before.status);
assert_eq!(cpu.registers.program_counter, (0xABCD));
}
}
#[test]
fn branch_if_positive_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.status.insert(Status::PS_NEGATIVE);
cpu.branch_if_positive(0xABCD);
assert_eq!(cpu.registers.program_counter, (0));
cpu.registers.status.remove(Status::PS_NEGATIVE);
cpu.branch_if_positive(0xABCD);
assert_eq!(cpu.registers.program_counter, (0xABCD));
}
#[test]
fn branch_if_overflow_clear_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.status.insert(Status::PS_OVERFLOW);
cpu.branch_if_overflow_clear(0xABCD);
assert_eq!(cpu.registers.program_counter, (0));
cpu.registers.status.remove(Status::PS_OVERFLOW);
cpu.branch_if_overflow_clear(0xABCD);
assert_eq!(cpu.registers.program_counter, (0xABCD));
}
#[test]
fn branch_across_end_of_address_space() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.program_counter = 0xffff;
cpu.registers.status.insert(Status::PS_OVERFLOW);
cpu.branch_if_overflow_set(0xABCD);
assert_eq!(cpu.registers.program_counter, (0xABCD));
}
#[test]
fn branch_if_overflow_set_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.branch_if_overflow_set(0xABCD);
assert_eq!(cpu.registers.program_counter, (0));
cpu.registers.status.insert(Status::PS_OVERFLOW);
cpu.branch_if_overflow_set(0xABCD);
assert_eq!(cpu.registers.program_counter, (0xABCD));
}
#[cfg(test)]
fn compare_test_helper<F>(compare: &mut F, load_instruction: Instruction)
where
F: FnMut(&mut CPU<Ram, crate::instruction::Nmos6502>, u8),
{
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.execute_instruction((
load_instruction,
AddressingMode::Immediate,
OpInput::UseImmediate(127),
));
compare(&mut cpu, 127);
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
load_instruction,
AddressingMode::Immediate,
OpInput::UseImmediate(127),
));
compare(&mut cpu, 1);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
load_instruction,
AddressingMode::Immediate,
OpInput::UseImmediate(1),
));
compare(&mut cpu, 2);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
load_instruction,
AddressingMode::Immediate,
OpInput::UseImmediate(20),
));
compare(&mut cpu, -50i8 as u8);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
load_instruction,
AddressingMode::Immediate,
OpInput::UseImmediate(1),
));
compare(&mut cpu, -1i8 as u8);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
load_instruction,
AddressingMode::Immediate,
OpInput::UseImmediate(127),
));
compare(&mut cpu, -128i8 as u8);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn compare_with_a_register_test() {
compare_test_helper(
&mut |cpu: &mut CPU<Ram, Nmos6502>, val: u8| {
cpu.compare_with_a_register(val);
},
Instruction::LDA,
);
}
#[test]
fn compare_with_x_register_test() {
compare_test_helper(
&mut |cpu: &mut CPU<Ram, Nmos6502>, val: u8| {
cpu.compare_with_x_register(val);
},
Instruction::LDX,
);
}
#[test]
fn compare_with_y_register_test() {
compare_test_helper(
&mut |cpu: &mut CPU<Ram, Nmos6502>, val: u8| {
cpu.compare_with_y_register(val);
},
Instruction::LDY,
);
}
#[test]
fn exclusive_or_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
for a_before in 0u8..=255u8 {
for val in 0u8..=255u8 {
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::Immediate,
OpInput::UseImmediate(a_before),
));
cpu.exclusive_or(val);
let a_after = a_before ^ val;
assert_eq!(cpu.registers.accumulator, a_after);
if a_after == 0 {
assert!(cpu.registers.status.contains(Status::PS_ZERO));
} else {
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
}
if (a_after as i8) < 0 {
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
} else {
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
}
}
}
}
#[test]
fn inclusive_or_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
for a_before in 0u8..=255u8 {
for val in 0u8..=255u8 {
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::Immediate,
OpInput::UseImmediate(a_before),
));
cpu.inclusive_or(val);
let a_after = a_before | val;
assert_eq!(cpu.registers.accumulator, a_after);
if a_after == 0 {
assert!(cpu.registers.status.contains(Status::PS_ZERO));
} else {
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
}
if (a_after as i8) < 0 {
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
} else {
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
}
}
}
}
#[test]
fn stack_underflow() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
let _val: u8 = cpu.pull_from_stack();
}
#[test]
fn nmos6502_adc_decimal_mode() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.accumulator = 0x09;
cpu.registers.status.insert(Status::PS_DECIMAL_MODE);
cpu.registers.status.remove(Status::PS_CARRY);
cpu.add_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x10);
assert!(!cpu.get_flag(Status::PS_CARRY));
}
#[test]
fn ricoh2a03_ignores_decimal_mode() {
use crate::instruction::Ricoh2a03;
let mut cpu = CPU::new(Ram::new(), Ricoh2a03);
cpu.registers.accumulator = 0x09;
cpu.registers.status.insert(Status::PS_DECIMAL_MODE);
cpu.registers.status.remove(Status::PS_CARRY);
cpu.add_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x0A);
assert!(!cpu.get_flag(Status::PS_CARRY));
}
#[test]
fn cmos6502_adc_decimal_mode() {
use crate::instruction::Cmos6502;
let mut cpu = CPU::new(Ram::new(), Cmos6502);
cpu.registers.accumulator = 0x09;
cpu.registers.status.insert(Status::PS_DECIMAL_MODE);
cpu.registers.status.remove(Status::PS_CARRY);
cpu.add_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x10);
assert!(!cpu.get_flag(Status::PS_CARRY));
}
#[test]
fn revision_a_adc_same_as_nmos() {
use crate::instruction::RevisionA;
let mut cpu = CPU::new(Ram::new(), RevisionA);
cpu.registers.accumulator = 0x09;
cpu.registers.status.insert(Status::PS_DECIMAL_MODE);
cpu.registers.status.remove(Status::PS_CARRY);
cpu.add_with_carry(0x01);
assert_eq!(cpu.registers.accumulator, 0x10);
assert!(!cpu.get_flag(Status::PS_CARRY));
}
#[test]
fn get_flag() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
assert!(!cpu.get_flag(Status::PS_CARRY));
assert!(!cpu.get_flag(Status::PS_ZERO));
assert!(!cpu.get_flag(Status::PS_NEGATIVE));
assert!(!cpu.get_flag(Status::PS_OVERFLOW));
assert!(!cpu.get_flag(Status::PS_DECIMAL_MODE));
cpu.registers
.status
.insert(Status::PS_CARRY | Status::PS_ZERO);
assert!(cpu.get_flag(Status::PS_CARRY));
assert!(cpu.get_flag(Status::PS_ZERO));
assert!(!cpu.get_flag(Status::PS_NEGATIVE));
}
#[test]
fn set_flag() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
assert!(!cpu.get_flag(Status::PS_CARRY));
assert!(!cpu.get_flag(Status::PS_ZERO));
assert!(!cpu.get_flag(Status::PS_NEGATIVE));
cpu.set_flag(Status::PS_CARRY);
assert!(cpu.get_flag(Status::PS_CARRY));
assert!(!cpu.get_flag(Status::PS_ZERO));
assert!(!cpu.get_flag(Status::PS_NEGATIVE));
cpu.set_flag(Status::PS_ZERO);
assert!(cpu.get_flag(Status::PS_CARRY));
assert!(cpu.get_flag(Status::PS_ZERO));
assert!(!cpu.get_flag(Status::PS_NEGATIVE));
cpu.set_flag(Status::PS_NEGATIVE);
assert!(cpu.get_flag(Status::PS_CARRY));
assert!(cpu.get_flag(Status::PS_ZERO));
assert!(cpu.get_flag(Status::PS_NEGATIVE));
}
#[test]
fn unset_flag() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.set_flag(Status::PS_CARRY);
cpu.set_flag(Status::PS_ZERO);
cpu.set_flag(Status::PS_NEGATIVE);
assert!(cpu.get_flag(Status::PS_CARRY));
assert!(cpu.get_flag(Status::PS_ZERO));
assert!(cpu.get_flag(Status::PS_NEGATIVE));
cpu.unset_flag(Status::PS_CARRY);
assert!(!cpu.get_flag(Status::PS_CARRY));
assert!(cpu.get_flag(Status::PS_ZERO));
assert!(cpu.get_flag(Status::PS_NEGATIVE));
cpu.unset_flag(Status::PS_ZERO);
assert!(!cpu.get_flag(Status::PS_CARRY));
assert!(!cpu.get_flag(Status::PS_ZERO));
assert!(cpu.get_flag(Status::PS_NEGATIVE));
cpu.unset_flag(Status::PS_NEGATIVE);
assert!(!cpu.get_flag(Status::PS_CARRY));
assert!(!cpu.get_flag(Status::PS_ZERO));
assert!(!cpu.get_flag(Status::PS_NEGATIVE));
}
#[test]
fn adc_function_nmos6502_binary_basic() {
use crate::instruction::Nmos6502;
use crate::{ArithmeticOutput, Variant};
let result = Nmos6502::adc_binary(5, 3, false);
assert_eq!(
result,
ArithmeticOutput {
result: 8,
did_carry: false,
overflow: false,
negative: false,
zero: false,
}
);
let result = Nmos6502::adc_binary(5, 3, true);
assert_eq!(
result,
ArithmeticOutput {
result: 9,
did_carry: false,
overflow: false,
negative: false,
zero: false,
}
);
}
#[test]
fn adc_function_nmos6502_binary_overflow() {
use crate::instruction::Nmos6502;
use crate::{ArithmeticOutput, Variant};
let result = Nmos6502::adc_binary(0x7F, 1, false);
assert_eq!(
result,
ArithmeticOutput {
result: 0x80,
did_carry: false,
overflow: true, negative: true, zero: false,
}
);
}
#[test]
fn adc_function_nmos6502_binary_carry() {
use crate::instruction::Nmos6502;
use crate::{ArithmeticOutput, Variant};
let result = Nmos6502::adc_binary(255, 1, false);
assert_eq!(
result,
ArithmeticOutput {
result: 0,
did_carry: true, overflow: false,
negative: false,
zero: true, }
);
}
#[test]
fn adc_function_nmos6502_decimal_basic() {
use crate::instruction::Nmos6502;
use crate::{ArithmeticOutput, Variant};
let result = Nmos6502::adc_decimal(0x09, 0x01, false);
assert_eq!(
result,
ArithmeticOutput {
result: 0x10, did_carry: false,
overflow: false, negative: false,
zero: false,
}
);
}
#[test]
fn adc_function_ricoh2a03_decimal_calls_binary() {
use crate::Variant;
use crate::instruction::Ricoh2a03;
let binary_result = Ricoh2a03::adc_binary(0x09, 0x01, false);
let decimal_result = Ricoh2a03::adc_decimal(0x09, 0x01, false);
assert_eq!(binary_result, decimal_result);
}
#[test]
fn reset_sequence_behavior() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_word(RESET_VECTOR_LO, 0x1234);
cpu.registers.stack_pointer = StackPointer(0xFF);
cpu.reset();
assert_eq!(cpu.registers.program_counter, 0x1234);
assert_eq!(cpu.registers.stack_pointer.0, 0xFC);
assert!(cpu.registers.status.contains(Status::PS_DISABLE_INTERRUPTS));
}
#[test]
fn cmos_bit_zpx() {
use crate::instruction::{Cmos6502, Instruction, OpInput};
let mut cpu = CPU::new(Ram::new(), Cmos6502);
cpu.registers.accumulator = 0b1100_0000;
cpu.memory.set_byte(0x15, 0b1100_0000);
cpu.execute_instruction((
Instruction::BIT,
AddressingMode::Absolute,
OpInput::UseAddress {
address: 0x15,
page_crossed: false,
},
));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(cpu.registers.status.contains(Status::PS_OVERFLOW));
}
#[test]
fn cmos_bit_absx() {
use crate::instruction::{Cmos6502, Instruction, OpInput};
let mut cpu = CPU::new(Ram::new(), Cmos6502);
cpu.registers.accumulator = 0b0100_0000;
cpu.memory.set_byte(0x1005, 0b0100_0000);
cpu.execute_instruction((
Instruction::BIT,
AddressingMode::Absolute,
OpInput::UseAddress {
address: 0x1005,
page_crossed: false,
},
));
assert!(cpu.registers.status.contains(Status::PS_OVERFLOW));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn cmos_jmp_absx_indirect() {
use crate::instruction::{Cmos6502, Instruction, OpInput};
let mut cpu = CPU::new(Ram::new(), Cmos6502);
cpu.execute_instruction((
Instruction::JMP,
AddressingMode::Absolute,
OpInput::UseAddress {
address: 0x3456,
page_crossed: false,
},
));
assert_eq!(cpu.registers.program_counter, 0x3456);
}
#[test]
fn cmos_wai() {
use crate::instruction::{Cmos6502, Instruction, OpInput};
let mut cpu = CPU::new(Ram::new(), Cmos6502);
let pc_before = cpu.registers.program_counter;
cpu.execute_instruction((
Instruction::WAI,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.program_counter, pc_before);
}
#[test]
fn cmos_stp() {
use crate::instruction::Cmos6502;
let mut cpu = CPU::new(Ram::new(), Cmos6502);
cpu.memory.set_byte(0x0000, 0xA9); cpu.memory.set_byte(0x0001, 0x42); cpu.memory.set_byte(0x0002, 0xDB);
cpu.single_step();
assert_eq!(cpu.registers.accumulator, 0x42);
cpu.single_step();
assert_eq!(cpu.wait_state, WaitState::WaitingForReset);
let pc_after_stp = cpu.registers.program_counter;
cpu.single_step();
assert_eq!(cpu.registers.program_counter, pc_after_stp);
cpu.reset();
assert_eq!(cpu.wait_state, WaitState::Running);
cpu.memory.set_word(RESET_VECTOR_LO, 0x8000);
cpu.memory.set_byte(0x8000, 0xA9); cpu.memory.set_byte(0x8001, 0x99); cpu.reset(); cpu.single_step(); assert_eq!(cpu.registers.accumulator, 0x99);
}
macro_rules! exec_zp {
($cpu:expr, $instr:ident, $addr:expr) => {
$cpu.execute_instruction((
Instruction::$instr,
AddressingMode::ZeroPage,
OpInput::UseAddress {
address: $addr,
page_crossed: false,
},
))
};
}
macro_rules! exec_imm {
($cpu:expr, $instr:ident, $val:expr) => {
$cpu.execute_instruction((
Instruction::$instr,
AddressingMode::Immediate,
OpInput::UseImmediate($val),
))
};
}
macro_rules! exec_impl {
($cpu:expr, $instr:ident) => {
$cpu.execute_instruction((
Instruction::$instr,
AddressingMode::Implied,
OpInput::UseImplied,
))
};
}
macro_rules! exec_aby {
($cpu:expr, $instr:ident, $addr:expr) => {
$cpu.execute_instruction((
Instruction::$instr,
AddressingMode::AbsoluteY,
OpInput::UseAddress {
address: $addr,
page_crossed: false,
},
))
};
}
#[test]
fn lax_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_byte(0x42, 0x55);
exec_zp!(cpu, LAX, 0x42);
assert_eq!(cpu.registers.accumulator, 0x55);
assert_eq!(cpu.registers.index_x, 0x55);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.memory.set_byte(0x10, 0x00);
exec_zp!(cpu, LAX, 0x10);
assert_eq!(cpu.registers.accumulator, 0x00);
assert_eq!(cpu.registers.index_x, 0x00);
assert!(cpu.registers.status.contains(Status::PS_ZERO));
cpu.memory.set_byte(0x20, 0x80);
exec_zp!(cpu, LAX, 0x20);
assert_eq!(cpu.registers.accumulator, 0x80);
assert_eq!(cpu.registers.index_x, 0x80);
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn sax_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.accumulator = 0xFF;
cpu.registers.index_x = 0x0F;
exec_zp!(cpu, SAX, 0x42);
assert_eq!(cpu.memory.get_byte(0x42), 0x0F);
cpu.registers.accumulator = 0xAA;
cpu.registers.index_x = 0x55;
exec_zp!(cpu, SAX, 0x43);
assert_eq!(cpu.memory.get_byte(0x43), 0x00);
}
#[test]
fn dcp_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_byte(0x42, 0x10);
cpu.registers.accumulator = 0x0F;
exec_zp!(cpu, DCP, 0x42);
assert_eq!(cpu.memory.get_byte(0x42), 0x0F);
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_CARRY));
cpu.memory.set_byte(0x43, 0x05);
cpu.registers.accumulator = 0x10;
exec_zp!(cpu, DCP, 0x43);
assert_eq!(cpu.memory.get_byte(0x43), 0x04);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_CARRY));
cpu.memory.set_byte(0x44, 0x00);
cpu.registers.accumulator = 0x00;
exec_zp!(cpu, DCP, 0x44);
assert_eq!(cpu.memory.get_byte(0x44), 0xFF);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
}
#[test]
fn isc_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.status.remove(Status::PS_DECIMAL_MODE);
cpu.memory.set_byte(0x42, 0x09);
cpu.registers.accumulator = 0x20;
cpu.registers.status.insert(Status::PS_CARRY);
exec_zp!(cpu, ISC, 0x42);
assert_eq!(cpu.memory.get_byte(0x42), 0x0A);
assert_eq!(cpu.registers.accumulator, 0x16);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
cpu.memory.set_byte(0x43, 0xFF);
cpu.registers.accumulator = 0x10;
cpu.registers.status.insert(Status::PS_CARRY);
exec_zp!(cpu, ISC, 0x43);
assert_eq!(cpu.memory.get_byte(0x43), 0x00);
assert_eq!(cpu.registers.accumulator, 0x10);
}
#[test]
fn slo_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_byte(0x42, 0x40);
cpu.registers.accumulator = 0x01;
exec_zp!(cpu, SLO, 0x42);
assert_eq!(cpu.memory.get_byte(0x42), 0x80);
assert_eq!(cpu.registers.accumulator, 0x81);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.memory.set_byte(0x43, 0x80);
cpu.registers.accumulator = 0x00;
exec_zp!(cpu, SLO, 0x43);
assert_eq!(cpu.memory.get_byte(0x43), 0x00);
assert_eq!(cpu.registers.accumulator, 0x00);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_ZERO));
}
#[test]
fn rla_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.status.remove(Status::PS_CARRY);
cpu.memory.set_byte(0x42, 0x40);
cpu.registers.accumulator = 0xFF;
exec_zp!(cpu, RLA, 0x42);
assert_eq!(cpu.memory.get_byte(0x42), 0x80);
assert_eq!(cpu.registers.accumulator, 0x80);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
cpu.registers.status.insert(Status::PS_CARRY);
cpu.memory.set_byte(0x43, 0x40);
cpu.registers.accumulator = 0xFF;
exec_zp!(cpu, RLA, 0x43);
assert_eq!(cpu.memory.get_byte(0x43), 0x81);
assert_eq!(cpu.registers.accumulator, 0x81);
}
#[test]
fn sre_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_byte(0x42, 0x02);
cpu.registers.accumulator = 0xFF;
exec_zp!(cpu, SRE, 0x42);
assert_eq!(cpu.memory.get_byte(0x42), 0x01);
assert_eq!(cpu.registers.accumulator, 0xFE);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
cpu.memory.set_byte(0x43, 0x01);
cpu.registers.accumulator = 0x00;
exec_zp!(cpu, SRE, 0x43);
assert_eq!(cpu.memory.get_byte(0x43), 0x00);
assert_eq!(cpu.registers.accumulator, 0x00);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
}
#[test]
fn rra_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.status.remove(Status::PS_DECIMAL_MODE);
cpu.registers.status.remove(Status::PS_CARRY);
cpu.memory.set_byte(0x42, 0x02);
cpu.registers.accumulator = 0x10;
exec_zp!(cpu, RRA, 0x42);
assert_eq!(cpu.memory.get_byte(0x42), 0x01);
assert_eq!(cpu.registers.accumulator, 0x11);
cpu.registers.status.insert(Status::PS_CARRY);
cpu.memory.set_byte(0x43, 0x02);
cpu.registers.accumulator = 0x00;
exec_zp!(cpu, RRA, 0x43);
assert_eq!(cpu.memory.get_byte(0x43), 0x81);
assert_eq!(cpu.registers.accumulator, 0x81);
}
#[test]
fn arr_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.accumulator = 0xFF;
cpu.registers.status.remove(Status::PS_CARRY);
exec_imm!(cpu, ARR, 0x55);
assert_eq!(cpu.registers.accumulator, 0x2A);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
cpu.registers.accumulator = 0xFF;
cpu.registers.status.insert(Status::PS_CARRY);
exec_imm!(cpu, ARR, 0x55);
assert_eq!(cpu.registers.accumulator, 0xAA);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
}
#[test]
fn sbx_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.accumulator = 0xFF;
cpu.registers.index_x = 0x0F;
exec_imm!(cpu, SBX, 0x05);
assert_eq!(cpu.registers.index_x, 0x0A);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
cpu.registers.accumulator = 0xFF;
cpu.registers.index_x = 0x0F;
exec_imm!(cpu, SBX, 0x10);
assert_eq!(cpu.registers.index_x, 0xFF);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn alr_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.accumulator = 0xFF;
exec_imm!(cpu, ALR, 0xAA);
assert_eq!(cpu.registers.accumulator, 0x55);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
cpu.registers.accumulator = 0xFF;
exec_imm!(cpu, ALR, 0x55);
assert_eq!(cpu.registers.accumulator, 0x2A);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
}
#[test]
fn anc_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.accumulator = 0xFF;
exec_imm!(cpu, ANC, 0x80);
assert_eq!(cpu.registers.accumulator, 0x80);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.accumulator = 0xFF;
exec_imm!(cpu, ANC, 0x7F);
assert_eq!(cpu.registers.accumulator, 0x7F);
assert!(!cpu.registers.status.contains(Status::PS_CARRY));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn xaa_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.index_x = 0xFF;
cpu.registers.accumulator = 0x00;
exec_imm!(cpu, XAA, 0x0F);
assert_eq!(cpu.registers.accumulator, 0x0F);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.index_x = 0xAA;
exec_imm!(cpu, XAA, 0xF0);
assert_eq!(cpu.registers.accumulator, 0xA0);
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.index_x = 0x55;
exec_imm!(cpu, XAA, 0xAA);
assert_eq!(cpu.registers.accumulator, 0x00);
assert!(cpu.registers.status.contains(Status::PS_ZERO));
}
#[test]
fn las_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.stack_pointer = StackPointer(0xFF);
cpu.memory.set_byte(0x1000, 0x0F);
exec_aby!(cpu, LAS, 0x1000);
assert_eq!(cpu.registers.accumulator, 0x0F);
assert_eq!(cpu.registers.index_x, 0x0F);
assert_eq!(cpu.registers.stack_pointer.0, 0x0F);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn usbc_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.status.remove(Status::PS_DECIMAL_MODE);
cpu.registers.accumulator = 0x20;
cpu.registers.status.insert(Status::PS_CARRY);
exec_imm!(cpu, USBC, 0x10);
assert_eq!(cpu.registers.accumulator, 0x10);
assert!(cpu.registers.status.contains(Status::PS_CARRY));
}
#[test]
fn jam_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
assert_eq!(cpu.wait_state, WaitState::Running);
exec_impl!(cpu, JAM);
assert_eq!(cpu.wait_state, WaitState::WaitingForReset);
assert!(!cpu.single_step());
}
#[test]
fn nop_variants_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
let initial_a = 0x42;
let initial_x = 0x13;
let initial_y = 0x37;
cpu.registers.accumulator = initial_a;
cpu.registers.index_x = initial_x;
cpu.registers.index_y = initial_y;
exec_imm!(cpu, NOPI, 0xFF);
assert_eq!(cpu.registers.accumulator, initial_a);
assert_eq!(cpu.registers.index_x, initial_x);
assert_eq!(cpu.registers.index_y, initial_y);
exec_zp!(cpu, NOPZ, 0x42);
assert_eq!(cpu.registers.accumulator, initial_a);
cpu.execute_instruction((
Instruction::NOPA,
AddressingMode::Absolute,
OpInput::UseAddress {
address: 0x1234,
page_crossed: false,
},
));
assert_eq!(cpu.registers.accumulator, initial_a);
cpu.execute_instruction((
Instruction::NOPAX,
AddressingMode::AbsoluteX,
OpInput::UseAddress {
address: 0x1234,
page_crossed: false,
},
));
assert_eq!(cpu.registers.accumulator, initial_a);
}
#[test]
fn dec_accumulator_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.accumulator = 0x05;
cpu.execute_instruction((
Instruction::DEC,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0x04);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.registers.accumulator = 0x01;
cpu.execute_instruction((
Instruction::DEC,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0x00);
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
Instruction::DEC,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0xFF);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn inc_accumulator_test() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.accumulator = 0xFE;
cpu.execute_instruction((
Instruction::INC,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0xFF);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
Instruction::INC,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0x00);
assert!(cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.execute_instruction((
Instruction::INC,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0x01);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
assert!(!cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn brk_pushes_bits_4_and_5_set() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_byte(0xFFFE, 0x00);
cpu.memory.set_byte(0xFFFF, 0x80);
cpu.registers.status = Status::from_bits_truncate(0x00);
cpu.execute_instruction((
Instruction::BRK,
AddressingMode::Implied,
OpInput::UseImplied,
));
let pushed_status = cpu.memory.get_byte(0x01FE);
assert_eq!(
pushed_status & 0x30,
0x30,
"BRK must push status with bits 4 (B) and 5 (unused) set, got {pushed_status:08b}"
);
}
#[test]
fn pla_negative_flag_from_pulled_value() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.push_on_stack(0x80);
cpu.registers.accumulator = 0x00;
cpu.execute_instruction((
Instruction::PLA,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.accumulator, 0x80);
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
}
#[test]
fn plx_ply_negative_flag_from_pulled_value() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.push_on_stack(0x80);
cpu.registers.index_x = 0x00;
cpu.execute_instruction((
Instruction::PLX,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.index_x, 0x80);
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
cpu.push_on_stack(0x80);
cpu.registers.index_y = 0x00;
cpu.execute_instruction((
Instruction::PLY,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.registers.index_y, 0x80);
assert!(cpu.registers.status.contains(Status::PS_NEGATIVE));
}
#[test]
fn trb_resets_bits_in_memory() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_byte(0x50, 0xFF);
cpu.registers.accumulator = 0b0000_1111;
cpu.execute_instruction((
Instruction::TRB,
AddressingMode::Absolute,
OpInput::UseAddress {
address: 0x50,
page_crossed: false,
},
));
assert_eq!(cpu.memory.get_byte(0x50), 0xF0);
assert!(!cpu.registers.status.contains(Status::PS_ZERO));
cpu.memory.set_byte(0x50, 0xAA);
cpu.registers.accumulator = 0x00;
cpu.execute_instruction((
Instruction::TRB,
AddressingMode::Absolute,
OpInput::UseAddress {
address: 0x50,
page_crossed: false,
},
));
assert_eq!(cpu.memory.get_byte(0x50), 0xAA); assert!(cpu.registers.status.contains(Status::PS_ZERO));
}
#[test]
fn tsb_sets_bits_in_memory() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_byte(0x50, 0x00);
cpu.registers.accumulator = 0b0000_1111;
cpu.execute_instruction((
Instruction::TSB,
AddressingMode::Absolute,
OpInput::UseAddress {
address: 0x50,
page_crossed: false,
},
));
assert_eq!(cpu.memory.get_byte(0x50), 0x0F);
assert!(cpu.registers.status.contains(Status::PS_ZERO));
cpu.memory.set_byte(0x50, 0x0F);
cpu.registers.accumulator = 0x0F;
cpu.execute_instruction((
Instruction::TSB,
AddressingMode::Absolute,
OpInput::UseAddress {
address: 0x50,
page_crossed: false,
},
));
assert_eq!(cpu.memory.get_byte(0x50), 0x0F); assert!(!cpu.registers.status.contains(Status::PS_ZERO));
}
#[test]
fn rmb_clears_individual_bits() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
for bit in 0u8..8 {
let instr = Instruction::RMB(bit);
cpu.memory.set_byte(0x10, 0xFF);
cpu.execute_instruction((
instr,
AddressingMode::ZeroPage,
OpInput::UseAddress {
address: 0x10,
page_crossed: false,
},
));
assert_eq!(
cpu.memory.get_byte(0x10),
!(1 << bit),
"RMB{bit} should clear bit {bit}"
);
}
}
#[test]
fn smb_sets_individual_bits() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
for bit in 0u8..8 {
let instr = Instruction::SMB(bit);
cpu.memory.set_byte(0x10, 0x00);
cpu.execute_instruction((
instr,
AddressingMode::ZeroPage,
OpInput::UseAddress {
address: 0x10,
page_crossed: false,
},
));
assert_eq!(
cpu.memory.get_byte(0x10),
1 << bit,
"SMB{bit} should set bit {bit}"
);
}
}
#[test]
fn bbr_branches_when_bit_is_clear() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_byte(0x20, 0b0010_0000);
let start_pc = 0x1000u16;
cpu.registers.program_counter = start_pc;
cpu.execute_instruction((
Instruction::BBR(3),
AddressingMode::ZeroPageRelative,
OpInput::UseBitBranch {
zp_address: 0x20,
relative: 5,
},
));
assert_eq!(cpu.registers.program_counter, start_pc.wrapping_add(5));
cpu.registers.program_counter = start_pc;
cpu.execute_instruction((
Instruction::BBR(5),
AddressingMode::ZeroPageRelative,
OpInput::UseBitBranch {
zp_address: 0x20,
relative: 5,
},
));
assert_eq!(cpu.registers.program_counter, start_pc);
}
#[test]
fn bbs_branches_when_bit_is_set() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_byte(0x20, 0b0010_0000);
let start_pc = 0x1000u16;
cpu.registers.program_counter = start_pc;
cpu.execute_instruction((
Instruction::BBS(5),
AddressingMode::ZeroPageRelative,
OpInput::UseBitBranch {
zp_address: 0x20,
relative: 10,
},
));
assert_eq!(cpu.registers.program_counter, start_pc.wrapping_add(10));
cpu.registers.program_counter = start_pc;
cpu.execute_instruction((
Instruction::BBS(3),
AddressingMode::ZeroPageRelative,
OpInput::UseBitBranch {
zp_address: 0x20,
relative: 10,
},
));
assert_eq!(cpu.registers.program_counter, start_pc);
}
#[test]
fn bbr_backward_branch() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.memory.set_byte(0x30, 0x00);
let start_pc = 0x1010u16;
let rel = (-8_i16).cast_unsigned();
cpu.registers.program_counter = start_pc;
cpu.execute_instruction((
Instruction::BBR(0),
AddressingMode::ZeroPageRelative,
OpInput::UseBitBranch {
zp_address: 0x30,
relative: rel,
},
));
assert_eq!(
cpu.registers.program_counter,
start_pc.wrapping_add(rel),
"BBR(0) backward branch should wrap correctly"
);
}
}
#[cfg(test)]
mod cycle_timing_tests {
use super::*;
use crate::instruction::{Cmos6502, Instruction, Nmos6502};
use crate::memory::Memory as Ram;
#[test]
fn test_basic_cycle_counting() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
assert_eq!(cpu.cycles, 0);
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::Immediate,
OpInput::UseImmediate(0x42),
));
assert_eq!(cpu.cycles, 2);
cpu.execute_instruction((
Instruction::NOP,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.cycles, 4);
cpu.execute_instruction((
Instruction::ADC,
AddressingMode::Immediate,
OpInput::UseImmediate(0x01),
));
assert_eq!(cpu.cycles, 6);
}
#[test]
fn test_decimal_mode_penalty_65c02() {
let mut cpu = CPU::new(Ram::new(), Cmos6502);
assert_eq!(cpu.cycles, 0);
cpu.execute_instruction((
Instruction::ADC,
AddressingMode::Immediate,
OpInput::UseImmediate(0x01),
));
assert_eq!(cpu.cycles, 2);
cpu.set_flag(Status::PS_DECIMAL_MODE);
cpu.execute_instruction((
Instruction::ADC,
AddressingMode::Immediate,
OpInput::UseImmediate(0x01),
));
assert_eq!(cpu.cycles, 5); }
#[test]
fn test_no_decimal_penalty_on_nmos() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.set_flag(Status::PS_DECIMAL_MODE);
cpu.execute_instruction((
Instruction::ADC,
AddressingMode::Immediate,
OpInput::UseImmediate(0x01),
));
assert_eq!(cpu.cycles, 2);
}
#[test]
fn test_various_instruction_cycles() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.execute_instruction((
Instruction::BRK,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.cycles, 7);
cpu.cycles = 0;
cpu.execute_instruction((
Instruction::JSR,
AddressingMode::Absolute,
OpInput::UseAddress {
address: 0x1234,
page_crossed: false,
},
));
assert_eq!(cpu.cycles, 6);
cpu.cycles = 0;
cpu.execute_instruction((
Instruction::ASL,
AddressingMode::Accumulator,
OpInput::UseImplied,
));
assert_eq!(cpu.cycles, 2);
cpu.cycles = 0;
cpu.execute_instruction((
Instruction::ASL,
AddressingMode::ZeroPage,
OpInput::UseAddress {
address: 0x00,
page_crossed: false,
},
));
assert_eq!(cpu.cycles, 5);
}
#[test]
fn test_cycles_accumulate() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
for _ in 0..10 {
cpu.execute_instruction((
Instruction::NOP,
AddressingMode::Implied,
OpInput::UseImplied,
));
}
assert_eq!(cpu.cycles, 20); }
#[test]
fn test_page_crossing_detection() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.index_x = 0xFF;
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::AbsoluteX,
OpInput::UseAddress {
address: 0x12FF,
page_crossed: false,
},
));
assert_eq!(cpu.cycles, 4);
cpu.cycles = 0;
cpu.registers.index_x = 0x02;
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::AbsoluteX,
OpInput::UseAddress {
address: 0x1301,
page_crossed: true,
},
));
assert_eq!(cpu.cycles, 5);
cpu.cycles = 0;
cpu.registers.index_y = 0x01;
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::AbsoluteY,
OpInput::UseAddress {
address: 0x1100,
page_crossed: true,
},
));
assert_eq!(cpu.cycles, 5);
cpu.cycles = 0;
cpu.registers.index_x = 0x02;
cpu.execute_instruction((
Instruction::STA,
AddressingMode::AbsoluteX,
OpInput::UseAddress {
address: 0x1301,
page_crossed: true,
},
));
assert_eq!(cpu.cycles, 5);
}
#[test]
fn test_page_crossing_edge_cases() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.index_x = 0xFF;
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::AbsoluteX,
OpInput::UseAddress {
address: 0x00FE,
page_crossed: true,
},
));
assert_eq!(cpu.cycles, 5);
cpu.cycles = 0;
cpu.registers.index_x = 0x00;
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::AbsoluteX,
OpInput::UseAddress {
address: 0x1200,
page_crossed: false,
},
));
assert_eq!(cpu.cycles, 4);
}
#[test]
fn test_indirect_indexed_page_crossing() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.index_y = 0x10;
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::IndirectIndexedY,
OpInput::UseAddress {
address: 0x1300,
page_crossed: true,
},
));
assert_eq!(cpu.cycles, 6);
cpu.cycles = 0;
cpu.registers.index_y = 0x05;
cpu.execute_instruction((
Instruction::LDA,
AddressingMode::IndirectIndexedY,
OpInput::UseAddress {
address: 0x1205,
page_crossed: false,
},
));
assert_eq!(cpu.cycles, 5);
}
struct TestMemory {
ram: Ram,
nmi: bool,
irq: bool,
}
impl TestMemory {
fn new() -> Self {
TestMemory {
ram: Ram::new(),
nmi: false,
irq: false,
}
}
}
impl Bus for TestMemory {
fn get_byte(&mut self, address: u16) -> u8 {
self.ram.get_byte(address)
}
fn set_byte(&mut self, address: u16, value: u8) {
self.ram.set_byte(address, value);
}
fn nmi_pending(&mut self) -> bool {
self.nmi
}
fn irq_pending(&mut self) -> bool {
self.irq
}
}
#[test]
fn test_irq_handling() {
use crate::instruction::Nmos6502;
let mut cpu = CPU::new(TestMemory::new(), Nmos6502);
cpu.memory.set_word(IRQ_INTERRUPT_VECTOR_LO, 0x8000);
cpu.registers.program_counter = 0x0200;
let initial_sp = cpu.registers.stack_pointer.0;
cpu.registers.status.remove(Status::PS_DISABLE_INTERRUPTS);
cpu.memory.irq = true;
cpu.memory.set_byte(0x0200, 0xEA); cpu.single_step();
assert_eq!(cpu.registers.program_counter, 0x8000);
assert!(cpu.registers.status.contains(Status::PS_DISABLE_INTERRUPTS));
assert_eq!(cpu.registers.stack_pointer.0, initial_sp.wrapping_sub(3));
}
#[test]
fn test_nmi_handling() {
use crate::instruction::Nmos6502;
let mut cpu = CPU::new(TestMemory::new(), Nmos6502);
cpu.memory.set_word(NMI_INTERRUPT_VECTOR_LO, 0x9000);
cpu.memory.nmi = false;
cpu.registers.program_counter = 0x0200;
cpu.memory.set_byte(0x0200, 0xEA);
cpu.single_step();
cpu.memory.nmi = true;
cpu.memory.set_byte(0x0201, 0xEA);
cpu.single_step();
assert_eq!(cpu.registers.program_counter, 0x9000);
}
#[test]
fn test_wai_waits_for_interrupt() {
use crate::instruction::{AddressingMode, Cmos6502, Instruction, OpInput};
let mut cpu = CPU::new(TestMemory::new(), Cmos6502);
cpu.memory.set_word(IRQ_INTERRUPT_VECTOR_LO, 0x8000);
cpu.registers.status.remove(Status::PS_DISABLE_INTERRUPTS);
cpu.execute_instruction((
Instruction::WAI,
AddressingMode::Implied,
OpInput::UseImplied,
));
assert_eq!(cpu.wait_state, WaitState::WaitingForInterrupt);
cpu.memory.irq = true;
cpu.single_step();
assert_eq!(cpu.wait_state, WaitState::Running);
assert_eq!(cpu.registers.program_counter, 0x8000);
}
#[test]
fn test_branch_cycle_timing() {
let mut cpu = CPU::new(Ram::new(), Nmos6502);
cpu.registers.status.remove(Status::PS_ZERO);
cpu.registers.program_counter = 0x1000;
cpu.execute_instruction((
Instruction::BEQ,
AddressingMode::Relative,
OpInput::UseRelative(0x10), ));
assert_eq!(cpu.cycles, 2, "Branch not taken should be 2 cycles");
cpu.cycles = 0;
cpu.registers.status.insert(Status::PS_ZERO);
cpu.registers.program_counter = 0x1000;
cpu.execute_instruction((
Instruction::BEQ,
AddressingMode::Relative,
OpInput::UseRelative(0x10), ));
assert_eq!(cpu.cycles, 3, "Branch taken (same page) should be 3 cycles");
cpu.cycles = 0;
cpu.registers.status.insert(Status::PS_ZERO);
cpu.registers.program_counter = 0x10F0;
cpu.execute_instruction((
Instruction::BEQ,
AddressingMode::Relative,
OpInput::UseRelative(0x10), ));
assert_eq!(
cpu.cycles, 4,
"Branch taken (page cross) should be 4 cycles"
);
cpu.cycles = 0;
cpu.registers.status.insert(Status::PS_ZERO);
cpu.registers.program_counter = 0x1000;
cpu.execute_instruction((
Instruction::BNE,
AddressingMode::Relative,
OpInput::UseRelative(0x10),
));
assert_eq!(cpu.cycles, 2, "BNE not taken should be 2 cycles");
cpu.cycles = 0;
cpu.registers.status.remove(Status::PS_ZERO);
cpu.registers.program_counter = 0x1000;
cpu.execute_instruction((
Instruction::BNE,
AddressingMode::Relative,
OpInput::UseRelative(0x20), ));
assert_eq!(cpu.cycles, 3, "BNE taken (same page) should be 3 cycles");
cpu.cycles = 0;
cpu.registers.status.insert(Status::PS_ZERO);
cpu.registers.program_counter = 0x1100;
cpu.execute_instruction((
Instruction::BEQ,
AddressingMode::Relative,
OpInput::UseRelative((-0x10_i16).cast_unsigned()), ));
assert_eq!(
cpu.cycles, 4,
"Backward branch (page cross) should be 4 cycles"
);
}
}