diff options
| author | Sophie Forrest <57433227+sophieforrest@users.noreply.github.com> | 2024-01-13 19:17:57 +1300 |
|---|---|---|
| committer | Sophie Forrest <57433227+sophieforrest@users.noreply.github.com> | 2024-01-13 19:17:57 +1300 |
| commit | 13becb31e8b669dd6254c68b3aae110e7711d9db (patch) | |
| tree | f4320bc6c0e86064291ac29f4dfaddb8fbb3f376 /src/cpu.rs | |
chore: init project
Diffstat (limited to 'src/cpu.rs')
| -rw-r--r-- | src/cpu.rs | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/src/cpu.rs b/src/cpu.rs new file mode 100644 index 0000000..b009160 --- /dev/null +++ b/src/cpu.rs @@ -0,0 +1,216 @@ +//! Implementation of the MOS 6502 CPU. + +use bitflags::bitflags; + +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 { + let data = *memory + .data + .get(usize::from(self.program_counter)) + .expect("program_counter indexed outside of memory"); + + self.program_counter = self.program_counter.wrapping_add(1); + *cycles -= 1; + + data + } + + /// Sets the LDA status after executing an LDA operation. + fn lda_set_status(&mut self) { + 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 { + let data = *memory + .data + .get(usize::from(address)) + .expect("address indexed outside of memory"); + + *cycles -= 1; + + data + } + + /// Executes instructions on the [`Cpu`] from memory. Runs the specified + /// number of cycles. + pub fn execute(&mut self, cycles: u32, memory: &Memory) { + // Shadow cycles with a mutable copy + let mut cycles = cycles; + + while cycles > 0 { + let opcode: u8 = self.fetch_byte(&mut cycles, memory); + + let operation = get_operation(opcode); + + if let Some(operation) = operation { + match (operation.instruction, operation.addressing_mode) { + (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; + 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; + } +} |