Skip to content

Screen Memory

How the Commodore 64 and the ZX Spectrum keep their pictures in memory

On the C64 and the ZX Spectrum the picture is ordinary memory that the video chip reads every frame. The C64's text screen is 1000 character codes that can move to any 1K boundary, beside 1000 nybbles of colour memory that never move. The Spectrum's is a fixed 6,144-byte bitmap in an interleaved order, followed by 768 attribute bytes, all in memory the ULA shares with the processor.

On the Commodore 64 and the ZX Spectrum the picture on the television is a block of ordinary memory. The video chip reads it every frame, so changing a byte changes the picture. The two machines lay that memory out very differently. The C64’s text screen stores one character code for each of its 1000 positions, in reading order, and keeps their colours in a separate chip that cannot move. The Spectrum stores every pixel, 6,144 bytes of them, in an order Sinclair’s own manual calls “rather curiously laid out”, and puts one colour byte for each 8×8 square straight after them. In both cases the layout comes from the video hardware, and it decides what drawing costs.

Commodore 64

Type POKE 1024,1 on a Commodore 64 and a letter A should appear in the top-left corner of the screen. Often nothing seems to happen at all. Sheldon Leemon explained why in Mapping the Commodore 64: the newer version of the operating system sets the colour of every character to the background colour whenever it clears the screen, so the A is there, drawn in blue on blue. POKE 55296,1 turns it white.

That small puzzle shows how the C64’s text screen works. It is two blocks of memory, not one. Screen memory holds a code for which character to show at each of the 40×25 positions. Colour memory holds each position’s colour. The VIC-II video chip reads both and draws the picture, again and again, with no help from the processor. Change a byte and the picture changes on the next frame. The two blocks also behave differently: screen memory can be moved almost anywhere, and colour memory cannot be moved at all.

Fast facts

  • Screen memory: 1000 bytes, normally $0400–$07E7 (1024–2023), one screen code per character position.
  • Colour memory: 1000 four-bit locations at $D800–$DBE7 (55296–56295). Fixed.
  • Layout: row by row, 40 bytes to a row, top-left first.
  • Character shapes: 8 bytes per character, normally from the character ROM.
  • Pointers: $D018 (53272) places screen memory and the character set within the VIC-II’s 16K bank; bits 0 and 1 of $DD00 (56576) choose the bank.

Screen memory

The Programmer’s Reference Guide counts “1000 possible locations on the Commodore 64 screen”, starting at 1024 and running to 2023. Each holds one byte, a screen code from 0 to 255. The rows follow each other in memory:

Row Addresses
0 (top) 1024–1063
1 1064–1103
2 1104–1143
… …
24 (bottom) 1984–2023

So any position is one sum away:

BASIC
10 R=10:C=5
20 S=1024+R*40+C:REM SCREEN MEMORY
30 K=55296+R*40+C:REM COLOUR MEMORY

Position (0,0) is 1024, (0,39) is 1063, (1,0) is 1064 and (24,39) is 2023.

Screen memory occupies a 1K block, of which the screen uses 1000 bytes. Of the 24 bytes left over, the last eight (2040–2047 with the screen at 1024) are the sprite pointers, which is why they move when the screen moves.

Colour memory

The Programmer’s Reference Guide describes “a group of 1000 locations called COLOR MEMORY or COLOR RAM”, from 55296 to 56295, each “4 bits wide”, enough for the 16 colours. It is a separate chip. Commodore’s service manual lists it as a 2114 static RAM, U6 on the board, “allocated for screen color data storage”, and suggests checking it first when “abnormal colors appear in the letters”.

Only the low four bits of each location exist. Leemon writes that “writing to the high bits will not affect them, and these four bits will usually return a random value when read”, so a program reading colour memory masks them off:

BASIC
10 CR=PEEK(55296)AND15

The colour values are the same as for the border and background registers: 0 black, 1 white, 2 red, 3 cyan, 4 purple, 5 green, 6 blue, 7 yellow, 8 orange, 9 brown, 10 light red, 11 dark grey, 12 grey, 13 light green, 14 light blue, 15 light grey.

How the VIC-II uses them

In the standard text mode each screen code selects a character from the character set, and its colour memory nybble sets the colour of that character’s pixels. Pixels that are off show the background colour from $D021.

