A ZX Spectrum clone with a CPLD

Based on my experience with FPGAs and the needs and aspects of the sinclair.hu group, I had the idea of ​​a ZX Spectrum implemented with CPLD. The CPLD, as a programmable circuit, can be said to be the little sister of the FPGA. It knows much less and is much simpler. In fact, it’s just a good “fat” GAL or PAL circuit in which ULA (its digital part) can be implemented. It is a bit outdated technology and relatively difficult to obtain. Why is it worthwhile to deal with this technology? Not really worth it, but an interesting challenge. Perhaps there is only one argument in favor of the fact that CPLD is still close to the classic 5V TTL technology that was available when the original Spectrum was created. Thanks to this, the circuit, although operating from 3.3V, still has 5V tolerant inputs, so it can be easily matched to a classic Z80 processor.

System overview

Resources are limited (compared to an FPGA), but ULA as a logic circuit still fits in it. That's a lot and a little. A lot, since the Spectrum is the ULA itself, as a video circuit is what turns a simple Z80 base system (CPU, ROM, RAM) into a ZX Spectrum. However, it's not enough because the video output of the CPLD can only be digital RGB (similar to the original ZX Spectrum 128). Digital RGB (more precisely RGBI along with the intensity bit) is obsolete, today you can’t find a monitor that can accept at most the classic CRT models (CGA, Commodore 1084) that still work thanks to collectors. Fortunately, it is quite easy to produce analog RGB and synchronous signals with simple resistors DAC, which can be received by the Euro-Scart input on most TVs as analogue RGB. Unfortunately this kind of input is also obsolete. Another option is to convert RGB to a PAL composite video signal, which is also obsolete but is still used by television manufacturers. A dedicated circuit (AD724ARZ) exists for RGB-CVBS conversion, although it is not a new technology but is still available.

I use an STM32 microcontroller (MCU from now on) to handle the mass storage device (SD card) and a PS2 keyboard. The microcontroller is programmable in C and has a rich sample program and software library. I designed the base system with 32K ROM area (2x16K), however, static RAM can also be paged into the spectrum ROM area. The system includes 2x32K RAM and 1x32K video RAM. Video RAM can be scrolled in 2x16K to the standard video RAM area ($4000-$7FFF) and 2x32K to the top of the address area ($8000-$FFFF), or the lower 16K of one of the RAM can be placed in the ROM area, allowing for full 64K address space for RAM implementation. By default, I designed a 2x16K ROM, but you can also use an optional 64K ROM chip, in which case a 4x16K ROM pages are available.

So all in all, this is the memory structure of the system

2x16K ROM (optionally 4x16K)

2x16K video RAM, can be paged for the ULA and CPU, too (double buffering)

2x32K RAM

The various functional elements and connectors are shown in the figure below

1. Video / Audio output, outputs RGB + Sync signals, composite video and audio output
2. SD card slot. The SD card must have a FAT32 file system
3. PS / 2 keyboard input
4. EAR analog input when the user wants to load games from a tape
5. Power supply connector, stabilized + 5V
6. PAL / RGB encoder IC, generates composite video signal from RGB signals (AD724)
7. STM32F103RCT6 MCU, system controller. Responsible for handling the SD card and reading the PS / 2 keyboard
8. TS1086 LDO IC for 3.3V supply voltage generation
9. 32K video RAM that connects directly to the CPLD. (61256, 32K static RAM)
10. Xilinx XC95288XL For CPLD, ULA and system control circuits
11. 32K static RAM, spectrum system memory (top 32K page1)
12. 32K static RAM, spectrum system memory (top 32K page0)
13. System ROM 32K or 64K EEPROM or EPROM (a 128K EEPROM is displayed in the development panel)
14. Z80 CPU
15. Keyboard connector (KD0..KD4), column reader input
16. Speaker
17. 28MHz system clock oscillator
18. Keyboard connector (A8..A15), line selector output
19. Nintendo NES controller connectivity, JOY
20. CPLD programming port (JTAG)
21. SWD / SWC MCU programming port (solder points)
22. MCU reset button (for development purposes)

All the system architecture, paging, and system control logic are implemented in the CPLD. This way, you won’t actually need more extra parts. The CPLD does not include an internal oscillator, so an external 28MHz dedicated clock chip is required.

The composite video signal is generated by the AD724 RGB-Pal decoder IC, which requires its own quartz crystal to produce the PAL color subcarrier frequency (4,433 MHz). Whether the user wants to use an analog RGB or composite video signal must be set with jumpers because the same RGB signals must be applied to the AD724 input as to the monitor, but the input impedance is different in the two cases.

Video circuit design (RGBI and synchronous signals are generated by the CPLD):

The generated RGB analog signal is converted by the AD724 circuit into a composite video signal.

The MCU also uses its own 8MHz oscillator, from which it generates its own internal 72MHz clock signal. The MCU handles the SD card (with FAT32 file system) as well as the PS/2 keyboard, if connected. A high-speed SPI line provides communication between the Z80 and the MCU (via the CPLD of course), which means a few IN / OUT instructions from the Z80 (because the hardware part is handled by the CPLD). The MCU could also perform a number of other co-processor tasks, which is just a matter of the firmware in it. Essentially, from the Z80, this appears to be a slave unit, the Z80 send commands, the MCU executes them and returns the result. (My long-term plan is to implement a software floating-point co-processor in the MCU, or some kind of voice emulation, such as AY-8912 or CBM SID)

