use super::*;
#[test]
fn a_no_operation_takes_four_t_states_and_advances_the_program_counter() {
let (cpu, _, t_states) = run(&[0x00], 1);
assert_eq!(t_states, 4);
assert_eq!(cpu.registers().pc, 1);
}
#[test]
fn an_immediate_load_reaches_the_register_it_names() {
let (cpu, _, t_states) = run(&[0x3E, 0x42], 1);
assert_eq!(t_states, 7);
assert_eq!(cpu.accumulator(), 0x42);
assert_eq!(cpu.registers().pc, 2);
}
#[test]
fn an_addition_reaches_the_accumulator_and_the_flags() {
let (cpu, _, t) = run(&[0x3E, 0x10, 0xC6, 0x05], 2);
assert_eq!(t, 14);
assert_eq!(cpu.accumulator(), 0x15);
assert_eq!(cpu.flags() & crate::flag::Z_MASK, 0);
}
#[test]
fn an_addition_that_carries_reports_it() {
let (cpu, _, _) = run(&[0x3E, 0xFF, 0xC6, 0x01], 2);
assert_eq!(cpu.accumulator(), 0x00);
assert_eq!(cpu.flags() & crate::flag::C_MASK, crate::flag::C_MASK);
assert_eq!(cpu.flags() & crate::flag::Z_MASK, crate::flag::Z_MASK);
}
#[test]
fn a_register_to_register_load_moves_the_byte() {
let (cpu, _, t) = run(&[0x3E, 0x77, 0x47], 2);
assert_eq!(t, 11);
assert_eq!(high_byte(cpu.registers().bc), 0x77);
}
#[test]
fn a_wide_immediate_load_fills_both_halves() {
let (cpu, _, t) = run(&[0x21, 0x34, 0x12], 1);
assert_eq!(t, 10);
assert_eq!(cpu.registers().hl, 0x1234);
}
#[test]
fn a_store_through_a_pair_reaches_memory() {
let (cpu, ram, _) = run(&[0x21, 0x00, 0x40, 0x3E, 0x5A, 0x77], 3);
assert_eq!(cpu.registers().hl, 0x4000);
assert_eq!(ram.bytes[0x4000], 0x5A);
}
#[test]
fn a_load_through_a_pair_reads_memory() {
let (cpu, _, t) = run_from(&[0x21, 0x00, 0x40, 0x7E], |_| {}, 1);
assert_eq!(t, 10);
assert_eq!(cpu.registers().hl, 0x4000);
}
#[test]
fn a_sixteen_bit_increment_takes_six_t_states() {
let (cpu, _, t) = run(&[0x21, 0xFF, 0x00, 0x23], 2);
assert_eq!(t, 16);
assert_eq!(cpu.registers().hl, 0x0100);
}
#[test]
fn an_eight_bit_increment_holds_the_carry_flag() {
let (cpu, _, _) = run(&[0x37, 0x3E, 0x0F, 0x3C], 3);
assert_eq!(cpu.accumulator(), 0x10);
assert_eq!(cpu.flags() & crate::flag::C_MASK, crate::flag::C_MASK);
assert_eq!(cpu.flags() & crate::flag::H_MASK, crate::flag::H_MASK);
}
#[test]
fn an_unconditional_jump_moves_the_program_counter() {
let (cpu, _, t) = run(&[0xC3, 0x34, 0x12], 1);
assert_eq!(t, 10);
assert_eq!(cpu.registers().pc, 0x1234);
}
#[test]
fn a_relative_jump_moves_forward_and_backward() {
let (cpu, _, t) = run(&[0x18, 0x02], 1);
assert_eq!(t, 12);
assert_eq!(cpu.registers().pc, 0x0004);
let (cpu, _, _) = run(&[0x00, 0x00, 0x18, 0xFC], 3);
assert_eq!(cpu.registers().pc, 0x0000);
}
#[test]
fn a_conditional_jump_is_taken_only_when_its_condition_holds() {
let (cpu, _, t) = run(&[0xAF, 0xCA, 0x34, 0x12], 2);
assert_eq!(t, 14);
assert_eq!(cpu.registers().pc, 0x1234);
let (cpu, _, _) = run(&[0xAF, 0xC2, 0x34, 0x12], 2);
assert_eq!(cpu.registers().pc, 0x0004);
}
#[test]
fn a_jump_through_a_pair_costs_four_t_states() {
let (cpu, _, t) = run(&[0x21, 0x00, 0x80, 0xE9], 2);
assert_eq!(t, 14);
assert_eq!(cpu.registers().pc, 0x8000);
}
#[test]
fn a_call_pushes_the_return_address_and_a_return_takes_it_back() {
let (cpu, ram, t) = run_from(
&[0xCD, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC9],
|cpu| cpu.registers_mut().sp = 0x4000,
1,
);
assert_eq!(t, 17);
assert_eq!(cpu.registers().pc, 0x0006);
assert_eq!(cpu.registers().sp, 0x3FFE);
assert_eq!(ram.bytes[0x3FFE], 0x03);
assert_eq!(ram.bytes[0x3FFF], 0x00);
let (cpu, _, t) = run_from(
&[0xCD, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC9],
|cpu| cpu.registers_mut().sp = 0x4000,
2,
);
assert_eq!(t, 27);
assert_eq!(cpu.registers().pc, 0x0003);
assert_eq!(cpu.registers().sp, 0x4000);
}
#[test]
fn a_restart_jumps_to_the_address_its_opcode_names() {
let (cpu, _, t) = run_from(&[0xDF], |cpu| cpu.registers_mut().sp = 0x4000, 1);
assert_eq!(t, 11);
assert_eq!(cpu.registers().pc, 0x0018);
assert_eq!(cpu.registers().sp, 0x3FFE);
}
#[test]
fn a_push_and_a_pop_round_trip_a_pair() {
let (cpu, _, t) = run_from(
&[0x01, 0x34, 0x12, 0xC5, 0xD1],
|cpu| cpu.registers_mut().sp = 0x4000,
3,
);
assert_eq!(t, 10 + 11 + 10);
assert_eq!(cpu.registers().de, 0x1234);
assert_eq!(cpu.registers().sp, 0x4000);
}
#[test]
fn the_exchanges_move_the_pairs_they_name() {
let (cpu, _, t) = run_from(
&[0xEB],
|cpu| {
cpu.registers_mut().de = 0x1111;
cpu.registers_mut().hl = 0x2222;
},
1,
);
assert_eq!(t, 4);
assert_eq!(cpu.registers().de, 0x2222);
assert_eq!(cpu.registers().hl, 0x1111);
let (cpu, _, _) = run_from(
&[0xD9],
|cpu| {
cpu.registers_mut().bc = 0x1111;
cpu.registers_mut().bc_alt = 0x2222;
},
1,
);
assert_eq!(cpu.registers().bc, 0x2222);
let (cpu, _, _) = run_from(
&[0x08],
|cpu| {
cpu.registers_mut().af = 0x1111;
cpu.registers_mut().af_alt = 0x2222;
},
1,
);
assert_eq!(cpu.registers().af, 0x2222);
}
#[test]
fn a_loop_counts_down_to_zero() {
let (cpu, _, _) = run_from(&[0x06, 0x03, 0x10, 0xFE], |_| {}, 4);
assert_eq!(high_byte(cpu.registers().bc), 0x00);
assert_eq!(cpu.registers().pc, 0x0004);
}
#[test]
fn a_prefixed_bit_test_reports_through_the_zero_flag() {
let (cpu, _, t) = run(&[0x3E, 0x80, 0xCB, 0x7F], 2);
assert_eq!(t, 7 + 8);
assert_eq!(cpu.flags() & crate::flag::Z_MASK, 0);
let (cpu, _, _) = run(&[0x3E, 0x00, 0xCB, 0x7F], 2);
assert_eq!(cpu.flags() & crate::flag::Z_MASK, crate::flag::Z_MASK);
}
#[test]
fn a_prefixed_bit_set_and_clear_reach_the_register() {
let (cpu, _, t) = run_from(&[0xCB, 0xFF], |cpu| cpu.registers_mut().af = 0x0000, 1);
assert_eq!(t, 8);
assert_eq!(cpu.accumulator(), 0x80);
let (cpu, _, _) = run(&[0x3E, 0xFF, 0xCB, 0xBF], 2);
assert_eq!(cpu.accumulator(), 0x7F);
}
#[test]
fn a_prefixed_rotate_reaches_the_register_and_the_carry() {
let (cpu, _, t) = run(&[0x3E, 0x81, 0xCB, 0x07], 2);
assert_eq!(t, 7 + 8);
assert_eq!(cpu.accumulator(), 0x03);
assert_eq!(cpu.flags() & crate::flag::C_MASK, crate::flag::C_MASK);
}
#[test]
fn an_extended_negate_subtracts_the_accumulator_from_zero() {
let (cpu, _, t) = run(&[0x3E, 0x01, 0xED, 0x44], 2);
assert_eq!(t, 7 + 8);
assert_eq!(cpu.accumulator(), 0xFF);
assert_eq!(cpu.flags() & crate::flag::N_MASK, crate::flag::N_MASK);
}
#[test]
fn an_extended_interrupt_mode_selection_is_remembered() {
let (cpu, _, t) = run(&[0xED, 0x5E], 1);
assert_eq!(t, 8);
assert_eq!(cpu.registers().interrupt_mode, crate::InterruptMode::Mode2);
}
#[test]
fn an_index_prefixed_wide_load_reaches_the_index_register() {
let (cpu, _, t) = run(&[0xDD, 0x21, 0x34, 0x12], 1);
assert_eq!(t, 14);
assert_eq!(cpu.registers().ix, 0x1234);
assert_eq!(cpu.registers().hl, 0x0000);
let (cpu, _, _) = run(&[0xFD, 0x21, 0x34, 0x12], 1);
assert_eq!(cpu.registers().iy, 0x1234);
}
#[test]
fn a_port_read_and_write_reach_the_host() {
let (cpu, ram, t) = run_from(&[0x3E, 0x12, 0xD3, 0x34], |_| {}, 2);
assert_eq!(t, 7 + 11);
assert_eq!(ram.ports[0x1234], 0x12);
assert_eq!(cpu.accumulator(), 0x12);
}
#[test]
fn a_halt_leaves_the_program_counter_past_the_instruction_that_halted() {
let mut cpu = Cpu::new();
cpu.reset();
let mut ram = Ram::with(&[0x00, 0x76]);
for _ in 0..4 {
cpu.step_by_edges(&mut ram);
}
assert!(cpu.is_halted());
assert_eq!(cpu.registers().pc, 0x0002);
}
#[test]
fn a_wide_add_with_carry_reaches_both_halves() {
let (cpu, _, t) = run_from(
&[0xED, 0x4A],
|cpu| {
cpu.registers_mut().af = 0x0000;
cpu.registers_mut().hl = 0x1000;
cpu.registers_mut().bc = 0x0234;
},
1,
);
assert_eq!(t, 15);
assert_eq!(cpu.registers().hl, 0x1234);
}
#[test]
fn a_wide_add_with_carry_reports_zero_only_when_both_halves_are_zero() {
let (cpu, _, _) = run_from(
&[0xED, 0x42],
|cpu| {
cpu.registers_mut().af = 0x0000;
cpu.registers_mut().hl = 0x1234;
cpu.registers_mut().bc = 0x1234;
},
1,
);
assert_eq!(cpu.registers().hl, 0x0000);
assert_eq!(cpu.flags() & crate::flag::Z_MASK, crate::flag::Z_MASK);
let (cpu, _, _) = run_from(
&[0xED, 0x42],
|cpu| {
cpu.registers_mut().af = 0x0000;
cpu.registers_mut().hl = 0x1234;
cpu.registers_mut().bc = 0x0034;
},
1,
);
assert_eq!(cpu.registers().hl, 0x1200);
assert_eq!(cpu.flags() & crate::flag::Z_MASK, 0);
}
#[test]
fn a_plain_wide_add_reaches_both_halves() {
let (cpu, _, t) = run_from(
&[0x09],
|cpu| {
cpu.registers_mut().hl = 0x1000;
cpu.registers_mut().bc = 0x0234;
},
1,
);
assert_eq!(t, 11);
assert_eq!(cpu.registers().hl, 0x1234);
}
#[test]
fn a_direct_wide_store_and_load_round_trip_through_memory() {
let (cpu, ram, t) = run_from(
&[0x22, 0x40, 0x00],
|cpu| {
cpu.registers_mut().hl = 0xBEEF;
},
1,
);
assert_eq!(t, 16);
assert_eq!(ram.bytes[0x0040], 0xEF);
assert_eq!(ram.bytes[0x0041], 0xBE);
let mut program = vec![0x2A, 0x40, 0x00];
program.resize(0x42, 0);
program[0x40] = 0xEF;
program[0x41] = 0xBE;
let (cpu2, _, t2) = run_from(&program, |_| {}, 1);
assert_eq!(t2, 16);
assert_eq!(cpu2.registers().hl, 0xBEEF);
let _ = cpu;
}
#[test]
fn a_displaced_index_load_reads_the_byte_the_offset_names() {
let mut program = vec![0xDD, 0x7E, 0x05];
program.resize(0x40, 0);
program[0x35] = 0x99;
let (cpu, _, t) = run_from(
&program,
|cpu| {
cpu.registers_mut().ix = 0x0030;
},
1,
);
assert_eq!(t, 19);
assert_eq!(cpu.accumulator(), 0x99);
}
#[test]
fn a_displaced_index_store_writes_where_the_offset_names() {
let (cpu, ram, t) = run_from(
&[0xDD, 0x77, 0x05],
|cpu| {
cpu.registers_mut().ix = 0x0030;
cpu.registers_mut().af = 0x7700;
},
1,
);
assert_eq!(t, 19);
assert_eq!(ram.bytes[0x0035], 0x77);
let _ = cpu;
}
#[test]
fn a_negative_displacement_reaches_below_the_index_register() {
let mut program = vec![0xDD, 0x7E, 0xFB];
program.resize(0x40, 0);
program[0x2B] = 0x5C;
let (cpu, _, _) = run_from(
&program,
|cpu| {
cpu.registers_mut().ix = 0x0030;
},
1,
);
assert_eq!(cpu.accumulator(), 0x5C);
}
#[test]
fn a_nibble_rotate_moves_a_digit_between_the_accumulator_and_memory() {
let mut program = vec![0xED, 0x6F];
program.resize(0x40, 0);
program[0x30] = 0x34;
let (cpu, ram, t) = run_from(
&program,
|cpu| {
cpu.registers_mut().af = 0x1200;
cpu.registers_mut().hl = 0x0030;
},
1,
);
assert_eq!(t, 18);
assert_eq!(cpu.accumulator(), 0x13);
assert_eq!(ram.bytes[0x0030], 0x42);
}