The VIC-II does not read screen and colour memory on every line. On the first pixel line of each character row, a badline, it stops the processor and reads all 40 screen codes and 40 colours for the row in one burst. On every line it reads one byte of shape data for each character. The VIC-II entry describes the timing and what it costs the processor.

Screen codes are not PETSCII

The number stored in screen memory is a screen code, and it is not the same as the PETSCII code that PRINT and CHR$ use. The Programmer’s Reference Guide lists the two in separate appendices.

Character PETSCII (CHR$) Screen code (POKE)
@ 64 0
A 65 1
B 66 2
Space 32 32
0 48 48

Screen codes 128–255 are, in the guide’s words, “reversed images of codes 0-127”: add 128 to any code to show it in reverse video. Screen code 160 is a reversed space, a solid block. See PETSCII Chart for the full tables.

Examples

A yellow H in the top-left corner (screen code 8, colour 7):

BASIC
10 POKE 1024,8
20 POKE 55296,7

A word at row 10, column 15, converting the letters A–Z from PETSCII to screen codes as it goes:

BASIC
10 T$="HELLO"
20 P=1024+10*40+15
30 FOR I=1 TO LEN(T$)
40 A=ASC(MID$(T$,I,1))
50 IF A>=65 AND A<=90 THEN A=A-64
60 POKE P+I-1,A
70 POKE P+I-1+54272,1
80 NEXT I

54272 is the distance from screen memory at 1024 to colour memory at 55296, so line 70 colours each letter white.

A screen of solid blue blocks:

BASIC
10 FOR I=0 TO 999
20 POKE 1024+I,160
30 POKE 55296+I,6
40 NEXT I

BASIC takes a few seconds over this. The same job in machine code, or a printed string, is far quicker, but the POKE loop shows exactly what is being stored.

The character set

A character is 8 bytes, one per row of 8 pixels, and a full set of 256 characters takes 2K. The shapes normally come from the character ROM. The processor sees the ROM at $D000–$DFFF only when the I/O chips are switched out of that address range. The VIC-II sees it somewhere else: the guide explains that the chip is made to “think that the ROM characters are at 4096-8191 ($1000-$1FFF)” in bank 0, and at $9000–$9FFF in bank 2, “even though the character ROM is actually at location 53248-57343”. Banks 1 and 3 have no ROM image, so a program using them must supply its own character set.

The letter A is character 1, so its shape is the second 8 bytes of the ROM:

Code
$D008  00011000  $18
$D009  00111100  $3C
$D00A  01100110  $66
$D00B  01111110  $7E   the crossbar
$D00C  01100110  $66
$D00D  01100110  $66
$D00E  01100110  $66
$D00F  00000000  $00

Pointing the VIC-II at a character set in RAM, a copy of the ROM with some characters redrawn, is how most C64 games draw their scenery.

Moving the screen: $D018 and $DD00

Register $D018 (53272) holds two pointers, each an offset inside the VIC-II’s 16K bank:

Code
Bits 7-4: screen memory, in 1K steps (value × $0400)
Bits 3-1: character set, in 2K steps
Bit 0:    unused

At power-up the upper four bits are 1, which puts screen memory at $0400, and bits 3–1 are 2, which puts the character set at $1000, the ROM image with upper case and graphics. Bit 0 reads back as 1, so PEEK(53272) gives 21. The guide’s two everyday values are POKE 53272,21 for upper case and graphics and POKE 53272,23 for lower and upper case, which moves the character set to $1800, the ROM’s second half.

To move screen memory without disturbing the character set, the guide gives:

BASIC
POKE 53272,(PEEK(53272)AND15)OR A

where A is 16 times the number of 1K steps: 16 for $0400, 32 for $0800, 48 for $0C00, up to 240 for $3C00. The guide adds that the KERNAL’s screen editor has to be told as well, with POKE 648 and the new address divided by 256, or PRINT carries on writing to the old screen.

The 16K bank comes from the lowest two bits of CIA 2’s port A at $DD00, and the bits count backwards: 3 (binary 11) selects bank 0, $0000–$3FFF, the default; 2 selects $4000–$7FFF; 1 selects $8000–$BFFF; and 0 selects $C000–$FFFF. Screen memory, character set and sprites all have to be in the chosen bank.

Two screens, one colour memory

Colour memory, the guide says, “can NOT move. It is always located at locations 55296 ($D800) through 56295 ($DBE7).” Screen memory can sit at any 1K boundary. That split decides how C64 programs switch between screens. A program can draw a second screen out of sight and then show it by changing $D018, but both screens share one set of colours.

