Interrupts are less complex than you might suppose. If you already understand how a 6502 fetches and executes instructions from memory, that covers a lot of what you need to know. Executing an interrupt is simply a variation on the process of executing an instruction fetched from memory. In fact, it may help to think of an interrupt as an instruction not fetched from memory. It is triggered by the IRQ or NMI input instead. (RESET operates similarly.)
The normal fetch-execute process for 6502 is basically this:
- wait until the currently-executing instruction is (almost*) complete.
- Use the Program Counter to drive the address bus and thus fetch an op-code from memory. The SYNC (or, for 65816, VPA and VDA) output(s) will be high.
- save the op-code internally on-chip
- increment the Program Counter
- perform whatever specific actions are required by the op-code
- wait until the currently-executing instruction is (almost*) complete.
- use the Program Counter to drive the address bus and thus fetch an op-code from memory. The output(s) will be high, even though the opcode won't execute.
- discard the op-code that was fetched. Instead, save a BRK op-code ($00) internally on-chip
- do not increment the Program Counter
- perform whatever specific actions are required by the op-code (in this case, BRK)
The point about not incrementing the Program Counter is simple when you think about it. Since an op-code was fetched but not executed (being pre-empted by BRK), it's necessary that, after the interrupt and its service routine are complete, the opcode be fetched again for another attempt. In order to fetch that same op-code again, the PC (which gets pushed to stack by BRK then restored upon RTI) must be the same -- ie, not incremented.
Re: almost* -- The subtle reality is that many instructions are able to perform their final cycle internally (without using the bus). Exploiting that fact, 65xx CPU's save a cycle by allowing the op-code fetch to occur simultaneously. The internal behavior is hidden, and generally of no relevance -- a curiosity.
It may seem wasteful that an interrupt causes an opcode to be fetched and then discarded. But the internal operation just described necessitates a one-cycle delay anyway, so the wasted fetch is of no consequence.
HTH! Cheers,
Jeff