Add conditional return instructions

old-bit-manip
Nick Krichevsky 2023-11-25 22:02:59 -05:00
parent a3f6025a6f
commit 552884b1cb
8 changed files with 90 additions and 5 deletions

View File

@ -25,4 +25,8 @@ pub enum ControlFlowInstruction {
},
Return,
ReturnAndEnableInterrupts,
ReturnIfFlagMatches {
flag: register::Flag,
value: u8,
},
}

View File

@ -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)),
}
}

View File

@ -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))
}
}
}
}
}

View File

@ -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);
}