Implement DAA instruction

old-bit-manip
Nick Krichevsky 2023-11-19 21:42:40 -05:00
parent 3a91f76f1e
commit f9cdccb5fd
7 changed files with 128 additions and 4 deletions

View File

@ -6,4 +6,5 @@ pub enum MiscInstruction {
SetCarryFlag, SetCarryFlag,
ComplementCarryFlag, ComplementCarryFlag,
ComplementARegister, ComplementARegister,
DecimalAdjustAccumulator,
} }

View File

@ -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,
)
}

View File

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

View File

@ -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>,
} }

View File

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

View File

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