Sunday 15 October 2023

How the ZX81 Generates Video

Now the Minstrel kits are back in stock, it's time for the second in this series of posts from my Patreon, this one covering the video output of the ZX81.

Following on from the previous post which covered the inner workings of the ZX80, today we look at the changes made for the ZX81.

Most of the video generation circuitry is the same as that in the ZX80, so please refer to the previous post if you have not already seen it.

Before discussing the actual changes, I should first address the biggest practical change. All of the TTL logic chips used in the ZX80, as well as all the logic required to implement the other changes have all been contained within a single IC. The ULA (Uncommitted Logic Array), an early form of CPLD, in this case a factory mask programmed array of logic gates. This is a very early one, with just a date code.

The new ZX81 board (right) was a lot smaller and simpler than the ZX80 (left) thanks to most of the chips being replaced by the ULA.

The exact implementation of the new features is not obvious from looking at the board or the schematic. It has been reverse engineered by various people over the years, most notably Grant Searle who's website is a mine of information for all things 8-bit single board computer and ZX80/81 related..

Other than the cost reduction and simplification of the ZX81 into essentially a 4 chip computer, the main aim was to get rid of the flicker whenever a key was pressed or code was run.

The Z80 in the ZX80 was usually in one of two states, it was either drawing the screen, or running code. 

Since the Z80 is so intricately involved in generating the screen, it cannot run code at the same time. When it has to run code, it can not generate a screen. This leads to anything from a slight flicker when you press a key to extended periods where no video signal is generated as user code is executing.

A modern TV will switch to "No signal" after a few seconds of that. 

