hg80 1.0.0

Z80 and Z80N CPU core, stepped one clock edge at a time
Documentation
use super::*;

#[test]
fn a_non_maskable_interrupt_out_of_a_halt_returns_past_the_instruction_that_halted() {
    let mut cpu = Cpu::new();
    cpu.reset();
    cpu.registers_mut().sp = 0x0100;
    let mut ram = Ram::with(&[0x00, 0x76]);
    cpu.step_by_edges(&mut ram);
    cpu.step_by_edges(&mut ram);
    assert!(cpu.is_halted());

    cpu.request_nmi();
    cpu.step_by_edges(&mut ram);
    cpu.step_by_edges(&mut ram);
    assert!(!cpu.is_halted());
    assert_eq!(cpu.registers().pc, 0x0066);
    assert_eq!(ram.bytes[0x00FE], 0x02);
    assert_eq!(ram.bytes[0x00FF], 0x00);
}

#[test]
fn a_machine_that_answers_the_stackless_reports_keeps_the_return_address_out_of_memory() {
    let mut cpu = Cpu::new();
    cpu.reset();
    cpu.set_z80n_enabled(true);
    cpu.registers_mut().sp = 0x0100;
    let mut machine = Stackless::new(&[0x00, 0x00]);

    cpu.step_by_edges(&mut machine);
    let accepting = take_a_non_maskable_interrupt(&mut cpu, &mut machine);

    assert_eq!(machine.captured, Some(0x0002));
    assert_eq!(machine.writes, 0);
    assert_eq!(machine.bytes[0x00FE], 0x00);
    assert_eq!(machine.bytes[0x00FF], 0x00);
    assert_eq!(cpu.registers().sp, 0x00FE);
    assert_eq!(cpu.registers().pc, 0x0066);

    let returning = cpu.step_by_edges(&mut machine);
    assert_eq!(machine.reads_of_the_stack, 2);
    assert_eq!(machine.captured, None);
    assert_eq!(cpu.registers().pc, 0x0002);
    assert_eq!(cpu.registers().sp, 0x0100);

    let mut plain = Cpu::new();
    plain.reset();
    plain.registers_mut().sp = 0x0100;
    let mut ram = Ram::with(&[0x00, 0x00]);
    ram.bytes[0x0066] = 0xED;
    ram.bytes[0x0067] = 0x45;
    plain.step_by_edges(&mut ram);
    let plain_accepting = take_a_non_maskable_interrupt(&mut plain, &mut ram);
    let plain_returning = plain.step_by_edges(&mut ram);

    assert_eq!(accepting, plain_accepting);
    assert_eq!(returning, plain_returning);
    assert_eq!(ram.bytes[0x00FE], 0x02);
    assert_eq!(ram.bytes[0x00FF], 0x00);
    assert_eq!(plain.registers().pc, 0x0002);
}

#[test]
fn setting_the_interrupt_enable_takes_effect_within_the_instruction() {
    let (cpu, _, t) = run(&[0xFB], 1);
    assert_eq!(t, 4);
    assert!(cpu.registers().iff1);
    assert!(cpu.registers().iff2);

    let (cpu, _, _) = run(&[0xFB, 0xF3], 2);
    assert!(!cpu.registers().iff1);
    assert!(!cpu.registers().iff2);
}

#[test]
fn a_return_from_an_interrupt_reports_itself_so_a_daisy_chain_can_release() {
    let mut cpu = Cpu::new();
    cpu.reset();
    cpu.registers_mut().sp = 0x0040;
    let mut ram = Ram::with(&[0xED, 0x4D]);
    cpu.step_by_edges(&mut ram);
    assert_eq!(ram.returns, 1);
}

#[test]
fn a_return_from_a_non_maskable_interrupt_is_not_part_of_the_chain() {
    let mut cpu = Cpu::new();
    cpu.reset();
    cpu.registers_mut().sp = 0x0040;
    let mut ram = Ram::with(&[0xED, 0x45]);
    cpu.step_by_edges(&mut ram);
    assert_eq!(ram.returns, 0);
}

#[test]
fn the_interrupt_enable_is_readable_through_the_parity_flag() {
    let (cpu, _, _) = run(&[0xFB, 0xED, 0x57], 2);
    assert_ne!(cpu.flags() & crate::flag::P_MASK, 0);

    let (cpu, _, _) = run(&[0xF3, 0xED, 0x57], 2);
    assert_eq!(cpu.flags() & crate::flag::P_MASK, 0);
}

#[test]
fn a_non_maskable_interrupt_keeps_the_saved_enable_for_the_return_to_restore() {
    let mut cpu = Cpu::new();
    cpu.reset();
    cpu.registers_mut().sp = 0x0200;
    let mut ram = Ram::with(&[0xFB, 0x76]);
    ram.bytes[0x0066] = 0xED;
    ram.bytes[0x0067] = 0x57;

    cpu.step_by_edges(&mut ram);
    cpu.step_by_edges(&mut ram);
    assert!(cpu.is_halted());
    assert!(cpu.registers().iff1 && cpu.registers().iff2);

    cpu.request_nmi();
    cpu.step_by_edges(&mut ram);
    cpu.step_by_edges(&mut ram);
    assert_eq!(cpu.registers().pc, 0x0066);
    assert!(!cpu.registers().iff1, "acceptance must disable interrupts");
    assert!(cpu.registers().iff2, "the saved copy must survive for RETN");

    cpu.step_by_edges(&mut ram);
    assert_ne!(
        cpu.flags() & crate::flag::P_MASK,
        0,
        "the saved copy must be readable through the parity flag"
    );
}