Interrupt Handling
Interrupt Handling
I just want to clarify a point, make sure I understand this.
The bit in the status register controlling IRQ is a DISABLE bit, so SEI "disables", CLI "enables" (that's not intuitive from a mnemonic perspective).
Assuming CLI has been executed, and the bit is cleared, and IRQ fires.
The CPU then:
o Pushes the PC
o Pushes the status register
o then disables the bit in the status register
So, initially while the interrupt routine is running, interrupts are disabled. (This makes intuitive sense.)
After the routine is done, it calls RTI.
RTI restores the status register, and then jumps back to the PC it stored.
So, I assume that since the interrupt bit was cleared when the interrupt fired (being an IRQ), that the restoration of the status register is what implicitly re-enables the interrupts for future interrupts.
Just wanted to clarify that point on how this all works, as that last parts seems a bit glossed over.
The bit in the status register controlling IRQ is a DISABLE bit, so SEI "disables", CLI "enables" (that's not intuitive from a mnemonic perspective).
Assuming CLI has been executed, and the bit is cleared, and IRQ fires.
The CPU then:
o Pushes the PC
o Pushes the status register
o then disables the bit in the status register
So, initially while the interrupt routine is running, interrupts are disabled. (This makes intuitive sense.)
After the routine is done, it calls RTI.
RTI restores the status register, and then jumps back to the PC it stored.
So, I assume that since the interrupt bit was cleared when the interrupt fired (being an IRQ), that the restoration of the status register is what implicitly re-enables the interrupts for future interrupts.
Just wanted to clarify that point on how this all works, as that last parts seems a bit glossed over.
-
leeeeee
- In Memoriam
- Posts: 347
- Joined: 30 Aug 2002
- Location: UK
- Contact:
Re: Interrupt Handling
Quote:
So, I assume that since the interrupt bit was cleared when the interrupt fired (being an IRQ), that the restoration of the status register is what implicitly re-enables the interrupts for future interrupts.
Lee.
- GARTHWILSON
- Forum Moderator
- Posts: 8775
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Interrupt Handling
Right. You can also re-enable interrupts in the ISR (interrupt-service routine) after you've cleared the original cause of the interrupt. This is not common, but you could do it if for example you wanted a higher-priority interrupt that can be serviced quickly to be able to interrupt the servicing of a lower-priority one. In my system to service interrupts in high-level Forth with no overhead, I actually do a PLA, ORA#4, PHA in order to exit the machine-language ISR without re-enabling interrupts.
Note that the processor comes out of RST with the IRQ-disable bit I set.
See my article on interrupts at if you haven't already.
Note that the processor comes out of RST with the IRQ-disable bit I set.
See my article on interrupts at if you haven't already.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
- BigDumbDinosaur
- Posts: 9428
- Joined: 28 May 2009
- Location: Midwestern USA (JB Pritzker’s dystopia)
- Contact:
Re: Interrupt Handling
whartung wrote:
The bit in the status register controlling IRQ is a DISABLE bit, so SEI "disables", CLI "enables" (that's not intuitive from a mnemonic perspective).
It is intuitive if you think in terms of what the instruction is doing: CLI = CLear the I bit, SEI = SEt the I bit.
x86? We ain't got no x86. We don't NEED no stinking x86!
- GARTHWILSON
- Forum Moderator
- Posts: 8775
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Interrupt Handling
Yes, it is important to think of it as SEt the Interrupt-disable bit and CLear the Interrupt-disable bit. Someone else recently seemed to be making the mistake of thinking of it as "clear interrupts" which is a separate thing, something you have to do by accessing the I/O ICs that produced them, not something you can do in the processor itself.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: Interrupt Handling
Ok, thanks. That's what I thought.
Now, the IRQ pin is set low (?) and that triggers the interrupt. The Interrupt Service Routine (ISR) is supposed to tell the device to reset the interrupt pin before it leaves the routine. If there is more than one device requesting an interrupt, then the pin remains set (low?), and the CPU, once it see CLI fire off, calls the ISR again.
So, what happens after RTI?
RTI pops the Status Register, and this (normally) re-enables interrupts. But the processor has not yet popped off the PC.
Does the processor, effectively, pop the status register, reset the interrupt bit, load the PC from the stack, and THEN notice that IRQ is still set and start the whole thing again? I assume that it's noticing the interrupt as its getting ready to fetch the instruction at the restored PC, so in effect the ISR is called twice with the same values on the stack, the processor never gets a chance to advance.
Now, the IRQ pin is set low (?) and that triggers the interrupt. The Interrupt Service Routine (ISR) is supposed to tell the device to reset the interrupt pin before it leaves the routine. If there is more than one device requesting an interrupt, then the pin remains set (low?), and the CPU, once it see CLI fire off, calls the ISR again.
So, what happens after RTI?
RTI pops the Status Register, and this (normally) re-enables interrupts. But the processor has not yet popped off the PC.
Does the processor, effectively, pop the status register, reset the interrupt bit, load the PC from the stack, and THEN notice that IRQ is still set and start the whole thing again? I assume that it's noticing the interrupt as its getting ready to fetch the instruction at the restored PC, so in effect the ISR is called twice with the same values on the stack, the processor never gets a chance to advance.
- BigDumbDinosaur
- Posts: 9428
- Joined: 28 May 2009
- Location: Midwestern USA (JB Pritzker’s dystopia)
- Contact:
Re: Interrupt Handling
whartung wrote:
Ok, thanks. That's what I thought.
Now, the IRQ pin is set low (?) and that triggers the interrupt. The Interrupt Service Routine (ISR) is supposed to tell the device to reset the interrupt pin before it leaves the routine. If there is more than one device requesting an interrupt, then the pin remains set (low?), and the CPU, once it see CLI fire off, calls the ISR again.
So, what happens after RTI?
RTI pops the Status Register, and this (normally) re-enables interrupts. But the processor has not yet popped off the PC.
Does the processor, effectively, pop the status register, reset the interrupt bit, load the PC from the stack, and THEN notice that IRQ is still set and start the whole thing again? I assume that it's noticing the interrupt as its getting ready to fetch the instruction at the restored PC, so in effect the ISR is called twice with the same values on the stack, the processor never gets a chance to advance.
Now, the IRQ pin is set low (?) and that triggers the interrupt. The Interrupt Service Routine (ISR) is supposed to tell the device to reset the interrupt pin before it leaves the routine. If there is more than one device requesting an interrupt, then the pin remains set (low?), and the CPU, once it see CLI fire off, calls the ISR again.
So, what happens after RTI?
RTI pops the Status Register, and this (normally) re-enables interrupts. But the processor has not yet popped off the PC.
Does the processor, effectively, pop the status register, reset the interrupt bit, load the PC from the stack, and THEN notice that IRQ is still set and start the whole thing again? I assume that it's noticing the interrupt as its getting ready to fetch the instruction at the restored PC, so in effect the ISR is called twice with the same values on the stack, the processor never gets a chance to advance.
The MPU always finishes the current instruction before responding to IRQ or NMI. As RTI is an instruction, it must finish before the MPU will notice that IRQ is low. So, yes, it is possible that the MPU may effectively repeat what it just did as soon as RTI has pulled PC, although presumably a different source would be interrupting.
x86? We ain't got no x86. We don't NEED no stinking x86!
Re: Interrupt Handling
You can see the effect in this simulation:
http://www.visual6502.org/JSSim/expert. ... ogmore=irq
The program is
CLI
INX
INY
and the interrupt handler is just
RTI
You notice that the INX completes, the INY is fetched but subsumed by the interrupt response sequence, and on RTI the INY is again fetched but again subsumed. The Y reg is never incremented.
Cheers
Ed
http://www.visual6502.org/JSSim/expert. ... ogmore=irq
The program is
CLI
INX
INY
and the interrupt handler is just
RTI
You notice that the INX completes, the INY is fetched but subsumed by the interrupt response sequence, and on RTI the INY is again fetched but again subsumed. The Y reg is never incremented.
Cheers
Ed
Re: Interrupt Handling
The described behavior of that simple code sequence when interrupted just doesn't seem right.
It seems to me that something is not right with the behavior described as it corresponds to the INY instruction. First, I am assuming that the IRQ is asserted before CLI. Second, at the completion of CLI, the interrupt is not immediately taken and the INX instruction is fetched and executed: X += 1. Third, at the completion of the INX, the I mask is not set, IRQ is asserted, so the processor takes the interrupt. (I expect that the I bit is cleared in P by CLI not on the falling edge of Phi2 or rising edge of Phi1, but on the rising edge of Phi2. This puts a half cycle delay in the clearing of the I bit, which allows INX to execute.)
The interrupt service routine (ISR) is simply an RTI instruction. To enter the ISR after the INX, the processor does not increment the PC, so it first pushes {PCH, PCL}, which point to INX, and then it pushes P. The processor then updates (sets) the I bit in P and performs a jmp ($FFFE) to get to the RTI instruction of the ISR. The RTI immediately reverses the process, by pulling P from the stack, pulling PCL for the INX instruction and incrementing it, pulling PCH for the INX instruction and incrementing it if a carry occurred (PCL += 1), and then performing a fetch of the INY instruction. INY should execute, and at its completion, before the PC is incremented for the next instruction fetch, the IRQ will be taken because I is not set.
I suppose it's possible that the processor's sequencer evaluates IRQ/NMI before the increment of (pulled) PC is performed. However, given the small number of temporary registers in the 6502, I would expect the increment of PCL to occur while the pull of PCH from the stack is being performed by the RTI/RTS instruction sequence. Then when PCH is ready, it can be incremented if there was a carry during the previous cycle. It also means that the RTI and RTS instructions can share the same return address adjustment sequence.
If the operation of the IRQ is as described, then it means that the processor has just been stopped unless the external logic deasserts IRQ at some point. Is this really the case with the 6502/65C02?
It seems to me that something is not right with the behavior described as it corresponds to the INY instruction. First, I am assuming that the IRQ is asserted before CLI. Second, at the completion of CLI, the interrupt is not immediately taken and the INX instruction is fetched and executed: X += 1. Third, at the completion of the INX, the I mask is not set, IRQ is asserted, so the processor takes the interrupt. (I expect that the I bit is cleared in P by CLI not on the falling edge of Phi2 or rising edge of Phi1, but on the rising edge of Phi2. This puts a half cycle delay in the clearing of the I bit, which allows INX to execute.)
The interrupt service routine (ISR) is simply an RTI instruction. To enter the ISR after the INX, the processor does not increment the PC, so it first pushes {PCH, PCL}, which point to INX, and then it pushes P. The processor then updates (sets) the I bit in P and performs a jmp ($FFFE) to get to the RTI instruction of the ISR. The RTI immediately reverses the process, by pulling P from the stack, pulling PCL for the INX instruction and incrementing it, pulling PCH for the INX instruction and incrementing it if a carry occurred (PCL += 1), and then performing a fetch of the INY instruction. INY should execute, and at its completion, before the PC is incremented for the next instruction fetch, the IRQ will be taken because I is not set.
I suppose it's possible that the processor's sequencer evaluates IRQ/NMI before the increment of (pulled) PC is performed. However, given the small number of temporary registers in the 6502, I would expect the increment of PCL to occur while the pull of PCH from the stack is being performed by the RTI/RTS instruction sequence. Then when PCH is ready, it can be incremented if there was a carry during the previous cycle. It also means that the RTI and RTS instructions can share the same return address adjustment sequence.
If the operation of the IRQ is as described, then it means that the processor has just been stopped unless the external logic deasserts IRQ at some point. Is this really the case with the 6502/65C02?
Michael A.
Re: Interrupt Handling
First, I should say that I was slightly surprised because I'd had the idea that a single instruction would be executed after the RTI(*). But, second, I should say that I have a very high degree of confidence in the simulation. If it doesn't match our expectations, we need to think harder!
I added a couple of NOPs to check that nothing changes - indeed, nothing changes. You can see I've tabulated 'irq'.
In a sense, I think this is not quite right. I think the processor was already committed to take the interrupt.Most things change at the fall of phi2/rise of phi1. Indeed, you can see the I bit changes at the beginning of cycle2.In fact, no - this is probably the crux - PC has already been incremented to 3Conceptually it executes a jmp indirect, but (as discussed elsewhere) it's actually executing something rather like a BRKActually no - PC was already pre-incrementedIndeed, we see INY is (re)fetchedBut no, it is subsumed by the IR being forced to zero, which causes the BRK-like subsequent behaviour
Hope this helps somewhat!
Cheers
Ed
(*)In fact, I think what I was misremembering might be the way that the instruction after a CLI will always be executed - precisely because of the pipelining, which means the next instruction is underway as the I bit is cleared.
MichaelM wrote:
The described behavior of that simple code sequence when interrupted just doesn't seem right.
It seems to me that something is not right with the behavior described as it corresponds to the INY instruction. First, I am assuming that the IRQ is asserted before CLI.
It seems to me that something is not right with the behavior described as it corresponds to the INY instruction. First, I am assuming that the IRQ is asserted before CLI.
Quote:
Second, at the completion of CLI, the interrupt is not immediately taken and the INX instruction is fetched and executed: X += 1. Third, at the completion of the INX, the I mask is not set, IRQ is asserted, so the processor takes the interrupt.
Quote:
(I expect that the I bit is cleared in P by CLI not on the falling edge of Phi2 or rising edge of Phi1, but on the rising edge of Phi2. This puts a half cycle delay in the clearing of the I bit, which allows INX to execute.)
Quote:
The interrupt service routine (ISR) is simply an RTI instruction. To enter the ISR after the INX, the processor does not increment the PC, so it first pushes {PCH, PCL}, which point to INX
Quote:
..., and then it pushes P. The processor then updates (sets) the I bit in P and performs a jmp ($FFFE) to get to the RTI instruction of the ISR.
Quote:
The RTI immediately reverses the process, by pulling P from the stack, pulling PCL for the INX instruction and incrementing it,
Quote:
... pulling PCH for the INX instruction and incrementing it if a carry occurred (PCL += 1), and then performing a fetch of the INY instruction.
Quote:
INY should execute,
Quote:
... and at its completion...
Cheers
Ed
(*)In fact, I think what I was misremembering might be the way that the instruction after a CLI will always be executed - precisely because of the pipelining, which means the next instruction is underway as the I bit is cleared.
Last edited by BigEd on Sun Dec 16, 2012 4:43 pm, edited 2 times in total.
Re: Interrupt Handling
In case anyone is unable to run that simulation, and for ease of reference, here's the tabulation it produces:
Code: Select all
cycle
ab db rw Fetch pc a x y s p irq
0 0001 58 1 CLI 0001 aa 00 00 fd nv‑BdIZc 1
0 0001 58 1 CLI 0001 aa 00 00 fd nv‑BdIZc 1
1 0002 e8 1 0002 aa 00 00 fd nv‑BdIZc 1
1 0002 e8 1 0002 aa 00 00 fd nv‑BdIZc 0
2 0002 e8 1 INX 0002 aa 00 00 fd nv‑BdiZc 0
2 0002 e8 1 INX 0002 aa 00 00 fd nv‑BdiZc 0
3 0003 c8 1 0003 aa 00 00 fd nv‑BdiZc 0
3 0003 c8 1 0003 aa 00 00 fd nv‑bdiZc 0
4 0003 c8 1 INY 0003 aa 00 00 fd nv‑bdiZc 0
4 0003 c8 1 INY 0003 aa 00 00 fd nv‑bdiZc 0
5 0003 c8 1 0003 aa 01 00 fd nv‑bdizc 0
5 0003 c8 1 0003 aa 01 00 fd nv‑bdizc 0
6 01fd c8 0 0003 aa 01 00 fd nv‑bdizc 0
6 01fd 00 0 0003 aa 01 00 fd nv‑bdizc 0
7 01fc c8 0 0003 aa 01 00 fd nv‑bdizc 0
7 01fc 03 0 0003 aa 01 00 fd nv‑bdizc 0
8 01fb c8 0 0003 aa 01 00 fd nv‑bdizc 0
8 01fb 20 0 0003 aa 01 00 fd nv‑bdizc 0
9 fffe 00 1 0003 aa 01 00 fa nv‑bdizc 0
9 fffe 00 1 0003 aa 01 00 fa nv‑Bdizc 0
10 ffff 00 1 0003 aa 01 00 fa nv‑BdIzc 0
10 ffff 00 1 0003 aa 01 00 fa nv‑BdIzc 0
11 0000 40 1 RTI 0000 aa 01 00 fa nv‑BdIzc 0
11 0000 40 1 RTI 0000 aa 01 00 fa nv‑BdIzc 0
12 0001 58 1 0001 aa 01 00 fa nv‑BdIzc 0
12 0001 58 1 0001 aa 01 00 fa nv‑BdIzc 0
13 01fa 00 1 0002 aa 01 00 fa nv‑BdIzc 0
13 01fa 00 1 0002 aa 01 00 fa nv‑BdIzc 0
14 01fb 20 1 0002 aa 01 00 fa nv‑BdIzc 0
14 01fb 20 1 0002 aa 01 00 fa nv‑BdIzc 0
15 01fc 03 1 0002 aa 01 00 fa nv‑Bdizc 0
15 01fc 03 1 0002 aa 01 00 fa nv‑Bdizc 0
16 01fd 00 1 0002 aa 01 00 fd nv‑Bdizc 0
16 01fd 00 1 0002 aa 01 00 fd nv‑bdizc 0
17 0003 c8 1 INY 0003 aa 01 00 fd nv‑bdizc 0
17 0003 c8 1 INY 0003 aa 01 00 fd nv‑bdizc 0
18 0003 c8 1 0003 aa 01 00 fd nv‑bdizc 0
18 0003 c8 1 0003 aa 01 00 fd nv‑bdizc 0
19 01fd c8 0 0003 aa 01 00 fd nv‑bdizc 0
19 01fd 00 0 0003 aa 01 00 fd nv‑bdizc 0
20 01fc c8 0 0003 aa 01 00 fd nv‑bdizc 0
20 01fc 03 0 0003 aa 01 00 fd nv‑bdizc 0
21 01fb c8 0 0003 aa 01 00 fd nv‑bdizc 0
21 01fb 20 0 0003 aa 01 00 fd nv‑bdizc 0
22 fffe 00 1 0003 aa 01 00 fa nv‑bdizc 0
22 fffe 00 1 0003 aa 01 00 fa nv‑Bdizc 0
23 ffff 00 1 0003 aa 01 00 fa nv‑BdIzc 0
23 ffff 00 1 0003 aa 01 00 fa nv‑BdIzc 0
24 0000 40 1 RTI 0000 aa 01 00 fa nv‑BdIzc 0
24 0000 40 1 RTI 0000 aa 01 00 fa nv‑BdIzc 0Re: Interrupt Handling
Since I had never looked at this scenario, I was curious how my core would do. It looks very similar. The RTI takes 6 cycles. In the 3rd cycle, the flags are read from the stack, and in the 4th cycle, the I bit is cleared. As soon as that happens, the IR bits are zeroed, simulating a BRK instruction. It still tries to read the next instruction from memory, and the following byte, but then it hits the DECODE state, and the BRK takes preference.
Normally, the IRQ handler takes care of removing the source of the interrupt, so this is not a problem.
Quote:
If the operation of the IRQ is as described, then it means that the processor has just been stopped unless the external logic deasserts IRQ at some point. Is this really the case with the 6502/65C02?
Re: Interrupt Handling
I can see from your state vector listing the points you are making. It's still less than satisfying to know that if IRQ is continuously asserted, then the second instruction past the CLI does not appear to execute unless the source of the IRQ is forced to deassert for at least one instruction cycle time in the ISR.
I have generally been unsatisfied with this type of behavior for level activated interrupts, so in the M65C02 core, I have forced all program flow control instructions (plus the SEI/CLI instructions) to be non-interruptable. That is, the target instruction of any branch, jump, or return is always executed. A self-referencing loop, i.e. jmp $, will not be interruptable, but the WAI instruction is a suitable replacement for such a construct.
With that simple change, the issue WRT INY becomes moot.
I have generally been unsatisfied with this type of behavior for level activated interrupts, so in the M65C02 core, I have forced all program flow control instructions (plus the SEI/CLI instructions) to be non-interruptable. That is, the target instruction of any branch, jump, or return is always executed. A self-referencing loop, i.e. jmp $, will not be interruptable, but the WAI instruction is a suitable replacement for such a construct.
With that simple change, the issue WRT INY becomes moot.
Michael A.
Re: Interrupt Handling
Interesting design choice!
Re: Interrupt Handling
I think that the 6502's behavior is the preferred one. From a systems standpoint, you want the interrupt handling to be as quick as possible.