The PCB before soldering anything.

Since this circuit is the second version of the CPLDZX, so I did pretty much everything based on my experience in the first version, some parts and developers went quickly. But it was also a stumbling block. (I will be happy to talk about these in person on occasion. Another time.)

ULA extensions

Although ULA is fully compatible with 48K spectrum ULA (cycle accurate, so border effects are also perfect), I made a few small tweaks to it, adding some extra features (which I wanted to solve for 20 years). These are the following:

Memory map

A few new system control bits were also required, primarily to properly paging the memory. The system memory map is shown in the figure below.

Three units can be paged for the lower 16K of the address space: the lower 16K memory of the ROM, the upper 16K memory of the ROM, and the lower 16K part of the RAM #1 32K memory. In addition, it is possible to open a narrow 256-byte RAM window while the ROMs are on, in order to provide a workspace for the system when switching ROM pages and loading snapshot files. The 48K snapshot files cover the entire memory (in the $4000-$FFFF range, so the snapshot loading routine needs to create a location elsewhere, and the stack must work as well. This is accomplished by the RAM WINDOW, which otherwise is in 3C00-$3CFF of RAM #1 is flipped to the same address in the ROM area, which also contains some subroutines for communication between ROM pages, which are loaded by the ROM code when the system initialized.

In the video area ($4000-$7FFF) range, the video RAM is always displayed, with two bits you can select the visible and the active page. Visible page is seen and read by the ULA, while the active page is accessable by the CPU.

The upper 32K part of the memory ($8000-$FFFF) always contains RAM, here too you can set the RAM #0 or RAM #1.

I/O ports

$FE - original ULA port as for a real Spectrum

$FB - communication channel between the CPU and MCU

Write: writes the data to be sent to the MCU in the transfer buffer (SPI TXBUFF)

Read: reads the data sent by the MCU from the receiver buffer (SPI RXBUFF). It can only be read safely after the transmission has been acknowledged.

$FD - System configuration port (output, RESET sets it to 00000000)

Bit 0: LORAM, turns on RAM #1 for $0000..$3FFF (0: ROM active, 1: RAM active)
Bit 1: LOPG selects the active ROM tab (0: ROM #0, 1: ROM #1) if the ROM is active
Bit 2: RAMSEL selects the active RAM tab in the $8000..$FFFF area (0: RAM #0, 1: RAM #1)
Bit 3: CPU7M, 7MHz clock for the CPU if the CPU is suitable for this
Bit 4: WINDOW: Disables the RAM window in the $3C00..$3CFF range (0: active, 1: inactive)
Bit 5: AUDIO: the Speaker bit (ULA D4) also drives the MIC output if 1
Bit 6: SPKROFF: turns off the speaker if 1
Bit 7: LOCK, locks the given configuration, then only RESET unlocks the system

$F7 - extra video features configuration port (output, RESET sets it to 00000000)

Bit 0: Enable linear screen area when 1
Bit 1: Disable Flash (use 16 background and 16 foreground colors) when 1
Bit 2: Enable Timex-Attribute (6K color space 32x192 color bytes) if 1
Bit 3: Screen off - screen off when 1
Bit 4: Visual page, which video page the ULA displays (0/1)
Bit 5: CPU page, which video page the processor sees (0/1)
Bit 6: Border Bit 3 ( 16(15) border color options with ULA bits 0..2)
Bit 7: n/a

$F7 - video and communication channel status port (input only)

Bit 0: REQ bit status (communication channel is busy), negative, if 0, then the REQ line is inactive
Bit 1: ACK line status, negative, if 0, then at ACK line is inactive
Bit 2: 1
Bit 3: 1
Bit 4: 1
Bit 5: 1
Bit 6: HSYNC status
Bit 7: VSYNC status

$1F - Kempston joystick port, read only. Status of any NES controller connected.

Communication channel

The data transmission between the CPU and the MCU takes place on this channel. The hardware solution can be found in the CPLD. Communication is always initiated by the CPU, and a byte is transmitted by handshake, so the MCU notifies the CPU when it has received the data and sent the response.

It goes as follows:

- The CPU sends the data to the SPIPORT ($FB) port (indicated by the REQ line for the MCU)
- The MCU reads the data with an SPI transmission and also sends a byte
- The MCU clears the REQ bit with a negative pulse on the ACK line. This can be detected on the bottom two bits of the SPISTAT ($F7) port
- The CPU reads the received byte on the SPIPORT ($FB) port

The logic of the send/receive is implemented by the following code:


    out   (SPIPORT),a    ; send byte, REQ goes low


    in    a,(SPISTAT)    ; wait for ACK

    and   3

    jr    nz,sr_w0

    in    a,(SPIPORT)    ; get byte from STM


The entire communication protocol is based on this single data transfer routine. The CPU sends certain commands to the MCU, which must follow the appropriate parameter data depending on the nature of the command, and then again the MCU responds only depending on the nature of the command. Such commands e.g. opening a file, reading data, writing, listing folders, common file management operations.

Some videos about the system... to see how it really works:

Video #1 Video #2