Y1/OS — the YACC1 disk operating system¶
User guide, programmer guide (commands and the syscall/file API), the on-disk format, the memory layout and the
plan for Y1/OS. Written 2026-09-23 from the YACC1-D tree. Later still that day (v0.2) the OS was rewritten in
YACC1 assembly, os/y1os.asm, with the same behaviour and ABI (section 1a); y1os.c is its specification and is
still built with make -C os OS=c. Where this guide names a y1os.c function, y1os.asm has a routine of that name. Later the same day the P8X commands were ported (26 programs in /BIN, ten shared libraries, 50 man pages in /MAN, vi): os/README.md carries the current command table and os/PORT-PLAN.md the per-command status; the API described here is unchanged.
Sources: os/y1os.c (v0.1, read as of 2026-09-23 09:43 — it was being edited while this was written),
os/lib_abi.c (09:37), os/README.md (the v0 text of 2026-09-22), os/commands/{hello,echo,wc}.c, os/Makefile,
os/disk/README.TXT, os/PORT-PLAN.md (2026-09-23), tools/p8xfs.py (the on-disk format and the host tool),
tools/img2bin.py, tests/os/run.py, tests/os/basic.session and basic.int.out (v0 transcripts),
docs/system/OS-PLAN.md, firmware/abi/README.md, firmware/monitor/monitor.asm (the O command, uartinne),
software/compiler/y1cc.py (sys(), funcaddr(), argstr()), software/cfmodel.h, BACKLOG.md.
1. What it is, and what it is not¶
Y1/OS is a RAM-resident shell and file layer over a P8XFS v2 CompactFlash volume, written in C for y1cc and loaded
by the ROM monitor's O command. v0 (2026-09-22, os/README.md) was read-only: dir/cd/pwd/cat/load/run. v0.1
(2026-09-23, y1os.c header) adds the file layer (fs_*), write support (save/del/ren/mkdir/rmdir), handles, and a
syscall table through which programs use the same functions. Everything is proven on the emulators only — the CF
card is not built (docs/system/MACHINE.md). Y1/OS is the YACC1's own from here on: it is not kept in step with
P8X/OS, and neither are the programs brought over (decided 2026-09-22/23, os/README.md, os/PORT-PLAN.md). Only the
on-disk format is shared, so tools/p8xfs.py (a fork of the P8X tool) builds the images and images can be exchanged.
State on 2026-09-23 morning: y1os.c references os/lib_fs.c (C wrappers fopen()… over sys()) which does not
exist in the tree yet; os/README.md, os/disk.img (13:45 on 2026-09-22) and the tests/os transcripts are the v0
ones (the v0.1 banner differs: Y1/OS v0.1 (2026-09-23) P8XFS v2). To verify: rebuild (make -C os) and
tests/os/run.py --update once the OS edit settles; the v0.1 commands below are read from the source, not from a
transcript.
1a. v0.2: the assembly OS (2026-09-23)¶
os/y1os.asm (hand-written, 7,137 bytes = 14 sectors; the C version is 14,619 = 29) does exactly what y1os.c
does: same shell, messages (os/strings.txt, assembled as numeric bytes by os/mkstrings.py), syscalls, results,
side effects and sectors written; only the banner says Y1/OS v0.2 (2026-09-23). The test sessions take 1.3-1.9x
fewer instructions (e.g. pipe 9.57M -> 4.91M, pack 29.97M -> 15.78M on the interpreter; the same ratio in
microcode steps). Its RAM is $4A00-$4FFF at fixed, aligned addresses; $2BE1-$49FF is free. os/README.md ("The
assembly OS") has the conventions, the test evidence and the list of y1os.c behaviours both versions keep.
make -C os # build/y1os.bin (the assembly OS), the /BIN programs, disk.img
make -C os OS=c # the same with the C OS (y1os.c)
make -C os run # the microcode emulator with the ROM and the disk: type O at the monitor prompt
make -C os run-int # the instruction-level emulator
make -C os test # tests/os/run.py: scripted sessions on both emulators against expected transcripts
2. User guide¶
Booting¶
At the monitor prompt type O. The ROM prints BOOT FROM CF, initialises the card, reads the boot block, checks the
P8 signature and OSCNT, loads LBA 1..OSCNT to $1000 and calls it (monitor.asm boot:, MONITOR.md
section 6). The OS installs its syscall table, reads the boot block again for OSCNT and the free pointer, announces
itself and prompts with the current path: />. Errors: CF ERROR (no card or a read failed), NO OS ON THE CARD
(bad signature or OSCNT = 0), CF read error (the OS's own read of LBA 0).
The shell (y1os.c main)¶
A line is read with readline() through UARTIN (which echoes; backspace and DEL erase; up to 128 characters; LF,
CR or NUL ends it). The first word is lower-cased and matched; the rest of the line is the argument string.
| Command | Effect (y1os.c) |
|---|---|
dir [path] |
list a directory through a directory handle: name, <DIR> or size (NNx64K+ prefix over 64K), @LOAD for a file with a load address; then N entries |
cd path |
change directory (fs_chdir): absolute /A/B or relative; . and .. are the directory's own entries; the prompt path follows the components |
pwd |
print the current path |
cat path / type path |
print a file, sector by sector through a read handle |
load path |
read a file into its stored load address; loaded N bytes at $XXXX |
run path [args] |
load the file and call its exec address; args (up to 127 characters) go to ARGBUF for argstr(); whatever the program left open is closed when it returns |
save path addr len |
new in v0.1: write len bytes of memory from addr (both hex) to a new file whose load and exec address are addr (fs_create, fs_write, fs_close); a same-named file is replaced |
del path |
tombstone a file (fs_delete; not a file otherwise) |
ren path newname |
rename in place: the entry keeps its slot and directory (fs_rename; newname is a bare name) |
mkdir path |
a new 4-sector directory extent at the free pointer with . and .. (fs_mkdir) |
rmdir path |
tombstone an empty directory (fs_rmdir; not an empty directory otherwise) |
help / ? |
the three-line summary |
exit |
prints bye and returns to the monitor (main returns; the ROM's JSRUR return lands in cmdloop) |
| anything else | try_bin: the word upper-cased, looked up as /BIN/NAME, then as NAME in the current directory; a file is loaded and run with the rest of the line as arguments; otherwise what? |
Paths: components 1..12 characters (name too long otherwise); a trailing / is accepted; not found, not a
directory, is a directory are the errors (too big, over 64K, until 2026-09-25). Names are case-sensitive and stored as the host
tool writes them; the Makefile puts programs in /BIN in upper case, which is why the implicit lookup upper-cases
the command word (so hello a b c finds /BIN/HELLO). Load-address rule (load_file): a program must load at or
above TPA = $5000 and end below TPATOP = $D000, else bad load address or size.
The v0 session tests/os/basic.session with its transcript basic.int.out shows the read-only commands' exact
output (including wc 45 1 on raw sector 45, cd /BIN/../BIN, and nothere → what?).
Moving files to and from the Mac: kermit (2026-09-26)¶
/BIN/KERMIT transfers files over the console cable with the Kermit protocol, against C-Kermit on the Mac (or
tools/y1kermit.py): kermit -r receives into the current directory, kermit -s FILE... sends, kermit -x is a
server (SEND, GET, FINISH from the Mac). The how-to with C-Kermit's exact settings is
docs/procedures/KERMIT.md (tools/y1.ksc holds them); the program, its E-Kermit
origin, its assembly line routines and its measured speed (about 260 bytes/s in, 240 out at 1 MHz) are in
os/README.md "kermit"; man kermit on the machine. It reads the 16C550 directly (os/kermit_io.asm: LSR, then
RBR; the FIFO on during a transfer) instead of the ROM's UARTINNE, which turns CR into LF and costs a JSR a
character, and times out by counting polls calibrated for 1 MHz. tests/kermit/run.py runs it on both emulators.
The disk image that ships¶
os/Makefile builds os/disk.img (2048 sectors = 1 MB): the OS at LBA 1.. (10 sectors for v0), /BIN/HELLO,
/BIN/ECHO, /BIN/WC, and /README.TXT from os/disk/. p8xfs.py tree disk.img lists it.
3. Programmer guide¶
Writing a /BIN command¶
A command is an ordinary y1cc program (os/commands/hello.c):
#include "../lib_abi.c"
#include "y1lib.c"
void main() {
puts("hello from /BIN/HELLO");
if (*argstr()) { putstr("args: "); puts(argstr()); }
}
- Compile at the transient area:
y1cc.py hello.c -o hello.asm --org 0x5000 --os(no--boot, no--vector: the OS calls the exec address withcall()=JSRUR R7, andmain'sRETreturns to the shell;--ossince 2026-09-23, so that the console goes through the OS and can be redirected). - Assemble (
asm hello -d=yacc1withyacc1.defand a-hrcasm.rcbeside it) →hello.img. - Flatten:
python3 tools/img2bin.py hello.img hello.bin --base 0x5000(unwritten bytes come out as 0; the--enddefault $F000 keeps any stub out). - Put on the disk:
python3 tools/p8xfs.py put disk.img hello.bin --name /BIN/HELLO --load 0x5000 --exec 0x5000(the Makefile does this for everycommands/*.c, upper-casing the name), or from the machinesave /BIN/HELLO 5000 lenafter loading it another way. Since 2026-09-25 the last three steps can happen on the machine: puthello.asmon the disk andasm HELLO.ASM /BIN/HELLO(/BIN/ASM,man asm) writes the same program file, load and exec address included. - Arguments:
argstr()returns the NUL-terminated tail at ARGBUF ($0F40, up to 127 characters). Console output is the compiler'sputchar/puts(with--osthe CONOUT syscall, which the shell redirects; without it BIOSCHAROUTon the machine and on ucemu, port 2 on the interpreter); input for a filter issys(SYS_CONIN)(stdin: no echo, 65535 at its end or at Ctrl-D) andsys(SYS_CONST); a key issys(SYS_KEYIN)(below). - All the compiler's rules apply: recursion only with its rules (since 2026-09-24; 768 bytes of stack), R2 untouched, carry only in the compiler's idioms
(C-COMPILER.md). A program's globals and BSS live in its own image (cleared at its
main).
The syscall interface (os/lib_abi.c, y1cc.py sys, y1os.c install)¶
sys(SYS_x, a, b, c) stores a, b, c (any of them optional) into the argument words SYSARG0..2
($0F06/$0F08/$0F0A), JSRURs the word at SYSTAB + 2·x ($0F14 + 2x) and returns SYSRES ($0F0C) — always a full
16-bit word: 1/0 for yes/no, a handle or 0, a count, a byte or 65535 for none / end of file. Y1/OS fills SYSTAB at
boot with pokew(SYSTAB + 2*n, funcaddr(h_x)); each handler reads SYSARGn with peekw(), calls the fs_*
function, and writes SYSRES with pokew(). The table keeps the shape of the P8X $20xx syscalls (OS-PLAN
decision 6) so that P8X C commands port by swapping lib_abi.c. Handles are 1..4 (NH), each with its own 512-byte
buffer and position; one write handle at a time.
| n | Name | Arguments → result (lib_abi.c) |
|---|---|---|
| 0 | SYS_OPEN |
(path) → handle 1..4, 0 not found / not a file / no handle free / 16M or more (64K until 2026-09-25) |
| 1 | SYS_READ |
(handle, buf512) → bytes put in buf from the sector holding the position; 0 at the end |
| 2 | SYS_GETC |
(handle) → next byte, 65535 at the end |
| 3 | SYS_CLOSE |
(handle) → 1; a written file is registered in its directory here (last sector flushed, entry written, free pointer moved) |
| 4 | SYS_CREATE |
(path, load, exec) → handle, 0 cannot (a same-named file is replaced; refused while another write is open) |
| 5 | SYS_WRITE |
(handle, buf, n) → bytes written (the assembly OS copies what fits in the rest of each sector at once since 2026-09-25: the same bytes and card writes as a PUTC per byte) |
| 6 | SYS_PUTC |
(handle, byte) → 1, 0 cannot (not the write handle, 16M - 1 bytes reached) |
| 7 | SYS_DELETE |
(path) → 1 tombstoned, 0 not a file |
| 8 | SYS_MKDIR |
(path) → 1, 0 cannot (exists, parent missing, no slot, a write is open) |
| 9 | SYS_RMDIR |
(path) → 1, 0 not a directory or not empty |
| 10 | SYS_OPENDIR |
(path) → handle, 0 not a directory ("" = the current directory) |
| 11 | SYS_READDIR |
(handle, buf32) → 1 with the next live 32-byte entry in buf, 0 at the end |
| 12 | SYS_RESOLVE |
(path, buf32 or 0) → 1 found (entry copied to buf), 0 not found |
| 13 | SYS_GETCWD |
(buf) → length; the current path, NUL-terminated (up to 64 bytes) |
| 14 | SYS_CHDIR |
(path) → 1, 0 not found, 2 not a directory |
| 15 | SYS_RENAME |
(oldpath, newname) → 1, 0 cannot |
| 16 | SYS_ENTRY |
(buf32) → 1; the 32-byte entry the last OPEN/OPENDIR/RESOLVE/CREATE… found, copied |
| 17 | SYS_CONIN |
() → the next byte of stdin, without echo: the shell's < file or pipe (65535 at its end), else the console (ROM UARTINNE; 65535 on Ctrl-D or NUL, the emulator's end of input). y1cc --os: getchar() |
| 18 | SYS_CONST |
() → 1 when a stdin byte is waiting (a < file or pipe: always 1; the console: ROM CONST, always 1 on the emulators) |
| 19 | SYS_CONOUT |
(byte) → nothing (SYSRES untouched): the byte to stdout, the shell's >/>> file or pipe, else the raw console (CHAROUT). y1cc --os: putchar()/puts() (2026-09-23) |
| 20 | SYS_KEYIN |
() → a key: always the console, never redirected, no echo; 65535 on Ctrl-D/NUL (2026-09-23) |
| 21 | SYS_STDIO |
() → bit 0 stdin redirected, bit 1 stdout redirected (2026-09-23). The last entry SYSTAB ($0F14) has: 22..31 are in SYSTAB2 ($4FC0, 32 entries, 0..21 the same; 2026-09-25), which y1cc's sys() uses for a constant number over 21 |
| 22 | SYS_EXIT |
(status) → does not return (2026-09-25): the program ends from any depth, on any stack, as if main had returned; STATUS ($0F0E) = status when it has ended, for the next program to read. lib_fs.c: osexit() |
| 23 | SYS_EXEC |
(path, args) → 0 when path cannot be run (over 63 characters, not found, not a file, does not load into $5000-$CFFF); else does not return: the caller ends and path runs with args as its tail, in the same shell command (> and | stay). osexec(). The native compiler's passes chain with it |
| 24 | SYS_SEEK |
(handle, hi, lo) → 1: a read handle at position hi:lo (24 bits), not past the end; 0 otherwise. fseek() |
| 25 | SYS_READN |
(handle, buf, n) → the bytes put in buf: the bytes n GETCs would give from the position (after a SEEK into a sector too - unlike READ, which starts at the sector's start), never past the end of the position's sector: exactly min(n, the rest of the sector, the rest of the file); 0 only at the end, for n = 0, for anything but a read handle (2026-09-25; mixes with GETC, SEEK and READ; the exact contract and tests/os/rdn.session: os/README.md). freadn(): a program's own buffer at a syscall per block (the native compiler's passes) |
Writing goes to the volume's free pointer (boot block bytes 4–5, kept in step on disk): CREATE takes the handle
and remembers the directory and the name, PUTC/WRITE fill the handle's sector buffer and flush full sectors,
CLOSE writes the last (possibly partial) sector, registers (name, start, length, load, exec, $01) in the first
free slot of the directory, and advances the free pointer — the same layout p8xfs.py writes, so the host tool
reads what the OS wrote and vice versa. Positions and lengths are 24 bits (2026-09-25; 16 before, when a file
over 64K could not be opened): a file is up to 16M - 1 bytes, the entry's byte 18 holding bits 16-23; the syscalls
did not change (a program that reads until GETC's 65535 or READ's 0 never sees a position), so every program works
unchanged and reads or writes any size; tests/os/big.session writes a 70K and a 140K file, reads them back
byte-wise and sector-wise, copies one through cat > and appends to the other, on both emulators with both kernels,
and the host checks every byte with p8xfs.py get and fsck. tests/compiler/syscall.c
is a stand-alone model of the whole mechanism (handlers, funcaddr, nested sys() calls).
Redirection and pipes (2026-09-23)¶
The shell takes cmd [args] [< in] [> out | >> out] [| cmd ...], up to four commands per line (os/man/shell,
os/README.md). It works because the OS and every /BIN command are compiled with y1cc --os: putchar/puts are
the syscall CONOUT (19) and getchar is CONIN (17). Before a command runs the shell opens its files and sets
so_h/si_h in y1os.c: CONOUT then writes the byte to the output file (or, with so_h = 0, to the raw console
through the ROM's CHAROUT, never through putchar, which would call CONOUT again); CONIN and CONST read the input
file (si_h) or the console. After the command, whatever happened, both files are closed and the console is back. A
pipe a | b runs a with its output to /PIPE0.TMP, then b with its input from it (stages alternate
/PIPE0.TMP//PIPE1.TMP); the temp files are deleted after the line. There is no multitasking: the stages run one
after the other.
Two kinds of input follow from it. Data is conin() (CONIN): the filters' "no file named" and -, redirectable.
A key the user presses in answer to the program is keyin() (KEYIN, 20): always the console, so cat F |
more pages the pipe while --More-- waits on the keyboard (a Unix pager reads /dev/tty for the same reason);
vi, dump, examine and the pager use it. STDIO (21) tells a program what is redirected; the pager does not
page when its output is a file or a pipe. Error messages use eputs() (os/lib_err.c), which writes through the
ROM's CHAROUT and so never lands in a file or a pipe.
Rules that come with it:
- One write handle. CREATE and MKDIR allocate at the single free pointer, so while a command's stdout is a file
(
>,>>, or a pipe) its own CREATE/MKDIR returns 0:cp,touch,save,mkdir,vi's:wfail cleanly inside a redirect or a pipe (they work with<). - Replace at close. A same-named file is replaced when the new one is closed, by writing the new entry over the
old one's slot (one sector); until then the old file is whole (
sort F > Fworks) and a write that fails leaves it. >>continues in place when the file is the last one written (its extent ends at the free pointer), else copies the old sectors to the free pointer first; no byte of the old file changes either way.- Static frames. y1cc frames are static and the OS's own
putcharis CONOUT, so nothing on the console handlers' path (con_out,con_in,key_in,fs_putc,fs_getc,cfread,cfwrite...) may print: a handler would re-enter itself. Their errors are return codes; the shell's messages use the raw console.
Reading sectors directly (os/commands/wc.c)¶
Before the file API, wc LBA COUNT read raw sectors through the BIOS, which remains the pattern for anything below
the file layer: poke(CFLBA0, lba); poke(CFLBA1, lba >> 8); poke(CFLBA2, 0); if (bios(CFREAD, buf, 0)) … with a
512-byte char array (R7 advances). os/PORT-PLAN.md wave 1 replaces this wc with the P8X one over the file API.
os/lib_abi.c is the address list: the BIOS vectors STRINGOUT $FFC0, CHAROUT $FFC4, UARTOUT $FFC8,
SHOWADDR $FFCC, UARTIN $FFE8, CFINIT $FFEC, CFREAD $FFF0, CFWRITE $FFF4, CONST $FFF8, UARTINNE $FFFC;
the variables SYSARG0..2 $0F06/$0F08/$0F0A, SYSRES $0F0C, CFLBA0..2 $0F10–$0F12, SYSTAB $0F14, ARGBUF
$0F40 (ARGMAX 127), OSBASE $1000, TPA $5000, TPATOP $D000; and the SYS_* numbers above.
4. P8XFS v2 on disk (tools/p8xfs.py, y1os.c)¶
A volume is a sequence of 512-byte sectors (LBAs). All multi-byte fields are little-endian (the P8X's order;
struct formats <H, <I in p8xfs.py; le16()/put16() in y1os.c).
| LBA | Contents |
|---|---|
| 0 | boot block: bytes 0–1 P8, byte 2 version = 2, byte 3 OSCNT (sectors of OS image, 0 = none), bytes 4–5 the free pointer (first unallocated LBA); the rest zero |
| 1–32 | the OS image, OSCNT sectors used (max 32 = 16 KB; p8xfs.py boot refuses more); loaded to $1000 by the O command |
| 33–36 | the root directory: a 4-sector extent = 64 entries; entry 0 = . (itself), entry 1 = .. (itself for the root) |
| 37… | files and subdirectory extents, contiguous, allocated at the free pointer (alloc / fs_create), moved only by pack |
A directory is a file whose extent holds 32-byte entries; a subdirectory's extent is 4 sectors by default
(mkdir --secs changes it on the host; the OS always makes 4; the OS reads the size from the entry). Entries are
used in order; the first entry with flag $00 ends the scan, and a deleted entry is a tombstone $FF that a new
entry may reuse. Neither the OS nor the host tool frees space when it deletes or replaces; /BIN/PACK does it in
one pass (below).
Compaction (pack, 2026-09-23). os/commands/pack.c reads the tree from the root without recursion (its
record table is the work list: start LBA, sectors, the record of the holding directory, the entry's byte offset),
sorts the live extents by start LBA, refuses a volume whose extents overlap or pass the free pointer, then moves
each extent down to the lowest free sector at or above LBA 37, sector by sector with CFREAD/CFWRITE. Ascending order
means every extent still to move lies above the current one, and a copy down only writes below the current one's
end. An extent whose hole is at least its size is copied in one step and its entry then rewritten (one sector
write); a bigger one would overwrite the start of its own old copy, so it goes in two steps through a scratch area
at the old free pointer (copy there, entry there, copy down, entry down), with the boot block's free pointer raised
over the scratch area before the first move. So at every moment every entry points at a complete copy of its
extent: pack is reset-safe, and a rerun finishes an interrupted one (a '.' or '..' that lags one step is fixed
by the repair pass every run makes). tests/os/run.py --cuts 60 proves it on the emulator: 120 cuts in every
phase, fsck clean, every file byte-identical, rerun complete. Entries are rewritten in their directory where that
directory is at that moment; pack then writes the final free pointer and re-enters the current directory by path
(the OS caches its LBA); the shell re-reads the free pointer after every program (read_free()), since the OS
caches that too. Refused under </>/>>/| (STDIO), and when the card has no room for the scratch area (the
last scratch sector must read; nothing lies past LBA 65535). p8xfs.py fsck checks '.' as well as '..' since then.
The 32-byte directory entry (pack_at/unpack_at, take_entry/set_entry):
| Offset | Size | Field |
|---|---|---|
| 0 | 12 | name, ASCII, space-padded, case preserved; the host tool silently truncates a longer name to 12 (aliasing another with the same first 12 bytes — --strict makes that an error); the OS refuses names over 12 |
| 12 | 4 | start LBA (<I; the OS reads the low 16 bits and writes the high 16 as 0) |
| 16 | 4 | length in bytes (<I; the OS uses the low 24 bits, byte 18 being "×64K", so files are up to 16 MB - 1 since 2026-09-25; before, it refused to open anything over 64K; byte 19 must be 0) |
| 20 | 2 | load address (<H; p8xfs.py put --load, default $B000 — always give --load 0x5000 for the YACC1) |
| 22 | 2 | exec address (<H; --exec) |
| 24 | 1 | flags: $00 end of directory, $01 file, $02 directory, $FF deleted |
| 25 | 7 | spare, zero |
Sector count of an entry = ⌈length / 512⌉ (take_entry: (e_len >> 9) + e_lenhi * 128, plus 1 when e_len & 511,
minimum 1; not (e_len + 511) / 512, which wraps at 16 bits); a directory's sector count comes from its length
field (dir_secs). p8xfs.py fsck checks the signature, the . and .. links, the extents and reports
reclaimable space.
Host tool summary (p8xfs.py --help): create img [--sectors N] (default 256; the Makefile uses 2048), boot img
os.bin, mkdir img /BIN [--secs N], put img file [--name /BIN/F] [--load A] [--exec A] [--replace] [--strict],
rm img /BIN/F, get img /BIN/F [--out path], ls img [path], tree img, fsck img. Both emulators take the
image with -c disk.img and create a zero-filled 256-sector one if the file is missing (software/cfmodel.h).
5. Memory layout with the OS running (os/README.md, lib_abi.c, OS-PLAN.md map A)¶
| Range | Use |
|---|---|
| $0000–$0EFF | system page: BASIC's areas (unused while the OS runs); the OS's four 512-byte handle buffers at $0400–$0BFF (since 2026-09-23); the stack from $0EFF down, not below $0C00 |
| $0F00–$0FFF | the ROM's variables, and the OS's syscall block inside their free space: SYSARG0..2 $0F06–$0F0B, SYSRES $0F0C, CFLBA0..2 $0F10, SYSTAB $0F14–$0F3F, ARGBUF $0F40–$0FBF (over the monitor's idle line buffer) |
| $1000–$4FFF | the OS: y1os.asm (v0.2) = a 7,137-byte image ($1000–$2BE0, 14 sectors), free $2BE1–$49FF, and its RAM $4A00–$4F0F (line, path, entry, pipeline table, the sector buffer at $4C00, the handle records at $4E00, the variables; the Makefile fails the build if the image reaches $4A00). The C version (OS=c): 14,619 bytes = 29 sectors + 1,424 bytes of data, 16,043 of 16,384 (5.1K for v0) |
| $5000–$CFFF | the transient program area (TPA..TPATOP), 32 K |
| $D000–$DFFF | video (map A: $D000–$D7FF the 2K display RAM, $D800–$DFFF unused) — not RAM |
| $E000–$FFFF | ROM |
Map B in OS-PLAN.md (video moved to $E000, $D000 jumpered as RAM) would make the TPA 36 K; the OS must not care
which — TPATOP is the one constant. Whether the OS load address stays $1000 if the OS outgrows 16 K is an open
question in the plan.
6. Tests¶
tests/os/run.py [--keep] [--update] builds os/disk.img (make -s -C os), then for every tests/os/*.session
feeds O\n + the session lines to both emulators (emulator -x -m -c disk.img -l 6000000 and y1ucemu -x -m -c
disk.img -l 80000000), cuts the transcript from BOOT FROM CF to the monitor prompt after bye, and compares
with NAME.int.out / NAME.uc.out (the microcode transcript shows the monitor's input echo). --update rewrites
the expectations after a change that was checked by eye. Part of make check and make os-test at the root. The
committed transcripts are v0's (banner Y1/OS v0 (2026-09-22)). tests/kermit/run.py (2026-09-26) boots the OS on a
pseudo-terminal instead and moves files with /BIN/KERMIT against tools/y1kermit.py (above; also in make check).
7. The plan and the backlog (OS-PLAN.md, os/PORT-PLAN.md, BACKLOG.md)¶
Decisions already taken (2026-09-22): CF in I/O space on P8/P9 (select + data, to keep six ports free; briefly moved
to P4/P5 on 2026-09-23 and back on 2026-09-24, OS-PLAN.md decision 1 note); the video
card v2 puts the 6845 on ports PA/PB so it needs only 2K of memory; the port map and the two memory-map variants;
the ROM holds only sectors (monitor + CF driver + boot), never the filesystem, so an OS change never needs a burn;
P8XFS v2 byte for byte; the kernel in C with y1cc (size is the risk: C is 2–3× assembly); the console is two BIOS
vectors so video/PS-2 can replace the UART without the OS knowing.
Done since (2026-09-23, in the source): write support and the file API/syscalls above (phase 2's second half and the start of phase 3). Still to do, in the plan's order:
- The CF interface in hardware: designed as its own card (CF card v1.0, KiCad, never ordered: 74LS138 decode of
P8/P9, 74LS32 strobe gating, 74LS175 select latch, 74LS08, 74LS245, True IDE 8-bit, status pull-ups, ACT LED;
docs/cards/cf.md); since 2026-09-24 planned onto the memory card, same circuit, still I/O-mapped (not designed yet); bench-tested with the bus tester (OUTI P8 / INP P9through the ROM driver, which the chip in the machine already has) before the CPU touches it. fsckon the OS side (packdone 2026-09-23,/BIN/PACK);os/lib_fs.c(the C wrappers oversys()); updatedos/README.md,disk.imgandtests/ostranscripts for v0.1.- Porting the P8X commands (
os/PORT-PLAN.md, survey of 46 commands + 18 libraries): wave 0 the shared libraries (lib_stdin,lib_glob/lib_regexmade iterative…) and the mechanical recipe (//#use X→#include "lib_X.c",bios()no longer returns a carry bit); wave 1 console-only and file readers (pwd help dep dump examine man cat wc head tail more sort uniq sed awk cmp diff md); wave 2 writers and directory tools (touch del mv, thentree find dir grep -r cp -rwith iterative walks); wave 3 development tools (vi, an on-targetasmfor the RC/asm dialect); the graphics/WM programs deferred or skipped; ~48 man pages to bring over as edited text; BASIC re-assembled at a TPA address as/bin/basic(OS-PLAN phase 3). The plan's open design points (its section 5): console EOF and shell redirection,rename(now done), several handles (done: 4),createwith load/exec (done), a no-echo console read (done:UARTINNE/SYS_CONIN). - Video console + PS/2 keyboard behind the console vectors (video card v2 on PA/PB, a keyboard controller on PC/PD).
Done 2026-09-23: redirection and pipes (<, >, >>, |, above). Also open: command history in the shell; dual CF (a second card at PC/PD);
the E-command RAM loader, which becomes unnecessary once the card boots.