forked from Lea/invadeez
memory mapping
This commit is contained in:
parent
465aadd54e
commit
a97824b26e
|
@ -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<M: MemoryMapper>(state: &mut EmulatorState<M>, 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<M: MemoryMapper>(register: Register, carry: bool, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(byte: u8, carry: bool, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, borrow: bool, state: &mut EmulatorState<M>) {
|
||||
sub(get_register(register, state), borrow, state);
|
||||
}
|
||||
|
||||
pub fn sub(byte: u8, borrow: bool, state: &mut EmulatorState) {
|
||||
pub fn sub<M: MemoryMapper>(byte: u8, borrow: bool, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
$name(get_register(register, state), state);
|
||||
}
|
||||
|
||||
pub fn $name(byte: u8, state: &mut EmulatorState) {
|
||||
pub fn $name<M: MemoryMapper>(byte: u8, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
cmp(get_register(register, state), state);
|
||||
}
|
||||
|
||||
pub fn cmp(byte: u8, state: &mut EmulatorState) {
|
||||
pub fn cmp<M: MemoryMapper>(byte: u8, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
state.cc.c = state.a & 0x01 > 0;
|
||||
state.a = state.a.rotate_right(1);
|
||||
}
|
||||
|
||||
pub fn ral(state: &mut EmulatorState) {
|
||||
pub fn ral<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
let new_carry = state.a & 0x01 > 0;
|
||||
let result = state.a >> 1;
|
||||
state.a = result | (new_carry as u8) << 7;
|
||||
|
|
|
@ -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<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(address: u16, cond: bool, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(address: u16, cond: bool, state: &mut EmulatorState<M>) {
|
||||
if cond {
|
||||
call(address, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Pop a value from the stack and jump to it
|
||||
pub fn ret(state: &mut EmulatorState) {
|
||||
pub fn ret<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(cond: bool, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(vector: u8, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
state.pc = get_register_pair(Register::H, state);
|
||||
}
|
||||
|
|
|
@ -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<M: MemoryMapper>(src: Register, dest: Register, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(byte: u8, dest: Register, state: &mut EmulatorState<M>) {
|
||||
set_register(dest, byte, state);
|
||||
}
|
||||
|
||||
// Store accumulator to the speficied address
|
||||
pub fn sta<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
|
||||
state.write_byte(address, state.a);
|
||||
}
|
||||
|
||||
// Load accumulator from the specified address
|
||||
pub fn lda<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
state.sp = get_register_pair(Register::H, state);
|
||||
}
|
||||
|
||||
/// Exchange the HL and DE register pairs
|
||||
pub fn xchg(state: &mut EmulatorState) {
|
||||
pub fn xchg<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(value: u16, state: &mut EmulatorState<M>) {
|
||||
(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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(state: &mut EmulatorState<M>) -> 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<TestMapper> = 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<TestMapper> = 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<TestMapper>::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<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||
let instruction = state.read_byte(state.pc);
|
||||
panic!(
|
||||
"Unimplemented instruction {:#02X} at {:#04X}",
|
||||
instruction, state.pc
|
||||
|
|
25
src/emulator/mapper/mod.rs
Normal file
25
src/emulator/mapper/mod.rs
Normal file
|
@ -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])
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use crate::MEMORY_SIZE;
|
||||
use crate::mapper::MemoryMapper;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct EmulatorState {
|
||||
pub struct EmulatorState<M: MemoryMapper> {
|
||||
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<M: MemoryMapper> EmulatorState<M> {
|
||||
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<M: MemoryMapper + Default> Default for EmulatorState<M> {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) -> 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<M: MemoryMapper>(register: Register, value: u8, state: &mut EmulatorState<M>) {
|
||||
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<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) -> 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<M: MemoryMapper>(
|
||||
register: Register,
|
||||
value: u16,
|
||||
state: &mut EmulatorState<M>,
|
||||
) {
|
||||
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<M: MemoryMapper>(state: &EmulatorState<M>) {
|
||||
// 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::<TestMapper>::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::<TestMapper>::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::<TestMapper>::default();
|
||||
state.mapper.0[0x10] = 0x34;
|
||||
state.mapper.0[0x11] = 0x12;
|
||||
state.pc = 0x10;
|
||||
assert_eq!(state.next_word(), 0x1234);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue