P8X ROM Monitor — Command Reference¶
The ROM monitor is the firmware that runs at power-on. It lives at $0000 in
EEPROM (firmware/p8xmon.asm), talks to you over the 6850 ACIA serial console
(9600 8N1), and is the entry point to everything else — from here you can
inspect/modify memory, drive the CompactFlash card, and boot P8X/OS.
Source of truth:
firmware/p8xmon.asm(and its in-ROMH/?help, which this document mirrors). The companion programmer's guide is the instruction-set reference; this is the monitor reference.
Running it¶
On reset the CPU jumps to $0000, which vectors to the monitor body at $0160;
it resets the ACIA and prints the P8X MONITOR banner and a * prompt. In the
emulator:
./os/run.sh # builds the monitor ROM (BASIC is no longer ROM-resident) and launches it
# or directly, against an EEPROM image you've assembled and a CF disk image:
emulator/p8xemu -c disk.img eeprom.bin
Type a command letter at the * prompt. Commands are single letters; those that
take an address read 4 hex digits (AAAA) right after the letter.
Commands¶
| Command | Syntax | What it does |
|---|---|---|
| E | E AAAA |
Examine / modify memory from AAAA. Interactive (see below). |
| D | D AAAA |
Dump 256 bytes from AAAA as hex + ASCII. Pages (see below). |
| I | I |
Init CF: reset the card, set 8-bit mode, IDENTIFY, print the model string. |
| F | F |
Format the CF card as P8XFS (writes the boot block + root directory). Asks Y/N. |
| B | B |
Boot the OS image from the CF card into $2000 and run it. |
| G | G AAAA |
Go: JSR AAAA. The called code returns to the monitor with RTS. |
| ? / H | ? or H |
Print the built-in command help. |
BASIC is no longer ROM-resident (the old
Xcommand is gone). It ships as the disk program/BIN/BASIC.BIN(assembled frombasic/p8xbasic.asm); run it from the OS like any other program, and itsBYEreturns to the OS.
E — examine / modify (interactive)¶
E AAAA shows one byte at a time: aaaa: vv and then waits for input:
- two hex digits — write that value to the location, then advance;
- Enter (CR) — leave the byte unchanged, advance to the next;
.— stop and return to the prompt.
So you can walk forward through memory, setting only the bytes you want.
D — dump with paging¶
D AAAA prints sixteen 16-byte rows (256 bytes) as hex with an ASCII column
(bytes <$20 or ≥$7F shown as .). After each block it waits for a key:
- Enter (CR) (or any other key) — dump the next 256-byte block; the address keeps walking forward, so repeated Enters page through memory;
.— return to the prompt.
(This mirrors the E command's CR=next / .=exit convention.)
Returning to the monitor¶
Anything the monitor launches can come back to it:
Gtarget — returns onRTS.- P8X/OS (
B) — typeEXIT(orMON).
Each of those re-enters the monitor (BASIC/OS do a cold restart via JMP $0000).
BIOS jump table — the program ABI¶
The monitor publishes a small jump table at $0100 so RAM-resident programs
(P8X/OS, your own code loaded via G) can call console + CF services without
knowing the monitor's internal addresses. These entry points are stable:
| Address | Name | Behaviour |
|---|---|---|
$0100 |
CONIN | wait for a key; char → A |
$0103 |
CONOUT | A → serial (expands a bare LF to CR LF — see below) |
$0106 |
CONST | A = RDRF bit; Z=1 when no key is waiting |
$0109 |
CFINIT | reset CF + set 8-bit mode; C=1 on error |
$010C |
CFREAD | read sector LBA → (P1); P1 += 512 |
$010F |
CFWRITE | write SBUF → sector LBA |
$0112 |
PUTS | print (P1)+ until $00 |
$0115 |
PHEX8 | print A as two hex digits |
$0118 |
FFIND | find root file FNAME → LBA+FLEN; C=1 if not found |
$011B |
FCREATE | create root file FNAME from FSRC/FLEN; C=1 on error |
$011E |
FDELETE | tombstone root file FNAME (flag → $FF); C=1 if not found |
$0121 |
FCOMMIT | register a streamed file: write a root entry for data already at the free pointer (FNAME, length FLEN, sectors =ceil(FLEN/512)) and bump the free pointer; C=1 if root full |
$0124 |
FOPEN | open root file FNAME for sequential reading; P1 = a caller-owned 512-byte sector buffer; C=1 if not found |
$0127 |
FGETB | next byte of the open read stream → A (C=0); C=1 at end of file (refills from disk as needed) |
$012A |
FWOPEN | open a sequential write stream (streams to disk at the free pointer; uses SBUF as its buffer) |
$012D |
FPUTB | append byte A to the write stream (flushes a full sector automatically) |
$0130 |
FCLOSE | flush the partial sector + register file FNAME (length = bytes written); C=1 if root full |
$0133 |
FRESOLVE | resolve path at P1 (/a/b) → set the directory extent + leaf FNAME; a following FFIND/FOPEN runs in that dir; C=1 on a bad path |
$0136 |
FNORM | format the string at P1 into FNAME (≤12 chars, upper-cased, space-padded; stops at NUL/space) |
$0139 |
FOPENDIR | begin iterating the directory at path P1 (""/"/" = root); C=1 if not a directory |
$013C |
FNEXT | next live entry → FNAME/FFLAG/LBA/FLEN; C=1 at end (skips deleted entries) |
$013F |
FLOADAT | bulk-read FLEN bytes from sector LBA into (P1), a whole sector at a time (the fast "slurp a file" primitive; EDIT + the OS loader use it) |
$0142 |
FOPENDIRAT | begin iterating the directory whose 4-sector extent starts at the 16-bit LBA A (low) + LBA1 ($1F48, high) — lets a caller iterate an extent it already resolved, e.g. the OS's CWD. Set LBA1=0 for LBA < 256 |
$0145 |
FSDIRBUF | point the directory sector buffer at the page in A (high byte; 512-byte page-aligned buffer; defaults to SBUF=$1D at boot and is reset to SBUF by FOPENDIR/FOPENDIRAT). Used by both FNEXT iteration and FSCAN (the engine behind FRESOLVE/FFIND/FOPEN), so repointing it lets a program iterate and resolve paths while a write stream keeps SBUF — e.g. DIR redirected/piped, or CAT *.X >OUT (resolve+open each match without clobbering the open write stream's SBUF) |
| $0148 | CFSEL | select the active CF drive for subsequent sector/FS I/O: A = drive (0/1) → DRVSEL. The OS's mount redirect (in FRESOLVE/RV_START) calls this to route a /D1 path to drive 1 — there is no drive-letter prefix. Both cards share the $FF10 task-file port; DRVSEL is ORed into CFHEAD as the ATA device bit |
| $014B | CFCURDRV | current CF drive → A (0/1) |
| $014E | GCLS | clear the on-screen text console (the "glass TTY") and home its cursor; a no-op when no GL card is fitted. Added for two-mode operation — the glass TTY that mirrors CONOUT onto a GL display; see p8x-two-mode-design.md |
Call them with JSR $0103 etc. (P8X/OS is built entirely on this table.)
FDELETE marks the directory entry deleted but leaves its data sectors in
place; they are reclaimed by the next PACK. To overwrite a file, FDELETE
then FCREATE.
Directory: the file calls operate on a current directory extent that
defaults to the root (LBA 33) and reverts there after each call. Call FRESOLVE
first with a path to aim the next call at a subdirectory (it walks the ./..
tree and leaves the leaf name in FNAME): FRESOLVE("/BIN/X") then FOPEN
reads /BIN/X; FRESOLVE("/SUB/W") then FWOPEN/FPUTB/FCLOSE writes
/SUB/W. FFIND/FOPEN/FCREATE/FDELETE/FCLOSE are all path-aware this
way. Parameters use fixed RAM:
FNAME ($1F4A, 12-byte space-padded name), FSRC ($1F56, FCREATE source
address), FLEN ($1F58, 24-bit length, 3 bytes — FCREATE input, FFIND
output; max file 16 MB, matching the 24-bit ROLBA); FFIND returns
the start LBA in the shared LBA ($1F47). (Subdirectory LBAs are 16-bit: the
directory-iteration/resolution path carries DIRLBA/DILBA plus their high
bytes, so a directory whose extent starts at LBA ≥ 256 resolves and lists
correctly.)
Memory map¶
| Range | Use |
|---|---|
$0000–$17FF |
EEPROM (6 KB; shrunk from 8 KB 2026-09-14 to free the $1800-$1FFF RAM island) — monitor + BIOS at $0000 (~4.9 KB used). BASIC is no longer ROM-resident; it ships as /BIN/BASIC.BIN on disk. |
$2000–$56FF |
RAM — P8X/OS image loads here ($2000, ~13.8 KB including the resident window-manager kernel, ending ~$55E6; the 16 KB cap = the on-disk LBA 1–32 boot region). |
$1800–$1CFF |
RAM — OS buffers in the low RAM island (moved there 2026-09-14): the stdin read buffer IBUF $1800, the search PATH $1A00, OS scratch, the >> prepend buffer APBUF $1B00. |
$5700–$58FF |
RAM — OS data: shell variables (input line $5700), FS/PACK/FSCK state, the CWD path text $5800. |
$1D00–$1EFF |
RAM — SBUF sector buffer (in the $1800-$1FFF island; was $6100, then $5E00; monitor-owned, but not part of the command //#define ABI). |
$1F00–$1FFF |
RAM — firmware/BIOS scratch (fixed ABI — commands reach it through //#use abi / //#use mem): monitor line buffer $1F00 (64 bytes, so an input line is capped at 63 characters), the parameter block + read/write/dir-iteration state $1F40 (CF LBA $1F47–$1F49, FNAME $1F4A, FSRC/FLEN, FFLAG $1F75, DIBUFH $1F7E), the console and glass-TTY state $1FA1–$1FAF. Moved from $6000–$60FF with the 2026-09-14 ROM shrink. |
$5900–$F7FF |
RAM — TPA: user programs + data (RUN loads at $5900, ~39.8 KB; the C stack grows down from CSTACKTOP $F800). Above $F800 sit the fixed scratch pages: the shell's 8-line command-history ring at $F800–$F9FF, commands' glob/dir-iteration page (FSDIRBUF) at $FA00, and the file-read buffer (RDBUF) at $FC00. |
$FE00–$FEFF |
RAM — stack (P3 grows down from $FEFF). |
$FF00 |
switch input port (read) |
$FF02 |
LED output port (write) |
$FF04 / $FF05 |
6850 ACIA status / data (the console serial port) |
$FF08 / $FF09 |
second 6850 ACIA status / data — the 2nd serial port added for two-mode operation (register-identical to the console ACIA: status bit0 RDRF, bit1 TDRE). Drives host file transfer via the kermit command while the console keeps $FF04. Modelled by p8xemu (-2i/-2o file-backed RX/TX). |
$FF10–$FF17 |
CF-IDE task-file registers |
$FF20–$FF2F |
retired — was the graphics display's DEVICE door (register pokes drew immediately). Closed by the single-interface migration: reads float $FF like any absent card, and the register file survives only as the GL walker's internal property. The GL/PGC port at $FF50 is the one graphics interface — see p8x-graphics-theory.md. Modelled by p8xemu — see emulator/README.md |
Every data address above is single-sourced in
generators/gen_memmap.py(which emitsmemmap.incfor the OS/monitor to.include, plusmemmap.h,memmap.py, and the command-facinglib_mem). This table is a hand-maintained mirror — regenerate the map and update it together. (Generating the table itself fromgen_memmap.pyis a tracked BACKLOG item.)
Reset clears the PC to $0000; the stack pointer (P3) is initialised to the top
of RAM.
Console newlines (CONOUT / $0103)¶
P8X emits a bare LF for a newline — p8cc's puts ends with LDA #10, and
most /bin commands follow suit. A bare LF moves the cursor down but not back,
so on a real serial terminal every line would step diagonally across the screen.
CONOUT (the monitor's PUTC) therefore expands a bare LF into CR LF. This
is the same place Unix puts the translation — on the terminal device (ONLCR in
the tty line discipline), not in the program and not in write().
Two consequences worth knowing:
- Files and pipes are unaffected. The OS's
OUTCHroutesREDIRF >= 1to a file or capture buffer, which never reachesCONOUT, sodir > out.txtanda | bcarry clean single-byte LF. Nothing has to check where its output is going; the routing already answers that. - An existing CR LF is not doubled.
TTYLSTremembers the last byte sent, so a caller that already emits CR LF — the monitor's ownCRLF, the OS's key echo — passes through unchanged rather than becoming CR CR LF.
| Address | Name | Meaning |
|---|---|---|
$1FA1 |
TTYRAW |
0 = expand; nonzero = pass bytes through untouched |
$1FA2 |
TTYLST |
last byte transmitted (the anti-doubling state) |
The two-mode / glass-TTY state lives just above, in the same console page (generated in generators/gen_memmap.py; see p8x-two-mode-design.md):
| Address | Name | Meaning |
|---|---|---|
$1FA4 |
GFXPRES |
1 = a GL card is fitted (the mode flag the monitor sets at wake); GL programs and the on-screen console gate on it |
$1FA5–$1FAE |
glass-TTY state | the on-screen console's own cursor and scratch: GTCOL/GTROW (text cursor), GTSUSP ($1FA7, 1 = a full-screen program owns the screen, so the console stops drawing), GTXL…GTYH (pixel position), GTCH/GTTMP/GTCNT |
$1FAF |
GCONEN |
1 = mirror CONOUT onto the GL screen (the glass TTY). On by default whenever a card is fitted — DISPINIT sets it at wake, installs the stroke font from /FONT.GL on the CF root, and blanks the screen, so the monitor itself is on the LCD pre-boot; screen off disables the mirror for a session |
TTYRAW is the escape hatch for sending binary down the serial link, where a
$0A is data rather than a newline — the equivalent of stty raw. Nothing in the
tree needs it yet; set it, do the transfer, clear it.