This short program puts a second screen at $3C00 (15360), the last 1K of bank 0. Line 10 first lowers the top of BASIC’s memory to 15360, the method Leemon recommends for keeping BASIC’s variables out of an area a program wants for itself.

BASIC
10 POKE 56,60:CLR
20 FOR I=15360 TO 16359:POKE I,160:NEXT
30 POKE 53272,(PEEK(53272)AND15)OR240
40 POKE 648,60
50 FOR T=1 TO 2000:NEXT
60 POKE 53272,21:POKE 648,4

Line 20 fills the hidden screen with screen code 160, a solid block. Line 30 shows it, in whatever colours are already in colour memory, and line 40 sends PRINT there too. Line 60 puts everything back. Games use the same switch for double buffering, building the next frame of a scrolling screen while the player watches the current one. Colour memory cannot be double-buffered, so when the colours scroll too they have to be moved while the beam is somewhere else, which is one of the costs of scrolling on the C64.

Things to watch for

  • Screen memory is 1000 bytes of a 1K block. Writing past the 1000th byte does not wrap to the top of the screen: it lands in the unused bytes and then the sprite pointers.
  • Only the low four bits of colour memory exist, so mask PEEK results with AND 15.
  • Screen memory must start on a 1K boundary within the current bank.
  • The character ROM cannot be written. To change a character, copy the set into RAM and point $D018 at the copy.
  • On machines with the newer operating system, clearing the screen sets colour memory to the background colour, so POKE a colour as well as a character.

Beyond text: FLI

Demo coders used the same two pointers to break the colour rules. In the bitmap modes the colour of each 8×8 cell comes from screen memory, which the VIC-II reads only on badlines, once every eight lines. Christian Bauer’s description of the chip explains the trick known as FLI (flexible line interpretation): “if you make every raster line a Bad Line by appropriately modifying YSCROLL, the VIC will read from the video matrix in every line and so it will also read new color information for every line.” Because the chip does not move on through screen memory when a badline is forced like this, the program also switches $D018 to a different screen on each line, cycling through eight of them. Colour memory still cannot be switched, so its share of the colour stays fixed for each cell.

It costs almost the whole processor. A widely used display routine on Codebase64 runs a loop of exactly 23 cycles per line, which with the 40 cycles the VIC-II takes for its colour fetches fills the 63 cycles of a PAL line. It also leaves a mark: the forced fetch starts too late for its first three reads, which get the value $FF, and Bauer notes that this shows as “24 pixel wide stripes on the left side of the screen”, the FLI bug. The FLI entry has the full story.

ZX Spectrum

Sinclair’s manual did not encourage anyone to touch the Spectrum’s screen directly. The display file, Steven Vickers wrote, “is rather curiously laid out, so you probably won’t want to PEEK or POKE in it”, and he advised programmers used to poking a screen to “start using SCREEN$ and PRINT AT instead, or PLOT and POINT”. Machine-code programmers wrote to it anyway. The layout follows simple rules once it is written as an address, and it was arranged with machine code in mind.

Fast facts

  • Display file: 6,144 bytes at 16384–22527 ($4000–$57FF), one bit per pixel for 256×192 pixels. A set bit shows the square’s INK colour, a clear bit its PAPER colour.
  • Attribute file: 768 bytes at 22528–23295 ($5800–$5AFF), one byte for each of the 32×24 character squares, in reading order.
  • Together: 6,912 bytes. The manual defines LOAD "name" SCREEN$ as the same as LOAD "name" CODE 16384,6912.
  • Fixed: the ULA always reads the picture from $4000 on the 48K machine. The Spectrum 128 and +2 add a second screen (below).
  • Border: not memory at all, but bits 0–2 of the byte written to port 254 ($FE).
  • Shared: all of it sits in the lower 16K of RAM, which the ULA shares with the Z80.

The display file

Each row of 256 pixels takes 32 bytes, left to right, with the leftmost pixel of each byte in bit 7. What the manual calls “curious” is the order of the rows. It explains that “the corresponding rows in the 32 characters of a single line are stored together as a scan of 32 bytes”, and then: “First come the top scans of lines 0 to 7, then the next scans of lines 0 to 7, and so on to the bottom scans of lines 0 to 7; then the same for lines 8 to 15; and then the same for lines 16 to 23.” A “line” there is a line of characters, eight pixel rows deep.

