From f0c0e818c9ba6596457412dc601e2fdb8b683909 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Sat, 23 Apr 2022 23:46:44 -0400 Subject: [PATCH] Implement add A to HL instruction --- src/cpu.rs | 98 ++++++++++++++++++++++++++++++++++ src/cpu/instructions/arith8.rs | 1 + src/cpu/parse/arith8/add.rs | 17 ++++++ src/cpu/run/arith8.rs | 37 ++++++++++++- src/memory.rs | 5 +- 5 files changed, 154 insertions(+), 4 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 597c6c7..1fc2847 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -661,6 +661,8 @@ mod tests { assert_eq!(20, processor.registers.a); } + // TODO: These add tests are almost entirely copy paste - we should find some way to break them up + #[test_case(0x80, register::SingleEightBit::B)] #[test_case(0x81, register::SingleEightBit::C)] #[test_case(0x82, register::SingleEightBit::D)] @@ -750,6 +752,102 @@ mod tests { ); } + #[test] + fn test_add_hl_addr_to_a_value() { + let mut processor = Processor::default(); + processor + .memory + .set(0xFF00, 0x34) + .expect("expected to be able to set 0xFF00"); + + processor + .registers + .set_combined_register(register::Combined::HL, 0xFF00); + processor.registers.a = 0x12; + + let data = [0x86, 0x02]; + + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + assert_eq!(extra_data, &[0x02]); + + processor.run(&ins); + + assert_eq!(0x46, processor.registers.a); + } + + #[test_case(0x00, 0x00, 1, 0, 0; "zero flag for zero value")] + #[test_case(0x00, 0x01, 0, 0, 0; "no zero flag for non-zero value")] + #[test_case(0x0F, 0x01, 0, 1, 0; "half carry flag")] + #[test_case(0x80, 0x80, 0, 0, 1; "full carry flag")] + #[test_case(0xFF, 0x01, 0, 1, 1; "both full and half carry flag")] + fn test_add_hl_addr_to_a_flags( + a_value: u8, + hl_addr_value: u8, + zero_flag: u8, + half_carry_flag: u8, + carry_flag: u8, + ) { + let mut processor = Processor::default(); + processor.registers.a = a_value; + + processor + .registers + .set_combined_register(register::Combined::HL, 0xFF00); + + processor + .memory + .set(0xFF00, hl_addr_value) + .expect("expected to set address 0xFF00 but could not"); + + // Set all the register to the opposite we expect to ensure they all get set + processor + .registers + .set_flag_bit(register::Flag::Zero, if zero_flag == 1 { 0 } else { 1 }); + + processor.registers.set_flag_bit( + register::Flag::HalfCarry, + if half_carry_flag == 1 { 0 } else { 1 }, + ); + processor + .registers + .set_flag_bit(register::Flag::Carry, if carry_flag == 1 { 0 } else { 1 }); + processor + .registers + .set_flag_bit(register::Flag::Subtract, 1); + + let data = [0x86, 0x01]; + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + assert_eq!(extra_data, &[0x01]); + + processor.run(&ins); + + assert_eq!( + zero_flag, + processor.registers.get_flag_bit(register::Flag::Zero), + "zero flag did not match", + ); + + assert_eq!( + 0, + processor.registers.get_flag_bit(register::Flag::Subtract), + "sub flag did not match", + ); + + assert_eq!( + half_carry_flag, + processor.registers.get_flag_bit(register::Flag::HalfCarry), + "half carry flag did not match" + ); + + assert_eq!( + carry_flag, + processor.registers.get_flag_bit(register::Flag::Carry), + "carry flag did not match" + ); + } + #[test] fn test_add_immediate_to_a_value() { let mut processor = Processor::default(); diff --git a/src/cpu/instructions/arith8.rs b/src/cpu/instructions/arith8.rs index 17f84c7..26ba7d3 100644 --- a/src/cpu/instructions/arith8.rs +++ b/src/cpu/instructions/arith8.rs @@ -7,4 +7,5 @@ use crate::register; pub enum EightBitArithmeticInstruction { AddSingleRegisterToA { src: register::SingleEightBit }, AddImmediateToA { n: u8 }, + AddHLAddressToA, } diff --git a/src/cpu/parse/arith8/add.rs b/src/cpu/parse/arith8/add.rs index 3d45c84..6b94a16 100644 --- a/src/cpu/parse/arith8/add.rs +++ b/src/cpu/parse/arith8/add.rs @@ -19,6 +19,7 @@ impl OpcodeParser for EightBitAddParser { 0x83 => build_add_register_to_a_data(register::SingleEightBit::E, data), 0x84 => build_add_register_to_a_data(register::SingleEightBit::H, data), 0x85 => build_add_register_to_a_data(register::SingleEightBit::L, data), + 0x86 => build_add_hl_address_to_a_data(data), 0xC6 => build_add_immediate_to_a_data(data), _ => Err(Error::UnknownOpcode(opcode)), } @@ -41,6 +42,22 @@ fn build_add_register_to_a_data(src: register::SingleEightBit, data: &[u8]) -> P .ok_or(Error::NoData) } +fn build_add_hl_address_to_a_data(data: &[u8]) -> ParseResult { + data.get(1..) + .map(|remaining_data| { + ( + RunnableInstruction { + instruction: Instruction::EightBitArithmetic( + EightBitArithmeticInstruction::AddHLAddressToA, + ), + cycles: 8, + }, + remaining_data, + ) + }) + .ok_or(Error::NoData) +} + fn build_add_immediate_to_a_data(data: &[u8]) -> ParseResult { let opcode = parse::get_opcode_from_data(data)?; let n = data.get(1).copied().ok_or(Error::NotEnoughArgs(opcode))?; diff --git a/src/cpu/run/arith8.rs b/src/cpu/run/arith8.rs index e5f476d..2898b4c 100644 --- a/src/cpu/run/arith8.rs +++ b/src/cpu/run/arith8.rs @@ -1,5 +1,5 @@ use crate::cpu::{instructions::arith8::EightBitArithmeticInstruction, run::Error, Processor}; -use crate::register; +use crate::{memory, register}; use super::{arithutil::CarryingAdd, InstructionRunner}; @@ -27,6 +27,7 @@ impl InstructionRunner for EightBitArithmeticRunn Ok(()) } + EightBitArithmeticInstruction::AddImmediateToA { n } => { let (total, half_carry, carry) = processor .registers @@ -41,6 +42,40 @@ impl InstructionRunner for EightBitArithmeticRunn Ok(()) } + + EightBitArithmeticInstruction::AddHLAddressToA => { + let src_addr = 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 hl_addr_value = + processor + .memory + .get(src_addr.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 (total, half_carry, carry) = processor + .registers + .get_single_8bit_register(register::SingleEightBit::A) + .add_with_carry(hl_addr_value); + + set_addition_flags(processor, total, half_carry, carry); + processor + .registers + .set_single_8bit_register(register::SingleEightBit::A, total); + + Ok(()) + } } } } diff --git a/src/memory.rs b/src/memory.rs index 80e34c8..b618581 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -89,10 +89,9 @@ mod tests { #[test] fn test_get_invalid_address() { let memory = Memory::new(); - let get_val = memory.get(0xCC_CC_CC_CC_CC); match memory.get(0xCC_CC_CC_CC_CC) { Err(Error::GetInvalidAddress(addr)) => { - assert_eq!(0xCC_CC_CC_CC_CC, addr) + assert_eq!(0xCC_CC_CC_CC_CC, addr); } Ok(_) => panic!("should not have succeeded in setting invalid address"), Err(_) => panic!("unexpected error variant returned"), @@ -104,7 +103,7 @@ mod tests { let mut memory = Memory::new(); match memory.set(0xCC_CC_CC_CC_CC, 100) { Err(Error::SetInvalidAddress(addr, value)) => { - assert_eq!((0xCC_CC_CC_CC_CC, 100), (addr, value)) + assert_eq!((0xCC_CC_CC_CC_CC, 100), (addr, value)); } Ok(_) => panic!("should not have succeeded in setting invalid address"), Err(_) => panic!("unexpected error variant returned"),