ZX Spectrum on FPGA

Introduction

The Speccy was the first computer I ever used. We had the 48K rubber key version, and it probably only had to share the living room TV with 3 channels when we first got it. I still have it in my possession and it is still fully functional, although it has few original parts – I repaired it a couple of years ago, requiring the replacement of the CPU, ROM and most of the DRAM. Luckily the ULA was still alive!

Manic Miner in-game screenshotSeveral years ago I implemented most of the 48K ULA in VHDL on a MAX7000 CPLD, mainly as an exercise to brush up on my HDL. I didn’t have enough space to fit the keyboard interface so it was pretty useless, but it did boot! The complete system used a pair of SRAMs, real Z80 and the Sinclair ROM in flash.

Back at the beginning of last year I decided to brush up on my HDL again and I bought myself an Altera DE1 development kit. The heart of this board is a Cyclone II FPGA with a range of support hardware including 512 KB SRAM, 8 MB SDRAM, 4 MB Flash, an I2S audio codec, VGA port and the usual switches, LEDs and buttons. I would highly recommend it for anyone looking for a relatively cheap way to play with FPGAs.

This is an implementation of the complete ZX Spectrum for the DE1 board. It can be configured to synthesise a 48K, 128K or +2A model, and it has a ZXMMC+ compatible interface. ZXMMC+ allows programs to be loaded from SD card, and it also allows extra RAM and Flash to be paged in over the top of the ROM. This feature makes it possible to run ResiDOS.

Architecture

All of the Spectrums used a Zilog Z80A CPU running at roughly 3.5 MHz (there is some variation between the different models). The display was generated by a custom Ferranti ULA, which was also responsible for the cassette interface and beeper. In the 128K models and later there was additional logic to handle bank switching, as well as the familiar AY-3-8912 sound generator.

16K/48K

In the earliest Spectrum model the memory map is completely linear. The first 16 KB is the ROM and the second 16 KB is a bank of DRAM which is shared with the ULA. The ULA takes priority, so CPU accesses to this region are slower than to the uncontended parts of the memory map. The 48K model has a further 32 KB of RAM in the upper half of the address space.

The ULA also presents a single IO port, nominally on port 0xFE, but actually visible at all even IO addresses (the Z80 has separate IO and memory address space). Writes to the port control the beeper, tape output and the colour of the screen border. Reads provide access to the keyboard and to the tape input.

128K

The 128K Spectrum adds a simple write-only paging register at port 0x7FFD, again incompletely decoded and actually appearing on all odd ports with A1 and A15 clear. Writes to this register select one of eight 16 KB banks of RAM to appear in the top quarter of the address space, with two of the banks also permanently accessible at 0x4000 and 0x8000. A further bit controls which of two 16 KB ROMs appears at the bottom of the memory map.

Proper sound hardware is also present – a General Instrument AY-3-8912, which was also found in many other machines at the time.

Spectrum +2A boot menu

Spectrum +2A boot menu

+2A/+3

The last of the Spectrums, the +2A and +3 were essentially the same machine but with the +3 adding a 3” disk drive. These were Amstrad machines, being made after the 1986 sale of the rights to Sugar’s company, and were styled similarly to the Amstrad CPC range of machines.

Hardware was comparable with that of the 128K Spectrum, with additional IO ports decoded for access to the floppy disk controller (where fitted) and to another paging register giving access to special paging modes. Two more ROM banks, four in total, were needed for the DOS and other extra features, although these are implemented as a pair of 32 KB ROMs in the real hardware.

Display

In all the machines the display is bitmapped 256×192 with an 32×24 attribute plane. The bitmap plane selects whether each pixel is displayed as background or foreground colour, and the attribute plane allows foreground and background colour, bright and flashing modes to be selected for all pixels within the corresponding 8×8 square.

The design allows for display of up to 15 different colours on screen at once for less than 7 KB of display memory, but results in the blocky “two colours per 8×8 square” style that makes Spectrum screenshots so easy to spot.

FPGA

The original DE1 implementation that I published last year supported only the 48K Spectrum. This has now been extended to support the 128K models as well, and SD card support has been added using logic compatible with the ZXMMC interface. The particular model to be synthesised can be selected using VHDL generics.

The design fits into 3252 Cyclone II logic elements in its most complex configuration (compiled on Quartus 9.1), and requires 32 Kbits of on-chip RAM for lookup tables if the 128K sound is included. The ROMs are now accessed out of the external Flash device and must be programmed using the DE1 tools prior to loading the Spectrum design.

CPU

The CPU is included on the FPGA and uses the T80 from OpenCores with fixes from Pace Dev.

Clocks

A 28 MHz master clock is used to allow the video to run out of phase with the CPU and other peripherals. This makes it possible to time-slice access to the SRAM, eliminating the contended memory present in the real Spectrum, but also necessary because the RAM here is in a single chip connected over a single bus.

Memory

The external SRAM is accessed over a 16-bit interface to enable the full 512 KB to be addressed. The first 128 KB is used for the Spectrum’s internal RAM, and the upper 256 KB is made available through the ZXMMC+ interface. The remaining 128 KB is not mapped.

Flash is accessed over a separate 8-bit interface and mapped in to the bottom 16 KB of address space. Different pages are selected depending on the type of Spectrum that was synthesised, and the condition of the ROM select bits in the paging registers, where applicable. Extra banks can also be used via the ZXMMC+ feature.

Display

The video logic generates an address for the SRAM on alternate clock phases, enabling it to fetch display data without interrupting the CPU. Attribute and bitmap data are assembled to produce the necessary RGB bitstream to drive the monitor, along with the related timing signals. In areas of active video that lie outside of the bitmapped screen area the logic generates a constant “border” colour, the particular colour of which is held in a 3-bit latch accessed through the single ULA register.

DE1 Spectrum

ZX Spectrum on Altera DE1

IO

Bus routing and address decoding are handled at the top level along with the paging registers present if synthesising one of the higher-end models. All the ports are incompletely decoded in the same way as in the real Spectrum.

Sound

The codec I2S signals are converted internally to 16-bit parallel buses for both input and output. A simple state machine drives the I2C bus necessary to program the required register settings into the codec when the board is first reset.

The beeper, present in all versions of the Spectrum, is driven by toggling a bit in the ULA register in software. The value of this bit drives the sign bit of the output to the Wolfson audio codec. Sound from the AY-3-8912 (actually a YM2149 core from FPGA Arcade) is mixed in as well.

The sign of the I2S input value is used to drive the EAR input in the ULA register to enable loading from tapes. A simple comparator and latch arrangement is used to provide a small amount of hysteresis to improve noise immunity.

Keyboard

The Spectrum keyboard is row-scanned by the 8 upper address bits, with 5 column outputs fed into the ULA and appearing as the bottom 5 bits in its only register. This topology is mimicked here by using an array of 5-bit registers, the appropriate one presented to the ULA depending on the value on the address bus. Further logic is used to set or clear bits in the registers according to press/release codes from the PS/2 keyboard interface.

Some key combinations (caps lock, backspace, escape and the cursor keys) are decoded into the required combination of keys automatically. The SHIFT keys operate CAPS SHIFT, and the CTRL keys operate SYMBOL SHIFT.

ZXMMC+

The latest release of the design supports access to the DE1’s SD card interface using logic compatible with the ZXMMC+. This also allows an additional 256 KB of both RAM and Flash to be paged in. Software support for SD cards is available through modified ROMs for the +2A and +3 computers (+3e by Garry Lancaster), and through an operating system extension called ResiDOS. More information about ZXMMC and ResiDOS (which is also supported) can be found on their respective websites.

Interfacing

The ULA implementation includes extensions to drive a VGA monitor directly (not through a scan doubler). Alternatively, a PAL TV can be connected through a SCART lead connected to the DE1’s VGA port. In this mode the HSYNC pin generates PAL compatible CSYNC and a small wire-mod is required in order to connect +5V to pin 9 of the VGA connector, which is in turn connected to pin 16 of the SCART plug. The need for the wire mod is historical and will be removed in a future release.

Beeper, tape and AY sound are all available on the green line-out connector, and the blue line-in connector can be used to load programs from a tape player (or from a PC running something like PlayTZX).

The PS/2 port needs to be connected to a PC keyboard.

ROMs

The Spectrum ROMs can be obtained from an emulator package and need to be loaded into the DE1’s Flash at the following addresses:

Offset ROM
0x00000 48K (Sinclair Research)
0x04000 Blank
0x08000 128K 0 (Sinclair Research)
0x0C000 128K 1 (Sinclair Research)
0x10000 +3 0 (Amstrad), or +3e 0 (Garry Lancaster)
0x14000 +3 1 (Amstrad), or +3e 1 (Garry Lancaster)
0x18000 +3 2 (Amstrad), or +3e 2 (Garry Lancaster)
0x1C000 +3 3 (Amstrad), or +3e 3 (Garry Lancaster)

A further 16 banks of 16 KB starting at 0x40000 can be paged in over the normal ROM using the ZXMMC+ feature.

If you want to use the +3e ROMs to access the SD card then you can download the bundle from World of Spectrum.  The mmcen3eE.rom image can be written to 0x10000 in one go, as it contains all four of the +3 ROMs.

Switches

Some of the switches on the DE1 affect the operation of the design:

  • SW9 controls system reset (up to run)
  • SW8 is used with the debugger, which is normally disabled
  • SW7 selects PAL mode (down) or VGA mode (up)
  • SW1 selects boot from normal ROM (down) or ZXMMC+ banks (up)
  • SW0 selects ZXMMC+ boot from RAM (down) or Flash (up)

Known Issues

Cycle accuracy – there is no memory contention and the CPU core doesn’t match the timing of the real Z80. This causes problems with games (and particularly demos) that do high-resolution colour by changing the attribute values at precise times. Multi-colour border effects also suffer.

Running “ulatest3” results in an immediate reset. This is suspected to be a CPU issue but has not been investigated extensively at this stage. Fixing this needs to happen before contention emulation can be considered.

The design currently fails timing, apparently due to the way in which the YM2149 is implemented. It seems to work alright, though.

Credits and Links

The following VHDL cores were used in the design:

These pages were of use during development and may be of interest:

Downloads

Update 2016-01-13: The files are now also available on GitHub

80 thoughts on “ZX Spectrum on FPGA

  1. Reinaldo

    What should be done for porting the code from DE2-70 to DE2-35? someone has any ideas? Thanks!

    Reinaldo.

  2. Till

    BTW: The fix for first eight pixels top left flickering is to include the hcounter into the equation resetting the vcounter. This allows the last line to finish correctly and to begin the first line from the beginning. Without taking hcounter into account the last line was becoming the first line after one cycle so the first data fetch in that line was missing and the whole display had one line less than it should.


    -- Wrap vertical counter at line 312-1,
    -- Top counter value is 623 for VGA, 622 for PAL
    if vcounter(9 downto 1) = "100110111" then
    if ((VGA = '1' and vcounter(0) = '1' and hcounter = "1101111110") or
    (VGA = '0' and hcounter = "1101111111")) then
    -- Start of picture area
    vcounter '0');
    -- Increment the flash counter once per frame
    flashcounter <= flashcounter + '1';
    end if;
    end if;

  3. Davie

    The screenshot at the top isn’t from “Subterranean Nightmare”, is it? Nobody I know can remember it. It was hardcore difficult if I remember correctly.

Leave a Reply

Your email address will not be published. Required fields are marked *