Add BIT instruction support

master
Nick Krichevsky 2023-12-06 19:48:54 -05:00
parent 70c5766f92
commit 7e44edeb24
68 changed files with 262 additions and 10 deletions

View File

@ -19,4 +19,8 @@ pub enum BitManipulationInstruction {
operand: Operand,
bit: u8,
},
TestIndividualBit {
operand: Operand,
bit: u8,
},
}

View File

@ -19,7 +19,9 @@ impl OpcodeParser for Parser {
fn parse_opcode(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
parse_set_opcode(opcode).or_parse(parse_clear_opcode)
parse_set_opcode(opcode)
.or_parse(parse_clear_opcode)
.or_parse(parse_bit_test_opcode)
}
}
@ -31,11 +33,32 @@ fn parse_clear_opcode(opcode: Opcode) -> ParseResult {
parse_individual_bit_operation(opcode, IndividualBitOperation::Clear, 0xCB_80..=0xCB_BF)
}
fn parse_bit_test_opcode(opcode: Opcode) -> ParseResult {
let (operand, bit) = decompose_bit_action_opcode(opcode, 0xCB_40..=0xCB_7F)?;
let ins = BitManipulationInstruction::TestIndividualBit { operand, bit };
Ok((Instruction::BitManipulation(ins), 2))
}
fn parse_individual_bit_operation(
opcode: Opcode,
operation: IndividualBitOperation,
opcode_range: RangeInclusive<u16>,
) -> ParseResult {
let (operand, bit) = decompose_bit_action_opcode(opcode, opcode_range)?;
let ins = BitManipulationInstruction::ManipulateIndividualBit {
operation,
operand,
bit,
};
Ok((Instruction::BitManipulation(ins), 2))
}
fn decompose_bit_action_opcode(
opcode: Opcode,
opcode_range: RangeInclusive<u16>,
) -> Result<(Operand, u8), parse::Error> {
let Opcode::SixteenBit(sixteen_bit_opcode) = opcode else {
return Err(parse::Error::UnknownOpcode(opcode));
};
@ -44,15 +67,10 @@ fn parse_individual_bit_operation(
return Err(parse::Error::UnknownOpcode(opcode));
}
let bit = bit_for_opcode(sixteen_bit_opcode, opcode_range)?;
let operand = operand_for_opcode(sixteen_bit_opcode)?;
let ins = BitManipulationInstruction::ManipulateIndividualBit {
operation,
operand,
bit,
};
let bit = bit_for_opcode(sixteen_bit_opcode, opcode_range)?;
Ok((Instruction::BitManipulation(ins), 2))
Ok((operand, bit))
}
fn operand_for_opcode(opcode: u16) -> Result<Operand, parse::Error> {

View File

@ -33,6 +33,24 @@ impl Run for BitManipulationInstruction {
Ok(Cycles(16))
}
BitManipulationInstruction::TestIndividualBit {
operand: Operand::Register(register),
bit,
} => {
set_test_flag_bits(processor, Operand::Register(register), bit)?;
Ok(Cycles(8))
}
BitManipulationInstruction::TestIndividualBit {
operand: Operand::HLValue,
bit,
} => {
set_test_flag_bits(processor, Operand::HLValue, bit)?;
Ok(Cycles(12))
}
}
}
}
@ -106,3 +124,53 @@ fn transform_hl_value<F: Fn(u8) -> u8>(
Ok(())
}
fn set_test_flag_bits(
processor: &mut Processor,
operand: Operand,
bit: u8,
) -> Result<(), run::Error> {
assert!(bit < 8, "Cannot test a bit greater than 7");
let operand_val = get_operand_value(processor, operand)?;
let compare_mask = 0x1 << bit;
let zero_flag = (compare_mask & operand_val) == 0;
processor
.registers
.set_flag_bit(register::Flag::Zero, zero_flag.into());
processor
.registers
.set_flag_bit(register::Flag::Subtract, 0);
processor
.registers
.set_flag_bit(register::Flag::HalfCarry, 1);
Ok(())
}
fn get_operand_value(processor: &mut Processor, operand: Operand) -> Result<u8, run::Error> {
let val = match operand {
Operand::Register(register) => processor.registers.get_single_8bit_register(register),
Operand::HLValue => {
let hl_addr = processor
.registers
.get_combined_register(register::Combined::HL);
#[allow(clippy::match_wildcard_for_single_variants)]
processor
.memory
.get(hl_addr.into())
.map_err(|err| match err {
memory::Error::GetInvalidAddress(addr) => run::Error::InvalidRegisterAddress(
register::SixteenBit::Combined(register::Combined::HL),
addr,
),
err => run::Error::Unknown(Box::new(err)),
})?
}
};
Ok(val)
}

View File

