Add conditional relative jumps
parent
709b0dd82e
commit
d53bed716a
|
@ -33,6 +33,12 @@ pub enum ControlFlowInstruction {
|
||||||
offset: i8,
|
offset: i8,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
JumpRelativeIfFlagMatches {
|
||||||
|
flag: register::Flag,
|
||||||
|
value: u8,
|
||||||
|
offset: i8,
|
||||||
|
},
|
||||||
|
|
||||||
Return,
|
Return,
|
||||||
|
|
||||||
ReturnAndEnableInterrupts,
|
ReturnAndEnableInterrupts,
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue