Skip to content

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

FromToWhat's there
$0000$00FFZero page
$0100$01FFCPU stack
$0200$02FFKeyboard ring buffer
$0300$03FFKernal variables
$0400$05FFBASIC line buffers
$0600$07FFCompactFlash sector buffer
$0800$7FFFProgram RAM
$8000$9FFFThe eight hardware slots
$A000$BFFFThe Kernal — jump table, then the routines behind it
$C000$FEFFBASIC
$FF00$FFF9Wozmon
$FFFA$FFFFThe 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.

FromToWhose
$00$39The Kernal's and BASIC's — pointers, the interpreter's working space, card and transfer state
$3A$FFYours. 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
AddressNameWhat it's for
$00ZP
$00READ_PTR
$01WRITE_PTR
$02STR_PTRString pointer (2 bytes)
$24CF_BUF_PTRCF sector data buffer pointer (2 bytes)
$26CF_LBACF 28-bit LBA address (4 bytes, little-endian)
$2AXFER_PTRSerial transfer data pointer (2 bytes)
$2CDELAY_CNTSysDelay 16-bit centisecond counter
$36XMODEM_BLKXModem expected/current block number
$37XMODEM_CHKXModem running checksum
$38XMODEM_RETRYXModem retry counter
$39XMODEM_RCVBLKXModem received block number
The 256 bytes of zero page, showing which are the Kernal’s and which are yours Zero page, byte by byte 0 1 2 3 4 5 6 7 8 9 A B C D E F $00 $10 $20 $30 $40 $50 $60 $70 $80 $90 $A0 $B0 $C0 $D0 $E0 $F0 $00–$39 — the Kernal, BASIC and XModem $3A–$FF — 198 bytes, yours
The Kernal has the first fifty-eight bytes. The other 198 are yours, and they are the fastest memory on the machine.

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

AddressNameWhat it's for
$0300KERNAL_VARS
$0300IRQ_PTR
$0302BRK_PTR
$0304NMI_PTR
$0306IO_MODEBit 0: 0=video, 1=serial output
$0307VID_CURSOR_XVideo cursor column (0-39)
$0308VID_CURSOR_YVideo cursor row (0-23)
$0309VID_CURSOR_ADDRVideo cursor VRAM address ($0309-$030A)
$030BRTC_BUF_CENTCentury value from last RtcReadDate / for RtcWriteDate
$030CRTC_TMPScratch byte for BCD conversion
$030DHW_PRESENTHardware present bitmask (set during Reset probe)
$030EBAS_PENDKEYPending key from BasCheckBreak (1 byte)
$030FCF_DISKCurrent CF "disk" bank (0-255); base LBA = CF_DISK * FS_DISK_SECTORS
$0310BRK_PSaved P at time of BRK
$0311BRK_PCLSaved PCL at time of BRK (points to BRK+2)
$0312BRK_PCHSaved PCH at time of BRK
$0313BRK_ASaved A at time of BRK
$0314BRK_XSaved X at time of BRK
$0315BRK_YSaved Y at time of BRK
$0316BRK_SPSaved SP at time of BRK
$0317XFER_REMAINRemaining bytes to transfer (2 bytes)
$0319XFER_IO_SAVESaved IO_MODE before switching to serial
$031ABAS_DATAPTRDATA read position (2 bytes)
$031CBAS_STOPLINESaved BAS_CURLINE for CONT (2 bytes)
$031EBAS_STOPTXTSaved BAS_TXTPTR for CONT (2 bytes)
$0348FS_START_SECFile start sector (2 bytes)
$034AFS_FILE_SIZEFile size in bytes (2 bytes)
$034CFS_SEC_COUNTNumber of sectors to read/write
$034DFS_DIR_IDXDirectory entry index (0-15)
$034EFS_NEXT_SECNext free sector for allocation (2 bytes)
$0350FS_FNAME_BUF11-byte filename buffer (8 name + 3 ext)
$035BBOOT_VECTORCart/boot redirect vector (2 bytes, 0=normal boot)
$035DBAS_TXTTABStart of program text (= $0800)
$035FBAS_VARTABEnd-of-program / start-of-vars pointer
$0361BAS_ARYTABStart of arrays
$0363BAS_STRENDEnd of arrays
$0365BAS_FRETOPTop-of-string-heap pointer
$0367BAS_MEMSIZEnd of usable memory (= $8000)
$0369BAS_CURLINCurrently executing line ($FFFF=direct mode)
$036BBAS_OLDLINSaved line for CONT
$036DBAS_OLDTEXTSaved text ptr for CONT
$036FBAS_WARMWarm-start magic ($A5 = previously initialized)
$0370BAS_POSXPRINT column counter (0-39 video / 0-79 serial)
$0371BAS_INPSAVSaved TXTPTR across INPUT REDO retry
$0373BAS_FNPTRFN variable slot addr held across FnCall's expression evaluations (out of ZP so FrmEvl/Garbag cannot touch it)
$0375BAS_STKBASEStack pointer the READY loop restores before each direct-mode line: $FF with no program suspended, or the running program's statement-boundary SP after a STOP/END/Ctrl+C break, which is what keeps its FOR and GOSUB frames alive for CONT. Reset to $FF wherever the stack is reset (RUN, CLR, NEW, an error)
$037FFS_IO_ADDRLoad/save target address for FsLoadFileAddr/FsSaveFileAddr
$0381BAS_FNAME13-byte scratch for null-terminated 8.3 filename (12-char "NAME.EXT" + null)
$038EPRG_IMAGE_ENDEnd address of a program image placed at PROGRAM_START by a loader outside BASIC; 0 = none. Cleared by KernalInit and consumed by ProgramEnd, so a stale value can never be mistaken for a fresh load.
$0390NV_IDOwner ID input for NvWrite ($00 is refused — that is NvErase)
$0391VID_PENAttribute byte for new output, fg<<4 | bg ($1F from KernalInit)
$0392VID_TOPName-table row shown at the top of the screen (0-23); L0SCRY = VID_TOP * 8
$0393VID_MODE$00 = console not set up since KernalInit (card in the legacy submode); $01 = text console intact. Otherwise low nibble = the VMODE the Kernal last set, b7 = disturbed (sprites, scroll, layers)
$0394VDP_FWSTAT5 (firmware version, BCD) at boot; $00 if no PICOVDP
$0395VDP_CAPSSTAT6 (capability bits) at boot; $00 if no PICOVDP
$0396VDP_L0CTRL_SHADOWShadow of the write-only L0CTRL register
$0397VDP_L1CTRL_SHADOWShadow of the write-only L1CTRL register
$0398VDP_P0Parameter block for VDP entries that take more than A/X/Y
$0399VDP_P1
$039AVDP_P2
$039BVDP_P3
$039CVID_BORDERThe border the console shows, register 7's low nibble ($0F from KernalInit, matching VID_PEN's background). Set before the console is brought up, so InitVideo's border write is the one that was asked for and COLOR fg,bg,border never shows another color on the way

