From a97824b26ecf11882e3ff5f3bb0f14a752553047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20L=C3=B6ffler?= Date: Sat, 28 Jan 2023 23:43:16 +0100 Subject: [PATCH] memory mapping --- src/emulator/instructions/arithmetic.rs | 37 +++---- src/emulator/instructions/branch.rs | 17 +-- src/emulator/instructions/transfer.rs | 60 ++++++----- src/emulator/main.rs | 21 ++-- src/emulator/mapper/mod.rs | 25 +++++ src/emulator/structs.rs | 134 ++++++++++++++---------- 6 files changed, 182 insertions(+), 112 deletions(-) create mode 100644 src/emulator/mapper/mod.rs diff --git a/src/emulator/instructions/arithmetic.rs b/src/emulator/instructions/arithmetic.rs index c5ba470..c68d7ae 100644 --- a/src/emulator/instructions/arithmetic.rs +++ b/src/emulator/instructions/arithmetic.rs @@ -1,22 +1,23 @@ +use crate::mapper::MemoryMapper; use crate::structs::{get_register_pair, set_register, set_register_pair}; use crate::{get_register, EmulatorState, Register}; /// Sets the condition code flags according to `result`. /// Does not set the carry flag and will always set the Z, S and P flags. #[inline(always)] -fn set_cc(state: &mut EmulatorState, result: u8) { +fn set_cc(state: &mut EmulatorState, result: u8) { state.cc.z = result == 0; state.cc.s = result & 0x80 > 0; state.cc.p = result.count_ones() % 2 == 0; } /// Add values of `register` and `A`, add +1 if carry arg is set (either false or state.cc.c) -pub fn add_reg(register: Register, carry: bool, state: &mut EmulatorState) { +pub fn add_reg(register: Register, carry: bool, state: &mut EmulatorState) { add(get_register(register, state), carry, state); } /// Add values of input byte and `A`, add +1 if carry arg is set (either false or state.cc.c) -pub fn add(byte: u8, carry: bool, state: &mut EmulatorState) { +pub fn add(byte: u8, carry: bool, state: &mut EmulatorState) { let (a, first) = state.a.overflowing_add(byte); let (result, second) = a.overflowing_add(carry as u8); @@ -25,11 +26,11 @@ pub fn add(byte: u8, carry: bool, state: &mut EmulatorState) { state.a = result; } -pub fn sub_reg(register: Register, borrow: bool, state: &mut EmulatorState) { +pub fn sub_reg(register: Register, borrow: bool, state: &mut EmulatorState) { sub(get_register(register, state), borrow, state); } -pub fn sub(byte: u8, borrow: bool, state: &mut EmulatorState) { +pub fn sub(byte: u8, borrow: bool, state: &mut EmulatorState) { let (a, first) = state.a.overflowing_sub(byte); let (result, second) = a.overflowing_sub(borrow as u8); @@ -40,11 +41,11 @@ pub fn sub(byte: u8, borrow: bool, state: &mut EmulatorState) { macro_rules! bitwise_op { ($name:ident, $reg_name:ident, $op:tt) => { - pub fn $reg_name(register: Register, state: &mut EmulatorState) { + pub fn $reg_name(register: Register, state: &mut EmulatorState) { $name(get_register(register, state), state); } - pub fn $name(byte: u8, state: &mut EmulatorState) { + pub fn $name(byte: u8, state: &mut EmulatorState) { let result = state.a $op byte; state.cc.c = false; set_cc(state, result); @@ -57,18 +58,18 @@ bitwise_op!(and, and_reg, &); bitwise_op!(or, or_reg, |); bitwise_op!(xor, xor_reg, ^); -pub fn cmp_reg(register: Register, state: &mut EmulatorState) { +pub fn cmp_reg(register: Register, state: &mut EmulatorState) { cmp(get_register(register, state), state); } -pub fn cmp(byte: u8, state: &mut EmulatorState) { +pub fn cmp(byte: u8, state: &mut EmulatorState) { let (result, carry) = state.a.overflowing_sub(byte); state.cc.c = carry; set_cc(state, result); } /// Double precision add - Add B&C, D&E or H&L to H&L -pub fn dad(register: Register, state: &mut EmulatorState) { +pub fn dad(register: Register, state: &mut EmulatorState) { let num = get_register_pair(register, state); let (result, overflow) = num.overflowing_add(u16::from_le_bytes([state.l, state.h])); @@ -81,50 +82,50 @@ pub fn dad(register: Register, state: &mut EmulatorState) { } /// Increase register -pub fn inr(register: Register, state: &mut EmulatorState) { +pub fn inr(register: Register, state: &mut EmulatorState) { let (result, _) = get_register(register, state).overflowing_add(1); set_cc(state, result); set_register(register, result, state); } /// Decrease register -pub fn dcr(register: Register, state: &mut EmulatorState) { +pub fn dcr(register: Register, state: &mut EmulatorState) { let (result, _) = get_register(register, state).overflowing_sub(1); set_cc(state, result); set_register(register, result, state); } /// Increase register pair -pub fn inx(register: Register, state: &mut EmulatorState) { +pub fn inx(register: Register, state: &mut EmulatorState) { let (result, _) = get_register_pair(register, state).overflowing_add(1); set_register_pair(register, result, state); } /// Decrease register pair -pub fn dcx(register: Register, state: &mut EmulatorState) { +pub fn dcx(register: Register, state: &mut EmulatorState) { let (result, _) = get_register_pair(register, state).overflowing_sub(1); set_register_pair(register, result, state); } -pub fn rlc(state: &mut EmulatorState) { +pub fn rlc(state: &mut EmulatorState) { let result = state.a.rotate_left(1); state.a = result; state.cc.c = result & 0x01 > 0; } -pub fn rrc(state: &mut EmulatorState) { +pub fn rrc(state: &mut EmulatorState) { state.cc.c = state.a & 0x01 > 0; state.a = state.a.rotate_right(1); } -pub fn ral(state: &mut EmulatorState) { +pub fn ral(state: &mut EmulatorState) { let new_carry = state.a & 0x80 > 0; let result = state.a << 1; state.a = result | new_carry as u8; state.cc.c = new_carry; } -pub fn rar(state: &mut EmulatorState) { +pub fn rar(state: &mut EmulatorState) { let new_carry = state.a & 0x01 > 0; let result = state.a >> 1; state.a = result | (new_carry as u8) << 7; diff --git a/src/emulator/instructions/branch.rs b/src/emulator/instructions/branch.rs index 9bf60ca..fb43a5d 100644 --- a/src/emulator/instructions/branch.rs +++ b/src/emulator/instructions/branch.rs @@ -1,38 +1,39 @@ +use crate::mapper::MemoryMapper; use crate::transfer::{pop, push}; use crate::{get_register_pair, EmulatorState, Register}; /// Jump (set PC) to specified address -pub fn jump(address: u16, state: &mut EmulatorState) { +pub fn jump(address: u16, state: &mut EmulatorState) { state.pc = address; } /// Jump to a specified address only if `cond` is true -pub fn jump_cond(address: u16, cond: bool, state: &mut EmulatorState) { +pub fn jump_cond(address: u16, cond: bool, state: &mut EmulatorState) { if cond { jump(address, state); } } /// Push the current PC to the stack and jump to the specified address -pub fn call(address: u16, state: &mut EmulatorState) { +pub fn call(address: u16, state: &mut EmulatorState) { push(state.pc, state); jump(address, state); } // Push the current PC to the stack and jump to the specified address if `cond` is true -pub fn call_cond(address: u16, cond: bool, state: &mut EmulatorState) { +pub fn call_cond(address: u16, cond: bool, state: &mut EmulatorState) { if cond { call(address, state); } } // Pop a value from the stack and jump to it -pub fn ret(state: &mut EmulatorState) { +pub fn ret(state: &mut EmulatorState) { jump(pop(state), state); } // Pop a value from the stack and jump to it if `cond` is true -pub fn ret_cond(cond: bool, state: &mut EmulatorState) { +pub fn ret_cond(cond: bool, state: &mut EmulatorState) { if cond { ret(state) } @@ -41,12 +42,12 @@ pub fn ret_cond(cond: bool, state: &mut EmulatorState) { /// "Restart" at (call) a specific interrupt vector /// /// Panics if `vector` is 8 or above -pub fn restart(vector: u8, state: &mut EmulatorState) { +pub fn restart(vector: u8, state: &mut EmulatorState) { assert!(vector < 8, "Illegal restart vector"); call((vector * 8) as u16, state); } /// Load a new program counter from the HL register pair -pub fn pchl(state: &mut EmulatorState) { +pub fn pchl(state: &mut EmulatorState) { state.pc = get_register_pair(Register::H, state); } diff --git a/src/emulator/instructions/transfer.rs b/src/emulator/instructions/transfer.rs index b297fd5..564ec2c 100644 --- a/src/emulator/instructions/transfer.rs +++ b/src/emulator/instructions/transfer.rs @@ -1,44 +1,55 @@ +use crate::mapper::MemoryMapper; use crate::{ get_register, get_register_pair, set_register, set_register_pair, EmulatorState, Register, }; /// Move (copy) value from source to destination register -pub fn mov(src: Register, dest: Register, state: &mut EmulatorState) { +pub fn mov(src: Register, dest: Register, state: &mut EmulatorState) { let data = get_register(src, state); set_register(dest, data, state); } /// Move immediate into destination register -pub fn mvi(byte: u8, dest: Register, state: &mut EmulatorState) { +pub fn mvi(byte: u8, dest: Register, state: &mut EmulatorState) { set_register(dest, byte, state); } +// Store accumulator to the speficied address +pub fn sta(address: u16, state: &mut EmulatorState) { + state.write_byte(address, state.a); +} + +// Load accumulator from the specified address +pub fn lda(address: u16, state: &mut EmulatorState) { + state.a = state.read_byte(address); +} + /// Store accumulator using the BC or DE register pair -pub fn stax(register: Register, state: &mut EmulatorState) { +pub fn stax(register: Register, state: &mut EmulatorState) { let address = get_register_pair(register, state); - state.memory[address as usize] = state.a; + state.write_byte(address, state.a); } /// Load accumulator using the BC or DE register pair -pub fn ldax(register: Register, state: &mut EmulatorState) { +pub fn ldax(register: Register, state: &mut EmulatorState) { let address = get_register_pair(register, state); - state.a = state.memory[address as usize]; + state.a = state.read_byte(address); } /// Store a 16-bit word from H and L to the specified address -pub fn shld(address: u16, state: &mut EmulatorState) { - state.memory[address as usize] = state.l; - state.memory[address as usize + 1] = state.h; +pub fn shld(address: u16, state: &mut EmulatorState) { + state.write_byte(address, state.l); + state.write_byte(address.overflowing_add(1).0, state.h) } /// Load a 16-bit word into H and L from the specified address -pub fn lhld(address: u16, state: &mut EmulatorState) { - state.l = state.memory[address as usize]; - state.h = state.memory[address as usize + 1]; +pub fn lhld(address: u16, state: &mut EmulatorState) { + state.l = state.read_byte(address); + state.h = state.read_byte(address.overflowing_add(1).0); } /// Exchange the HL register pair with the value at the top of the stack -pub fn xthl(state: &mut EmulatorState) { +pub fn xthl(state: &mut EmulatorState) { let at_hl = get_register_pair(Register::H, state); let at_stack = state.read_word(state.sp); // Set HL to the 16-bit value currently on top of the stack @@ -48,12 +59,12 @@ pub fn xthl(state: &mut EmulatorState) { } /// Load the stack pointer from the HL register pair -pub fn sphl(state: &mut EmulatorState) { +pub fn sphl(state: &mut EmulatorState) { state.sp = get_register_pair(Register::H, state); } /// Exchange the HL and DE register pairs -pub fn xchg(state: &mut EmulatorState) { +pub fn xchg(state: &mut EmulatorState) { let at_hl = get_register_pair(Register::H, state); let at_de = get_register_pair(Register::D, state); // Set HL to DE @@ -64,23 +75,23 @@ pub fn xchg(state: &mut EmulatorState) { /* STACK */ /// Push a 16-bit value from a register pair onto the stack -pub fn push_reg(register: Register, state: &mut EmulatorState) { +pub fn push_reg(register: Register, state: &mut EmulatorState) { push(get_register_pair(register, state), state); } /// Push a 16-bit value onto the stack -pub fn push(value: u16, state: &mut EmulatorState) { +pub fn push(value: u16, state: &mut EmulatorState) { (state.sp, ..) = state.sp.overflowing_sub(2); state.write_word(state.sp, value); } /// Pop a 16-bit value from the stack into a register pair -pub fn pop_reg(register: Register, state: &mut EmulatorState) { +pub fn pop_reg(register: Register, state: &mut EmulatorState) { set_register_pair(register, pop(state), state); } /// Pop a 16-bit value from the stack -pub fn pop(state: &mut EmulatorState) -> u16 { +pub fn pop(state: &mut EmulatorState) -> u16 { let value = state.read_word(state.sp); (state.sp, ..) = state.sp.overflowing_add(2); value @@ -88,6 +99,7 @@ pub fn pop(state: &mut EmulatorState) -> u16 { #[cfg(test)] mod tests { + use crate::mapper::TestMapper; use crate::EmulatorState; #[test] @@ -100,14 +112,14 @@ mod tests { // memory address two less than the contents of the // stack pointer. // (3) The stack pointer is automatically decremented by two. - let mut state = EmulatorState { + let mut state: EmulatorState = EmulatorState { sp: 0x12, ..Default::default() }; super::push(0x1234, &mut state); assert_eq!(state.sp, 0x10); - assert_eq!(state.memory[0x10..=0x11], [0x34, 0x12]); + assert_eq!(state.mapper.0[0x10..=0x11], [0x34, 0x12]); } #[test] @@ -121,13 +133,13 @@ mod tests { // memory address one greater than the address held in // the stack pointer. // (3) The stack pointer is automatically incremented by two. - let mut state = EmulatorState { + let mut state: EmulatorState = EmulatorState { sp: 0x10, ..Default::default() }; - state.memory[0x10] = 0x34; - state.memory[0x11] = 0x12; + state.mapper.0[0x10] = 0x34; + state.mapper.0[0x11] = 0x12; assert_eq!(super::pop(&mut state), 0x1234); } } diff --git a/src/emulator/main.rs b/src/emulator/main.rs index 30adfca..449b5ea 100644 --- a/src/emulator/main.rs +++ b/src/emulator/main.rs @@ -1,15 +1,18 @@ use instructions::{arithmetic, branch, transfer}; +use mapper::MemoryMapper; use std::{env, fs}; use crate::structs::*; mod instructions; +mod mapper; mod structs; pub const MEMORY_SIZE: usize = 8192; fn main() { - let mut state = EmulatorState::default(); + use crate::mapper::TestMapper; + let mut state = EmulatorState::::default(); // Load the ROM into memory let mut args = env::args(); @@ -18,10 +21,10 @@ fn main() { .expect("Provide a path to a ROM file to emulate as an argument"); let file = fs::read(filename).expect("where file"); - let to_copy = MEMORY_SIZE.min(file.len()); - state.memory[..to_copy].copy_from_slice(&file[..to_copy]); + let to_copy = state.mapper.0.len().min(file.len()); + state.mapper.0[..to_copy].copy_from_slice(&file[..to_copy]); - while state.pc < MEMORY_SIZE as u16 { + while state.pc < u16::MAX { tick(&mut state); } @@ -29,7 +32,7 @@ fn main() { print_state(&state); } -fn tick(state: &mut EmulatorState) { +fn tick(state: &mut EmulatorState) { let instruction = state.next_byte(); match instruction { @@ -69,8 +72,8 @@ fn tick(state: &mut EmulatorState) { 0x0a => transfer::ldax(Register::B, state), // LDAX B 0x12 => transfer::stax(Register::D, state), // STAX D 0x1a => transfer::ldax(Register::D, state), // LDAX D - 0x32 => state.memory[state.next_word() as usize] = state.a, // STA - 0x3a => state.a = state.memory[state.next_word() as usize], // LDA + 0x32 => transfer::sta(state.next_word(), state), // STA + 0x3a => transfer::lda(state.next_word(), state), // LDA // 16-bit transfer instructions 0x01 => set_register_pair(Register::B, state.next_word(), state), // LXI B @@ -217,8 +220,8 @@ fn tick(state: &mut EmulatorState) { state.pc += 1; } -fn not_implemented(state: &EmulatorState) { - let instruction = state.memory[state.pc as usize]; +fn not_implemented(state: &mut EmulatorState) { + let instruction = state.read_byte(state.pc); panic!( "Unimplemented instruction {:#02X} at {:#04X}", instruction, state.pc diff --git a/src/emulator/mapper/mod.rs b/src/emulator/mapper/mod.rs new file mode 100644 index 0000000..897e917 --- /dev/null +++ b/src/emulator/mapper/mod.rs @@ -0,0 +1,25 @@ +pub trait MemoryMapper { + fn read(&mut self, address: u16) -> u8; + fn write(&mut self, address: u16, value: u8); +} + +//#[cfg(test)] +pub struct TestMapper(pub [u8; u16::MAX as usize + 1]); + +//#[cfg(test)] +impl MemoryMapper for TestMapper { + fn read(&mut self, address: u16) -> u8 { + self.0[address as usize] + } + + fn write(&mut self, address: u16, value: u8) { + self.0[address as usize] = value; + } +} + +//#[cfg(test)] +impl Default for TestMapper { + fn default() -> Self { + Self([0; u16::MAX as usize + 1]) + } +} diff --git a/src/emulator/structs.rs b/src/emulator/structs.rs index a129516..37570bf 100644 --- a/src/emulator/structs.rs +++ b/src/emulator/structs.rs @@ -1,7 +1,7 @@ -use crate::MEMORY_SIZE; +use crate::mapper::MemoryMapper; #[derive(Clone, PartialEq, Eq, Debug)] -pub struct EmulatorState { +pub struct EmulatorState { pub a: u8, pub b: u8, pub c: u8, @@ -18,43 +18,13 @@ pub struct EmulatorState { pub pc: u16, /// Enable interrupts pub ei: bool, - /// Memory map - pub memory: [u8; MEMORY_SIZE], + + /// Memory mapper + pub mapper: M, } -impl EmulatorState { - // Read the next byte from memory pointed at by PC - pub fn next_byte(&mut self) -> u8 { - let value = self.memory[self.pc as usize]; - (self.pc, ..) = self.pc.overflowing_add(1); - value - } - - // Read the next 16-bit word from memory pointed at by PC, in little endian order - pub fn next_word(&mut self) -> u16 { - let value = self.read_word(self.pc); - (self.pc, ..) = self.pc.overflowing_add(2); - value - } - - // Read a 16-bit word at a specific address - pub fn read_word(&mut self, address: u16) -> u16 { - u16::from_le_bytes([ - self.memory[address as usize], - self.memory[address.overflowing_add(1).0 as usize], - ]) - } - - // Write a 16-bit word at a specific address - pub fn write_word(&mut self, address: u16, value: u16) { - let [low, high] = u16::to_le_bytes(value); - self.memory[address as usize] = low; - self.memory[address.overflowing_add(1).0 as usize] = high; - } -} - -impl Default for EmulatorState { - fn default() -> Self { +impl EmulatorState { + pub fn new(mapper: M) -> Self { Self { a: 0, b: 0, @@ -72,9 +42,60 @@ impl Default for EmulatorState { }, pc: 0, ei: false, - memory: [0; MEMORY_SIZE], + mapper, } } + + /// Read a byte at a specific address + #[inline] + pub fn read_byte(&mut self, address: u16) -> u8 { + self.mapper.read(address) + } + + /// Write a byte at a specific address + #[inline] + pub fn write_byte(&mut self, address: u16, value: u8) { + self.mapper.write(address, value); + } + + /// Read the next byte from memory pointed at by PC + #[inline] + pub fn next_byte(&mut self) -> u8 { + let value = self.read_byte(self.pc); + (self.pc, ..) = self.pc.overflowing_add(1); + value + } + + /// Read a 16-bit word at a specific address + #[inline] + pub fn read_word(&mut self, address: u16) -> u16 { + u16::from_le_bytes([ + self.mapper.read(address), + self.mapper.read(address.overflowing_add(1).0), + ]) + } + + /// Write a 16-bit word at a specific address + #[inline] + pub fn write_word(&mut self, address: u16, value: u16) { + let [low, high] = u16::to_le_bytes(value); + self.mapper.write(address, low); + self.mapper.write(address.overflowing_add(1).0, high); + } + + /// Read the next 16-bit word from memory pointed at by PC, in little endian order + #[inline] + pub fn next_word(&mut self) -> u16 { + let value = self.read_word(self.pc); + (self.pc, ..) = self.pc.overflowing_add(2); + value + } +} + +impl Default for EmulatorState { + fn default() -> Self { + Self::new(M::default()) + } } #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -121,7 +142,7 @@ pub fn register_from_num(b: u8) -> Register { } } -pub fn get_register(register: Register, state: &EmulatorState) -> u8 { +pub fn get_register(register: Register, state: &mut EmulatorState) -> u8 { match register { Register::B => state.b, Register::C => state.c, @@ -130,12 +151,12 @@ pub fn get_register(register: Register, state: &EmulatorState) -> u8 { Register::H => state.h, Register::L => state.l, Register::A => state.a, - Register::M => state.memory[u16::from_le_bytes([state.l, state.h]) as usize], + Register::M => state.mapper.read(u16::from_le_bytes([state.l, state.h])), Register::SP => unreachable!(), } } -pub fn set_register(register: Register, value: u8, state: &mut EmulatorState) { +pub fn set_register(register: Register, value: u8, state: &mut EmulatorState) { match register { Register::B => state.b = value, Register::C => state.c = value, @@ -144,12 +165,14 @@ pub fn set_register(register: Register, value: u8, state: &mut EmulatorState) { Register::H => state.h = value, Register::L => state.l = value, Register::A => state.a = value, - Register::M => state.memory[u16::from_le_bytes([state.l, state.h]) as usize] = value, + Register::M => state + .mapper + .write(u16::from_le_bytes([state.l, state.h]), value), Register::SP => panic!("Cannot set 'SP' through set_register()"), }; } -pub fn get_register_pair(register: Register, state: &mut EmulatorState) -> u16 { +pub fn get_register_pair(register: Register, state: &mut EmulatorState) -> u16 { match register { Register::B => u16::from_le_bytes([state.c, state.b]), Register::D => u16::from_le_bytes([state.e, state.d]), @@ -170,7 +193,11 @@ pub fn get_register_pair(register: Register, state: &mut EmulatorState) -> u16 { } } -pub fn set_register_pair(register: Register, value: u16, state: &mut EmulatorState) { +pub fn set_register_pair( + register: Register, + value: u16, + state: &mut EmulatorState, +) { let arr = value.to_le_bytes(); let high = arr[1]; let low = arr[0]; @@ -205,7 +232,7 @@ pub fn set_register_pair(register: Register, value: u16, state: &mut EmulatorSta } /// Print values of registers and flags to stdout -pub fn print_state(state: &EmulatorState) { +pub fn print_state(state: &EmulatorState) { // State println!("\nsp\tpc\tei"); println!("{:#06x}\t{:#06x}\t{}", state.sp, state.pc, state.ei); @@ -228,27 +255,28 @@ pub fn print_state(state: &EmulatorState) { #[cfg(test)] mod tests { use super::*; + use crate::mapper::TestMapper; #[test] fn read_word() { - let mut state = EmulatorState::default(); - state.memory[0x10] = 0x34; - state.memory[0x11] = 0x12; + let mut state = EmulatorState::::default(); + state.mapper.0[0x10] = 0x34; + state.mapper.0[0x11] = 0x12; assert_eq!(state.read_word(0x10), 0x1234); } #[test] fn write_word() { - let mut state = EmulatorState::default(); + let mut state = EmulatorState::::default(); state.write_word(0x10, 0x1234); - assert_eq!(state.memory[0x10..=0x11], [0x34, 0x12]); + assert_eq!(state.mapper.0[0x10..=0x11], [0x34, 0x12]); } #[test] fn next_word_at_pc() { - let mut state = EmulatorState::default(); - state.memory[0x10] = 0x34; - state.memory[0x11] = 0x12; + let mut state = EmulatorState::::default(); + state.mapper.0[0x10] = 0x34; + state.mapper.0[0x11] = 0x12; state.pc = 0x10; assert_eq!(state.next_word(), 0x1234); }