Sinclair ZX80 & ZX81
zxplay_go emulates the Sinclair ZX80 (1980) and ZX81 (1981) alongside the Spectrum family. They share the Z80 CPU with the Spectrum but generate their display in a completely different way, which is the interesting part of the implementation.
Running
- GUI:
Machine → Sinclair ZX81orMachine → Sinclair ZX80. - Command line:
zxplay_go --zx81orzxplay_go --zx80.
The ZX80/ZX81 system ROMs (4 KB / 8 KB) are bundled, so no extra setup is needed. The machine boots to the inverse-K cursor and runs BASIC.
Loading programs
File → Open File… accepts:
.P/.81— ZX81 programs (a raw dump of memory from$4009, including the saved system variables). Switch to the ZX81 first..O/.80— ZX80 programs (a raw dump from$4000). Switch to the ZX80 first.
Keyboard
The ZX80/ZX81 use an 8×5 matrix with a single SHIFT (no SYMBOL SHIFT). The host mapping covers the whole keyboard:
- Letters, digits, SPACE, ENTER (NEWLINE),
.— direct. - SHIFT — host Shift.
- Cursor keys — arrow keys (SHIFT + 5/6/7/8 on the hardware).
- RUBOUT (delete) — Backspace / Delete (SHIFT + 0).
- Symbols/operators — every symbol is SHIFT + a letter on the hardware, so the host symbol keys are mapped to those combinations:
=(Shift+L),-(Shift+J),+(Shift+K),*(Shift+B),/(Shift+V),,(Shift+N),;(Shift+X),:(Shift+Z),?(Shift+C),((Shift+I),)(Shift+O),$(Shift+U),"(Shift+P),<(Shift+W),>(Shift+E).
In K (keyword) mode each letter key types a whole BASIC keyword, as on real hardware. Mappings can be overridden via keymap.json.
How the display works (the short version)
There is no frame-buffer ULA. The Z80 is the video generator:
- The ROM points the CPU at the display file, executed through the upper 32 KB (so address line A15 = 1).
- On each opcode fetch with A15 = 1, the ULA inspects the byte. If bit 6 is clear, it forces a NOP onto the bus (so the CPU does nothing) and uses the byte as a character code: it reads that glyph's bitmap from the character ROM — addressed by the I register, the character code, and a 3-bit scanline counter — and shifts those 8 pixels onto the current TV line. Bit 7 selects inverse video. If bit 6 is set (e.g.
HALT,$76, which terminates each display line), the byte executes normally. - The maskable interrupt is wired to bit 6 of the R register; the ROM reloads R in the
$0038handler so an INT lands at the end of every scanline and un-halts the display loop. (R must keep counting duringHALT, as on real hardware — seeRefreshDuringHaltin the Z80 core.) - On the ZX81 in SLOW mode, an NMI generator (enabled/disabled by
OUTto$FE/$FD) pulses an NMI once per scanline to count the top and bottom border lines, which is what lets the ZX81 compute and display at the same time. The ZX80 has no NMI generator: it blanks the screen while computing (the characteristic ZX80 flicker).
The implementation lives in pkg/zx8x (the Machine, Screen and CharGen types) and is verified against the MAME and ZEsarUX cores and the published hardware write-ups.
Differences between the two machines
| ZX80 | ZX81 | |
|---|---|---|
| ROM | 4 KB | 8 KB |
Character set base (I) | $0E00 | $1E00 |
| SLOW mode / NMI generator | none (display blanks while computing) | yes |
| File format | .O / .80 (load at $4000) | .P / .81 (load at $4009) |