main.rsannotatedmain.rssource272 lines · 7.2 KB · raw
1use clap::Parser;
2use env_logger::Env;
3use serde::Deserialize;
4use spc700_emu::traits::BusInterface;
5use spc700_emu::{Registers, Spc700};
6use std::error::Error;
7use std::ffi::OsStr;
8use std::fmt::{Display, Formatter};
9use std::fs::File;
10use std::io::BufReader;
11use std::path::Path;
12use std::{fs, process};
13
14const RAM_LEN: usize = 1 << 16;
15
16const SLEEP_OPCODE: u8 = 0xEF;
17const STOP_OPCODE: u8 = 0xFF;
18const STOP_TEST_CYCLES: usize = 7;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21enum BusOp {
22    Read(u16, u8),
23    Write(u16, u8),
24    Idle,
25}
26
27impl Display for BusOp {
28    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Self::Read(address, value) => write!(f, "Read({address:04X}, {value:02X})"),
31            Self::Write(address, value) => write!(f, "Write({address:04X}, {value:02X})"),
32            Self::Idle => write!(f, "Idle"),
33        }
34    }
35}
36
37#[derive(Debug, Clone)]
38struct RecordingBus {
39    ram: Box<[u8; RAM_LEN]>,
40    ops: Vec<BusOp>,
41}
42
43impl RecordingBus {
44    fn new() -> Self {
45        Self { ram: vec![0; RAM_LEN].into_boxed_slice().try_into().unwrap(), ops: Vec::new() }
46    }
47
48    fn clear(&mut self) {
49        self.ops.clear();
50    }
51}
52
53impl BusInterface for RecordingBus {
54    fn read(&mut self, address: u16) -> u8 {
55        let value = self.ram[address as usize];
56        self.ops.push(BusOp::Read(address, value));
57        value
58    }
59
60    fn write(&mut self, address: u16, value: u8) {
61        self.ops.push(BusOp::Write(address, value));
62        self.ram[address as usize] = value;
63    }
64
65    fn idle(&mut self) {
66        self.ops.push(BusOp::Idle);
67    }
68}
69
70#[derive(Debug, Clone, Deserialize)]
71struct State {
72    pc: u16,
73    a: u8,
74    x: u8,
75    y: u8,
76    sp: u8,
77    psw: u8,
78    ram: Vec<(u16, u8)>,
79}
80
81#[derive(Debug, Clone, Deserialize)]
82struct Cycle(Option<u16>, Option<u8>, String);
83
84impl Cycle {
85    fn to_bus_op(&self) -> BusOp {
86        match (self.0, self.1, self.2.as_str()) {
87            (None, _, _) | (_, None, _) | (_, _, "wait") => BusOp::Idle,
88            (Some(address), Some(value), "read") => BusOp::Read(address, value),
89            (Some(address), Some(value), "write") => BusOp::Write(address, value),
90            _ => panic!("unexpected cycle descriptor string: {}", self.2),
91        }
92    }
93}
94
95#[derive(Debug, Clone, Deserialize)]
96struct TestDescription {
97    name: String,
98    initial: State,
99    #[serde(rename = "final")]
100    final_: State,
101    cycles: Vec<Cycle>,
102}
103
104#[derive(Debug, Clone, Parser)]
105struct Args {
106    #[arg(short = 'f', long)]
107    file_path: Option<String>,
108    #[arg(short = 'd', long)]
109    directory_path: Option<String>,
110}
111
112fn main() -> Result<(), Box<dyn Error>> {
113    env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
114
115    let args = Args::parse();
116
117    match (args.file_path, args.directory_path) {
118        (Some(file_path), None) => {
119            run_test(&file_path)?;
120        }
121        (None, Some(directory_path)) => {
122            run_directory(&directory_path)?;
123        }
124        _ => {
125            eprintln!(
126                "ERROR: Exactly one of -f and -d must be set; use -h to see full help output"
127            );
128            process::exit(1);
129        }
130    }
131
132    Ok(())
133}
134
135fn run_directory(directory_path: &str) -> Result<(), Box<dyn Error>> {
136    let mut file_paths: Vec<_> = fs::read_dir(directory_path)?
137        .filter_map(Result::ok)
138        .filter_map(|dir_entry| {
139            let path = dir_entry.path();
140            (path.extension().and_then(OsStr::to_str) == Some("json")).then_some(path)
141        })
142        .collect();
143
144    file_paths.sort();
145
146    for file_path in file_paths {
147        run_test(&file_path)?;
148    }
149
150    Ok(())
151}
152
153fn run_test<P: AsRef<Path>>(file_path: P) -> Result<(), Box<dyn Error>> {
154    let file_path = file_path.as_ref();
155
156    let file = File::open(file_path)?;
157    let test_descriptions: Vec<TestDescription> = serde_json::from_reader(BufReader::new(file))?;
158    let num_tests = test_descriptions.len();
159
160    let mut bus = RecordingBus::new();
161
162    let mut failures = 0;
163    for test_description in test_descriptions {
164        let mut cpu = Spc700::new();
165
166        init_test(&mut cpu, &mut bus, &test_description.initial);
167
168        let opcode = bus.ram[test_description.initial.pc as usize];
169        if opcode != SLEEP_OPCODE && opcode != STOP_OPCODE {
170            // Run CPU for a full instruction
171            cpu.tick(&mut bus);
172            while cpu.is_mid_instruction() {
173                cpu.tick(&mut bus);
174            }
175        } else {
176            // SLEEP/STOP: Run CPU for a fixed number of cycles (7); the CPU should remain halted
177            // after executing the instruction
178            for _ in 0..STOP_TEST_CYCLES {
179                cpu.tick(&mut bus);
180            }
181        }
182
183        let errors = check_test(&cpu, &bus, &test_description.final_, &test_description.cycles);
184        if !errors.is_empty() {
185            failures += 1;
186
187            log::error!("Failed test '{}':", test_description.name);
188            for error in errors {
189                log::error!("  {error}");
190            }
191        }
192
193        bus.clear();
194    }
195
196    if failures != 0 {
197        log::info!("Failed {failures} out of {num_tests} in '{}'", file_path.display());
198    }
199
200    Ok(())
201}
202
203macro_rules! check_registers {
204    ($([$name:literal: $actual:expr, $expected:expr]),* $(,)?) => {
205        {
206            let mut errors: Vec<String> = Vec::new();
207
208            $(
209                let actual = $actual;
210                let expected = $expected;
211                if actual != expected {
212                    errors.push(format!("{}: actual={actual:04X}, expected={expected:04X}", $name));
213                }
214            )*
215
216            errors
217        }
218    }
219}
220
221fn init_test(cpu: &mut Spc700, bus: &mut RecordingBus, state: &State) {
222    cpu.set_registers(Registers {
223        a: state.a,
224        x: state.x,
225        y: state.y,
226        sp: state.sp,
227        pc: state.pc,
228        psw: state.psw.into(),
229    });
230
231    for &(address, value) in &state.ram {
232        bus.ram[address as usize] = value;
233    }
234}
235
236fn check_test(cpu: &Spc700, bus: &RecordingBus, state: &State, cycles: &[Cycle]) -> Vec<String> {
237    let registers = cpu.registers();
238    let mut errors = check_registers!(
239        ["A": registers.a, state.a],
240        ["X": registers.x, state.x],
241        ["Y": registers.y, state.y],
242        ["SP": registers.sp, state.sp],
243        ["PC": registers.pc, state.pc],
244        ["PSW": u8::from(registers.psw), state.psw],
245    );
246
247    for &(address, expected_value) in &state.ram {
248        let actual_value = bus.ram[address as usize];
249        if actual_value != expected_value {
250            errors.push(format!(
251                "RAM[{address:04X}]: actual={actual_value:02X}, expected={expected_value:02X}"
252            ));
253        }
254    }
255
256    let expected_ops: Vec<_> = cycles.iter().map(Cycle::to_bus_op).collect();
257    if bus.ops.len() != expected_ops.len() {
258        errors.push(format!(
259            "Cycle count: actual={}, expected={}",
260            bus.ops.len(),
261            expected_ops.len()
262        ));
263    }
264
265    for (i, (actual_op, expected_op)) in bus.ops.iter().zip(&expected_ops).enumerate() {
266        if actual_op != expected_op {
267            errors.push(format!("Cycle {i}: actual={actual_op}, expected={expected_op}"));
268        }
269    }
270
271    errors
272}