use ferris_boi::{ cpu::{instructions::Instruction, Processor}, register, }; use crate::testutil; 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) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x00]); processor.run_instruction(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) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x05]); processor.run_instruction(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) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x00]); processor.run_instruction(ins); assert_eq!(0x1234, processor.registers.stack_pointer); } #[test_case(0xFF00, 0, 0xFF00)] #[test_case(0xFF00, 16, 0xFF10)] #[test_case(0xFF00, -5, 0xFEFB)] #[test_case(0xFF00, 127, 0xFF7F)] #[test_case(0xFF00, -127, 0xFE81)] #[test_case(0xFF00, -128, 0xFE80)] #[test_case(0xFFFF, 10, 0x0009)] fn test_load_effective_address(start_sp: u16, value: i8, expected_sp: u16) { let mut processor = Processor::default(); processor.registers.set_16bit_register( register::SixteenBit::Single(register::SingleSixteenBit::StackPointer), start_sp, ); let unsigned_value = if value >= 0 { value as u8 } else if value == i8::MIN { i8::MIN.unsigned_abs() } else { !(value.unsigned_abs()) + 1 }; let data = [0xF8, unsigned_value, 0x00]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x00]); processor.run_instruction(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, 1; "negative both carry")] #[test_case(0x000F, -0x01, 1, 1; "underflow with half carry")] #[test_case(0x00FE, -0x0F, 0, 1; "underflow with no half carry")] #[test_case(0xFFFF, 0xA, 1, 1; "16 bit overflow results in both carries")] 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; let unsigned_value = if add_value >= 0 { add_value as u8 } else if add_value == i8::MIN { i8::MIN.unsigned_abs() } else { !(add_value.unsigned_abs()) + 1 }; let data = [0xF8, unsigned_value, 0x00]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x00]); processor.run_instruction(ins); testutil::assert_flags_eq!( processor, (register::Flag::HalfCarry, half_carry), (register::Flag::Carry, carry), (register::Flag::Zero, 0), (register::Flag::Subtract, 0), ); } #[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) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x01]); processor.run_instruction(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] fn test_stack_push_from_af() { let mut processor = Processor::default(); processor .registers .set_combined_register(register::Combined::AF, 0x1234); processor.registers.stack_pointer = 0xFFFE; let data = [0xF5, 0x01]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x01]); processor.run_instruction(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!(0x30, processor.memory.get(0xFFFE - 2).unwrap()); assert_eq!(0xFFFE - 2, processor.registers.stack_pointer); } #[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) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x01]); processor.run_instruction(ins); assert_eq!(0x1234, processor.registers.get_combined_register(dst)); assert_eq!(0xFFF0 + 2, processor.registers.stack_pointer); } #[test] fn test_stack_pop_to_af() { 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 = [0xF1, 0x01]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x01]); processor.run_instruction(ins); assert_eq!( 0x1230, processor .registers .get_combined_register(register::Combined::AF) ); assert_eq!(0xFFF0 + 2, processor.registers.stack_pointer); }