From d53bed716af8c1793ed4ba86a987dee69a0ef4a4 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Sun, 26 Nov 2023 20:26:34 -0500 Subject: [PATCH] Add conditional relative jumps --- src/cpu/instructions/control.rs | 6 ++ src/cpu/parse/control.rs | 49 +++++++++----- src/cpu/run/arithutil.rs | 1 + src/cpu/run/control.rs | 36 ++++++++--- tests/cpu/control.rs | 64 ++++++++++++++++++- .../cpu/jsmoo/testdata/{disabled => }/20.json | 0 .../cpu/jsmoo/testdata/{disabled => }/28.json | 0 .../cpu/jsmoo/testdata/{disabled => }/30.json | 0 .../cpu/jsmoo/testdata/{disabled => }/38.json | 0 9 files changed, 130 insertions(+), 26 deletions(-) rename tests/cpu/jsmoo/testdata/{disabled => }/20.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/28.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/30.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/38.json (100%) diff --git a/src/cpu/instructions/control.rs b/src/cpu/instructions/control.rs index d1ae89c..140589c 100644 --- a/src/cpu/instructions/control.rs +++ b/src/cpu/instructions/control.rs @@ -33,6 +33,12 @@ pub enum ControlFlowInstruction { offset: i8, }, + JumpRelativeIfFlagMatches { + flag: register::Flag, + value: u8, + offset: i8, + }, + Return, ReturnAndEnableInterrupts, diff --git a/src/cpu/parse/control.rs b/src/cpu/parse/control.rs index d3a29c3..05213a4 100644 --- a/src/cpu/parse/control.rs +++ b/src/cpu/parse/control.rs @@ -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 ControlFlowInstruction>( +fn build_immediate_addr_parameterized_control_flow_data ControlFlowInstruction>( data: &View, make_variant: F, ) -> ParseOutput { @@ -139,3 +146,15 @@ fn build_immediate_parameterized_control_flow_data 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) +} diff --git a/src/cpu/run/arithutil.rs b/src/cpu/run/arithutil.rs index 4db919d..335d549 100644 --- a/src/cpu/run/arithutil.rs +++ b/src/cpu/run/arithutil.rs @@ -171,6 +171,7 @@ impl CarryingSub> 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 is just a shortcut because we // only use up to u16s diff --git a/src/cpu/run/control.rs b/src/cpu/run/control.rs index 8ab9359..96a0cda 100644 --- a/src/cpu/run/control.rs +++ b/src/cpu/run/control.rs @@ -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()) } diff --git a/tests/cpu/control.rs b/tests/cpu/control.rs index 0587cab..083044e 100644 --- a/tests/cpu/control.rs +++ b/tests/cpu/control.rs @@ -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) { diff --git a/tests/cpu/jsmoo/testdata/disabled/20.json b/tests/cpu/jsmoo/testdata/20.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/20.json rename to tests/cpu/jsmoo/testdata/20.json diff --git a/tests/cpu/jsmoo/testdata/disabled/28.json b/tests/cpu/jsmoo/testdata/28.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/28.json rename to tests/cpu/jsmoo/testdata/28.json diff --git a/tests/cpu/jsmoo/testdata/disabled/30.json b/tests/cpu/jsmoo/testdata/30.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/30.json rename to tests/cpu/jsmoo/testdata/30.json diff --git a/tests/cpu/jsmoo/testdata/disabled/38.json b/tests/cpu/jsmoo/testdata/38.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/38.json rename to tests/cpu/jsmoo/testdata/38.json