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::structs::{get_register_pair, set_register, set_register_pair};
|
||||||
use crate::{get_register, EmulatorState, Register};
|
use crate::{get_register, EmulatorState, Register};
|
||||||
|
|
||||||
/// Sets the condition code flags according to `result`.
|
/// Sets the condition code flags according to `result`.
|
||||||
/// Does not set the carry flag and will always set the Z, S and P flags.
|
/// Does not set the carry flag and will always set the Z, S and P flags.
|
||||||
#[inline(always)]
|
#[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.z = result == 0;
|
||||||
state.cc.s = result & 0x80 > 0;
|
state.cc.s = result & 0x80 > 0;
|
||||||
state.cc.p = result.count_ones() % 2 == 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)
|
/// 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(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)
|
/// 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 (a, first) = state.a.overflowing_add(byte);
|
||||||
let (result, second) = a.overflowing_add(carry as u8);
|
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;
|
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);
|
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 (a, first) = state.a.overflowing_sub(byte);
|
||||||
let (result, second) = a.overflowing_sub(borrow as u8);
|
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 {
|
macro_rules! bitwise_op {
|
||||||
($name:ident, $reg_name:ident, $op:tt) => {
|
($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);
|
$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;
|
let result = state.a $op byte;
|
||||||
state.cc.c = false;
|
state.cc.c = false;
|
||||||
set_cc(state, result);
|
set_cc(state, result);
|
||||||
|
@ -57,18 +58,18 @@ bitwise_op!(and, and_reg, &);
|
||||||
bitwise_op!(or, or_reg, |);
|
bitwise_op!(or, or_reg, |);
|
||||||
bitwise_op!(xor, xor_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);
|
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);
|
let (result, carry) = state.a.overflowing_sub(byte);
|
||||||
state.cc.c = carry;
|
state.cc.c = carry;
|
||||||
set_cc(state, result);
|
set_cc(state, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Double precision add - Add B&C, D&E or H&L to H&L
|
/// 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 num = get_register_pair(register, state);
|
||||||
let (result, overflow) = num.overflowing_add(u16::from_le_bytes([state.l, state.h]));
|
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
|
/// 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);
|
let (result, _) = get_register(register, state).overflowing_add(1);
|
||||||
set_cc(state, result);
|
set_cc(state, result);
|
||||||
set_register(register, result, state);
|
set_register(register, result, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrease register
|
/// 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);
|
let (result, _) = get_register(register, state).overflowing_sub(1);
|
||||||
set_cc(state, result);
|
set_cc(state, result);
|
||||||
set_register(register, result, state);
|
set_register(register, result, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Increase register pair
|
/// 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);
|
let (result, _) = get_register_pair(register, state).overflowing_add(1);
|
||||||
set_register_pair(register, result, state);
|
set_register_pair(register, result, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrease register pair
|
/// 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);
|
let (result, _) = get_register_pair(register, state).overflowing_sub(1);
|
||||||
set_register_pair(register, result, state);
|
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);
|
let result = state.a.rotate_left(1);
|
||||||
state.a = result;
|
state.a = result;
|
||||||
state.cc.c = result & 0x01 > 0;
|
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.cc.c = state.a & 0x01 > 0;
|
||||||
state.a = state.a.rotate_right(1);
|
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 new_carry = state.a & 0x80 > 0;
|
||||||
let result = state.a << 1;
|
let result = state.a << 1;
|
||||||
state.a = result | new_carry as u8;
|
state.a = result | new_carry as u8;
|
||||||
state.cc.c = new_carry;
|
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 new_carry = state.a & 0x01 > 0;
|
||||||
let result = state.a >> 1;
|
let result = state.a >> 1;
|
||||||
state.a = result | (new_carry as u8) << 7;
|
state.a = result | (new_carry as u8) << 7;
|
||||||
|
|
|
@ -1,38 +1,39 @@
|
||||||
|
use crate::mapper::MemoryMapper;
|
||||||
use crate::transfer::{pop, push};
|
use crate::transfer::{pop, push};
|
||||||
use crate::{get_register_pair, EmulatorState, Register};
|
use crate::{get_register_pair, EmulatorState, Register};
|
||||||
|
|
||||||
/// Jump (set PC) to specified address
|
/// 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;
|
state.pc = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Jump to a specified address only if `cond` is true
|
/// 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 {
|
if cond {
|
||||||
jump(address, state);
|
jump(address, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push the current PC to the stack and jump to the specified address
|
/// 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);
|
push(state.pc, state);
|
||||||
jump(address, state);
|
jump(address, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the current PC to the stack and jump to the specified address if `cond` is true
|
// 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 {
|
if cond {
|
||||||
call(address, state);
|
call(address, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop a value from the stack and jump to it
|
// 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);
|
jump(pop(state), state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop a value from the stack and jump to it if `cond` is true
|
// 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 {
|
if cond {
|
||||||
ret(state)
|
ret(state)
|
||||||
}
|
}
|
||||||
|
@ -41,12 +42,12 @@ pub fn ret_cond(cond: bool, state: &mut EmulatorState) {
|
||||||
/// "Restart" at (call) a specific interrupt vector
|
/// "Restart" at (call) a specific interrupt vector
|
||||||
///
|
///
|
||||||
/// Panics if `vector` is 8 or above
|
/// 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");
|
assert!(vector < 8, "Illegal restart vector");
|
||||||
call((vector * 8) as u16, state);
|
call((vector * 8) as u16, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a new program counter from the HL register pair
|
/// 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);
|
state.pc = get_register_pair(Register::H, state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,55 @@
|
||||||
|
use crate::mapper::MemoryMapper;
|
||||||
use crate::{
|
use crate::{
|
||||||
get_register, get_register_pair, set_register, set_register_pair, EmulatorState, Register,
|
get_register, get_register_pair, set_register, set_register_pair, EmulatorState, Register,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Move (copy) value from source to destination 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);
|
let data = get_register(src, state);
|
||||||
set_register(dest, data, state);
|
set_register(dest, data, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move immediate into destination register
|
/// 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);
|
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
|
/// 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);
|
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
|
/// 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);
|
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
|
/// Store a 16-bit word from H and L to the specified address
|
||||||
pub fn shld(address: u16, state: &mut EmulatorState) {
|
pub fn shld<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
|
||||||
state.memory[address as usize] = state.l;
|
state.write_byte(address, state.l);
|
||||||
state.memory[address as usize + 1] = state.h;
|
state.write_byte(address.overflowing_add(1).0, state.h)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a 16-bit word into H and L from the specified address
|
/// Load a 16-bit word into H and L from the specified address
|
||||||
pub fn lhld(address: u16, state: &mut EmulatorState) {
|
pub fn lhld<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
|
||||||
state.l = state.memory[address as usize];
|
state.l = state.read_byte(address);
|
||||||
state.h = state.memory[address as usize + 1];
|
state.h = state.read_byte(address.overflowing_add(1).0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exchange the HL register pair with the value at the top of the stack
|
/// 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_hl = get_register_pair(Register::H, state);
|
||||||
let at_stack = state.read_word(state.sp);
|
let at_stack = state.read_word(state.sp);
|
||||||
// Set HL to the 16-bit value currently on top of the stack
|
// 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
|
/// 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);
|
state.sp = get_register_pair(Register::H, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exchange the HL and DE register pairs
|
/// 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_hl = get_register_pair(Register::H, state);
|
||||||
let at_de = get_register_pair(Register::D, state);
|
let at_de = get_register_pair(Register::D, state);
|
||||||
// Set HL to DE
|
// Set HL to DE
|
||||||
|
@ -64,23 +75,23 @@ pub fn xchg(state: &mut EmulatorState) {
|
||||||
|
|
||||||
/* STACK */
|
/* STACK */
|
||||||
/// Push a 16-bit value from a register pair onto the 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(get_register_pair(register, state), state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a 16-bit value onto the stack
|
/// 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.sp, ..) = state.sp.overflowing_sub(2);
|
||||||
state.write_word(state.sp, value);
|
state.write_word(state.sp, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pop a 16-bit value from the stack into a register pair
|
/// 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);
|
set_register_pair(register, pop(state), state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pop a 16-bit value from the stack
|
/// 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);
|
let value = state.read_word(state.sp);
|
||||||
(state.sp, ..) = state.sp.overflowing_add(2);
|
(state.sp, ..) = state.sp.overflowing_add(2);
|
||||||
value
|
value
|
||||||
|
@ -88,6 +99,7 @@ pub fn pop(state: &mut EmulatorState) -> u16 {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::mapper::TestMapper;
|
||||||
use crate::EmulatorState;
|
use crate::EmulatorState;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -100,14 +112,14 @@ mod tests {
|
||||||
// memory address two less than the contents of the
|
// memory address two less than the contents of the
|
||||||
// stack pointer.
|
// stack pointer.
|
||||||
// (3) The stack pointer is automatically decremented by two.
|
// (3) The stack pointer is automatically decremented by two.
|
||||||
let mut state = EmulatorState {
|
let mut state: EmulatorState<TestMapper> = EmulatorState {
|
||||||
sp: 0x12,
|
sp: 0x12,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
super::push(0x1234, &mut state);
|
super::push(0x1234, &mut state);
|
||||||
assert_eq!(state.sp, 0x10);
|
assert_eq!(state.sp, 0x10);
|
||||||
assert_eq!(state.memory[0x10..=0x11], [0x34, 0x12]);
|
assert_eq!(state.mapper.0[0x10..=0x11], [0x34, 0x12]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -121,13 +133,13 @@ mod tests {
|
||||||
// memory address one greater than the address held in
|
// memory address one greater than the address held in
|
||||||
// the stack pointer.
|
// the stack pointer.
|
||||||
// (3) The stack pointer is automatically incremented by two.
|
// (3) The stack pointer is automatically incremented by two.
|
||||||
let mut state = EmulatorState {
|
let mut state: EmulatorState<TestMapper> = EmulatorState {
|
||||||
sp: 0x10,
|
sp: 0x10,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
state.memory[0x10] = 0x34;
|
state.mapper.0[0x10] = 0x34;
|
||||||
state.memory[0x11] = 0x12;
|
state.mapper.0[0x11] = 0x12;
|
||||||
assert_eq!(super::pop(&mut state), 0x1234);
|
assert_eq!(super::pop(&mut state), 0x1234);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
use instructions::{arithmetic, branch, transfer};
|
use instructions::{arithmetic, branch, transfer};
|
||||||
|
use mapper::MemoryMapper;
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
use crate::structs::*;
|
use crate::structs::*;
|
||||||
|
|
||||||
mod instructions;
|
mod instructions;
|
||||||
|
mod mapper;
|
||||||
mod structs;
|
mod structs;
|
||||||
|
|
||||||
pub const MEMORY_SIZE: usize = 8192;
|
pub const MEMORY_SIZE: usize = 8192;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut state = EmulatorState::default();
|
use crate::mapper::TestMapper;
|
||||||
|
let mut state = EmulatorState::<TestMapper>::default();
|
||||||
|
|
||||||
// Load the ROM into memory
|
// Load the ROM into memory
|
||||||
let mut args = env::args();
|
let mut args = env::args();
|
||||||
|
@ -18,10 +21,10 @@ fn main() {
|
||||||
.expect("Provide a path to a ROM file to emulate as an argument");
|
.expect("Provide a path to a ROM file to emulate as an argument");
|
||||||
let file = fs::read(filename).expect("where file");
|
let file = fs::read(filename).expect("where file");
|
||||||
|
|
||||||
let to_copy = MEMORY_SIZE.min(file.len());
|
let to_copy = state.mapper.0.len().min(file.len());
|
||||||
state.memory[..to_copy].copy_from_slice(&file[..to_copy]);
|
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);
|
tick(&mut state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +32,7 @@ fn main() {
|
||||||
print_state(&state);
|
print_state(&state);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(state: &mut EmulatorState) {
|
fn tick<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||||
let instruction = state.next_byte();
|
let instruction = state.next_byte();
|
||||||
|
|
||||||
match instruction {
|
match instruction {
|
||||||
|
@ -69,8 +72,8 @@ fn tick(state: &mut EmulatorState) {
|
||||||
0x0a => transfer::ldax(Register::B, state), // LDAX B
|
0x0a => transfer::ldax(Register::B, state), // LDAX B
|
||||||
0x12 => transfer::stax(Register::D, state), // STAX D
|
0x12 => transfer::stax(Register::D, state), // STAX D
|
||||||
0x1a => transfer::ldax(Register::D, state), // LDAX D
|
0x1a => transfer::ldax(Register::D, state), // LDAX D
|
||||||
0x32 => state.memory[state.next_word() as usize] = state.a, // STA
|
0x32 => transfer::sta(state.next_word(), state), // STA
|
||||||
0x3a => state.a = state.memory[state.next_word() as usize], // LDA
|
0x3a => transfer::lda(state.next_word(), state), // LDA
|
||||||
|
|
||||||
// 16-bit transfer instructions
|
// 16-bit transfer instructions
|
||||||
0x01 => set_register_pair(Register::B, state.next_word(), state), // LXI B
|
0x01 => set_register_pair(Register::B, state.next_word(), state), // LXI B
|
||||||
|
@ -217,8 +220,8 @@ fn tick(state: &mut EmulatorState) {
|
||||||
state.pc += 1;
|
state.pc += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn not_implemented(state: &EmulatorState) {
|
fn not_implemented<M: MemoryMapper>(state: &mut EmulatorState<M>) {
|
||||||
let instruction = state.memory[state.pc as usize];
|
let instruction = state.read_byte(state.pc);
|
||||||
panic!(
|
panic!(
|
||||||
"Unimplemented instruction {:#02X} at {:#04X}",
|
"Unimplemented instruction {:#02X} at {:#04X}",
|
||||||
instruction, state.pc
|
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)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct EmulatorState {
|
pub struct EmulatorState<M: MemoryMapper> {
|
||||||
pub a: u8,
|
pub a: u8,
|
||||||
pub b: u8,
|
pub b: u8,
|
||||||
pub c: u8,
|
pub c: u8,
|
||||||
|
@ -18,43 +18,13 @@ pub struct EmulatorState {
|
||||||
pub pc: u16,
|
pub pc: u16,
|
||||||
/// Enable interrupts
|
/// Enable interrupts
|
||||||
pub ei: bool,
|
pub ei: bool,
|
||||||
/// Memory map
|
|
||||||
pub memory: [u8; MEMORY_SIZE],
|
/// Memory mapper
|
||||||
|
pub mapper: M,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmulatorState {
|
impl<M: MemoryMapper> EmulatorState<M> {
|
||||||
// Read the next byte from memory pointed at by PC
|
pub fn new(mapper: M) -> Self {
|
||||||
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 {
|
|
||||||
Self {
|
Self {
|
||||||
a: 0,
|
a: 0,
|
||||||
b: 0,
|
b: 0,
|
||||||
|
@ -72,9 +42,60 @@ impl Default for EmulatorState {
|
||||||
},
|
},
|
||||||
pc: 0,
|
pc: 0,
|
||||||
ei: false,
|
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)]
|
#[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 {
|
match register {
|
||||||
Register::B => state.b,
|
Register::B => state.b,
|
||||||
Register::C => state.c,
|
Register::C => state.c,
|
||||||
|
@ -130,12 +151,12 @@ pub fn get_register(register: Register, state: &EmulatorState) -> u8 {
|
||||||
Register::H => state.h,
|
Register::H => state.h,
|
||||||
Register::L => state.l,
|
Register::L => state.l,
|
||||||
Register::A => state.a,
|
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!(),
|
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 {
|
match register {
|
||||||
Register::B => state.b = value,
|
Register::B => state.b = value,
|
||||||
Register::C => state.c = 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::H => state.h = value,
|
||||||
Register::L => state.l = value,
|
Register::L => state.l = value,
|
||||||
Register::A => state.a = 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()"),
|
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 {
|
match register {
|
||||||
Register::B => u16::from_le_bytes([state.c, state.b]),
|
Register::B => u16::from_le_bytes([state.c, state.b]),
|
||||||
Register::D => u16::from_le_bytes([state.e, state.d]),
|
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 arr = value.to_le_bytes();
|
||||||
let high = arr[1];
|
let high = arr[1];
|
||||||
let low = arr[0];
|
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
|
/// Print values of registers and flags to stdout
|
||||||
pub fn print_state(state: &EmulatorState) {
|
pub fn print_state<M: MemoryMapper>(state: &EmulatorState<M>) {
|
||||||
// State
|
// State
|
||||||
println!("\nsp\tpc\tei");
|
println!("\nsp\tpc\tei");
|
||||||
println!("{:#06x}\t{:#06x}\t{}", state.sp, state.pc, state.ei);
|
println!("{:#06x}\t{:#06x}\t{}", state.sp, state.pc, state.ei);
|
||||||
|
@ -228,27 +255,28 @@ pub fn print_state(state: &EmulatorState) {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::mapper::TestMapper;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_word() {
|
fn read_word() {
|
||||||
let mut state = EmulatorState::default();
|
let mut state = EmulatorState::<TestMapper>::default();
|
||||||
state.memory[0x10] = 0x34;
|
state.mapper.0[0x10] = 0x34;
|
||||||
state.memory[0x11] = 0x12;
|
state.mapper.0[0x11] = 0x12;
|
||||||
assert_eq!(state.read_word(0x10), 0x1234);
|
assert_eq!(state.read_word(0x10), 0x1234);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_word() {
|
fn write_word() {
|
||||||
let mut state = EmulatorState::default();
|
let mut state = EmulatorState::<TestMapper>::default();
|
||||||
state.write_word(0x10, 0x1234);
|
state.write_word(0x10, 0x1234);
|
||||||
assert_eq!(state.memory[0x10..=0x11], [0x34, 0x12]);
|
assert_eq!(state.mapper.0[0x10..=0x11], [0x34, 0x12]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn next_word_at_pc() {
|
fn next_word_at_pc() {
|
||||||
let mut state = EmulatorState::default();
|
let mut state = EmulatorState::<TestMapper>::default();
|
||||||
state.memory[0x10] = 0x34;
|
state.mapper.0[0x10] = 0x34;
|
||||||
state.memory[0x11] = 0x12;
|
state.mapper.0[0x11] = 0x12;
|
||||||
state.pc = 0x10;
|
state.pc = 0x10;
|
||||||
assert_eq!(state.next_word(), 0x1234);
|
assert_eq!(state.next_word(), 0x1234);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue