use std::path::PathBuf;
use k580_core::{Cpu8080State, NullBus, RegisterName};
const BUG_TESTS_DIR: &str = r"D:\kr\Examples\bug-tests";
fn load_580(name: &str) -> Vec<u8> {
let path = PathBuf::from(BUG_TESTS_DIR).join(name);
let bytes = std::fs::read(&path).unwrap_or_else(|err| {
panic!("could not read {}: {err}", path.display());
});
assert!(
bytes.len() >= 65_536,
"{} too short: {} bytes",
path.display(),
bytes.len()
);
bytes[..65_536].to_vec()
}
fn run_until_halt(cpu: &mut Cpu8080State) {
let mut bus = NullBus::default();
let executed = cpu
.run_until_halt(&mut bus, 10_000)
.expect("CPU error during bug-test execution");
assert!(
cpu.halted,
"program did not reach HLT within {executed} instructions; pc={:#06X}",
cpu.pc
);
}
fn run_bug_test(filename: &str, expected_a: u8) {
let ram = load_580(filename);
let mut cpu = Cpu8080State::default();
cpu.memory.as_mut_slice().copy_from_slice(&ram);
run_until_halt(&mut cpu);
let actual = cpu.get_register(RegisterName::A);
assert_eq!(
actual, expected_a,
"{filename}: expected A={:#04X}, got A={:#04X}",
expected_a, actual
);
}
#[test]
fn bug_test1_rz_z1_must_return() {
run_bug_test("test1_RZ_Z1_must_return.580", 0x55);
}
#[test]
fn bug_test2_rz_z0_must_not_return() {
run_bug_test("test2_RZ_Z0_must_NOT_return.580", 0xFF);
}
#[test]
fn bug_test3_rnz_z0_must_return() {
run_bug_test("test3_RNZ_Z0_must_return.580", 0x55);
}
#[test]
fn bug_test4_rnz_z1_must_not_return() {
run_bug_test("test4_RNZ_Z1_must_NOT_return.580", 0xFF);
}
#[test]
fn bug_test5_rc_cy1_must_return() {
run_bug_test("test5_RC_CY1_must_return.580", 0x55);
}
#[test]
fn bug_test6_rc_cy0_must_not_return() {
run_bug_test("test6_RC_CY0_must_NOT_return.580", 0xFF);
}
#[test]
fn bug_test7_rnc_cy0_must_return() {
run_bug_test("test7_RNC_CY0_must_return.580", 0x55);
}
#[test]
fn bug_test8_rnc_cy1_must_not_return() {
run_bug_test("test8_RNC_CY1_must_NOT_return.580", 0xFF);
}
#[test]
fn bug_test9_rpe_p1_must_return() {
run_bug_test("test9_RPE_P1_must_return.580", 0x55);
}
#[test]
fn bug_test10_rpe_p0_must_not_return() {
run_bug_test("test10_RPE_P0_must_NOT_return.580", 0xFF);
}
#[test]
fn bug_test11_rpo_p0_must_return() {
run_bug_test("test11_RPO_P0_must_return.580", 0x55);
}
#[test]
fn bug_test12_rpo_p1_must_not_return() {
run_bug_test("test12_RPO_P1_must_NOT_return.580", 0xFF);
}
#[test]
fn bug_test13_cp_s0_p1_must_call() {
run_bug_test("test13_CP_S0_P1_must_call.580", 0x22);
}
#[test]
fn bug_test14_cm_s1_p0_must_call() {
run_bug_test("test14_CM_S1_P0_must_call.580", 0x22);
}
#[test]
fn bug_test15_cm_s0_p1_must_not_call() {
run_bug_test("test15_CM_S0_P1_must_NOT_call.580", 0x11);
}
#[test]
fn bug_test16_cp_s1_p0_must_not_call() {
run_bug_test("test16_CP_S1_P0_must_NOT_call.580", 0x11);
}
#[test]
fn bug01_jp_uses_sign_not_parity() {
let mut cpu = Cpu8080State::default();
let prog = [
0x3E, 0x03, 0xB7, 0xF2, 0x0C, 0x00, 0x3E, 0x11, 0x76, 0x00, 0x00, 0x00, 0x3E, 0x22, 0x76, ];
for (i, b) in prog.iter().enumerate() {
cpu.memory.write(i as u16, *b);
}
run_until_halt(&mut cpu);
assert_eq!(
cpu.get_register(RegisterName::A),
0x22,
"JP must jump on S=0"
);
}
#[test]
fn bug01_jm_uses_sign_not_parity() {
let mut cpu = Cpu8080State::default();
let prog = [
0x3E, 0x80, 0xB7, 0xFA, 0x0C, 0x00, 0x3E, 0x11, 0x76, 0x00, 0x00, 0x00, 0x3E, 0x22, 0x76,
];
for (i, b) in prog.iter().enumerate() {
cpu.memory.write(i as u16, *b);
}
run_until_halt(&mut cpu);
assert_eq!(
cpu.get_register(RegisterName::A),
0x22,
"JM must jump on S=1"
);
}
#[test]
fn bug02_rrc_uses_bit0_not_old_carry() {
let mut cpu = Cpu8080State::default();
cpu.flags.carry = true;
cpu.registers.a = 0x02;
cpu.memory.write(0, 0x0F); cpu.memory.write(1, 0x76); run_until_halt(&mut cpu);
assert_eq!(cpu.get_register(RegisterName::A), 0x01, "RRC must use bit0");
assert!(!cpu.flags.carry, "RRC of 0x02 sets CY=0 (bit0 of 0x02)");
}
#[test]
fn bug03_rar_preserves_data_and_uses_old_carry() {
let mut cpu = Cpu8080State::default();
cpu.registers.a = 0xC3;
cpu.flags.carry = true;
cpu.memory.write(0, 0x1F); cpu.memory.write(1, 0x76); run_until_halt(&mut cpu);
assert_eq!(cpu.get_register(RegisterName::A), 0xE1);
assert!(cpu.flags.carry, "old bit0 of 0xC3 = 1 → new CY=1");
}
#[test]
fn bug04_daa_adjusts_bcd_addition() {
let mut cpu = Cpu8080State::default();
cpu.registers.a = 0x15;
cpu.registers.b = 0x27;
cpu.memory.write(0, 0x80); cpu.memory.write(1, 0x27); cpu.memory.write(2, 0x76); run_until_halt(&mut cpu);
assert_eq!(cpu.get_register(RegisterName::A), 0x42);
}
#[test]
fn bug05_in_returns_port_byte_and_advances() {
let mut cpu = Cpu8080State::default();
cpu.memory.write(0, 0xDB); cpu.memory.write(1, 0x05); cpu.memory.write(2, 0x76); let mut bus = NullBus::default();
bus.set_input(0x05, 0x99);
cpu.run_until_halt(&mut bus, 10).unwrap();
assert!(cpu.halted);
assert_eq!(cpu.get_register(RegisterName::A), 0x99);
}
#[test]
fn bug07_ana_ac_follows_8080_quirk() {
let mut cpu = Cpu8080State::default();
cpu.registers.a = 0x08;
cpu.registers.b = 0x00;
cpu.memory.write(0, 0xA0); cpu.memory.write(1, 0x76); run_until_halt(&mut cpu);
assert_eq!(cpu.get_register(RegisterName::A), 0x00);
assert!(cpu.flags.zero);
assert!(
cpu.flags.auxiliary_carry,
"ANA must set AC per 8080 quirk: ((A|operand)&0x08)!=0"
);
}