1353 lines
44 KiB
Rust
1353 lines
44 KiB
Rust
use crate::{opcodes, VERBOSE_DISASSEMBLE, VERBOSE_DISASSEMBLE_SECTION, DISASSEMBLE_SECTION_START, DISASSEMBLE_SECTION_END};
|
|
use crate::opcodes::*;
|
|
use crate::memory::Memory;
|
|
use crate::state::*;
|
|
use crate::emulator_state::SharedState;
|
|
use std::thread;
|
|
use wasm_timer::{SystemTime, Instant};
|
|
use std::time::{Duration};
|
|
use lazy_static::lazy_static;
|
|
use std::sync::{Mutex, RwLock, MutexGuard};
|
|
use wasm_bindgen::prelude::*;
|
|
use once_cell::sync::OnceCell;
|
|
|
|
static SHARED_STATE: OnceCell<Mutex<SharedState>> = OnceCell::new();
|
|
|
|
// lazy_static! {
|
|
// pub(crate) static ref SHARED_STATE: Mutex<SharedState> = Mutex::new(SharedState::new());
|
|
// }
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub fn graphic_memory() -> Vec<u8> {
|
|
let memory = crate::memory::STATIC_MEMORY.read().unwrap();
|
|
memory[0x2400..0x2400 + crate::memory::GRAPHIC_MEMORY_SIZE].to_vec()
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
pub enum StepStatus {
|
|
Continue,
|
|
Paused,
|
|
Success(String),
|
|
Failure(String)
|
|
}
|
|
|
|
pub struct StepResult {
|
|
pub status: StepStatus,
|
|
pub cycles: u8,
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub struct Emulator {
|
|
memory: Box<Memory>,
|
|
state: Option<State>,
|
|
shift_register: u16,
|
|
shift_register_offset: u8,
|
|
output_buffer: Vec<char>,
|
|
}
|
|
|
|
pub const WIDTH: u16 = 224;
|
|
pub const HEIGHT: u16 = 256;
|
|
|
|
#[wasm_bindgen(js_namespace = console)]
|
|
pub fn start_emulator_wasm() {
|
|
println!("Starting emulator wasm");
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn spawn_emulator() {
|
|
let mut emulator = Emulator::new_space_invaders();
|
|
let time_per_frame_ms = 16;
|
|
loop {
|
|
let start = SystemTime::now();
|
|
// Run one frame
|
|
let cycles = emulator.run_one_frame(false);
|
|
let elapsed = start.elapsed().unwrap().as_millis();
|
|
|
|
// Wait until we reach 16ms before running the next frame.
|
|
// TODO: I'm not 100% sure the event pump is being invoked on a 16ms cadence,
|
|
// which might explain why my game is going a bit too fast. I should actually
|
|
// rewrite this logic to guarantee that it runs every 16ms
|
|
if elapsed < time_per_frame_ms {
|
|
std::thread::sleep(Duration::from_millis((time_per_frame_ms - elapsed) as u64));
|
|
}
|
|
let after_sleep = start.elapsed().unwrap().as_micros();
|
|
if false {
|
|
println!("Actual time frame: {}ms, after sleep: {} ms, cycles: {}",
|
|
elapsed,
|
|
after_sleep,
|
|
cycles);
|
|
}
|
|
|
|
SHARED_STATE.get().unwrap().lock().unwrap()
|
|
.set_megahertz(cycles as f64 / after_sleep as f64);
|
|
}
|
|
}
|
|
|
|
pub fn spawn_emulator_thread() {
|
|
//
|
|
// Spawn the game logic in a separate thread. This logic will communicate with the
|
|
// main thread (and therefore, the actual graphics on your screen) via the `listener`
|
|
// object that this function receives in parameter.
|
|
//
|
|
thread::spawn(move || {
|
|
spawn_emulator();
|
|
});
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
extern {
|
|
// Use `js_namespace` here to bind `console.log(..)` instead of just
|
|
// `log(..)`
|
|
#[cfg(target_arch = "wasm32")]
|
|
#[wasm_bindgen(js_namespace = console)]
|
|
fn log(s: &str);
|
|
}
|
|
|
|
|
|
// #[cfg(target_arch = "wasm32")]
|
|
// pub fn log(s: &str) {
|
|
// javascript_log(s);
|
|
// }
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
pub fn log(s: &str) {
|
|
println!("{}", s);
|
|
}
|
|
|
|
impl Emulator {
|
|
|
|
pub fn new_space_invaders() -> Emulator {
|
|
let mut memory = Memory::new();
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
memory.read_file("space-invaders.rom", 0);
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
log("Warning: need to read the rom file in WASM mode");
|
|
|
|
Emulator::new(Box::new(memory), 0)
|
|
}
|
|
|
|
pub fn new(memory: Box<Memory>, pc: usize) -> Emulator {
|
|
Emulator { memory,
|
|
shift_register: 0,
|
|
shift_register_offset: 0,
|
|
state: Some(State::new(pc)),
|
|
output_buffer: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn start_emulator() -> &'static Mutex<SharedState> {
|
|
SHARED_STATE.set(Mutex::new(SharedState::new()));
|
|
spawn_emulator_thread();
|
|
&SHARED_STATE.get().unwrap()
|
|
}
|
|
|
|
pub fn run_one_frame(&mut self, verbose: bool) -> u64 {
|
|
let mut total_cycles: u64 = 0;
|
|
let cycle_max = 33_000;
|
|
while total_cycles < cycle_max / 2 {
|
|
total_cycles += self.step(verbose).cycles as u64;
|
|
}
|
|
self.interrupt(1);
|
|
|
|
while total_cycles < cycle_max {
|
|
total_cycles += self.step(verbose).cycles as u64;
|
|
}
|
|
self.interrupt(2);
|
|
|
|
total_cycles
|
|
}
|
|
|
|
pub fn step(&mut self, verbose: bool) -> StepResult {
|
|
let shared = SHARED_STATE.get().unwrap();
|
|
if shared.lock().unwrap().is_paused() {
|
|
return StepResult { status: StepStatus::Paused, cycles: 0 };
|
|
}
|
|
|
|
let state = &mut self.state.as_mut().unwrap();
|
|
let op: u8 = self.memory.read(state.pc);
|
|
let opcode = OPCODES.get(&op).expect(format!("Couldn't find opcode {:02x} at pc {:04x}",
|
|
op, state.pc).as_str());
|
|
let mut pc_was_assigned = false;
|
|
let byte1 = self.memory.read(state.pc + 1);
|
|
let byte2 = self.memory.read(state.pc + 2);
|
|
let word = Memory::to_word(byte1, byte2);
|
|
let cycles;
|
|
|
|
unsafe {
|
|
if VERBOSE_DISASSEMBLE || (VERBOSE_DISASSEMBLE_SECTION &&
|
|
(DISASSEMBLE_SECTION_START..=DISASSEMBLE_SECTION_END).contains(&state.pc)) {
|
|
let line1 = self.memory.disassemble(&opcode, state.pc);
|
|
println!("{} {:10}", line1.0, state.disassemble());
|
|
}
|
|
}
|
|
|
|
let mut test_status = StepResult { status: StepStatus::Continue, cycles: 0};
|
|
|
|
match op {
|
|
opcodes::NOP => {
|
|
cycles = 4;
|
|
},
|
|
opcodes::INX_B => {
|
|
state.c = ((state.c as u16) + 1) as u8;
|
|
if state.c == 0 {
|
|
state.b = ((state.b as u16) + 1) as u8;
|
|
}
|
|
cycles = 5;
|
|
},
|
|
opcodes::INX_D => {
|
|
state.e = ((state.e as u16) + 1) as u8;
|
|
if state.e == 0 {
|
|
state.d = ((state.d as u16) + 1) as u8;
|
|
}
|
|
cycles = 5;
|
|
},
|
|
opcodes::INX_SP => {
|
|
if state.sp == 0xffff {
|
|
state.sp = 0;
|
|
} else {
|
|
state.sp += 1;
|
|
}
|
|
cycles = 5;
|
|
},
|
|
opcodes::INX_H => {
|
|
state.l = ((state.l as u16) + 1) as u8;
|
|
if state.l == 0 {
|
|
state.h = ((state.h as u16) + 1) as u8;
|
|
}
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCX_B => {
|
|
state.c = ((state.c as i8) - 1) as u8;
|
|
if state.c == 0xff {
|
|
state.b = ((state.b as i8) - 1) as u8;
|
|
}
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCX_D => {
|
|
state.e = ((state.e as i8) - 1) as u8;
|
|
if state.e == 0xff {
|
|
state.d = ((state.d as i8) - 1) as u8;
|
|
}
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCX_H => {
|
|
let m = state.m() as i32 - 1;
|
|
state.h = (m >> 8) as u8;
|
|
state.l = m as u8;
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCX_SP => {
|
|
if state.sp == 0 {
|
|
state.sp = 0xffff as usize;
|
|
} else {
|
|
state.sp -= 1;
|
|
}
|
|
cycles = 5;
|
|
},
|
|
opcodes::DAD_B => {
|
|
state.add_hl(state.c, state.b);
|
|
cycles = 10;
|
|
},
|
|
opcodes::DAD_D => {
|
|
state.add_hl(state.e, state.d);
|
|
cycles = 10;
|
|
},
|
|
opcodes::DAD_H => {
|
|
// not tested by cpudiag
|
|
state.add_hl(state.l, state.h);
|
|
cycles = 10;
|
|
},
|
|
opcodes::DAD_SP => {
|
|
state.add_hl(state.sp as u8, state.sp as u8 - 1);
|
|
cycles = 10;
|
|
},
|
|
opcodes::RAL => {
|
|
// high order bit
|
|
let hob = (state.psw.a & 0x80) >> 7;
|
|
let carry_value = Psw::to_u8(state.psw.carry) as u8;
|
|
state.psw.a = (state.psw.a << 1) | carry_value;
|
|
state.psw.carry = if hob == 1 { true } else { false };
|
|
cycles = 4;
|
|
},
|
|
opcodes::RAR => {
|
|
// low order bit
|
|
let lob = state.psw.a & 1;
|
|
let carry_value = Psw::to_u8(state.psw.carry) as u8;
|
|
state.psw.a = state.psw.a >> 1 | (carry_value << 7);
|
|
state.psw.carry = if lob == 1 { true } else { false };
|
|
cycles = 4;
|
|
},
|
|
opcodes::RRC => {
|
|
// low order bit
|
|
let lob = state.psw.a & 1;
|
|
state.psw.carry = lob != 0;
|
|
state.psw.a = (lob << 7) | state.psw.a >> 1;
|
|
cycles = 4;
|
|
},
|
|
opcodes::RLC => {
|
|
// high order bit
|
|
let hob = (state.psw.a & 0x80) >> 7;
|
|
state.psw.carry = hob != 0;
|
|
state.psw.a = (state.psw.a << 1) | hob;
|
|
cycles = 4;
|
|
},
|
|
opcodes::LDAX_B => {
|
|
state.psw.a = self.memory.read_word(state.c, state.b);
|
|
cycles = 7;
|
|
},
|
|
opcodes::LDAX_D => {
|
|
state.psw.a = self.memory.read_word(state.e, state.d);
|
|
cycles = 7;
|
|
},
|
|
opcodes::STAX_B => {
|
|
self.memory.write_word(state.c, state.b, state.psw.a);
|
|
cycles = 7;
|
|
},
|
|
opcodes::STAX_D => {
|
|
self.memory.write_word(state.e, state.d, state.psw.a);
|
|
cycles = 7;
|
|
},
|
|
opcodes::LHLD => {
|
|
state.l = self.memory.read(word);
|
|
state.h = self.memory.read(word + 1);
|
|
cycles = 16;
|
|
},
|
|
opcodes::SHLD => {
|
|
self.memory.write(word, state.l);
|
|
self.memory.write(word + 1, state.h);
|
|
cycles = 16;
|
|
},
|
|
opcodes::LDA => {
|
|
state.psw.a = self.memory.read(word);
|
|
cycles = 13;
|
|
},
|
|
opcodes::STA => {
|
|
self.memory.write(word, state.psw.a);
|
|
cycles = 13;
|
|
},
|
|
opcodes::MVI_A => {
|
|
state.psw.a = byte1;
|
|
cycles = 7;
|
|
},
|
|
opcodes::MVI_B => {
|
|
state.b = byte1;
|
|
cycles = 7;
|
|
},
|
|
opcodes::MVI_C => {
|
|
state.c = byte1;
|
|
cycles = 7;
|
|
},
|
|
opcodes::MVI_D => {
|
|
state.d = byte1;
|
|
cycles = 7;
|
|
},
|
|
opcodes::MVI_E => {
|
|
state.e = byte1;
|
|
cycles = 7;
|
|
},
|
|
opcodes::MVI_H => {
|
|
state.h = byte1;
|
|
cycles = 7;
|
|
},
|
|
opcodes::MVI_L => {
|
|
state.l = byte1;
|
|
cycles = 7;
|
|
},
|
|
opcodes::MVI_M => {
|
|
self.memory.write(state.m(), byte1);
|
|
cycles = 10;
|
|
},
|
|
opcodes::INR_A => {
|
|
state.psw.a = state.inr(state.psw.a);
|
|
cycles = 5;
|
|
},
|
|
opcodes::INR_B => {
|
|
state.b = state.inr(state.b);
|
|
cycles = 5;
|
|
},
|
|
opcodes::INR_C => {
|
|
state.c = state.inr(state.c);
|
|
cycles = 5;
|
|
},
|
|
opcodes::INR_D => {
|
|
state.d = state.inr(state.d);
|
|
cycles = 5;
|
|
},
|
|
opcodes::INR_E => {
|
|
state.e = state.inr(state.e);
|
|
cycles = 5;
|
|
},
|
|
opcodes::INR_H => {
|
|
state.h = state.inr(state.h);
|
|
cycles = 5;
|
|
},
|
|
opcodes::INR_L => {
|
|
state.l = state.inr(state.l);
|
|
cycles = 5;
|
|
},
|
|
opcodes::INR_M => {
|
|
self.memory.write(state.m(), state.inr(self.memory.read(state.m())));
|
|
cycles = 10;
|
|
},
|
|
opcodes::DCR_A => {
|
|
state.psw.a = state.dec(state.psw.a);
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCR_B => {
|
|
state.b = state.dec(state.b);
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCR_C => {
|
|
state.c = state.dec(state.c);
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCR_D => {
|
|
state.d = state.dec(state.d);
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCR_E => {
|
|
state.e = state.dec(state.e);
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCR_H => {
|
|
state.h = state.dec(state.h);
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCR_L => {
|
|
state.l = state.dec(state.l);
|
|
cycles = 5;
|
|
},
|
|
opcodes::DCR_M => {
|
|
self.memory.write(state.m(), state.dec(self.memory.read(state.m())));
|
|
cycles = 10;
|
|
},
|
|
opcodes::MOV_B_A => {
|
|
state.b = state.psw.a;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_B_B => {
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_B_C => {
|
|
state.b = state.c;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_B_D => {
|
|
state.b = state.d;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_B_E => {
|
|
state.b = state.e;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_B_H => {
|
|
state.b = state.h;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_B_L => {
|
|
state.b = state.l;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_A_M => {
|
|
state.psw.a = self.memory.read(state.m());
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_B_M => {
|
|
state.b = self.memory.read(state.m());
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_C_M => {
|
|
state.c = self.memory.read(state.m());
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_D_M => {
|
|
state.d = self.memory.read(state.m());
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_E_M => {
|
|
state.e = self.memory.read(state.m());
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_H_M => {
|
|
state.h = self.memory.read(state.m());
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_L_M => {
|
|
state.l = self.memory.read(state.m());
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_C_A => {
|
|
state.c = state.psw.a;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_C_B => {
|
|
state.c = state.b;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_C_C => {
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_C_D => {
|
|
state.c = state.d;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_C_E => {
|
|
state.c = state.e;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_C_H => {
|
|
state.c = state.h;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_C_L => {
|
|
state.c = state.l;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_D_A => {
|
|
state.d = state.psw.a;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_D_B => {
|
|
state.d = state.b;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_D_C => {
|
|
state.d = state.c;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_D_D => {
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_D_E => {
|
|
state.d = state.e;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_D_H => {
|
|
state.d = state.h;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_D_L => {
|
|
state.d = state.l;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_E_A => {
|
|
// not tested by cpudiag, was bogus
|
|
state.e = state.psw.a;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_E_B => {
|
|
state.e = state.b;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_E_C => {
|
|
state.e = state.c;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_E_D => {
|
|
state.e = state.d;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_E_E => {
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_E_H => {
|
|
state.e = state.h;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_E_L => {
|
|
state.e = state.l;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_H_A => {
|
|
state.h = state.psw.a;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_H_B => {
|
|
state.h = state.b;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_H_C => {
|
|
state.h = state.c;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_H_D => {
|
|
state.h = state.d;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_H_E => {
|
|
state.h = state.e;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_H_H => {
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_H_L => {
|
|
state.h = state.l;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_L_A => {
|
|
state.l = state.psw.a;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_M_B => {
|
|
self.memory.write(state.m(), state.b);
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_M_C => {
|
|
self.memory.write(state.m(), state.c);
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_M_D => {
|
|
self.memory.write(state.m(), state.d);
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_M_E => {
|
|
self.memory.write(state.m(), state.e);
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_M_H => {
|
|
self.memory.write(state.m(), state.h);
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_M_L => {
|
|
self.memory.write(state.m(), state.l);
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_M_A => {
|
|
// self.memory.disassemble_instructions(state.pc, 10);
|
|
self.memory.write(state.m(), state.psw.a);
|
|
cycles = 7;
|
|
},
|
|
opcodes::MOV_L_B => {
|
|
state.l = state.b;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_L_C => {
|
|
state.l = state.c;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_L_D => {
|
|
state.l = state.d;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_L_E => {
|
|
state.l = state.e;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_L_H => {
|
|
state.l = state.h;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_L_L => {
|
|
cycles = 5;
|
|
},
|
|
opcodes::LXI_B => {
|
|
state.c = byte1;
|
|
state.b = byte2;
|
|
cycles = 10;
|
|
},
|
|
opcodes::LXI_D => {
|
|
state.e = byte1;
|
|
state.d = byte2;
|
|
cycles = 10;
|
|
},
|
|
opcodes::LXI_H => {
|
|
state.l = byte1;
|
|
state.h = byte2;
|
|
cycles = 10;
|
|
},
|
|
opcodes::MOV_A_B => {
|
|
state.psw.a = state.b;
|
|
cycles = 5;
|
|
},
|
|
opcodes::MOV_A_C => {
|
|
state.psw.a = state.c;
|
|
cycles = 10;
|
|
},
|
|
opcodes::MOV_A_D => {
|
|
state.psw.a = state.d;
|
|
cycles = 10;
|
|
},
|
|
opcodes::MOV_A_E => {
|
|
state.psw.a = state.e;
|
|
cycles = 10;
|
|
},
|
|
opcodes::MOV_A_H => {
|
|
state.psw.a = state.h;
|
|
cycles = 10;
|
|
},
|
|
opcodes::MOV_A_L => {
|
|
state.psw.a = state.l;
|
|
cycles = 10;
|
|
},
|
|
opcodes::LXI_SP => {
|
|
state.sp = word;
|
|
cycles = 10;
|
|
},
|
|
opcodes::SUI => {
|
|
let value = state.psw.a as i16 - byte1 as i16;
|
|
state.psw.a = value as u8;
|
|
state.set_arithmetic_flags(value);
|
|
cycles = 7;
|
|
},
|
|
opcodes::SBI => {
|
|
let value = state.psw.a as i16 - (byte1 as i16 + state.psw.carry as i16);
|
|
state.psw.a = value as u8;
|
|
state.set_arithmetic_flags(value);
|
|
cycles = 7;
|
|
},
|
|
opcodes::ADD_A => {
|
|
state.add(state.psw.a, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADD_B => {
|
|
state.add(state.b, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADD_C => {
|
|
state.add(state.c, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADD_D => {
|
|
state.add(state.d, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADD_E => {
|
|
state.add(state.e, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADD_H => {
|
|
state.add(state.h, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADD_L => {
|
|
state.add(state.l, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADD_M => {
|
|
state.add(self.memory.read(state.m()), 0);
|
|
cycles = 7;
|
|
}
|
|
opcodes::ADC_A => {
|
|
state.add(state.psw.a, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADC_B => {
|
|
state.add(state.b, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADC_C => {
|
|
state.add(state.c, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADC_D => {
|
|
state.add(state.d, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADC_E => {
|
|
state.add(state.e, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADC_H => {
|
|
state.add(state.h, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADC_L => {
|
|
state.add(state.l, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::ADC_M => {
|
|
state.add(self.memory.read(state.m()), Psw::to_u8(state.psw.carry));
|
|
cycles = 7;
|
|
}
|
|
opcodes::SUB_A => {
|
|
state.sub(state.psw.a, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::SUB_B => {
|
|
state.sub(state.b, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::SUB_C => {
|
|
state.sub(state.c, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::SUB_D => {
|
|
state.sub(state.d, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::SUB_E => {
|
|
state.sub(state.e, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::SUB_H => {
|
|
state.sub(state.h, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::SUB_L => {
|
|
state.sub(state.l, 0);
|
|
cycles = 4;
|
|
}
|
|
opcodes::SUB_M => {
|
|
state.sub(self.memory.read(state.m()), 0);
|
|
cycles = 7;
|
|
}
|
|
opcodes::SBB_A => {
|
|
state.sub(state.psw.a, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::SBB_B => {
|
|
state.sub(state.b, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::SBB_C => {
|
|
state.sub(state.c, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::SBB_D => {
|
|
state.sub(state.d, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::SBB_E => {
|
|
state.sub(state.e, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::SBB_H => {
|
|
state.sub(state.h, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::SBB_L => {
|
|
state.sub(state.l, Psw::to_u8(state.psw.carry));
|
|
cycles = 4;
|
|
}
|
|
opcodes::SBB_M => {
|
|
state.sub(self.memory.read(state.m()), Psw::to_u8(state.psw.carry));
|
|
cycles = 7;
|
|
}
|
|
opcodes::ADI => {
|
|
let value = state.psw.a as i16 + byte1 as i16;
|
|
state.psw.a = value as u8;
|
|
state.set_arithmetic_flags(value);
|
|
cycles = 7;
|
|
},
|
|
opcodes::ACI => {
|
|
let value = state.psw.a as i16 + byte1 as i16 + Psw::to_u8(state.psw.carry) as i16;
|
|
state.psw.a = value as u8;
|
|
state.set_arithmetic_flags(value);
|
|
cycles = 7;
|
|
},
|
|
opcodes::JMP => {
|
|
if word == 0 {
|
|
let output: String = self.output_buffer.clone().into_iter().collect();
|
|
println!("{}", output);
|
|
} else {
|
|
state.pc = word;
|
|
pc_was_assigned = true;
|
|
}
|
|
cycles = 10;
|
|
},
|
|
opcodes::RPO => {
|
|
pc_was_assigned = state.ret(&mut self.memory, ! state.psw.parity);
|
|
cycles = if pc_was_assigned { 11 } else { 5 };
|
|
},
|
|
opcodes::RPE => {
|
|
pc_was_assigned = state.ret(&mut self.memory, state.psw.parity);
|
|
cycles = if pc_was_assigned { 11 } else { 5 };
|
|
},
|
|
opcodes::RNC => {
|
|
pc_was_assigned = state.ret(&mut self.memory, ! state.psw.carry);
|
|
cycles = if pc_was_assigned { 11 } else { 5 };
|
|
},
|
|
opcodes::RC => {
|
|
pc_was_assigned = state.ret(&mut self.memory, state.psw.carry);
|
|
cycles = if pc_was_assigned { 11 } else { 5 };
|
|
},
|
|
opcodes::RP => {
|
|
pc_was_assigned = state.ret(&mut self.memory, ! state.psw.sign);
|
|
cycles = if pc_was_assigned { 11 } else { 5 };
|
|
},
|
|
opcodes::RM => {
|
|
pc_was_assigned = state.ret(&mut self.memory, state.psw.sign);
|
|
cycles = if pc_was_assigned { 11 } else { 5 };
|
|
},
|
|
opcodes::RZ => {
|
|
pc_was_assigned = state.ret(&mut self.memory, state.psw.zero);
|
|
cycles = if pc_was_assigned { 11 } else { 5 };
|
|
},
|
|
opcodes::RNZ => {
|
|
pc_was_assigned = state.ret(&mut self.memory, ! state.psw.zero);
|
|
cycles = if pc_was_assigned { 11 } else { 5 };
|
|
},
|
|
opcodes::RET => {
|
|
pc_was_assigned = state.ret(&mut self.memory, true);
|
|
cycles = 11;
|
|
},
|
|
opcodes::POP_B => {
|
|
state.c = self.memory.read(state.sp);
|
|
state.b = self.memory.read(state.sp + 1);
|
|
state.sp += 2;
|
|
cycles = 10;
|
|
},
|
|
opcodes::POP_D => {
|
|
state.e = self.memory.read(state.sp);
|
|
state.d = self.memory.read(state.sp + 1);
|
|
state.sp += 2;
|
|
cycles = 10;
|
|
},
|
|
opcodes::POP_H => {
|
|
state.l = self.memory.read(state.sp);
|
|
state.h = self.memory.read(state.sp + 1);
|
|
state.sp += 2;
|
|
cycles = 10;
|
|
},
|
|
opcodes::PUSH_B => {
|
|
self.memory.write(state.sp - 1, state.b);
|
|
self.memory.write(state.sp - 2, state.c);
|
|
state.sp -= 2;
|
|
cycles = 11;
|
|
},
|
|
opcodes::PUSH_D => {
|
|
self.memory.write(state.sp - 1, state.d);
|
|
self.memory.write(state.sp - 2, state.e);
|
|
state.sp -= 2;
|
|
cycles = 11;
|
|
},
|
|
opcodes::PUSH_H => {
|
|
self.memory.write(state.sp - 1, state.h);
|
|
self.memory.write(state.sp - 2, state.l);
|
|
state.sp -= 2;
|
|
cycles = 11;
|
|
},
|
|
opcodes::CC => {
|
|
if state.psw.carry {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
cycles = if pc_was_assigned { 11 } else { 17 };
|
|
},
|
|
opcodes::CPO => {
|
|
if ! state.psw.parity {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
cycles = if pc_was_assigned { 11 } else { 17 };
|
|
},
|
|
opcodes::CPE => {
|
|
if state.psw.parity {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
cycles = if pc_was_assigned { 11 } else { 17 };
|
|
},
|
|
opcodes::CM => {
|
|
if state.psw.sign {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
cycles = if pc_was_assigned { 11 } else { 17 };
|
|
},
|
|
opcodes::CP => {
|
|
if ! state.psw.sign {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
cycles = if pc_was_assigned { 11 } else { 17 };
|
|
},
|
|
opcodes::CNZ => {
|
|
if ! state.psw.zero {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
cycles = if pc_was_assigned { 11 } else { 17 };
|
|
},
|
|
opcodes::CZ => {
|
|
if state.psw.zero {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
cycles = if pc_was_assigned { 11 } else { 17 };
|
|
},
|
|
opcodes::CNC => {
|
|
if ! state.psw.carry {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
cycles = if pc_was_assigned { 11 } else { 17 };
|
|
},
|
|
opcodes::STC => {
|
|
state.psw.carry = true;
|
|
cycles = 4;
|
|
},
|
|
opcodes::CMC => {
|
|
state.psw.carry = ! state.psw.carry;
|
|
cycles = 4;
|
|
},
|
|
opcodes::CMA => {
|
|
state.psw.a ^= 0xff;
|
|
cycles = 4;
|
|
},
|
|
opcodes::DAA => {
|
|
let mut a: u16 = state.psw.a as u16;
|
|
// least significant bits
|
|
let lsb = a & 0x0f;
|
|
if lsb > 9 || state.psw.auxiliary_carry {
|
|
a += 6;
|
|
state.psw.auxiliary_carry = (lsb + 6) > 0xf;
|
|
};
|
|
// most significant bits
|
|
let mut msb = (a & 0xf0) >> 4;
|
|
if (msb > 9) || state.psw.carry { msb += 6; }
|
|
a = (msb << 4) | (a & 0xf);
|
|
state.psw.auxiliary_carry = (msb + 6) > 0xf;
|
|
state.set_arithmetic_flags(a as i16);
|
|
state.psw.a = a as u8;
|
|
|
|
cycles = 4;
|
|
},
|
|
opcodes::CALL => {
|
|
cycles = 17;
|
|
if cfg!(test) {
|
|
// In test mode, this function returns 1 for successful test, 2 for failure
|
|
if word == 5 && state.c == 9 {
|
|
// print message at address word(D, E)
|
|
let ind = Memory::to_word(state.e, state.d);
|
|
if ind == 0x174 {
|
|
test_status.status = StepStatus::Success("Success".into());
|
|
} else if ind == 0x18b {
|
|
let sp = state.sp + 4;
|
|
let pc = Memory::to_word(self.memory.read(sp), self.memory.read(sp+1))
|
|
- 6;
|
|
for i in 0..20 {
|
|
println!("sp + {}: {:02x}", i, self.memory.read(sp + i));
|
|
}
|
|
let s = format!("Failure at pc {:04x}", pc);
|
|
test_status.status = StepStatus::Failure(s);
|
|
} else {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
} else {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
} else {
|
|
state.call(&mut self.memory, word);
|
|
pc_was_assigned = true;
|
|
}
|
|
},
|
|
opcodes::ANI => {
|
|
let value = state.psw.a & byte1;
|
|
state.psw.a = value;
|
|
state.set_logic_flags(value.into());
|
|
cycles = 7;
|
|
},
|
|
opcodes::ORI => {
|
|
let value = state.psw.a | byte1;
|
|
state.psw.a = value;
|
|
state.set_logic_flags(value.into());
|
|
cycles = 7;
|
|
},
|
|
opcodes::XRI => {
|
|
state.psw.a = state.xra(byte1);
|
|
cycles = 7;
|
|
},
|
|
opcodes::XRA_A => {
|
|
state.psw.a = state.xra(state.psw.a);
|
|
cycles = 4;
|
|
},
|
|
opcodes::XRA_B => {
|
|
state.psw.a = state.xra(state.b);
|
|
cycles = 4;
|
|
},
|
|
opcodes::XRA_C => {
|
|
state.psw.a = state.xra(state.c);
|
|
cycles = 4;
|
|
},
|
|
opcodes::XRA_D => {
|
|
state.psw.a = state.xra(state.d);
|
|
cycles = 4;
|
|
},
|
|
opcodes::XRA_E => {
|
|
state.psw.a = state.xra(state.e);
|
|
cycles = 4;
|
|
},
|
|
opcodes::XRA_H => {
|
|
state.psw.a = state.xra(state.h);
|
|
cycles = 4;
|
|
},
|
|
opcodes::XRA_L => {
|
|
state.psw.a = state.xra(state.l);
|
|
cycles = 4;
|
|
},
|
|
opcodes::XRA_M => {
|
|
state.psw.a = state.xra(self.memory.read(state.m()));
|
|
cycles = 7;
|
|
},
|
|
opcodes::ANA_A => {
|
|
state.psw.a = state.and(state.psw.a);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ANA_B => {
|
|
state.psw.a = state.and(state.b);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ANA_C => {
|
|
state.psw.a = state.and(state.c);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ANA_D => {
|
|
state.psw.a = state.and(state.d);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ANA_E => {
|
|
state.psw.a = state.and(state.e);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ANA_H => {
|
|
state.psw.a = state.and(state.h);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ANA_L => {
|
|
state.psw.a = state.and(state.l);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ANA_M => {
|
|
state.psw.a = state.and(self.memory.read(state.m()));
|
|
cycles = 7;
|
|
},
|
|
opcodes::ORA_A => {
|
|
state.psw.a = state.or(state.psw.a);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ORA_B => {
|
|
state.psw.a = state.or(state.b);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ORA_C => {
|
|
state.psw.a = state.or(state.c);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ORA_D => {
|
|
state.psw.a = state.or(state.d);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ORA_E => {
|
|
state.psw.a = state.or(state.e);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ORA_H => {
|
|
state.psw.a = state.or(state.h);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ORA_L => {
|
|
state.psw.a = state.or(state.l);
|
|
cycles = 4;
|
|
},
|
|
opcodes::ORA_M => {
|
|
state.psw.a = state.or(self.memory.read(state.m()));
|
|
cycles = 7;
|
|
},
|
|
opcodes::XTHL => {
|
|
let l = self.memory.read(state.sp);
|
|
self.memory.write(state.sp, state.l);
|
|
state.l = l;
|
|
let h = self.memory.read(state.sp + 1);
|
|
self.memory.write(state.sp + 1, state.h);
|
|
state.h = h;
|
|
cycles = 18;
|
|
},
|
|
opcodes::JPO => {
|
|
pc_was_assigned = state.jump_if_flag(word, ! state.psw.parity);
|
|
cycles = 10;
|
|
},
|
|
opcodes::JPE => {
|
|
pc_was_assigned = state.jump_if_flag(word, state.psw.parity);
|
|
cycles = 10;
|
|
},
|
|
opcodes::JNZ => {
|
|
pc_was_assigned = state.jump_if_flag(word, ! state.psw.zero);
|
|
cycles = 10;
|
|
},
|
|
opcodes::JZ => {
|
|
pc_was_assigned = state.jump_if_flag(word, state.psw.zero);
|
|
cycles = 10;
|
|
},
|
|
opcodes::JP => {
|
|
pc_was_assigned = state.jump_if_flag(word, ! state.psw.sign);
|
|
cycles = 10;
|
|
},
|
|
opcodes::JM => {
|
|
pc_was_assigned = state.jump_if_flag(word, state.psw.sign);
|
|
cycles = 10;
|
|
},
|
|
opcodes::JC => {
|
|
pc_was_assigned = state.jump_if_flag(word, state.psw.carry);
|
|
cycles = 10;
|
|
},
|
|
opcodes::JNC => {
|
|
pc_was_assigned = state.jump_if_flag(word, ! state.psw.carry);
|
|
cycles = 10;
|
|
},
|
|
opcodes::XCHG => {
|
|
let h = state.h;
|
|
state.h = state.d;
|
|
state.d = h;
|
|
let l = state.l;
|
|
state.l = state.e;
|
|
state.e = l;
|
|
cycles = 4;
|
|
},
|
|
opcodes::PUSH_PSW => {
|
|
self.memory.write(state.sp - 1, state.psw.a);
|
|
self.memory.write(state.sp - 2, (state.psw.value() & 0xff) as u8);
|
|
state.sp -= 2;
|
|
cycles = 11;
|
|
},
|
|
opcodes::POP_PSW => {
|
|
state.psw.a = self.memory.read(state.sp + 1);
|
|
state.psw.set_flags(self.memory.read(state.sp));
|
|
state.sp += 2;
|
|
cycles = 10;
|
|
},
|
|
opcodes::CPI => {
|
|
state.cmp(byte1);
|
|
cycles = 7;
|
|
},
|
|
opcodes::CMP_B => {
|
|
state.cmp(state.b);
|
|
cycles = 4; // not sure, couldn't find it in the reference
|
|
},
|
|
opcodes::CMP_C => {
|
|
state.cmp(state.c);
|
|
cycles = 4; // not sure, couldn't find it in the reference
|
|
},
|
|
opcodes::CMP_D => {
|
|
state.cmp(state.d);
|
|
cycles = 4; // not sure, couldn't find it in the reference
|
|
},
|
|
opcodes::CMP_E => {
|
|
state.cmp(state.e);
|
|
cycles = 4; // not sure, couldn't find it in the reference
|
|
},
|
|
opcodes::CMP_H => {
|
|
state.cmp(state.h);
|
|
cycles = 4; // not sure, couldn't find it in the reference
|
|
},
|
|
opcodes::CMP_L => {
|
|
state.cmp(state.l);
|
|
cycles = 4; // not sure, couldn't find it in the reference
|
|
},
|
|
opcodes::CMP_M => {
|
|
state.cmp(self.memory.read(state.m()));
|
|
cycles = 7;
|
|
},
|
|
opcodes::CMP_A => {
|
|
state.cmp(state.psw.a);
|
|
cycles = 4; // not sure, couldn't find it in the reference
|
|
},
|
|
opcodes::SPHL => {
|
|
state.sp = ((state.h as u16) << 8) as usize | state.l as usize;
|
|
cycles = 5;
|
|
},
|
|
opcodes::PCHL => {
|
|
state.pc = ((state.h as u16) << 8) as usize | state.l as usize;
|
|
pc_was_assigned = true;
|
|
cycles = 5;
|
|
},
|
|
opcodes::EI => {
|
|
state.enable_interrupts = true;
|
|
cycles = 4;
|
|
}
|
|
opcodes::DI => {
|
|
state.enable_interrupts = false;
|
|
cycles = 4;
|
|
}
|
|
opcodes::OUT => {
|
|
match byte1 {
|
|
2 => {
|
|
self.shift_register_offset = state.psw.a & 0x7;
|
|
},
|
|
3 => {
|
|
// sound
|
|
},
|
|
4 => {
|
|
self.shift_register = ((state.psw.a as u16) << 8)
|
|
| (self.shift_register >> 8)
|
|
},
|
|
5 => {
|
|
// sound
|
|
},
|
|
6 => {
|
|
// watch dog
|
|
}
|
|
_ => {
|
|
println!("Unsupported OUT port: {}", byte1);
|
|
}
|
|
}
|
|
cycles = 10;
|
|
}
|
|
opcodes::IN => {
|
|
match byte1 {
|
|
1 => {
|
|
state.psw.a = SHARED_STATE.get().unwrap().lock().unwrap().get_in_1();
|
|
},
|
|
2 => {
|
|
state.psw.a = SHARED_STATE.get().unwrap().lock().unwrap().get_in_2();
|
|
},
|
|
3 => {
|
|
let shift_amount = 8 - self.shift_register_offset;
|
|
state.psw.a = (self.shift_register >> shift_amount) as u8;
|
|
},
|
|
_ => {
|
|
panic!("Unsupported IN port: {}", byte1);
|
|
}
|
|
}
|
|
cycles = 10;
|
|
}
|
|
opcodes::RST_1 => {
|
|
state.call(&mut self.memory, 1 * 8);
|
|
pc_was_assigned = true;
|
|
cycles = 10;
|
|
}
|
|
opcodes::RST_2 => {
|
|
state.call(&mut self.memory, 2 * 8);
|
|
pc_was_assigned = true;
|
|
cycles = 10;
|
|
}
|
|
opcodes::RST_7 => {
|
|
state.call(&mut self.memory, 7 * 8);
|
|
pc_was_assigned = true;
|
|
cycles = 10;
|
|
}
|
|
_ => panic!("Don't know how to run opcode: {:02x} at {:04x}", op, state.pc),
|
|
}
|
|
|
|
if ! pc_was_assigned {
|
|
state.pc += opcode.size;
|
|
}
|
|
|
|
if cycles == 0 {
|
|
panic!("Cycles not assigned");
|
|
}
|
|
|
|
if test_status.status != StepStatus::Continue {
|
|
test_status
|
|
} else {
|
|
StepResult { status: StepStatus::Continue, cycles }
|
|
}
|
|
}
|
|
|
|
fn interrupt(&mut self, interrupt_number: u8) {
|
|
if self.state.as_ref().unwrap().enable_interrupts {
|
|
// log_time(format!("Interrupt {}", interrupt_number).as_str());
|
|
let state = self.state.as_mut().unwrap();
|
|
self.memory.write(state.sp - 1, ((state.pc as u16 & 0xff00) >> 8) as u8);
|
|
self.memory.write(state.sp - 2, (state.pc as u16 & 0xff) as u8);
|
|
state.sp -= 2;
|
|
// Interrupt 0 goes to $0, 1 to $08, 2 to $10, etc...
|
|
state.pc = (interrupt_number as usize) << 3;
|
|
}
|
|
}
|
|
}
|