use super::*;
use crate::mcode::InstructionSet;
use crate::{BusCycle, BusRequest, Cpu, Host};
use std::prelude::rust_2024::*;
struct Watch {
bytes: Box<[u8; 0x10000]>,
cycles: Vec<(CycleKind, u32)>,
opening: Option<CycleKind>,
}
fn kind_of(request: BusRequest) -> CycleKind {
match request {
BusRequest::OpcodeFetch { .. } | BusRequest::InterruptAcknowledge { .. } => {
CycleKind::Fetch
}
BusRequest::MemoryRead { .. } => CycleKind::MemoryRead,
BusRequest::MemoryWrite { .. } => CycleKind::MemoryWrite,
BusRequest::PortRead { .. } => CycleKind::PortRead,
BusRequest::PortWrite { .. } => CycleKind::PortWrite,
_ => CycleKind::Internal,
}
}
impl Host for Watch {
fn read(&mut self, address: u16, _: u32) -> u8 {
self.bytes[address as usize]
}
fn write(&mut self, address: u16, value: u8, _: u32) {
self.bytes[address as usize] = value;
}
fn input(&mut self, _: u16, _: u32) -> u8 {
0xFF
}
fn output(&mut self, _: u16, _: u8, _: u32) {}
fn wait_states(&mut self, request: &BusRequest) -> u32 {
self.opening = Some(kind_of(*request));
0
}
fn bus_cycle(&mut self, cycle: &BusCycle) {
self.cycles.push((kind_of(cycle.request), cycle.t_states));
}
}
fn observed(prefix: &[u8], ir: u8, z80n: bool) -> Vec<(CycleKind, u32)> {
let mut program = prefix.to_vec();
program.push(ir);
program.extend_from_slice(&[0x00; 8]);
let mut bytes = vec![0u8; 0x10000].into_boxed_slice();
bytes[0x8000..0x8000 + program.len()].copy_from_slice(&program);
let mut host = Watch {
bytes: bytes.try_into().unwrap(),
cycles: Vec::new(),
opening: None,
};
let mut cpu = Cpu::new();
cpu.reset();
cpu.set_z80n_enabled(z80n);
cpu.registers_mut().sp = 0x4080;
cpu.registers_mut().hl = 0x4000;
cpu.registers_mut().de = 0x4010;
cpu.registers_mut().bc = 0x4020;
cpu.registers_mut().ix = 0x4000;
cpu.registers_mut().iy = 0x4010;
cpu.registers_mut().pc = 0x8000;
cpu.abandon_instruction();
cpu.step_by_edges(&mut host);
cpu.step_by_edges(&mut host);
host.cycles
}
fn compare(set: InstructionSet, prefix: &[u8], ir: u8, z80n: bool) -> Option<String> {
let planned = plan(set, ir, z80n);
if planned.varies {
return None;
}
let seen = observed(prefix, ir, z80n);
let want: Vec<(CycleKind, u32)> = planned
.cycles()
.iter()
.map(|c| (c.kind, u32::from(c.t_states)))
.collect();
let from = prefix.len();
if seen.len() < from + want.len() {
return Some(format!("saw {seen:?}, planned {want:?}"));
}
let tail = &seen[from..from + want.len()];
if tail == want.as_slice() {
None
} else {
Some(format!("saw {tail:?}, planned {want:?}"))
}
}
#[test]
fn a_derived_plan_matches_what_the_core_does() {
let mut wrong = Vec::new();
let mut checked = 0;
for (set, prefix) in [
(InstructionSet::Base, &[][..]),
(InstructionSet::Cb, &[0xCB][..]),
(InstructionSet::Ed, &[0xED][..]),
] {
for ir in 0u8..=0xFF {
if matches!(set, InstructionSet::Base) && matches!(ir, 0xCB | 0xED | 0xDD | 0xFD) {
continue;
}
if let Some(why) = compare(set, prefix, ir, false) {
wrong.push(format!("{set:?} {ir:02X}: {why}"));
} else {
checked += 1;
}
}
}
assert!(
checked > 400,
"only {checked} instructions had a fixed shape"
);
assert!(
wrong.is_empty(),
"{} of {} disagree:\n{}",
wrong.len(),
wrong.len() + checked,
wrong.join("\n")
);
}
#[test]
fn most_instructions_have_a_single_shape() {
let mut varying = Vec::new();
let mut total = 0;
for (set, z80n) in [
(InstructionSet::Base, false),
(InstructionSet::Cb, false),
(InstructionSet::Ed, false),
(InstructionSet::Ed, true),
] {
for ir in 0u8..=0xFF {
total += 1;
if plan(set, ir, z80n).varies {
varying.push(format!("{set:?}{} {ir:02X}", if z80n { "+n" } else { "" }));
}
}
}
assert!(
varying.len() * 8 < total,
"{} of {total} instructions vary, which is too many to be a small set:\n{}",
varying.len(),
varying.join(" ")
);
std::println!(
"{} of {total} instructions vary: {}",
varying.len(),
varying.join(" ")
);
}