Add BIT instruction support
parent
70c5766f92
commit
7e44edeb24
|
@ -19,4 +19,8 @@ pub enum BitManipulationInstruction {
|
||||||
operand: Operand,
|
operand: Operand,
|
||||||
bit: u8,
|
bit: u8,
|
||||||
},
|
},
|
||||||
|
TestIndividualBit {
|
||||||
|
operand: Operand,
|
||||||
|
bit: u8,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,9 @@ impl OpcodeParser for Parser {
|
||||||
fn parse_opcode(data: &View) -> ParseResult {
|
fn parse_opcode(data: &View) -> ParseResult {
|
||||||
let opcode = parse::get_opcode_from_data(data);
|
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)
|
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(
|
fn parse_individual_bit_operation(
|
||||||
opcode: Opcode,
|
opcode: Opcode,
|
||||||
operation: IndividualBitOperation,
|
operation: IndividualBitOperation,
|
||||||
opcode_range: RangeInclusive<u16>,
|
opcode_range: RangeInclusive<u16>,
|
||||||
) -> ParseResult {
|
) -> 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 {
|
let Opcode::SixteenBit(sixteen_bit_opcode) = opcode else {
|
||||||
return Err(parse::Error::UnknownOpcode(opcode));
|
return Err(parse::Error::UnknownOpcode(opcode));
|
||||||
};
|
};
|
||||||
|
@ -44,15 +67,10 @@ fn parse_individual_bit_operation(
|
||||||
return Err(parse::Error::UnknownOpcode(opcode));
|
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 operand = operand_for_opcode(sixteen_bit_opcode)?;
|
||||||
let ins = BitManipulationInstruction::ManipulateIndividualBit {
|
let bit = bit_for_opcode(sixteen_bit_opcode, opcode_range)?;
|
||||||
operation,
|
|
||||||
operand,
|
|
||||||
bit,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((Instruction::BitManipulation(ins), 2))
|
Ok((operand, bit))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operand_for_opcode(opcode: u16) -> Result<Operand, parse::Error> {
|
fn operand_for_opcode(opcode: u16) -> Result<Operand, parse::Error> {
|
||||||
|
|
|
@ -33,6 +33,24 @@ impl Run for BitManipulationInstruction {
|
||||||
|
|
||||||
Ok(Cycles(16))
|
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(())
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use ferris_boi::{
|
use ferris_boi::{
|
||||||
cpu::{instructions::Instruction, Processor},
|
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(0xC0, register::SingleEightBit::B, 0b00000001)]
|
||||||
#[test_case(0xC8, register::SingleEightBit::B, 0b00000010)]
|
#[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")
|
.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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue