Skip to content
Hardware

The ULA

One Ferranti gate array — display, keyboard, sound, I/O, and the Spectrum's whole personality

The Uncommitted Logic Array (ULA) is the custom Ferranti chip that combined video generation, memory contention, keyboard scanning, tape and beeper I/O, and the border into a single integrated circuit. It is the reason the ZX Spectrum cost £125 instead of £250, and the reason the platform has the visual and acoustic identity it does.

sinclair-zx-spectrumgraphicscustom-chipsferrantisinclair1982–present

The Uncommitted Logic Array — the ULA — is the custom Ferranti gate array that sits next to the Z80 in every Sinclair Spectrum. It does almost everything that isn’t CPU and RAM: video signal generation, memory-bus arbitration, keyboard matrix scanning, tape and beeper I/O, border colour, and (on later models) memory bank switching. The Spectrum’s hardware bill of materials is, essentially, “Z80 + 16 KB or 48 KB of RAM + ROM + ULA + RF modulator.” That extreme integration is what made the £125 launch price possible in April 1982.

The ULA is also the source of nearly everything that gives the Spectrum its distinctive feel. Attribute clash, contended memory timing, the one-bit beeper, the way the border can be striped during loading and demos — all of it is the ULA. Programming the Spectrum at the hardware level is programming the ULA.

Fast facts

  • Manufacturer: Ferranti, designed in collaboration with Sinclair Research; Amstrad-redesigned variant on the +2A/+3.
  • Process: Ferranti 5C series uncommitted logic array (mask-programmable gate array).
  • Function: display, memory arbitration, keyboard, tape, beeper, border.
  • Video: PAL composite via RF modulator; 256 × 192 active display in a 32 × 24 attribute grid.
  • Colour space: 8 hues × 2 brightnesses = 15 unique colours (BRIGHT 0 black ≡ BRIGHT 1 black).
  • Timing: 3.5 MHz CPU clock; 69,888 T-states per frame; 312 scanlines; ~50.08 Hz refresh.

Contention, as the silicon does it

Contended memory is usually described by its effect — the Z80 running slower in the lower 16K while the screen is being drawn. Chris Smith’s reconstruction of the ULA gives the mechanism, and it is simpler and stranger than the effect suggests.

The contention controller “is divided into two parts: one that detects and controls memory contention, the other that detects and controls I/O contention”, and the two together “combine to form the clock control signal that gates” the master clock “to produce the Z80 clock”. The ULA does not stall the processor by holding a wait line. It gates the processor’s clock. The Z80 is not waiting; for that moment it is not being clocked at all.

The address test is equally direct. On the earliest part, the “issue 1 memory contention handler considers A14 and A15, looking for [an] address between 0x4000 and 0x7FFF, and delays /MREQ until the end of the T-state”. Two address lines, one comparison. That is the whole reason contention applies to the lower 16K and not the upper 32K of a 48K machine: the ULA is only looking at two bits, and those two bits describe exactly that block.

The book also records that this was not settled at launch — the contention controller “has been the subject of several revisions” across the production run, because “early ULA versions contained” design errors “that affected the reliability of the machine”. Contention timing is not one behaviour but a family of them, which is why timing-exact Spectrum emulation has to name a ULA issue number rather than just “the Spectrum”.

Snow, and the refresh register again

The “snow effect” — corrupted pixels crawling up the display — has a precise cause, and it involves the part of the Z80 nobody programs.

The ULA does not treat a Z80 refresh address as a contention condition. Refresh cycles are housekeeping, not memory access, so ignoring them is, as the book puts it, “technically correct”. But if the refresh address happens to fall between 0x4000 and 0x7FFF, the CPU’s /RAS generator reads it as a genuine access to the lower 16K and asserts /RAS anyway — colliding with the /RAS the video generator is producing, and corrupting the video byte fetch already in progress.

Snow is therefore not a timing bug in the usual sense. It is two pieces of the machine disagreeing about whether a refresh cycle counts as an access. The effect depends on where the I register points, because Zilog’s own manual has it that “during refresh, the contents of the I Register are placed on the upper eight bits of the address bus” — so I decides which 256-byte page every refresh cycle lands in, and pointing it into 0x4000-0x7FFF is what puts those cycles into contended memory.

There is a pleasing symmetry here with the ZX81, where the Z80’s refresh cycle is deliberately harnessed to address the character set and generate the picture. Sinclair’s previous machine made the refresh register do the display’s work. On the Spectrum the same register, left pointing in the wrong place, damages it.

Chip variants

The Spectrum line shipped at least three different ULA designs:

Machine Part number Crystal Memory contention Floating bus
16K / 48K Ferranti 6C001E (also 5C112E, 5C102E early variants) 14 MHz MREQ + IORQ Yes
128K / +2 (Sinclair-era) Sinclair-branded 7K010E 17.7345 MHz (PAL master) MREQ + IORQ Yes
+2A / +3 (Amstrad-redesign) Amstrad 40077 17.7345 MHz MREQ only No

The +2A/+3 was a clean redesign by Amstrad after they acquired Sinclair’s computer business in 1986. Software relying on the floating-bus read (port $FF) for video-data sniffing breaks on those models — a small but real source of game incompatibility across the Spectrum range.

Display generation

The ULA fetches pixel and attribute data directly from main memory and converts it to a PAL composite video signal. Pixel and attribute regions are fixed:

