Skip to content
Game 0 Unit 4 of 16 1 hr learning time

A Street of Numbered Boxes

Meet memory — one long street of numbered boxes, each holding a byte. Store a byte at an address and read it back, the same round-trip you used for registers, now through RAM.

25% of Meet The Machine

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

The Spectrum's default screen surrounded by a solid green border.
A green border — value 4, fetched back from memory box $9000 after the register was wiped. The border is the proof the box kept it.

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

InstructionMeans
ld ($9000), astore — 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…Abox $9000
ld a, 44
ld ($9000), a44
ld a, 004
ld a, ($9000)44

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     start

We 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

pasmonext --sna numbered-boxes.asm primer.sna

Load 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:

Two independent boxes: $9000 holds 2, $9001 holds 4
+12-28
11 ; ============================================================================
2-; PRIMER — Beat 4: A Street of Numbered Boxes
2+; PRIMER — Beat 4, "Try this: two boxes"
33 ; ============================================================================
4-; Memory is one long street of numbered boxes. Every box has an address (just
5-; a number) and holds one byte. There are 65536 of them, and they're yours.
6-;
7-; Two new instructions, and they're mirror images:
8-;
9-; ld ($9000), a -- STORE: put A's byte into the box at address $9000
10-; ld a, ($9000) -- READ: fetch the box at $9000 back into A
11-;
12-; (The brackets mean "the box AT this address", not the address itself.)
13-;
14-; We prove a byte really lives in memory the same way we proved registers in
15-; Beat 2 — a round trip. But this time it travels out to address $9000 and
16-; back, not just into register B:
17-;
18-; 1. Put 4 into A. (4 = green)
19-; 2. Store A at $9000. (the box at $9000 now holds 4)
20-; 3. Wipe A to 0. (A = 0; the 4 is out in memory)
21-; 4. Read $9000 back into A. (A = 4 again, fetched from the box)
22-; 5. Show A on the border. (green proves the box kept our 4)
23-;
24-; $9000 is just a free patch of RAM, well clear of our program at $8000. Open
25-; the memory view in Emu198x and watch $9000 fill with 4.
4+; Two addresses are two independent boxes. Store a different byte in each,
5+; then read ONE back and show it. Reading $9000 gives 2 (red), not 4 — the
6+; two boxes don't interfere. Change the read address to $9001 to fetch the
7+; other box (4 = green) instead.
268 ; ============================================================================
279
...
2911
3012 start:
31- ld a, 4 ; a byte to store (4 = green)
32- ld ($9000), a ; STORE it at $9000 (POKE)
33- ld a, 0 ; wipe A (the 4 is in memory now)
34- ld a, ($9000) ; READ it back from $9000 (PEEK)
35- out ($FE), a ; show A — green proves the box kept it
13+ ld a, 2 ; red
14+ ld ($9000), a ; box $9000 <- 2
15+ ld a, 4 ; green
16+ ld ($9001), a ; box $9001 <- 4
17+
18+ ld a, ($9000) ; read box $9000 back (A = 2, not 4)
19+ out ($FE), a ; border red: the boxes are separate
3620
3721 .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, so A was still 0 from the wipe. Check the brackets are there — ($9000), not $9000.
  • pasmonext errors on the store/read line. A bracket is missing or unbalanced. Store is ld ($9000), a; read is ld 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.