Your pixels last unit came out black on white. You never chose those colours — so where do they come from, and how do you change them?
Not from $4000. The pixels at $4000 say only which dots are lit, never what colour. Colour lives in a separate map that starts at $5800 — one byte for each 8×8 cell on the screen. That single byte sets the colours for the whole cell, and it has nothing to do with the pixel bytes at all. Two maps, two jobs: $4000 for shape, $5800 for colour.
What you’ll see by the end

One red cell, top-left. And here’s the point: we drew no pixels to make it. We wrote a single byte to the colour map, and the whole 8×8 cell turned red. Colour and pixels are genuinely separate things in genuinely separate memory.
The attribute byte
That colour byte is called an attribute, and it packs four things into eight bits:
bit: 7 6 5 4 3 2 1 0
FLASH BRIGHT P P P I I I
└ PAPER ┘ └ INK ┘
(0-7) (0-7)- INK (bits 0–2) — the colour lit pixels show in.
- PAPER (bits 3–5) — the background colour, where pixels aren’t lit.
- BRIGHT (bit 6) — a brighter shade of both.
- FLASH (bit 7) — swap ink and paper back and forth, changing every sixteen frames: about three swaps a second.
So %00010111 ($17) means PAPER 2 (red), INK 7 (white), no bright, no flash. With no pixels set, the whole cell is just its PAPER — a solid red block.
; ============================================================================
; PRIMER — Beat 6: Colour Is a Separate Map
; ============================================================================
; The pixels live at $4000. Colour lives somewhere else entirely: a second
; map starting at $5800, one byte per 8x8 cell. That one byte holds BOTH
; colours for the cell, packed into its bits:
;
; bit: 7 6 5 4 3 2 1 0
; FLASH BRIGHT PAPER INK
; (0-7) (0-7)
;
; So $17 = %00010111 is PAPER 2 (red), INK 7 (white), no bright, no flash.
;
; We write ONE attribute byte and the whole top-left cell turns red — and we
; never touch a single pixel. Colour and pixels are stored separately. (That
; separation is exactly why "attribute clash" happens: a cell has only one
; ink and one paper, whatever its pixels are doing.)
;
; And unlike the pixels' peculiar layout (Beat 5), the colour map is laid out
; sanely: $5800 + row*32 + col. The top-left cell is just $5800.
; ============================================================================
org 32768
start:
ld a, %00010111 ; PAPER 2 (red), INK 7 (white)
ld ($5800), a ; colour the top-left cell — no pixels touched
.loop:
halt
jr .loop
end startTwo consequences worth holding onto
This is the root of “attribute clash.” A cell carries one ink and one paper for all 64 of its pixels. You cannot colour two pixels in the same cell differently — the colour map has no room to say so. Every Spectrum game works within that rule, and the good ones make a virtue of it.
The colour map is laid out sanely — unlike the pixels. Remember Beat 5’s puzzle, where $4020 landed eight rows down? The attribute map has no such tricks: it’s plainly $5800 + row × 32 + col. The top-left cell is $5800, the one to its right is $5801, and the cell directly below is $5820. Where the pixel map is famously twisted, the colour map is a plain grid.
In BASIC, INK and PAPER were writing these exact bytes for you. Now you write them yourself — and it’s the same ld (addr), a store you’ve used since Beat 4.
Assemble and run
asm198x --dialect pasmo --cpu z80 --tapbas colour-map.asm -o primer.tapOne red cell, top-left, drawn with a single byte and no pixels at all.
Try this: mix your own colour
Change %00010111 to other combinations and predict the cell before you run it. PAPER blue, INK yellow is %00001110. PAPER black, INK green is %00000100. Work out the bits — three for paper, three for ink — then check. (Set BRIGHT, bit 6, and watch the shade lift.)
Try this: a 2×2 block, one cell bright
Colour four neighbouring cells, and make one of them BRIGHT so you can see the difference side by side — the single attribute write, grown to a 2×2 grid:
; ============================================================================
Removed: ; PRIMER — Beat 6: Colour Is a Separate Map
Added: ; PRIMER — Beat 6, "Try this: a 2x2 block, one cell BRIGHT"
; ============================================================================
Removed: ; The pixels live at $4000. Colour lives somewhere else entirely: a second
Removed: ; map starting at $5800, one byte per 8x8 cell. That one byte holds BOTH
Removed: ; colours for the cell, packed into its bits:
Removed: ;
Removed: ; bit: 7 6 5 4 3 2 1 0
Removed: ; FLASH BRIGHT PAPER INK
Removed: ; (0-7) (0-7)
Removed: ;
Removed: ; So $17 = %00010111 is PAPER 2 (red), INK 7 (white), no bright, no flash.
Removed: ;
Removed: ; We write ONE attribute byte and the whole top-left cell turns red — and we
Removed: ; never touch a single pixel. Colour and pixels are stored separately. (That
Removed: ; separation is exactly why "attribute clash" happens: a cell has only one
Removed: ; ink and one paper, whatever its pixels are doing.)
Removed: ;
Removed: ; And unlike the pixels' peculiar layout (Beat 5), the colour map is laid out
Removed: ; sanely: $5800 + row*32 + col. The top-left cell is just $5800.
Added: ; The colour map is linear: $5800 + row*32 + col. So a 2x2 block of cells is
Added: ; $5800 (top-left) $5801 (top-right)
Added: ; $5820 (bot-left) $5821 (bot-right) <- +32 is one cell row down
Added: ; Colour all four red; set the BRIGHT bit (bit 6, +$40) on the last one to
Added: ; see bright red against normal red.
; ============================================================================
…
start:
Removed: ld a, %00010111 ; PAPER 2 (red), INK 7 (white)
Removed: ld ($5800), a ; colour the top-left cell — no pixels touched
Added: ld a, %00010111 ; PAPER red, INK white
Added: ld ($5800), a ; top-left
Added: ld ($5801), a ; top-right
Added: ld ($5820), a ; bottom-left (one cell row down)
Added:
Added: ld a, %01010111 ; same colours, BRIGHT bit set
Added: ld ($5821), a ; bottom-right, bright red
.loop:
The bottom-right cell is the same red as the other three but with bit 6 set — bright red against normal red. Notice the addresses: $5800, $5801 across, then $5820 a row down. The sane grid in action.
When it’s wrong, see why
- The cell isn’t coloured. Check the address is
$5800, not$4000—$4000is the pixel map; colour lives at$5800. - Wrong colour. Recount the bits: paper is bits 3–5, ink is bits 0–2 — a common slip is to swap them.
%00010111is paper red, ink white; flip to%00111010and it’s paper white, ink… work it out. - The whole cell flashes. You set bit 7 (FLASH). Clear it unless you meant it.
What you’ve learnt
Colour is a separate map at $5800 — one byte per cell, packing FLASH, BRIGHT, PAPER and INK — stored entirely apart from the pixels, and laid out as a plain grid.
What’s next
You’ve now met everything the machine is: the toolchain, registers, bytes, memory, the screen and its colour. From here we turn to what the machine can do. Next — Test, Then Jump — there’s no IF on the Z80; you compare, and you jump on the result. It’s where your programs start making decisions.
Sources
Sinclair Research, ZX Spectrum BASIC Programming, chapter 16, colours: one INK, one PAPER, a brightness and a flash setting for each of the 768 character positions, and ATTR returning 128 for flash, 64 for bright, 8 × PAPER and INK; chapter 24, the memory: attributes from 22528, stored line by line. Chris Smith, The ZX Spectrum ULA: How to Design a Microcomputer (ZX Design and Media), chapter 14, p. 135: the flash clock counting 32 frames for each full flash cycle.
Run it here
This unit's program, built for you.
- Program
- primer.tap
- Machine
- ZX Spectrum 48K (PAL)