The ZX Spectrum Z80 interrupts were briefly examined in the Alignment Issues Again page, but it's worth going through the behaviour fully here.
The ZX Spectrum generates an interrupt at the start of each TV frame, so that's one every 50th of a second. The traditional description is that the interrupt occurs when the electron beam is in its fly-back to the top of the screen. Careful measurement has shown that on a standard 48K ZX Spectrum exactly 14336 T-states pass from the interrupt generation to the first display byte is being sent to the TV. Many programs rely on this timing to function properly, and it is often used in conjunction with the floating bus behaviour.
One scanline takes 224 T-states, so 14336 / 224 = 64 scanlines pass between the interrupt generation and the first display byte being sent to the screen. This corresponds to 8 scanlines of vertical sync and 56 scanlines of top-border, and therefore the interrupt should be generated at the start of the vertical sync period, see Vertical Control for details.
Note however that since exactly 14336 T-states are to pass between the interrupt and the first display byte and 14336 is an exact multiple of scanline width, then the interrupt must be generated not at the start of a line, but at the same offset into a line as the first display byte and 64 scanlines earlier.
We have previously defined the end of one line and the start of another as being the at the start of the horizontal sync (horizontal flyback in blue below) (see vertical line count), which according to our revised horizontal timing is 64 T-states earlier than we require for interrupt generation:
|T Start||T End||Length||Description|
To generate a correctly timed interrupt we can use the vertical sync period but delay it by 64 T-states. If we examine the table above we see that the start of the video display portion of a scanline is defined as T0 (when our Horizontal Counter is zero), and as this is the point in a scanline that the interrupt should be generated, instead of delaying VSyncEn by 64 T-states we can wait for HC to become zero.
There is a further shortcut. As the horizontal counter becomes zero on receipt of a horizontal reset signal we don't have to check that HC0 - 8 equals zero, but can use Hrst instead.
The interrupt therefore should be generated when both VSyncEn and Hrst are active.
A Z80 interrupt must not be held active for longer than is necessary as this may lead to the Z80 responding to it multiple times. It is documented somewhere that the ZX Spectrum holds the interrupt active for 32 T-states, which is long enough for all instructions to have time to respond to it, but is still long enough for it to be detected more than once.
The Z80 provides a mechanism for indicating acknowledgement of an interrupt, and I propose to use that to clear the interrupt so that we don't hold it active for longer than required, removing the chance of multiple detection. Sinclair probably chose to implement a 32 T-state interrupt period to avoid having to dedicate a ULA pin to the interrupt acknowledge.
Before coming up with an implementation design, a clear understanding of the identified signals and their relative timing is required:
- Hrst occurs briefly once a scanline.
- VSyncEn goes low once every frame and remains low for eight complete scanlines.
- VSyncEn goes low at the start of a scanline, which is 64 T-states before the next Hrst.
The classic way of latching and clearing a signal is through a flip-flop. Latching the product of VSyncEn + Hrst will give problems because it becomes active eight times per frame (VSyncEn spans eight scan lines), resulting in the generation of eight interrupts per frame.
However I realise the timing of the signals gives a solution. We can use Hrst to clock VSyncEn into a flip-flop. That latching will occur at precisely the desired point in time as we're actually delaying VSyncEn until the low to high transition of Hrst. This delayed signal would remain low for eight scanlines until VSyncEn goes high and the next Hrst is received, at which point the low to high transition of VSyncEn will be clocked into the flip-flop.
We cannot use the output of this flip-flop as our interrupt directly as we need to be able to reset it on acknowledgement, and if this occurs during the eight scanlines that VSyncEn is low, the next Hrst will re-latch the low VSyncEn and generate another interrupt.
Instead, we can use the delayed VSyncEn to clock another flip-flop, and it is from this flip-flop that we take our interrupt signal. This second flip-flop can be reset once the interrupt is acknowledged as it will only be clocked by the first flip-flop once per frame.
The Z80 acknowledges an interrupt by sending M1 and IORQ low at the same time. A simple OR of these two control lines produces the interrupt acknowledge signal.
The interrupt, keyboard interface and additional CPU WAIT schematics are shown in schematics version 1.8.