Skip to content

Published 28 October 202626 min

Racing the ULA: How Programmers Broke the ZX Spectrum's Rules

Press play to watch here. The video loads from YouTube only then.

How did programmers make the ZX Spectrum draw colour it was never meant to hold, and why could the same code freeze on a later model?

From border stripes and the floating bus to scanline multicolour, we follow the exact races between the Z80 and ULA that made the tricks work, then show what changed on the +3 and 128K.

See a border turn the ULA’s screen fetches blue, Cobra run on a 48K and lock on its first frame on a +3, a 16-column colour band collapse at 17, Dominic Robinson’s 1987 stack routine write two cells at once, and modern Spectrum games and demos carry multicolour across the screen. The border, floating-bus and multicolour demonstration source is available to inspect and run.

Watch on YouTube

The code from this film

Programs we wrote or measured for the film, to read and run yourself.

Sources

Every fact in the film is tied to a quotation from a source. These are the 26 published ones, most cited first.

  1. The Definitive Programmer’s Guide to Using the Floating Bus Trick on the ZX Spectrumsky.relative-path.com · cited 57 times
  2. 48K ZX Spectrum Technical Informationworldofspectrum.org · cited 46 times
  3. ZX Spectrum (Wikipedia)en.wikipedia.org · cited 21 times
  4. StarTip1.txt (spectrumcomputing.co.uk, PDF)spectrumcomputing.co.uk · cited 20 times
  5. BIFROSTENGINEV1.2.txt (spectrumcomputing.co.uk, PDF)spectrumcomputing.co.uk · cited 19 times
  6. Spectrum timing informationspectrumcomputing.co.uk · cited 18 times
  7. Cobra (Spectrum Computing)spectrumcomputing.co.uk · cited 17 times
  8. 128K ZX Spectrum Technical Informationworldofspectrum.org · cited 16 times
  9. NIRVANA+ENGINE.txt (spectrumcomputing.co.uk, PDF)spectrumcomputing.co.uk · cited 12 times
  10. Z80 Opcode Tableclrhome.org · cited 10 times
  11. Performing the IN FF mod on a Spectrum +2A / +3 (Spectrum for Everyone)spectrumforeveryone.com · cited 8 times
  12. NIRVANAENGINE.txt (spectrumcomputing.co.uk, PDF)spectrumcomputing.co.uk · cited 8 times
Show all 26 sources
  1. GitHub - Paulie68000/ZXSpectrumCobra: Full reverse engineered source code of Jonathan "Joffa" Smith's Cobragithub.com · cited 6 times
  2. BIFROST2ENGINE.txt (spectrumcomputing.co.uk, PDF)spectrumcomputing.co.uk · cited 6 times
  3. In Color by Darklite & Offence (pouët.net)pouet.net · cited 4 times
  4. Cobra (video game) (Wikipedia)en.wikipedia.org · cited 3 times
  5. Star Tip 1 (Spectrum Computing)spectrumcomputing.co.uk · cited 3 times
  6. Jonathan Smith (games programmer) (Wikipedia)en.wikipedia.org · cited 2 times
  7. Uridium (Wikipedia)en.wikipedia.org · cited 2 times
  8. 70908 by Scoopex (pouët.net)pouet.net · cited 2 times
  9. 70908 (Spectrum Computing)spectrumcomputing.co.uk · cited 2 times
  10. Zilog Z80A Technical Informationworldofspectrum.org · cited once
  11. Dominic Robinson (Wikipedia)en.wikipedia.org · cited once
  12. Zynaps (Wikipedia)en.wikipedia.org · cited once
  13. Call stack (Wikipedia)en.wikipedia.org · cited once
  14. NIRVANA ENGINE (Spectrum Computing)spectrumcomputing.co.uk · cited once

Our own research: 7 records of the experiments, measurements and readings we made for this film, cited 191 times.

Transcript

The narration as it is spoken in the film, chapter by chapter. Pick a time to play the film from there.

Read the full transcript

Colour That Should Not Fit

0:12This is In Color on the ZX Spectrum. How did programmers get a different colour on every pixel line, inside squares that normally share just two colours?

0:30This copy of Cobra plays on a 48K. On a +3, the same game freezes on its first frame. The +3 was a later Spectrum, made after Amstrad bought Sinclair's computer range.

0:48Both puzzles come from programs betting on what the Spectrum's display hardware will do at an exact moment. One extra column can be enough to make that bet fail. We'll see the deadline it misses, then the twenty-column routine Your Sinclair printed in nineteen eighty-seven.

1:09By the end, you'll understand how programmers raced the Spectrum's display chip, the ULA, down to individual processor ticks. You'll see why the same code could work on one Spectrum and fail on another.

