The memory map in full
Sixty-four kilobytes of address space, and every one of them is already spoken for. Here is who owns what.
The whole thing at a glance
| From | To | What's there |
|---|---|---|
$0000 | $00FF | Zero page |
$0100 | $01FF | CPU stack |
$0200 | $02FF | Keyboard ring buffer |
$0300 | $03FF | Kernal variables |
$0400 | $05FF | BASIC line buffers |
$0600 | $07FF | CompactFlash sector buffer |
$0800 | $7FFF | Program RAM |
$8000 | $9FFF | The eight hardware slots |
$A000 | $B7FF | The Kernal — jump table, then the routines behind it |
$B800 | $BFFF | The character set |
$C000 | $EDFF | BASIC |
$EE00 | $FEFF | The Monitor |
$FF00 | $FFF9 | Wozmon |
$FFFA | $FFFF | The processor's NMI, reset and interrupt vectors |
The bottom 32 KB is RAM, the top 24 KB is ROM, and the eight kilobytes between them are where the cards live — one kilobyte each.
$8000 is not memory
Reading or writing an address in the hardware window talks to a chip, not to storage. A stray write there can change the screen mode, retune a voice, or select a different bank of RAM. Everything in this range is described in What's fitted.
Zero page
The first 256 bytes, where every access is a byte shorter and a cycle faster.
| From | To | Whose |
|---|---|---|
$00 | $39 | The Kernal's — pointers, filesystem and transfer state, Monitor scratch |
$3A | $FF | Yours. 198 bytes. |
Which means it is yours once your program is the thing running. Underneath a running BASIC the interpreter is using that space as it goes, so a BASIC program that poked a value into zero page would find it gone a moment later. Machine code loaded and started from BASIC is fine: BASIC is sitting still while you run.
What the Kernal keeps down there
| Address | Name | What it's for |
|---|---|---|
$00 | ZP | |
$00 | READ_PTR | |
$01 | WRITE_PTR | |
$02 | STR_PTR | String pointer (2 bytes) |
$24 | CF_BUF_PTR | CF sector data buffer pointer (2 bytes) |
$26 | CF_LBA | CF 28-bit LBA address (4 bytes, little-endian) |
$2A | XFER_PTR | Serial transfer data pointer (2 bytes) |
$2C | DELAY_CNT | SysDelay 16-bit centisecond counter |
$2E | MON_ADDR | Monitor current address (2 bytes) |
$36 | XMODEM_BLK | XModem expected/current block number |
$37 | XMODEM_CHK | XModem running checksum |
$38 | XMODEM_RETRY | XModem retry counter |
$39 | XMODEM_RCVBLK | XModem received block number |
$30 | MON_END | Monitor end address (2 bytes) |
$32 | MON_TMP | Monitor temp pointer (2 bytes) |
$34 | MON_BYTE | Monitor parsed byte scratch |
$35 | MON_IDX | Monitor input line index |
The rest of the low RAM
$0100–$01FF — the stack. 256 bytes, growing down from $01FF. BASIC keeps its FOR and GOSUB frames here too, which is why deep nesting runs out of memory rather than slowing down.
$0200–$02FF — the keyboard ring buffer. Keys and serial bytes land here the moment they arrive, put there by the interrupt handler. Chrin takes them out again.
$0300–$03FF — the Kernal's variables. Interrupt vectors, the cursor, what hardware was found, the current disk, the cartridge boot vector. The named ones you are most likely to want are below.
$0400–$05FF — BASIC's line buffers. The raw line you typed and the tokenized version of it.
$0600–$07FF — the card sector buffer. 512 bytes, and any filesystem call overwrites it. It is tempting free memory when BASIC is not doing anything, and it is a trap the first time your program saves a file.
$0800–$7FFF — yours. About 30 KB. A .prg loads at $0800; a BASIC program lives there too, which is why the two cannot be resident at once.
The Kernal's variables
| Address | Name | What it's for |
|---|---|---|
$0300 | KERNAL_VARS | |
$0300 | IRQ_PTR | |
$0302 | BRK_PTR | |
$0304 | NMI_PTR | |
$0306 | IO_MODE | Bit 0: 0=video, 1=serial output |
$0307 | VID_CURSOR_X | Video cursor column (0-39) |
$0308 | VID_CURSOR_Y | Video cursor row (0-23) |
$0309 | VID_CURSOR_ADDR | Video cursor VRAM address ($0309-$030A) |
$030B | RTC_BUF_CENT | Century value from last RtcReadDate / for RtcWriteDate |
$030C | RTC_TMP | Scratch byte for BCD conversion |
$030D | HW_PRESENT | Hardware present bitmask (set during Reset probe) |
$030E | BAS_PENDKEY | Pending key from BasCheckBreak (1 byte) |
$030F | CF_DISK | Current CF "disk" bank (0-255); base LBA = CF_DISK * FS_DISK_SECTORS |
$0310 | BRK_P | Saved P at time of BRK |
$0311 | BRK_PCL | Saved PCL at time of BRK (points to BRK+2) |
$0312 | BRK_PCH | Saved PCH at time of BRK |
$0313 | BRK_A | Saved A at time of BRK |
$0314 | BRK_X | Saved X at time of BRK |
$0315 | BRK_Y | Saved Y at time of BRK |
$0316 | BRK_SP | Saved SP at time of BRK |
$0317 | XFER_REMAIN | Remaining bytes to transfer (2 bytes) |
$0319 | XFER_IO_SAVE | Saved IO_MODE before switching to serial |
$031A | BAS_DATAPTR | DATA read position (2 bytes) |
$031C | BAS_STOPLINE | Saved BAS_CURLINE for CONT (2 bytes) |
$031E | BAS_STOPTXT | Saved BAS_TXTPTR for CONT (2 bytes) |
$0320 | SCROLL_BUF | 40-byte scroll temp buffer |
$0348 | FS_START_SEC | File start sector (2 bytes) |
$034A | FS_FILE_SIZE | File size in bytes (2 bytes) |
$034C | FS_SEC_COUNT | Number of sectors to read/write |
$034D | FS_DIR_IDX | Directory entry index (0-15) |
$034E | FS_NEXT_SEC | Next free sector for allocation (2 bytes) |
$0350 | FS_FNAME_BUF | 11-byte filename buffer (8 name + 3 ext) |
$035B | BOOT_VECTOR | Cart/boot redirect vector (2 bytes, 0=normal boot) |
$035D | BAS_TXTTAB | Start of program text (= $0800) |
$035F | BAS_VARTAB | End-of-program / start-of-vars pointer |
$0361 | BAS_ARYTAB | Start of arrays |
$0363 | BAS_STREND | End of arrays |
$0365 | BAS_FRETOP | Top-of-string-heap pointer |
$0367 | BAS_MEMSIZ | End of usable memory (= $8000) |
$0369 | BAS_CURLIN | Currently executing line ($FFFF=direct mode) |
$036B | BAS_OLDLIN | Saved line for CONT |
$036D | BAS_OLDTEXT | Saved text ptr for CONT |
$036F | BAS_WARM | Warm-start magic ($A5 = previously initialized) |
$0370 | BAS_POSX | PRINT column counter (0-39 video / 0-79 serial) |
$0371 | BAS_INPSAV | Saved TXTPTR across INPUT REDO retry |
$0373 | BAS_FNPTR | FN variable slot addr held across FnCall's expression evaluations (out of ZP so FrmEvl/Garbag cannot touch it) |
$0375 | BAS_STKBASE | Stack pointer the READY loop restores before each direct-mode line: $FF with no |
$037F | FS_IO_ADDR | Load/save target address for FsLoadFileAddr/FsSaveFileAddr |
$0381 | BAS_FNAME | 13-byte scratch for null-terminated 8.3 filename (12-char "NAME.EXT" + null) |
$038E | PRG_IMAGE_END | End address of a program image placed at PROGRAM_START by a loader |
The hardware window
Eight slots of one kilobyte each, from $8000 to $9FFF, one per card.
| Slot | From | To | What's there |
|---|---|---|---|
| 1 | $8000 | $83FF | AS6C4008 banked SRAM (low) |
| 2 | $8400 | $87FF | AS6C4008 banked SRAM (high) |
| 3 | $8800 | $8BFF | DS1511Y |
| 4 | $8C00 | $8FFF | CompactFlash (8-bit True IDE) |
| 5 | $9000 | $93FF | R65C51 / W65C51 ACIA |
| 6 | $9400 | $97FF | W65C22 VIA |
| 7 | $9800 | $9BFF | MOS 6581 SID / ARMSID |
| 8 | $9C00 | $9FFF | TMS9918A / Pico9918 |
The ROM
The first 256 bytes of the Kernal are the jump table — the only addresses in the whole ROM you should ever write down. $B800 upwards is the character set, which is worth knowing about because you can read it, copy it, and change it (The screen).
$FF00 is Wozmon, Steve Wozniak's 256-byte monitor from the Apple I, kept because it fits and because it is a lovely thing to have.
📄 Memory Map card — the whole address space and every I/O register on the board, on three printable pages.
Next: the Kernal — the routines that live in that ROM.

