use mos6502::cpu;
use mos6502::instruction::Nmos6502;
use mos6502::memory::Bus;
use mos6502::memory::Memory;
use std::fs::read;
const TEST_BINARY_PATH: &str = "tests/assets/6502_timing_test.bin";
const PROGRAM_LOAD_ADDR: u16 = 0x2000;
const PROGRAM_START_ADDR: u16 = 0x2000;
const FAILURE_COUNT_ADDR: u16 = 0xFCD0;
const VECTOR_PRINTCHAR: u16 = 0x2010; const VECTOR_INIT: u16 = 0x2020; const VECTOR_TIMER_START: u16 = 0x2030; const VECTOR_TIMER_READ: u16 = 0x2040; const VECTOR_END_TEST: u16 = 0x2050;
const MAX_INSTRUCTIONS: u64 = 200_000_000;
struct TimingTestMemory {
ram: Memory,
output_buffer: Vec<u8>,
failure_count: Option<u8>,
}
impl TimingTestMemory {
fn new() -> Self {
Self {
ram: Memory::new(),
output_buffer: Vec::new(),
failure_count: None,
}
}
}
impl Bus for TimingTestMemory {
fn get_byte(&mut self, addr: u16) -> u8 {
self.ram.get_byte(addr)
}
fn set_byte(&mut self, addr: u16, val: u8) {
if addr == FAILURE_COUNT_ADDR {
self.failure_count = Some(val);
}
self.ram.set_byte(addr, val);
}
}
#[test]
#[ignore]
fn timing_test_6502() {
let program = read(TEST_BINARY_PATH).expect("Could not read timing test binary");
let mut memory = TimingTestMemory::new();
memory.ram.set_bytes(PROGRAM_LOAD_ADDR, &program);
memory.ram.set_bytes(
VECTOR_PRINTCHAR,
&[
0x48, 0x68, 0x60, ],
);
memory.ram.set_bytes(
VECTOR_INIT,
&[
0x60, ],
);
memory.ram.set_bytes(
VECTOR_TIMER_START,
&[
0x8D, 0x64, 0xFE, 0x60, ],
);
memory.ram.set_bytes(
VECTOR_TIMER_READ,
&[
0xA2, 0x00, 0x60, ],
);
memory.ram.set_bytes(
VECTOR_END_TEST,
&[
0x8D, 0xD0, 0xFC, 0x4C, 0x50, 0x20, ],
);
let mut cpu = cpu::CPU::new(memory, Nmos6502);
cpu.registers.program_counter = PROGRAM_START_ADDR;
let mut instr_count = 0u64;
let mut last_pc = 0u16;
let mut stuck_count = 0u32;
let mut timer_start_cycles = 0u64;
loop {
let current_pc = cpu.registers.program_counter;
if current_pc == VECTOR_TIMER_START {
timer_start_cycles = cpu.cycles;
} else if current_pc == VECTOR_TIMER_READ {
let elapsed = cpu.cycles - timer_start_cycles;
let decoded = cpu.fetch_next_and_decode().unwrap();
cpu.execute_instruction(decoded);
cpu.registers.index_x = (elapsed & 0xFF) as u8;
instr_count += 1;
continue; }
if current_pc == VECTOR_END_TEST {
stuck_count += 1;
if stuck_count > 10 {
break; }
}
assert!(
instr_count < MAX_INSTRUCTIONS,
"Test exceeded maximum instruction count ({}) at PC ${:04X}\n\
This likely indicates a bug in the emulator.\n\
CPU state: {:?}",
MAX_INSTRUCTIONS,
current_pc,
cpu.registers
);
if cpu.memory.failure_count.is_some() {
break;
}
let decoded_instr = cpu.fetch_next_and_decode();
if let Some(instr) = decoded_instr {
instr_count += 1;
cpu.execute_instruction(instr);
} else {
let opcode = cpu.memory.get_byte(current_pc);
panic!(
"Illegal opcode ${:02X} at PC ${:04X} after {} instructions\n\
CPU state: {:?}",
opcode, current_pc, instr_count, cpu.registers
);
}
if current_pc == last_pc && current_pc != VECTOR_END_TEST {
panic!(
"Infinite loop detected at PC ${:04X} after {} instructions\n\
CPU state: {:?}",
current_pc, instr_count, cpu.registers
);
}
last_pc = current_pc;
}
let failures = cpu.memory.failure_count.unwrap_or(255);
eprintln!(
"6502 Timing test completed after {} instructions",
instr_count
);
eprintln!("Failure count: {}", failures);
if !cpu.memory.output_buffer.is_empty() {
eprintln!("Test output:");
for &ch in &cpu.memory.output_buffer {
eprint!("{}", ch as char);
}
eprintln!();
}
assert_eq!(
failures, 0,
"Timing test failed with {} timing errors. \
This means the cycle counts for some instructions don't match expected timing.",
failures
);
}