The Race Begins

1:24A write can beat the ULA's read, or miss it. The ULA is the chip that turns screen memory into a television picture. It reads across one line and then the next, about fifty times a second. The moving point where the picture is being drawn is the beam.

1:48The processor, the Z80, can change that memory while the picture is being made. Whether the ULA reads the old value or the new one depends on which gets there first. To make that race repeat, programmers count to the same moments each time. They count in T-states, the individual ticks of the processor clock. On a 48K, one complete television line takes two hundred and twenty-four of them.

2:21A frame is one complete television picture. At the start of every frame, an interrupt normally pauses the program so the processor can run a routine, giving the code a reference point. A whole 48K frame takes sixty-nine thousand, eight hundred and eighty-eight T-states. On the 128K, it takes seventy thousand, nine hundred and eight. Those are timetables a program can work to.

Drawing in the Border

2:53The border makes a good place to start. You can't put a drawing there by changing the screen's pixels. Yet this program is drawing outside the picture.

3:07The border has no picture stored in screen memory. The ULA has a setting for its colour. Change that setting while a line is being drawn, and the rest of the line takes the new colour. The part already sent to the television keeps the old one.

3:28The instruction on screen writes to the ULA's input and output port, its numbered connection to the processor. The lowest three bits choose the border colour. The drawing is in the timing: send this colour now, wait, then send the next. The stripes are one of our small demonstration programs, and we've released their source with the film. Our stripes do exactly that, counting from the frame interrupt. In this test, an edge can occupy only one of the border's eight-pixel positions. The border becomes a canvas, but the program has to send the colours again for every frame.

4:12On some Spectrums the screen timetable starts one T-state later. Here, that tiny shift tips the same colour change into the previous eight-pixel position, so the edge appears one whole step earlier.

4:32Artists could make the border part of the composition, rather than merely a frame around it. Here, lines from the picture continue into the space outside. None of that needs extra picture memory out there. It needs the processor to keep its appointments.

The Port Nobody Answers

4:49Counting from an interrupt is one way to find your place. This program gets a clue from the screen memory itself. Watch the border beside the blue band.

5:06For colour, the Spectrum picture is divided into eight-by-eight cells. Each cell has one colour byte, called its attribute. In the marked band, rows eight to fifteen use an attribute value found nowhere else on the screen. The processor keeps reading a port with nothing attached to it. Rather unhelpfully for the idea of an empty port, it gets an answer. The data bus is the set of connections carrying a byte between parts of the machine. On the original Spectrum, a value the ULA fetches for the picture can also turn up in the processor's port read. That's the floating bus. When our program catches that particular attribute, it turns the border blue. Otherwise it turns it white. So blue appears in the border beside those rows and nowhere else. It's the ULA's fetching made visible. A program can use that reading to tell where the beam has reached.

6:12It isn't a continuous stream of screen data. In the border, and in the gaps between fetches, the read gives two hundred and fifty-five. In code, that's $FF: the dollar sign marks a hexadecimal number, and $FF is two hundred and fifty-five. A program has to distinguish a useful screen byte from those gaps. On the +3, this same probe stays white. At the port it's reading, the expected screen byte never arrives. That missing reading is the difference Cobra depends on.

Ten Bytes Freeze Cobra

6:57Cobra turns that difference into a stopped game. It's Joffa Smith's nineteen eighty-six game for Ocean, very loosely based on the Stallone film. The part that matters here takes ten bytes.

7:17According to Paul Hughes, who worked with Smith at Ocean and later documented Cobra's code, the game draws straight into screen memory, without a second picture buffer to prepare out of sight. Waiting for a known place lets it draw the map and sprites, and finish before the beam catches up. Without that timing, the beam can reach a partly updated picture. The wait is called with interrupts switched off, so an interrupt can't break into the timing loop.

7:51Like our probe, Cobra separates a useful fetched value from the idle answer. The first line loads $28 for the upper half of a port address and $3F, sixty-three, as the comparison value. The read instruction uses $FF for the lower half, making $28FF, a port nothing answers, then the loop compares the byte that came back with sixty-three. That cut-off isn't a general rule for Spectrum screen data. It works here because Cobra's displayed screen includes a fetched byte below sixty-three, while the idle answer is two hundred and fifty-five. At sixty-three or more, the loop repeats. As soon as the smaller screen byte arrives, the routine returns and the game gets on with drawing.

8:47The border's $FF value can't let it out. Neither can the $FF values in the gaps between fetches. A smaller return therefore tells the game that the ULA has reached its screen data. Hughes says that documentation came from many long chats with Smith. His source gives us this little wait routine. Ast A. Moore, in his floating-bus guide, suggests Smith found the trick by accident around nineteen eighty-six. On the +3, that port always returns FF. The comparison never succeeds. The processor isn't waiting for something slow to finish. It's going round those four instructions for ever, with interrupts off. The menu is still running before play begins. Once the game starts, it freezes on its first frame.

