Add conditional CALL instructions
parent
e6adf5d6af
commit
6eb5f42ea4
|
@ -1,6 +1,17 @@
|
||||||
|
use crate::cpu::register;
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum ControlFlowInstruction {
|
pub enum ControlFlowInstruction {
|
||||||
JumpToImmediate { addr: u16 },
|
JumpToImmediate {
|
||||||
Call { addr: u16 },
|
addr: u16,
|
||||||
|
},
|
||||||
|
Call {
|
||||||
|
addr: u16,
|
||||||
|
},
|
||||||
|
CallIfFlagMatches {
|
||||||
|
flag: register::Flag,
|
||||||
|
value: u8,
|
||||||
|
addr: u16,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
cpu::{
|
cpu::{
|
||||||
instructions::{control::ControlFlowInstruction, Instruction},
|
instructions::{control::ControlFlowInstruction, Instruction},
|
||||||
parse,
|
parse, register,
|
||||||
},
|
},
|
||||||
memory::{GetViewTuple, View},
|
memory::{GetViewTuple, View},
|
||||||
};
|
};
|
||||||
|
@ -22,6 +22,38 @@ impl OpcodeParser for Parser {
|
||||||
data,
|
data,
|
||||||
|addr| ControlFlowInstruction::Call { addr },
|
|addr| ControlFlowInstruction::Call { addr },
|
||||||
)),
|
)),
|
||||||
|
0xC4 => Ok(build_immediate_parameterized_control_flow_data(
|
||||||
|
data,
|
||||||
|
|addr| ControlFlowInstruction::CallIfFlagMatches {
|
||||||
|
flag: register::Flag::Zero,
|
||||||
|
value: 0,
|
||||||
|
addr,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
0xD4 => Ok(build_immediate_parameterized_control_flow_data(
|
||||||
|
data,
|
||||||
|
|addr| ControlFlowInstruction::CallIfFlagMatches {
|
||||||
|
flag: register::Flag::Carry,
|
||||||
|
value: 0,
|
||||||
|
addr,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
0xCC => Ok(build_immediate_parameterized_control_flow_data(
|
||||||
|
data,
|
||||||
|
|addr| ControlFlowInstruction::CallIfFlagMatches {
|
||||||
|
flag: register::Flag::Zero,
|
||||||
|
value: 1,
|
||||||
|
addr,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
0xDC => Ok(build_immediate_parameterized_control_flow_data(
|
||||||
|
data,
|
||||||
|
|addr| ControlFlowInstruction::CallIfFlagMatches {
|
||||||
|
flag: register::Flag::Carry,
|
||||||
|
value: 1,
|
||||||
|
addr,
|
||||||
|
},
|
||||||
|
)),
|
||||||
_ => Err(parse::Error::UnknownOpcode(opcode)),
|
_ => Err(parse::Error::UnknownOpcode(opcode)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,21 +13,32 @@ impl Run for ControlFlowInstruction {
|
||||||
Ok(Cycles(16))
|
Ok(Cycles(16))
|
||||||
}
|
}
|
||||||
Self::Call { addr } => {
|
Self::Call { addr } => {
|
||||||
let current_pc = processor
|
do_call(processor, addr)?;
|
||||||
.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))
|
Ok(Cycles(24))
|
||||||
}
|
}
|
||||||
|
Self::CallIfFlagMatches { flag, value, addr } => {
|
||||||
|
if processor.registers.get_flag_bit(flag) == value {
|
||||||
|
do_call(processor, addr)?;
|
||||||
|
Ok(Cycles(24))
|
||||||
|
} else {
|
||||||
|
Ok(Cycles(12))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn do_call(processor: &mut Processor, to_addr: u16) -> Result<(), super::Error> {
|
||||||
|
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)?;
|
||||||
|
|
||||||
|
processor
|
||||||
|
.registers
|
||||||
|
.set_single_16bit_register(register::SingleSixteenBit::ProgramCounter, to_addr);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use ferris_boi::cpu::{instructions::Instruction, Processor};
|
use ferris_boi::{
|
||||||
|
cpu::{instructions::Instruction, Processor},
|
||||||
|
register::Flag,
|
||||||
|
};
|
||||||
|
use test_case::test_case;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_can_jump_to_immediate() {
|
fn test_can_jump_to_immediate() {
|
||||||
|
@ -47,3 +51,64 @@ fn test_call_pushes_pc_onto_stack() {
|
||||||
assert_eq!(0xF2, processor.memory.get((old_sp - 2).into()).unwrap());
|
assert_eq!(0xF2, processor.memory.get((old_sp - 2).into()).unwrap());
|
||||||
assert_eq!(old_sp - 2, processor.registers.stack_pointer);
|
assert_eq!(old_sp - 2, processor.registers.stack_pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case(0xCC, Flag::Zero, 1; "CALL Z")]
|
||||||
|
#[test_case(0xDC, Flag::Carry, 1; "CALL C")]
|
||||||
|
#[test_case(0xC4, Flag::Zero, 0; "CALL NZ")]
|
||||||
|
#[test_case(0xD4, Flag::Carry, 0; "CALL NC")]
|
||||||
|
fn test_call_jumps_if_condition_matches(opcode: u8, flag: Flag, expected_value: u8) {
|
||||||
|
let mut processor = Processor::default();
|
||||||
|
processor.registers.set_flag_bit(flag, expected_value);
|
||||||
|
|
||||||
|
let data = [opcode, 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_case(0xCC, Flag::Zero, 1; "CALL Z")]
|
||||||
|
#[test_case(0xDC, Flag::Carry, 1; "CALL C")]
|
||||||
|
#[test_case(0xC4, Flag::Zero, 0; "CALL NZ")]
|
||||||
|
#[test_case(0xD4, Flag::Carry, 0; "CALL NC")]
|
||||||
|
fn test_call_grows_stack_if_condition_matches(opcode: u8, flag: Flag, expected_value: u8) {
|
||||||
|
let mut processor = Processor::default();
|
||||||
|
processor.registers.set_flag_bit(flag, expected_value);
|
||||||
|
|
||||||
|
let data = [opcode, 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!(old_sp - 2, processor.registers.stack_pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case(0xCC, Flag::Zero, 1; "CALL Z")]
|
||||||
|
#[test_case(0xDC, Flag::Carry, 1; "CALL C")]
|
||||||
|
#[test_case(0xC4, Flag::Zero, 0; "CALL NZ")]
|
||||||
|
#[test_case(0xD4, Flag::Carry, 0; "CALL NC")]
|
||||||
|
fn test_call_does_nothing_if_condition_fails(opcode: u8, flag: Flag, expected_value: u8) {
|
||||||
|
let mut processor = Processor::default();
|
||||||
|
processor
|
||||||
|
.registers
|
||||||
|
.set_flag_bit(flag, if expected_value == 0 { 1 } else { 0 });
|
||||||
|
|
||||||
|
let data = [opcode, 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;
|
||||||
|
let old_pc = processor.registers.program_counter;
|
||||||
|
processor.run_instruction(ins);
|
||||||
|
|
||||||
|
assert_eq!(old_sp, processor.registers.stack_pointer);
|
||||||
|
// run_instruction does not advance the program counter, so this will be the same. Normally it would be old_pc plus 3
|
||||||
|
assert_eq!(old_pc, processor.registers.program_counter);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue