diff --git a/src/cpu/instructions/arith8.rs b/src/cpu/instructions/arith8.rs index aaf63f9..c387d10 100644 --- a/src/cpu/instructions/arith8.rs +++ b/src/cpu/instructions/arith8.rs @@ -10,4 +10,5 @@ pub enum EightBitArithmeticInstruction { AddHLAddressToA, AddSingleRegisterToAWithCarry { src: register::SingleEightBit }, AddImmediateToAWithCarry { n: u8 }, + AddHLAddressToAWithCarry, } diff --git a/src/cpu/parse/arith8/add.rs b/src/cpu/parse/arith8/add.rs index 3175fba..3797edf 100644 --- a/src/cpu/parse/arith8/add.rs +++ b/src/cpu/parse/arith8/add.rs @@ -47,6 +47,8 @@ impl OpcodeParser for EightBitAddParser { )), 0xCE => Ok(build_add_immediate_to_a_with_carry_data(data)), + 0x8E => Ok(build_add_hl_address_to_a_with_carry_data()), + _ => Err(Error::UnknownOpcode(opcode)), } } @@ -115,3 +117,15 @@ fn build_add_immediate_to_a_with_carry_data(data: &View) -> ParseOutput { 2, ) } + +fn build_add_hl_address_to_a_with_carry_data() -> ParseOutput { + ( + RunnableInstruction { + instruction: Instruction::EightBitArithmetic( + EightBitArithmeticInstruction::AddHLAddressToAWithCarry, + ), + cycles: 8, + }, + 1, + ) +} diff --git a/src/cpu/run/arith8.rs b/src/cpu/run/arith8.rs index 418a417..7105483 100644 --- a/src/cpu/run/arith8.rs +++ b/src/cpu/run/arith8.rs @@ -7,6 +7,8 @@ use super::{arithutil::CarryingAdd, InstructionRunner}; pub(super) struct EightBitArithmeticRunner; impl InstructionRunner for EightBitArithmeticRunner { + // TODO: Fix this + #[allow(clippy::too_many_lines)] fn run_instruction( processor: &mut Processor, instruction: &EightBitArithmeticInstruction, @@ -115,6 +117,37 @@ impl InstructionRunner for EightBitArithmeticRunn Ok(()) } + + EightBitArithmeticInstruction::AddHLAddressToAWithCarry => { + let a_value = processor.registers.a; + let src_address = processor.registers.get_combined_register(register::Combined::HL); + + // While this is true, we really do want a wildcard match in map_err + #[allow(clippy::match_wildcard_for_single_variants)] + let stored_value = processor.memory.get(src_address.into()) + .map_err(|err| match err { + memory::Error::GetInvalidAddress(bad_addr) => { + Error::InvalidRegisterAddress( + register::SixteenBit::Combined(register::Combined::HL), + bad_addr, + ) + } + err => Error::Unknown(Box::new(err)), + })?; + let carried_operand = arithutil::CarriedNumber::new( + stored_value, + processor.registers.get_flag_bit(register::Flag::Carry) + ) + .map_err(|err| match err { + arithutil::Error::InvalidCarryBit(value) => Error::InvalidCarryFlagValue(value), + })?; + + let (result, half_carry, carry) = a_value.add_with_carry(carried_operand); + processor.registers.a = result; + set_addition_flags(processor, result, half_carry, carry); + + Ok(()) + } } } } diff --git a/tests/cpu/arith8.rs b/tests/cpu/arith8.rs index b4a115a..4638e2c 100644 --- a/tests/cpu/arith8.rs +++ b/tests/cpu/arith8.rs @@ -458,3 +458,69 @@ fn test_add_immediate_with_carry_to_a_flags( (register::Flag::Carry, expected_flags.carry), ); } + +#[test_case(0, 0xFE; "no carry bit")] +#[test_case(1, 0xFF; "carry bit")] +fn test_add_hl_addr_to_a_with_carry_value(carry_flag: u8, expected: u8) { + let mut processor = Processor::default(); + processor + .memory + .set(0xFFFE, 0xF0) + .expect("expected to be able to set 0xFF00"); + + processor + .registers + .set_combined_register(register::Combined::HL, 0xFFFE); + processor.registers.a = 0x0E; + processor.registers.set_flag_bit(register::Flag::Carry, carry_flag); + + let data = [0x8E, 0x02]; + + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + assert_eq!(extra_data, &[0x02]); + + processor.run_instruction(&ins); + + assert_eq!(expected, processor.registers.a); +} + +#[test_case(0x01, 0, 0x02, AdditionOperationFlags{zero: 0, half_carry: 0, carry: 0}; "no carry")] +#[test_case(0xFF, 1, 0x00, AdditionOperationFlags{zero: 1, half_carry: 1, carry: 1}; "all flags")] +#[test_case(0x0F, 1, 0x80, AdditionOperationFlags{zero: 0, half_carry: 1, carry: 0}; "half carry flag")] +#[test_case(0xF0, 1, 0xF0, AdditionOperationFlags{zero: 0, half_carry: 0, carry: 1}; "full carry flag")] +fn test_add_hl_addr_to_a_with_carry_flags( + initial_value: u8, + carry_bit: u8, + operand: u8, + expected_flags: AdditionOperationFlags, +) { + let mut processor = Processor::default(); + processor + .memory + .set(0xFFFE, operand) + .expect("expected to be able to set 0xFF00"); + + processor + .registers + .set_combined_register(register::Combined::HL, 0xFFFE); + + processor.registers.a = initial_value; + processor.registers.set_flag_bit(register::Flag::Carry, carry_bit); + + let data = [0x8E, 0x02]; + + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + assert_eq!(extra_data, &[0x02]); + + processor.run_instruction(&ins); + + testutil::assert_flags_eq!( + processor, + (register::Flag::Zero, expected_flags.zero), + (register::Flag::Subtract, 0), + (register::Flag::HalfCarry, expected_flags.half_carry), + (register::Flag::Carry, expected_flags.carry), + ); +} diff --git a/tests/cpu/jsmoo/testdata/disabled/8e.json b/tests/cpu/jsmoo/testdata/8e.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/8e.json rename to tests/cpu/jsmoo/testdata/8e.json