diff --git a/src/cpu.rs b/src/cpu.rs index 5b2e09e..a04a38b 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -65,10 +65,15 @@ impl Processor { self.num_cycles += u128::from(run_res.unwrap().0); } - pub fn enable_interrupts(&mut self) { + pub fn stage_interrupt_enable(&mut self) { self.interrupt_enable_pending = true; } + pub fn enable_interrupts_now(&mut self) { + self.interrupt_enable_pending = false; + self.registers.enable_interrupts(); + } + pub fn disable_interrupts(&mut self) { self.interrupt_enable_pending = false; self.registers.disable_interrupts(); diff --git a/src/cpu/instructions/control.rs b/src/cpu/instructions/control.rs index 1ea42ff..a5f868f 100644 --- a/src/cpu/instructions/control.rs +++ b/src/cpu/instructions/control.rs @@ -23,4 +23,6 @@ pub enum ControlFlowInstruction { value: u8, addr: u16, }, + Return, + ReturnAndEnableInterrupts, } diff --git a/src/cpu/parse/control.rs b/src/cpu/parse/control.rs index 486314e..3fbc74a 100644 --- a/src/cpu/parse/control.rs +++ b/src/cpu/parse/control.rs @@ -25,6 +25,7 @@ impl OpcodeParser for Parser { fn parse_unconditional_control_operation(data: &View) -> ParseResult { parse_unconditional_parameterized_control_flow_operation(data) + .or_parse(parse_unconditional_return) .or_parse(parse_restart_instruction) } @@ -58,6 +59,17 @@ fn parse_restart_instruction(opcode: u8) -> ParseResult { Ok((Instruction::ControlFlow(control_flow_instruction), 1)) } +fn parse_unconditional_return(opcode: u8) -> ParseResult { + match opcode { + 0xC9 => Ok((Instruction::ControlFlow(ControlFlowInstruction::Return), 1)), + 0xD9 => Ok(( + Instruction::ControlFlow(ControlFlowInstruction::ReturnAndEnableInterrupts), + 1, + )), + _ => Err(parse::Error::UnknownOpcode(opcode)), + } +} + fn parse_conditional_control_operation(data: &View) -> ParseResult { let opcode = parse::get_opcode_from_data(data); diff --git a/src/cpu/run/control.rs b/src/cpu/run/control.rs index 5594d22..24eec72 100644 --- a/src/cpu/run/control.rs +++ b/src/cpu/run/control.rs @@ -58,6 +58,19 @@ impl Run for ControlFlowInstruction { Ok(Cycles(16)) } + + Self::Return => { + return_to_popped_addr(processor)?; + + Ok(Cycles(16)) + } + + Self::ReturnAndEnableInterrupts => { + processor.enable_interrupts_now(); + return_to_popped_addr(processor)?; + + Ok(Cycles(16)) + } } } } @@ -80,3 +93,7 @@ fn jump_to(processor: &mut Processor, to_addr: u16) { .registers .set_single_16bit_register(register::SingleSixteenBit::ProgramCounter, to_addr); } + +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/src/cpu/run/misc.rs b/src/cpu/run/misc.rs index b239fab..9ad2553 100644 --- a/src/cpu/run/misc.rs +++ b/src/cpu/run/misc.rs @@ -66,7 +66,7 @@ impl Run for MiscInstruction { } MiscInstruction::EnableInterrupts => { - processor.enable_interrupts(); + processor.stage_interrupt_enable(); Ok(Cycles(4)) } diff --git a/tests/cpu/control.rs b/tests/cpu/control.rs index 13066c1..38eb7a3 100644 --- a/tests/cpu/control.rs +++ b/tests/cpu/control.rs @@ -184,3 +184,82 @@ fn test_call_and_jump_does_nothing_if_condition_fails(opcode: u8, flag: Flag, ex // run_instruction does not advance the program counter, so this will be the same. Normally it would be old_pc plus 3 assert_eq!(old_pc, processor.registers.program_counter); } + +#[test_case(0xC9; "RET")] +#[test_case(0xD9; "RETI")] +fn test_ret_moves_pc_to_address_on_stack(opcode: u8) { + let mut processor = Processor::default(); + + let starting_sp = processor.registers.stack_pointer; + processor + .memory + .set_both(( + ((starting_sp - 1).into(), 0x13), + ((starting_sp - 2).into(), 0x37), + )) + .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!(0x1337, processor.registers.program_counter); +} + +#[test_case(0xC9; "RET")] +#[test_case(0xD9; "RETI")] +fn test_ret_moves_sp_two_bytes(opcode: u8) { + let mut processor = Processor::default(); + + let starting_sp = processor.registers.stack_pointer; + processor + .memory + .set_both(( + ((starting_sp - 1).into(), 0x13), + ((starting_sp - 2).into(), 0x37), + )) + .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_sp, processor.registers.stack_pointer); +} + +#[test] +fn test_reti_enables_interrupts_immediately() { + let mut processor = Processor::default(); + + let starting_sp = processor.registers.stack_pointer; + processor + .memory + .set_both(( + ((starting_sp - 1).into(), 0x13), + ((starting_sp - 2).into(), 0x37), + )) + .expect("failed to write to stack"); + + processor.disable_interrupts(); + + processor.registers.stack_pointer -= 2; + + let data = [0xD9, 0x06]; + let (ins, extra_data) = Instruction::from_data(&data).expect("could not parse instruction"); + + assert_eq!(extra_data, &[0x06]); + + processor.run_instruction(ins); + + assert!(processor.interrupts_enabled()); +} diff --git a/tests/cpu/jsmoo/testdata/disabled/c9.json b/tests/cpu/jsmoo/testdata/c9.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/c9.json rename to tests/cpu/jsmoo/testdata/c9.json diff --git a/tests/cpu/jsmoo/testdata/disabled/d9.json b/tests/cpu/jsmoo/testdata/d9.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/d9.json rename to tests/cpu/jsmoo/testdata/d9.json