diff --git a/Cargo.lock b/Cargo.lock
index 2a5f2ee..1546f56 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,6 +9,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -31,6 +37,7 @@ name = "mos6502"
version = "0.1.0"
dependencies = [
"bitflags",
+ "byteorder",
"tracing",
"tracing-subscriber",
]
diff --git a/Cargo.toml b/Cargo.toml
index 8724300..29d8ab1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
bitflags = "2.6.0"
+byteorder = "1.5.0"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
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);
}
|