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, offset: i8,
}, },
JumpRelativeIfFlagMatches {
flag: register::Flag,
value: u8,
offset: i8,
},
Return, Return,
ReturnAndEnableInterrupts, ReturnAndEnableInterrupts,

View File

@ -14,6 +14,7 @@ pub struct Parser;
enum ConditionalControlFlowType { enum ConditionalControlFlowType {
Call, Call,
Jump, Jump,
RelativeJump,
Return, Return,
} }
@ -34,11 +35,11 @@ fn parse_unconditional_control_operation(data: &View) -> ParseResult {
fn parse_unconditional_parameterized_control_flow_operation(data: &View) -> ParseResult { fn parse_unconditional_parameterized_control_flow_operation(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_immediate_parameterized_control_flow_data( 0xC3 => Ok(build_immediate_addr_parameterized_control_flow_data(
data, data,
|addr| ControlFlowInstruction::JumpToImmediate { addr }, |addr| ControlFlowInstruction::JumpToImmediate { addr },
)), )),
0xCD => Ok(build_immediate_parameterized_control_flow_data( 0xCD => Ok(build_immediate_addr_parameterized_control_flow_data(
data, data,
|addr| ControlFlowInstruction::Call { addr }, |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 (flag, value) = conditional_control_flow_flag_for_opcode(opcode)?;
let control_flow_type = conditional_control_flow_type_for_opcode(opcode)?; let control_flow_type = conditional_control_flow_type_for_opcode(opcode)?;
match control_flow_type { match control_flow_type {
ConditionalControlFlowType::Jump => Ok(build_immediate_parameterized_control_flow_data( ConditionalControlFlowType::Jump => Ok(
data, build_immediate_addr_parameterized_control_flow_data(data, |addr| {
|addr| ControlFlowInstruction::JumpToImmediateIfFlagMatches { flag, value, addr }, ControlFlowInstruction::JumpToImmediateIfFlagMatches { flag, value, addr }
)), }),
ConditionalControlFlowType::Call => Ok(build_immediate_parameterized_control_flow_data( ),
data, ConditionalControlFlowType::Call => Ok(
|addr| ControlFlowInstruction::CallIfFlagMatches { flag, value, addr }, 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(( ConditionalControlFlowType::Return => Ok((
Instruction::ControlFlow(ControlFlowInstruction::ReturnIfFlagMatches { flag, value }), Instruction::ControlFlow(ControlFlowInstruction::ReturnIfFlagMatches { flag, value }),
1, 1,
@ -115,21 +121,22 @@ fn conditional_control_flow_type_for_opcode(
0xC2 | 0xD2 | 0xCA | 0xDA => Ok(ConditionalControlFlowType::Jump), 0xC2 | 0xD2 | 0xCA | 0xDA => Ok(ConditionalControlFlowType::Jump),
0xC4 | 0xD4 | 0xCC | 0xDC => Ok(ConditionalControlFlowType::Call), 0xC4 | 0xD4 | 0xCC | 0xDC => Ok(ConditionalControlFlowType::Call),
0xC0 | 0xD0 | 0xC8 | 0xD8 => Ok(ConditionalControlFlowType::Return), 0xC0 | 0xD0 | 0xC8 | 0xD8 => Ok(ConditionalControlFlowType::Return),
0x20 | 0x30 | 0x28 | 0x38 => Ok(ConditionalControlFlowType::RelativeJump),
_ => Err(parse::Error::UnknownOpcode(opcode)), _ => Err(parse::Error::UnknownOpcode(opcode)),
} }
} }
fn conditional_control_flow_flag_for_opcode(opcode: u8) -> Result<(Flag, u8), parse::Error> { fn conditional_control_flow_flag_for_opcode(opcode: u8) -> Result<(Flag, u8), parse::Error> {
match opcode { match opcode {
0xC0 | 0xC2 | 0xC4 => Ok((Flag::Zero, 0)), 0xC0 | 0xC2 | 0xC4 | 0x20 => Ok((Flag::Zero, 0)),
0xD0 | 0xD2 | 0xD4 => Ok((Flag::Carry, 0)), 0xD0 | 0xD2 | 0xD4 | 0x30 => Ok((Flag::Carry, 0)),
0xC8 | 0xCA | 0xCC => Ok((Flag::Zero, 1)), 0xC8 | 0xCA | 0xCC | 0x28 => Ok((Flag::Zero, 1)),
0xD8 | 0xDA | 0xDC => Ok((Flag::Carry, 1)), 0xD8 | 0xDA | 0xDC | 0x38 => Ok((Flag::Carry, 1)),
_ => Err(parse::Error::UnknownOpcode(opcode)), _ => 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, data: &View,
make_variant: F, make_variant: F,
) -> ParseOutput { ) -> ParseOutput {
@ -139,3 +146,15 @@ fn build_immediate_parameterized_control_flow_data<F: Fn(u16) -> ControlFlowInst
(Instruction::ControlFlow(make_variant(addr)), 3) (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. /// `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 // 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 // only use up to u16s

View File

@ -81,18 +81,22 @@ impl Run for ControlFlowInstruction {
} }
Self::JumpRelative { offset } => { Self::JumpRelative { offset } => {
let current_pc = processor do_relative_jump(processor, offset);
.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);
Ok(Cycles(12)) 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); .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> { fn return_to_popped_addr(processor: &mut Processor) -> Result<(), super::Error> {
processor.pop_from_stack_to_16bit_register(register::SingleSixteenBit::ProgramCounter.into()) processor.pop_from_stack_to_16bit_register(register::SingleSixteenBit::ProgramCounter.into())
} }

View File

@ -2,7 +2,7 @@ use ferris_boi::{
cpu::{instructions::Instruction, Processor}, cpu::{instructions::Instruction, Processor},
register::{self, Flag}, register::{self, Flag},
}; };
use test_case::test_case; use test_case::{test_case, test_matrix};
#[test] #[test]
fn test_can_jump_to_immediate() { 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); 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(0xC9; "RET")]
#[test_case(0xD9; "RETI")] #[test_case(0xD9; "RETI")]
fn test_ret_jumps_to_address_on_stack(opcode: u8) { fn test_ret_jumps_to_address_on_stack(opcode: u8) {