From 552884b1cb85fbba3bf1ee1fd0d8e29fe015964a Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Sat, 25 Nov 2023 22:02:59 -0500 Subject: [PATCH] Add conditional return instructions --- src/cpu/instructions/control.rs | 4 ++ src/cpu/parse/control.rs | 14 ++-- src/cpu/run/control.rs | 9 +++ tests/cpu/control.rs | 68 ++++++++++++++++++- .../cpu/jsmoo/testdata/{disabled => }/c0.json | 0 .../cpu/jsmoo/testdata/{disabled => }/c8.json | 0 .../cpu/jsmoo/testdata/{disabled => }/d0.json | 0 .../cpu/jsmoo/testdata/{disabled => }/d8.json | 0 8 files changed, 90 insertions(+), 5 deletions(-) rename tests/cpu/jsmoo/testdata/{disabled => }/c0.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/c8.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/d0.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/d8.json (100%) diff --git a/src/cpu/instructions/control.rs b/src/cpu/instructions/control.rs index a5f868f..24c9000 100644 --- a/src/cpu/instructions/control.rs +++ b/src/cpu/instructions/control.rs @@ -25,4 +25,8 @@ pub enum ControlFlowInstruction { }, Return, ReturnAndEnableInterrupts, + ReturnIfFlagMatches { + flag: register::Flag, + value: u8, + }, } diff --git a/src/cpu/parse/control.rs b/src/cpu/parse/control.rs index 3fbc74a..a07251a 100644 --- a/src/cpu/parse/control.rs +++ b/src/cpu/parse/control.rs @@ -14,6 +14,7 @@ pub struct Parser; enum ConditionalControlFlowType { Call, Jump, + Return, } impl OpcodeParser for Parser { @@ -84,6 +85,10 @@ fn parse_conditional_control_operation(data: &View) -> ParseResult { data, |addr| ControlFlowInstruction::CallIfFlagMatches { flag, value, addr }, )), + ConditionalControlFlowType::Return => Ok(( + Instruction::ControlFlow(ControlFlowInstruction::ReturnIfFlagMatches { flag, value }), + 1, + )), } } @@ -93,16 +98,17 @@ fn conditional_control_flow_type_for_opcode( match opcode { 0xC2 | 0xD2 | 0xCA | 0xDA => Ok(ConditionalControlFlowType::Jump), 0xC4 | 0xD4 | 0xCC | 0xDC => Ok(ConditionalControlFlowType::Call), + 0xC0 | 0xD0 | 0xC8 | 0xD8 => Ok(ConditionalControlFlowType::Return), _ => Err(parse::Error::UnknownOpcode(opcode)), } } fn conditional_control_flow_flag_for_opcode(opcode: u8) -> Result<(Flag, u8), parse::Error> { match opcode { - 0xC2 | 0xC4 => Ok((Flag::Zero, 0)), - 0xD2 | 0xD4 => Ok((Flag::Carry, 0)), - 0xCA | 0xCC => Ok((Flag::Zero, 1)), - 0xDA | 0xDC => Ok((Flag::Carry, 1)), + 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)), _ => Err(parse::Error::UnknownOpcode(opcode)), } } diff --git a/src/cpu/run/control.rs b/src/cpu/run/control.rs index 24eec72..588d280 100644 --- a/src/cpu/run/control.rs +++ b/src/cpu/run/control.rs @@ -71,6 +71,15 @@ impl Run for ControlFlowInstruction { Ok(Cycles(16)) } + + Self::ReturnIfFlagMatches { flag, value } => { + if processor.registers.get_flag_bit(flag) == value { + return_to_popped_addr(processor)?; + Ok(Cycles(20)) + } else { + Ok(Cycles(8)) + } + } } } } diff --git a/tests/cpu/control.rs b/tests/cpu/control.rs index 38eb7a3..d47debf 100644 --- a/tests/cpu/control.rs +++ b/tests/cpu/control.rs @@ -187,7 +187,7 @@ fn test_call_and_jump_does_nothing_if_condition_fails(opcode: u8, flag: Flag, ex #[test_case(0xC9; "RET")] #[test_case(0xD9; "RETI")] -fn test_ret_moves_pc_to_address_on_stack(opcode: u8) { +fn test_ret_jumps_to_address_on_stack(opcode: u8) { let mut processor = Processor::default(); let starting_sp = processor.registers.stack_pointer; @@ -263,3 +263,69 @@ fn test_reti_enables_interrupts_immediately() { assert!(processor.interrupts_enabled()); } + +#[test_case(0xC8, Flag::Zero, 1; "RET Z")] +#[test_case(0xD8, Flag::Carry, 1; "RET C")] +#[test_case(0xC0, Flag::Zero, 0; "RET NZ")] +#[test_case(0xD0, Flag::Carry, 0; "RET NC")] +fn test_conditional_ret_jumps_to_address_on_stack(opcode: u8, flag: Flag, expected_value: u8) { + let mut processor = Processor::default(); + processor.registers.set_flag_bit(flag, expected_value); + + let starting_sp = processor.registers.stack_pointer; + processor + .memory + .set_both(( + ((starting_sp - 1).into(), 0xBE), + ((starting_sp - 2).into(), 0xEF), + )) + .expect("failed to write to stack"); + + processor.registers.stack_pointer -= 2; + + let data = [opcode, 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!(0xBEEF, processor.registers.program_counter); +} + +#[test_case(0xC8, Flag::Zero, 1; "RET Z")] +#[test_case(0xD8, Flag::Carry, 1; "RET C")] +#[test_case(0xC0, Flag::Zero, 0; "RET NZ")] +#[test_case(0xD0, Flag::Carry, 0; "RET NC")] +fn test_conditional_ret_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 starting_pc = processor.registers.program_counter; + let starting_sp = processor.registers.stack_pointer; + processor + .memory + .set_both(( + ((starting_sp - 1).into(), 0xBE), + ((starting_sp - 2).into(), 0xEF), + )) + .expect("failed to write to stack"); + + processor.registers.stack_pointer -= 2; + + let data = [opcode, 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!(starting_pc, processor.registers.program_counter); + assert_eq!(starting_sp - 2, processor.registers.stack_pointer); +} diff --git a/tests/cpu/jsmoo/testdata/disabled/c0.json b/tests/cpu/jsmoo/testdata/c0.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/c0.json rename to tests/cpu/jsmoo/testdata/c0.json diff --git a/tests/cpu/jsmoo/testdata/disabled/c8.json b/tests/cpu/jsmoo/testdata/c8.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/c8.json rename to tests/cpu/jsmoo/testdata/c8.json diff --git a/tests/cpu/jsmoo/testdata/disabled/d0.json b/tests/cpu/jsmoo/testdata/d0.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/d0.json rename to tests/cpu/jsmoo/testdata/d0.json diff --git a/tests/cpu/jsmoo/testdata/disabled/d8.json b/tests/cpu/jsmoo/testdata/d8.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/d8.json rename to tests/cpu/jsmoo/testdata/d8.json