Implement unconditional CALL instruction
parent
f5b0ba2a2f
commit
e6adf5d6af
|
@ -173,13 +173,19 @@ impl Processor {
|
||||||
&mut self,
|
&mut self,
|
||||||
register: register::SixteenBit,
|
register: register::SixteenBit,
|
||||||
) -> Result<(), Error> {
|
) -> 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
|
let current_sp = self
|
||||||
.registers
|
.registers
|
||||||
.get_single_16bit_register(register::SingleSixteenBit::StackPointer);
|
.get_single_16bit_register(register::SingleSixteenBit::StackPointer);
|
||||||
|
|
||||||
// we want to pop the LSB first (i.e. we write the MSB first)
|
// 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
|
// 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((
|
let res = self.memory.set_both((
|
||||||
((current_sp - 1).into(), higher_bits),
|
((current_sp - 1).into(), higher_bits),
|
||||||
((current_sp - 2).into(), lower_bits),
|
((current_sp - 2).into(), lower_bits),
|
||||||
|
|
|
@ -2,4 +2,5 @@
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum ControlFlowInstruction {
|
pub enum ControlFlowInstruction {
|
||||||
JumpToImmediate { addr: u16 },
|
JumpToImmediate { addr: u16 },
|
||||||
|
Call { addr: u16 },
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,20 +14,26 @@ impl OpcodeParser for Parser {
|
||||||
fn parse_opcode(data: &View) -> ParseResult {
|
fn parse_opcode(data: &View) -> ParseResult {
|
||||||
let opcode = parse::get_opcode_from_data(data);
|
let opcode = parse::get_opcode_from_data(data);
|
||||||
match opcode {
|
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)),
|
_ => 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();
|
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]);
|
let addr = u16::from_le_bytes([lower_bytes, upper_bytes]);
|
||||||
(
|
|
||||||
Instruction::ControlFlow(ControlFlowInstruction::JumpToImmediate { addr }),
|
(Instruction::ControlFlow(make_variant(addr)), 3)
|
||||||
3,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,22 @@ impl Run for ControlFlowInstruction {
|
||||||
|
|
||||||
Ok(Cycles(16))
|
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);
|
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())
|
.get(addr.into())
|
||||||
.unwrap_or_else(|_| panic!("failed to set up final memory for test {test_name}"));
|
.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;
|
let num_cycles_expected = test_case.cycles.len() * 4;
|
||||||
|
|
Loading…
Reference in New Issue