Display generation signals within the ZX80
(also applicable to the ZX81)

Click here to return to my ZX80 page

Please note that you are NOT allowed to reproduce any of this page elsewhere on the Web without my permission.

by Grant Searle

Last update: 30th December 2011


 Oscilloscope pictures of signals
 How it works - a description

Signals present


ZX80 to ZX81 comparison notes
As you can see, the way the ZX81 forces a NOP opcode onto the data bus is slightly different as the ULA has no access to the data at the RAM, so must read the data once the RAM has put it on the bus then the NOP can be applied ready for the CPU opcode read at the start of T3. The additional NOP pulse on the data bus is not relevant and is probably due to limited signal decoding.
Additionally, the ZX81 character output is identical to the ZX80, implying the data shift load for the ZX81 occurs at the same time as in the ZX80.
This would imply that the logic used to do this within the ZX81 ULA is the same as in the ZX80 discrete logic.
The difference that can also be measured between the ZX80 and ZX81 is the ROM Enable time during refresh, as can be seen above.
So, the notes on how it works can be applied to both machines, with a slight difference in the ROM refresh only and the NOP application.

How it works

The display file (DFILE) is held in RAM, somewhere between the 16K and 32K boundary.

Code can be executed from anywhere below the 32K boundary with no interception.
Due to limited decoding used, the same RAM also appears later on in the memory map between the 48K and 64K boundary.
If any code execution is attempted above 32K then the ZX80 (and ZX81) hardware will assume it is display memory and special processing will occur.

Each line of the display file is a set of characters, 0 to 32 long, terminated by a "HALT" character.
The display file consists of an initial HALT, then followed by 24 of these sequences, which then makes up all 24 lines on the screen.

According to the ZX81 manual, if the total memory is  less than 3 1/4 K then the display file is "collapsed" - each line is 0 to 32 chars (plus the HALT) and is only as long as needed to display all characters in a line. If the memory is greater then the display is held expanded - each line is always expanded to 32 characters, plus the HALT.

To display a line of characters the CPU jumps to the start of a line of characters to in the ghost image of it above the 32K boundary to "execute" it.
When execution is called above the 32K boundary, hardware intercepts the opcode fetch and the Z80 CPU "sees" a "NOP" instruction on the data bus and executes it.
The only affect this has is to increment the program counter to point to the next character and also increments the refresh counter.

It is during this execution of each byte in the display memory that a character (1 scan line of it, anyway) is displayed on the TV. The CPU clock timing within the ZX80/81 allows 32 program opcodes to be executed in the time it takes for the scanline to cross most of the screen. These opcodes correspond to the 32 characters that are displayed.

The opcode execution normally (not always) consists of four clock cycles (T-states) as shown in the diagram above. The first two (T1 and T2) retrieves the opcode from memory and the last two (T3 and T4) actually performs the operation.

So, as far as the Z80 CPU is concerned, it executes 0 to 32 NOP instructions then hits a HALT instruction. This will then cause the processor to execute internal NOPS (incrementing the refresh register) until interrupted.

The refresh register is set before the call so that bit 6 of the refresh address is high while each character is being executed (displayed). Once the TV scan line has reached the end of the row of characters, the refresh register has been continually incremented so that bit 6 then falls to zero. This is connected to the interrupt line of the Z80, so it will then "acknowledge" the interrupt and continue processing.

This acknowledge signal is connected to the horizontal sync circuitry which will then generate a HSYNC pulse a short while later. At the same time, the ZX80/81 display routine resets registers as needed and then executes the same line again (8 times total for each line). Once 8 calls to the same line have been executed, it continues to the next block of characters to display the next line. This process occurs 24 times, displaying 8 lines for each of the 24 lines of characters.

There is a 3-bit counter which is incremented for each scan line 0..7,0..7,0..7,.... This is clocked on the HSYNC pulse and reset on the VSYNC pulse.

This is 0 for the top line of a character and 7 for the bottom (8th) line of the character.

The DFILE holds a list of characters to be displayed. The actual bitmaps of these are held at the end of the BASIC ROM. So, the character code is obtained, this is then converted to a ROM address that holds the bitmap for that character. Each character consists of 8 bytes in the ROM, the first line of the character appearing first, followed sequentially by the remaining lines. The address of the start of a character is xxxxxxxx xxxxx000.

This count is used as part of the character lookup-address (bits 0 to 2).

For each character displayed, the RAM contents D0 to D5, and D6 are stored in a latch. D0 to D5 forms bits 3 to 8 of the character lookup address.

The remainder of the character lookup address (bits 9 to 12) are retrieved directly from the "refresh" address that is placed on the processor address bus. This part of the address is set using the IX register, so this register is set before the DFILE execution to ensure the correct character map lookup address is retrieved from the ROM.

The address of a bitmap of particular character line is therefore 000RRRRC CCCCCLLL , where R is the refresh IX register, C is the character code and L is the display line in the character.

Here is a small extract from the ZX80 and ZX81 ROMS showing the character bitmaps within it, and how the addresses then point to the character to be displayed...

        ZX80 ROM                         ZX81 ROM
