dev, computing and games

On WDC 65816, NMI isn't a "real" instruction.

It's a directive ("non-maskable interrupt") typical 65816 assemblers understand.

You use the directive like

    NMI MyHandler

where MyHandler is a label in the source code.

I had trouble finding this information written anywhere plainly, so I'm writing this in case you or someone else is in the same position as me where you need to know.

In response to NMI, the assembler doesn't directly emit any opcodes. What it will do for SNES, anyway, is

  • After assembly, look at the object code address of MyHandler.
    • The short, 16-bit address. Why? Because interrupts have to be in bank 0.
  • Write the address to 0x7FEA or 0xFFEA, depending on whether it's LoROM or HiROM.
  • The rule is that the vectors live in the last page of the first ROM bank, so that's in 0x7FEA for LoROM or 0xFFEA for HiROM.

Fun fact: in Geiger, you can't set a breakpoint to code inside an NMI. Well, you can try, but it won't hit. If you want to see control flow through an NMI, you can use the Logging-CPU feature.

After you assemble and load your program, and execute to something that entails an interrupt, your interrupt handler will get called automagically.

The choice of "what raises NMI" is up to the computer manufacturer. On SNES, the NMI vector is called when VBlank begins. That's just how it is. So sometimes NMI and VBlank get talked about interchangeably on that platform.

And I wish I had known to don't get hardware and software interrupts confused. NMI is different from BRK and COP. Those are software interrupts. They have an instruction, they launch from an opcode. NMI is a hardware interrupt.

For hardware interrupts, the K (program bank reg) gets automatically pushed, then the 16-bit PC is pushed, then the P (processor flags reg) is pushed, then there's this automatic transfer of control to the interrupt handler. You drop everything you're doing and go right to the handler. At least the CPU has the decency to push the execution state beforehand, I guess.

One detail- in emulation mode, the program bank reg does not get pushed. So you probably want to NOT be executing code in a nonzero bank in emulation mode if there's a hardware interrupt, because you don't know where to return to when the interrupt handler is done.

For NMI, the whole thing is no you can't disable it with a processor flag (that is, 'i') so forget trying to disable them either. And since it's a hardware interrupt, it's set up by special vector not by an instruction like BRK or COP. For those software interrupts, you can predict when they'll happen or make it so they're never happen since you know where there's a BRK or COP in your code. Hardware interrupts are different- you can't decide when they occur like that. The condition that raises them is fixed. Fortunately I guess, they're fixed based on something useful like vertical blank. And fair enough, since there are lots of things that are only safe to do during vertical blank.

I haven't had to write code that chases scanlines. I hope you don't either, although it might happen to everyone at some point.

A lot of the SNES reference material I used emphasize 65816 which is great for a while. But you get to a point in debugging SNES where it's not enough to only know things about the CPU. Eventually you will see things in the debugger you can't explain. Since there's more to a computer than just the CPU.

January 30th, 2022 at 10:48 pm