Move all CPU tests into integration test directory
parent
2caf7fe7e8
commit
321ab461ff
924
src/cpu.rs
924
src/cpu.rs
|
@ -6,19 +6,23 @@ use crate::{
|
|||
register::{self, Registers},
|
||||
};
|
||||
|
||||
mod instructions;
|
||||
pub mod instructions;
|
||||
mod parse;
|
||||
mod run;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Processor {
|
||||
pub registers: Registers,
|
||||
pub memory: Memory,
|
||||
num_cycles: u64,
|
||||
registers: Registers,
|
||||
memory: Memory,
|
||||
}
|
||||
|
||||
impl Processor {
|
||||
fn run(&mut self, instruction: &RunnableInstruction) {
|
||||
/// Run a single instruction on the CPU.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if an internal error occurred within the CPU. These are always bugs.
|
||||
pub fn run(&mut self, instruction: &RunnableInstruction) {
|
||||
let run_res = run::run_instruction(self, &instruction.instruction);
|
||||
if let Err(err) = run_res {
|
||||
panic!("Fatal CPU error occured: {err}")
|
||||
|
@ -117,915 +121,3 @@ impl Processor {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use test_case::test_case;
|
||||
|
||||
fn set_opposite_of_expected_flags(
|
||||
processor: &mut Processor,
|
||||
(zero_flag, sub_flag, half_carry_flag, carry_flag): (u8, u8, u8, u8),
|
||||
) {
|
||||
let invert_flag = |value| {
|
||||
if value == 0 {
|
||||
1
|
||||
} else if value == 1 {
|
||||
0
|
||||
} else {
|
||||
panic!("invalid flag value of {value} ")
|
||||
}
|
||||
};
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Zero, invert_flag(zero_flag));
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::HalfCarry, invert_flag(half_carry_flag));
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Carry, invert_flag(carry_flag));
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Subtract, invert_flag(sub_flag));
|
||||
}
|
||||
|
||||
macro_rules! assert_flags_eq {
|
||||
($processor: expr, $(($flag: path, $value: expr)),+ $(,)?) => {
|
||||
$(
|
||||
assert_eq!(
|
||||
$value,
|
||||
$processor.registers.get_flag_bit($flag),
|
||||
"{:?} flag had unexpected value",
|
||||
$flag
|
||||
);
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
#[test_case(0x3E, register::SingleEightBit::A)]
|
||||
#[test_case(0x06, register::SingleEightBit::B)]
|
||||
#[test_case(0x0E, register::SingleEightBit::C)]
|
||||
#[test_case(0x16, register::SingleEightBit::D)]
|
||||
#[test_case(0x1E, register::SingleEightBit::E)]
|
||||
#[test_case(0x26, register::SingleEightBit::H)]
|
||||
#[test_case(0x2E, register::SingleEightBit::L)]
|
||||
fn test_load_immediate(load_opcode: u8, expected_register: register::SingleEightBit) {
|
||||
let mut processor = Processor::default();
|
||||
let data = [load_opcode, 0x23, 0x00, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
// assert our extra data after the instruction is returned
|
||||
// this data is just garbage; no idea if it's a valid instruction
|
||||
assert_eq!(extra_data, &[0x00, 0x01]);
|
||||
|
||||
processor.run(&ins);
|
||||
assert_eq!(
|
||||
0x23,
|
||||
processor
|
||||
.registers
|
||||
.get_single_8bit_register(expected_register)
|
||||
);
|
||||
}
|
||||
|
||||
// lol going from a register to itself is kind of a weird thing to test
|
||||
#[test_case(0x7F, register::SingleEightBit::A, register::SingleEightBit::A)]
|
||||
#[test_case(0x78, register::SingleEightBit::A, register::SingleEightBit::B)]
|
||||
#[test_case(0x79, register::SingleEightBit::A, register::SingleEightBit::C)]
|
||||
#[test_case(0x7A, register::SingleEightBit::A, register::SingleEightBit::D)]
|
||||
#[test_case(0x7B, register::SingleEightBit::A, register::SingleEightBit::E)]
|
||||
#[test_case(0x7C, register::SingleEightBit::A, register::SingleEightBit::H)]
|
||||
#[test_case(0x7D, register::SingleEightBit::A, register::SingleEightBit::L)]
|
||||
#[test_case(0x40, register::SingleEightBit::B, register::SingleEightBit::B)]
|
||||
#[test_case(0x41, register::SingleEightBit::B, register::SingleEightBit::C)]
|
||||
#[test_case(0x42, register::SingleEightBit::B, register::SingleEightBit::D)]
|
||||
#[test_case(0x43, register::SingleEightBit::B, register::SingleEightBit::E)]
|
||||
#[test_case(0x44, register::SingleEightBit::B, register::SingleEightBit::H)]
|
||||
#[test_case(0x45, register::SingleEightBit::B, register::SingleEightBit::L)]
|
||||
#[test_case(0x48, register::SingleEightBit::C, register::SingleEightBit::B)]
|
||||
#[test_case(0x49, register::SingleEightBit::C, register::SingleEightBit::C)]
|
||||
#[test_case(0x4A, register::SingleEightBit::C, register::SingleEightBit::D)]
|
||||
#[test_case(0x4B, register::SingleEightBit::C, register::SingleEightBit::E)]
|
||||
#[test_case(0x4C, register::SingleEightBit::C, register::SingleEightBit::H)]
|
||||
#[test_case(0x4D, register::SingleEightBit::C, register::SingleEightBit::L)]
|
||||
#[test_case(0x50, register::SingleEightBit::D, register::SingleEightBit::B)]
|
||||
#[test_case(0x51, register::SingleEightBit::D, register::SingleEightBit::C)]
|
||||
#[test_case(0x52, register::SingleEightBit::D, register::SingleEightBit::D)]
|
||||
#[test_case(0x53, register::SingleEightBit::D, register::SingleEightBit::E)]
|
||||
#[test_case(0x54, register::SingleEightBit::D, register::SingleEightBit::H)]
|
||||
#[test_case(0x55, register::SingleEightBit::D, register::SingleEightBit::L)]
|
||||
#[test_case(0x58, register::SingleEightBit::E, register::SingleEightBit::B)]
|
||||
#[test_case(0x59, register::SingleEightBit::E, register::SingleEightBit::C)]
|
||||
#[test_case(0x5A, register::SingleEightBit::E, register::SingleEightBit::D)]
|
||||
#[test_case(0x5B, register::SingleEightBit::E, register::SingleEightBit::E)]
|
||||
#[test_case(0x5C, register::SingleEightBit::E, register::SingleEightBit::H)]
|
||||
#[test_case(0x5D, register::SingleEightBit::E, register::SingleEightBit::L)]
|
||||
#[test_case(0x60, register::SingleEightBit::H, register::SingleEightBit::B)]
|
||||
#[test_case(0x61, register::SingleEightBit::H, register::SingleEightBit::C)]
|
||||
#[test_case(0x62, register::SingleEightBit::H, register::SingleEightBit::D)]
|
||||
#[test_case(0x63, register::SingleEightBit::H, register::SingleEightBit::E)]
|
||||
#[test_case(0x64, register::SingleEightBit::H, register::SingleEightBit::H)]
|
||||
#[test_case(0x65, register::SingleEightBit::H, register::SingleEightBit::L)]
|
||||
#[test_case(0x68, register::SingleEightBit::L, register::SingleEightBit::B)]
|
||||
#[test_case(0x69, register::SingleEightBit::L, register::SingleEightBit::C)]
|
||||
#[test_case(0x6A, register::SingleEightBit::L, register::SingleEightBit::D)]
|
||||
#[test_case(0x6B, register::SingleEightBit::L, register::SingleEightBit::E)]
|
||||
#[test_case(0x6C, register::SingleEightBit::L, register::SingleEightBit::H)]
|
||||
#[test_case(0x6D, register::SingleEightBit::L, register::SingleEightBit::L)]
|
||||
#[test_case(0x47, register::SingleEightBit::B, register::SingleEightBit::A)]
|
||||
#[test_case(0x4F, register::SingleEightBit::C, register::SingleEightBit::A)]
|
||||
#[test_case(0x57, register::SingleEightBit::D, register::SingleEightBit::A)]
|
||||
#[test_case(0x5F, register::SingleEightBit::E, register::SingleEightBit::A)]
|
||||
#[test_case(0x67, register::SingleEightBit::H, register::SingleEightBit::A)]
|
||||
#[test_case(0x6F, register::SingleEightBit::L, register::SingleEightBit::A)]
|
||||
fn test_load_register(
|
||||
load_opcode: u8,
|
||||
dst_register: register::SingleEightBit,
|
||||
src_register: register::SingleEightBit,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(src_register, 0x45);
|
||||
|
||||
let data = [load_opcode, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
// assert our extra data after the instruction is returned
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
|
||||
processor.run(&ins);
|
||||
assert_eq!(
|
||||
0x45,
|
||||
processor.registers.get_single_8bit_register(dst_register)
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x7E, register::Combined::HL, register::SingleEightBit::A)]
|
||||
#[test_case(0x46, register::Combined::HL, register::SingleEightBit::B)]
|
||||
#[test_case(0x4E, register::Combined::HL, register::SingleEightBit::C)]
|
||||
#[test_case(0x56, register::Combined::HL, register::SingleEightBit::D)]
|
||||
#[test_case(0x5E, register::Combined::HL, register::SingleEightBit::E)]
|
||||
#[test_case(0x66, register::Combined::HL, register::SingleEightBit::H)]
|
||||
#[test_case(0x6E, register::Combined::HL, register::SingleEightBit::L)]
|
||||
#[test_case(0x0A, register::Combined::BC, register::SingleEightBit::A)]
|
||||
#[test_case(0x1A, register::Combined::DE, register::SingleEightBit::A)]
|
||||
fn test_load_from_memory(
|
||||
opcode: u8,
|
||||
expected_deref_register: register::Combined,
|
||||
expected_register: register::SingleEightBit,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
let set_val = processor.memory.set(0xCCDD, 105);
|
||||
assert_eq!(105, set_val.unwrap());
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(expected_deref_register, 0xCCDD);
|
||||
|
||||
let data = [opcode, 0x10, 0x20];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse isntruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x10, 0x20]);
|
||||
|
||||
processor.run(&ins);
|
||||
assert_eq!(
|
||||
105,
|
||||
processor
|
||||
.registers
|
||||
.get_single_8bit_register(expected_register)
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x70, register::Combined::HL, register::SingleEightBit::B)]
|
||||
#[test_case(0x71, register::Combined::HL, register::SingleEightBit::C)]
|
||||
#[test_case(0x72, register::Combined::HL, register::SingleEightBit::D)]
|
||||
#[test_case(0x73, register::Combined::HL, register::SingleEightBit::E)]
|
||||
#[test_case(0x74, register::Combined::HL, register::SingleEightBit::H)]
|
||||
#[test_case(0x75, register::Combined::HL, register::SingleEightBit::L)]
|
||||
#[test_case(0x02, register::Combined::BC, register::SingleEightBit::A)]
|
||||
#[test_case(0x12, register::Combined::DE, register::SingleEightBit::A)]
|
||||
#[test_case(0x77, register::Combined::HL, register::SingleEightBit::A)]
|
||||
fn test_load_to_memory(
|
||||
opcode: u8,
|
||||
expected_deref_register: register::Combined,
|
||||
expected_register: register::SingleEightBit,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
// This test does a bit of a sneaky - because we want to use CC as the high/low addresses
|
||||
// in the H/L variants of these tests, but we also want to use it store addreses, we must
|
||||
// be careful that the stored immediate value is also part of the address in these cases.
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(expected_register, 0xCC);
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(expected_deref_register, 0xCCCC);
|
||||
|
||||
let data = [opcode, 0x10, 0x20];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x10, 0x20]);
|
||||
|
||||
processor.run(&ins);
|
||||
assert_eq!(0xCC, processor.memory.get(0xCCCC).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_immediate_to_memory() {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, 0xCCDE);
|
||||
|
||||
let data = [0x36, 0x1D, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(0x1D, processor.memory.get(0xCCDE).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_from_immediate_address() {
|
||||
let mut processor = Processor::default();
|
||||
processor.memory.set(0xCCDE, 105);
|
||||
|
||||
let data = [0xFA, 0xDE, 0xCC, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(105, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_to_immadiate_address() {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = 105;
|
||||
let data = [0xEA, 0xDE, 0xCC, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(105, processor.memory.get(0xCCDE).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_from_io_register_zone() {
|
||||
let mut processor = Processor::default();
|
||||
processor.memory.set(0xFF64, 10);
|
||||
processor.registers.c = 0x64;
|
||||
|
||||
let data = [0xF2, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(10, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_to_io_register_zone() {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.c = 0x64;
|
||||
processor.registers.a = 10;
|
||||
|
||||
let data = [0xE2, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(10, processor.memory.get(0xFF64).unwrap());
|
||||
}
|
||||
|
||||
#[test_case(0x32, 0x64, 0x63)]
|
||||
#[test_case(0x22, 0x64, 0x65)]
|
||||
fn test_load_to_register_address_then_do_arithmetic(
|
||||
opcode: u8,
|
||||
hl_value_before: u16,
|
||||
hl_value_after: u16,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, hl_value_before);
|
||||
processor.registers.a = 10;
|
||||
|
||||
let data = [opcode, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(10, processor.memory.get(hl_value_before.into()).unwrap());
|
||||
assert_eq!(
|
||||
hl_value_after,
|
||||
processor
|
||||
.registers
|
||||
.get_combined_register(register::Combined::HL)
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x3A, 0x64, 0x63)]
|
||||
#[test_case(0x2A, 0x64, 0x65)]
|
||||
fn test_load_from_register_address_then_do_arithmetic(
|
||||
opcode: u8,
|
||||
hl_value_before: u16,
|
||||
hl_value_after: u16,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, hl_value_before);
|
||||
processor.memory.set(hl_value_before.into(), 10);
|
||||
|
||||
let data = [opcode, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(10, processor.registers.a);
|
||||
assert_eq!(
|
||||
hl_value_after,
|
||||
processor
|
||||
.registers
|
||||
.get_combined_register(register::Combined::HL)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_to_io_register_zone_by_immediate() {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = 0xAF;
|
||||
|
||||
let data = [0xE0, 0x05, 0x06];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x06]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(0xAF, processor.memory.get(0xFF05).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_from_io_register_zone_by_immediate() {
|
||||
let mut processor = Processor::default();
|
||||
processor.memory.set(0xFF05, 0xAF);
|
||||
|
||||
let data = [0xF0, 0x05, 0x06];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x06]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(0xAF, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test_case(0x01, register::SixteenBit::Combined(register::Combined::BC))]
|
||||
#[test_case(0x11, register::SixteenBit::Combined(register::Combined::DE))]
|
||||
#[test_case(0x21, register::SixteenBit::Combined(register::Combined::HL))]
|
||||
#[test_case(
|
||||
0x31,
|
||||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer)
|
||||
)]
|
||||
fn test_load_16bit_immediate_to_register(
|
||||
opcode: u8,
|
||||
expected_dst_register: register::SixteenBit,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
// The manual doesn't specify this is little endian, but from what I can gather
|
||||
// from googling, it should be.
|
||||
//
|
||||
// Note this excerpt from the Tetris disassembly
|
||||
// l05c0: ld hl,0ff02h ; 05c0 21 02 ff !..
|
||||
let data = [opcode, 0x34, 0x12, 0x05];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x05]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(
|
||||
0x1234,
|
||||
processor
|
||||
.registers
|
||||
.get_16bit_register(expected_dst_register)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_from_hl_to_sp() {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, 0x1234);
|
||||
let data = [0xF9, 0x00];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(0x1234, processor.registers.stack_pointer);
|
||||
}
|
||||
|
||||
#[test_case(0, 0xFF00)]
|
||||
#[test_case(16, 0xFF10)]
|
||||
#[test_case(-5, 0xFEFB)]
|
||||
#[test_case(127, 0xFF7F)]
|
||||
#[test_case(-127, 0xFE81)]
|
||||
#[test_case(-128, 0xFE80)]
|
||||
fn test_load_effective_address(value: i8, expected_sp: u16) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.set_16bit_register(
|
||||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
|
||||
0xFF00,
|
||||
);
|
||||
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
// The truncation will only happen if |value| > 128 in the branch where we check it's equal to -128
|
||||
// so we're fine.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let unsigned_value = if value >= 0 {
|
||||
value as u8
|
||||
} else if value == i8::MIN {
|
||||
i16::from(i8::MIN).abs() as u8
|
||||
} else {
|
||||
!(value.abs() as u8) + 1
|
||||
};
|
||||
let data = [0xF8, unsigned_value, 0x00];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(
|
||||
expected_sp,
|
||||
processor
|
||||
.registers
|
||||
.get_combined_register(register::Combined::HL)
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x0000, 0x00, 0, 0; "no addition")]
|
||||
#[test_case(0x00FF, 0x20, 0, 1; "overflow")]
|
||||
#[test_case(0x00C0, 0x7f, 0, 1; "overflow 2")]
|
||||
#[test_case(0x00FF, 0x01, 1, 1; "overflow with half carry")]
|
||||
#[test_case(0x000A, 0x0C, 1, 0; "half carry")]
|
||||
#[test_case(0x0018, -0x4, 1, 0; "negative half carry")]
|
||||
#[test_case(0x000F, -0x10, 1, 1; "underflow with half carry")]
|
||||
#[test_case(0x000E, -0x0F, 0, 1; "underflow with no half carry")]
|
||||
fn test_load_effective_address_flags(
|
||||
starting_sp: u16,
|
||||
add_value: i8,
|
||||
half_carry: u8,
|
||||
carry: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.stack_pointer = starting_sp;
|
||||
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
// The truncation will only happen if |value| > 128 in the branch where we check it's equal to -128
|
||||
// so we're fine.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let unsigned_value = if add_value >= 0 {
|
||||
add_value as u8
|
||||
} else if add_value == i8::MIN {
|
||||
i16::from(i8::MIN).abs() as u8
|
||||
} else {
|
||||
!(add_value.abs() as u8) + 1
|
||||
};
|
||||
|
||||
let data = [0xF8, unsigned_value, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(
|
||||
half_carry,
|
||||
processor.registers.get_flag_bit(register::Flag::HalfCarry),
|
||||
"incorrect half carry bit"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
carry,
|
||||
processor.registers.get_flag_bit(register::Flag::Carry),
|
||||
"incorrect carry bit"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
0,
|
||||
processor.registers.get_flag_bit(register::Flag::Zero),
|
||||
"incorrect zero bit"
|
||||
);
|
||||
assert_eq!(
|
||||
0,
|
||||
processor.registers.get_flag_bit(register::Flag::Subtract),
|
||||
"incorrect subtract bit"
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0xF5, register::Combined::AF)]
|
||||
#[test_case(0xC5, register::Combined::BC)]
|
||||
#[test_case(0xD5, register::Combined::DE)]
|
||||
#[test_case(0xE5, register::Combined::HL)]
|
||||
fn test_stack_push(opcode: u8, src: register::Combined) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.set_combined_register(src, 0x1234);
|
||||
processor.registers.stack_pointer = 0xFFFE;
|
||||
let data = [opcode, 0x01];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
processor.run(&ins);
|
||||
|
||||
// we want to pop the LSB first (i.e. we write the MSB first)
|
||||
// https://rgbds.gbdev.io/docs/v0.5.2/gbz80.7#PUSH_r16
|
||||
assert_eq!(0x12, processor.memory.get(0xFFFE - 1).unwrap());
|
||||
assert_eq!(0x34, processor.memory.get(0xFFFE - 2).unwrap());
|
||||
assert_eq!(0xFFFE - 2, processor.registers.stack_pointer);
|
||||
}
|
||||
|
||||
#[test_case(0xF1, register::Combined::AF)]
|
||||
#[test_case(0xC1, register::Combined::BC)]
|
||||
#[test_case(0xD1, register::Combined::DE)]
|
||||
#[test_case(0xE1, register::Combined::HL)]
|
||||
fn test_stack_pop(opcode: u8, dst: register::Combined) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.stack_pointer = 0xFFF0;
|
||||
processor.memory.set_both(((0xFFF0, 0x34), (0xFFF1, 0x12)));
|
||||
let data = [opcode, 0x01];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(0x1234, processor.registers.get_combined_register(dst));
|
||||
assert_eq!(0xFFF0 + 2, processor.registers.stack_pointer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_a_to_itself() {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = 10;
|
||||
let data = [0x87, 0x02];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x02]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(20, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test_case(0x80, register::SingleEightBit::B)]
|
||||
#[test_case(0x81, register::SingleEightBit::C)]
|
||||
#[test_case(0x82, register::SingleEightBit::D)]
|
||||
#[test_case(0x83, register::SingleEightBit::E)]
|
||||
#[test_case(0x84, register::SingleEightBit::H)]
|
||||
#[test_case(0x85, register::SingleEightBit::L)]
|
||||
fn test_add_to_a_value(opcode: u8, src: register::SingleEightBit) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = 10;
|
||||
processor.registers.set_single_8bit_register(src, 20);
|
||||
let data = [opcode, 0x02];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x02]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(30, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test_case(0x80, register::SingleEightBit::B, 0x00, 0x00, 1, 0, 0; "zero flag for zero value")]
|
||||
#[test_case(0x80, register::SingleEightBit::B, 0x00, 0x01, 0, 0, 0; "no zero flag for non-zero value")]
|
||||
#[test_case(0x81, register::SingleEightBit::C, 0x0F, 0x01, 0, 1, 0; "half carry flag")]
|
||||
#[test_case(0x81, register::SingleEightBit::C, 0x80, 0x80, 0, 0, 1; "full carry flag")]
|
||||
#[test_case(0x81, register::SingleEightBit::C, 0xFF, 0x01, 0, 1, 1; "both full and half carry flag")]
|
||||
// 0000 1111
|
||||
fn test_add_register_to_a_flags(
|
||||
opcode: u8,
|
||||
src: register::SingleEightBit,
|
||||
a_value: u8,
|
||||
src_value: u8,
|
||||
zero_flag: u8,
|
||||
half_carry_flag: u8,
|
||||
carry_flag: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = a_value;
|
||||
|
||||
// Set all the register to the opposite we expect to ensure they all get set
|
||||
set_opposite_of_expected_flags(&mut processor, (zero_flag, 0, half_carry_flag, carry_flag));
|
||||
|
||||
processor.registers.set_single_8bit_register(src, src_value);
|
||||
let data = [opcode, 0x02];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x02]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_flags_eq!(
|
||||
processor,
|
||||
(register::Flag::Zero, zero_flag),
|
||||
(register::Flag::Subtract, 0),
|
||||
(register::Flag::HalfCarry, half_carry_flag),
|
||||
(register::Flag::Carry, carry_flag),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_hl_addr_to_a_value() {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.memory
|
||||
.set(0xFF00, 0x34)
|
||||
.expect("expected to be able to set 0xFF00");
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, 0xFF00);
|
||||
processor.registers.a = 0x12;
|
||||
|
||||
let data = [0x86, 0x02];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x02]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(0x46, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test_case(0x00, 0x00, 1, 0, 0; "zero flag for zero value")]
|
||||
#[test_case(0x00, 0x01, 0, 0, 0; "no zero flag for non-zero value")]
|
||||
#[test_case(0x0F, 0x01, 0, 1, 0; "half carry flag")]
|
||||
#[test_case(0x80, 0x80, 0, 0, 1; "full carry flag")]
|
||||
#[test_case(0xFF, 0x01, 0, 1, 1; "both full and half carry flag")]
|
||||
fn test_add_hl_addr_to_a_flags(
|
||||
a_value: u8,
|
||||
hl_addr_value: u8,
|
||||
zero_flag: u8,
|
||||
half_carry_flag: u8,
|
||||
carry_flag: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = a_value;
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, 0xFF00);
|
||||
|
||||
processor
|
||||
.memory
|
||||
.set(0xFF00, hl_addr_value)
|
||||
.expect("expected to set address 0xFF00 but could not");
|
||||
|
||||
// Set all the register to the opposite we expect to ensure they all get set
|
||||
set_opposite_of_expected_flags(&mut processor, (zero_flag, 0, half_carry_flag, carry_flag));
|
||||
|
||||
let data = [0x86, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_flags_eq!(
|
||||
processor,
|
||||
(register::Flag::Zero, zero_flag),
|
||||
(register::Flag::Subtract, 0),
|
||||
(register::Flag::HalfCarry, half_carry_flag),
|
||||
(register::Flag::Carry, carry_flag),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_immediate_to_a_value() {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = 50;
|
||||
|
||||
let data = [0xC6, 0x13, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(69, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test_case(0x00, 0x00, 1, 0, 0; "zero flag for zero value")]
|
||||
#[test_case(0x00, 0x01, 0, 0, 0; "no zero flag for non-zero value")]
|
||||
#[test_case(0x0F, 0x01, 0, 1, 0; "half carry flag")]
|
||||
#[test_case(0x80, 0x80, 0, 0, 1; "full carry flag")]
|
||||
#[test_case(0xFF, 0x01, 0, 1, 1; "both full and half carry flag")]
|
||||
fn test_add_immediate_to_a_flags(
|
||||
a_value: u8,
|
||||
n: u8,
|
||||
zero_flag: u8,
|
||||
half_carry_flag: u8,
|
||||
carry_flag: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = a_value;
|
||||
|
||||
// Set all the register to the opposite we expect to ensure they all get set
|
||||
set_opposite_of_expected_flags(&mut processor, (zero_flag, 0, half_carry_flag, carry_flag));
|
||||
|
||||
let data = [0xC6, n, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_flags_eq!(
|
||||
processor,
|
||||
(register::Flag::Zero, zero_flag),
|
||||
(register::Flag::Subtract, 0),
|
||||
(register::Flag::HalfCarry, half_carry_flag),
|
||||
(register::Flag::Carry, carry_flag),
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x88, 0x12, 0, register::SingleEightBit::B, 0x5, 0x17; "no carry to register b")]
|
||||
#[test_case(0x88, 0x12, 1, register::SingleEightBit::B, 0x5, 0x18; "carry to register b")]
|
||||
#[test_case(0x89, 0x12, 0, register::SingleEightBit::C, 0x5, 0x17; "no carry to register c")]
|
||||
#[test_case(0x89, 0x12, 1, register::SingleEightBit::C, 0x5, 0x18; "carry to register c")]
|
||||
#[test_case(0x8A, 0x12, 0, register::SingleEightBit::D, 0x5, 0x17; "no carry to register d")]
|
||||
#[test_case(0x8A, 0x12, 1, register::SingleEightBit::D, 0x5, 0x18; "carry to register d")]
|
||||
#[test_case(0x8B, 0x12, 0, register::SingleEightBit::E, 0x5, 0x17; "no carry to register e")]
|
||||
#[test_case(0x8B, 0x12, 1, register::SingleEightBit::E, 0x5, 0x18; "carry to register e")]
|
||||
#[test_case(0x8C, 0x12, 0, register::SingleEightBit::H, 0x5, 0x17; "no carry to register h")]
|
||||
#[test_case(0x8C, 0x12, 1, register::SingleEightBit::H, 0x5, 0x18; "carry to register h")]
|
||||
#[test_case(0x8D, 0x12, 0, register::SingleEightBit::L, 0x5, 0x17; "no carry to register l")]
|
||||
#[test_case(0x8D, 0x12, 1, register::SingleEightBit::L, 0x5, 0x18; "carry to register l")]
|
||||
fn test_add_with_carry_to_a_value(
|
||||
opcode: u8,
|
||||
initial_value: u8,
|
||||
carry_bit: u8,
|
||||
operand_register: register::SingleEightBit,
|
||||
operand: u8,
|
||||
expected_value: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = initial_value;
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(operand_register, operand);
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Carry, carry_bit);
|
||||
|
||||
let data = [opcode, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(expected_value, processor.registers.a);
|
||||
}
|
||||
|
||||
// these parameters are like impossible to read but I can't think of a nicer way to do this right now
|
||||
// B register
|
||||
#[test_case(0x88, 0x12, 0, register::SingleEightBit::B, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register b")]
|
||||
#[test_case(0x88, 0x12, 1, register::SingleEightBit::B, 0x5, 0, 0, 0; "carry bit set, results in no flags, register b")]
|
||||
#[test_case(0x88, 0x00, 0, register::SingleEightBit::B, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register b")]
|
||||
#[test_case(0x88, 0x00, 1, register::SingleEightBit::B, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register b")]
|
||||
#[test_case(0x88, 0x0F, 0, register::SingleEightBit::B, 0x1, 0, 1, 0; "no carry bit, half carry, register b")]
|
||||
#[test_case(0x88, 0x0F, 1, register::SingleEightBit::B, 0x1, 0, 1, 0; "carry bit, half carry, register b")]
|
||||
#[test_case(0x88, 0x0F, 0, register::SingleEightBit::B, 0x0F, 0, 1, 0; "no carry bit, full carry, register b")]
|
||||
#[test_case(0x88, 0x0F, 1, register::SingleEightBit::B, 0x0F, 0, 1, 0; "carry bit, full carry, register b")]
|
||||
#[test_case(0x88, 0xFF, 1, register::SingleEightBit::B, 0xFF, 0, 1, 1; "carry bit, both carries, register b")]
|
||||
// C register
|
||||
#[test_case(0x89, 0x12, 0, register::SingleEightBit::C, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register c")]
|
||||
#[test_case(0x89, 0x12, 1, register::SingleEightBit::C, 0x5, 0, 0, 0; "carry bit set, results in no flags, register c")]
|
||||
#[test_case(0x89, 0x00, 0, register::SingleEightBit::C, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register c")]
|
||||
#[test_case(0x89, 0x00, 1, register::SingleEightBit::C, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register c")]
|
||||
#[test_case(0x89, 0x0F, 0, register::SingleEightBit::C, 0x1, 0, 1, 0; "no carry bit, half carry, register c")]
|
||||
#[test_case(0x89, 0x0F, 1, register::SingleEightBit::C, 0x1, 0, 1, 0; "carry bit, half carry, register c")]
|
||||
#[test_case(0x89, 0x0F, 0, register::SingleEightBit::C, 0x0F, 0, 1, 0; "no carry bit, full carry, register c")]
|
||||
#[test_case(0x89, 0x0F, 1, register::SingleEightBit::C, 0x0F, 0, 1, 0; "carry bit, full carry, register c")]
|
||||
#[test_case(0x89, 0xFF, 1, register::SingleEightBit::C, 0xFF, 0, 1, 1; "carry bit, both carries, register c")]
|
||||
// D register
|
||||
#[test_case(0x8A, 0x12, 0, register::SingleEightBit::D, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register d")]
|
||||
#[test_case(0x8A, 0x12, 1, register::SingleEightBit::D, 0x5, 0, 0, 0; "carry bit set, results in no flags, register d")]
|
||||
#[test_case(0x8A, 0x00, 0, register::SingleEightBit::D, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register d")]
|
||||
#[test_case(0x8A, 0x00, 1, register::SingleEightBit::D, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register d")]
|
||||
#[test_case(0x8A, 0x0F, 0, register::SingleEightBit::D, 0x1, 0, 1, 0; "no carry bit, half carry, register d")]
|
||||
#[test_case(0x8A, 0x0F, 1, register::SingleEightBit::D, 0x1, 0, 1, 0; "carry bit, half carry, register d")]
|
||||
#[test_case(0x8A, 0x0F, 0, register::SingleEightBit::D, 0x0F, 0, 1, 0; "no carry bit, full carry, register d")]
|
||||
#[test_case(0x8A, 0x0F, 1, register::SingleEightBit::D, 0x0F, 0, 1, 0; "carry bit, full carry, register d")]
|
||||
#[test_case(0x8A, 0xFF, 1, register::SingleEightBit::D, 0xFF, 0, 1, 1; "carry bit, both carries, register d")]
|
||||
// E register
|
||||
#[test_case(0x8B, 0x12, 0, register::SingleEightBit::E, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register e")]
|
||||
#[test_case(0x8B, 0x12, 1, register::SingleEightBit::E, 0x5, 0, 0, 0; "carry bit set, results in no flags, register e")]
|
||||
#[test_case(0x8B, 0x00, 0, register::SingleEightBit::E, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register e")]
|
||||
#[test_case(0x8B, 0x00, 1, register::SingleEightBit::E, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register e")]
|
||||
#[test_case(0x8B, 0x0F, 0, register::SingleEightBit::E, 0x1, 0, 1, 0; "no carry bit, half carry, register e")]
|
||||
#[test_case(0x8B, 0x0F, 1, register::SingleEightBit::E, 0x1, 0, 1, 0; "carry bit, half carry, register e")]
|
||||
#[test_case(0x8B, 0x0F, 0, register::SingleEightBit::E, 0x0F, 0, 1, 0; "no carry bit, full carry, register e")]
|
||||
#[test_case(0x8B, 0x0F, 1, register::SingleEightBit::E, 0x0F, 0, 1, 0; "carry bit, full carry, register e")]
|
||||
#[test_case(0x8B, 0xFF, 1, register::SingleEightBit::E, 0xFF, 0, 1, 1; "carry bit, both carries, register e")]
|
||||
// H register
|
||||
#[test_case(0x8C, 0x12, 0, register::SingleEightBit::H, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register h")]
|
||||
#[test_case(0x8C, 0x12, 1, register::SingleEightBit::H, 0x5, 0, 0, 0; "carry bit set, results in no flags, register h")]
|
||||
#[test_case(0x8C, 0x00, 0, register::SingleEightBit::H, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register h")]
|
||||
#[test_case(0x8C, 0x00, 1, register::SingleEightBit::H, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register h")]
|
||||
#[test_case(0x8C, 0x0F, 0, register::SingleEightBit::H, 0x1, 0, 1, 0; "no carry bit, half carry, register h")]
|
||||
#[test_case(0x8C, 0x0F, 1, register::SingleEightBit::H, 0x1, 0, 1, 0; "carry bit, half carry, register h")]
|
||||
#[test_case(0x8C, 0x0F, 0, register::SingleEightBit::H, 0x0F, 0, 1, 0; "no carry bit, full carry, register h")]
|
||||
#[test_case(0x8C, 0x0F, 1, register::SingleEightBit::H, 0x0F, 0, 1, 0; "carry bit, full carry, register h")]
|
||||
#[test_case(0x8C, 0xFF, 1, register::SingleEightBit::H, 0xFF, 0, 1, 1; "carry bit, both carries, register h")]
|
||||
// L register
|
||||
#[test_case(0x8D, 0x12, 0, register::SingleEightBit::L, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register l")]
|
||||
#[test_case(0x8D, 0x12, 1, register::SingleEightBit::L, 0x5, 0, 0, 0; "carry bit set, results in no flags, register l")]
|
||||
#[test_case(0x8D, 0x00, 0, register::SingleEightBit::L, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register l")]
|
||||
#[test_case(0x8D, 0x00, 1, register::SingleEightBit::L, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register l")]
|
||||
#[test_case(0x8D, 0x0F, 0, register::SingleEightBit::L, 0x1, 0, 1, 0; "no carry bit, half carry, register l")]
|
||||
#[test_case(0x8D, 0x0F, 1, register::SingleEightBit::L, 0x1, 0, 1, 0; "carry bit, half carry, register l")]
|
||||
#[test_case(0x8D, 0x0F, 0, register::SingleEightBit::L, 0x0F, 0, 1, 0; "no carry bit, full carry, register l")]
|
||||
#[test_case(0x8D, 0x0F, 1, register::SingleEightBit::L, 0x0F, 0, 1, 0; "carry bit, full carry, register l")]
|
||||
#[test_case(0x8D, 0xFF, 1, register::SingleEightBit::L, 0xFF, 0, 1, 1; "carry bit, both carries, register l")]
|
||||
fn test_add_with_carry_to_a_flags(
|
||||
opcode: u8,
|
||||
initial_value: u8,
|
||||
carry_bit: u8,
|
||||
operand_register: register::SingleEightBit,
|
||||
operand: u8,
|
||||
expected_zero_flag: u8,
|
||||
expected_half_carry_flag: u8,
|
||||
expected_carry_flag: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = initial_value;
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(operand_register, operand);
|
||||
|
||||
// Set all the register to the opposite we expect to ensure they all get set
|
||||
set_opposite_of_expected_flags(
|
||||
&mut processor,
|
||||
(
|
||||
expected_zero_flag,
|
||||
0,
|
||||
expected_half_carry_flag,
|
||||
expected_carry_flag,
|
||||
),
|
||||
);
|
||||
|
||||
// ...except for the carry bit, which we must set for the test
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Carry, carry_bit);
|
||||
|
||||
let data = [opcode, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_flags_eq!(
|
||||
processor,
|
||||
(register::Flag::Zero, expected_zero_flag),
|
||||
(register::Flag::Subtract, 0),
|
||||
(register::Flag::HalfCarry, expected_half_carry_flag),
|
||||
(register::Flag::Carry, expected_carry_flag),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#![warn(clippy::all, clippy::pedantic)]
|
||||
|
||||
pub mod cpu;
|
||||
mod memory;
|
||||
mod register;
|
||||
mod cpu;
|
||||
pub mod register;
|
||||
|
|
|
@ -0,0 +1,387 @@
|
|||
use ferris_boi::{
|
||||
cpu::{instructions::RunnableInstruction, Processor},
|
||||
register,
|
||||
};
|
||||
|
||||
use test_case::test_case;
|
||||
|
||||
fn set_opposite_of_expected_flags(
|
||||
processor: &mut Processor,
|
||||
(zero_flag, sub_flag, half_carry_flag, carry_flag): (u8, u8, u8, u8),
|
||||
) {
|
||||
let invert_flag = |value| {
|
||||
if value == 0 {
|
||||
1
|
||||
} else if value == 1 {
|
||||
0
|
||||
} else {
|
||||
panic!("invalid flag value of {value} ")
|
||||
}
|
||||
};
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Zero, invert_flag(zero_flag));
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::HalfCarry, invert_flag(half_carry_flag));
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Carry, invert_flag(carry_flag));
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Subtract, invert_flag(sub_flag));
|
||||
}
|
||||
|
||||
macro_rules! assert_flags_eq {
|
||||
($processor: expr, $(($flag: path, $value: expr)),+ $(,)?) => {
|
||||
$(
|
||||
assert_eq!(
|
||||
$value,
|
||||
$processor.registers.get_flag_bit($flag),
|
||||
"{:?} flag had unexpected value",
|
||||
$flag
|
||||
);
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_a_to_itself() {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = 10;
|
||||
let data = [0x87, 0x02];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x02]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(20, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test_case(0x80, register::SingleEightBit::B)]
|
||||
#[test_case(0x81, register::SingleEightBit::C)]
|
||||
#[test_case(0x82, register::SingleEightBit::D)]
|
||||
#[test_case(0x83, register::SingleEightBit::E)]
|
||||
#[test_case(0x84, register::SingleEightBit::H)]
|
||||
#[test_case(0x85, register::SingleEightBit::L)]
|
||||
fn test_add_to_a_value(opcode: u8, src: register::SingleEightBit) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = 10;
|
||||
processor.registers.set_single_8bit_register(src, 20);
|
||||
let data = [opcode, 0x02];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x02]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(30, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test_case(0x80, register::SingleEightBit::B, 0x00, 0x00, 1, 0, 0; "zero flag for zero value")]
|
||||
#[test_case(0x80, register::SingleEightBit::B, 0x00, 0x01, 0, 0, 0; "no zero flag for non-zero value")]
|
||||
#[test_case(0x81, register::SingleEightBit::C, 0x0F, 0x01, 0, 1, 0; "half carry flag")]
|
||||
#[test_case(0x81, register::SingleEightBit::C, 0x80, 0x80, 0, 0, 1; "full carry flag")]
|
||||
#[test_case(0x81, register::SingleEightBit::C, 0xFF, 0x01, 0, 1, 1; "both full and half carry flag")]
|
||||
// 0000 1111
|
||||
fn test_add_register_to_a_flags(
|
||||
opcode: u8,
|
||||
src: register::SingleEightBit,
|
||||
a_value: u8,
|
||||
src_value: u8,
|
||||
zero_flag: u8,
|
||||
half_carry_flag: u8,
|
||||
carry_flag: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = a_value;
|
||||
|
||||
// Set all the register to the opposite we expect to ensure they all get set
|
||||
set_opposite_of_expected_flags(&mut processor, (zero_flag, 0, half_carry_flag, carry_flag));
|
||||
|
||||
processor.registers.set_single_8bit_register(src, src_value);
|
||||
let data = [opcode, 0x02];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x02]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_flags_eq!(
|
||||
processor,
|
||||
(register::Flag::Zero, zero_flag),
|
||||
(register::Flag::Subtract, 0),
|
||||
(register::Flag::HalfCarry, half_carry_flag),
|
||||
(register::Flag::Carry, carry_flag),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_hl_addr_to_a_value() {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.memory
|
||||
.set(0xFF00, 0x34)
|
||||
.expect("expected to be able to set 0xFF00");
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, 0xFF00);
|
||||
processor.registers.a = 0x12;
|
||||
|
||||
let data = [0x86, 0x02];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x02]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(0x46, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test_case(0x00, 0x00, 1, 0, 0; "zero flag for zero value")]
|
||||
#[test_case(0x00, 0x01, 0, 0, 0; "no zero flag for non-zero value")]
|
||||
#[test_case(0x0F, 0x01, 0, 1, 0; "half carry flag")]
|
||||
#[test_case(0x80, 0x80, 0, 0, 1; "full carry flag")]
|
||||
#[test_case(0xFF, 0x01, 0, 1, 1; "both full and half carry flag")]
|
||||
fn test_add_hl_addr_to_a_flags(
|
||||
a_value: u8,
|
||||
hl_addr_value: u8,
|
||||
zero_flag: u8,
|
||||
half_carry_flag: u8,
|
||||
carry_flag: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = a_value;
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, 0xFF00);
|
||||
|
||||
processor
|
||||
.memory
|
||||
.set(0xFF00, hl_addr_value)
|
||||
.expect("expected to set address 0xFF00 but could not");
|
||||
|
||||
// Set all the register to the opposite we expect to ensure they all get set
|
||||
set_opposite_of_expected_flags(&mut processor, (zero_flag, 0, half_carry_flag, carry_flag));
|
||||
|
||||
let data = [0x86, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_flags_eq!(
|
||||
processor,
|
||||
(register::Flag::Zero, zero_flag),
|
||||
(register::Flag::Subtract, 0),
|
||||
(register::Flag::HalfCarry, half_carry_flag),
|
||||
(register::Flag::Carry, carry_flag),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_immediate_to_a_value() {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = 50;
|
||||
|
||||
let data = [0xC6, 0x13, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(69, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test_case(0x00, 0x00, 1, 0, 0; "zero flag for zero value")]
|
||||
#[test_case(0x00, 0x01, 0, 0, 0; "no zero flag for non-zero value")]
|
||||
#[test_case(0x0F, 0x01, 0, 1, 0; "half carry flag")]
|
||||
#[test_case(0x80, 0x80, 0, 0, 1; "full carry flag")]
|
||||
#[test_case(0xFF, 0x01, 0, 1, 1; "both full and half carry flag")]
|
||||
fn test_add_immediate_to_a_flags(
|
||||
a_value: u8,
|
||||
n: u8,
|
||||
zero_flag: u8,
|
||||
half_carry_flag: u8,
|
||||
carry_flag: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = a_value;
|
||||
|
||||
// Set all the register to the opposite we expect to ensure they all get set
|
||||
set_opposite_of_expected_flags(&mut processor, (zero_flag, 0, half_carry_flag, carry_flag));
|
||||
|
||||
let data = [0xC6, n, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
|
||||
processor.run(&ins);
|
||||
|
||||
assert_flags_eq!(
|
||||
processor,
|
||||
(register::Flag::Zero, zero_flag),
|
||||
(register::Flag::Subtract, 0),
|
||||
(register::Flag::HalfCarry, half_carry_flag),
|
||||
(register::Flag::Carry, carry_flag),
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x88, 0x12, 0, register::SingleEightBit::B, 0x5, 0x17; "no carry to register b")]
|
||||
#[test_case(0x88, 0x12, 1, register::SingleEightBit::B, 0x5, 0x18; "carry to register b")]
|
||||
#[test_case(0x89, 0x12, 0, register::SingleEightBit::C, 0x5, 0x17; "no carry to register c")]
|
||||
#[test_case(0x89, 0x12, 1, register::SingleEightBit::C, 0x5, 0x18; "carry to register c")]
|
||||
#[test_case(0x8A, 0x12, 0, register::SingleEightBit::D, 0x5, 0x17; "no carry to register d")]
|
||||
#[test_case(0x8A, 0x12, 1, register::SingleEightBit::D, 0x5, 0x18; "carry to register d")]
|
||||
#[test_case(0x8B, 0x12, 0, register::SingleEightBit::E, 0x5, 0x17; "no carry to register e")]
|
||||
#[test_case(0x8B, 0x12, 1, register::SingleEightBit::E, 0x5, 0x18; "carry to register e")]
|
||||
#[test_case(0x8C, 0x12, 0, register::SingleEightBit::H, 0x5, 0x17; "no carry to register h")]
|
||||
#[test_case(0x8C, 0x12, 1, register::SingleEightBit::H, 0x5, 0x18; "carry to register h")]
|
||||
#[test_case(0x8D, 0x12, 0, register::SingleEightBit::L, 0x5, 0x17; "no carry to register l")]
|
||||
#[test_case(0x8D, 0x12, 1, register::SingleEightBit::L, 0x5, 0x18; "carry to register l")]
|
||||
fn test_add_with_carry_to_a_value(
|
||||
opcode: u8,
|
||||
initial_value: u8,
|
||||
carry_bit: u8,
|
||||
operand_register: register::SingleEightBit,
|
||||
operand: u8,
|
||||
expected_value: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = initial_value;
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(operand_register, operand);
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Carry, carry_bit);
|
||||
|
||||
let data = [opcode, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(expected_value, processor.registers.a);
|
||||
}
|
||||
|
||||
// these parameters are like impossible to read but I can't think of a nicer way to do this right now
|
||||
// B register
|
||||
#[test_case(0x88, 0x12, 0, register::SingleEightBit::B, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register b")]
|
||||
#[test_case(0x88, 0x12, 1, register::SingleEightBit::B, 0x5, 0, 0, 0; "carry bit set, results in no flags, register b")]
|
||||
#[test_case(0x88, 0x00, 0, register::SingleEightBit::B, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register b")]
|
||||
#[test_case(0x88, 0x00, 1, register::SingleEightBit::B, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register b")]
|
||||
#[test_case(0x88, 0x0F, 0, register::SingleEightBit::B, 0x1, 0, 1, 0; "no carry bit, half carry, register b")]
|
||||
#[test_case(0x88, 0x0F, 1, register::SingleEightBit::B, 0x1, 0, 1, 0; "carry bit, half carry, register b")]
|
||||
#[test_case(0x88, 0x0F, 0, register::SingleEightBit::B, 0x0F, 0, 1, 0; "no carry bit, full carry, register b")]
|
||||
#[test_case(0x88, 0x0F, 1, register::SingleEightBit::B, 0x0F, 0, 1, 0; "carry bit, full carry, register b")]
|
||||
#[test_case(0x88, 0xFF, 1, register::SingleEightBit::B, 0xFF, 0, 1, 1; "carry bit, both carries, register b")]
|
||||
// C register
|
||||
#[test_case(0x89, 0x12, 0, register::SingleEightBit::C, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register c")]
|
||||
#[test_case(0x89, 0x12, 1, register::SingleEightBit::C, 0x5, 0, 0, 0; "carry bit set, results in no flags, register c")]
|
||||
#[test_case(0x89, 0x00, 0, register::SingleEightBit::C, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register c")]
|
||||
#[test_case(0x89, 0x00, 1, register::SingleEightBit::C, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register c")]
|
||||
#[test_case(0x89, 0x0F, 0, register::SingleEightBit::C, 0x1, 0, 1, 0; "no carry bit, half carry, register c")]
|
||||
#[test_case(0x89, 0x0F, 1, register::SingleEightBit::C, 0x1, 0, 1, 0; "carry bit, half carry, register c")]
|
||||
#[test_case(0x89, 0x0F, 0, register::SingleEightBit::C, 0x0F, 0, 1, 0; "no carry bit, full carry, register c")]
|
||||
#[test_case(0x89, 0x0F, 1, register::SingleEightBit::C, 0x0F, 0, 1, 0; "carry bit, full carry, register c")]
|
||||
#[test_case(0x89, 0xFF, 1, register::SingleEightBit::C, 0xFF, 0, 1, 1; "carry bit, both carries, register c")]
|
||||
// D register
|
||||
#[test_case(0x8A, 0x12, 0, register::SingleEightBit::D, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register d")]
|
||||
#[test_case(0x8A, 0x12, 1, register::SingleEightBit::D, 0x5, 0, 0, 0; "carry bit set, results in no flags, register d")]
|
||||
#[test_case(0x8A, 0x00, 0, register::SingleEightBit::D, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register d")]
|
||||
#[test_case(0x8A, 0x00, 1, register::SingleEightBit::D, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register d")]
|
||||
#[test_case(0x8A, 0x0F, 0, register::SingleEightBit::D, 0x1, 0, 1, 0; "no carry bit, half carry, register d")]
|
||||
#[test_case(0x8A, 0x0F, 1, register::SingleEightBit::D, 0x1, 0, 1, 0; "carry bit, half carry, register d")]
|
||||
#[test_case(0x8A, 0x0F, 0, register::SingleEightBit::D, 0x0F, 0, 1, 0; "no carry bit, full carry, register d")]
|
||||
#[test_case(0x8A, 0x0F, 1, register::SingleEightBit::D, 0x0F, 0, 1, 0; "carry bit, full carry, register d")]
|
||||
#[test_case(0x8A, 0xFF, 1, register::SingleEightBit::D, 0xFF, 0, 1, 1; "carry bit, both carries, register d")]
|
||||
// E register
|
||||
#[test_case(0x8B, 0x12, 0, register::SingleEightBit::E, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register e")]
|
||||
#[test_case(0x8B, 0x12, 1, register::SingleEightBit::E, 0x5, 0, 0, 0; "carry bit set, results in no flags, register e")]
|
||||
#[test_case(0x8B, 0x00, 0, register::SingleEightBit::E, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register e")]
|
||||
#[test_case(0x8B, 0x00, 1, register::SingleEightBit::E, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register e")]
|
||||
#[test_case(0x8B, 0x0F, 0, register::SingleEightBit::E, 0x1, 0, 1, 0; "no carry bit, half carry, register e")]
|
||||
#[test_case(0x8B, 0x0F, 1, register::SingleEightBit::E, 0x1, 0, 1, 0; "carry bit, half carry, register e")]
|
||||
#[test_case(0x8B, 0x0F, 0, register::SingleEightBit::E, 0x0F, 0, 1, 0; "no carry bit, full carry, register e")]
|
||||
#[test_case(0x8B, 0x0F, 1, register::SingleEightBit::E, 0x0F, 0, 1, 0; "carry bit, full carry, register e")]
|
||||
#[test_case(0x8B, 0xFF, 1, register::SingleEightBit::E, 0xFF, 0, 1, 1; "carry bit, both carries, register e")]
|
||||
// H register
|
||||
#[test_case(0x8C, 0x12, 0, register::SingleEightBit::H, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register h")]
|
||||
#[test_case(0x8C, 0x12, 1, register::SingleEightBit::H, 0x5, 0, 0, 0; "carry bit set, results in no flags, register h")]
|
||||
#[test_case(0x8C, 0x00, 0, register::SingleEightBit::H, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register h")]
|
||||
#[test_case(0x8C, 0x00, 1, register::SingleEightBit::H, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register h")]
|
||||
#[test_case(0x8C, 0x0F, 0, register::SingleEightBit::H, 0x1, 0, 1, 0; "no carry bit, half carry, register h")]
|
||||
#[test_case(0x8C, 0x0F, 1, register::SingleEightBit::H, 0x1, 0, 1, 0; "carry bit, half carry, register h")]
|
||||
#[test_case(0x8C, 0x0F, 0, register::SingleEightBit::H, 0x0F, 0, 1, 0; "no carry bit, full carry, register h")]
|
||||
#[test_case(0x8C, 0x0F, 1, register::SingleEightBit::H, 0x0F, 0, 1, 0; "carry bit, full carry, register h")]
|
||||
#[test_case(0x8C, 0xFF, 1, register::SingleEightBit::H, 0xFF, 0, 1, 1; "carry bit, both carries, register h")]
|
||||
// L register
|
||||
#[test_case(0x8D, 0x12, 0, register::SingleEightBit::L, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register l")]
|
||||
#[test_case(0x8D, 0x12, 1, register::SingleEightBit::L, 0x5, 0, 0, 0; "carry bit set, results in no flags, register l")]
|
||||
#[test_case(0x8D, 0x00, 0, register::SingleEightBit::L, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register l")]
|
||||
#[test_case(0x8D, 0x00, 1, register::SingleEightBit::L, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register l")]
|
||||
#[test_case(0x8D, 0x0F, 0, register::SingleEightBit::L, 0x1, 0, 1, 0; "no carry bit, half carry, register l")]
|
||||
#[test_case(0x8D, 0x0F, 1, register::SingleEightBit::L, 0x1, 0, 1, 0; "carry bit, half carry, register l")]
|
||||
#[test_case(0x8D, 0x0F, 0, register::SingleEightBit::L, 0x0F, 0, 1, 0; "no carry bit, full carry, register l")]
|
||||
#[test_case(0x8D, 0x0F, 1, register::SingleEightBit::L, 0x0F, 0, 1, 0; "carry bit, full carry, register l")]
|
||||
#[test_case(0x8D, 0xFF, 1, register::SingleEightBit::L, 0xFF, 0, 1, 1; "carry bit, both carries, register l")]
|
||||
fn test_add_with_carry_to_a_flags(
|
||||
opcode: u8,
|
||||
initial_value: u8,
|
||||
carry_bit: u8,
|
||||
operand_register: register::SingleEightBit,
|
||||
operand: u8,
|
||||
expected_zero_flag: u8,
|
||||
expected_half_carry_flag: u8,
|
||||
expected_carry_flag: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = initial_value;
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(operand_register, operand);
|
||||
|
||||
// Set all the register to the opposite we expect to ensure they all get set
|
||||
set_opposite_of_expected_flags(
|
||||
&mut processor,
|
||||
(
|
||||
expected_zero_flag,
|
||||
0,
|
||||
expected_half_carry_flag,
|
||||
expected_carry_flag,
|
||||
),
|
||||
);
|
||||
|
||||
// ...except for the carry bit, which we must set for the test
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Carry, carry_bit);
|
||||
|
||||
let data = [opcode, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_flags_eq!(
|
||||
processor,
|
||||
(register::Flag::Zero, expected_zero_flag),
|
||||
(register::Flag::Subtract, 0),
|
||||
(register::Flag::HalfCarry, expected_half_carry_flag),
|
||||
(register::Flag::Carry, expected_carry_flag),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
use ferris_boi::{
|
||||
cpu::{instructions::RunnableInstruction, Processor},
|
||||
register,
|
||||
};
|
||||
|
||||
use test_case::test_case;
|
||||
|
||||
#[test_case(0x32, 0x64, 0x63)]
|
||||
#[test_case(0x22, 0x64, 0x65)]
|
||||
fn test_load_to_register_address_then_do_arithmetic(
|
||||
opcode: u8,
|
||||
hl_value_before: u16,
|
||||
hl_value_after: u16,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, hl_value_before);
|
||||
processor.registers.a = 10;
|
||||
|
||||
let data = [opcode, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(10, processor.memory.get(hl_value_before.into()).unwrap());
|
||||
assert_eq!(
|
||||
hl_value_after,
|
||||
processor
|
||||
.registers
|
||||
.get_combined_register(register::Combined::HL)
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x01, register::SixteenBit::Combined(register::Combined::BC))]
|
||||
#[test_case(0x11, register::SixteenBit::Combined(register::Combined::DE))]
|
||||
#[test_case(0x21, register::SixteenBit::Combined(register::Combined::HL))]
|
||||
#[test_case(
|
||||
0x31,
|
||||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer)
|
||||
)]
|
||||
fn test_load_16bit_immediate_to_register(opcode: u8, expected_dst_register: register::SixteenBit) {
|
||||
let mut processor = Processor::default();
|
||||
// The manual doesn't specify this is little endian, but from what I can gather
|
||||
// from googling, it should be.
|
||||
//
|
||||
// Note this excerpt from the Tetris disassembly
|
||||
// l05c0: ld hl,0ff02h ; 05c0 21 02 ff !..
|
||||
let data = [opcode, 0x34, 0x12, 0x05];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x05]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(
|
||||
0x1234,
|
||||
processor
|
||||
.registers
|
||||
.get_16bit_register(expected_dst_register)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_from_hl_to_sp() {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, 0x1234);
|
||||
let data = [0xF9, 0x00];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(0x1234, processor.registers.stack_pointer);
|
||||
}
|
||||
|
||||
#[test_case(0, 0xFF00)]
|
||||
#[test_case(16, 0xFF10)]
|
||||
#[test_case(-5, 0xFEFB)]
|
||||
#[test_case(127, 0xFF7F)]
|
||||
#[test_case(-127, 0xFE81)]
|
||||
#[test_case(-128, 0xFE80)]
|
||||
fn test_load_effective_address(value: i8, expected_sp: u16) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.set_16bit_register(
|
||||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
|
||||
0xFF00,
|
||||
);
|
||||
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
// The truncation will only happen if |value| > 128 in the branch where we check it's equal to -128
|
||||
// so we're fine.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let unsigned_value = if value >= 0 {
|
||||
value as u8
|
||||
} else if value == i8::MIN {
|
||||
i16::from(i8::MIN).abs() as u8
|
||||
} else {
|
||||
!(value.abs() as u8) + 1
|
||||
};
|
||||
let data = [0xF8, unsigned_value, 0x00];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(
|
||||
expected_sp,
|
||||
processor
|
||||
.registers
|
||||
.get_combined_register(register::Combined::HL)
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x0000, 0x00, 0, 0; "no addition")]
|
||||
#[test_case(0x00FF, 0x20, 0, 1; "overflow")]
|
||||
#[test_case(0x00C0, 0x7f, 0, 1; "overflow 2")]
|
||||
#[test_case(0x00FF, 0x01, 1, 1; "overflow with half carry")]
|
||||
#[test_case(0x000A, 0x0C, 1, 0; "half carry")]
|
||||
#[test_case(0x0018, -0x4, 1, 0; "negative half carry")]
|
||||
#[test_case(0x000F, -0x10, 1, 1; "underflow with half carry")]
|
||||
#[test_case(0x000E, -0x0F, 0, 1; "underflow with no half carry")]
|
||||
fn test_load_effective_address_flags(starting_sp: u16, add_value: i8, half_carry: u8, carry: u8) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.stack_pointer = starting_sp;
|
||||
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
// The truncation will only happen if |value| > 128 in the branch where we check it's equal to -128
|
||||
// so we're fine.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let unsigned_value = if add_value >= 0 {
|
||||
add_value as u8
|
||||
} else if add_value == i8::MIN {
|
||||
i16::from(i8::MIN).abs() as u8
|
||||
} else {
|
||||
!(add_value.abs() as u8) + 1
|
||||
};
|
||||
|
||||
let data = [0xF8, unsigned_value, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(
|
||||
half_carry,
|
||||
processor.registers.get_flag_bit(register::Flag::HalfCarry),
|
||||
"incorrect half carry bit"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
carry,
|
||||
processor.registers.get_flag_bit(register::Flag::Carry),
|
||||
"incorrect carry bit"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
0,
|
||||
processor.registers.get_flag_bit(register::Flag::Zero),
|
||||
"incorrect zero bit"
|
||||
);
|
||||
assert_eq!(
|
||||
0,
|
||||
processor.registers.get_flag_bit(register::Flag::Subtract),
|
||||
"incorrect subtract bit"
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0xF5, register::Combined::AF)]
|
||||
#[test_case(0xC5, register::Combined::BC)]
|
||||
#[test_case(0xD5, register::Combined::DE)]
|
||||
#[test_case(0xE5, register::Combined::HL)]
|
||||
fn test_stack_push(opcode: u8, src: register::Combined) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.set_combined_register(src, 0x1234);
|
||||
processor.registers.stack_pointer = 0xFFFE;
|
||||
let data = [opcode, 0x01];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
processor.run(&ins);
|
||||
|
||||
// we want to pop the LSB first (i.e. we write the MSB first)
|
||||
// https://rgbds.gbdev.io/docs/v0.5.2/gbz80.7#PUSH_r16
|
||||
assert_eq!(0x12, processor.memory.get(0xFFFE - 1).unwrap());
|
||||
assert_eq!(0x34, processor.memory.get(0xFFFE - 2).unwrap());
|
||||
assert_eq!(0xFFFE - 2, processor.registers.stack_pointer);
|
||||
}
|
||||
|
||||
#[test_case(0xF1, register::Combined::AF)]
|
||||
#[test_case(0xC1, register::Combined::BC)]
|
||||
#[test_case(0xD1, register::Combined::DE)]
|
||||
#[test_case(0xE1, register::Combined::HL)]
|
||||
fn test_stack_pop(opcode: u8, dst: register::Combined) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.stack_pointer = 0xFFF0;
|
||||
processor
|
||||
.memory
|
||||
.set_both(((0xFFF0, 0x34), (0xFFF1, 0x12)))
|
||||
.expect("failed to set values in memory");
|
||||
let data = [opcode, 0x01];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(0x1234, processor.registers.get_combined_register(dst));
|
||||
assert_eq!(0xFFF0 + 2, processor.registers.stack_pointer);
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
use ferris_boi::{
|
||||
cpu::{instructions::RunnableInstruction, Processor},
|
||||
register,
|
||||
};
|
||||
use test_case::test_case;
|
||||
|
||||
// lol going from a register to itself is kind of a weird thing to test
|
||||
#[test_case(0x7F, register::SingleEightBit::A, register::SingleEightBit::A)]
|
||||
#[test_case(0x78, register::SingleEightBit::A, register::SingleEightBit::B)]
|
||||
#[test_case(0x79, register::SingleEightBit::A, register::SingleEightBit::C)]
|
||||
#[test_case(0x7A, register::SingleEightBit::A, register::SingleEightBit::D)]
|
||||
#[test_case(0x7B, register::SingleEightBit::A, register::SingleEightBit::E)]
|
||||
#[test_case(0x7C, register::SingleEightBit::A, register::SingleEightBit::H)]
|
||||
#[test_case(0x7D, register::SingleEightBit::A, register::SingleEightBit::L)]
|
||||
#[test_case(0x40, register::SingleEightBit::B, register::SingleEightBit::B)]
|
||||
#[test_case(0x41, register::SingleEightBit::B, register::SingleEightBit::C)]
|
||||
#[test_case(0x42, register::SingleEightBit::B, register::SingleEightBit::D)]
|
||||
#[test_case(0x43, register::SingleEightBit::B, register::SingleEightBit::E)]
|
||||
#[test_case(0x44, register::SingleEightBit::B, register::SingleEightBit::H)]
|
||||
#[test_case(0x45, register::SingleEightBit::B, register::SingleEightBit::L)]
|
||||
#[test_case(0x48, register::SingleEightBit::C, register::SingleEightBit::B)]
|
||||
#[test_case(0x49, register::SingleEightBit::C, register::SingleEightBit::C)]
|
||||
#[test_case(0x4A, register::SingleEightBit::C, register::SingleEightBit::D)]
|
||||
#[test_case(0x4B, register::SingleEightBit::C, register::SingleEightBit::E)]
|
||||
#[test_case(0x4C, register::SingleEightBit::C, register::SingleEightBit::H)]
|
||||
#[test_case(0x4D, register::SingleEightBit::C, register::SingleEightBit::L)]
|
||||
#[test_case(0x50, register::SingleEightBit::D, register::SingleEightBit::B)]
|
||||
#[test_case(0x51, register::SingleEightBit::D, register::SingleEightBit::C)]
|
||||
#[test_case(0x52, register::SingleEightBit::D, register::SingleEightBit::D)]
|
||||
#[test_case(0x53, register::SingleEightBit::D, register::SingleEightBit::E)]
|
||||
#[test_case(0x54, register::SingleEightBit::D, register::SingleEightBit::H)]
|
||||
#[test_case(0x55, register::SingleEightBit::D, register::SingleEightBit::L)]
|
||||
#[test_case(0x58, register::SingleEightBit::E, register::SingleEightBit::B)]
|
||||
#[test_case(0x59, register::SingleEightBit::E, register::SingleEightBit::C)]
|
||||
#[test_case(0x5A, register::SingleEightBit::E, register::SingleEightBit::D)]
|
||||
#[test_case(0x5B, register::SingleEightBit::E, register::SingleEightBit::E)]
|
||||
#[test_case(0x5C, register::SingleEightBit::E, register::SingleEightBit::H)]
|
||||
#[test_case(0x5D, register::SingleEightBit::E, register::SingleEightBit::L)]
|
||||
#[test_case(0x60, register::SingleEightBit::H, register::SingleEightBit::B)]
|
||||
#[test_case(0x61, register::SingleEightBit::H, register::SingleEightBit::C)]
|
||||
#[test_case(0x62, register::SingleEightBit::H, register::SingleEightBit::D)]
|
||||
#[test_case(0x63, register::SingleEightBit::H, register::SingleEightBit::E)]
|
||||
#[test_case(0x64, register::SingleEightBit::H, register::SingleEightBit::H)]
|
||||
#[test_case(0x65, register::SingleEightBit::H, register::SingleEightBit::L)]
|
||||
#[test_case(0x68, register::SingleEightBit::L, register::SingleEightBit::B)]
|
||||
#[test_case(0x69, register::SingleEightBit::L, register::SingleEightBit::C)]
|
||||
#[test_case(0x6A, register::SingleEightBit::L, register::SingleEightBit::D)]
|
||||
#[test_case(0x6B, register::SingleEightBit::L, register::SingleEightBit::E)]
|
||||
#[test_case(0x6C, register::SingleEightBit::L, register::SingleEightBit::H)]
|
||||
#[test_case(0x6D, register::SingleEightBit::L, register::SingleEightBit::L)]
|
||||
#[test_case(0x47, register::SingleEightBit::B, register::SingleEightBit::A)]
|
||||
#[test_case(0x4F, register::SingleEightBit::C, register::SingleEightBit::A)]
|
||||
#[test_case(0x57, register::SingleEightBit::D, register::SingleEightBit::A)]
|
||||
#[test_case(0x5F, register::SingleEightBit::E, register::SingleEightBit::A)]
|
||||
#[test_case(0x67, register::SingleEightBit::H, register::SingleEightBit::A)]
|
||||
#[test_case(0x6F, register::SingleEightBit::L, register::SingleEightBit::A)]
|
||||
fn test_load_register(
|
||||
load_opcode: u8,
|
||||
dst_register: register::SingleEightBit,
|
||||
src_register: register::SingleEightBit,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(src_register, 0x45);
|
||||
|
||||
let data = [load_opcode, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
// assert our extra data after the instruction is returned
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
|
||||
processor.run(&ins);
|
||||
assert_eq!(
|
||||
0x45,
|
||||
processor.registers.get_single_8bit_register(dst_register)
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x3E, register::SingleEightBit::A)]
|
||||
#[test_case(0x06, register::SingleEightBit::B)]
|
||||
#[test_case(0x0E, register::SingleEightBit::C)]
|
||||
#[test_case(0x16, register::SingleEightBit::D)]
|
||||
#[test_case(0x1E, register::SingleEightBit::E)]
|
||||
#[test_case(0x26, register::SingleEightBit::H)]
|
||||
#[test_case(0x2E, register::SingleEightBit::L)]
|
||||
fn test_load_immediate(load_opcode: u8, expected_register: register::SingleEightBit) {
|
||||
let mut processor = Processor::default();
|
||||
let data = [load_opcode, 0x23, 0x00, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
// assert our extra data after the instruction is returned
|
||||
// this data is just garbage; no idea if it's a valid instruction
|
||||
assert_eq!(extra_data, &[0x00, 0x01]);
|
||||
|
||||
processor.run(&ins);
|
||||
assert_eq!(
|
||||
0x23,
|
||||
processor
|
||||
.registers
|
||||
.get_single_8bit_register(expected_register)
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x7E, register::Combined::HL, register::SingleEightBit::A)]
|
||||
#[test_case(0x46, register::Combined::HL, register::SingleEightBit::B)]
|
||||
#[test_case(0x4E, register::Combined::HL, register::SingleEightBit::C)]
|
||||
#[test_case(0x56, register::Combined::HL, register::SingleEightBit::D)]
|
||||
#[test_case(0x5E, register::Combined::HL, register::SingleEightBit::E)]
|
||||
#[test_case(0x66, register::Combined::HL, register::SingleEightBit::H)]
|
||||
#[test_case(0x6E, register::Combined::HL, register::SingleEightBit::L)]
|
||||
#[test_case(0x0A, register::Combined::BC, register::SingleEightBit::A)]
|
||||
#[test_case(0x1A, register::Combined::DE, register::SingleEightBit::A)]
|
||||
fn test_load_from_memory(
|
||||
opcode: u8,
|
||||
expected_deref_register: register::Combined,
|
||||
expected_register: register::SingleEightBit,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
let set_val = processor.memory.set(0xCCDD, 105);
|
||||
assert_eq!(105, set_val.unwrap());
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(expected_deref_register, 0xCCDD);
|
||||
|
||||
let data = [opcode, 0x10, 0x20];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse isntruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x10, 0x20]);
|
||||
|
||||
processor.run(&ins);
|
||||
assert_eq!(
|
||||
105,
|
||||
processor
|
||||
.registers
|
||||
.get_single_8bit_register(expected_register)
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x70, register::Combined::HL, register::SingleEightBit::B)]
|
||||
#[test_case(0x71, register::Combined::HL, register::SingleEightBit::C)]
|
||||
#[test_case(0x72, register::Combined::HL, register::SingleEightBit::D)]
|
||||
#[test_case(0x73, register::Combined::HL, register::SingleEightBit::E)]
|
||||
#[test_case(0x74, register::Combined::HL, register::SingleEightBit::H)]
|
||||
#[test_case(0x75, register::Combined::HL, register::SingleEightBit::L)]
|
||||
#[test_case(0x02, register::Combined::BC, register::SingleEightBit::A)]
|
||||
#[test_case(0x12, register::Combined::DE, register::SingleEightBit::A)]
|
||||
#[test_case(0x77, register::Combined::HL, register::SingleEightBit::A)]
|
||||
fn test_load_to_memory(
|
||||
opcode: u8,
|
||||
expected_deref_register: register::Combined,
|
||||
expected_register: register::SingleEightBit,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
// This test does a bit of a sneaky - because we want to use CC as the high/low addresses
|
||||
// in the H/L variants of these tests, but we also want to use it store addreses, we must
|
||||
// be careful that the stored immediate value is also part of the address in these cases.
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(expected_register, 0xCC);
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(expected_deref_register, 0xCCCC);
|
||||
|
||||
let data = [opcode, 0x10, 0x20];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x10, 0x20]);
|
||||
|
||||
processor.run(&ins);
|
||||
assert_eq!(0xCC, processor.memory.get(0xCCCC).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_immediate_to_memory() {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, 0xCCDE);
|
||||
|
||||
let data = [0x36, 0x1D, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(0x1D, processor.memory.get(0xCCDE).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_from_immediate_address() {
|
||||
let mut processor = Processor::default();
|
||||
processor.memory.set(0xCCDE, 105);
|
||||
|
||||
let data = [0xFA, 0xDE, 0xCC, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(105, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_to_immadiate_address() {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = 105;
|
||||
let data = [0xEA, 0xDE, 0xCC, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(105, processor.memory.get(0xCCDE).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_from_io_register_zone() {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.memory
|
||||
.set(0xFF64, 10)
|
||||
.expect("could not set memory value");
|
||||
processor.registers.c = 0x64;
|
||||
|
||||
let data = [0xF2, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(10, processor.registers.a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_to_io_register_zone() {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.c = 0x64;
|
||||
processor.registers.a = 10;
|
||||
|
||||
let data = [0xE2, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(10, processor.memory.get(0xFF64).unwrap());
|
||||
}
|
||||
|
||||
#[test_case(0x3A, 0x64, 0x63)]
|
||||
#[test_case(0x2A, 0x64, 0x65)]
|
||||
fn test_load_from_register_address_then_do_arithmetic(
|
||||
opcode: u8,
|
||||
hl_value_before: u16,
|
||||
hl_value_after: u16,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(register::Combined::HL, hl_value_before);
|
||||
processor.memory.set(hl_value_before.into(), 10);
|
||||
|
||||
let data = [opcode, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
assert_eq!(10, processor.registers.a);
|
||||
assert_eq!(
|
||||
hl_value_after,
|
||||
processor
|
||||
.registers
|
||||
.get_combined_register(register::Combined::HL)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_to_io_register_zone_by_immediate() {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = 0xAF;
|
||||
|
||||
let data = [0xE0, 0x05, 0x06];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x06]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(0xAF, processor.memory.get(0xFF05).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_from_io_register_zone_by_immediate() {
|
||||
let mut processor = Processor::default();
|
||||
processor.memory.set(0xFF05, 0xAF);
|
||||
|
||||
let data = [0xF0, 0x05, 0x06];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x06]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(0xAF, processor.registers.a);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
mod arith8;
|
||||
mod load16;
|
||||
mod load8;
|
Loading…
Reference in New Issue