From 321ab461ff932b9d197eed58a788fe11f8fc4287 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Sat, 15 Apr 2023 19:00:50 -0400 Subject: [PATCH] Move all CPU tests into integration test directory --- src/cpu.rs | 924 +------------------------------------------- src/lib.rs | 4 +- tests/cpu/arith8.rs | 387 +++++++++++++++++++ tests/cpu/load16.rs | 222 +++++++++++ tests/cpu/load8.rs | 312 +++++++++++++++ tests/cpu/main.rs | 3 + 6 files changed, 934 insertions(+), 918 deletions(-) create mode 100644 tests/cpu/arith8.rs create mode 100644 tests/cpu/load16.rs create mode 100644 tests/cpu/load8.rs create mode 100644 tests/cpu/main.rs diff --git a/src/cpu.rs b/src/cpu.rs index 45e9d65..7a6e5f5 100644 --- a/src/cpu.rs +++ b/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), - ); - } -} diff --git a/src/lib.rs b/src/lib.rs index 912918d..760f0cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![warn(clippy::all, clippy::pedantic)] +pub mod cpu; mod memory; -mod register; -mod cpu; +pub mod register; diff --git a/tests/cpu/arith8.rs b/tests/cpu/arith8.rs new file mode 100644 index 0000000..22b01e5 --- /dev/null +++ b/tests/cpu/arith8.rs @@ -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), + ); +} diff --git a/tests/cpu/load16.rs b/tests/cpu/load16.rs new file mode 100644 index 0000000..f823dbd --- /dev/null +++ b/tests/cpu/load16.rs @@ -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); +} diff --git a/tests/cpu/load8.rs b/tests/cpu/load8.rs new file mode 100644 index 0000000..bbed723 --- /dev/null +++ b/tests/cpu/load8.rs @@ -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); +} diff --git a/tests/cpu/main.rs b/tests/cpu/main.rs new file mode 100644 index 0000000..4ee0b8f --- /dev/null +++ b/tests/cpu/main.rs @@ -0,0 +1,3 @@ +mod arith8; +mod load16; +mod load8;