241 lines
7.7 KiB
Rust
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);
|
|
}
|