Add conditional CALL instructions

old-bit-manip
Nick Krichevsky 2023-11-21 16:40:03 -05:00
parent e6adf5d6af
commit 6eb5f42ea4
8 changed files with 136 additions and 17 deletions

View File

@ -1,6 +1,17 @@
use crate::cpu::register;
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Copy)]
pub enum ControlFlowInstruction {
JumpToImmediate { addr: u16 },
Call { addr: u16 },
JumpToImmediate {
addr: u16,
},
Call {
addr: u16,
},
CallIfFlagMatches {
flag: register::Flag,
value: u8,
addr: u16,
},
}

View File

@ -1,7 +1,7 @@
use crate::{
cpu::{
instructions::{control::ControlFlowInstruction, Instruction},
parse,
parse, register,
},
memory::{GetViewTuple, View},
};
@ -22,6 +22,38 @@ impl OpcodeParser for Parser {
data,
|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)),
}
}

View File

@ -13,21 +13,32 @@ impl Run for ControlFlowInstruction {
Ok(Cycles(16))
}
Self::Call { addr } => {
do_call(processor, addr)?;
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)?;
dbg!(current_pc);
dbg!(current_pc + 3);
processor
.registers
.set_single_16bit_register(register::SingleSixteenBit::ProgramCounter, addr);
.set_single_16bit_register(register::SingleSixteenBit::ProgramCounter, to_addr);
Ok(Cycles(24))
}
}
}
Ok(())
}

View File

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