I came to this topic looking for information on how the 6502 handles overlapping BRKs, IRQs, and NMIs - I think the visual6502 examples above are a great illustration of this for the older NMOS chips. On those it appears that an NMI, for example, occuring in the middle of a BRK instruction or IRQ sequence, will change the vector that gets loaded at the end of the sequence, to the NMI vector. This is actually a bug, as the program counter has already advanced past the BRK instruction, and it leads to the BRK being skipped.
In addition I found by
modifying the visual6502 example that an NMI that occurs during the vector fetch is actually ignored; also, short low pulses on NMI seem to be ignored, it is necessary for NMI to be low at the falling edge of PHI2 for the low transition to be spotted by the NMOS CPU. This is consistent with the timing documentation in the datasheet even for the CMOS part.
Knowing that the bug in particular was fixed in the WDC 65C02, and that I'm going to be dependent on it, I thought it worth carrying out more tests on the newer hardware, and thought I'd share the results here as it differs quite a bit from the NMOS behaviour. I performed these tests by hooking up an Arduino to a bare 65C02 and having it drive the control signals and data bus appropriately, logging the results.
Firstly, as a reference I wanted to measure the timing behaviour of IRQB - after it goes low, how long is it before the interrupt sequence can begin? It appears that, for a sync cycle to start the interrupt sequence, IRQB needs to have been low back at the start of a full cycle before it (treating a cycle as the low phase followed by the high phase of the PHI2 clock) - i.e. it must have gone low during the high phase of the cycle two before the current cycle, otherwise the current sync cycle will execute its instruction as normal.
Here's a log taken by an Arduino that's carefully driving the CPU at a fairly slow clock rate. Each line is the bus state in the middle of a half-cycle. 'i' means IRQB is low; transitions happen in the middle of the half-cycle, before the state is printed. 'S' means it's a sync cycle.
Code:
15 H -- -S 0007 R ea
16 l i- -- 0008 R ea <= IRQB goes low during phase 1
16 H i- -- 0008 R ea
17 l i- -S 0008 R ea <= Sync, too soon
17 H i- -S 0008 R ea
18 l i- -- 0009 R ea
18 H i- -- 0009 R ea
19 l i- -S 0009 R ea <= Sync, interrupt sequence starts
19 H i- -S 0009 R ea
20 l i- -- 0009 R ea
20 H i- -- 0009 R ea
21 l i- -- 01ff w ea
16 l -- -- 0008 R ea
16 H i- -- 0008 R ea <= IRQB goes low during phase 2
17 l i- -S 0008 R ea <= Sync, too soon
17 H i- -S 0008 R ea
18 l i- -- 0009 R ea
18 H i- -- 0009 R ea
19 l i- -S 0009 R ea <= Sync, interrupt sequence starts
19 H i- -S 0009 R ea
20 l i- -- 0009 R ea
20 H i- -- 0009 R ea
21 l i- -- 01ff w ea
15 l i- -S 0007 R ea <= IRQB goes low during phase 1
15 H i- -S 0007 R ea
16 l i- -- 0008 R ea
16 H i- -- 0008 R ea
17 l i- -S 0008 R ea <= Sync, interrupt sequence starts
17 H i- -S 0008 R ea
18 l i- -- 0008 R ea
18 H i- -- 0008 R ea
19 l i- -- 01ff w ea
15 l -- -S 0007 R ea
15 H i- -S 0007 R ea <= IRQB goes low during phase 2
16 l i- -- 0008 R ea
16 H i- -- 0008 R ea
17 l i- -S 0008 R ea <= Sync, interrupt sequence starts
17 H i- -S 0008 R ea
18 l i- -- 0008 R ea
18 H i- -- 0008 R ea
19 l i- -- 01ff w ea
So there's a baseline for IRQ latency. What about NMIB? Well as I said above, unlike the NMOS version, brief low pulses seem to be enough to trigger an NMI with the CMOS part, so it is clearly no longer being latched on a clock edge. Here are examples of that - 'n' indicates that NMIB was brought low then quickly high again, in the middle of the half-cycle. The timing is substantially the same as for IRQB.
Code:
17 l -n -- 00ff R 00 <= NMIB pulse during phase 1
17 H -- -- 00ff R 00
18 l -- -S 0009 R ea <= Sync, too soon
18 H -- -S 0009 R ea
19 l -- -- 000a R ea
19 H -- -- 000a R ea
20 l -- -S 000a R ea <= Sync, interrupt sequence starts
20 H -- -S 000a R ea
21 l -- -- 000a R ea
21 H -- -- 000a R ea
22 l -- -- 01ff w ea
17 H -n -- 00ff R 00 <= NMIB pulse during phase 2
18 l -- -S 0009 R ea <= Sync, too soon
18 H -- -S 0009 R ea
19 l -- -- 000a R ea
19 H -- -- 000a R ea
20 l -- -S 000a R ea <= Sync, interrupt sequence starts
20 H -- -S 000a R ea
21 l -- -- 000a R ea
21 H -- -- 000a R ea
22 l -- -- 01ff w ea
18 l -n -S 0009 R ea <= NMIB pulse during phase 1
18 H -- -S 0009 R ea
19 l -- -- 000a R ea
19 H -- -- 000a R ea
20 l -- -S 000a R ea <= Sync, interrupt sequence starts
20 H -- -S 000a R ea
21 l -- -- 000a R ea
21 H -- -- 000a R ea
22 l -- -- 01ff w ea
18 H -n -S 0009 R ea <= NMIB pulse during phase 2
19 l -- -- 000a R ea
19 H -- -- 000a R ea
20 l -- -S 000a R ea <= Sync, interrupt sequence starts
20 H -- -S 000a R ea
21 l -- -- 000a R ea
21 H -- -- 000a R ea
22 l -- -- 01ff w ea
Next, here are typical BRK and IRQ sequences for reference:
Code:
22 l -- -S 000b R 00 <= BRK
22 H -- -S 000b R 00
23 l -- -- 000c R 00 <= PC incremented
23 H -- -- 000c R 00
24 l -- -- 01ff w 00
24 H -- -- 01ff w 00
25 l -- -- 01fe w 00
25 H -- -- 01fe w 0d
26 l -- -- 01fd w 0d
26 H -- -- 01fd w 32 <= flags bit 4 is set
27 l -- v- fffe R 00
27 H -- v- fffe R 00
28 l -- v- ffff R 00
28 H -- v- ffff R 00
16 H i- -- 0008 R ff <= IRQ
17 l i- -- 00ff R 00
17 H i- -- 00ff R 00
18 l i- -S 0009 R ea <= Interrupt sequence
18 H i- -S 0009 R ea
19 l i- -- 0009 R ea <= No PC increment
19 H i- -- 0009 R ea
20 l i- -- 01ff w ea
20 H i- -- 01ff w 00
21 l i- -- 01fe w 00
21 H i- -- 01fe w 09
22 l i- -- 01fd w 09
22 H i- -- 01fd w 22 <= flags bit 4 is clear
23 l i- v- fffe R 00
23 H i- v- fffe R 00
24 l i- v- ffff R 00
24 H i- v- ffff R 00
What happens if IRQ and BRK happen at roughly the same time? On the NMOS CPUs the BRK sequence would be converted (mostly) into an IRQ sequence, just with the PC being incremented incorrectly. On the CMOS CPUs it depends which happens first:
Code:
IRQ during BRK - treated as BRK
22 l -- -S 000b R 00 <= BRK
22 H i- -S 000b R 00 <= IRQB low
23 l i- -- 000c R 00 <= PC incremented
23 H i- -- 000c R 00
24 l i- -- 01ff w 00
24 H i- -- 01ff w 00
25 l i- -- 01fe w 00
25 H i- -- 01fe w 0d
26 l i- -- 01fd w 0d
26 H i- -- 01fd w 32 <= flags bit 4 is set
27 l i- v- fffe R 00
27 H i- v- fffe R 00
28 l i- v- ffff R 00
28 H i- v- ffff R 00
IRQ before BRK - treated as IRQ
20 H i- -S 000a R ea <= IRQB low
21 l i- -- 000b R 00
21 H i- -- 000b R 00
22 l i- -S 000b R 00 <= Interrupt sequence
22 H i- -S 000b R 00
23 l i- -- 000b R 00 <= PC not incremented
23 H i- -- 000b R 00
24 l i- -- 01ff w 00
24 H i- -- 01ff w 00
25 l i- -- 01fe w 00
25 H i- -- 01fe w 0b
26 l i- -- 01fd w 0b
26 H i- -- 01fd w 22 <= flags bit 4 clear
27 l i- v- fffe R 00
27 H i- v- fffe R 00
28 l i- v- ffff R 00
28 H i- v- ffff R 00
In the latter case of course the BRK will execute after the RTI from the interrupt handler's response to IRQB.
Finally, how do NMI and IRQ behave when they collide? On NMOS, an NMI during an IRQ sequence changes the vector to the NMI vector, effectively masking the IRQ. If IRQB is still low after the NMI returns then the IRQ sequence will run at that point.
However, on CMOS, if NMI is not triggered soon enough before the interrupt sequence begins, the CPU delays processing the NMI until after the interrupt sequence that's in progress. So we see the IRQ sequence run to completion, followed by an immediate NMI sequence:
Code:
20 H i- -S 000a R ea <= IRQB low
21 l in -- 000b R 00 <= NMI pulse
21 H i- -- 000b R 00
22 l i- -S 000b R 00 <= IRQ sequences starts
22 H i- -S 000b R 00
23 l i- -- 000b R 00 <= No PC increment
23 H i- -- 000b R 00
24 l i- -- 01ff w 00
24 H i- -- 01ff w 00
25 l i- -- 01fe w 00
25 H i- -- 01fe w 0b
26 l i- -- 01fd w 0b
26 H i- -- 01fd w 22
27 l i- v- fffe R 00 <= IRQ vector read
27 H i- v- fffe R 00
28 l i- v- ffff R 00
28 H i- v- ffff R 00
29 l i- -S 0000 R e6 <= NMI sequence starts
29 H i- -S 0000 R e6
30 l i- -- 0000 R e6
30 H i- -- 0000 R e6
31 l i- -- 01fc w e6
31 H i- -- 01fc w 00
32 l i- -- 01fb w 00
32 H i- -- 01fb w 00
33 l i- -- 01fa w 00
33 H i- -- 01fa w 26
34 l i- v- fffa R 00 <= NMI vector read
34 H i- v- fffa R 00
35 l i- v- fffb R 00
35 H i- v- fffb R 00
The same is true for an NMI occuring during a BRK sequence. Essentially, the NMI has to wait until the next viable SYNC cycle, just as it would for any other instruction - the ability to modify the sequence that's already in progress is no longer there in the CMOS version. But even though it doesn't run the NMI sequence straight away, it does remember that NMIB was triggered and an NMI sequence is due, unlike the NMOS CPU in some cases.
I hope this is useful to some people - it has highlighted some issues that would have occured if I'd gone ahead as planned, especially in these differences to the behaviour seen for the NMOS CPU in visual6502. In case anybody else wants to try this out, I'll attach my Arduino code - it's set up for an Arduino Mega.