summary refs log tree commit diff
path: root/src/cpu.rs
diff options
context:
space:
mode:
authorSophie Forrest <57433227+sophieforrest@users.noreply.github.com>2024-01-13 19:17:57 +1300
committerSophie Forrest <57433227+sophieforrest@users.noreply.github.com>2024-01-13 19:17:57 +1300
commit13becb31e8b669dd6254c68b3aae110e7711d9db (patch)
treef4320bc6c0e86064291ac29f4dfaddb8fbb3f376 /src/cpu.rs
chore: init project
Diffstat (limited to 'src/cpu.rs')
-rw-r--r--src/cpu.rs216
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;
+	}
+}