Does interrupt check happen after fetch?

Discussion of programming and development for the original Game Boy and Game Boy Color.
Post Reply
User avatar
zeroone
Posts: 929
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Does interrupt check happen after fetch?

Post by zeroone » Fri Dec 15, 2017 7:04 pm

Here's the fetch function from Gekkio's source:

Code: Select all

  fn fetch_cycle<H: Bus>(&mut self, bus: &mut H) {
    let result = bus.fetch_cycle(self.regs.pc);
    let interrupt = match self.ime {
      Ime::Enabled => result.interrupt,
      Ime::Enabling => {
        self.ime = Ime::Enabled;
        false
      },
      _ => false,
    };
    if interrupt {
      self.dispatch_interrupt(bus)
    } else {
      self.regs.pc = self.regs.pc.wrapping_add(1);
      ops::decode((self, bus), result.opcode)
    }
  }
It appears to perform the interrupt check after fetch rather than in between instructions. Is that actually the case? And, does this mean that a return from interrupt jumps to the byte after the opcode?

User avatar
zeroone
Posts: 929
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: Does interrupt check happen after fetch?

Post by zeroone » Sat Dec 16, 2017 12:02 pm

I studied the code a bit further. Here's the Bus fetch_cycle function:

Code: Select all

  fn fetch_cycle(&mut self, addr: u16) -> FetchResult {
    let interrupt = self.has_interrupt();
    self.emu_time += EmuDuration::machine_cycles(1);
    self.emulate();

    let opcode = self.read_internal(addr);
    FetchResult {
      opcode,
      interrupt,
    }
  }
self.emulate() steps the OAM module, the timer, the GPU and the APU. That step potentially transfers 1 byte via OAM, it increments the timer register by 4, etc. In other words, the opcode read takes 1 M-cycle, as expected.

However, before it calls self.emulate(), it checks the interrupt flags. Meaning, if the interrupt flags change during the read M-cycle, that change is not reflected in the returned pair.

Again, here's the code that invokes it:

Code: Select all

  fn fetch_cycle<H: Bus>(&mut self, bus: &mut H) {
    let result = bus.fetch_cycle(self.regs.pc);
    let interrupt = match self.ime {
      Ime::Enabled => result.interrupt,
      Ime::Enabling => {
        self.ime = Ime::Enabled;
        false
      },
      _ => false,
    };
    if interrupt {
      self.dispatch_interrupt(bus)
    } else {
      self.regs.pc = self.regs.pc.wrapping_add(1);
      ops::decode((self, bus), result.opcode)
    }
  }
Note that if an interrupt did not occur, then PC is incremented and the opcode is decoded, completing the fetch.

But, if an interrupt occurs, then PC is not incremented. Rather, this happens:

Code: Select all

  fn dispatch_interrupt<H: Bus>(&mut self, bus: &mut H) {
    self.halt = false;
    self.ime = Ime::Disabled;
    self.internal_cycle(bus);
    self.internal_cycle(bus);
    let pc = self.regs.pc;
    self.push_u8(bus, (pc >> 8) as u8);
    let interrupt = bus.ack_interrupt();
    self.push_u8(bus, pc as u8);
    self.regs.pc = interrupt.map(|i| i.get_addr()).unwrap_or(0x0000);
  }
Both self.internal_cycle() and self.push_u8() consume an M-cycle. So, the opcode read + the 2 internal cycles + the 2 push cycles = 5 M-cycles, the expected duration of interrupt handling. It's just very odd that the opcode read cycle happens as part of that chain rather than just having 3 internal cycles prior to the 2 pushes.

It's also interesting that the IF bit is cleared in between the pushes. I wonder if that was motivated by some timing test.

Long story short, all the timings make sense. It does check for interrupts in between instructions. And, of course, it handles interrupts only in between instructions. What is unusual is that the opcode read happens even when the interrupt is handled. Since it's reading from PC as opposed to a memory mapped register address, it seems unlikely that opcode read would make much of a difference as compared to an idle M-cycle. That said, why the code was written this was is unclear.

User avatar
zeroone
Posts: 929
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: Does interrupt check happen after fetch?

Post by zeroone » Sat Dec 16, 2017 2:59 pm

I just realized that Gekkio's emulator doesn't pass most of the test roms that I was actually interested in. Gekkio created the tests, but doesn't provide the solutions :( The strange code above might not explain much at all.

Post Reply