// SPDX-License-Identifier: AGPL-3.0-or-later //! Implementation of the MOS 6502 CPU. use bitflags::bitflags; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use tracing::{debug, trace}; use crate::{ instruction::{get_operation, AddressingMode, Instruction}, memory::Memory, }; /// Implementation of the Central Processing Unit of the 6502. pub struct Cpu { /// The 8 bit accumulator is used all arithmetic and logical operations /// (with the exception of increments and decrements). The contents of the /// accumulator can be stored and retrieved either from memory or the stack. /// /// Most complex operations will need to use the accumulator for arithmetic /// and efficient optimisation of its use is a key feature of time critical /// routines. accumulator: u8, /// The 8 bit index register is most commonly used to hold counters or /// offsets for accessing memory. The value of the X register can be loaded /// and saved in memory, compared with values held in memory or incremented /// and decremented. /// /// The X register has one special function. It can be used to get a copy of /// the stack pointer or change its value. index_register_x: u8, /// The Y register is similar to the X register in that it is available for /// holding counter or offsets memory access and supports the same set of /// memory load, save and compare operations as wells as increments and /// decrements. It has no special functions. index_register_y: u8, /// Processor flags. See documentation for [`ProcessorStatus`]. processor_status: ProcessorStatus, /// The program counter is a 16 bit register which points to the next /// instruction to be executed. The value of program counter is modified /// automatically as instructions are executed. /// /// The value of the program counter can be modified by executing a jump, a /// relative branch or a subroutine call to another memory address or by /// returning from a subroutine or interrupt. program_counter: u16, /// The processor supports a 256 byte stack located between $0100 and $01FF. /// The stack pointer is an 8 bit register and holds the low 8 bits of the /// next free location on the stack. The location of the stack is fixed and /// cannot be moved. /// /// Pushing bytes to the stack causes the stack pointer to /// be decremented. Conversely pulling bytes causes it to be incremented. /// /// The CPU does not detect if the stack is overflowed by excessive pushing /// or pulling operations and will most likely result in the program /// crashing. stack_pointer: u8, } impl Cpu { /// Creates a new instance of the [`Cpu`]. pub const fn new() -> Self { // TODO: Ensure these are the correct values for init Self { accumulator: 0, index_register_x: 0, index_register_y: 0, program_counter: 0xFFFC, stack_pointer: 0x0000, processor_status: ProcessorStatus::empty(), } } /// Fetches the next byte, pointed to by the program counter. fn fetch_byte(&mut self, cycles: &mut u32, memory: &Memory) -> u8 { debug!("fetching byte from address {:#04X}", self.program_counter); let data = *memory .data .get(usize::from(self.program_counter)) .expect("program_counter indexed outside of memory"); debug!( "fetched byte {data:#02X} from address {:#04X}", self.program_counter ); self.program_counter = self.program_counter.wrapping_add(1); Self::expend_cycles(cycles, 1); data } /// Fetches the next byte, pointed to by the program counter. fn fetch_word(&mut self, cycles: &mut u32, memory: &Memory) -> u16 { debug!("fetching word from address {:#04X}", self.program_counter); // MOS6502 is little endian. let data: u16 = memory .data .get(usize::from(self.program_counter)..usize::from(self.program_counter + 2)) .expect("program_counter indexed outside of memory") .read_u16::() .expect("could not read word from memory"); debug!( "fetched word {data:#04X} from address {:#04X}", self.program_counter ); self.program_counter = self.program_counter.wrapping_add(2); Self::expend_cycles(cycles, 1); data } /// Sets the LDA status after executing an LDA operation. fn lda_set_status(&mut self) { trace!("setting LDA status"); self.processor_status .set(ProcessorStatus::ZERO, self.accumulator == 0); self.processor_status.set( ProcessorStatus::NEGATIVE, (self.accumulator & 0b1000_0000) > 0, ); } /// Reads the next byte, pointed to by the address. fn read_byte(cycles: &mut u32, address: u8, memory: &Memory) -> u8 { debug!("reading byte from address {address:#04X}"); let data = *memory .data .get(usize::from(address)) .expect("address indexed outside of memory"); debug!("received byte {data:#02X} from address {address:#02X}"); Self::expend_cycles(cycles, 1); data } /// Writes a word to memory. fn write_word(cycles: &mut u32, data: u16, address: u32, memory: &mut Memory) { debug!("writing word to address {address:#08X}"); memory .data .get_mut((address as usize)..(address + 2) as usize) .expect("write_word address indexed out of bounds") .write_u16::(data) .expect("failed to write data"); Self::expend_cycles(cycles, 2); } /// Expends the specified number of cycles. This method is for internal use to log expended /// cycles. fn expend_cycles(cycles: &mut u32, count: u32) { *cycles -= count; trace!("expended {count} cycles, {cycles} cycles remaining"); } /// Executes instructions on the [`Cpu`] from memory. Runs the specified /// number of cycles. pub fn execute(&mut self, cycles: u32, memory: &mut Memory) { // Shadow cycles with a mutable copy let mut cycles = cycles; while cycles > 0 { let opcode: u8 = self.fetch_byte(&mut cycles, memory); trace!("got opcode {opcode:#02X}"); let operation = get_operation(opcode); if let Some(operation) = operation { trace!( "running instruction {:?}, addressing mode {:?}", operation.instruction, operation.addressing_mode ); match (operation.instruction, operation.addressing_mode) { (Instruction::Jsr, AddressingMode::Absolute) => { // Fetch the subroutine address from memory. let subroutine_address: u16 = self.fetch_word(&mut cycles, memory); // Write the program counter - 1 to the stack. Self::write_word( &mut cycles, self.program_counter - 1, self.stack_pointer.into(), memory, ); // Increment stack pointer. self.stack_pointer += 1; // Set program counter to subroutine address. self.program_counter = subroutine_address; Self::expend_cycles(&mut cycles, 1); } ( Instruction::Jsr, AddressingMode::Immediate | AddressingMode::ZeroPage | AddressingMode::ZeroPageX, ) => unreachable!(), (Instruction::Lda, AddressingMode::Absolute) => todo!(), (Instruction::Lda, AddressingMode::Immediate) => { let value = self.fetch_byte(&mut cycles, memory); self.accumulator = value; self.lda_set_status(); } (Instruction::Lda, AddressingMode::ZeroPage) => { let zero_page_address = self.fetch_byte(&mut cycles, memory); self.accumulator = Self::read_byte(&mut cycles, zero_page_address, memory); self.lda_set_status(); } (Instruction::Lda, AddressingMode::ZeroPageX) => { let mut zero_page_address = self.fetch_byte(&mut cycles, memory); zero_page_address += self.index_register_x; Self::expend_cycles(&mut cycles, 1); self.accumulator = Self::read_byte(&mut cycles, zero_page_address, memory); self.lda_set_status(); } } } } } /// Resets the [`Cpu`]. This will put the Cpu back into the state it was at /// on boot. pub fn reset(&mut self, memory: &mut Memory) { // TODO: Incomplete. Use correct values from later on in tutorial self.program_counter = 0xFFFC; self.stack_pointer = 0x0000; // Set all flags to 0 self.processor_status = ProcessorStatus::empty(); self.accumulator = 0; self.index_register_x = 0; self.index_register_y = 0; // Zero out memory memory.reset(); } } bitflags! { /// As instructions are executed a set of processor flags are set or clear to /// record the results of the operation. This flags and some additional control /// flags are held in a special status register. Each flag has a single bit /// within the register. pub struct ProcessorStatus: u8 { /// The negative flag is set if the result of the last operation had bit 7 /// set to a one. const NEGATIVE = 0b1000_0000; /// The overflow flag is set during arithmetic operations if the result has /// yielded an invalid 2's complement result (e.g. adding to positive numbers /// and ending up with a negative result: 64 + 64 => -128). It is determined by /// looking at the carry between bits 6 and 7 and between bit 7 and the carry /// flag. const OVERFLOW = 0b0100_0000; // No instruction for 0b0010_0000 /// The break command bit is set when a BRK instruction has been executed and an /// interrupt has been generated to process it. const BREAK_COMMAND = 0b0001_0000; /// While the decimal mode flag is set the processor will obey the rules of /// Binary Coded Decimal (BCD) arithmetic during addition and subtraction. The /// flag can be explicitly set using 'Set Decimal Flag' (SED) and cleared with /// 'Clear Decimal Flag' (CLD). const DECIMAL_MODE = 0b0000_1000; /// The interrupt disable flag is set if the program has executed a 'Set /// Interrupt Disable' (SEI) instruction. While this flag is set the processor /// will not respond to interrupts from devices until it is cleared by a 'Clear /// Interrupt Disable' (CLI) instruction. const INTERRUPT_DISABLE = 0b0000_0100; /// The zero flag is set if the result of the last operation as was zero. const ZERO = 0b0000_0010; /// The carry flag is set if the last operation caused an overflow from bit 7 of /// the result or an underflow from bit 0. This condition is set during /// arithmetic, comparison and during logical shifts. It can be explicitly set /// using the 'Set Carry Flag' (SEC) instruction and cleared with 'Clear Carry /// Flag' (CLC). const CARRY = 0b0000_0001; } }