#![cfg(feature = "serde")]
use hg80::{BusRequest, Cpu, Host};
struct Machine {
bytes: Vec<u8>,
trace: Vec<(u8, u16)>,
}
impl Machine {
fn with(program: &[u8]) -> Self {
let mut bytes = vec![0u8; 0x10000];
bytes[..program.len()].copy_from_slice(program);
bytes[0x40] = 0x5A;
bytes[0x41] = 0xC3;
Self {
bytes,
trace: Vec::new(),
}
}
}
impl Host for Machine {
fn read(&mut self, address: u16, _at: u32) -> u8 {
self.bytes[address as usize]
}
fn write(&mut self, address: u16, value: u8, _at: u32) {
self.bytes[address as usize] = value;
}
fn input(&mut self, _port: u16, _at: u32) -> u8 {
0x7E
}
fn output(&mut self, _port: u16, _value: u8, _at: u32) {}
fn bus_edge(&mut self, request: &BusRequest) {
let entry = match *request {
BusRequest::OpcodeFetch { address } => (1, address),
BusRequest::Refresh { address } => (2, address),
BusRequest::MemoryRead { address } => (3, address),
BusRequest::MemoryWrite { address, value } => (4, address ^ u16::from(value)),
BusRequest::PortRead { port } => (5, port),
BusRequest::PortWrite { port, .. } => (6, port),
BusRequest::Internal { address } => (7, address),
BusRequest::InterruptAcknowledge { address } => (8, address),
_ => (9, 0),
};
self.trace.push(entry);
}
}
const PROGRAM: &[u8] = &[
0x21, 0x40, 0x00, 0x11, 0x80, 0x00, 0x01, 0x02, 0x00, 0xED, 0xB0, 0xDD, 0x21, 0x50, 0x00, 0xDD, 0xCB, 0x02, 0x46, 0x3E, 0x99, 0xCD, 0x20, 0x00, ];
struct Flat {
bytes: Vec<u8>,
}
impl Flat {
fn with(program: &[u8]) -> Self {
let mut bytes = vec![0u8; 0x10000];
bytes[..program.len()].copy_from_slice(program);
Self { bytes }
}
}
impl Host for Flat {
fn read(&mut self, address: u16, _at: u32) -> u8 {
self.bytes[address as usize]
}
fn write(&mut self, address: u16, value: u8, _at: u32) {
self.bytes[address as usize] = value;
}
fn input(&mut self, _port: u16, _at: u32) -> u8 {
0xFF
}
fn output(&mut self, _port: u16, _value: u8, _at: u32) {}
}
fn run(cpu: &mut Cpu, machine: &mut Machine, edges: usize) {
for _ in 0..edges {
cpu.tick(machine);
}
}
fn fresh() -> (Cpu, Machine) {
let mut cpu = Cpu::new();
cpu.reset();
cpu.set_z80n_enabled(true);
cpu.registers_mut().sp = 0x0200;
(cpu, Machine::with(PROGRAM))
}
#[test]
fn a_core_paused_between_any_two_edges_resumes_the_same_after_a_round_trip() {
const EDGES: usize = 260;
for pause in 0..EDGES {
let (mut cpu, mut machine) = fresh();
run(&mut cpu, &mut machine, pause);
let carried = serde_json::to_string(&cpu).expect("the core serialises");
let mut restored: Cpu = serde_json::from_str(&carried).expect("the core deserialises");
assert_eq!(cpu, restored, "paused at edge {pause}");
let mark = machine.trace.len();
let mut continued = Machine {
bytes: machine.bytes.clone(),
trace: Vec::new(),
};
run(&mut cpu, &mut machine, EDGES - pause);
run(&mut restored, &mut continued, EDGES - pause);
assert_eq!(
machine.trace[mark..],
continued.trace[..],
"the trace after edge {pause} differs"
);
assert_eq!(cpu, restored, "the cores diverged after edge {pause}");
assert_eq!(machine.bytes, continued.bytes, "memory differs at {pause}");
}
}
#[test]
fn a_core_paused_mid_instruction_is_not_merely_its_register_set() {
let (mut cpu, mut machine) = fresh();
run(&mut cpu, &mut machine, 79);
let mut rebuilt = Cpu::new();
rebuilt.reset();
rebuilt.set_z80n_enabled(true);
*rebuilt.registers_mut() = *cpu.registers();
assert_eq!(cpu.registers(), rebuilt.registers());
assert_ne!(
cpu, rebuilt,
"a core carrying only its registers should not compare equal part-way through an \
instruction"
);
}
const REACHES_THE_LATCHES: &[u8] = &{
let mut bytes = [0u8; 0x40];
bytes[0] = 0xED;
bytes[1] = 0x91;
bytes[2] = 0x07;
bytes[3] = 0x02;
bytes[4] = 0xFB;
bytes[6] = 0x76;
bytes[0x38] = 0xC9;
bytes
};
#[test]
fn a_core_paused_between_any_two_instructions_resumes_the_same_after_a_round_trip() {
for program in [PROGRAM, REACHES_THE_LATCHES] {
paused_at_every_instruction(program);
}
}
fn paused_at_every_instruction(program: &[u8]) {
const STEPS: usize = 60;
for pause in 0..STEPS {
let mut cpu = Cpu::new();
cpu.reset();
cpu.set_z80n_enabled(true);
cpu.registers_mut().sp = 0x0200;
cpu.set_interrupt_requested(true);
let mut machine = Machine::with(program);
for _ in 0..pause {
cpu.step(&mut machine);
}
let carried = serde_json::to_string(&cpu).expect("the core serialises");
let mut restored: Cpu = serde_json::from_str(&carried).expect("the core deserialises");
assert_eq!(cpu, restored, "paused after step {pause}");
let mark = machine.trace.len();
let mut continued = Machine {
bytes: machine.bytes.clone(),
trace: Vec::new(),
};
for _ in pause..STEPS {
cpu.step(&mut machine);
restored.step(&mut continued);
}
assert_eq!(
machine.trace[mark..],
continued.trace[..],
"the trace after step {pause} differs"
);
assert_eq!(cpu, restored, "the cores diverged after step {pause}");
assert_eq!(machine.bytes, continued.bytes, "memory differs at {pause}");
}
}
const PART_WAY: [u8; 6] = [0x01, 0x34, 0x12, 0x3C, 0x3C, 0x3C];
#[cfg(debug_assertions)]
#[test]
#[should_panic = "the sequencer is holding an instruction"]
fn stepping_after_driving_by_edge_is_caught() {
let mut host = Flat::with(&PART_WAY);
let mut cpu = Cpu::new();
cpu.reset();
cpu.tick(&mut host);
cpu.tick(&mut host);
cpu.step(&mut host);
}
#[test]
fn abandoning_the_instruction_is_what_makes_the_two_safe_to_mix() {
let mut host = Flat::with(&PART_WAY);
let mut cpu = Cpu::new();
cpu.reset();
cpu.tick(&mut host);
cpu.tick(&mut host);
cpu.abandon_instruction();
for _ in 0..3 {
cpu.step(&mut host);
}
let registers = cpu.registers();
assert_eq!(registers.bc, 0x1234, "the pair was not loaded");
assert_eq!(registers.pc, 0x0005, "three instructions did not run");
}
#[test]
fn stepping_and_then_driving_by_edge_needs_no_abandon() {
let mut host = Flat::with(&PART_WAY);
let mut cpu = Cpu::new();
cpu.reset();
cpu.step(&mut host);
for _ in 0..8 {
cpu.tick(&mut host);
}
assert_eq!(cpu.registers().bc, 0x1234, "the step did not take effect");
cpu.abandon_instruction();
cpu.step(&mut host);
}