So the screen is three blocks of 2,048 bytes, the top, middle and bottom thirds, each 64 pixel rows high. Within a third, the first 256 bytes hold the top pixel row of each of its eight character lines, the next 256 bytes the second pixel row of each, and so on.

That order looks strange as a list of rows. As an address it is tidy. Number the pixel rows y from 0 at the top to 191 at the bottom, and the pixel columns x from 0 to 255. Then the 16-bit address of the byte holding pixel (x, y) is made from the bits of those two numbers:

Code
             high byte (H)                   low byte (L)
bit     15 14 13 12 11 10  9  8      7  6  5  4  3  2  1  0
         0  1  0 y7 y6 y2 y1 y0     y5 y4 y3 x7 x6 x5 x4 x3

y7 y6       which third (0, 1 or 2)
y2 y1 y0    pixel row inside the character square (0-7)
y5 y4 y3    character line inside the third (0-7)
x7 ... x3   byte column (0-31)

Chris Smith, who reconstructed the ULA’s logic from the silicon, puts it in one sentence: the address of a display byte comes from “setting A4-0 to the column number, A12-5 to the line number and swapping A10-8 with A7-5”. Take a row-by-row address and swap two groups of three bits.

A worked example: the byte holding pixel (100, 100).

  • y = 100 is 01 100 100 in binary. The third (y7 y6) is 1, the middle third. The character line inside the third (y5 y4 y3) is 4, and the pixel row inside the square (y2 y1 y0) is 4.
  • x = 100 gives byte column 100 ÷ 8 = 12 (01100), and 100 mod 8 = 4, so the pixel is the fifth from the left, bit 3.
  • H = 010 01 100 = $4C. L = 100 01100 = $8C.
  • The address is $4C8C, 19596: 16384 + 2048×1 + 256×4 + 32×4 + 12.

Sinclair BASIC has no bitwise operators, so a BASIC program builds the same sum with INT. This lights the eight pixels from x = 96 to 103 on row 100:

BASIC
10 LET x=100: LET y=100
20 LET a=16384+2048*INT (y/64)+256*(y-8*INT (y/8))+32*(INT (y/8)-8*INT (y/64))+INT (x/8)
30 POKE a,255
40 PRINT AT 0,0;a

The ROM does the same job for PLOT and POINT in its PIXEL-ADD routine at 22AA. Ian Logan and Frank O’Hara’s Complete Spectrum ROM Disassembly annotates it building H as “64 + 8INT (B/64) + B (mod 8)” and L as “32INT (B(mod 64)/8) + INT(x/8)”, which are the same fields. One difference catches people out: B there is 175 minus the PLOT y co-ordinate, because PLOT counts upwards from the bottom, and the manual gives it only 22 of the 24 character lines: the bottom 16 pixel rows belong to the lower part of the screen, where commands are typed.

One row down: INC H

The layout makes the most common step in drawing very cheap. Moving down one pixel row inside a character square changes only y2 y1 y0, which sit at the bottom of the high byte, so it adds exactly 256 to the address. Smith explains that this was the reason for putting them there: a 256-byte offset “allows software to step an address from one line to the next simply by incrementing the address high byte, easily achieved with a single Z80 instruction”. That instruction is INC H, and a routine that draws an 8-pixel-high character or sprite into one square needs nothing else between rows.

Crossing into the next square down takes a correction. Smith: the next line “may be stepped to by incrementing the high address byte until A10-8 are 111 binary, at which point the next line address may be found by recalculating the whole address or by applying an address adjustment”. The usual adjustment, with the address in HL:

Assembly
down:   inc  h          ; next pixel row: add 256
        ld   a,h
        and  7          ; did y2-y0 wrap from 7 to 0?
        ret  nz         ; no: still inside the square, done
        ld   a,l
        add  a,32       ; yes: next character line (y5-y3 + 1)
        ld   l,a
        ret  c          ; carry out of L: next third, and H is already right
        ld   a,h
        sub  8          ; same third: undo the carry INC H made into y6
        ld   h,a
        ret

When INC H takes y2–y0 from 7 back to 0, the carry goes into the third bits and moves the address down a whole third. Adding 32 to L moves to the next character line instead. If that addition overflows, the move really was into the next third and the carry already made H correct. If it does not, the routine takes the 8 back off H. Starting from $4700, pixel row 7, it returns $4020, row 8. Starting from $47E0, row 63, it returns $4800, row 64, the top of the middle third.