(you still get a black screen on an old TV, it would only be snow if the modulator was shut down, but it's difficult to take a picture of a black screen, so here is a picture of some snow in case you are feeling festive)

To get around that, the ZX81 added a new mode. The ZX80 style mode became known as "fast" mode. A mode where code is run at full speed, but cannot generate a screen.

The ZX81's new mode was the corresponding "slow" mode. Here, code is run when the blank areas at the top and bottom of the screen are being generated. The rest of the time, the screen is generated as normal. Code is only run for a short amount of time, hence the "slow" mode.

The USA models had fewer blank lines each frame (64 vs 112), so slow mode on a TS1000 was even slower than on a ZX81.

The "fast" mode was still available if a lot of code needed to be run quickly, a notable example being the "mists of time" when the maze is generated in 3D Monster Maze (oddly, this is mainly due to that section being written in BASIC, unlike the rest of the game).

The new version of BASIC for the ZX81 was double the size, an 8K ROM, with trig functions and a floating point number system. It also had an updated font (see ZX80 post for examples of the change to the K character).

It added lots of functions, and moved many of the existing ones around, into more familiar places such as PRINT and " on the P key, and LOAD on J.

There were also keywords to enter the new SLOW mode and FAST mode.

So, how does it work?

A quick recap of how the ZX80 generates normal lines:

The Z80 is setup to read a line of characters in the display file. It progresses through that line, incrementing the refresh counter for each character, until the new line character is reached. This halts the Z80 and it remains halted for a short time until woken by an interrupt at the end of the line.

HSync indicates the start of a line, /Halt goes low whilst halted, and the IntAck pulses show where the interrupt is acknowledged. (I am not showing /Int here as it is tied to A6 which does a lot of other things, so it does not help)

The blank lines at the top and bottom of the display are generated by pointing at the start of the DFILE which is a newline character, so it immediately halts and starts to run NOP instructions internally.

It remains halted for the rest of the line, until it is again woken by an interrupt.

That is a lot of time spent halted, and this wasted time is used on the ZX81 to execute user code.

Previously the mechanism to generate the interrupt at the end of the line relied on the Z80 refresh counter counting up to a point where A6 was low and an interrupt was triggered.

Using the refresh counter relies on the instructions all being the same number of cycles (they are NOP instructions). User code will contain a variety of instructions and the number of cycles each instruction uses varies, so this will no longer be a constant time.

To get around this, the ZX81 added a counter which is reset at the start of each line. When it counts 208 cycles, it is reset and the next line begins. The start of each line generates the horizontal sync pulse, and when configured to do so, a Non Maskable Interrupt that will interrupt user code and increment an internal counter of how many lines have been drawn.

NMI generator enable

To control if the NMI signals should be generated, a latch is used. This is set by OUT FE,A, and cleared by OUT FD,A. These calls are generated by the OS as part of generating the display.

Looking at a whole frame, you can see the NMI generator is enabled in the sections at the top and bottom of the screen.

Why is the generator disabled during the VSync pulse? well that is when the keyboard is scanned, as it was in the ZX80. The first keyboard read is actually the trigger for the VSync pulse (see the ZX80 post for further details).

The NMI Generator enabled signal (here shown as NMI_On) is used to gate the HSync signal to create an /NMI pulse at the same time.

/Wait is also pulled low, to ensure the Z80 is in the correct T-State when the NMI is serviced. This is gated by the /Halt signal, which would always be high as that is normally triggered by processing the End of Line character, which will not happen on the non-visible lines.

I have drawn the /Wait signal generator using a NAND gate (as it is in the Minstrel 3), but that is not the way it was implemented on the ZX81. The ULA contains all the counters and logic to create the /NMI pulse, but the /Wait signal was implemented externally with a single transistor and some passive components.

It looks a little odd in the ZX81 schematic, so I have redrawn it differently to make it easier to see what is happening. For the moment, imagine the emitter was connected to 0V instead of /NMI. It is then a simple inverter. When /Halt is low, the transistor is off, so /Wait is pulled high by R17. When /Halt is high, the transistor switches on and pulls /Wait low. Now, add in the complication that the emitter is actually tied to /NMI. No current can flow in the transistor unless /Halt is high and /NMI is low, in which case, /Wait is also pulled low. (what is the capacitor doing there? it speeds up the switching time of the transistor).

Here you can see the last few visible lines, the NMI generator being enabled and then the first lines where user code is run.

During the text lines, /Halt is used to halt the Z80 until it is at the end of the line. This stops when user code is running, and /NMI pulses take over.

HSync counter

HSync pulses are generated based on a self resetting counter. The counter is reset when outputs Q0, Q1, Q2, Q3, Q6 and Q7 are high. The first time this is reached, Q4 and Q5 will be low, so the value will be 1100 1111, 0xCF, or 207. So it counts from 0 to 207 based on a 3,25MHz clock. 3,250,000 / 208 = 15,625. The gives a line frequency of 15.625KHz, a period of 64uS, exactly as required.

Q4 and Q5 are not tested, so the pulse would be generated at CF, DF, EF and FF, but the counter should be reset the first time the other outputs are all high at CF, so it is not necessary to test those.

I have drawn the schematic using ideal parts, to make it easier to follow. For example, the counter is actually two 4 bit counters chained together, and you can't get a 6 input AND gate, so in practice a triple 3 input AND gate or dual 4 input AND gate would be used, but that makes it a little more difficult to follow.

For the text lines, the end of the line causes an interrupt to fire. Once this is processed, an Interrupt Acknowledge signal is generated. The IntAck appears to arrive just before the counter would reach 208, so the count = 208 pulse is not fired for text lines. Both the IntAck and counter=208 pulses should reset the counter, so they are ORed to create the counter reset pulse.

The counter is tapped at the Q3 point to get a 16 cycle clock, a pulse every 4.92uS. A series of flip flops is then used to generate some pulses based on this. HSync needs to start after 16 cycles, and last 16 cycles, so the following logic is used to create the appropriate 16 cycle / 4.92uS pulse.

Fast Mode

In "fast" mode, (the only option in the ZX80) the Z80 is dedicated to running code, so the screen does not get updated.

The 208 cycle counter is still running and generating HSync pulses, but there is no activity on the other signals.

The NMI generator is not enabled, so no /NMI pulses are generated. The keyboard is being periodically checked, which activates the VSync, so that signal is permanently stuck low, and the composite video signal is flat. Yellow is composite video, blue shows the HSync signal.

Disabling VSync Generation

When blank lines are being generated and user code being run, the VSync generation circuitry is disabled. An AND gate is added to stop a user keyboard read from starting a VSync pulse in the middle of a line.

A NOR gate is used to interrupt the signal which loads the shift register, so the output remains low, which translates to a white line on the screen.

The HSync signal generated by the old hardware is no longer used, so in the Minstrel 3 I omitted the circuit. That means only VSync is used as the save signal now, with no glitches caused by the HSync circuitry.

Back Porch

On the Minstrel 3, I added an extra flip flop to the circuit that generates the HSync pulse. This is an additional 16 cycle pulse directly after the HSync, and is used to create the missing back porch of the video signal. On the Minstrel 2, this was generated using an RC circuit, but I prefer the this approach.

Here you can see the three pulses generated at the start of each line.

The timings are close enough to the requirements, so that works well.

Ideal Timing
Actual Timings
Horizontal Sync
Back Porch

Here you can see the /Hsync in blue and the back porch in green and the resulting composite video signal in yellow at the end of a line.

The later ZX81 2C210E ULA also had a back porch signal, probably generated in a similar way.

The first pulse on the above schematic is marked as the front porch, although it is not used. That could have been generated and ANDed with the back porch signal to generate a proper front and back porch, but in practice, it does not seem to matter. The timing would also need adjusting. The front porch should be approximately 1.65μS, so that 16 cycle pulse would need to be gated with the lower bits of the times to make it only active for the last 4, 5 or 6 cycles of the 16 cycle pulse.

Ideal Timing
Actual Timings
Front Porch
Alternate 1
Alternate 2
Alternate 3

Other Minstrel 3 changes

The Minstrel 2 followed the ZX80 schematic quite closely, other than the improved composite video circuitry tacked onto the end in place of the RF modulator, and the modern ROM and RAM chips.

The Minstrel 3 deviated quite considerably. I started with the Minstrel 2 version of the ZX80 schematic and added in all of the above changes.

A big change was the switch from 74LS series TTL chips to the high speed CMOS 74HC series. This cleaned up all the signals, and reduced the current consumption by about a third.

There were also two new jumper options.

The first disables slow mode to make the circuitry act like the ZX80. It stops the NMI generator latch being triggered, so slow mode will never be enabled.

This has also been reimplemented with a 74HC74 flip flop, so the power on state can be controlled by the reset line. The same has happened with the VSync flip flop.

This helps when using the ZX80 4K ROM on the Minstrel 3 hardware. The 8K ROM starts with OUT ($FD),A which disabled the NMI generator, but the 4K ROM knows nothing of it. VSync starting on just means the composite video output with start low for a clean first frame.

The second creates a grey-scale border by feeding a chequerboard pulse into the shift register input which is used when no data is being clocked in, when no characters are being displayed. This is the clock pulse XORed with the lowest bit of the line counter so it alternates each line. As seen in the previous post, this gives an insight into the unexpanded display file.

There are also jumpers to select normal / inverse video and 50Hz / 60Hz frame rate as in the Minstrel 2, and jumpers to select the upper address lines to allow multiple ROM images to be used.

As was later added to the Minstrel 2, a character synchronisation flip flop is used to tidy up the video signal so it always generates equally sized pixels. The clear input of the flip flop is used to add the back porch signal.

A composite video mixer is used to provide a reasonably standard composite video signal.

Here showing a screen full of chequerboard characters.

For details of other changes, including the mysterious "high resolution graphics enabling device", see a previous post:

The latest revision of the Minstrel 3 has a pin header which mirrors the signals at the edge connector. This is going to be useful for expansion cards, and also for attaching logic analysers for debugging.

For the traces in this post, most of the signals I need are on the other side of the board, so there are a few more wires there.


Minstrel 3

The Minstrel 3 is a ZX81 compatible Z80 based computer with 32K of RAM and 8K floating point BASIC. It does support slow mode, and several high resolution mechanisms, so the majority of ZX81 games will run fine.

Minstrel 3 kits are available from

And also in built, kit and PCB only form from SellMyRetro

See also Minstrel 2 (ZX80 compatible) and Mini PET (Commodore PET compatible) kits, more info here

And Minstrel 4D kits (Jupiter Ace compatible)


You can support me via Patreon, and get access to advance previews of posts like this and behind the scenes updates. These are often in more detail than I can fit in here, and some of these posts contain bits from several Patreon posts. This also includes access to my Patreon only Discord server for even more regular updates.