main.rsannotatedmain.rssource246 lines · 6.5 KB · raw
1use clap::Parser;
2use huc6280_emu::bus::{BusInterface, ClockSpeed, InterruptLines};
3use huc6280_emu::{Flags, Huc6280, Registers};
4use rayon::prelude::*;
5use rustc_hash::FxHashMap;
6use serde::Deserialize;
7use std::error::Error;
8use std::ffi::OsStr;
9use std::fs;
10use std::fs::File;
11use std::io::BufReader;
12use std::path::{Path, PathBuf};
13
14type DynError = dyn Error + Send + Sync + 'static;
15
16#[derive(Debug, Clone, Deserialize)]
17struct TestState {
18    #[serde(rename = "A")]
19    a: u8,
20    #[serde(rename = "X")]
21    x: u8,
22    #[serde(rename = "Y")]
23    y: u8,
24    #[serde(rename = "S")]
25    s: u8,
26    #[serde(rename = "P")]
27    p: u8,
28    #[serde(rename = "PC")]
29    pc: u16,
30    #[serde(rename = "MPR")]
31    mpr: [u8; 8],
32    #[serde(rename = "RAM")]
33    ram: Vec<(u32, u8)>,
34}
35
36impl From<&TestState> for Registers {
37    fn from(value: &TestState) -> Self {
38        Self {
39            a: value.a,
40            x: value.x,
41            y: value.y,
42            pc: value.pc,
43            s: value.s,
44            p: value.p.into(),
45            mpr: value.mpr,
46        }
47    }
48}
49
50#[derive(Debug, Clone, Deserialize)]
51struct TestDefinition {
52    name: String,
53    opcode: u8,
54    initial: TestState,
55    #[serde(rename = "final")]
56    final_: TestState,
57    num_cycles: u32,
58}
59
60struct TestBus {
61    ram: FxHashMap<u32, u8>,
62    cycles: u32,
63}
64
65impl TestBus {
66    fn new(initial_state: &TestState) -> Self {
67        Self { ram: initial_state.ram.iter().copied().collect(), cycles: 0 }
68    }
69
70    fn read_ram(&self, address: u32) -> u8 {
71        self.ram.get(&address).copied().unwrap_or(0)
72    }
73}
74
75impl BusInterface for TestBus {
76    fn read(&mut self, address: u32) -> u8 {
77        self.cycles += 1;
78        self.read_ram(address)
79    }
80
81    fn write(&mut self, address: u32, value: u8) {
82        self.cycles += 1;
83        self.ram.insert(address, value);
84    }
85
86    fn idle(&mut self) {
87        self.cycles += 1;
88    }
89
90    fn interrupt_lines(&self) -> InterruptLines {
91        InterruptLines::default()
92    }
93
94    fn set_clock_speed(&mut self, _speed: ClockSpeed) {}
95}
96
97#[derive(Debug, Parser)]
98struct Args {
99    /// Path to a JSON test file or to a directory full of JSON test files
100    #[arg(long, short = 'f')]
101    file_path: PathBuf,
102}
103
104fn main() -> Result<(), Box<DynError>> {
105    env_logger::Builder::from_env(
106        env_logger::Env::new().default_filter_or("info,huc6280_emu=error"),
107    )
108    .init();
109
110    let args = Args::parse();
111
112    let errors = if args.file_path.is_dir() {
113        run_test_directory(&args.file_path)?
114    } else {
115        run_test_file(&args.file_path)?
116    };
117
118    if errors.is_empty() {
119        println!("All tests passed!");
120    } else {
121        eprintln!("{} errors!", errors.len());
122        for error in errors {
123            eprintln!("{error}");
124        }
125    }
126
127    Ok(())
128}
129
130fn run_test_directory(dir_path: &Path) -> Result<Vec<String>, Box<DynError>> {
131    let mut json_paths = Vec::new();
132
133    for dir_entry in fs::read_dir(dir_path)? {
134        let dir_entry = dir_entry?;
135
136        if !dir_entry.metadata()?.is_file() {
137            continue;
138        }
139
140        let file_path = dir_entry.path();
141        if file_path.extension().and_then(OsStr::to_str) != Some("json") {
142            continue;
143        }
144
145        json_paths.push(file_path);
146    }
147
148    json_paths.sort();
149
150    let errors = json_paths
151        .par_iter()
152        .map(|json_path| run_test_file(json_path))
153        .collect::<Result<Vec<_>, _>>()?
154        .into_iter()
155        .flatten()
156        .collect::<Vec<_>>();
157
158    Ok(errors)
159}
160
161fn run_test_file(file_path: &Path) -> Result<Vec<String>, Box<DynError>> {
162    let file = File::open(file_path)?;
163    let tests: Vec<TestDefinition> = serde_json::from_reader(BufReader::new(file))?;
164
165    let mut errors = Vec::new();
166
167    for test in tests {
168        if should_skip_test(&test) {
169            continue;
170        }
171
172        let mut bus = TestBus::new(&test.initial);
173
174        let mut cpu = Huc6280::new();
175        cpu.set_registers(Registers::from(&test.initial));
176
177        loop {
178            cpu.execute_instruction(&mut bus);
179            if !cpu.is_mid_block_transfer() {
180                break;
181            }
182        }
183
184        let expected_registers = Registers::from(&test.final_);
185        if &expected_registers != cpu.registers() {
186            errors.push(format!(
187                "[{:02X} '{}'] registers: expected {expected_registers:#X?}, actual {:#X?}",
188                test.opcode,
189                test.name,
190                cpu.registers()
191            ));
192        }
193
194        for &(address, expected) in &test.final_.ram {
195            let actual = bus.read_ram(address);
196            if expected != actual {
197                errors.push(format!("[{:02X} '{}'] RAM[{address:06X}]: expected {expected:02X}, actual {actual:02X}", test.opcode, test.name));
198            }
199        }
200
201        if bus.cycles != test.num_cycles {
202            errors.push(format!(
203                "[{:02X} '{}'] cycles: expected {}, actual {}",
204                test.opcode, test.name, test.num_cycles, bus.cycles
205            ));
206        }
207    }
208
209    Ok(errors)
210}
211
212fn should_skip_test(test: &TestDefinition) -> bool {
213    const TMA_OPCODE: u8 = 0x43;
214    const BLOCK_TRANSFER_OPCODES: &[u8] = &[0x73, 0xC3, 0xD3, 0xE3, 0xF3];
215    const SBC_OPCODES: &[u8] = &[0xE1, 0xE5, 0xE9, 0xED, 0xF1, 0xF2, 0xF5, 0xF9, 0xFD];
216
217    // Skip TMA tests where the operand doesn't have exactly one bit set
218    // When multiple bits are set, behavior is officially undefined and varies between different emulators
219    // When no bits are set, A should get set to the MPR buffer contents, but the buffer contents
220    // aren't specified anywhere in the test definition
221    if test.opcode == TMA_OPCODE {
222        let physical_pc = (u32::from(test.initial.mpr[(test.initial.pc >> 13) as usize]) << 13)
223            | u32::from(test.initial.pc & 0x1FFF);
224        for &(address, value) in &test.initial.ram {
225            if address == physical_pc + 1 && value.count_ones() != 1 {
226                return true;
227            }
228        }
229    }
230
231    // Skip block transfer tests that hit the JSON test RAM limits; too many caveats to be useful
232    if BLOCK_TRANSFER_OPCODES.contains(&test.opcode) && test.final_.ram.len() >= 499 {
233        return true;
234    }
235
236    // Skip SBC decimal mode tests that have invalid inputs (approximated by checking for invalid output)
237    // Behavior is officially undefined and varies between different emulators
238    if SBC_OPCODES.contains(&test.opcode) && Flags::from(test.initial.p).decimal {
239        let result = test.final_.a;
240        if result & 0x0F > 0x09 || result & 0xF0 > 0x90 {
241            return true;
242        }
243    }
244
245    false
246}