diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f423fc3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nes_emulator_rs" +version = "0.1.0" +edition = "2021" + +[dependencies] +lazy_static = "1.5.0" diff --git a/src/cpu.rs b/src/cpu.rs new file mode 100644 index 0000000..01ca427 --- /dev/null +++ b/src/cpu.rs @@ -0,0 +1,283 @@ +use crate::opcodes; +use std::collections::HashMap; + +/// The `CPU` struct represents the central processing unit of the NES emulator. +/// It contains the following fields: +/// * `register_a`: The accumulator register, used for arithmetic operations. +/// * `register_x`: The index register. +/// * `status`: The status register, used to store flags that represent the result of operations. +/// * `program_counter`: The program counter register, used to keep track of the current execution point in the program. +pub struct CPU { + pub register_a: u8, + pub register_x: u8, + pub register_y: u8, + pub status: u8, + pub program_counter: u16, + memory: [u8; 0xFFFF], +} + +impl Default for CPU { + fn default() -> Self { + Self::new() + } +} + +trait Mem { + fn mem_read(&self, addr: u16) -> u8; + + fn mem_write(&mut self, addr: u16, data: u8); + + fn mem_read_u16(&self, pos: u16) -> u16 { + let lo = self.mem_read(pos) as u16; + let hi = self.mem_read(pos + 1) as u16; + (hi << 8) | lo + } + + fn mem_write_u16(&mut self, pos: u16, data: u16) { + let hi = (data >> 8) as u8; + let lo = (data & 0xff) as u8; + self.mem_write(pos, lo); + self.mem_write(pos + 1, hi); + } +} + +impl Mem for CPU { + fn mem_read(&self, addr: u16) -> u8 { + self.memory[addr as usize] + } + + fn mem_write(&mut self, addr: u16, data: u8) { + self.memory[addr as usize] = data; + } +} + +#[derive(Debug)] +#[allow(non_camel_case_types)] +pub enum AddressingMode { + Immediate, + ZeroPage, + ZeroPage_X, + ZeroPage_Y, + Absolute, + Absolute_X, + Absolute_Y, + Indirect_X, + Indirect_Y, + NoneAddressing, +} + +impl CPU { + pub fn new() -> Self { + CPU { + register_a: 0, + register_x: 0, + register_y: 0, + status: 0, + program_counter: 0, + memory: [0; 0xFFFF], + } + } + + pub fn load_and_run(&mut self, program: Vec) { + self.load(program); + self.reset(); + self.run() + } + + pub fn load(&mut self, program: Vec) { + self.memory[0x8000..(0x8000 + program.len())].copy_from_slice(&program[..]); + self.mem_write_u16(0xFFFC, 0x8000); + } + + pub fn run(&mut self) { + let opcodes: &HashMap = &opcodes::OPCODES_MAP; + + loop { + let code = self.mem_read(self.program_counter); + self.program_counter += 1; + let program_counter_state = self.program_counter; + + let opcode = opcodes + .get(&code) + .unwrap_or_else(|| panic!("opcode {:x} is not supported", code)); + match code { + 0xa9 | 0xa5 | 0xb5 | 0xad | 0xbd | 0xb9 | 0xa1 | 0xb1 => { + self.lda(&opcode.mode); + } + + 0x85 | 0x95 | 0x8d | 0x9d | 0x99 | 0x81 | 0x91 => { + self.sta(&opcode.mode); + } + + 0xAA => self.tax(), + 0xe8 => self.inx(), + 0x00 => return, + + _ => todo!(), + } + + if program_counter_state == self.program_counter { + self.program_counter += (opcode.len - 1) as u16; + } + } + } + + pub fn reset(&mut self) { + self.register_a = 0; + self.register_x = 0; + self.register_y = 0; + self.status = 0; + self.program_counter = self.mem_read_u16(0xFFFC); + } + + fn get_operand_address(&self, mode: &AddressingMode) -> u16 { + match mode { + AddressingMode::Immediate => self.program_counter, + AddressingMode::ZeroPage => self.mem_read(self.program_counter) as u16, + AddressingMode::Absolute => self.mem_read_u16(self.program_counter), + AddressingMode::ZeroPage_X => { + let pos = self.mem_read(self.program_counter); + let addr = pos.wrapping_add(self.register_x); + addr as u16 + } + AddressingMode::ZeroPage_Y => { + let pos = self.mem_read(self.program_counter); + let addr = pos.wrapping_add(self.register_y); + addr as u16 + } + AddressingMode::Absolute_X => { + let base = self.mem_read_u16(self.program_counter); + base.wrapping_add(self.register_x as u16) + } + AddressingMode::Absolute_Y => { + let base = self.mem_read_u16(self.program_counter); + base.wrapping_add(self.register_y as u16) + } + AddressingMode::Indirect_X => { + let base = self.mem_read(self.program_counter); + + let ptr: u8 = base.wrapping_add(self.register_x); + let lo = self.mem_read(ptr as u16); + let hi = self.mem_read(ptr.wrapping_add(1) as u16); + (hi as u16) << 8 | (lo as u16) + } + AddressingMode::Indirect_Y => { + let base = self.mem_read(self.program_counter); + + let lo = self.mem_read(base as u16); + let hi = self.mem_read(base.wrapping_add(1) as u16); + let deref_base = (hi as u16) << 8 | (lo as u16); + deref_base.wrapping_add(self.register_y as u16) + } + AddressingMode::NoneAddressing => { + panic!("mode {:?} is not supported", mode); + } + } + } + + /// LDA - Load accumulator + /// Loads a byte of memory into the accumulator setting the zero and negative flags as appropriate. + fn lda(&mut self, mode: &AddressingMode) { + let addr = self.get_operand_address(mode); + + let value = self.mem_read(addr); + + self.register_a = value; + self.update_zero_and_negative_flags(self.register_a); + } + + /// STA - Store Accumulator + /// Stores the contents of the accumulator into memory. + fn sta(&mut self, mode: &AddressingMode) { + let addr = self.get_operand_address(mode); + self.mem_write(addr, self.register_a); + } + + /// TAX - Transfer Accumulator to X + /// Copies the current contents of the accumulator into the X register and sets the zero and negative flags as appropriate. + fn tax(&mut self) { + self.register_x = self.register_a; + self.update_zero_and_negative_flags(self.register_x); + } + + /// INX - Increment X Register + /// Adds one to the X register setting the zero and negative flags as appropriate. + fn inx(&mut self) { + self.register_x = self.register_x.wrapping_add(1); + self.update_zero_and_negative_flags(self.register_x); + } + + fn update_zero_and_negative_flags(&mut self, result: u8) { + if result == 0 { + self.status |= 0b0000_0010; + } else { + self.status &= 0b1111_1101; + } + + if result & 0b1000_0000 != 0 { + self.status |= 0b1000_0000; + } else { + self.status &= 0b0111_1111; + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_0xa9_lda_immediate_load_data() { + let mut cpu = CPU::new(); + cpu.reset(); + cpu.load_and_run(vec![0xa9, 0x05, 0x00]); + assert_eq!(cpu.register_a, 5); + assert_eq!(cpu.status & 0b0000_0010, 0); + assert_eq!(cpu.status & 0b1000_0000, 0); + } + + #[test] + fn test_0xa9_lda_zero_flag() { + let mut cpu = CPU::new(); + cpu.reset(); + cpu.load_and_run(vec![0xa9, 0x00, 0x00]); + assert_eq!(cpu.status & 0b0000_0010, 0b10); + } + + #[test] + fn test_0xaa_tax_move_a_to_x() { + let mut cpu = CPU::new(); + cpu.reset(); + cpu.load_and_run(vec![0xa9, 0x0A, 0xaa, 0x00]); + + assert_eq!(cpu.register_x, 10) + } + + #[test] + fn test_5_ops_working_together() { + let mut cpu = CPU::new(); + cpu.reset(); + cpu.load_and_run(vec![0xa9, 0xc0, 0xaa, 0xe8, 0x00]); + + assert_eq!(cpu.register_x, 0xc1) + } + + #[test] + fn test_inx_overflow() { + let mut cpu = CPU::new(); + cpu.reset(); + cpu.load_and_run(vec![0xa9, 0xff, 0xaa, 0xe8, 0xe8, 0x00]); + + assert_eq!(cpu.register_x, 1) + } + + #[test] + fn test_lda_from_memory() { + let mut cpu = CPU::new(); + cpu.reset(); + cpu.mem_write(0x10, 0x55); + cpu.load_and_run(vec![0xa5, 0x10, 0x00]); + + assert_eq!(cpu.register_a, 0x55); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8bdbbf6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,9 @@ +pub mod cpu; +pub mod opcodes; + +#[macro_use] +extern crate lazy_static; + +fn main() { + println!("Hello, world!"); +} diff --git a/src/opcodes.rs b/src/opcodes.rs new file mode 100644 index 0000000..7deae37 --- /dev/null +++ b/src/opcodes.rs @@ -0,0 +1,57 @@ +use crate::cpu::AddressingMode; +use std::collections::HashMap; + +pub struct OpCode { + pub code: u8, + pub mnemonic: &'static str, + pub len: u8, + pub cycles: u8, + pub mode: AddressingMode, +} + +impl OpCode { + fn new(code: u8, mnemonic: &'static str, len: u8, cycles: u8, mode: AddressingMode) -> Self { + OpCode { + code, + mnemonic, + len, + cycles, + mode, + } + } +} + +lazy_static! { + pub static ref CPU_OPS_CODES: Vec = vec![ + OpCode::new(0x00, "BRK", 1, 7, AddressingMode::NoneAddressing), + OpCode::new(0xaa, "TAX", 1, 2, AddressingMode::NoneAddressing), + OpCode::new(0xe8, "INX", 1, 2, AddressingMode::NoneAddressing), + + OpCode::new(0xa9, "LDA", 2, 2, AddressingMode::Immediate), + OpCode::new(0xa5, "LDA", 2, 3, AddressingMode::ZeroPage), + OpCode::new(0xb5, "LDA", 2, 4, AddressingMode::ZeroPage_X), + OpCode::new(0xad, "LDA", 3, 4, AddressingMode::Absolute), + OpCode::new(0xbd, "LDA", 3, 4/*+1 if page crossed*/, AddressingMode::Absolute_X), + OpCode::new(0xb9, "LDA", 3, 4/*+1 if page crossed*/, AddressingMode::Absolute_Y), + OpCode::new(0xa1, "LDA", 2, 6, AddressingMode::Indirect_X), + OpCode::new(0xb1, "LDA", 2, 5/*+1 if page crossed*/, AddressingMode::Indirect_Y), + + OpCode::new(0x85, "STA", 2, 3, AddressingMode::ZeroPage), + OpCode::new(0x95, "STA", 2, 4, AddressingMode::ZeroPage_X), + OpCode::new(0x8d, "STA", 3, 4, AddressingMode::Absolute), + OpCode::new(0x9d, "STA", 3, 5, AddressingMode::Absolute_X), + OpCode::new(0x99, "STA", 3, 5, AddressingMode::Absolute_Y), + OpCode::new(0x81, "STA", 2, 6, AddressingMode::Indirect_X), + OpCode::new(0x91, "STA", 2, 6, AddressingMode::Indirect_Y), + + ]; + + + pub static ref OPCODES_MAP: HashMap = { + let mut map = HashMap::new(); + for cpuop in &*CPU_OPS_CODES { + map.insert(cpuop.code, cpuop); + } + map + }; +}