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 directory of .json test files
Suppress logging when no test cases fail
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}