@ -1,8 +1,10 @@
use ferris_boi::{
cpu::{instructions::Instruction, Processor},
register::{self, SingleEightBit},
register::{self, Flag, SingleEightBit},
};
use test_case::test_case;
use test_case::{test_case, test_matrix};
use crate::testutil;
#[test_case(0xC0, register::SingleEightBit::B, 0b00000001)]
#[test_case(0xC8, register::SingleEightBit::B, 0b00000010)]
@ -234,3 +236,163 @@ fn test_clear_hl_value_bit(opcode_variant: u8, expected: u8) {
.expect("failed to get memory value")
);
}
#[test_matrix(
[0, 1],
[
(0x40, SingleEightBit::B, 0),
(0x41, SingleEightBit::C, 0),
(0x42, SingleEightBit::D, 0),
(0x43, SingleEightBit::E, 0),
(0x44, SingleEightBit::H, 0),
(0x45, SingleEightBit::L, 0),
(0x47, SingleEightBit::A, 0),
(0x48, SingleEightBit::B, 1),
(0x49, SingleEightBit::C, 1),
(0x4A, SingleEightBit::D, 1),
(0x4B, SingleEightBit::E, 1),
(0x4C, SingleEightBit::H, 1),
(0x4D, SingleEightBit::L, 1),
(0x4F, SingleEightBit::A, 1),
(0x50, SingleEightBit::B, 2),
(0x51, SingleEightBit::C, 2),
(0x52, SingleEightBit::D, 2),
(0x53, SingleEightBit::E, 2),
(0x54, SingleEightBit::H, 2),
(0x55, SingleEightBit::L, 2),
(0x57, SingleEightBit::A, 2),
(0x58, SingleEightBit::B, 3),
(0x59, SingleEightBit::C, 3),
(0x5A, SingleEightBit::D, 3),
(0x5B, SingleEightBit::E, 3),
(0x5C, SingleEightBit::H, 3),
(0x5D, SingleEightBit::L, 3),
(0x5F, SingleEightBit::A, 3),
(0x60, SingleEightBit::B, 4),
(0x61, SingleEightBit::C, 4),
(0x62, SingleEightBit::D, 4),
(0x63, SingleEightBit::E, 4),
(0x64, SingleEightBit::H, 4),
(0x65, SingleEightBit::L, 4),
(0x67, SingleEightBit::A, 4),
(0x68, SingleEightBit::B, 5),
(0x69, SingleEightBit::C, 5),
(0x6A, SingleEightBit::D, 5),
(0x6B, SingleEightBit::E, 5),
(0x6C, SingleEightBit::H, 5),
(0x6D, SingleEightBit::L, 5),
(0x6F, SingleEightBit::A, 5),
(0x70, SingleEightBit::B, 6),
(0x71, SingleEightBit::C, 6),
(0x72, SingleEightBit::D, 6),
(0x73, SingleEightBit::E, 6),
(0x74, SingleEightBit::H, 6),
(0x75, SingleEightBit::L, 6),
(0x77, SingleEightBit::A, 6),
(0x78, SingleEightBit::B, 7),
(0x79, SingleEightBit::C, 7),
(0x7A, SingleEightBit::D, 7),
(0x7B, SingleEightBit::E, 7),
(0x7C, SingleEightBit::H, 7),
(0x7D, SingleEightBit::L, 7),
(0x7F, SingleEightBit::A, 7),
]
)]
fn test_test_register_bit_sets_zero_flag_to_opposite_of_bit(
bit_value: u8,
(opcode_variant, register, bit): (u8, SingleEightBit, u8),
) {
let mut processor = Processor::default();
let expected_zero_flag = if bit_value == 0 { 1 } else { 0 };
// Set all the register to the opposite we expect to ensure they all get set
testutil::set_opposite_of_expected_flags(
&mut processor,
(
expected_zero_flag,
0,
1,
// value of carry does not matter
0,
),
);
processor
.registers
.set_single_8bit_register(register, bit_value << bit);
let data = [0xCB, opcode_variant, 0x02];
let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x02]);
processor.run_instruction(ins);
testutil::assert_flags_eq!(
processor,
(Flag::Zero, expected_zero_flag),
(Flag::Subtract, 0),
(Flag::HalfCarry, 1)
);
}
#[test_matrix(
[0, 1],
[
(0x46, 0),
(0x4e, 1),
(0x56, 2),
(0x5e, 3),
(0x66, 4),
(0x6e, 5),
(0x76, 6),
(0x7e, 7),
]
)]
fn test_test_hl_value_bit_sets_zero_flag_to_opposite_of_bit(
bit_value: u8,
(opcode_variant, bit): (u8, u8),
) {
let mut processor = Processor::default();
let expected_zero_flag = if bit_value == 0 { 1 } else { 0 };
// Set all the register to the opposite we expect to ensure they all get set
testutil::set_opposite_of_expected_flags(
&mut processor,
(
expected_zero_flag,
0,
1,
// value of carry does not matter
0,
),
);
processor
.memory
.set(0xFF00, bit_value << bit)
.expect("failed to set memory value");
processor
.registers
.set_combined_register(register::Combined::HL, 0xFF00);
let data = [0xCB, opcode_variant, 0x02];
let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x02]);
processor.run_instruction(ins);
testutil::assert_flags_eq!(
processor,
(Flag::Zero, expected_zero_flag),
(Flag::Subtract, 0),
(Flag::HalfCarry, 1)
);
}