You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

999 lines
37 KiB

use std::collections::HashMap;
// This is all new to me so it's heavily commented so we can understand what is happening.
use bitflags::bitflags;
use crate::opcodes;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CpuFlags: u8 {
// Represents whether the last operation caused a carry or borrow
const CARRY = 0b00000001;
// Set if the result of the last operation was zero
const ZERO = 0b00000010;
// Disables interrupts when set
const INTERRUPT_DISABLE = 0b00000100;
// Enables binary-coded decimal arithmetic
const DECIMAL_MODE = 0b00001000;
// Indicates the CPU is handling in interupt
const BREAK = 0b00010000;
// A duplicate "break" flag, as both bit 4 and 5 are set by the BRK instruction
// Bit 5 is not explicitly used on NES
const BREAK2 = 0b00100000;
// Set if an arithmetic operation caused an overflow (adding two positive numbers results
// in a negative value)
const OVERFLOW = 0b01000000;
// Indicates whether the result of the last operation was negative - determined by the MSB
// of the result
const NEGATIVE = 0b10000000;
}
}
const STACK: u16 = 0x0100;
const STACK_RESET: u8 = 0xfd;
#[derive(Debug)]
#[allow(non_camel_case_types)]
// AddressingMode is a method used by a CPU to determine where the operand (data or memory
// location) for an instruction comes from. It basically defines how the CPU finds the data it
// needs to execute a given instruction.
//
// The zero page term refers to the first 256 bytes (from 0x0000 to 0x00FF). It's called zero page
// because the high page is always zero (e.g. 00XX). Instructions in this range use fewer bytes and
// cycles compared to acessing memory in other regions. This is due to the fact that only the low
// bytes need to be specified which makes the instruction shorter and faster.
pub enum AddressingMode {
// Operand is provided directly as part of the instruction
Immediate,
//Operand is located in the zero page (memory address 0x00 to 0xFF)
ZeroPage,
// Operand is in the zero page, with an offet from the X register
ZeroPage_X,
// Operand is in the zero page, with an offet from the Y register
ZeroPage_Y,
// Operand is at the full 16-bit address (0x0000 to 0xFFFF)
Absolute,
// Operand is at an absolute address, with an offset from the X register
Absolute_X,
// Operand is at an absolute address, with an offset from the Y register
Absolute_Y,
// Operand is at an address stored in a zero-page address plus an X offset
Indirect_X,
// Operand is at an address stored in a zero-page address plus an Y offset
Indirect_Y,
// No addressing mode (used for instructions that don't require an operand like NOP)
NoneAddressing,
}
pub struct CPU {
// The accumulator register is a specific register used for arithmetic and logic operations.
// The cpu instruction loads a value into the accumulator register and then updates certain
// flags in the processor status register to relect the operation of the result.
pub register_a: u8,
// The Process Status Register is a collection of individual bits (flags) that represent the
// current state of the CPU. Each bit has purpose such as if a calculation resulted in zero or
// if the result is negative
//
// Zero flag:
// - Bit 1 of the status register
// - Set if register_a == 0, cleared otherwise
//
// Negative flag (N) - indicates whether the result of the most recent operation is negative
// - Bit 7 of the status register (most significant bit is Bit 7 because it's zero based)
// - Set if the most significant bit of register_a is 1, cleared otherwise
pub status: CpuFlags,
// track our current position in the program
pub program_counter: u16,
// most commonly used to hold counters or offsets for accessing memory. The value can be loaded
// and saved in memory, compared with values held in memory or incremented and decremented.
//
// X register has one special function. It can be used to copy a stack pointer or change it's
// value.
pub register_x: u8,
// used to assist in memory addressing and certain operations. Most commonly used as an offset
// for addressing memory. Can also be used as temporary storage space or to manipulate data for
// computations
pub register_y: u8,
// this creates an array with size of 0xFFFF and initializes all elements to 0 (see below in t
// he new function). 0xFFFF is hexadecimal for 65535 in decimal. This defines the size of the
// array with 65535 elements, the typical size for a 6502 CPU system.
memory: [u8; 0xFFFF],
pub stack_pointer: u8,
}
pub trait Mem {
fn mem_read(&self, addr: u16) -> u8;
fn mem_write(&mut self, addr: u16, data: u8);
// combines two consecutives bytes in memory into a single 16 bit value
fn mem_read_u16(&mut self, pos: u16) -> u16 {
// read the low byte (pos)
let lo = self.mem_read(pos) as u16;
// read the high byte (pos + 1)
let hi = self.mem_read(pos + 1) as u16;
// combine the bytes. Shelf the high byte 8 bits to the left, effectively moving it into
// the upper 8 bits of a 16-bit value. This will combine to a single 16 bit value.
(hi << 8) | (lo as u16)
}
// split a 16-bit number into 2 8-bit pieces
fn mem_write_u16(&mut self, pos: u16, data: u16) {
// take the top half of the 16 bit number
let hi = (data >> 8) as u8;
// take the bottom half of the 16 bit number
let lo = (data & 0xff) as u8;
// store low byte in the first compartment of memory (pos)
self.mem_write(pos, lo);
// store the high byte in the next compartment (pos + 1)
self.mem_write(pos + 1, hi)
}
}
impl Mem for CPU {
fn mem_read(&self, addr: u16) -> u8 {
self.memory[addr as usize]
}
fn mem_write(&mut self, addr: u16, data: u8) {
self.memory[addr as usize] = data;
}
}
impl CPU {
pub fn new() -> Self {
CPU {
register_a: 0,
status: CpuFlags::from_bits_truncate(0b100100),
program_counter: 0,
register_x: 0,
register_y: 0,
memory: [0; 0xFFFF],
stack_pointer: STACK_RESET,
}
}
/// Determines the memory address for the operand based on the specified addressing mode.
///
/// # Arguments
/// - `mode`: The addressing mode, which speicifies how the oeprand is accessed.
///
/// # Returns
/// - A 16 bit memory address where the operand can be found
fn get_operand_address(&mut self, mode: &AddressingMode) -> u16 {
match mode {
// Immediate mode: The operand is part of the instruction itself, located at the
// program counter
AddressingMode::Immediate => self.program_counter,
// ZeroPage mode: The operand is in the zero page (first 256 bytes of memory), with the
// address specificed as a single byte at the program counter.
AddressingMode::ZeroPage => self.mem_read(self.program_counter) as u16,
// Absolute mode: The operand's address is a full 16-bit address, stored in two
// consecutive bytes starting at the program counter
AddressingMode::Absolute => self.mem_read_u16(self.program_counter),
// ZeroPage_X mode: The operand is in the zero page, with it's address calculated by
// adding the X register to a base address stored at the program counter
AddressingMode::ZeroPage_X => {
let pos = self.mem_read(self.program_counter);
let addr = pos.wrapping_add(self.register_x) as u16;
addr
}
// ZeroPage_Y mode: Similar to ZeroPage_X, but the address is calculated
// using the Y register instead of the X register.
AddressingMode::ZeroPage_Y => {
let pos = self.mem_read(self.program_counter);
let addr = pos.wrapping_add(self.register_y) as u16;
addr
}
// Absolute_X mode: The operand's address is calculated by adding the X register
// to a 16-bit base address stored at the program counter.
AddressingMode::Absolute_X => {
let base = self.mem_read_u16(self.program_counter);
let addr = base.wrapping_add(self.register_x as u16);
addr
}
// Absolute_Y mode: Similar to Absolute_X, but the address is calculated
// by adding the Y register to the 16-bit base address.
AddressingMode::Absolute_Y => {
let base = self.mem_read_u16(self.program_counter);
let addr = base.wrapping_add(self.register_x as u16);
addr
}
// Indirect_X mode: The operand's address is calculated by first adding the X register
// to a zero-page base address, then fetching the actual 16-bit address from
// the zero page.
AddressingMode::Indirect_X => {
// read the single byte from the program counter. This is our base.
let base = self.mem_read(self.program_counter);
// Take the base and add the value in the X register. If the result goes passed
// 0xFF, it wraps back around to 0x00 (like starting over in a circle). This is
// essentially behaving like a ring buffer. Using wrapping_add ensures the
// calculation stays within the valid range without requiring additional checks
let ptr: u8 = (base as u8).wrapping_add(self.register_x);
// Read the low byte of the memory address from memory at ptr. The goal here is
// to fetch the 16 byte address from the zero page using the pointer.
let lo = self.mem_read(ptr as u16);
// read the high byte of the memory address from memory at ptr + 1
let hi = self.mem_read(ptr.wrapping_add(1) as u16);
// take the high byte and shift it left by 8 bits to place it in the upper half of
// the 16 bit value. Then take the low byte and keep it in the lower half of the 16
// bit value. Combine the two values using the bitwise OR (|) operator to form a
// full 16 bit value.
(hi as u16) << 8 | (lo as u16)
}
// Indirect_Y mode: The operand's address is calculated by first fetching a 16-bit address
// from a zero-page base address, then adding the Y register to it.
AddressingMode::Indirect_Y => {
// read from the program counter (an 8 bit value). This pointer specifies a zero
// page address where the actual 16 bit address is stored.
let base = self.mem_read(self.program_counter);
// read the low byte of the final address from the zero page address specificed by
// `base`
let lo = self.mem_read(base as u16);
// read the high byte of the final address from the next consecutive zero-page
// address (base +1). wrapping_add(1) ensures that if base = 0xFF, it wraps around
// the 0x00
let hi = self.mem_read((base as u8).wrapping_add(1) as u16);
// Combine the low and high bytes into a 16 bit address
let deref_base = (hi as u16) << 8 | (lo as u16);
// Add the Y Registered to the dereferenced address
let deref = deref_base.wrapping_add(self.register_y as u16);
// Return the final address
deref
}
// NoneAddressing: This mode is used for instructions that don't require an operand
// or don't involve memory access.
AddressingMode::NoneAddressing => {
panic!("mode {:?} is not supported", mode);
}
}
}
/// Road the program, reset the state of the cpu and then run the program.
pub fn load_and_run(&mut self, program: Vec<u8>) {
self.load(program);
self.program_counter = self.mem_read_u16(0xFFFC);
self.run();
}
/// Reset the state of the CPU
pub fn reset(&mut self) {
self.register_a = 0;
self.register_x = 0;
self.status = CpuFlags::from_bits_truncate(0b100100);
self.program_counter = self.mem_read_u16(0xFFFC);
}
/// Load the program code into memroy starting at address 0x8000. 0x8000 .. 0xFFFF is reserved
/// from program ROM and we can assume that the instructions should start somewhere here.
pub fn load(&mut self, program: Vec<u8>) {
self.memory[0x0600..(0x0600 + program.len())].copy_from_slice(&program[..]);
self.mem_write_u16(0xFFFC, 0x0600);
}
/// Load a value from memory into the Y register
fn ldy(&mut self, mode: &AddressingMode) {
// compute the memory address based on the given addressing mode
let addr = self.get_operand_address(mode);
// fetch the data from memory
let data = self.mem_read(addr);
// store data in register y
self.register_y = data;
// Update the CpuFlags based on the new value of register y
self.update_zero_and_negative_flags(self.register_y);
}
/// LDA stands for Load X Register
/// Load a value from memory into the x register and updates the CPU status flags based on the
/// new value.
fn ldx(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode);
let data = self.mem_read(addr);
self.register_x = data;
self.update_zero_and_negative_flags(self.register_x);
}
/// LDA stands for Load Accumulator. It loads a value into the accumulator register. It affects
/// the Zero Flag and Negative Flag.
fn lda(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(&mode);
let value = self.mem_read(addr);
self.set_register_a(value);
}
/// TAX instruction copies the value from teh accumulator register (register_a) into the x
/// register. The way this works is that the CPU takes the current value in the A register,
/// copies it to the X register, updates the Zero Flag and Negative Flag in the status register.
///
/// The Zero Flag is set if the value is copied to the X register is 0
/// The Negative Flag is set if the most significant bit of the vlaue (Bit 7) is 1
///
/// In the TAX instruction, we don't need any arguments because it always stransfers data from A
/// to X.
fn tax(&mut self) {
self.register_x = self.register_a;
self.update_zero_and_negative_flags(self.register_x);
}
/// STA stands for Store Accumulator. This instruction takes the value in the accumulator
/// register (register_a) and stores it in a specified memory location.
///
/// How this works is that the CPU reads the value currently in the accumulator (register_a)
/// The CPu then writes this value to the specified memory address.
fn sta(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode);
self.mem_write(addr, self.register_a);
}
/// Perform a bitwise AND operation betwen the value in register_a and a value fetched from
/// memory (determined by the addressing mode)
///
/// The result is stored back in register_a. This is useful for isolating specific bits in the
/// accumulator.
fn and(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode);
let data = self.mem_read(addr);
self.set_register_a(data & self.register_a);
}
/// EOR stands for Exclusive OR.
/// Performs a bitwise exclosive OR operation between a vlaue from memory and the Accumulator
/// register (register_a), then stores the value back in the accumulator
fn eor(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode);
let data = self.mem_read(addr);
self.set_register_a(data ^ self.register_a);
}
/// ORA stands for OR Accumulator.
/// Perform a bitwise inclusive OR operation between a vlaue fetched from emmory and the
/// Accumulator (register_a), then stores the value back in the Accumulator
fn ora(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode);
let data = self.mem_read(addr);
self.set_register_a(data | self.register_a);
}
/// This is used to update the status register flags based on the operation. It updates the Zero
/// Flag and the Negative Flag.
///
/// This will ensure that the CPU's status register reflects whether the result is zerl (update
/// the Zero Flag) and if the result is negative (update the Negative Flag)
fn update_zero_and_negative_flags(&mut self, result: u8) {
if result == 0 {
self.status.insert(CpuFlags::ZERO)
} else {
self.status.remove(CpuFlags::ZERO);
}
if result >> 7 == 1 {
self.status.insert(CpuFlags::NEGATIVE)
} else {
self.status.remove(CpuFlags::NEGATIVE);
}
}
/// Update the Negative Flag in the status register based on the most significant bit of the
/// result
fn update_negative_flags(&mut self, result: u8) {
// shifting right by 7 places moves the 7th bit to the least significant position (thus
// isolating it)
if result >> 7 == 1 {
self.status.insert(CpuFlags::NEGATIVE);
} else {
self.status.remove(CpuFlags::NEGATIVE);
}
}
fn set_carry_flag(&mut self) {
self.status.insert(CpuFlags::CARRY);
}
fn clear_carry_flag(&mut self) {
self.status.remove(CpuFlags::CARRY);
}
/// Simulate the ADC (Add with Carry) instruction. It adds a value (data) to the accumulator
/// register (register_a), optionally including the carry flag and updates the status falgs
/// accordingly.
fn add_to_register_a(&mut self, data: u8) {
// Add register_a, data and carry flag togehter. All values are cast to u16 to prevent
// overflow during the calculation (since the result might exceed 8 bits).
let sum = self.register_a as u16
+ data as u16
+ (if self.status.contains(CpuFlags::CARRY) {
1
} else {
0
}) as u16;
let carry = sum > 0xff;
// the carry flag is set if the result exceeds 255 (0xFF), meaning the additon produced a
// value that requires more than 8 bits to store.
if carry {
// this happens if the sum = 0x101
self.status.insert(CpuFlags::CARRY);
} else {
// this happens if sum = 0xFE
self.status.remove(CpuFlags::CARRY);
}
// reduce the sum down to a u8 which will discord any overflow beyond 8 bits
let result = sum as u8;
// update the overflow flag to be set if there is a signed arithmetic overflow.
// A signed arithmetic overflow occurs when adding two signed numbers produces a result
// that is outside th range of a signed 8-bit value (-128 to 127)
//
// `data ^ result` checks if the sign of data is different from the sign of result
// `result ^ self.register_a` checks if the sign of result is different from the sign of
// register_a.
// `& 0x80` isolates the most significant bit whcih indicates the sign in signed arithmetic.
if (data ^ result) & (result ^ self.register_a) & 0x80 != 0 {
self.status.insert(CpuFlags::OVERFLOW)
} else {
self.status.remove(CpuFlags::OVERFLOW);
}
// store the final 8-bit result back in the accumulator
self.set_register_a(result);
}
/// This will set the value of the A register (register_A)
fn set_register_a(&mut self, value: u8) {
self.register_a = value;
self.update_zero_and_negative_flags(self.register_a);
}
/// SBC stands for Subtract with Borrow. It is the counter to ADC (Add with Carry) instruction
/// and performs subtraction while considering the Carry Flag. This works with unsigned binary
/// numbers or two's complement signed numbers.
///
/// The Carry flag represents the absence of a borrow.
/// The Overflow flag is set if the result goes outside the range of a signed 8-bit value
/// The Negative flag and Zero flag are updated based on the result
fn sbc(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode);
let data = self.mem_read(addr);
self.add_to_register_a(((data as i8).wrapping_neg().wrapping_sub(1)) as u8);
}
/// ADC stands for Add with Carry
/// Perform addition between the Accumulator and a value fetched from memory, taking into
/// account the carry flag
fn adc(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode);
let value = self.mem_read(addr);
self.add_to_register_a(value);
}
fn stack_pop(&mut self) -> u8 {
self.stack_pointer = self.stack_pointer.wrapping_add(1);
self.mem_read((STACK as u16) + self.stack_pointer as u16)
}
fn stack_push(&mut self, data: u8) {
self.mem_write((STACK as u16) + self.stack_pointer as u16, data);
self.stack_pointer = self.stack_pointer.wrapping_sub(1)
}
fn stack_push_u16(&mut self, data: u16) {
let hi = (data >> 8) as u8;
let lo = (data & 0xff) as u8;
self.stack_push(hi);
self.stack_push(lo);
}
fn stack_pop_u16(&mut self) -> u16 {
let lo = self.stack_pop() as u16;
let hi = self.stack_pop() as u16;
hi << 8 | lo
}
/// ASL stands for Arithmetic Shift Left
/// Shift all bits in the Accumulator one position to the left, which effectively multiplies
/// the value by two. It also updates the carry flag and handles the overflow of the leftmost
/// bit.
fn asl_accumulator(&mut self) {
let mut data = self.register_a;
// shift all the bits of data to the right by 6 positions
if data >> 6 == 1 {
// set the carry flag to indicate overflow
self.set_carry_flag();
} else {
self.clear_carry_flag();
}
data = data << 1;
self.set_register_a(data);
}
fn lsr_accumulator(&mut self) {
let mut data = self.register_a;
if data & 1 == 1 {
self.set_carry_flag();
} else {
self.clear_carry_flag();
}
data = data >> 1;
self.set_register_a(data)
}
fn asl(&mut self, mode: &AddressingMode) -> u8 {
let addr = self.get_operand_address(mode);
let mut data = self.mem_read(addr);
if data >> 7 == 1 {
self.set_carry_flag();
} else {
self.clear_carry_flag();
}
data = data << 1;
self.mem_write(addr, data);
self.update_zero_and_negative_flags(data);
data
}
fn lsr(&mut self, mode: &AddressingMode) -> u8 {
let addr = self.get_operand_address(mode);
let mut data = self.mem_read(addr);
if data & 1 == 1 {
self.set_carry_flag();
} else {
self.clear_carry_flag();
}
data = data >> 1;
self.mem_write(addr, data);
self.update_zero_and_negative_flags(data);
data
}
fn rol(&mut self, mode: &AddressingMode) -> u8 {
let addr = self.get_operand_address(mode);
let mut data = self.mem_read(addr);
let _old_carry = self.status.contains(CpuFlags::CARRY);
if data >> 7 == 1 {
self.set_carry_flag();
} else {
self.clear_carry_flag();
}
data = data << 1;
if _old_carry {
data = data | 1;
}
self.mem_write(addr, data);
self.update_negative_flags(data);
data
}
fn rol_accumulator(&mut self) {
let mut data = self.register_a;
let old_carry = self.status.contains(CpuFlags::CARRY);
if data >> 7 == 1 {
self.set_carry_flag();
} else {
self.clear_carry_flag();
}
data = data << 1;
if old_carry {
data = data | 1;
}
self.set_register_a(data);
}
fn ror(&mut self, mode: &AddressingMode) -> u8 {
let addr = self.get_operand_address(mode);
let data = self.mem_read(addr);
if data & 1 == 1 {
self.set_carry_flag();
} else {
self.clear_carry_flag();
}
self.mem_write(addr, data);
self.update_negative_flags(data);
data
}
fn ror_accumulator(&mut self) {
let mut data = self.register_a;
let old_carry = self.status.contains(CpuFlags::CARRY);
if data & 1 == 1 {
self.set_carry_flag();
} else {
self.clear_carry_flag();
}
data = data >> 1;
if old_carry {
data = data | 0b10000000;
}
self.set_register_a(data);
}
fn inc(&mut self, mode: &AddressingMode) -> u8 {
let addr = self.get_operand_address(mode);
let mut data = self.mem_read(addr);
data = data.wrapping_add(1);
self.mem_write(addr, data);
self.update_zero_and_negative_flags(data);
data
}
fn dey(&mut self) {
self.register_y = self.register_y.wrapping_sub(1);
self.update_zero_and_negative_flags(self.register_y);
}
fn dex(&mut self) {
self.register_x = self.register_x.wrapping_sub(1);
self.update_zero_and_negative_flags(self.register_x);
}
fn dec(&mut self, mode: &AddressingMode) -> u8 {
let addr = self.get_operand_address(mode);
let mut data = self.mem_read(addr);
data = data.wrapping_sub(1);
self.mem_write(addr, data);
self.update_zero_and_negative_flags(data);
data
}
fn pla(&mut self) {
let data = self.stack_pop();
self.set_register_a(data);
}
fn plp(&mut self) {
let raw_bits = self.stack_pop();
// update the status register
self.status = CpuFlags::from_bits_truncate(raw_bits);
// update flags
self.status.remove(CpuFlags::BREAK);
self.status.insert(CpuFlags::BREAK2);
}
fn php(&mut self) {
let mut flags = self.status.clone();
flags.insert(CpuFlags::BREAK);
flags.insert(CpuFlags::BREAK2);
self.stack_push(flags.bits());
}
fn bit(&mut self, mode: &AddressingMode) {
let addr = self.get_operand_address(mode);
let data = self.mem_read(addr);
let and = self.register_a & data;
if and == 0 {
self.status.insert(CpuFlags::ZERO);
} else {
self.status.remove(CpuFlags::ZERO);
}
self.status.set(CpuFlags::NEGATIVE, data & 0b10000000 > 0);
self.status.set(CpuFlags::OVERFLOW, data & 0b01000000 > 0);
}
fn compare(&mut self, mode: &AddressingMode, compare_with: u8) {
let addr = self.get_operand_address(mode);
let data = self.mem_read(addr);
if data <= compare_with {
self.status.insert(CpuFlags::CARRY);
} else {
self.status.remove(CpuFlags::CARRY);
}
self.update_zero_and_negative_flags(compare_with.wrapping_sub(data));
}
fn branch(&mut self, condition: bool) {
if condition {
let jump: i8 = self.mem_read(self.program_counter) as i8;
let jump_addr = self
.program_counter
.wrapping_add(1)
.wrapping_add(jump as u16);
self.program_counter = jump_addr;
}
}
/// INX stands for Increment Index Register X - increase the value of the X register
/// by one and update specific processor flags based on the result.
fn inx(&mut self) {
self.register_x = self.register_x.wrapping_add(1);
self.update_zero_and_negative_flags(self.register_x);
}
fn iny(&mut self) {
self.register_y = self.register_y.wrapping_add(1);
self.update_zero_and_negative_flags(self.register_y);
}
pub fn run(&mut self) {
self.run_with_callback(|_| {});
}
// The interpret method takes in mutalbe reference to self as we know we will need to modify
// register_a during execution
//
// - Fetch next instruction from instruction memory
// - Decode instruction
// - Execute the instruction
// - Repeat
pub fn run_with_callback<F>(&mut self, mut callback: F)
where
F: FnMut(&mut CPU),
{
let ref opcodes: HashMap<u8, &'static opcodes::OpCode> = *opcodes::OPCODES_MAP;
loop {
let code = self.mem_read(self.program_counter);
self.program_counter += 1;
let program_counter_state = self.program_counter;
let opcode = opcodes.get(&code).unwrap();
match code {
0xA9 | 0xA5 | 0xB5 | 0xAD | 0xBD | 0xB9 | 0xA1 | 0xB1 => {
self.lda(&opcode.mode);
}
0xA0 | 0xA4 | 0xB4 | 0xAC | 0xBC => {
self.ldy(&opcode.mode);
}
0xA2 | 0xA6 | 0xB6 | 0xAE | 0xBE => {
self.ldx(&opcode.mode);
}
0xEA => {
// do nothing
}
0xAA => self.tax(),
0xE8 => self.inx(),
0x00 => return,
0xd8 => self.status.remove(CpuFlags::DECIMAL_MODE),
0x58 => self.status.remove(CpuFlags::INTERRUPT_DISABLE),
0xb8 => self.status.remove(CpuFlags::OVERFLOW),
0x18 => self.clear_carry_flag(),
0x38 => self.set_carry_flag(),
0x78 => self.status.insert(CpuFlags::INTERRUPT_DISABLE),
0xf8 => self.status.insert(CpuFlags::DECIMAL_MODE),
0x48 => self.stack_push(self.register_a),
0x68 => {
self.pla();
}
0x08 => {
self.php();
}
0x28 => {
self.plp();
}
0x69 | 0x65 | 0x75 | 0x6d | 0x7d | 0x79 | 0x61 | 0x71 => {
self.adc(&opcode.mode);
}
0xe9 | 0xe5 | 0xf5 | 0xed | 0xfd | 0xf9 | 0xe1 | 0xf1 => {
self.sbc(&opcode.mode);
}
0x29 | 0x25 | 0x35 | 0x2d | 0x3d | 0x39 | 0x21 | 0x31 => {
self.and(&opcode.mode);
}
0x49 | 0x45 | 0x55 | 0x4d | 0x5d | 0x41 | 0x51 => {
self.eor(&opcode.mode);
}
0x09 | 0x05 | 0x15 | 0x0d | 0x1d | 0x19 | 0x01 | 0x11 => {
self.ora(&opcode.mode);
}
0x4a => self.lsr_accumulator(),
0x46 | 0x56 | 0x4e | 0x5e => {
self.lsr(&opcode.mode);
}
0x0a => self.asl_accumulator(),
0x06 | 0x16 | 0x0e | 0x1e => {
self.asl(&opcode.mode);
}
0x2a => self.rol_accumulator(),
0x26 | 0x36 | 0x23 | 0x3e => {
self.rol(&opcode.mode);
}
0x6a => self.ror_accumulator(),
0x66 | 0x76 | 0x6e | 0x7e => {
self.ror(&opcode.mode);
}
0xe6 | 0xf6 | 0xee | 0xfe => {
self.inc(&opcode.mode);
}
0xc8 => self.iny(),
0xc6 | 0xd6 | 0xce | 0xde => {
self.dec(&opcode.mode);
}
0xca => self.dex(),
0x88 => {
self.dey();
}
0xc9 | 0xc5 | 0xd5 | 0xcd | 0xdd | 0xd9 | 0xc1 | 0xd1 => {
self.compare(&opcode.mode, self.register_a);
}
0xc0 | 0xc4 | 0xcc => {
self.compare(&opcode.mode, self.register_y);
}
0xe0 | 0xe4 | 0xec => self.compare(&opcode.mode, self.register_x),
0x4c => {
let mem_address = self.mem_read_u16(self.program_counter);
self.program_counter = mem_address;
}
0x6c => {
let mem_address = self.mem_read_u16(self.program_counter);
let indirect_ref = if mem_address & 0x00FF == 0x00FF {
let lo = self.mem_read(mem_address);
let hi = self.mem_read(mem_address & 0xFF00);
(hi as u16) << 8 | lo as u16
} else {
self.mem_read_u16(mem_address)
};
self.program_counter = indirect_ref;
}
0x20 => {
self.stack_push_u16(self.program_counter + 2 - 1);
let target_address = self.mem_read_u16(self.program_counter);
self.program_counter = target_address
}
0x60 => {
self.program_counter = self.stack_pop_u16() + 1;
}
0x40 => {
let raw_bits = self.stack_pop();
self.status = CpuFlags::from_bits_truncate(raw_bits);
self.status.remove(CpuFlags::BREAK);
self.status.insert(CpuFlags::BREAK2);
self.program_counter = self.stack_pop_u16();
}
0xd0 => {
self.branch(!self.status.contains(CpuFlags::ZERO));
}
0x70 => {
self.branch(self.status.contains(CpuFlags::OVERFLOW));
}
0x50 => {
self.branch(!self.status.contains(CpuFlags::OVERFLOW));
}
0x10 => {
self.branch(!self.status.contains(CpuFlags::NEGATIVE));
}
0x30 => {
self.branch(self.status.contains(CpuFlags::NEGATIVE));
}
0xf0 => {
self.branch(self.status.contains(CpuFlags::ZERO));
}
0xb0 => {
self.branch(self.status.contains(CpuFlags::CARRY));
}
0x90 => {
self.branch(!self.status.contains(CpuFlags::CARRY));
}
0x24 | 0x2c => {
self.bit(&opcode.mode);
}
0x85 | 0x95 | 0x8d | 0x9d | 0x99 | 0x81 | 0x91 => {
self.sta(&opcode.mode);
}
0x86 | 0x96 | 0x8e => {
let addr = self.get_operand_address(&opcode.mode);
self.mem_write(addr, self.register_x);
}
0x84 | 0x94 | 0x8c => {
let addr = self.get_operand_address(&opcode.mode);
self.mem_write(addr, self.register_y);
}
0xa8 => {
self.register_y = self.register_a;
self.update_zero_and_negative_flags(self.register_y);
}
0xba => {
self.register_x = self.stack_pointer;
self.update_zero_and_negative_flags(self.register_x);
}
0x8a => {
self.register_a = self.register_x;
self.update_zero_and_negative_flags(self.register_a);
}
0x9a => {
self.stack_pointer = self.register_x;
}
0x98 => {
self.register_a = self.register_y;
self.update_zero_and_negative_flags(self.register_a);
}
_ => {
eprintln!("Unimplemented opcode: {:#X}", code);
}
}
if program_counter_state == self.program_counter {
self.program_counter += (opcode.len - 1) as u16;
}
callback(self)
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_0xa9_lda_immediate_load_data() {
let mut cpu = CPU::new();
cpu.load_and_run(vec![0xa9, 0x05, 0x00]);
assert_eq!(cpu.register_a, 5);
assert!(cpu.status.bits() & 0b0000_0010 == 0b00);
assert!(cpu.status.bits() & 0b1000_0000 == 0);
}
#[test]
fn test_0xaa_tax_move_a_to_x() {
let mut cpu = CPU::new();
cpu.register_a = 10;
cpu.load_and_run(vec![0xaa, 0x00]);
assert_eq!(cpu.register_x, 10)
}
#[test]
fn test_5_ops_working_together() {
let mut cpu = CPU::new();
cpu.load_and_run(vec![0xa9, 0xc0, 0xaa, 0xe8, 0x00]);
assert_eq!(cpu.register_x, 0xc1)
}
#[test]
fn test_inx_overflow() {
let mut cpu = CPU::new();
cpu.register_x = 0xff;
cpu.load_and_run(vec![0xe8, 0xe8, 0x00]);
assert_eq!(cpu.register_x, 1)
}
#[test]
fn test_lda_from_memory() {
let mut cpu = CPU::new();
cpu.mem_write(0x10, 0x55);
cpu.load_and_run(vec![0xa5, 0x10, 0x00]);
assert_eq!(cpu.register_a, 0x55);
}
}