0F25 0000111100100101 .X.....X | 1F25 0001111100100101 .X....X.
0F26 0000111100100110 ..XXXXX. | 1F26 0001111100100110 ..XXXX..
0F27 0000111100100111 ........ | 1F27 0001111100100111 ........
0F28 0000111100101000 ........ | 1F28 0001111100101000 ........
0F29 0000111100101001 ..XXXXX. | 1F29 0001111100101001 ..XXXX..
0F2A 0000111100101010 .X.....X | 1F2A 0001111100101010 .X....X.
0F2B 0000111100101011 .X.....X | 1F2B 0001111100101011 .X....X.
0F2C 0000111100101100 ..XXXXXX | 1F2C 0001111100101100 ..XXXXX.
0F2D 0000111100101101 .......X | 1F2D 0001111100101101 ......X.
0F2E 0000111100101110 ..XXXXX. | 1F2E 0001111100101110 ..XXXX..
0F2F 0000111100101111 ........ | 1F2F 0001111100101111 ........
0F30 0000111100110000 ........ | 1F30 0001111100110000 ........
0F31 0000111100110001 ..XXXXX. | 1F31 0001111100110001 ..XXXX..
0F32 0000111100110010 .X.....X | 1F32 0001111100110010 .X....X.
0F33 0000111100110011 .X.....X | 1F33 0001111100110011 .X....X.
0F34 0000111100110100 .XXXXXXX | 1F34 0001111100110100 .XXXXXX.
0F35 0000111100110101 .X.....X | 1F35 0001111100110101 .X....X.
0F36 0000111100110110 .X.....X | 1F36 0001111100110110 .X....X.
0F37 0000111100110111 ........ | 1F37 0001111100110111 ........
0F38 0000111100111000 ........ | 1F38 0001111100111000 ........
0F39 0000111100111001 .XXXXXX. | 1F39 0001111100111001 .XXXXX..
0F3A 0000111100111010 .X.....X | 1F3A 0001111100111010 .X....X.
0F3B 0000111100111011 .XXXXXX. | 1F3B 0001111100111011 .XXXXX..
0F3C 0000111100111100 .X.....X | 1F3C 0001111100111100 .X....X.
0F3D 0000111100111101 .X.....X | 1F3D 0001111100111101 .X....X.
0F3E 0000111100111110 .XXXXXX. | 1F3E 0001111100111110 .XXXXX..
0F3F 0000111100111111 ........ | 1F3F 0001111100111111 ........
0F40 0000111101000000 ........ | 1F40 0001111101000000 ........
0F41 0000111101000001 ...XXXX. | 1F41 0001111101000001 ..XXXX..
0F42 0000111101000010 ..X....X | 1F42 0001111101000010 .X....X.
0F43 0000111101000011 .X...... | 1F43 0001111101000011 .X......

The dot represents 0 and the X represents 1 (changed to make it easier to see the characters).
As you can see, the ZX81 characters have been made slightly thinner than the ZX80 versions.
Both appear at the end of the ROMS, but the ZX80 ROM is 4K long and the ZX81 ROM is 8K long, so the top bits of the address are different.

D6 is reserved, and "0" for a display character. If a HALT instruction is read, this bit is set to "1".
D7 is the "inverse" bit, and is used to trigger a flip-flop to "exclusive or" the video output stream, causing inverted video to be produced.

Due to 2 bits being used, only 6 bits remain to define the character, so only 64 characters are possible (plus 64 "inverse" images of the same characters).

Normal opcode retrieval pulls /M1 and /MREQ low. The DFILE execution can inject a NOP instruction (00) onto the data bus when needed as follows...
IC 13 pin 6 takes D6 and inverts it. This is "NAND"ed (IC 16) with A15 and /HALT.
So, IC16, pin 6 goes low if /HALT is high, A15 is high (as it would be if executing above the 32K boundary) and D6 is low.
This output is the "OR"ed with the /M1 signal, then goes to a NOT gate, so the output of IC 15 is high when /M1 is low (opcode fetch) above 32K with an opcode where bit 6 is low. This combination enables the "NOP" generation (see picture, above), which forces (via IC14 and 15) the data bits on the CPU data bus low, so the CPU reads 00, which is a NOP code, and executes it.

Resistors R4 to R11 isolates this pull to ground from the rest of the data bus, which shows the RAM data contents instead.

The 74LS373 data latch has outputs that follow the inputs if the latch input is high. As soon as this input goes low, the outputs are frozen, holding the value currently displayed on the input.

This latch updates the output if: (/MREQ=LOW) AND (CPU CLOCK = LOW) AND  (REFRESH=HIGH)

See the picture above. This will allow the data bus to briefly update the outputs to whatever is read from the RAM during the "Opcode fetch" then freeze the outputs during the "Opcode execution" (memory refresh) part of the process.

It is during the "Opcode execution" part that the ROM address multiplexor switches the ROM from the address bus to the character lookup address, as mentioned above. The multiplexor also enables the ROM outputs.

The 74LS165 output display shift register, IC9, is continually clocked and will serialise any data loaded into it at a clock rate of 6.5MHz. This allows all 8 bits to be passed to the TV before the next character is loaded.

At the very end of the opcode execution (actually, the very start of the next opcode), the load pin on the register is very briefly (via a capacitor) pulsed low, to load whatever is being output on the data bus (from the ROM) into the shift register.The pulse is activated by a positive clock transition. This is only active when /MREQ is high. As you can see from the oscilloscope traces, the only time a positive transition occurs when /MREQ is high is at the start of the T1 state. A further blocking signal (into pin 10 of IC 16) ensures that the trigger pulses only occur when a character has been loaded (ie. when the NOP circuitry has been activated). This ensures that no data is loaded into the shift register at other times during the display.

The data that has been loaded is is then streamed one bit at a time (highest bit first) to the output (via the XOR IC 20, to invert if needed) and then passed directly to the TV modulator to convert the 0 and 1 levels into dark/light bits on the screen.

<Click to return to ZX80 main page>