9:46Amstrad bought Sinclair's computer range in nineteen eighty-six. Its later hardware separated the processor's side of the data bus from the ULA's side in a different way. Earlier Spectrums linked those sides through eight four-hundred-and-seventy-ohm resistors. That allowed the ULA's byte to appear in the processor's port read. The +3 and +2A used tri-state buffers inside their custom chip to keep the processor's side electrically isolated from the ULA's. At Cobra's old port, the ULA's byte no longer came through.

10:28I'd call that a compatibility trade-off, rather than a botched Spectrum. The separation was sound engineering, but software had found a use for what leaked through the old arrangement. And Cobra came first: the +3 arrived in nineteen eighty-seven, the +2A from late nineteen eighty-eight. Smith couldn't have tested his nineteen eighty-six game on either. The Hit Squad's nineteen eighty-nine re-release repairs the problem in place. The same ten bytes, at the same address, now count seven hundred and sixty-eight down to zero and return. There's even a NOP, an instruction that does nothing, to keep the replacement exactly the right length.

11:21That version plays on both the 48K and the +3. But it now waits a fixed amount of time, about a third of a 48K frame, whatever the beam is doing. It's a delay, not a check on the display.

11:40Ast A. Moore's account says: “Cobra, however, suffered from a great deal of flickering, much to Joffa’s chagrin.” That's his description of the re-release, not a measured comparison of the games you've just seen.

11:58Moore's guide gives a qualification. In his account, Cesar Hernandez found signs of a floating bus on the +2A and +3 in early twenty sixteen, and Moore took up the question in autumn twenty seventeen. Moore says this behaviour appears only at a narrow set of ports, and only while the 128K's extra memory can still be switched in. That makes it unavailable in 48K mode. Cobra's $28FF and our probe's $00FF are both outside that narrow set. On Moore's account, the later machines exposed a different floating-bus behaviour at other ports. So, on that account, saying the +3 had no floating bus at all goes too far. It didn't have the behaviour Cobra was waiting for.

One Line Before the Next

12:58On a 48K, the next of our demonstration programs gives one cell-wide strip a different colour on every pixel line.

13:10As we've seen, ordinary Spectrum colour belongs to an eight-by-eight cell. Its attribute gives that whole cell one foreground and one background colour. If two objects need different colours inside the same cell, something has to give. That's colour clash. The useful detail is that the ULA doesn't read that colour once and remember it for all eight lines. It fetches the attribute again for each pixel line. Let it fetch the first colour. Then change the attribute before it fetches the next line. Change it again for the line after that. The same byte of memory supplies different colours at different moments.

13:58There are still only two colours for any eight pixels on one line. But the next line can use a different pair. That's the principle of multicolour: finer colour detail without changing the hardware's screen format. One column leaves plenty of screen untouched. To make a wider band, our routine writes a colour byte, moves to the next cell, and writes again. It has to finish each change after that cell's previous fetch and before its next one.

14:42Sixteen columns still work. The colours stay separate on all eight lines. Add one more column, and this happens.

14:58At the end, cells keep the previous line's colour. The miss repeats farther along each new line, so the wrong colours grow into steps down the band. Seventeen columns are beyond this routine, not beyond the Spectrum. The ULA fetches the next cell's attribute every four T-states. Our processor can't just write whenever it likes, because the ULA needs the same memory. When the two compete, the ULA stops the processor's clock, takes its byte, then lets the processor carry on. That's memory contention. While the ULA is fetching the picture, those waits make our writes land every sixteen T-states. In the border, the ULA isn't fetching screen memory, so the writes aren't held. The routine's schedule uses both kinds of slot. The decisive deadline is one complete 48K line, two hundred and twenty-four T-states. The start is the moment in that line when the routine begins its writes. It can be moved so those writes meet the cells' fetch windows. With that start tuned, sixteen columns take exactly two hundred and twenty-four T-states. A seventeenth needs one more write, held like the others, so the pass takes at least two hundred and forty.

16:22At sixteen columns, a tuned start fits with no time to spare. At seventeen, each pass overruns the line, so the next begins later and the stepped error grows. Moving the start can't shorten the work, and none of the start times tried produced a complete seventeen-column band. The work itself has to change.

Two Cells at a Time