The attribute file

After the display file come the attributes, which the manual says “are stored line by line in the order you’d expect”: 32 bytes for each of the 24 character lines, so the square at line r, column c is at 22528 + 32×r + c. The manual documents the byte through the ATTR function, whose result is the sum of “128 if the character position is flashing”, “64 if the character position is bright”, “8* the code for the paper colour” and “the code for the ink colour”:

Bit Meaning
7 FLASH: 1 swaps INK and PAPER at the ULA’s flash rate
6 BRIGHT: 1 for the brighter version of both colours
5–3 PAPER colour, 0–7
2–0 INK colour, 0–7

The colour codes are 0 black, 1 blue, 2 red, 3 magenta, 4 green, 5 cyan, 6 yellow and 7 white. The manual’s own example is a flashing square with yellow paper and blue ink: 128 + 0 + 8×6 + 1 = 177. POKE that value into the attribute file and ATTR reads it back:

BASIC
10 LET r=10: LET c=5
20 PRINT AT r,c;"A"
30 POKE 22528+32*r+c,177
40 PRINT AT 0,0;ATTR (r,c)

Line 20 prints the A in the current colours, line 30 recolours its square without touching its pixels, and line 40 prints 177. Two colours per square is the whole colour system, and the source of colour clash; attribute-aware design covers how games worked with it.

The low byte of a display address and of its attribute address are the same, y5 y4 y3 followed by the byte column. Only the high byte differs, and the ROM’s PO-ATTR routine at 0BDB converts one to the other in five instructions: the display high byte “is divided by eight and ANDed with +03 to determine which third of the screen is being addressed”, then ORed with $58. In the example above, $4C becomes 1, then $59, so the attribute for the byte at $4C8C is at $598C, 22924, which is 22528 + 32×12 + 12.

Why the layout is interleaved

The layout comes from the way the ULA reads memory. For each 8 pixels of the picture it needs a display byte and the attribute byte for the same square, and it fetches them as a pair using the dynamic RAM’s page mode: one row address, then two column addresses. Smith: “both the display byte and attribute byte addresses must share the same 7-bit row address.” In a plain row-by-row bitmap the low bits of a pixel byte’s address change with every pixel row, while its attribute address stays put, so the pairing is impossible. Moving y2 y1 y0 out of the low byte makes the low bits of the two addresses match. Smith sets out the places they could have gone, 128, 256 or 1,024 bytes apart, and the ULA uses bits 8–10, the 256-byte step, for the INC H reason above. In Smith’s words, the memory is “carefully arranged so that an attribute byte is in the same memory row as its display byte, leading to the peculiar screen byte order”.

The order is visible whenever a picture loads from tape. LOAD "" SCREEN$ writes the 6,912 bytes in address order, so the picture arrives as a pixel row in every eighth line of the top third, then the rows below them, then the same in the middle and bottom thirds. The colours come last, square by square, because the attribute file comes after the display file.

The border

The border has no memory of its own. The manual: “Port address 254 in output drives the loudspeaker (D4) and the MIC socket (D3), and also sets the border colour (D2, D1 and D0).” In machine code:

Assembly
        ld   a,2        ; red, with the speaker and MIC bits clear
        out  ($fe),a

BASIC’s BORDER command does the same OUT and then, Logan and O’Hara show, keeps the colour times eight in the system variable BORDCR, which the ROM uses to colour the lower part of the screen. The ULA entry explains how the ROM’s tape routines use the same bits to draw the loading stripes.

What drawing costs

The screen lives in the 16K of RAM that the ULA reads to draw the picture, and the ULA comes first. Smith describes what happens when the ULA “detects a CPU address between 16384 and 32767 while it is reading video bytes”: it “immediately holds the Z80 clock high” until its own fetch is done. Every write to the screen made while the picture is being drawn can be held up like this, while code and data in the upper 32K run at full speed. This is contended memory; the ULA entry gives the delay pattern.

