diff --git a/src/cpu/instructions/misc.rs b/src/cpu/instructions/misc.rs index 5e64d03..087eddc 100644 --- a/src/cpu/instructions/misc.rs +++ b/src/cpu/instructions/misc.rs @@ -6,4 +6,5 @@ pub enum MiscInstruction { SetCarryFlag, ComplementCarryFlag, ComplementARegister, + DecimalAdjustAccumulator, } diff --git a/src/cpu/parse/misc.rs b/src/cpu/parse/misc.rs index 6d8a188..911fb0f 100644 --- a/src/cpu/parse/misc.rs +++ b/src/cpu/parse/misc.rs @@ -12,6 +12,7 @@ impl OpcodeParser for Parser { match opcode { 0x37 => Ok(build_set_carry_flag_data()), 0x3F => Ok(build_complement_carry_flag_data()), + 0x27 => Ok(build_daa_data()), 0x2F => Ok(build_complement_a_register_data()), _ => Err(super::Error::UnknownOpcode(opcode)), } @@ -47,3 +48,13 @@ fn build_complement_a_register_data() -> ParseOutput { 1, ) } + +fn build_daa_data() -> ParseOutput { + ( + RunnableInstruction { + instruction: Instruction::Misc(MiscInstruction::DecimalAdjustAccumulator), + cycles: 4, + }, + 1, + ) +} diff --git a/src/cpu/run/misc.rs b/src/cpu/run/misc.rs index 51563e1..04a8c53 100644 --- a/src/cpu/run/misc.rs +++ b/src/cpu/run/misc.rs @@ -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; @@ -35,6 +38,27 @@ impl Run for MiscInstruction { 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 .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) +} diff --git a/tests/cpu/jsmoo.rs b/tests/cpu/jsmoo.rs index f344d37..c702751 100644 --- a/tests/cpu/jsmoo.rs +++ b/tests/cpu/jsmoo.rs @@ -25,5 +25,5 @@ struct TestCase { name: String, initial: TestState, r#final: TestState, - cycles: Vec + cycles: Vec, } diff --git a/tests/cpu/jsmoo/testdata/disabled/27.json b/tests/cpu/jsmoo/testdata/27.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/27.json rename to tests/cpu/jsmoo/testdata/27.json diff --git a/tests/cpu/jsmoo/tests.rs b/tests/cpu/jsmoo/tests.rs index e9f4d35..cf9712c 100644 --- a/tests/cpu/jsmoo/tests.rs +++ b/tests/cpu/jsmoo/tests.rs @@ -92,6 +92,9 @@ fn test_jsmoo_test(filename: &str) { } 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 + ); } } diff --git a/tests/cpu/misc.rs b/tests/cpu/misc.rs index 4c78db3..b9f8d8b 100644 --- a/tests/cpu/misc.rs +++ b/tests/cpu/misc.rs @@ -1,10 +1,21 @@ -use crate::testutil; +use crate::testutil::{self, assert_flags_eq}; use ferris_boi::{ cpu::{instructions::RunnableInstruction, Processor}, register, }; 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(0)] 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), ); } + +#[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)) + ); +}