1use clap::Parser; 2use env_logger::Env; 3use serde::Deserialize; 4use spc700_emu::traits::BusInterface; 5use spc700_emu::{Registers, Spc700}; 6use std::error::Error; 7use std::ffi::OsStr; 8use std::fmt::{Display, Formatter}; 9use std::fs::File; 10use std::io::BufReader; 11use std::path::Path; 12use std::{fs, process}; 13 14const RAM_LEN: usize = 1 << 16; 15 16const SLEEP_OPCODE: u8 = 0xEF; 17const STOP_OPCODE: u8 = 0xFF; 18const STOP_TEST_CYCLES: usize = 7; 19 20#[derive(Debug, Clone, Copy, PartialEq, Eq)] 21enum BusOp { 22 Read(u16, u8), 23 Write(u16, u8), 24 Idle, 25} 26 27impl Display for BusOp { 28 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 29 match self { 30 Self::Read(address, value) => write!(f, "Read({address:04X}, {value:02X})"), 31 Self::Write(address, value) => write!(f, "Write({address:04X}, {value:02X})"), 32 Self::Idle => write!(f, "Idle"), 33 } 34 } 35} 36 37#[derive(Debug, Clone)] 38struct RecordingBus { 39 ram: Box<[u8; RAM_LEN]>, 40 ops: Vec<BusOp>, 41} 42 43impl RecordingBus { 44 fn new() -> Self { 45 Self { ram: vec![0; RAM_LEN].into_boxed_slice().try_into().unwrap(), ops: Vec::new() } 46 } 47 48 fn clear(&mut self) { 49 self.ops.clear(); 50 } 51} 52 53impl BusInterface for RecordingBus { 54 fn read(&mut self, address: u16) -> u8 { 55 let value = self.ram[address as usize]; 56 self.ops.push(BusOp::Read(address, value)); 57 value 58 } 59 60 fn write(&mut self, address: u16, value: u8) { 61 self.ops.push(BusOp::Write(address, value)); 62 self.ram[address as usize] = value; 63 } 64 65 fn idle(&mut self) { 66 self.ops.push(BusOp::Idle); 67 } 68} 69 70#[derive(Debug, Clone, Deserialize)] 71struct State { 72 pc: u16, 73 a: u8, 74 x: u8, 75 y: u8, 76 sp: u8, 77 psw: u8, 78 ram: Vec<(u16, u8)>, 79} 80 81#[derive(Debug, Clone, Deserialize)] 82struct Cycle(Option<u16>, Option<u8>, String); 83 84impl Cycle { 85 fn to_bus_op(&self) -> BusOp { 86 match (self.0, self.1, self.2.as_str()) { 87 (None, _, _) | (_, None, _) | (_, _, "wait") => BusOp::Idle, 88 (Some(address), Some(value), "read") => BusOp::Read(address, value), 89 (Some(address), Some(value), "write") => BusOp::Write(address, value), 90 _ => panic!("unexpected cycle descriptor string: {}", self.2), 91 } 92 } 93} 94 95#[derive(Debug, Clone, Deserialize)] 96struct TestDescription { 97 name: String, 98 initial: State, 99 #[serde(rename = "final")] 100 final_: State, 101 cycles: Vec<Cycle>, 102} 103 104#[derive(Debug, Clone, Parser)] 105struct Args { 106 #[arg(short = 'f', long)] 107 file_path: Option<String>, 108 #[arg(short = 'd', long)] 109 directory_path: Option<String>, 110} 111 112fn main() -> Result<(), Box<dyn Error>> { 113 env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); 114 115 let args = Args::parse(); 116 117 match (args.file_path, args.directory_path) { 118 (Some(file_path), None) => { 119 run_test(&file_path)?; 120 } 121 (None, Some(directory_path)) => { 122 run_directory(&directory_path)?; 123 } 124 _ => { 125 eprintln!( 126 "ERROR: Exactly one of -f and -d must be set; use -h to see full help output" 127 ); 128 process::exit(1); 129 } 130 } 131 132 Ok(()) 133} 134 135fn run_directory(directory_path: &str) -> Result<(), Box<dyn Error>> { 136 let mut file_paths: Vec<_> = fs::read_dir(directory_path)? 137 .filter_map(Result::ok) 138 .filter_map(|dir_entry| { 139 let path = dir_entry.path(); 140 (path.extension().and_then(OsStr::to_str) == Some("json")).then_some(path) 141 }) 142 .collect(); 143 144 file_paths.sort(); 145 146 for file_path in file_paths { 147 run_test(&file_path)?; 148 } 149 150 Ok(()) 151} 152 153fn run_test<P: AsRef<Path>>(file_path: P) -> Result<(), Box<dyn Error>> { 154 let file_path = file_path.as_ref(); 155 156 let file = File::open(file_path)?; 157 let test_descriptions: Vec<TestDescription> = serde_json::from_reader(BufReader::new(file))?; 158 let num_tests = test_descriptions.len(); 159 160 let mut bus = RecordingBus::new(); 161 162 let mut failures = 0; 163 for test_description in test_descriptions { 164 let mut cpu = Spc700::new(); 165 166 init_test(&mut cpu, &mut bus, &test_description.initial); 167 168 let opcode = bus.ram[test_description.initial.pc as usize]; 169 if opcode != SLEEP_OPCODE && opcode != STOP_OPCODE { 170 // Run CPU for a full instruction 171 cpu.tick(&mut bus); 172 while cpu.is_mid_instruction() { 173 cpu.tick(&mut bus); 174 } 175 } else { 176 // SLEEP/STOP: Run CPU for a fixed number of cycles (7); the CPU should remain halted 177 // after executing the instruction 178 for _ in 0..STOP_TEST_CYCLES { 179 cpu.tick(&mut bus); 180 } 181 } 182 183 let errors = check_test(&cpu, &bus, &test_description.final_, &test_description.cycles); 184 if !errors.is_empty() { 185 failures += 1; 186 187 log::error!("Failed test '{}':", test_description.name); 188 for error in errors { 189 log::error!(" {error}"); 190 } 191 } 192 193 bus.clear(); 194 } 195 196 if failures != 0 { 197 log::info!("Failed {failures} out of {num_tests} in '{}'", file_path.display()); 198 } 199 200 Ok(()) 201} 202 203macro_rules! check_registers { 204 ($([$name:literal: $actual:expr, $expected:expr]),* $(,)?) => { 205 { 206 let mut errors: Vec<String> = Vec::new(); 207 208 $( 209 let actual = $actual; 210 let expected = $expected; 211 if actual != expected { 212 errors.push(format!("{}: actual={actual:04X}, expected={expected:04X}", $name)); 213 } 214 )* 215 216 errors 217 } 218 } 219} 220 221fn init_test(cpu: &mut Spc700, bus: &mut RecordingBus, state: &State) { 222 cpu.set_registers(Registers { 223 a: state.a, 224 x: state.x, 225 y: state.y, 226 sp: state.sp, 227 pc: state.pc, 228 psw: state.psw.into(), 229 }); 230 231 for &(address, value) in &state.ram { 232 bus.ram[address as usize] = value; 233 } 234} 235 236fn check_test(cpu: &Spc700, bus: &RecordingBus, state: &State, cycles: &[Cycle]) -> Vec<String> { 237 let registers = cpu.registers(); 238 let mut errors = check_registers!( 239 ["A": registers.a, state.a], 240 ["X": registers.x, state.x], 241 ["Y": registers.y, state.y], 242 ["SP": registers.sp, state.sp], 243 ["PC": registers.pc, state.pc], 244 ["PSW": u8::from(registers.psw), state.psw], 245 ); 246 247 for &(address, expected_value) in &state.ram { 248 let actual_value = bus.ram[address as usize]; 249 if actual_value != expected_value { 250 errors.push(format!( 251 "RAM[{address:04X}]: actual={actual_value:02X}, expected={expected_value:02X}" 252 )); 253 } 254 } 255 256 let expected_ops: Vec<_> = cycles.iter().map(Cycle::to_bus_op).collect(); 257 if bus.ops.len() != expected_ops.len() { 258 errors.push(format!( 259 "Cycle count: actual={}, expected={}", 260 bus.ops.len(), 261 expected_ops.len() 262 )); 263 } 264 265 for (i, (actual_op, expected_op)) in bus.ops.iter().zip(&expected_ops).enumerate() { 266 if actual_op != expected_op { 267 errors.push(format!("Cycle {i}: actual={actual_op}, expected={expected_op}")); 268 } 269 } 270 271 errors 272}