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 {
Path to a JSON test file or to a directory full of JSON test files
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}