16:53For a better answer, go back to nineteen eighty-seven. This is Dominic Robinson's Rainbow Processor, printed as a type-in in the August issue of Your Sinclair. Its band is twenty columns wide. Robinson had converted Uridium to the Spectrum in nineteen eighty-six. Your Sinclair said the Rainbow Processor Robinson wrote was used for effects on the title and high-score screens in two games published by Hewson, Uridium and Zynaps. Here, Uridium's rainbow lettering shows the effect directly. In August nineteen eighty-seven, readers could put the printed routine into their own programs. Robinson was twenty-one.

17:41His routine doesn't write one cell, move along, then write another. It borrows the Z80's stack, an area of memory normally used to put working values aside. A PUSH instruction saves one register pair, two bytes at once, and moves backwards through memory. The stack pointer is an address, and Robinson points it at the right-hand end of the colour row. He loads both halves of a register pair with the line's colour, so each PUSH writes that colour into two neighbouring cells and steps left through memory. Ten pushes write twenty cells, right to left. On the next pixel line, he points the stack back at the row and does it again with the next colour.

18:33Each PUSH handles two adjacent cells in one instruction, so the routine spends fewer instructions inside each line's deadline. That's how it reaches twenty columns where our one-cell-at-a-time loop stopped at sixteen. It uses the frame interrupt from earlier to begin that repeated work in step with the television picture.

18:58The deeper the effect, the less processor time remains for anything else. Your Sinclair warned that it “can only really be used for title screens and special effects.”

Making Time for a Game

19:14An engine here is reusable display code that another programmer can build a game around. Einar Saukas developed engines that did that for multicolour. Yazzie names NIRVANA on its title. Then it gets on with being a game. In this first level, the colour changes every second pixel line across all thirty-two columns, the width Saukas documents for NIRVANA Plus. It isn't confined to the lettering before play begins.

19:52This is the same picture restricted to ordinary eight-by-eight colour. The shapes haven't changed. Each square just has to share its colours again. That's the detail those repeated attribute changes buy.

20:10The trade-off is time. BIFROST star and BIFROST star two rewrite the colours for every pixel line. That preserves the finest vertical colour detail, but their chosen bands stop short of the full width. NIRVANA and NIRVANA Plus let each colour pair last for two pixel lines. That leaves twice as much time for each colour write, so these engines can reach much farther across. Their documentation calls this bicolor. It's coarser vertically, but wider. Those are choices made by these engines, not a proof of what the Spectrum can never do.

20:54BIFROST star is free to use, even for commercial games, provided the programmer credits it. The hard timing work can become a tool somebody else starts from.

21:10There's still more than one timetable to support. A 48K line takes two hundred and twenty-four T-states. On the 128K family it's two hundred and twenty-eight, part of the longer frame timetable we met at the start. Run a 48K line schedule unchanged, and it gets four T-states farther ahead of the beam on every line. That accumulating drift moves the writes farther out of their intended windows as the code works down the screen. The earlier border example was one edge tipping between two eight-pixel positions. Here, the schedule repeats line after line. Saukas's BIFROST star engine automatically chooses a different execution path for the 48K and the 128K variants. Its manual warns that being just a couple of T-states out can make an edge column flicker.

22:12And a game still needs time to be a game. A 48K frame has just under seventy thousand processor ticks in all. Across BIFROST star's documented eighteen-by-eighteen area, about twenty thousand remain for other work. With NIRVANA Plus covering twenty-three rows, about twelve and a half thousand remain. Those aren't like-for-like areas, but in either case the controls and game logic have to fit into what's left after the colour.

Why 70,908?

22:45A demo can spend its time differently. In Color won the oldskool demo competition at Revision in twenty twenty-three. Here, colour changes on every pixel line across twenty-eight columns, wider than the BIFROST engines' eighteen and twenty columns, which also change colour on every pixel line. Across twenty-eight columns, the ordinary-colour conversion makes the lost smoothness visible.

23:32This is Scoopex's Seventy Thousand, Nine Hundred and Eight, winner of the Spectrum demo competition at Forever in twenty nineteen. Its multicolour reaches across all thirty-two columns, with colour changes most often every fourth line. The number is familiar now. Seventy thousand, nine hundred and eight T-states make one complete frame on the 128K Spectrum this demo runs on. For any program on that machine, that's the whole span before the next frame begins. The border edge, Cobra's wait and every multicolour row all had to land inside a machine's own schedule.

24:38The Spectrum's colour rule described what one stored picture could hold. Programmers found what they could show by changing it while it was being read. That bargain worked only if the next machine kept the timing, and exposed the same hardware behaviour, or the program learned the difference. You can inspect and run our border, floating-bus and multicolour programs at GitHub dot com, slash Retro Computer TV, slash Spectrum Racing the ULA. For more stories from inside these machines, subscribe to Retro Computer TV.

Credits

Every image, clip and piece of music in the film, with its licence and where it came from: the film's credits.

More on ZX Spectrum