From e6adf5d6af67ac672aeca0f37a6a9a5deb46161b Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Tue, 21 Nov 2023 15:50:17 -0500 Subject: [PATCH] Implement unconditional CALL instruction --- src/cpu.rs | 8 ++++- src/cpu/instructions/control.rs | 1 + src/cpu/parse/control.rs | 22 +++++++----- src/cpu/run/control.rs | 16 +++++++++ tests/cpu/control.rs | 34 +++++++++++++++++++ .../cpu/jsmoo/testdata/{disabled => }/cd.json | 0 tests/cpu/jsmoo/tests.rs | 5 ++- 7 files changed, 76 insertions(+), 10 deletions(-) rename tests/cpu/jsmoo/testdata/{disabled => }/cd.json (100%) diff --git a/src/cpu.rs b/src/cpu.rs index e714dcd..5b2e09e 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -173,13 +173,19 @@ impl Processor { &mut self, register: register::SixteenBit, ) -> Result<(), Error> { + let register_value = self.registers.get_16bit_register(register); + + self.push_16bit_value_to_stack(register_value) + } + + fn push_16bit_value_to_stack(&mut self, value: u16) -> Result<(), Error> { let current_sp = self .registers .get_single_16bit_register(register::SingleSixteenBit::StackPointer); // we want to pop the LSB first (i.e. we write the MSB first) // https://rgbds.gbdev.io/docs/v0.5.2/gbz80.7#PUSH_r16 - let [lower_bits, higher_bits] = self.registers.get_16bit_register(register).to_le_bytes(); + let [lower_bits, higher_bits] = value.to_le_bytes(); let res = self.memory.set_both(( ((current_sp - 1).into(), higher_bits), ((current_sp - 2).into(), lower_bits), diff --git a/src/cpu/instructions/control.rs b/src/cpu/instructions/control.rs index 673fa7a..a78d1af 100644 --- a/src/cpu/instructions/control.rs +++ b/src/cpu/instructions/control.rs @@ -2,4 +2,5 @@ #[derive(Debug, Clone, Copy)] pub enum ControlFlowInstruction { JumpToImmediate { addr: u16 }, + Call { addr: u16 }, } diff --git a/src/cpu/parse/control.rs b/src/cpu/parse/control.rs index d92ce3a..3de2927 100644 --- a/src/cpu/parse/control.rs +++ b/src/cpu/parse/control.rs @@ -14,20 +14,26 @@ impl OpcodeParser for Parser { fn parse_opcode(data: &View) -> ParseResult { let opcode = parse::get_opcode_from_data(data); match opcode { - 0xC3 => Ok(build_jump_to_immediate_data(data)), + 0xC3 => Ok(build_immediate_parameterized_control_flow_data( + data, + |addr| ControlFlowInstruction::JumpToImmediate { addr }, + )), + 0xCD => Ok(build_immediate_parameterized_control_flow_data( + data, + |addr| ControlFlowInstruction::Call { addr }, + )), _ => Err(parse::Error::UnknownOpcode(opcode)), } } } -fn build_jump_to_immediate_data(data: &View) -> ParseOutput { +fn build_immediate_parameterized_control_flow_data ControlFlowInstruction>( + data: &View, + make_variant: F, +) -> ParseOutput { let (_opcode, lower_bytes, upper_bytes) = data.get_tuple(); - // manual doesn't state this should be LE, but some inspection of games and googling - // indicates it should be. let addr = u16::from_le_bytes([lower_bytes, upper_bytes]); - ( - Instruction::ControlFlow(ControlFlowInstruction::JumpToImmediate { addr }), - 3, - ) + + (Instruction::ControlFlow(make_variant(addr)), 3) } diff --git a/src/cpu/run/control.rs b/src/cpu/run/control.rs index d5fd151..9fb2690 100644 --- a/src/cpu/run/control.rs +++ b/src/cpu/run/control.rs @@ -12,6 +12,22 @@ impl Run for ControlFlowInstruction { Ok(Cycles(16)) } + Self::Call { addr } => { + let current_pc = processor + .registers + .get_single_16bit_register(register::SingleSixteenBit::ProgramCounter); + + // We know the call instruction to be 3 bytes. A bit of a hack, but eh. + processor.push_16bit_value_to_stack(current_pc + 3)?; + dbg!(current_pc); + dbg!(current_pc + 3); + + processor + .registers + .set_single_16bit_register(register::SingleSixteenBit::ProgramCounter, addr); + + Ok(Cycles(24)) + } } } } diff --git a/tests/cpu/control.rs b/tests/cpu/control.rs index 265800a..e22ba09 100644 --- a/tests/cpu/control.rs +++ b/tests/cpu/control.rs @@ -13,3 +13,37 @@ fn test_can_jump_to_immediate() { assert_eq!(0x1337, processor.registers.program_counter); } + +#[test] +fn test_call_adjusts_program_counter_to_given_value() { + let mut processor = Processor::default(); + + let data = [0xCD, 0x37, 0x13, 0x06]; + let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); + + assert_eq!(extra_data, &[0x06]); + + processor.run_instruction(ins); + + assert_eq!(0x1337, processor.registers.program_counter); +} + +#[test] +fn test_call_pushes_pc_onto_stack() { + let mut processor = Processor::default(); + processor.registers.program_counter = 0xBEEF; + + let data = [0xCD, 0x37, 0x13, 0x06]; + let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); + + assert_eq!(extra_data, &[0x06]); + + let old_sp = processor.registers.stack_pointer; + + processor.run_instruction(ins); + + assert_eq!(0xBE, processor.memory.get((old_sp - 1).into()).unwrap()); + // 0xEF + 3, which is the size of the CALL instruction + assert_eq!(0xF2, processor.memory.get((old_sp - 2).into()).unwrap()); + assert_eq!(old_sp - 2, processor.registers.stack_pointer); +} diff --git a/tests/cpu/jsmoo/testdata/disabled/cd.json b/tests/cpu/jsmoo/testdata/cd.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/cd.json rename to tests/cpu/jsmoo/testdata/cd.json diff --git a/tests/cpu/jsmoo/tests.rs b/tests/cpu/jsmoo/tests.rs index f9cb634..fc8be9f 100644 --- a/tests/cpu/jsmoo/tests.rs +++ b/tests/cpu/jsmoo/tests.rs @@ -89,7 +89,10 @@ fn test_jsmoo_test(filename: &str) { .get(addr.into()) .unwrap_or_else(|_| panic!("failed to set up final memory for test {test_name}")); - assert_eq!(value, stored_val); + assert_eq!( + value, stored_val, + "memory did not match at address 0x{addr:0X}" + ); } let num_cycles_expected = test_case.cycles.len() * 4;