diff --git a/Cargo.toml b/Cargo.toml index bbc199a..f590fae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,10 @@ edition = "2021" [dependencies] +[[bin]] +name = "emulator" +path = "src/emulator/main.rs" + [[bin]] name = "disassembler" path = "src/disassembler/main.rs" diff --git a/src/emulator/instructions/arithmetic.rs b/src/emulator/instructions/arithmetic.rs new file mode 100644 index 0000000..d8dc7d6 --- /dev/null +++ b/src/emulator/instructions/arithmetic.rs @@ -0,0 +1,15 @@ +use crate::{EmulatorState, Register, get_register}; + +/// Sets the condition code flags according to `result` +fn set_cc(state: &mut EmulatorState, result: u16) { + state.cc.z = (result & 0xff) == 0; + state.cc.s = (result & 0x80) == 1; + state.cc.c = result > 0xff; + state.cc.p = (result & 0xff).count_ones() % 2 == 0; +} + +pub fn add(register: Register, state: &mut EmulatorState) { + let result = get_register(register, state) + state.a as u16; + set_cc(state, result); + state.a = (result & 0xff) as u8; +} diff --git a/src/emulator/instructions/mod.rs b/src/emulator/instructions/mod.rs new file mode 100644 index 0000000..965db62 --- /dev/null +++ b/src/emulator/instructions/mod.rs @@ -0,0 +1 @@ +pub mod arithmetic; diff --git a/src/emulator/main.rs b/src/emulator/main.rs new file mode 100644 index 0000000..75da473 --- /dev/null +++ b/src/emulator/main.rs @@ -0,0 +1,134 @@ +use std::{fs, env}; +use instructions::arithmetic::add; + +mod instructions; + +pub struct EmulatorState { + a: u8, + b: u8, + c: u8, + d: u8, + e: u8, + h: u8, + l: u8, + + cc: ConditionCodes, + + /// Stack pointer + sp: u16, + /// Memory pointer + pc: u16, + memory: [u8; 8000], +} + +pub struct ConditionCodes { + /// Zero (Z), set if the result is zero. + z: bool, + /// Sign (S), set if the result is negative. + s: bool, + /// Parity (P), set if the number of 1 bits in the result is even. + p: bool, + /// Carry (C), set if the last addition operation resulted in a carry or if the last subtraction operation required a borrow. + c: bool, + /// Auxiliary carry (AC or H), used for binary-coded decimal arithmetic (BCD). + ac: bool, +} + +pub enum Register { + B, C, D, E, + H, L, M, A, +} + +/// Returns a Register enum based on the input number 0..7 in the order B,C,D,E,H,L,M,A +fn register_from_num(b: u8) -> Register { + match b { + 0 => Register::B, + 1 => Register::C, + 2 => Register::D, + 3 => Register::E, + 4 => Register::H, + 5 => Register::L, + 6 => Register::M, + 7 => Register::A, + _ => panic!("'{b}' cannot be converted to register enum"), + } +} + +fn get_register(register: Register, state: &EmulatorState) -> u16 { + match register { + Register::B => state.b as u16, + Register::C => state.c as u16, + Register::D => state.d as u16, + Register::E => state.e as u16, + Register::H => state.h as u16, + Register::L => state.l as u16, + Register::A => state.a as u16, + Register::M => (state.memory[(state.h as usize)] as u16) << 8 | (state.memory[state.l as usize] as u16), + } +} + +fn set_register(register: Register, value: u8, state: &mut EmulatorState) { + match register { + Register::B => state.b = value, + Register::C => state.c = value, + Register::D => state.d = value, + Register::E => state.e = value, + Register::H => state.h = value, + Register::L => state.l = value, + Register::A => state.a = value, + Register::M => panic!("Cannot set pseudoregister 'M'"), + }; +} + +fn main() { + let mut state = EmulatorState { + a: 0, + b: 0, + c: 0, + d: 0, + e: 0, + h: 0, + l: 0, + sp: 0, + cc: ConditionCodes { z: true, s: true, p: true, c: true, ac: true }, + pc: 0, + memory: [0; 8000], + }; + + // Load the ROM into memory + let mut args = env::args(); + let filename = args + .nth(1) + .expect("Provide a path to a ROM file to emulate as an argument"); + let file = fs::read(filename).expect("where file"); + + for i in 0..8000 { + state.memory[i] = file[i]; + } + + while state.pc < 8000 { + tick(&mut state); + } +} + +fn tick(state: &mut EmulatorState) { + let instruction = state.memory[state.pc as usize]; + + let mut next_byte = || { + state.pc += 1; + return state.memory[state.pc as usize]; + }; + + match instruction { + 0x0 => {} // NOP + 0x80..=0x87 => add(register_from_num(instruction & 0xf), state), // ADD + _ => not_implemented(state), + } + + state.pc += 1; +} + +fn not_implemented(state: &EmulatorState) { + let instruction = state.memory[state.pc as usize]; + panic!("Unimplemented instruction {:#02X} at {:#04X}", instruction, state.pc); +}