ferris-boi/tests/cpu/load16.rs

241 lines
7.7 KiB
Rust

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