Memory region Size Purpose
$4000-$57FF 6,144 bytes Pixel bitmap (256 × 192 at 1 bit/pixel)
$5800-$5AFF 768 bytes Attribute data (32 × 24 cells, one byte each)

The screen address quirk

The pixel data is not linearly arranged. It’s interleaved by character row within each third of the display:

addr = $4000
     | ((y & 0xC0) << 5)    ; which third (top / middle / bottom)
     | ((y & 0x07) << 8)    ; pixel row within character
     | ((y & 0x38) << 2)    ; character row within third
     | (x >> 3)             ; column

This non-linearity exists because it matches the ULA’s scanline fetch order — the chip reads the bitmap in display order, and the address bits decompose into a shape that minimises addressing logic. It’s awkward for game code (no straight address + y * 32 arithmetic) but is the foundation of the platform’s distinctive bitmap_rows lookup tables. Shadowkeep Unit 3 uses one.

Attribute format

Each 8 × 8 pixel cell has one attribute byte at $5800 + row*32 + col:

Bit Purpose
7 FLASH — swaps ink and paper every 16 frames
6 BRIGHT — affects both ink and paper
5-3 PAPER colour (0-7)
2-0 INK colour (0-7)

This single-byte-per-cell scheme is the origin of the Spectrum’s colour clash: a 1 in the bitmap shows in INK, a 0 shows in PAPER, and there are only two colours per 8 × 8 cell. Sprites crossing cell boundaries change colour at those boundaries. The Project’s whole Spectrum aesthetic strategy is downstream of this byte.

Memory contention

The ULA and the Z80 share the same RAM bus. When the ULA is fetching pixel or attribute data — during active display lines — the Z80 has to wait. This is contention:

  • During active display (top border end through bottom border start), the ULA owns the bus for specific T-states per scanline.
  • The Z80 is delayed when accessing contended memory ($4000-$7FFF on 48K) during those T-states.
  • The contention pattern repeats predictably every 8 T-states per scanline:
T-state within group:  0  1  2  3  4  5  6  7
CPU delay (T-states):  6  5  4  3  2  1  0  0

This means CPU access to screen memory during display is slower than access to upper RAM ($8000-$FFFF on 48K). Performance-critical code typically lives above $8000.

128K / +2 use the same shape with a small starting offset. The Amstrad +2A/+3 contends MREQ only — no IORQ contention — which changes the exact T-state cost of IN/OUT instructions on those machines.

Demo-scene coders exploit contention deliberately, building effects that depend on the exact T-state cost of each scanline. For game code it’s usually a thing to avoid by structuring data layout, not exploit.

I/O ports

The ULA decodes I/O addresses incompletely — the low byte determines the port. Two addresses matter:

Port Read Write
$FE (any odd port with A0=0) Keyboard rows (bits 0-4) + EAR input (bit 6) Border (bits 0-2) + MIC (bit 3) + EAR/beeper (bit 4)
$FF (any port with A0=1, where contention is active) Floating bus — last byte the ULA fetched (48K / 128K / +2 only)

Port $FE — write

Bit Function
0-2 Border colour (0-7)
3 MIC out (tape signal)
4 EAR out (beeper / speaker)
5-7 Unused (reads back as 1 on the floating bus)

The single-bit speaker — bit 4 — is the entire Spectrum sound system on the 48K. Beeper music is the art of generating perceived polyphony from this one bit.

Port $FE — read (keyboard)

The keyboard is an 8 × 5 matrix. Eight address-line bits select a half-row; the data byte reads bits 0-4 of the five keys in that half-row (active low — 0 means pressed).

Address read Keys returned in bits 0-4
$FEFE SHIFT, Z, X, C, V
$FDFE A, S, D, F, G
$FBFE Q, W, E, R, T
$F7FE 1, 2, 3, 4, 5
$EFFE 0, 9, 8, 7, 6
$DFFE P, O, I, U, Y
$BFFE ENTER, L, K, J, H
$7FFE SPACE, SYM, M, N, B

Shadowkeep Unit 4 builds a QAOP input handler by reading $FBFE for Q, $FDFE for A, $DFFE for O and P.

Border colour

        ld      a, 2            ; red
        out     ($fe), a        ; set border

Bits 0-2 of the byte written to port $FE set the border. The classic “loading stripes” effect is the border being set rapidly to different colours during tape pulse decoding — driven by the same bit 4 that the beeper uses.

Cultural impact

The ULA’s design constraints — colour clash, contention, the one-bit beeper, the slow-clear screen, the awkward addressing — are not the platform’s bugs; they’re the platform’s voice. Spectrum games look like Spectrum games because of the ULA. Programmers who learnt to dodge contention, design around clash, and squeeze beeper polyphony from one bit weren’t being held back; they were learning a craft. The platform’s aesthetic and the techniques that produce it both trace to this single Ferranti chip.

Why the ULA matters for Code Like It’s 198x

Three out of four of Shadowkeep’s load-bearing techniques are ULA techniques: bitmap addressing via the non-linear screen layout (Unit 3), attribute-as-game-rule via the one-attribute-per-cell scheme (Unit 5), and port $FE for keyboard and beeper (Units 4 and 7). The Project teaches the Spectrum as a platform whose constraints are integral to the design, not obstacles to it — which is, structurally, an argument about learning to think with the ULA rather than against it.

See also

Not yet fact-checked. This entry was drafted by an AI and nobody has verified it. The dates, figures and technical details may be wrong. Use it to find your bearings, then confirm anything that matters against a primary source.