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.
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 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:
| 1 | 1 | ; ============================================================================ | |
| 2 | - | ; PRIMER — Beat 4: A Street of Numbered Boxes | |
| 2 | + | ; PRIMER — Beat 4, "Try this: two boxes" | |
| 3 | 3 | ; ============================================================================ | |
| 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. | |
| 26 | 8 | ; ============================================================================ | |
| 27 | 9 | | |
| ... | |||
| 29 | 11 | | |
| 30 | 12 | 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 | |
| 36 | 20 | | |
| 37 | 21 | .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. pasmonexterrors on the store/read line. A bracket is missing or unbalanced. Store isld ($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.