Implement DAA instruction
parent
3a91f76f1e
commit
f9cdccb5fd
|
@ -6,4 +6,5 @@ pub enum MiscInstruction {
|
||||||
SetCarryFlag,
|
SetCarryFlag,
|
||||||
ComplementCarryFlag,
|
ComplementCarryFlag,
|
||||||
ComplementARegister,
|
ComplementARegister,
|
||||||
|
DecimalAdjustAccumulator,
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ impl OpcodeParser for Parser {
|
||||||
match opcode {
|
match opcode {
|
||||||
0x37 => Ok(build_set_carry_flag_data()),
|
0x37 => Ok(build_set_carry_flag_data()),
|
||||||
0x3F => Ok(build_complement_carry_flag_data()),
|
0x3F => Ok(build_complement_carry_flag_data()),
|
||||||
|
0x27 => Ok(build_daa_data()),
|
||||||
0x2F => Ok(build_complement_a_register_data()),
|
0x2F => Ok(build_complement_a_register_data()),
|
||||||
_ => Err(super::Error::UnknownOpcode(opcode)),
|
_ => Err(super::Error::UnknownOpcode(opcode)),
|
||||||
}
|
}
|
||||||
|
@ -47,3 +48,13 @@ fn build_complement_a_register_data() -> ParseOutput {
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_daa_data() -> ParseOutput {
|
||||||
|
(
|
||||||
|
RunnableInstruction {
|
||||||
|
instruction: Instruction::Misc(MiscInstruction::DecimalAdjustAccumulator),
|
||||||
|
cycles: 4,
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::cpu::{instructions::misc::MiscInstruction, register, run::Error, Processor};
|
use crate::{
|
||||||
|
cpu::{instructions::misc::MiscInstruction, register, run::Error, Processor},
|
||||||
|
register::Registers,
|
||||||
|
};
|
||||||
|
|
||||||
use super::Run;
|
use super::Run;
|
||||||
|
|
||||||
|
@ -35,6 +38,27 @@ impl Run for MiscInstruction {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
MiscInstruction::DecimalAdjustAccumulator => {
|
||||||
|
let (adjusted_a_value, carry) = get_daa_value(&processor.registers);
|
||||||
|
|
||||||
|
processor
|
||||||
|
.registers
|
||||||
|
.set_single_8bit_register(register::SingleEightBit::A, adjusted_a_value);
|
||||||
|
|
||||||
|
processor
|
||||||
|
.registers
|
||||||
|
.set_flag_bit(register::Flag::HalfCarry, 0);
|
||||||
|
|
||||||
|
processor
|
||||||
|
.registers
|
||||||
|
.set_flag_bit(register::Flag::Carry, carry.into());
|
||||||
|
|
||||||
|
processor
|
||||||
|
.registers
|
||||||
|
.set_flag_bit(register::Flag::Zero, (adjusted_a_value == 0).into());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,3 +75,30 @@ fn set_flags_in_carry_bit_instruction(processor: &mut Processor, carry_flag: u8)
|
||||||
.registers
|
.registers
|
||||||
.set_flag_bit(register::Flag::Subtract, 0);
|
.set_flag_bit(register::Flag::Subtract, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_daa_value(registers: &Registers) -> (u8, bool) {
|
||||||
|
let mut offset = 0_u8;
|
||||||
|
let mut carry = false;
|
||||||
|
|
||||||
|
let a_value = registers.get_single_8bit_register(register::SingleEightBit::A);
|
||||||
|
let did_half_carry = registers.get_flag_bit(register::Flag::HalfCarry) == 1;
|
||||||
|
let did_carry = registers.get_flag_bit(register::Flag::Carry) == 1;
|
||||||
|
let did_subtract = registers.get_flag_bit(register::Flag::Subtract) == 1;
|
||||||
|
|
||||||
|
if (!did_subtract && a_value & 0xF > 0x09) || did_half_carry {
|
||||||
|
offset |= 0x06;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!did_subtract && a_value > 0x99) || did_carry {
|
||||||
|
offset |= 0x60;
|
||||||
|
carry = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated_value = if did_subtract {
|
||||||
|
a_value.wrapping_sub(offset)
|
||||||
|
} else {
|
||||||
|
a_value.wrapping_add(offset)
|
||||||
|
};
|
||||||
|
|
||||||
|
(updated_value, carry)
|
||||||
|
}
|
||||||
|
|
|
@ -25,5 +25,5 @@ struct TestCase {
|
||||||
name: String,
|
name: String,
|
||||||
initial: TestState,
|
initial: TestState,
|
||||||
r#final: TestState,
|
r#final: TestState,
|
||||||
cycles: Vec<serde_json::Value>
|
cycles: Vec<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,9 @@ fn test_jsmoo_test(filename: &str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let num_cycles_expected = test_case.cycles.len() * 4;
|
let num_cycles_expected = test_case.cycles.len() * 4;
|
||||||
assert_eq!(u128::try_from(num_cycles_expected).unwrap(), processor.num_cycles);
|
assert_eq!(
|
||||||
|
u128::try_from(num_cycles_expected).unwrap(),
|
||||||
|
processor.num_cycles
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
use crate::testutil;
|
use crate::testutil::{self, assert_flags_eq};
|
||||||
use ferris_boi::{
|
use ferris_boi::{
|
||||||
cpu::{instructions::RunnableInstruction, Processor},
|
cpu::{instructions::RunnableInstruction, Processor},
|
||||||
register,
|
register,
|
||||||
};
|
};
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
|
struct DAAInputFlags {
|
||||||
|
subtract: bool,
|
||||||
|
half_carry: bool,
|
||||||
|
full_carry: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DAAOutputFlags {
|
||||||
|
zero: bool,
|
||||||
|
full_carry: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[test_case(1)]
|
#[test_case(1)]
|
||||||
#[test_case(0)]
|
#[test_case(0)]
|
||||||
fn test_set_carry_flag_always_sets_to_1(starting_value: u8) {
|
fn test_set_carry_flag_always_sets_to_1(starting_value: u8) {
|
||||||
|
@ -105,3 +116,50 @@ fn test_complement_a_register_flags() {
|
||||||
(register::Flag::Zero, 1),
|
(register::Flag::Zero, 1),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case(0x22, DAAInputFlags{subtract: false, half_carry: false, full_carry: false}, 0x22, DAAOutputFlags{zero: false, full_carry: false}; "both digits less than 9 should not adjust A register")]
|
||||||
|
#[test_case(0x0A, DAAInputFlags{subtract: false, half_carry: false, full_carry: false}, 0x10, DAAOutputFlags{zero: false, full_carry: false}; "adjust result by 0x06 if lower nibble is greater than 9")]
|
||||||
|
#[test_case(0xA5, DAAInputFlags{subtract: false, half_carry: false, full_carry: false}, 0x05, DAAOutputFlags{zero: false, full_carry: true}; "adjust result by 0x60 if greater than 99")]
|
||||||
|
#[test_case(0x20, DAAInputFlags{subtract: false, half_carry: true, full_carry: false}, 0x26, DAAOutputFlags{zero: false, full_carry: false}; "adjust result by 0x06 if half carry was performed")]
|
||||||
|
#[test_case(0x33, DAAInputFlags{subtract: false, half_carry: false, full_carry: true}, 0x93, DAAOutputFlags{zero: false, full_carry: true}; "adjust result by 0x60 if full carry was performed")]
|
||||||
|
#[test_case(0x26, DAAInputFlags{subtract: true, half_carry: true, full_carry: false}, 0x20, DAAOutputFlags{zero: false, full_carry: false}; "adjust result by -0x06 if half carry and a subtract were performed")]
|
||||||
|
#[test_case(0x93, DAAInputFlags{subtract: true, half_carry: false, full_carry: true}, 0x33, DAAOutputFlags{zero: false, full_carry: true}; "adjust result by -0x60 if full carry and a subtract were performed")]
|
||||||
|
#[test_case(0x00, DAAInputFlags{subtract: false, half_carry: false, full_carry: false}, 0x00, DAAOutputFlags{zero: true, full_carry: false}; "zero flag is true if result is zero")]
|
||||||
|
#[test_case(0x9C, DAAInputFlags{subtract: false, half_carry: true, full_carry: false}, 0x02, DAAOutputFlags{zero: false, full_carry: true}; "just because the upper nibble is 9 does not mean that 0x06 is added")]
|
||||||
|
fn test_daa(
|
||||||
|
a_value: u8,
|
||||||
|
flags: DAAInputFlags,
|
||||||
|
expected_a_value: u8,
|
||||||
|
expected_flags: DAAOutputFlags,
|
||||||
|
) {
|
||||||
|
let mut processor = Processor::default();
|
||||||
|
processor.registers.a = a_value;
|
||||||
|
processor
|
||||||
|
.registers
|
||||||
|
.set_flag_bit(register::Flag::Carry, flags.full_carry.into());
|
||||||
|
processor
|
||||||
|
.registers
|
||||||
|
.set_flag_bit(register::Flag::HalfCarry, flags.half_carry.into());
|
||||||
|
processor
|
||||||
|
.registers
|
||||||
|
.set_flag_bit(register::Flag::Subtract, flags.subtract.into());
|
||||||
|
|
||||||
|
processor
|
||||||
|
.registers
|
||||||
|
// Set the opposite of expected so we know we're setting it right
|
||||||
|
.set_flag_bit(register::Flag::Zero, (!expected_flags.zero).into());
|
||||||
|
|
||||||
|
let data = [0x27, 0x06];
|
||||||
|
let (ins, extra_data) =
|
||||||
|
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||||
|
|
||||||
|
assert_eq!(extra_data, &[0x06]);
|
||||||
|
processor.run_instruction(&ins);
|
||||||
|
|
||||||
|
assert_eq!(expected_a_value, processor.registers.a);
|
||||||
|
assert_flags_eq!(
|
||||||
|
processor,
|
||||||
|
(register::Flag::Carry, u8::from(expected_flags.full_carry)),
|
||||||
|
(register::Flag::Zero, u8::from(expected_flags.zero))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue