Add conditional return instructions
parent
a3f6025a6f
commit
552884b1cb
|
@ -25,4 +25,8 @@ pub enum ControlFlowInstruction {
|
|||
},
|
||||
Return,
|
||||
ReturnAndEnableInterrupts,
|
||||
ReturnIfFlagMatches {
|
||||
flag: register::Flag,
|
||||
value: u8,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue