diff --git a/src/run.rs b/src/run.rs index 1abeeaa..6eba00c 100644 --- a/src/run.rs +++ b/src/run.rs @@ -107,6 +107,32 @@ impl Processor { "invalid offset stored in dst register ({offset_register})" ); } + Instruction::LDToRegisterAddressThenDec { dst, src } => { + let value = self.registers.get_single_register(src); + let dst_address = self.registers.get_combined_register(dst); + + let memory_value = self.memory.set(dst_address.into(), value); + + assert!( + memory_value.is_some(), + "invalid address stored in ({src}): {dst_address:X}" + ); + + self.registers.set_combined_register(dst, dst_address - 1); + } + Instruction::LDToRegisterAddressThenInc { dst, src } => { + let value = self.registers.get_single_register(src); + let dst_address = self.registers.get_combined_register(dst); + + let memory_value = self.memory.set(dst_address.into(), value); + + assert!( + memory_value.is_some(), + "invalid address stored in ({src}): {dst_address:X}" + ); + + self.registers.set_combined_register(dst, dst_address + 1); + } } self.num_cycles += u64::from(instruction.cycles); @@ -354,4 +380,32 @@ mod tests { processor.run(&ins); assert_eq!(Some(10), processor.memory.get(0xFF64)); } + + #[test_case(0x32, 0x64, 0x63)] + #[test_case(0x2A, 0x64, 0x65)] + fn test_load_to_register_then_do_arithmetic( + opcode: u8, + hl_value_before: u16, + hl_value_after: u16, + ) { + let mut processor = Processor::default(); + processor + .registers + .set_combined_register(register::Combined::HL, hl_value_before); + processor.registers.a = 10; + + let data = [opcode, 0x00]; + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + + assert_eq!(extra_data, &[0x00]); + processor.run(&ins); + assert_eq!(Some(10), processor.memory.get(hl_value_before.into())); + assert_eq!( + hl_value_after, + processor + .registers + .get_combined_register(register::Combined::HL) + ); + } } diff --git a/src/run/instructions.rs b/src/run/instructions.rs index 923b126..a902f84 100644 --- a/src/run/instructions.rs +++ b/src/run/instructions.rs @@ -55,6 +55,20 @@ pub enum Instruction { LDnToHLAddress { value: u8, }, + // 3.3.1.{10,11,12} + LDToRegisterAddressThenDec { + src: register::Single, + // The destination, unlike some other destination instructions, refers to a register + // whose address will be dereferenced (and then decremented after the load) + dst: register::Combined, + }, + // 3.3.1.{13,14,15} + LDToRegisterAddressThenInc { + src: register::Single, + // The destination, unlike some other destination instructions, refers to a register + // whose address will be dereferenced (and then incremented after the load) + dst: register::Combined, + }, } pub struct RunnableInstruction { diff --git a/src/run/parse/load8/memory.rs b/src/run/parse/load8/memory.rs index 709a4ce..0e1c553 100644 --- a/src/run/parse/load8/memory.rs +++ b/src/run/parse/load8/memory.rs @@ -12,6 +12,7 @@ impl OpcodeParser for Memory8BitLoadParser { parse_load_from_register_to_address, parse_load_from_address_to_register, parse_load_immediate_instructions, + parse_load_from_register_to_address_then_do_arithmetic, ]; for parse_func in parse_funcs { @@ -82,6 +83,25 @@ fn parse_load_immediate_instructions(data: &[u8]) -> ParseResult { } } +fn parse_load_from_register_to_address_then_do_arithmetic(data: &[u8]) -> ParseResult { + let opcode = parse::get_opcode_from_data(data)?; + match opcode { + 0x32 => make_ld_to_address_then_do_arithmetic( + register::Combined::HL, + register::Single::A, + |dst, src| Instruction::LDToRegisterAddressThenDec { dst, src }, + data, + ), + 0x2A => make_ld_to_address_then_do_arithmetic( + register::Combined::HL, + register::Single::A, + |dst, src| Instruction::LDToRegisterAddressThenInc { dst, src }, + data, + ), + _ => Err(Error::UnknownOpcode(opcode)), + } +} + fn make_ld_from_register_address( dst: register::Single, src: register::Combined, @@ -206,3 +226,25 @@ fn make_ld_to_memory_relative_to_io_register_start( }) .ok_or(Error::NoData) } + +fn make_ld_to_address_then_do_arithmetic< + F: Fn(register::Combined, register::Single) -> Instruction, +>( + dst: register::Combined, + src: register::Single, + make: F, + data: &[u8], +) -> ParseResult { + data.get(1..) + .map(|remaining_data| { + ( + RunnableInstruction { + instruction: make(dst, src), + cycles: 8, + }, + // guaranteed to succeed given we found the opcode + remaining_data, + ) + }) + .ok_or(Error::NoData) +}