The hardware window

Eight slots of one kilobyte each, from $8000 to $9FFF, one per card.

SlotFromToWhat's there
1$8000$83FFAS6C4008 banked SRAM (low)
2$8400$87FFAS6C4008 banked SRAM (high)
3$8800$8BFFDS1511Y
4$8C00$8FFFCompactFlash (8-bit True IDE)
5$9000$93FFR65C51 / W65C51 ACIA
6$9400$97FFW65C22 VIA
7$9800$9BFFMOS 6581 SID / ARMSID
8$9C00$9FFF6502-PICOVDP
The eight I/O slots between $8000 and $9FFF The hardware window, $8000 to $9FFF Eight slots of one kilobyte, one card each $8000 1 RAM Card AS6C4008 banked SRAM (low) $8400 2 RAM Card AS6C4008 banked SRAM (high) $8800 3 RTC Card DS1511Y $8C00 4 Storage Card CompactFlash (8-bit True IDE) $9000 5 Serial Card R65C51 / W65C51 ACIA $9400 6 GPIO Card / Input Board W65C22 VIA $9800 7 Sound Card MOS 6581 SID / ARMSID $9C00 8 Video Card / VGA Card 6502-PICOVDP The Reset probe writes one bit per slot to $030D, in slot order. MEM prints it as HW=$xx; from BASIC it is PEEK(781).
One kilobyte per slot, in the order the detection bits come in. A card that is not fitted leaves its slot reading nothing in particular — which is what HW_PRESENT is for.

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. The Kernal runs on from there to $BFFF, and BASIC takes everything from $C000 to just below Wozmon.

What the ROM does not hold is a character set. The letters on the screen are the video card's own, loaded into the card's memory when the console starts, so changing how a character looks is a job for the card rather than for ROM (The screen).

$FF00 is Wozmon, Steve Wozniak's 250-byte monitor from the Apple I, kept because it fits and because it is a lovely thing to have. SYS 65280 from BASIC reaches it — Reaching the machine shows it working.

The 64K address space, from zero page to the processor vectors The 64K address space $FFFF Vectors $FFFA 6 bytes Wozmon $FF00 250 bytes BASIC $C000 15.8 KB Kernal $A000 8 KB Eight I/O slots $8000 8 KB Program RAM $0800 30 KB CompactFlash sector buffer $0600 512 bytes BASIC line buffers $0400 512 bytes Kernal variables $0300 256 bytes Keyboard ring buffer $0200 256 bytes CPU stack $0100 256 bytes Zero page $0000 256 bytes RAM I/O ROM Your program loads at $0800. Everything from $8000 up is cards and ROM.
The whole 64K, bottom to top. The bands are not to scale — at 64K in a page-height strip, zero page would be two pixels.

Next: the Kernal — the routines that live in that ROM.

Written for BIOS v2.0. Released under the MIT License.