Add conditional relative jumps

old-bit-manip
Nick Krichevsky 2023-11-26 20:26:34 -05:00
parent 709b0dd82e
commit d53bed716a
9 changed files with 130 additions and 26 deletions

View File

@ -33,6 +33,12 @@ pub enum ControlFlowInstruction {
offset: i8,
},
JumpRelativeIfFlagMatches {
flag: register::Flag,
value: u8,
offset: i8,
},
Return,
ReturnAndEnableInterrupts,

View File

@ -14,6 +14,7 @@ pub struct Parser;
enum ConditionalControlFlowType {
Call,
Jump,
RelativeJump,
Return,
}
@ -34,11 +35,11 @@ fn parse_unconditional_control_operation(data: &View) -> ParseResult {
fn parse_unconditional_parameterized_control_flow_operation(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0xC3 => Ok(build_immediate_parameterized_control_flow_data(
0xC3 => Ok(build_immediate_addr_parameterized_control_flow_data(
data,
|addr| ControlFlowInstruction::JumpToImmediate { addr },
)),
0xCD => Ok(build_immediate_parameterized_control_flow_data(
0xCD => Ok(build_immediate_addr_parameterized_control_flow_data(
data,
|addr| ControlFlowInstruction::Call { addr },
)),
@ -93,14 +94,19 @@ fn parse_conditional_control_operation(data: &View) -> ParseResult {
let (flag, value) = conditional_control_flow_flag_for_opcode(opcode)?;
let control_flow_type = conditional_control_flow_type_for_opcode(opcode)?;
match control_flow_type {
ConditionalControlFlowType::Jump => Ok(build_immediate_parameterized_control_flow_data(
data,
|addr| ControlFlowInstruction::JumpToImmediateIfFlagMatches { flag, value, addr },
)),
ConditionalControlFlowType::Call => Ok(build_immediate_parameterized_control_flow_data(
data,
|addr| ControlFlowInstruction::CallIfFlagMatches { flag, value, addr },
)),
ConditionalControlFlowType::Jump => Ok(
build_immediate_addr_parameterized_control_flow_data(data, |addr| {
ControlFlowInstruction::JumpToImmediateIfFlagMatches { flag, value, addr }
}),
),
ConditionalControlFlowType::Call => Ok(
build_immediate_addr_parameterized_control_flow_data(data, |addr| {
ControlFlowInstruction::CallIfFlagMatches { flag, value, addr }
}),
),
ConditionalControlFlowType::RelativeJump => {
Ok(build_conditional_jump_relative_data(data, flag, value))
}
ConditionalControlFlowType::Return => Ok((
Instruction::ControlFlow(ControlFlowInstruction::ReturnIfFlagMatches { flag, value }),
1,
@ -115,21 +121,22 @@ fn conditional_control_flow_type_for_opcode(
0xC2 | 0xD2 | 0xCA | 0xDA => Ok(ConditionalControlFlowType::Jump),
0xC4 | 0xD4 | 0xCC | 0xDC => Ok(ConditionalControlFlowType::Call),
0xC0 | 0xD0 | 0xC8 | 0xD8 => Ok(ConditionalControlFlowType::Return),
0x20 | 0x30 | 0x28 | 0x38 => Ok(ConditionalControlFlowType::RelativeJump),
_ => Err(parse::Error::UnknownOpcode(opcode)),
}
}
fn conditional_control_flow_flag_for_opcode(opcode: u8) -> Result<(Flag, u8), parse::Error> {
match opcode {
0xC0 | 0xC2 | 0xC4 => Ok((Flag::Zero, 0)),
0xD0 | 0xD2 | 0xD4 => Ok((Flag::Carry, 0)),
0xC8 | 0xCA | 0xCC => Ok((Flag::Zero, 1)),
0xD8 | 0xDA | 0xDC => Ok((Flag::Carry, 1)),
0xC0 | 0xC2 | 0xC4 | 0x20 => Ok((Flag::Zero, 0)),
0xD0 | 0xD2 | 0xD4 | 0x30 => Ok((Flag::Carry, 0)),
0xC8 | 0xCA | 0xCC | 0x28 => Ok((Flag::Zero, 1)),
0xD8 | 0xDA | 0xDC | 0x38 => Ok((Flag::Carry, 1)),
_ => Err(parse::Error::UnknownOpcode(opcode)),
}
}
fn build_immediate_parameterized_control_flow_data<F: Fn(u16) -> ControlFlowInstruction>(
fn build_immediate_addr_parameterized_control_flow_data<F: Fn(u16) -> ControlFlowInstruction>(
data: &View,
make_variant: F,
) -> ParseOutput {
@ -139,3 +146,15 @@ fn build_immediate_parameterized_control_flow_data<F: Fn(u16) -> ControlFlowInst
(Instruction::ControlFlow(make_variant(addr)), 3)
}
fn build_conditional_jump_relative_data(data: &View, flag: Flag, flag_value: u8) -> ParseOutput {
let (_opcode, offset) = data.get_tuple();
let ins = Instruction::ControlFlow(ControlFlowInstruction::JumpRelativeIfFlagMatches {
flag,
value: flag_value,
offset: i8::from_be_bytes([offset]),
});
(ins, 2)
}

View File

@ -171,6 +171,7 @@ impl CarryingSub<CarriedNumber<u8>> for u8 {
}
}
/// `did_8_bit_add_half_carry` checks whether or not the given addition would have done an 8 bit half carry.
// NOTE: These generics are not as generic as they could be. The Into<u16> is just a shortcut because we
// only use up to u16s

View File

@ -81,18 +81,22 @@ impl Run for ControlFlowInstruction {
}
Self::JumpRelative { offset } => {
let current_pc = processor
.registers
.get_single_16bit_register(register::SingleSixteenBit::ProgramCounter);
// We don't need the flags, but this trait does the propper adding for us
// TODO: Break this out into its own arithutil function?
let (updated_pc, _half_carry, _carry) = current_pc.add_with_carry(offset);
jump_to(processor, updated_pc);
do_relative_jump(processor, offset);
Ok(Cycles(12))
}
Self::JumpRelativeIfFlagMatches {
flag,
value,
offset,
} => {
if processor.registers.get_flag_bit(flag) == value {
do_relative_jump(processor, offset);
Ok(Cycles(12))
} else {
Ok(Cycles(8))
}
}
}
}
}
@ -115,6 +119,18 @@ fn jump_to(processor: &mut Processor, to_addr: u16) {
.set_single_16bit_register(register::SingleSixteenBit::ProgramCounter, to_addr);
}
fn do_relative_jump(processor: &mut Processor, offset: i8) {
let current_pc = processor
.registers
.get_single_16bit_register(register::SingleSixteenBit::ProgramCounter);
// We don't need the flags, but this trait does the propper adding for us
// TODO: Break this out into its own arithutil function?
let (updated_pc, _half_carry, _carry) = current_pc.add_with_carry(offset);
jump_to(processor, updated_pc);
}
fn return_to_popped_addr(processor: &mut Processor) -> Result<(), super::Error> {
processor.pop_from_stack_to_16bit_register(register::SingleSixteenBit::ProgramCounter.into())
}

View File

@ -2,7 +2,7 @@ use ferris_boi::{
cpu::{instructions::Instruction, Processor},
register::{self, Flag},
};
use test_case::test_case;
use test_case::{test_case, test_matrix};
#[test]
fn test_can_jump_to_immediate() {
@ -232,6 +232,68 @@ fn test_jump_relative_jumps_by_the_given_number_of_bytes(distance: i8) {
assert_eq!(expected_pc, processor.registers.program_counter);
}
#[test_matrix(
[
(0x20, Flag::Zero, 0),
(0x30, Flag::Carry, 0),
(0x28, Flag::Zero, 1),
(0x38, Flag::Carry, 1),
],
[10, 0, -8]
)]
fn test_jump_relative_jumps_by_the_given_number_of_bytes_if_condition_matches(
(opcode, flag, flag_value): (u8, Flag, u8),
distance: i8,
) {
let mut processor = Processor::default();
processor.registers.set_flag_bit(flag, flag_value);
let starting_pc = processor.registers.program_counter;
let data = [opcode, i8::to_be_bytes(distance)[0], 0x06];
let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x06]);
processor.run_instruction(ins);
let expected_pc = if distance > 0 {
starting_pc + u16::from(distance.unsigned_abs())
} else {
starting_pc - u16::from(distance.unsigned_abs())
};
assert_eq!(expected_pc, processor.registers.program_counter);
}
#[test_matrix(
[
(0x20, Flag::Zero, 0),
(0x30, Flag::Carry, 0),
(0x28, Flag::Zero, 1),
(0x38, Flag::Carry, 1),
],
[10, 0, -8]
)]
fn test_jump_relative_does_nothing_if_condition_does_not_match(
(opcode, flag, flag_value): (u8, Flag, u8),
distance: i8,
) {
let mut processor = Processor::default();
processor
.registers
.set_flag_bit(flag, if flag_value == 0 { 1 } else { 0 });
let starting_pc = processor.registers.program_counter;
let data = [opcode, i8::to_be_bytes(distance)[0], 0x06];
let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x06]);
processor.run_instruction(ins);
// Would normally advance, but run_instruction does not advance PC
assert_eq!(starting_pc, processor.registers.program_counter);
}
#[test_case(0xC9; "RET")]
#[test_case(0xD9; "RETI")]
fn test_ret_jumps_to_address_on_stack(opcode: u8) {