Last time you sent a number to the border and met the build-run loop. You used a register — A — without properly meeting it. Let’s meet it now, because this is the first place the machine stops behaving like the language you came from.
In BASIC, LET a=5 gave you a variable, and you could have as many as you liked — score, lives, name$, on and on. The CPU is not so generous. It has a handful of registers — small stores inside the chip itself — and that’s all. Their names are fixed: A, B, C, D, E, H, L. You don’t declare new ones. You learn to orchestrate the few you have.
And each one holds a single 8-bit number: 0 to 255, nothing larger. A register isn’t a roomy named box; it’s a tiny slot with a hard limit. That constraint shapes everything you’ll write.
What you’ll see by the end

A cyan border — colour 5. But the colour isn’t the point this time. The point is how the 5 got there: it travelled from A into B, survived us wiping A, and came back. The border is just the proof you can see. The real show is in the register view — open it in your emulator and watch A and B change step by step.
LD just moves a number
There’s one new idea here, and it’s LD — “load”. Despite the name, it doesn’t “load” in any grand sense; all it does is move a number. Into a register from a literal (ld a, 5), or from one register into another (ld b, a). That’s the whole instruction.
To prove a value moves between registers — instead of just trusting it — we send 5 on a round trip:
; ============================================================================
; PRIMER — Beat 2: LD Is Not LET
; ============================================================================
; In BASIC, LET a = 5 gave you as many named variables as you liked. The CPU
; doesn't work that way. It has a *handful* of registers — physical stores
; inside the chip, each holding one 8-bit number (0 to 255). Their names are
; fixed: A, B, C, D, E, H, L. You don't make more; you orchestrate these few.
;
; LD means "load", and all it ever does is move a number — into a register,
; or from one register to another. To prove a value really moves, we take it
; on a round trip and watch it come back:
;
; 1. Put 5 into A. (5 = cyan)
; 2. Copy A into B. (now B holds 5 too)
; 3. Wipe A back to 0. (A = 0; if we stopped here the border'd be black)
; 4. Copy B back into A. (A = 5 again — the 5 was safe in B all along)
; 5. Show A on the border. (cyan proves B kept our 5)
;
; Open the register view in Emu198x and watch A and B change at each step.
; The border is just the visible proof; the registers are the lesson.
; ============================================================================
org 32768
start:
ld a, 5 ; put 5 into A (5 = cyan)
ld b, a ; copy A into B (B now holds 5 too)
ld a, 0 ; wipe A (A = 0; B still holds 5)
ld a, b ; copy B back into A (A = 5 again, from B)
out ($FE), a ; show A on the border (only A can do OUT)
.loop:
halt
jr .loop
end startThis is last unit’s program with three lines added in the middle. Follow A and B down the steps:
| After… | A |
B |
|---|---|---|
ld a, 5 |
5 | — |
ld b, a |
5 | 5 |
ld a, 0 |
0 | 5 |
ld a, b |
5 | 5 |
We deliberately wipe A to 0, then bring the value back from B. The border ends up cyan — so the 5 must have been safe in B the whole time. That’s the proof the copy was real.
Why copy back into A just to show it? Because out ($FE), a — the form of out with the port number written into the instruction — only ever sends A, never B or the others. It’s one of the chip’s fixed rules. (The Z80 has a second form of out that can send other registers; you won’t need it here.) It’s also what makes the round trip prove anything: the 5 had to come home through B.
Assemble and run
Same loop as before — change, build, run:
asm198x --dialect pasmo --cpu z80 --tapbas registers.asm -o primer.tapLoad primer.tap in your emulator with a register view open, and watch A and B move through the table above before the border settles on cyan.
Try this: too big to fit
Change ld a, 5 to ld a, 300, assemble, and run. Now look at A in the register view: it reads 44, not 300. A register holds 8 bits — 0 to 255 — and 300 doesn’t fit, so the machine keeps only the low 8 bits (300 − 256 = 44). And notice what didn’t happen: no error, no warning. The assembler let it through; the machine kept the bits that fit and carried on. The tools won’t catch this for you — a theme we’ll return to.
Try this: a longer trip
Send a value through three registers before showing it — A into B, B into C, then C back into A and out it. It’s the round trip with one more hop (and a different colour to tell them apart):
; ============================================================================
Removed: ; PRIMER — Beat 2: LD Is Not LET
Added: ; PRIMER — Beat 2, "Try this: a longer trip"
; ============================================================================
Removed: ; In BASIC, LET a = 5 gave you as many named variables as you liked. The CPU
Removed: ; doesn't work that way. It has a *handful* of registers — physical stores
Removed: ; inside the chip, each holding one 8-bit number (0 to 255). Their names are
Removed: ; fixed: A, B, C, D, E, H, L. You don't make more; you orchestrate these few.
Removed: ;
Removed: ; LD means "load", and all it ever does is move a number — into a register,
Removed: ; or from one register to another. To prove a value really moves, we take it
Removed: ; on a round trip and watch it come back:
Removed: ;
Removed: ; 1. Put 5 into A. (5 = cyan)
Removed: ; 2. Copy A into B. (now B holds 5 too)
Removed: ; 3. Wipe A back to 0. (A = 0; if we stopped here the border'd be black)
Removed: ; 4. Copy B back into A. (A = 5 again — the 5 was safe in B all along)
Removed: ; 5. Show A on the border. (cyan proves B kept our 5)
Removed: ;
Removed: ; Open the register view in Emu198x and watch A and B change at each step.
Removed: ; The border is just the visible proof; the registers are the lesson.
Added: ; Send a value through THREE registers before showing it:
Added: ; A -> B -> C -> back to A -> border.
Added: ; Predict A, B and C at each step, then watch them in the register view.
Added: ; (6 is yellow.)
; ============================================================================
…
start:
Removed: ld a, 5 ; put 5 into A (5 = cyan)
Removed: ld b, a ; copy A into B (B now holds 5 too)
Removed: ld a, 0 ; wipe A (A = 0; B still holds 5)
Removed: ld a, b ; copy B back into A (A = 5 again, from B)
Removed: out ($FE), a ; show A on the border (only A can do OUT)
Added: ld a, 6 ; put 6 into A (6 = yellow)
Added: ld b, a ; copy A into B
Added: ld c, b ; copy B into C
Added: ld a, c ; copy C back into A
Added: out ($FE), a ; show A on the border (only A can do OUT)
.loop:
Before you run it, predict A, B and C at each step — then open your emulator’s register view and check. (6 is yellow.)
When it’s wrong, see why
- The assembler says “Expected ‘A’ but…” on the
outline. You tried toouta register other thanA. OnlyAcan go out throughout ($FE), a— route the value back intoAfirst. - The border is black, not cyan. The
ld a, bstep is missing or mistyped, soAwas still 0 (from the wipe) when you showed it. Put the round trip back exactly as shown. - The border’s the wrong colour. Check the number in
ld a, …— and remember the border reads only the low 3 bits, so values above 7 repeat the eight colours.
What you’ve learnt
A register is a physical, finite 8-bit store — the CPU has only a handful (A B C D E H L) — and LD moves numbers between them.
What’s next
This isn’t a Spectrum quirk: nearly every CPU is built around a small set of registers — some have more, some fewer. You’re learning how machines work, not a Spectrum trick. Next — Everything Is a Number — we look at what those bytes you’re moving around are, and discover the machine doesn’t know a letter from a colour from a number. They’re all just bytes.
Sources
Zilog, Z80 CPU User Manual, UM008011-0816: LD r, r' and LD r, n for register loads; OUT (n), A, p. 306, which sends only A, and OUT (C), r, p. 307. Sinclair Research, ZX Spectrum BASIC Programming, chapter 23, IN and OUT: the border colour from bits 0–2 of port 254.
Run it here
This unit's program, built for you.
- Program
- primer.tap
- Machine
- ZX Spectrum 48K (PAL)