diff --git a/src/cpu.rs b/src/cpu.rs index e7beeee..4a23f3b 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -15,6 +15,7 @@ pub struct Processor { pub registers: Registers, pub memory: Memory, pub num_cycles: u128, + interrupt_enable_pending: bool, } impl Processor { @@ -32,9 +33,17 @@ impl Processor { let (instruction, bytes_read) = parse::next_instruction(&memory_view).expect("invalid instruction"); + let interrupt_enable_pending_already = self.interrupt_enable_pending; self.run_instruction(instruction); - let (next_pc, _carry) = pc.overflowing_add(bytes_read); + // EI does not work until after the following instruction, so we check after running an instruction if + // this is true + if interrupt_enable_pending_already && self.interrupt_enable_pending { + self.interrupt_enable_pending = false; + self.registers.enable_interrupts(); + } + + let next_pc = pc.wrapping_add(bytes_read); self.registers.program_counter = next_pc; } @@ -51,6 +60,20 @@ impl Processor { self.num_cycles += u128::from(run_res.unwrap().0); } + pub fn enable_interrupts(&mut self) { + self.interrupt_enable_pending = true; + } + + pub fn disable_interrupts(&mut self) { + self.interrupt_enable_pending = false; + self.registers.disable_interrupts(); + } + + #[must_use] + pub fn interrupts_enabled(&mut self) -> bool { + self.registers.interrupts_enabled() + } + fn load_from_register_to_register_address( &mut self, register_with_dst_address: register::Combined, diff --git a/src/cpu/instructions/misc.rs b/src/cpu/instructions/misc.rs index 488548b..da6c3a9 100644 --- a/src/cpu/instructions/misc.rs +++ b/src/cpu/instructions/misc.rs @@ -8,4 +8,6 @@ pub enum MiscInstruction { ComplementARegister, DecimalAdjustAccumulator, Nop, + EnableInterrupts, + DisableInterrupts, } diff --git a/src/cpu/parse/misc.rs b/src/cpu/parse/misc.rs index 13b5e7b..826e2e1 100644 --- a/src/cpu/parse/misc.rs +++ b/src/cpu/parse/misc.rs @@ -15,6 +15,8 @@ impl OpcodeParser for Parser { 0x27 => Ok(build_daa_data()), 0x2F => Ok(build_complement_a_register_data()), 0x00 => Ok(build_nop_data()), + 0xFB => Ok(build_enable_interrupts_data()), + 0xF3 => Ok(build_disable_interrupts_data()), _ => Err(super::Error::UnknownOpcode(opcode)), } } @@ -40,8 +42,13 @@ fn build_daa_data() -> ParseOutput { } fn build_nop_data() -> ParseOutput { - ( - Instruction::Misc(MiscInstruction::Nop), - 1, - ) + (Instruction::Misc(MiscInstruction::Nop), 1) +} + +fn build_enable_interrupts_data() -> ParseOutput { + (Instruction::Misc(MiscInstruction::EnableInterrupts), 1) +} + +fn build_disable_interrupts_data() -> ParseOutput { + (Instruction::Misc(MiscInstruction::DisableInterrupts), 1) } diff --git a/src/cpu/run/misc.rs b/src/cpu/run/misc.rs index 4e72cf3..bcd6b63 100644 --- a/src/cpu/run/misc.rs +++ b/src/cpu/run/misc.rs @@ -8,9 +8,7 @@ use super::{Cycles, Run}; impl Run for MiscInstruction { fn run_on(&self, processor: &mut Processor) -> Result { match *self { - MiscInstruction::Nop => { - Ok(Cycles(4)) - } + MiscInstruction::Nop => Ok(Cycles(4)), MiscInstruction::SetCarryFlag => { set_flags_in_carry_bit_instruction(processor, 1); @@ -66,6 +64,18 @@ impl Run for MiscInstruction { Ok(Cycles(4)) } + + MiscInstruction::EnableInterrupts => { + processor.enable_interrupts(); + + Ok(Cycles(4)) + }, + + MiscInstruction::DisableInterrupts => { + processor.disable_interrupts(); + + Ok(Cycles(4)) + } } } } diff --git a/src/register.rs b/src/register.rs index 94035bd..8ea2fc3 100644 --- a/src/register.rs +++ b/src/register.rs @@ -66,6 +66,7 @@ pub struct Registers { pub program_counter: u16, // the "F" register and the "flags" register are the same register flags: u8, + interrupts_enabled: bool, } impl Flag { @@ -99,6 +100,7 @@ impl Default for Registers { e: 0, h: 0, l: 0, + interrupts_enabled: false, stack_pointer: 0, program_counter: INITIAL_PROGRAM_COUNTER_VALUE, flags: 0, @@ -264,6 +266,19 @@ impl Registers { Combined::HL => (&mut self.h, &mut self.l), } } + + pub(super) fn enable_interrupts(&mut self) { + self.interrupts_enabled = true; + } + + pub(super) fn disable_interrupts(&mut self) { + self.interrupts_enabled = false; + } + + #[must_use] + pub fn interrupts_enabled(&self) -> bool { + self.interrupts_enabled + } } #[cfg(test)] diff --git a/tests/cpu/jsmoo/testdata/disabled/f3.json b/tests/cpu/jsmoo/testdata/f3.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/f3.json rename to tests/cpu/jsmoo/testdata/f3.json diff --git a/tests/cpu/jsmoo/testdata/disabled/fb.json b/tests/cpu/jsmoo/testdata/fb.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/fb.json rename to tests/cpu/jsmoo/testdata/fb.json diff --git a/tests/cpu/misc.rs b/tests/cpu/misc.rs index 742c398..3e035b6 100644 --- a/tests/cpu/misc.rs +++ b/tests/cpu/misc.rs @@ -171,3 +171,69 @@ fn test_nop_executes_successfully() { // uhhh it does nothing assert_eq!(processor.num_cycles, 4); } + +#[test] +fn test_enable_interrupts_enables_interrupts_on_the_following_instruction() { + let mut processor = Processor::default(); + + [ + // Enable interrupts + 0xFB, // Nop + 0x00, + ] + .iter() + .copied() + .enumerate() + .for_each(|(idx, opcode)| { + processor + .memory + .set( + usize::from(processor.registers.program_counter) + idx, + opcode, + ) + .expect("could not program data"); + }); + + assert!(!processor.interrupts_enabled()); + + processor.run_next_instruction(); + assert!(!processor.interrupts_enabled()); + + processor.run_next_instruction(); + assert!(processor.interrupts_enabled()); +} + +#[test] +fn test_disable_interrupts_disables_interrupts_once_enabled() { + let mut processor = Processor::default(); + + [ + // Enable interrupts + 0xFB, // Nop + 0x00, + 0xF3, + ] + .iter() + .copied() + .enumerate() + .for_each(|(idx, opcode)| { + processor + .memory + .set( + usize::from(processor.registers.program_counter) + idx, + opcode, + ) + .expect("could not program data"); + }); + + assert!(!processor.interrupts_enabled()); + + processor.run_next_instruction(); + assert!(!processor.interrupts_enabled()); + + processor.run_next_instruction(); + assert!(processor.interrupts_enabled()); + + processor.run_next_instruction(); + assert!(!processor.interrupts_enabled()); +}