Move all CPU tests into integration test directory

jsmoo
Nick Krichevsky 2023-04-15 19:00:50 -04:00
parent 2caf7fe7e8
commit 321ab461ff
6 changed files with 934 additions and 918 deletions

View File

@ -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),
);
}
}

View File

@ -1,5 +1,5 @@
#![warn(clippy::all, clippy::pedantic)]
pub mod cpu;
mod memory;
mod register;
mod cpu;
pub mod register;

387
tests/cpu/arith8.rs Normal file
View File

@ -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),
);
}

222
tests/cpu/load16.rs Normal file
View File

@ -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);
}

312
tests/cpu/load8.rs Normal file
View File

@ -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);
}

3
tests/cpu/main.rs Normal file
View File

@ -0,0 +1,3 @@
mod arith8;
mod load16;
mod load8;