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}