Size is the other cost. Copying the whole display file with LDIR takes 21 T-states a byte, about 129,000 T-states, nearly two of the 69,888-T-state frames, before any contention. The attribute file is an eighth of the size, which is why recolouring the screen is so much cheaper than redrawing it. Period programmers worked around both. In Your Spectrum in August 1985, Mike Leaman’s “3D Turbo Charger” drew everything into a second 6K buffer, then “the entire 6K of memory is copied to the real screen, updating the whole screen at once”, and he called it “a trick that you’ll find on many commercial pieces of software though it has the disadvantage of taking up another 6K of memory”. Two months later, Chris Wood’s sprite routine in the same magazine could be told to work on 8, 16 or all 24 character lines, starting from the top, middle or bottom third by POKEing 64, 72 or 80, the high bytes $40, $48 and $50, because “the less that has to move, the faster the routine runs”. He pointed out that Matthew Smith “only used the top two thirds”. The Scrolling entry works through what moving the whole picture costs, and Double Buffering covers the second-screen approach.

The Spectrum 128 and +2 gave machine code a second screen. Amstrad’s +2 manual explains that bit 3 of port $7FFD “switches screens; screen 0 is held in RAM 5 (beginning at 4000h) and is the one that BASIC uses, screen 1 is held in RAM 7 (beginning at C000h) and can only be used by machine code programs”. Both screens have the same layout, and both are in RAM that the manual describes as contended, sharing “time with the video circuitry”.

Things to watch for

  • Count y from the top when working out addresses. PLOT counts from the bottom and covers only the top 176 rows.
  • The leftmost pixel of a byte is bit 7.
  • INC H moves down a pixel row only inside a character square. Every eighth row needs the adjustment.
  • An attribute covers a whole 8×8 square. POKEing it recolours all 64 pixels.
  • Writing the border through port 254 also writes the speaker and MIC bits, so keep bits 3 and 4 as the program needs them.

Sources

  • Commodore, Commodore 64 Programmer’s Reference Guide (1983): screen and colour memory (p. 100); bank selection (pp. 101–102); moving screen memory and POKE 648, and colour memory’s fixed address (p. 102); character memory and the ROM images in banks 0 and 2 (pp. 103–104); the character ROM’s contents and the shape of A (p. 107); POKE 53272,21 and POKE 53272,23, and the screen display codes (Appendix B, pp. 376–378); PETSCII codes (Appendix C, from p. 379).
  • Sheldon Leemon, Mapping the Commodore 64 (COMPUTE! Publications, 1984): lowering the top of BASIC with POKE 56 (p. 18); screen memory, the invisible POKE 1024,1 and the sprite pointers (pp. 81–82); $D018 and location 648 (pp. 145–146); colour memory’s four connected bits (p. 170).
  • Commodore, C64/C64C Service Manual: the 2114 colour RAM (U6) and the fault table.
  • Christian Bauer, The MOS 6567/6569 video controller (VIC-II) and its application in the Commodore 64 (1996), section 3.14.3, “FLI”.
  • Codebase64, “FLI” and “FLI display routine” (web, secondary): the 23-cycle loop and the FLI bug.
  • Steven Vickers, Sinclair ZX Spectrum BASIC Programming (Sinclair Research, 2nd edn 1983): the colour codes (ch. 16, p. 109); ATTR and the value 177 (ch. 16, pp. 115–116); the 22 lines available to PLOT (ch. 17, p. 121); port 254 (ch. 23, p. 161); the memory map, the display file’s layout and the attributes’ order (ch. 24, p. 164); the ATTR bit layout (Appendix C, p. 198); LOAD SCREEN$ as LOAD CODE 16384,6912 (Appendix C, p. 204).
  • Ian Logan and Frank O’Hara, The Complete Spectrum ROM Disassembly (Melbourne House, 1983): PO-ATTR at 0BDB (p. 39); CL-ADDR at 0E9B (p. 48); BORDER at 2294 and PIXEL-ADD at 22AA (pp. 115–116).
  • Chris Smith, The ZX Spectrum ULA: How to Design a Microcomputer (ZX Design and Media, 2010): page-mode fetches and the screen byte order (ch. 12, p. 112); the shared 7-bit row address (ch. 15, p. 137); the 256-byte step and the display address (ch. 15, pp. 141–142); the ULA holding the Z80 clock (ch. 18, p. 190); 69,888 T-states per frame (p. 225).
  • Zilog, Z80 CPU User Manual (UM008011-0816): LDIR at 21 T-states per byte.
  • Your Spectrum issue 17, August 1985, p. 36: Mike Leaman, “3D Turbo Charger”.
  • Your Spectrum issue 19, October 1985, p. 27: Chris Wood’s sprite routine.
  • Amstrad, Sinclair ZX Spectrum +2 manual (1986), p. 147: port $7FFD and the second screen.

See also

Checked. Verified against primary sources and signed off by a person.