Implement unconditional CALL instruction

old-bit-manip
Nick Krichevsky 2023-11-21 15:50:17 -05:00
parent f5b0ba2a2f
commit e6adf5d6af
7 changed files with 76 additions and 10 deletions

View File

@ -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),

View File

@ -2,4 +2,5 @@
#[derive(Debug, Clone, Copy)]
pub enum ControlFlowInstruction {
JumpToImmediate { addr: u16 },
Call { addr: u16 },
}

View File

@ -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<F: Fn(u16) -> 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)
}

View File

@ -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))
}
}
}
}

View File

@ -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);
}

View File

@ -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;