A byte in a register is a byte you’re holding in your hand. But there are only seven registers, and you can’t put a whole game in your hands. So where do bytes live the rest of the time?
In memory. Picture one long street of numbered boxes — 65,536 of them on the Spectrum. Each box has an address (just a number, like a house number) and holds exactly one byte. The whole street is yours: you can write a byte into any box and read it back out whenever you like.
You met this street in BASIC, through a side door — POKE to write a box, PEEK to read one. There, it felt like an advanced trick. Here it’s your main tool, and it’s two ordinary instructions.
What you’ll see by the end

A green border — value 4. But as in Beat 2, the colour isn’t the point: this 4 went out into memory and came back. We stored it at a box, wiped the register clean, then fetched it from the box again. The border is the proof; the real evidence is in the memory view — open it in your emulator and watch box $9000 fill with 4.
Store and read: two mirror-image instructions
| Instruction | Means |
|---|---|
ld ($9000), a |
store — put A’s byte into the box at address $9000 |
ld a, ($9000) |
read — fetch the box at $9000 back into A |
The brackets are the whole trick: ($9000) means the box at address $9000, not the number $9000 itself. (And notice we’re using a hex address — that’s the $ from last unit earning its keep; $9000 is just a house number, written in hex.)
It’s the same round trip you ran for registers in Beat 2 — but the value travels out to a memory box and back, instead of into register B:
| After… | A |
box $9000 |
|---|---|---|
ld a, 4 |
4 | — |
ld ($9000), a |
4 | 4 |
ld a, 0 |
0 | 4 |
ld a, ($9000) |
4 | 4 |
The program
; ============================================================================
; PRIMER — Beat 4: A Street of Numbered Boxes
; ============================================================================
; Memory is one long street of numbered boxes. Every box has an address (just
; a number) and holds one byte. There are 65536 of them, and they're yours.
;
; Two new instructions, and they're mirror images:
;
; ld ($9000), a -- STORE: put A's byte into the box at address $9000
; ld a, ($9000) -- READ: fetch the box at $9000 back into A
;
; (The brackets mean "the box AT this address", not the address itself.)
;
; We prove a byte really lives in memory the same way we proved registers in
; Beat 2 — a round trip. But this time it travels out to address $9000 and
; back, not just into register B:
;
; 1. Put 4 into A. (4 = green)
; 2. Store A at $9000. (the box at $9000 now holds 4)
; 3. Wipe A to 0. (A = 0; the 4 is out in memory)
; 4. Read $9000 back into A. (A = 4 again, fetched from the box)
; 5. Show A on the border. (green proves the box kept our 4)
;
; $9000 is just a free patch of RAM, well clear of our program at $8000. Open
; the memory view in Emu198x and watch $9000 fill with 4.
; ============================================================================
org 32768
start:
ld a, 4 ; a byte to store (4 = green)
ld ($9000), a ; STORE it at $9000 (POKE)
ld a, 0 ; wipe A (the 4 is in memory now)
ld a, ($9000) ; READ it back from $9000 (PEEK)
out ($FE), a ; show A — green proves the box kept it
.loop:
halt
jr .loop
end startWe wipe A to 0 between storing and reading, so the green border can only mean the 4 came back from memory. ld ($9000), a is your POKE; ld a, ($9000) is your PEEK.
Assemble and run
asm198x --dialect pasmo --cpu z80 --tapbas numbered-boxes.asm -o primer.tapLoad it with your emulator’s memory view open, and watch box $9000 take the 4 before the border turns green.
Try this: any box will do
Change both $9000s to another free address — say $9100. Same green border. Nothing is special about $9000; any free box of RAM holds your byte just as well. (Keep clear of $8000, where the program itself lives — overwrite that and you’d be editing the running code.)
Try this: two boxes don’t interfere
Store a different byte in two neighbouring boxes, then read one back — the round trip from the main program, grown to a second box:
; ============================================================================
Removed: ; PRIMER — Beat 4: A Street of Numbered Boxes
Added: ; PRIMER — Beat 4, "Try this: two boxes"
; ============================================================================
Removed: ; Memory is one long street of numbered boxes. Every box has an address (just
Removed: ; a number) and holds one byte. There are 65536 of them, and they're yours.
Removed: ;
Removed: ; Two new instructions, and they're mirror images:
Removed: ;
Removed: ; ld ($9000), a -- STORE: put A's byte into the box at address $9000
Removed: ; ld a, ($9000) -- READ: fetch the box at $9000 back into A
Removed: ;
Removed: ; (The brackets mean "the box AT this address", not the address itself.)
Removed: ;
Removed: ; We prove a byte really lives in memory the same way we proved registers in
Removed: ; Beat 2 — a round trip. But this time it travels out to address $9000 and
Removed: ; back, not just into register B:
Removed: ;
Removed: ; 1. Put 4 into A. (4 = green)
Removed: ; 2. Store A at $9000. (the box at $9000 now holds 4)
Removed: ; 3. Wipe A to 0. (A = 0; the 4 is out in memory)
Removed: ; 4. Read $9000 back into A. (A = 4 again, fetched from the box)
Removed: ; 5. Show A on the border. (green proves the box kept our 4)
Removed: ;
Removed: ; $9000 is just a free patch of RAM, well clear of our program at $8000. Open
Removed: ; the memory view in Emu198x and watch $9000 fill with 4.
Added: ; Two addresses are two independent boxes. Store a different byte in each,
Added: ; then read ONE back and show it. Reading $9000 gives 2 (red), not 4 — the
Added: ; two boxes don't interfere. Change the read address to $9001 to fetch the
Added: ; other box (4 = green) instead.
; ============================================================================
…
start:
Removed: ld a, 4 ; a byte to store (4 = green)
Removed: ld ($9000), a ; STORE it at $9000 (POKE)
Removed: ld a, 0 ; wipe A (the 4 is in memory now)
Removed: ld a, ($9000) ; READ it back from $9000 (PEEK)
Removed: out ($FE), a ; show A — green proves the box kept it
Added: ld a, 2 ; red
Added: ld ($9000), a ; box $9000 <- 2
Added: ld a, 4 ; green
Added: ld ($9001), a ; box $9001 <- 4
Added:
Added: ld a, ($9000) ; read box $9000 back (A = 2, not 4)
Added: out ($FE), a ; border red: the boxes are separate
.loop:
The border comes up red — we read box $9000 (which holds 2), and the 4 we put in $9001 sat there untouched. Change the read to ld a, ($9001) and the border turns green instead. Two addresses, two independent boxes.
When it’s wrong, see why
- The border is black, not green. The
ld a, ($9000)read is missing or mistyped, soAwas still 0 from the wipe. Check the brackets are there —($9000), not$9000. - The assembler errors on the store/read line. A bracket is missing or unbalanced. Store is
ld ($9000), a; read isld a, ($9000). - The border’s a colour you didn’t expect. You stored a value whose low 3 bits give that colour — the border still only reads the bottom 3 bits of whatever byte came back.
What you’ve learnt
Memory is 65,536 numbered boxes, each holding one byte: ld (addr), a stores A’s byte at an address, and ld a, (addr) reads it back.
What’s next
Some of those boxes are special. Write a byte to the ones starting at $4000 and you don’t change a number — you light up pixels. Next — The Screen Is Memory — we find out the screen is just a stretch of this same street, and PLOT was poking boxes all along.
Sources
Zilog, Z80 CPU User Manual, UM008011-0816: LD (nn), A and LD A, (nn), and the 16-bit address bus that gives 65,536 addresses. Sinclair Research, ZX Spectrum BASIC Programming, chapter 24, the memory: the map from 0 to 65535, with ROM below 16384 and RAM above it.
Run it here
This unit's program, built for you.
- Program
- primer.tap
- Machine
- ZX Spectrum 48K (PAL)