Implement unconditional CALL instruction
parent
f5b0ba2a2f
commit
e6adf5d6af
|
@ -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),
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ControlFlowInstruction {
|
||||
JumpToImmediate { addr: u16 },
|
||||
Call { addr: u16 },
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue