summary refs log tree commit diff
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
chore: init project
Diffstat (limited to '')
-rw-r--r--.gitignore1
-rw-r--r--.rust-toolchain.toml2
-rw-r--r--.rustfmt.toml13
-rw-r--r--.taplo.toml10
-rw-r--r--Cargo.lock16
-rw-r--r--Cargo.toml75
-rw-r--r--clippy.toml1
-rw-r--r--src/cpu.rs216
-rw-r--r--src/instruction.rs51
-rw-r--r--src/main.rs33
-rw-r--r--src/memory.rs26
11 files changed, 444 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/.rust-toolchain.toml b/.rust-toolchain.toml
new file mode 100644
index 0000000..5d56faf
--- /dev/null
+++ b/.rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
diff --git a/.rustfmt.toml b/.rustfmt.toml
new file mode 100644
index 0000000..ebf394c
--- /dev/null
+++ b/.rustfmt.toml
@@ -0,0 +1,13 @@
+edition = "2021"
+
+format_code_in_doc_comments = true
+format_strings = true
+group_imports = "StdExternalCrate"
+hard_tabs = true
+hex_literal_case = "Upper"
+imports_granulatiry = "Crate"
+reorder_impl_items = true
+reorder_modules = true
+use_field_init_shorthand = true
+use_try_shorthand = true
+wrap_comments = true
diff --git a/.taplo.toml b/.taplo.toml
new file mode 100644
index 0000000..88958ed
--- /dev/null
+++ b/.taplo.toml
@@ -0,0 +1,10 @@
+[formatting]
+indent_string = "	"
+reorder_keys = false
+
+[[rule]]
+include = ["**/Cargo.toml"]
+keys = ["dependencies"]
+
+[rule.formatting]
+reorder_keys = true
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..f7ac645
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,16 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "emulator_6502"
+version = "0.1.0"
+dependencies = [
+ "bitflags",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..874b019
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,75 @@
+[package]
+name = "mos6502"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bitflags = "2.4.1"
+
+[lints.rust]
+missing_copy_implementations = "deny"
+missing_debug_implementations = "deny"
+missing_docs = "deny"
+single_use_lifetimes = "deny"
+unsafe_code = "deny"
+unused = "deny"
+
+[lints.clippy]
+complexity = "deny"
+nursery = "deny"
+pedantic = "deny"
+perf = "deny"
+suspicious = "deny"
+alloc_instead_of_core = "deny"
+as_undercore = "deny"
+clone_on_ref_ptr = "deny"
+create_dir = "deny"
+dbg_macro = "deny"
+default_numeric_fallback = "deny"
+default_union_representation = "deny"
+deref_by_slicing = "deny"
+empty_structs_with_brackets = "deny"
+exit = "deny"
+filetype_is_file = "deny"
+fn_to_numeric_cast = "deny"
+format_push_string = "deny"
+get_unwrap = "deny"
+if_then_some_else_none = "deny"
+implicit_return = "allow"
+indexing_slicing = "deny"
+large_include_file = "deny"
+let_underscore_must_use = "deny"
+lossy_float_literal = "deny"
+map_err_ignore = "deny"
+mem_forget = "deny"
+missing_docs_in_private_items = "deny"
+missing_trait_methods = "deny"
+mod_module_files = "deny"
+multiple_inrerent_impl = "deny"
+mutex_atomic = "deny"
+needless_return = "deny"
+non_ascii_literal = "deny"
+panic_in_result_fn = "deny"
+pattern_type_mismatch = "deny"
+rc_buffer = "deny"
+rc_mutex = "deny"
+rest_pat_in_fully_bound_structs = "deny"
+same_name_method = "deny"
+separated_literal_suffix = "deny"
+str_to_string = "deny"
+string_add = "deny"
+string_slice = "deny"
+string_to_string = "deny"
+tabs_in_doc_comments = "allow"
+try_err = "deny"
+undocumented_unsafe_blocks = "deny"
+unnecessary_self_imports = "deny"
+unneeded_field_pattern = "deny"
+unwrap_in_resuslt = "deny"
+unwrap_used = "deny"
+use_debug = "deny"
+verbose_file_reads = "deny"
+wildcard_dependencies = "deny"
+wildcard_enum_match_arm = "deny"
diff --git a/clippy.toml b/clippy.toml
new file mode 100644
index 0000000..cda8d17
--- /dev/null
+++ b/clippy.toml
@@ -0,0 +1 @@
+avoid-breaking-exported-api = false
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;
+	}
+}
diff --git a/src/instruction.rs b/src/instruction.rs
new file mode 100644
index 0000000..2744048
--- /dev/null
+++ b/src/instruction.rs
@@ -0,0 +1,51 @@
+//! Implementation of instructions for MOS 6502.
+
+/// List of all instructions for MOS 6502.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum Instruction {
+	/// Load accumulator.
+	Lda,
+}
+
+/// Addressing modes for MOS 6502.
+#[derive(Clone, Copy, Debug)]
+pub enum AddressingMode {
+	/// Immediate mode
+	Immediate,
+
+	/// Zero Page mode
+	ZeroPage,
+
+	/// ZeroPage.X mode
+	ZeroPageX,
+}
+
+/// Operation retrieved from decoding an opcode.
+pub struct Operation {
+	/// [`AddressingMode`]
+	pub addressing_mode: AddressingMode,
+
+	/// [`Instruction`]
+	pub instruction: Instruction,
+}
+
+impl Operation {
+	/// Creates a new operation from the `instruction` and `addressing_mode`.
+	pub const fn new(instruction: Instruction, addressing_mode: AddressingMode) -> Self {
+		Self {
+			addressing_mode,
+			instruction,
+		}
+	}
+}
+
+/// Gets the operation from the passed in opcode. Returns [`None`] if no
+/// operation exists.
+pub const fn get_operation(opcode: u8) -> Option<Operation> {
+	match opcode {
+		0xA5 => Some(Operation::new(Instruction::Lda, AddressingMode::ZeroPage)),
+		0xA9 => Some(Operation::new(Instruction::Lda, AddressingMode::Immediate)),
+		0xB5 => Some(Operation::new(Instruction::Lda, AddressingMode::ZeroPageX)),
+		_ => None,
+	}
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..003d198
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,33 @@
+//! 6502 Emulator written in Rust. Based off of information from Dave's playlist
+//! <https://www.youtube.com/playlist?list=PLLwK93hM93Z13TRzPx9JqTIn33feefl37> using the now defunct
+//! obelisk 6502 documentation.
+//!
+//! Many comments are sourced from the Obelisk 6502 documentation <https://web.archive.org/web/20210501031403/http://www.obelisk.me.uk/index.html>
+
+mod cpu;
+mod instruction;
+mod memory;
+
+use cpu::Cpu;
+use memory::Memory;
+
+fn main() {
+	let mut cpu = Cpu::new();
+	let mut memory = Memory::new();
+
+	cpu.reset(&mut memory);
+
+	// little program
+	if let Some(opcode) = memory.data.get_mut(0xFFFC) {
+		*opcode = 0xA5;
+	}
+	if let Some(opcode) = memory.data.get_mut(0xFFFD) {
+		*opcode = 0x42;
+	}
+	if let Some(opcode) = memory.data.get_mut(0x0042) {
+		*opcode = 0x84;
+	}
+	// end program
+
+	cpu.execute(3, &memory);
+}
diff --git a/src/memory.rs b/src/memory.rs
new file mode 100644
index 0000000..d251231
--- /dev/null
+++ b/src/memory.rs
@@ -0,0 +1,26 @@
+//! Implementation of memory for the MOS 6502.
+
+/// Maximum amount of memory that exists within the emulator.
+pub const MAXIMUM_MEMORY: usize = 1024 * 64;
+
+/// Implementation of memory for the 6502.
+pub struct Memory {
+	/// Data for the memory.
+	pub data: [u8; MAXIMUM_MEMORY],
+}
+
+impl Memory {
+	/// Creates a new instance of the memory. Returns blank memory with all
+	/// zeroes.
+	pub const fn new() -> Self {
+		Self {
+			data: [0; MAXIMUM_MEMORY],
+		}
+	}
+
+	/// Resets all data in the memory to zero. This simply allocates a new array
+	/// and sets the memory to the new, zeroed array.
+	pub fn reset(&mut self) {
+		self.data = [0; MAXIMUM_MEMORY];
+	}
+}