Zero Page

The most valuable 256 bytes on the BBC. Zero-page addressing modes (zp, zp,X, (zp,X), (zp),Y, 65C12 (zp)) are 1c-2c faster than absolute equivalents and 1 byte shorter. The right zero-page layout is the foundation of every fast loop.

Allocation (Model B / B+ / Master, BASIC-loaded)

RangeOwnerUse
&00-&6FBASIC (or current language)Language workspace — off limits while a language is loaded
&70-&8FUSER32 bytes free for user code. Safe to use from machine code called via CALL from BASIC.
&90-&9FEconetNetwork station, FS station, etc.
&A0-&A7NMINetwork/disc filing-system NMI work
&A8-&AFOSOS temporary scratch
&B0-&BFFSFiling-system temporary scratch
&C0-&CFFSFiling-system workspace (current handle, etc.)
&D0-&E1VDUPer-cell pointers, masks (see VDU table below)
&E2-onwardsOSCFS/RFS state, key buffer pointers, OSBYTE/OSWORD save, IRQ A-save

VDU-driver zero page (&D0-&E1)

From naug-ch13-video §13.2:

AddrUse
&D0STATS — VDU status byte (= OSBYTE &75)
&D1ZMASK — current graphics-point bit mask
&D2ZORA — text colour OR mask
&D3ZEOR — text colour EOR mask
&D4Graphics colour OR mask
&D5Graphics colour EOR mask
&D6-&D7Graphics character cell
&D8-&D9Top scan line
&DA-&DFTemporary workspace
&E0-&E1BBC/Electron: Row multiplication tables ptr. Master: General workspace.

(VDU character-cell addresses live in page 3, not zero page — see os-workspace for &034A-&034B (text cursor 6845 address) and the graphics workspace at &0330-&0349.)

Touching these while the VDU driver is active = visible corruption.

Critical OS locations

AddrUse
&E7Auto-repeat countdown
&EASerial timeout counter
&ECLast key press (internal key number)
&EDPenultimate key press
&EE1 MHz bus paging-register RAM copy + internal-key-number to ignore (OSBYTE &79)
&EFOSBYTE/OSWORD A value (during call)
&F0OSBYTE/OSWORD X value
&F1OSBYTE/OSWORD Y value
&F2-&F3GSINIT/GSREAD string pointer
&F4Copy of ROM select register (paging)
&F5Speech PHROM / RFS ROM number
&F6-&F7OSRDSC address
&F8-&F9Master only: soft key expansion pointer (BBC/Electron unused)
&FA-&FBGeneral OS workspace, used by buffer access during interrupts
&FCA save during IRQ
&FD-&FEError message pointer — initialised to language version string; after BRK, points at the error text following the BRK opcode
&FFESCAPE flag (bit 7 only)

Performance: claim more than &70-&8F

The 32-byte user block is rarely enough for serious work. Strategies for extending:

Take over BASIC’s zero page

If your code is the entire foreground (no CALL back to BASIC), all of &00-&8F is yours. Save the relevant bytes at the entry of your routine, restore at exit. This buys 144 bytes of zp. Strict rule: don’t call OS routines that BASIC has hooked unless you’ve restored the relevant state.

Take over filing-system / VDU zp regions

If you’ve turned off the filing system (e.g. by setting up your own NMI handler and managing the disc directly) you can reclaim &B0-&CF. If you’re running your own video driver (direct screen writes), the VDU workspace at &D0-&E1 is free.

The trade-off: the more MOS state you displace, the more state you must restore on exit. For code that runs to completion and resets the machine (games, demos), restoration is trivial — just BRK or JMP &E00 (RESET). For code that returns to BASIC, you must save and restore.

Don’t bother with &EF-&FF

These are used during every IRQ, OSBYTE, OSWORD, BRK. Even with interrupts disabled, the first OS call after re-enabling them will trash this region. Leave it alone unless you’re running entirely without MOS.

A zp-save preamble

For code that returns to BASIC and uses zp &00-&8F:

.entry
    LDX #&8F
.save_loop
    LDA &00,X
    STA zp_save_block,X
    DEX
    BPL save_loop
    ; ... use any of &00-&8F freely ...
.exit
    LDX #&8F
.restore_loop
    LDA zp_save_block,X
    STA &00,X
    DEX
    BPL restore_loop
    RTS
 
.zp_save_block SKIP &90

144 bytes of save + 144 of restore = ~1500 cycles, paid once per routine entry/exit. Worth it for inner loops that benefit from zp pointers.

65C12 (Master): the (zp) mode is free zp

The 65C12 (zp) addressing mode (LDA/STA/etc. with no index register) means you don’t need X or Y to use a zp pointer. This is a major efficiency win on Master:

; NMOS (and Master): walking a pointer requires Y
LDY #0
LDA (ptr),Y
INY
LDA (ptr),Y
...
 
; 65C12 only: walk by manually incrementing the pointer, no Y needed
LDA (ptr)
INC ptr
BNE skip : INC ptr+1
.skip
LDA (ptr)
...

Costs more cycles per increment (5c vs INY’s 2c) but frees Y entirely — a huge win when Y is needed for the inner address-arithmetic.

See also


This wiki is curated by Claude following the LLM-Wiki methodology — a human curates source documents, the LLM compiles structured cross-linked markdown. Content may contain errors, omissions, or stale claims. For authoritative information refer to the original source documents in the bbc-documents GitHub archive.