The Interrupt mechanism in the Gate Array
This document was written by Pierre Guerrier. Thankyou Pierre.
Please send any comments or corrections to him. Document HTMLised by K.E.W.Thacker
This section tries to describe exactly the features of the maskable interrupt generator in the Gate Array. This generator is basically a counter, with some exceptionnal interrupt delay ability. This feature has probably been implemented for several security reasons:
- keep a minimal interval between interrupts, because handlers are usually not reentrant,
- reliable discrimination between internal and external interrupts, the latter requiring special handlers (see this paper by Brice Rivé about that),
- suppress internal interrupts while staying receptive to external interrupts that may require time critical handling.
It is important to note that the Gate Array depends upon the Sync signals generated by the CRTC to compute interrupts. If you apply some demo techniques to the CRTC that suppress Hsyncs (which in itself is dangerous) you will have no more interrupts.
Also if you arrange to have several Hsyncs in a scan (with a pulse length too short to trigger the flyback) this scan will count as several for the Gate Array. And if you suppress or move Vsyncs, you will also suppress the automatic resynchronization of interrupts with the VDU (see below).
My interpretation of the Gate Array interrupt hardware is here
The resulting chronograms are here. Time unit is the Hsync-clock tick.
PAL/SECAM chronogram
NTSC chronogram
Interrupt Delay chronogram
It is organized around a 6 bit counter with general reset, top bit reset, a few latches, and Hsync as a clock for the whole. As a result, when the Vsync signal is raised, it will take one scan before it is sensed, and a pulse of 1 scan in duration will be generated by the senser gate.
This pulse will be echoed to the general counter reset. So the second interrupt will happen 54 (52+2) scans after the Vsync. This is why the first interrupt in a frame occurs 2 scans inside the Vsync (it is actually a remainder of the previous frame, not a direct consequence of this Vsync).
(The Interruption latch, which triggers on the falling edge of its signal, induces a supplementary delay of one scan)
The counter is compared against the constant 51, or 0x110011, and the result of this test is fed back into the general reset. As the reset will be sensed only at the next Hsync (it is the clock) the period of the counter is actually 52 scans.
When the test is true the interrupt latch is also triggered, generating 6 interrupts per frame (52*6 = 312 scans = a PAL/SECAM frame). The last match resets the counter normally exactly at the time the Vsync would reset it (2 scans inside the Vsync pulse).
On US models of the CPC, the frame is 262 scans, so there are only 5 interrupts per frame (the interrupt frequency is always 300 Hz). Moreover, 52*5 = 260, so the Vsync interrupt actually falls exactly when Vsync raises, not 2 scans inside, and the upper screen block is 54 scans high instead of 52 (because the counter is reset by the Vsync as it held the value 2).
The interrupt latch will keep sending an interrupt to the Z80 until it acknowledged or manually cleared. If the Z80 makes a bus request (for some interrupt modes) the value put on the bus is always &FF. When the interrupt is acknowledged or a manual clear occurs, the top bit of the counter will also be cleared.
The manual clear is accessible by sending a 1 in bit 4 of a Gate Array call of the form 0x10... This bit is actually latched for some time by a 4Mhz-clocked gate, to be sensed by the Hsync-clocked circuit I describe.
The next interrupt will be delayed by 32 scans when the top bit of the counter is cleared if it was non-zero. This matches the following conditions:
- the Z80 took more than 32 scans to acknowledge an interrupt (DI state),
- the user got rid of the interrupt with a manual clear at the right moment,
Once one interrupt in a frame has been delayed, all the following "blocks" will be shifted by 32 scans on the screen. Moreover, delays can add up inside a frame: e.g. the last and only interrupt in a frame can be made to fall only 4 scans before the next general reset (that is two scans before the Vsync) by adding up 8 delays (8*32+52 = 308).
You can see that if the Vsync directly triggered the first interrupt in the frame, there would be some very close interrupts ! The role of the Vsync is only to make sure the counter gets reset at some point in the frame, to avoid a derive of the ints.
It is even possible to arrange to have no interrupt around the Vsync: the second interrupt in the frame will always take place at least 54 scans after Vsync raises. Just delay the last interrupt of the previous frame: the usual Vsync interrupt will be pushed to 34 scans inside it, so it will actually never take place because of the general reset: You have no interrupt within 20 scans of the Vsync !
If we put enough delays in the frames so that the counter never gets close to 51, we can simply get rid of internal interrupts.
The Interrupt mechanism in the Z80
The Z80 features 3 interrupt modes, 2 kinds of interrupts with two different pins, 3 kinds of return from subroutine instructions, a nasty EI delay, and some of you probably heard of the strange "two interrupt flip flops" IFF1 and IFF2.
I will try to show you that all this was not meant to annoy authors of emulator, and had design reasons...
Why the three interrupt modes ?
IM0 is for binary compatibility with INTeL 8080/8085 (no, Cyrix and AMD were not the first to clone INTeL, Zilog did it first !)
IM1 is meant to be used in cheap logic boards with nothing around the CPU to put bytes on the bus at interrupt time.
IM2 is a feature Zilog added for more flexibility, but it requires extra logic on the board.
Why the two kinds of Interrupts ?
To allow board designers to hierarchize interrupts. NMIs are always processed, whereas INTs are processed only if IFF1 and IFF2 are on (see why below).
Why the three return opcodes ?
Interrupt handlers are called with RSTs, so why not use RETs all the time ?
The whole story is that Zilog had made a peripheral for the Z80, called the Z80PIO, which could implement smarter hierarchization between INTs from different sources. High priority INT handlers must not be interrupted by lower priority INTs, but how could the PIO know that the last INT he passed to the CPU was handled ? By monitoring the data bus: when it saw a RETI instruction there, it knew the handler was finished.
That's the only difference between RET and RETI: the bit pattern. On the CPC, Amstrad chose the INTel 8255PIO instead of the Z80 PIO, so RET and RETI are really the same thing.
And RETN ? Look at IFFs, you'll understand what it does.
Why the EI delay ?
EI re-enables the maskable interrupts when they had been disabled, e.g. by starting an interrupt handler. The handler must re-enable the INTs when it finishes. Then it will typically do a RETI. But what if the CPU was interrupted again before the RETI ? The handler would be called again, pushing an extra address on the stack.
Zilog designers didn't want that, because they feared that the stack could grow indefinitely if INTs were frequent:
- it could overflow,
- it would slow down handlers that use it to know from which point in the program they were called.
So they implemented the EI delay feature, typically to make the EI, RET sequence atomic. Why didn't they just include the enabling of IFFs in the RETI instruction ? Because actually INTeL had no RETI, it's a Zilog addition, and the EI feature dates back to the 8080. And why did INTeL do that ? Well, you know, if designers always made the right choices right away, we would all have RISC PCs since a long time...
Why the blurb with the two FFs ?
NMI handlers cannot be interrupted by INTs, for that they reset IFF1, which is enough to disable INTs. But they must also restore the state of the INTs when they're done !
So actually, NMIs push IFF1 into IFF2 before resetting it. And NMI handlers should end up with a RETN, that is a RET instruction with the added feature that it pops back IFF2 into IFF1.
This way, if INTs were enabled (both FF = 1 after EI) they will be reenabled and if they were disabled (both FF = 0 after DI) they will stay disabled.
Think it over and you will see that the case where IFF1=1 and IFF2=0 is impossible. IFF1=0 and IFF2=1 happens only when processing a NMI with INTs temporarily disabled. In this case, there can be a problem if a second NMI occurs: INT will not be reenabled by RETN ! It was up to board designers to reserve the NMI pin to *real* emergencies (rare), and use a peripheral like the Z80PIO to hierarchize other requests within INTs.
You cannot learn the value of IFF1 by software. But you can find IFF2 by checking the P flag after LD A,R or LD A,I. I don't think this was really intended by Zilog. This way you can check the interrupt state (or the back up if you're writing a NMI handler).
On the CPC, only expansion peripherals can generate NMI (and among these, I think only the Multiface does).