main.rsannotatedmain.rssource299 lines · 8.7 KB · raw
1//! Designed to run the 68000 tests from <https://github.com/TomHarte/ProcessorTests>
2
3use clap::Parser;
4use env_logger::Env;
5use flate2::read::GzDecoder;
6use m68000_emu::M68000;
7use m68000_emu::bus::InMemoryBus;
8use m68000_emu::traits::BusInterface;
9use serde::{Deserialize, Serialize};
10use std::ffi::OsStr;
11use std::fs::File;
12use std::io::{BufReader, Read};
13use std::path::Path;
14use std::sync::mpsc;
15use std::thread;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18struct State {
19    d0: u32,
20    d1: u32,
21    d2: u32,
22    d3: u32,
23    d4: u32,
24    d5: u32,
25    d6: u32,
26    d7: u32,
27    a0: u32,
28    a1: u32,
29    a2: u32,
30    a3: u32,
31    a4: u32,
32    a5: u32,
33    a6: u32,
34    usp: u32,
35    ssp: u32,
36    sr: u16,
37    pc: u32,
38    prefetch: [u16; 2],
39    ram: Vec<(u32, u8)>,
40}
41
42macro_rules! diff_field {
43    ($actual:expr, $expected:expr, $field:ident) => {
44        if $actual.$field != $expected.$field {
45            log::info!(
46                "  {}: actual={:08X}, expected={:08X}",
47                stringify!($field),
48                $actual.$field,
49                $expected.$field
50            );
51        }
52    };
53}
54
55macro_rules! diff_fields {
56    ($actual:expr, $expected:expr, [$($hex_field:ident),*]) => {
57        $(
58            diff_field!($actual, $expected, $hex_field);
59        )*
60    }
61}
62
63impl State {
64    fn from(m68000: &M68000, bus: &mut InMemoryBus, final_state: &State) -> Self {
65        let [d0, d1, d2, d3, d4, d5, d6, d7] = m68000.data_registers();
66        let [a0, a1, a2, a3, a4, a5, a6] = m68000.address_registers();
67
68        let ram =
69            final_state.ram.iter().map(|&(address, _)| (address, bus.read_byte(address))).collect();
70
71        Self {
72            d0,
73            d1,
74            d2,
75            d3,
76            d4,
77            d5,
78            d6,
79            d7,
80            a0,
81            a1,
82            a2,
83            a3,
84            a4,
85            a5,
86            a6,
87            usp: m68000.user_stack_pointer(),
88            ssp: m68000.supervisor_stack_pointer(),
89            sr: m68000.status_register(),
90            pc: m68000.pc(),
91            prefetch: final_state.prefetch,
92            ram,
93        }
94    }
95
96    fn diff(&self, expected: &Self) {
97        diff_fields!(
98            self,
99            expected,
100            [d0, d1, d2, d3, d4, d5, d6, d7, a0, a1, a2, a3, a4, a5, a6, usp, ssp, sr, pc]
101        );
102
103        if self.ram != expected.ram {
104            log::info!("  ram:");
105            for ((address, actual), (_, expected)) in
106                self.ram.iter().copied().zip(expected.ram.iter().copied())
107            {
108                if actual != expected {
109                    log::info!("    {address:08X}: actual={actual:02X}, expected={expected:02X}");
110                }
111            }
112        }
113    }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117struct TestDescription {
118    name: String,
119    initial: State,
120    #[serde(rename = "final")]
121    final_state: State,
122    length: u32,
123}
124
125#[derive(Debug, Parser)]
126struct Args {
127    /// Path to a single test file to run.
128    #[arg(short = 'f', long)]
129    file_path: Option<String>,
130
131    /// Path to a directory of tests to run.
132    #[arg(short = 'd', long)]
133    dir_path: Option<String>,
134
135    /// Don't log details on individual test case failures
136    #[arg(short = 's', long = "no-individual-logs", default_value_t = true, action = clap::ArgAction::SetFalse)]
137    individual_logs: bool,
138}
139
140fn main() {
141    env_logger::Builder::from_env(Env::default().default_filter_or("info,m68000_emu::core=off"))
142        .init();
143
144    let args = Args::parse();
145    match (args.file_path, args.dir_path) {
146        (Some(file_path), None) => {
147            run_file_test(&file_path, args.individual_logs);
148        }
149        (None, Some(dir_path)) => {
150            run_directory_of_tests(&dir_path, args.individual_logs);
151        }
152        (Some(_), Some(_)) | (None, None) => {
153            panic!("exactly one of file_path and dir_path must be set");
154        }
155    }
156}
157
158fn run_file_test(file_path: &str, individual_logs: bool) {
159    let file_path = Path::new(&file_path);
160
161    let file_ext = file_path.extension().and_then(OsStr::to_str).unwrap();
162    let file = BufReader::new(File::open(file_path).unwrap());
163    let file: Box<dyn Read> = match file_ext {
164        "json" => Box::new(file),
165        "gz" => Box::new(GzDecoder::new(file)),
166        _ => panic!("unsupported file extension: {file_ext}"),
167    };
168
169    let test_descriptions: Vec<TestDescription> = serde_json::from_reader(file).unwrap();
170
171    log::info!("Loaded {} tests", test_descriptions.len());
172
173    let mut bus = InMemoryBus::new();
174    run_single_test(&test_descriptions, &mut bus, file_path, individual_logs);
175}
176
177struct ParseResult {
178    file_path: String,
179    test_descriptions: Vec<TestDescription>,
180}
181
182fn run_directory_of_tests(dir_path: &str, individual_logs: bool) {
183    let mut receivers = vec![];
184    let read_dir = Path::new(dir_path).read_dir().expect("Unable to read directory");
185    for dir_entry in read_dir {
186        let dir_entry = dir_entry.expect("Unable to read directory entry");
187        let metadata = dir_entry.metadata().expect("Unable to read file metadata");
188
189        if metadata.is_file() && dir_entry.file_name().to_string_lossy().ends_with(".json.gz") {
190            let (sender, receiver) = mpsc::channel();
191            receivers.push(receiver);
192
193            let file_path = dir_entry.path().to_string_lossy().to_string();
194            thread::spawn(move || {
195                let file = GzDecoder::new(BufReader::new(
196                    File::open(Path::new(&file_path)).expect("Unable to open file"),
197                ));
198
199                let test_descriptions: Vec<TestDescription> = match serde_json::from_reader(file) {
200                    Ok(descriptions) => descriptions,
201                    Err(err) => {
202                        log::error!("Unable to parse JSON at '{file_path}': {err}");
203                        panic!("JSON parse error");
204                    }
205                };
206
207                sender.send(ParseResult { file_path, test_descriptions }).unwrap();
208            });
209        }
210    }
211
212    let mut parse_results = vec![];
213    for receiver in receivers {
214        let parse_result = receiver.recv().unwrap();
215        parse_results.push(parse_result);
216    }
217
218    parse_results.sort_by(|a, b| a.file_path.cmp(&b.file_path));
219
220    let mut bus = InMemoryBus::new();
221    for ParseResult { file_path, test_descriptions } in parse_results {
222        run_single_test(&test_descriptions, &mut bus, Path::new(&file_path), individual_logs);
223    }
224}
225
226fn run_single_test<P: AsRef<Path>>(
227    test_descriptions: &[TestDescription],
228    bus: &mut InMemoryBus,
229    file_path: P,
230    individual_logs: bool,
231) {
232    let mut failure_count = 0_u32;
233    let mut timing_failure_count = 0_u32;
234    let mut address_error_count = 0_u32;
235    for test_description in test_descriptions {
236        let mut m68000 = init_test_state(&test_description.initial, bus);
237        let cycles = m68000.execute_instruction(bus);
238
239        let state = State::from(&m68000, bus, &test_description.final_state);
240        if state != test_description.final_state {
241            if individual_logs {
242                log::info!("Failed test '{}'", test_description.name);
243                state.diff(&test_description.final_state);
244            }
245
246            failure_count += 1;
247        }
248
249        if cycles != test_description.length && !m68000.address_error() {
250            if individual_logs {
251                log::info!(
252                    "Timing mismatch for test '{}'; actual={cycles}, expected={}",
253                    test_description.name,
254                    test_description.length
255                );
256            }
257
258            timing_failure_count += 1;
259        }
260
261        if m68000.address_error() {
262            address_error_count += 1;
263        }
264    }
265
266    let num_tests = test_descriptions.len();
267    let display_path = file_path.as_ref().display();
268    log::info!("{failure_count} failed out of {num_tests} tests in {display_path}");
269
270    let num_tests_without_address_errors = num_tests as u32 - address_error_count;
271    log::info!(
272        "{timing_failure_count} timing mismatches out of {num_tests_without_address_errors} tests in {display_path}"
273    );
274}
275
276fn init_test_state(state: &State, bus: &mut InMemoryBus) -> M68000 {
277    let mut m68000 = M68000::default();
278
279    m68000.set_data_registers([
280        state.d0, state.d1, state.d2, state.d3, state.d4, state.d5, state.d6, state.d7,
281    ]);
282    m68000.set_address_registers(
283        [state.a0, state.a1, state.a2, state.a3, state.a4, state.a5, state.a6],
284        state.usp,
285        state.ssp,
286    );
287    m68000.set_status_register(state.sr);
288
289    bus.write_word(state.pc, state.prefetch[0]);
290    bus.write_word(state.pc.wrapping_add(2), state.prefetch[1]);
291
292    for &(address, value) in &state.ram {
293        bus.write_byte(address, value);
294    }
295
296    m68000.set_pc(state.pc, bus);
297
298    m68000
299}