summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorSophie Forrest <git@sophieforrest.com>2024-10-05 01:38:15 +1300
committerSophie Forrest <git@sophieforrest.com>2024-10-05 01:38:15 +1300
commitc4fbf6746a067eb1db7a836a52024539dde26af1 (patch)
tree948c5812ebf6c4272c6cc0bc22f960d55775bd4c /src
parent48ac2fa41c8da78cc4c26a5175476b972f1663da (diff)
feat: implement JSR instruction HEAD main
I decided to pull in byteorder to ease up some of the work here.
Additionally, the test program has been modified, and I've added a todo
for the LDR Absolute instruction.
Diffstat (limited to 'src')
-rw-r--r--src/cpu.rs66
-rw-r--r--src/instruction.rs22
-rw-r--r--src/main.rs12
3 files changed, 93 insertions, 7 deletions
diff --git a/src/cpu.rs b/src/cpu.rs
index d2d6d4d..1955a86 100644
--- a/src/cpu.rs
+++ b/src/cpu.rs
@@ -3,6 +3,7 @@
 //! Implementation of the MOS 6502 CPU.
 
 use bitflags::bitflags;
+use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
 use tracing::{debug, trace};
 
 use crate::{
@@ -97,6 +98,29 @@ impl Cpu {
 		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::<LittleEndian>()
+			.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");
@@ -125,6 +149,20 @@ impl Cpu {
 		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::<LittleEndian>(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) {
@@ -134,7 +172,7 @@ impl Cpu {
 
 	/// Executes instructions on the [`Cpu`] from memory. Runs the specified
 	/// number of cycles.
-	pub fn execute(&mut self, cycles: u32, memory: &Memory) {
+	pub fn execute(&mut self, cycles: u32, memory: &mut Memory) {
 		// Shadow cycles with a mutable copy
 		let mut cycles = cycles;
 
@@ -152,6 +190,32 @@ impl Cpu {
 				);
 
 				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);
 
diff --git a/src/instruction.rs b/src/instruction.rs
index 4a23992..2390046 100644
--- a/src/instruction.rs
+++ b/src/instruction.rs
@@ -5,20 +5,35 @@
 /// List of all instructions for MOS 6502.
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum Instruction {
+	/// Jump to subroutine.
+	///
+	/// The JSR instruction pushes the address (minus one) of the return point on to the stack and
+	/// then sets the program counter to the target memory address.
+	Jsr,
+
 	/// Load accumulator.
+	///
+	/// Loads a byte of memory into the accumulator, setting the zero and negative flags as
+	/// appropriate.
 	Lda,
 }
 
 /// Addressing modes for MOS 6502.
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum AddressingMode {
-	/// Immediate mode
+	/// Absolute mode.
+	///
+	/// Instructions using absolute addressing contain a full 16 bit address to identify the
+	/// target location.
+	Absolute,
+
+	/// Immediate mode.
 	Immediate,
 
-	/// Zero Page mode
+	/// Zero Page mode.
 	ZeroPage,
 
-	/// ZeroPage.X mode
+	/// ZeroPage.X mode.
 	ZeroPageX,
 }
 
@@ -46,6 +61,7 @@ impl Operation {
 /// operation exists.
 pub const fn get_operation(opcode: u8) -> Option<Operation> {
 	match opcode {
+		0x20 => Some(Operation::new(Instruction::Jsr, AddressingMode::Absolute)),
 		0xA5 => Some(Operation::new(Instruction::Lda, AddressingMode::ZeroPage)),
 		0xA9 => Some(Operation::new(Instruction::Lda, AddressingMode::Immediate)),
 		0xB5 => Some(Operation::new(Instruction::Lda, AddressingMode::ZeroPageX)),
diff --git a/src/main.rs b/src/main.rs
index 7f6df89..2fcee18 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -28,15 +28,21 @@ fn main() {
 
 	// little program
 	if let Some(opcode) = memory.data.get_mut(0xFFFC) {
-		*opcode = 0xA5;
+		*opcode = 0x20;
 	}
 	if let Some(opcode) = memory.data.get_mut(0xFFFD) {
 		*opcode = 0x42;
 	}
-	if let Some(opcode) = memory.data.get_mut(0x0042) {
+	if let Some(opcode) = memory.data.get_mut(0xFFFE) {
+		*opcode = 0x42;
+	}
+	if let Some(opcode) = memory.data.get_mut(0x4242) {
+		*opcode = 0xA9;
+	}
+	if let Some(opcode) = memory.data.get_mut(0x4243) {
 		*opcode = 0x84;
 	}
 	// end program
 
-	cpu.execute(3, &memory);
+	cpu.execute(9, &mut memory);
 }