main.rsannotatedmain.rssource287 lines · 8.0 KB · raw
1mod bus;
2
3use crate::bus::RecordingBus;
4use clap::Parser;
5use env_logger::Env;
6use serde::Deserialize;
7use std::error::Error;
8use std::ffi::OsStr;
9use std::fmt::{Display, Formatter};
10use std::fs::File;
11use std::io::{BufReader, Read};
12use std::path::Path;
13use std::{fs, process};
14use wdc65816_emu::core::{Registers, Wdc65816};
15
16const MVN_OPCODE: u8 = 0x44;
17const MVP_OPCODE: u8 = 0x54;
18
19#[derive(Debug, Clone, Deserialize)]
20struct State {
21    pc: u16,
22    s: u16,
23    p: u8,
24    a: u16,
25    x: u16,
26    y: u16,
27    dbr: u8,
28    d: u16,
29    pbr: u8,
30    e: u8,
31    ram: Vec<(u32, u8)>,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35enum BusOp {
36    Read(u32, u8),
37    Write(u32, u8),
38    Idle,
39}
40
41impl Display for BusOp {
42    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
43        match self {
44            Self::Read(address, value) => write!(f, "Read({address:06X}, {value:02X})"),
45            Self::Write(address, value) => write!(f, "Write({address:06X}, {value:02X})"),
46            Self::Idle => write!(f, "Idle"),
47        }
48    }
49}
50
51#[derive(Debug, Clone, Deserialize)]
52struct Cycle(Option<u32>, Option<u8>, String);
53
54impl Cycle {
55    fn is_valid(&self) -> bool {
56        // STP and WAI tests use null address to indicate that the CPU has halted
57        self.0.is_some()
58    }
59
60    fn to_bus_op(&self) -> BusOp {
61        match (self.0, self.1) {
62            (Some(address), Some(value)) => {
63                if self.2.as_bytes()[3] == b'r' {
64                    BusOp::Read(address, value)
65                } else {
66                    BusOp::Write(address, value)
67                }
68            }
69            _ => BusOp::Idle,
70        }
71    }
72}
73
74#[derive(Debug, Clone, Deserialize)]
75struct TestDescription {
76    name: String,
77    initial: State,
78    #[serde(rename = "final")]
79    final_: State,
80    cycles: Vec<Cycle>,
81}
82
83#[derive(Debug, Parser)]
84struct Args {

Path to .json test file

86    #[arg(short = 'f', long)]
87    file_path: Option<String>,

Path to directory of .json test files

89    #[arg(short = 'd', long)]
90    directory_path: Option<String>,

Suppress logging when no test cases fail

92    #[arg(short = 's', long)]
93    suppress_success_logs: bool,
94}
96fn main() -> Result<(), Box<dyn Error>> {
97    env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
98
99    let args = Args::parse();
100
101    match (args.file_path, args.directory_path) {
102        (Some(file_path), None) => {
103            let file = File::open(&file_path)?;
104            let file_name = Path::new(&file_path).file_name().and_then(OsStr::to_str).unwrap();
105            test_file(file, file_name, &mut RecordingBus::new(), args.suppress_success_logs)?;
106        }
107        (None, Some(directory_path)) => {
108            test_directory(&directory_path, args.suppress_success_logs)?;
109        }
110        (Some(_), Some(_)) | (None, None) => {
111            eprintln!("ERROR: Exactly one of -d and -f must be set; use -h to see help output");
112            process::exit(1);
113        }
114    }
115
116    Ok(())
117}
118
119fn test_directory(directory_path: &str, suppress_success_logs: bool) -> Result<(), Box<dyn Error>> {
120    let mut files: Vec<_> = fs::read_dir(directory_path)?
121        .filter_map(|dir_entry| {
122            let dir_entry = dir_entry.ok()?;
123
124            let path = dir_entry.path();
125            (path.extension().and_then(OsStr::to_str) == Some("json")).then_some(path)
126        })
127        .collect();
128
129    files.sort_by(|a, b| a.file_name().unwrap().cmp(b.file_name().unwrap()));
130
131    let mut bus = RecordingBus::new();
132    for file in files {
133        let file_name = file.file_name().and_then(OsStr::to_str).unwrap();
134        let file = File::open(&file)?;
135        test_file(file, file_name, &mut bus, suppress_success_logs)?;
136    }
137
138    Ok(())
139}
140
141fn test_file<R: Read>(
142    reader: R,
143    file_name: &str,
144    bus: &mut RecordingBus,
145    suppress_success_logs: bool,
146) -> Result<(), Box<dyn Error>> {
147    let test_descriptions = parse_tests(reader)?;
148    let num_tests = test_descriptions.len();
149
150    let mut failures = 0;
151    for test_description in test_descriptions {
152        let mut wdc65816 = Wdc65816::new();
153        init_test(&mut wdc65816, bus, &test_description.initial);
154
155        // Execute a single full instruction
156        let opcode_addr = (u32::from(test_description.initial.pbr) << 16)
157            | u32::from(test_description.initial.pc);
158        let opcode = bus.ram[opcode_addr as usize];
159        if opcode != MVN_OPCODE && opcode != MVP_OPCODE {
160            wdc65816.tick(bus);
161            while wdc65816.is_mid_instruction() {
162                wdc65816.tick(bus);
163            }
164        } else {
165            // For MVN and MVP, the test suite expects the CPU to execute either until A reaches $FFFF
166            // or until it has executed 100 cycles
167            wdc65816.tick(bus);
168            while bus.ops.len() < 100
169                && (wdc65816.is_mid_instruction() || wdc65816.registers().a != 0xFFFF)
170            {
171                wdc65816.tick(bus);
172            }
173        }
174
175        let errors = check_test(&wdc65816, bus, &test_description.final_, &test_description.cycles);
176
177        if !errors.is_empty() {
178            failures += 1;
179
180            log::error!("Failed test '{}'", test_description.name);
181            for error in errors {
182                log::error!("  {error}");
183            }
184        }
185    }
186
187    if failures > 0 || !suppress_success_logs {
188        log::info!("Failed {failures} out of {num_tests} in '{file_name}'");
189    }
190
191    Ok(())
192}
193
194fn parse_tests<R: Read>(reader: R) -> Result<Vec<TestDescription>, Box<dyn Error>> {
195    let mut test_descriptions: Vec<TestDescription> =
196        serde_json::from_reader(BufReader::new(reader))?;
197
198    for test_description in &mut test_descriptions {
199        test_description.cycles.retain(Cycle::is_valid);
200    }
201
202    Ok(test_descriptions)
203}
204
205fn init_test(wdc65816: &mut Wdc65816, bus: &mut RecordingBus, state: &State) {
206    wdc65816.set_registers(Registers {
207        a: state.a,
208        x: state.x,
209        y: state.y,
210        s: state.s,
211        d: state.d,
212        pbr: state.pbr,
213        pc: state.pc,
214        dbr: state.dbr,
215        p: state.p.into(),
216        emulation_mode: state.e != 0,
217    });
218
219    bus.clear();
220    for &(address, value) in &state.ram {
221        bus.ram[address as usize] = value;
222    }
223}
224
225macro_rules! check_registers {
226    ($([$name:literal: $actual:expr, $expected:expr],)* $(,)?) => {
227        {
228            let mut errors: Vec<String> = Vec::new();
229
230            $(
231                let actual = $actual;
232                let expected = $expected;
233                if actual != expected {
234                    errors.push(format!("{}: actual={actual:04X}, expected={expected:04X}", $name));
235                }
236            )*
237
238            errors
239        }
240    }
241}
242
243fn check_test(
244    wdc65816: &Wdc65816,
245    bus: &RecordingBus,
246    state: &State,
247    cycles: &[Cycle],
248) -> Vec<String> {
249    let registers = wdc65816.registers();
250    let mut errors = check_registers!(
251        ["A": registers.a, state.a],
252        ["X": registers.x, state.x],
253        ["Y": registers.y, state.y],
254        ["S": registers.s, state.s],
255        ["D": registers.d, state.d],
256        ["PBR": registers.pbr, state.pbr],
257        ["PC": registers.pc, state.pc],
258        ["DBR": registers.dbr, state.dbr],
259        ["P": u8::from(registers.p), state.p],
260        ["E": u8::from(registers.emulation_mode), state.e],
261    );
262
263    for &(address, expected) in &state.ram {
264        let actual = bus.ram[address as usize];
265        if actual != expected {
266            errors
267                .push(format!("RAM[{address:06X}]: actual={actual:02X}, expected={expected:02X}"));
268        }
269    }
270
271    let expected_bus_ops: Vec<_> = cycles.iter().map(Cycle::to_bus_op).collect();
272    if bus.ops.len() != expected_bus_ops.len() {
273        errors.push(format!(
274            "Cycle count: actual={}, expected={}",
275            bus.ops.len(),
276            expected_bus_ops.len()
277        ));
278    }
279
280    for (i, (actual_op, expected_op)) in bus.ops.iter().zip(&expected_bus_ops).enumerate() {
281        if actual_op != expected_op {
282            errors.push(format!("Cycle {i}: actual={actual_op}, expected={expected_op}"));
283        }
284    }
285
286    errors
287}