use ferris_boi::{ cpu::{instructions::Instruction, Processor}, register::{self, Flag}, }; use test_case::{test_case, test_matrix}; #[test] fn test_can_jump_to_immediate() { let mut processor = Processor::default(); let data = [0xC3, 0x37, 0x13, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); assert_eq!(0x1337, processor.registers.program_counter); } #[test] fn test_can_jump_to_address_in_hl() { let mut processor = Processor::default(); processor .registers .set_combined_register(register::Combined::HL, 0x1337); let data = [0xE9, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); assert_eq!(0x1337, processor.registers.program_counter); } #[test] fn test_call_adjusts_program_counter_to_given_value() { let mut processor = Processor::default(); let data = [0xCD, 0x37, 0x13, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); assert_eq!(0x1337, processor.registers.program_counter); } #[test] fn test_call_pushes_pc_onto_stack() { let mut processor = Processor::default(); processor.registers.program_counter = 0xBEEF; let data = [0xCD, 0x37, 0x13, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); let old_sp = processor.registers.stack_pointer; processor.run_instruction(ins); assert_eq!(0xBE, processor.memory.get((old_sp - 1).into()).unwrap()); // HACK: This SHOULD be F2 in reality (0xEF + 3, the size of the call instruction) // however since run_instruction does not advance the PC, we need to adjust our test assert_eq!(0xEF, processor.memory.get((old_sp - 2).into()).unwrap()); assert_eq!(old_sp - 2, processor.registers.stack_pointer); } #[test_case(0xC7, 0x00)] #[test_case(0xCF, 0x08)] #[test_case(0xD7, 0x10)] #[test_case(0xDF, 0x18)] #[test_case(0xE7, 0x20)] #[test_case(0xEF, 0x28)] #[test_case(0xF7, 0x30)] #[test_case(0xFF, 0x38)] fn test_restart_adjusts_program_counter_to_encoded_value(opcode: u8, addr: u8) { let mut processor = Processor::default(); let data = [opcode, 0x13]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x13]); processor.run_instruction(ins); assert_eq!(u16::from(addr), processor.registers.program_counter); } #[test_case(0xC7)] #[test_case(0xCF)] #[test_case(0xD7)] #[test_case(0xDF)] #[test_case(0xE7)] #[test_case(0xEF)] #[test_case(0xF7)] #[test_case(0xFF)] fn test_restart_adjusts_pushes_current_program_counter_to_stack(opcode: u8) { let mut processor = Processor::default(); processor.registers.program_counter = 0xDEAD; let data = [opcode, 0x13]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x13]); let old_sp = processor.registers.stack_pointer; processor.run_instruction(ins); assert_eq!(0xDE, processor.memory.get((old_sp - 1).into()).unwrap()); // HACK: This SHOULD be AE in reality (0xAD + 1, the size of the rst instruction) // however since run_instruction does not advance the PC, we need to adjust our test assert_eq!(0xAD, processor.memory.get((old_sp - 2).into()).unwrap()); assert_eq!(old_sp - 2, processor.registers.stack_pointer); } #[test_case(0xCC, Flag::Zero, 1; "CALL Z")] #[test_case(0xDC, Flag::Carry, 1; "CALL C")] #[test_case(0xC4, Flag::Zero, 0; "CALL NZ")] #[test_case(0xD4, Flag::Carry, 0; "CALL NC")] #[test_case(0xCA, Flag::Zero, 1; "JP Z")] #[test_case(0xDA, Flag::Carry, 1; "JP C")] #[test_case(0xC2, Flag::Zero, 0; "JP NZ")] #[test_case(0xD2, Flag::Carry, 0; "JP NC")] fn test_call_and_jump_jumps_if_condition_matches(opcode: u8, flag: Flag, expected_value: u8) { let mut processor = Processor::default(); processor.registers.set_flag_bit(flag, expected_value); let data = [opcode, 0x37, 0x13, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); assert_eq!(0x1337, processor.registers.program_counter); } #[test_case(0xCC, Flag::Zero, 1; "CALL Z")] #[test_case(0xDC, Flag::Carry, 1; "CALL C")] #[test_case(0xC4, Flag::Zero, 0; "CALL NZ")] #[test_case(0xD4, Flag::Carry, 0; "CALL NC")] fn test_call_grows_stack_if_condition_matches(opcode: u8, flag: Flag, expected_value: u8) { let mut processor = Processor::default(); processor.registers.set_flag_bit(flag, expected_value); let data = [opcode, 0x37, 0x13, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); let old_sp = processor.registers.stack_pointer; processor.run_instruction(ins); assert_eq!(old_sp - 2, processor.registers.stack_pointer); } #[test_case(0xCC, Flag::Zero, 1; "CALL Z")] #[test_case(0xDC, Flag::Carry, 1; "CALL C")] #[test_case(0xC4, Flag::Zero, 0; "CALL NZ")] #[test_case(0xD4, Flag::Carry, 0; "CALL NC")] #[test_case(0xCA, Flag::Zero, 1; "JP Z")] #[test_case(0xDA, Flag::Carry, 1; "JP C")] #[test_case(0xC2, Flag::Zero, 0; "JP NZ")] #[test_case(0xD2, Flag::Carry, 0; "JP NC")] fn test_call_and_jump_does_nothing_if_condition_fails(opcode: u8, flag: Flag, expected_value: u8) { let mut processor = Processor::default(); processor .registers .set_flag_bit(flag, if expected_value == 0 { 1 } else { 0 }); let data = [opcode, 0x37, 0x13, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); let old_sp = processor.registers.stack_pointer; let old_pc = processor.registers.program_counter; processor.run_instruction(ins); assert_eq!(old_sp, processor.registers.stack_pointer); // run_instruction does not advance the program counter, so this will be the same. Normally it would be old_pc plus 3 assert_eq!(old_pc, processor.registers.program_counter); } #[test] fn test_jump_to_same_addr_does_not_advance_pc() { let mut processor = Processor::default(); processor.registers.program_counter = 0x1337; [0xC3, 0x37, 0x13] .iter() .copied() .enumerate() .for_each(|(idx, opcode)| { processor .memory .set( usize::from(processor.registers.program_counter) + idx, opcode, ) .expect("could not program data"); }); processor.run_next_instruction(); assert_eq!(0x1337, processor.registers.program_counter); } #[test_case(10)] #[test_case(0)] #[test_case(-8)] fn test_jump_relative_jumps_by_the_given_number_of_bytes(distance: i8) { let mut processor = Processor::default(); let starting_pc = processor.registers.program_counter; let data = [0x18, i8::to_be_bytes(distance)[0], 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); let expected_pc = if distance > 0 { starting_pc + u16::from(distance.unsigned_abs()) } else { starting_pc - u16::from(distance.unsigned_abs()) }; assert_eq!(expected_pc, processor.registers.program_counter); } #[test_matrix( [ (0x20, Flag::Zero, 0), (0x30, Flag::Carry, 0), (0x28, Flag::Zero, 1), (0x38, Flag::Carry, 1), ], [10, 0, -8] )] fn test_jump_relative_jumps_by_the_given_number_of_bytes_if_condition_matches( (opcode, flag, flag_value): (u8, Flag, u8), distance: i8, ) { let mut processor = Processor::default(); processor.registers.set_flag_bit(flag, flag_value); let starting_pc = processor.registers.program_counter; let data = [opcode, i8::to_be_bytes(distance)[0], 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); let expected_pc = if distance > 0 { starting_pc + u16::from(distance.unsigned_abs()) } else { starting_pc - u16::from(distance.unsigned_abs()) }; assert_eq!(expected_pc, processor.registers.program_counter); } #[test_matrix( [ (0x20, Flag::Zero, 0), (0x30, Flag::Carry, 0), (0x28, Flag::Zero, 1), (0x38, Flag::Carry, 1), ], [10, 0, -8] )] fn test_jump_relative_does_nothing_if_condition_does_not_match( (opcode, flag, flag_value): (u8, Flag, u8), distance: i8, ) { let mut processor = Processor::default(); processor .registers .set_flag_bit(flag, if flag_value == 0 { 1 } else { 0 }); let starting_pc = processor.registers.program_counter; let data = [opcode, i8::to_be_bytes(distance)[0], 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); // Would normally advance, but run_instruction does not advance PC assert_eq!(starting_pc, processor.registers.program_counter); } #[test_case(0xC9; "RET")] #[test_case(0xD9; "RETI")] fn test_ret_jumps_to_address_on_stack(opcode: u8) { let mut processor = Processor::default(); let starting_sp = processor.registers.stack_pointer; processor .memory .set_both(( ((starting_sp - 1).into(), 0x13), ((starting_sp - 2).into(), 0x37), )) .expect("failed to write to stack"); processor.registers.stack_pointer -= 2; let data = [opcode, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); assert_eq!(0x1337, processor.registers.program_counter); } #[test_case(0xC9; "RET")] #[test_case(0xD9; "RETI")] fn test_ret_moves_sp_two_bytes(opcode: u8) { let mut processor = Processor::default(); let starting_sp = processor.registers.stack_pointer; processor .memory .set_both(( ((starting_sp - 1).into(), 0x13), ((starting_sp - 2).into(), 0x37), )) .expect("failed to write to stack"); processor.registers.stack_pointer -= 2; let data = [opcode, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); assert_eq!(starting_sp, processor.registers.stack_pointer); } #[test] fn test_reti_enables_interrupts_immediately() { let mut processor = Processor::default(); let starting_sp = processor.registers.stack_pointer; processor .memory .set_both(( ((starting_sp - 1).into(), 0x13), ((starting_sp - 2).into(), 0x37), )) .expect("failed to write to stack"); processor.disable_interrupts(); processor.registers.stack_pointer -= 2; let data = [0xD9, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); assert!(processor.interrupts_enabled()); } #[test_case(0xC8, Flag::Zero, 1; "RET Z")] #[test_case(0xD8, Flag::Carry, 1; "RET C")] #[test_case(0xC0, Flag::Zero, 0; "RET NZ")] #[test_case(0xD0, Flag::Carry, 0; "RET NC")] fn test_conditional_ret_jumps_to_address_on_stack(opcode: u8, flag: Flag, expected_value: u8) { let mut processor = Processor::default(); processor.registers.set_flag_bit(flag, expected_value); let starting_sp = processor.registers.stack_pointer; processor .memory .set_both(( ((starting_sp - 1).into(), 0xBE), ((starting_sp - 2).into(), 0xEF), )) .expect("failed to write to stack"); processor.registers.stack_pointer -= 2; let data = [opcode, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); assert_eq!(0xBEEF, processor.registers.program_counter); } #[test_case(0xC8, Flag::Zero, 1; "RET Z")] #[test_case(0xD8, Flag::Carry, 1; "RET C")] #[test_case(0xC0, Flag::Zero, 0; "RET NZ")] #[test_case(0xD0, Flag::Carry, 0; "RET NC")] fn test_conditional_ret_does_nothing_if_condition_fails( opcode: u8, flag: Flag, expected_value: u8, ) { let mut processor = Processor::default(); processor .registers .set_flag_bit(flag, if expected_value == 0 { 1 } else { 0 }); let starting_pc = processor.registers.program_counter; let starting_sp = processor.registers.stack_pointer; processor .memory .set_both(( ((starting_sp - 1).into(), 0xBE), ((starting_sp - 2).into(), 0xEF), )) .expect("failed to write to stack"); processor.registers.stack_pointer -= 2; let data = [opcode, 0x06]; let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); assert_eq!(extra_data, &[0x06]); processor.run_instruction(ins); // run_instruction does not advance the program_counter. Normally this would add one assert_eq!(starting_pc, processor.registers.program_counter); assert_eq!(starting_sp - 2, processor.registers.stack_pointer); }