summaryrefslogtreecommitdiff
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
-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];
+ }
+}