The host node starts in its ROM (entry 12), and the boot feeds it the system's source through the console: capsules/v4/sys/map-64.f18, the fourteen .f18 blobs and end.f18, each found, hash-checked and signature-checked as any capsule, then EOT. The ROM assembles the system and starts it at (IDLE); the host reads the image record (v4_image_from_node) for what only the assembled system knows. A ROM error is reported with its blob, line and meaning, then PARITY:FAIL. Deleted: v4/tools/mkimage.c, capsules/v4/nucleus-64.f18, v4/capsule/*.v4, v4/tools/v4tof18.py. The .f18 files are the source now. - text.c speaks the rest of the ROM's language (set, here, store, start; v4_text_value, v4_text_start), so the oracle reads the same source: test_host_rom_nucleus compares the ROM against text.c on the .f18 files, word for word, image record included. Host tests load the .f18 files. - mkmap image writes v4_map_image (the map's description of the system); hosted and the kernel link the ROM and the map instead of v4_image_64.c. - Hera (Captain Bob's ruling, option 1): births stream the system the ROM assembled, as assembled code; hera.4th is unchanged. test_host_unit's host builds the system with the ROM. - New parity lines: PARITY:V4_F18 per blob, PARITY:V4_ROM words=2049 rom_hash=0xa6c792b5821010b7 instructions=60725216. Acceptance: amd64, aarch64, riscv64 boot to ok> with PARITY:V4_POST tests=538 pass=538 fail=0, POST: PASSED, and PARITY:V4_SYSTEM word_count=317 dict_hash=0xe5a57f3eb72ff94a on all three -- the same hash as the prebuilt nucleus gave. make -C v4 test test-32 hosted-check: all pass. Logs: logs/20261015-141648, -141938, -142350. Startup cost under QEMU TCG, the ROM's assembly (last V4_F18 line to V4_ROM): amd64 26.7 s of 132 s to ok>; aarch64 41.0 s of 195 s; riscv64 20.5 s of 101 s. Hosted native: +0.85 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
223 lines
12 KiB
Markdown
223 lines
12 KiB
Markdown
# v4/
|
|
|
|
StarForth v4: the 32-instruction F18-derived core. The design lives in
|
|
`docs/v4.0.0/JUSTIFICATION.md` (why) and `docs/v4.0.0/DECOMPOSITION.md`
|
|
(every v3 word mapped to a v4 fate).
|
|
|
|
The first deliverable is the hosted C99 golden model (`JUSTIFICATION.md` §10,
|
|
step 1). It must pass POST and hold K≡1.0 with 32- and 64-bit cells on all
|
|
three host ISAs.
|
|
|
|
Acceptance (`JUSTIFICATION.md` §16): v4 must be equivalent to v3 at any point
|
|
in time, with the same vocabularies and behaviour, on the F18-derived engine;
|
|
and every ISA, hosted and bare metal, must still reach its `ok` prompt.
|
|
`make -C v4 test` passing is a development check, not acceptance.
|
|
|
|
What exists so far is the single node: registers, circular stacks, memory, all
|
|
32 opcodes, per-opcode and per-call-target heat, and three console
|
|
registers (`CONSOLE-TX`, which captures output, and `CONSOLE-RX` and `CONSOLE-STATUS`, which hand out
|
|
input a test feeds) standing in for the console node until the mesh exists. The tests load definitions
|
|
onto a node either opcode by opcode (`v4/include/v4/asm.h`) or as text in the notation `DECOMPOSITION.md`
|
|
uses (`v4/include/v4/text.h`). `make -C v4 test` builds
|
|
and runs the tests at both cell widths; `make -C v4 sanitize` repeats them
|
|
under ASan and UBSan. There is no POST and no K measurement yet.
|
|
|
|
## The system: ROM, source, capsules, prompt
|
|
|
|
`docs/v4.0.0/MACHINE.md` and `docs/v4.0.0/NUCLEUS.md` are the design. A v4
|
|
system is five things:
|
|
|
|
| Part | Where | What it is |
|
|
|---|---|---|
|
|
| Engine | `v4/src` | The golden model of the 32-opcode node |
|
|
| ROM | `capsules/v4/rom/*.f18`, assembled by `v4/tools/mkrom.c` | The machine's one binary: F18 code that assembles F18 source when the machine starts |
|
|
| System's source | `capsules/v4/sys/*.f18` | The system as F18 assembler text: the memory map, the fourteen, and `end.f18` (the switch-on state and `start`). No prebuilt nucleus: the ROM assembles it at every boot |
|
|
| Capsules | `capsules/v4/*.4th`, baked by `tools/mkcapsule.c` | FORTH source, loaded when the system comes up |
|
|
| Boot | `v4/system/boot.c` | Feeds the system's source to the ROM, checks and loads each capsule, prints the parity lines, gives the prompt |
|
|
|
|
A node that is born is sent the system as assembled code, never source
|
|
(Captain Bob, 2026-10-15): the host's machine assembles it once.
|
|
|
|
Two products link the same five and differ only in the console:
|
|
|
|
- **Hosted Linux**, `v4/tools/hosted.c`: `make -C v4 hosted` builds
|
|
`v4/build/starforth4-amd64`, `-aarch64` and `-riscv64`, static, 64-bit cells.
|
|
- **Bare metal**, `kernel/src/v4/sk_v4.c`: `make -f kernel/Makefile ARCH=<arch> STARFORTH_V4=1`.
|
|
|
|
A boot prints:
|
|
|
|
```
|
|
PARITY:V4_F18 name=v4:sys:map-64.f18 capsule_hash=0x... (one line for each blob of the system's source)
|
|
PARITY:V4_ROM words=2049 rom_hash=0x... instructions=60719827
|
|
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x... capsule_hash=0x... dict_hash=0x...
|
|
PARITY:OK
|
|
ok>
|
|
```
|
|
|
|
Every build of one commit prints the same hashes. `make -C v4 hosted-check`
|
|
boots the three hosted binaries (the two foreign ones under user-mode QEMU)
|
|
and fails unless their output is identical and ends in `PARITY:OK`.
|
|
|
|
A capsule line the node does not answer ` ok` to ends the boot, naming the
|
|
capsule, block and line, with `PARITY:FAIL` and `POST: FAILED`.
|
|
|
|
State, 2026-10-05, after `ENGINE.md` step 1: all six builds start the nucleus,
|
|
load `v4:forth79.4th`, pass POST and reach `ok>`, with the same lines:
|
|
|
|
```
|
|
PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=... capsule_hash=0x97a655081b2af559 words=298
|
|
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=...
|
|
PARITY:V4_POST tests=538 pass=538 fail=0
|
|
PARITY:V4_SYSTEM word_count=314 dict_hash=...
|
|
PARITY:OK
|
|
POST: PASSED
|
|
ok>
|
|
```
|
|
|
|
Bare-metal logs: `logs/20261006-110551/amd64/`, `logs/20261006-111621/aarch64/`,
|
|
`logs/20261006-111341/riscv64/`. On each, seven lines were then typed at the
|
|
prompt through the serial port — a definition, its use with the capsule's
|
|
`U*`, `COLD`, the capsule word again, a `FORGET` of it (refused), a kernel
|
|
word no one serves, and `BYE` — and each was answered as the hosted binary
|
|
answers it. The
|
|
capsules were unsigned (no signing key on this machine).
|
|
|
|
**A node is sent text as a message** (`docs/v4.0.0/MESH.md` section 6). It
|
|
does not read its own command line, prints no prompt and has no console of
|
|
its own. With nothing to do it is blocked reading its ports. A neighbour
|
|
writes it a message; text for it is interpreted; what it prints goes back
|
|
as messages to the sender, and then a message saying how the text ended.
|
|
Its console says ` ok` or ` ERROR` and prompts, as the kernel's REPL does
|
|
for a v3 VM. A message carries 1024 characters, a block. `v4/src/message.c`
|
|
is the format for a host; `v4_boot_line` (`v4/system/boot.c`) is the
|
|
console both products and the capsule loader use.
|
|
|
|
**A node asks its kernel by writing to its port** (`ENGINE.md` 3.3). The
|
|
write blocks the node until it has been served. A kernel word is a
|
|
dictionary entry made by `n KERNEL-WORD name`, whose body writes `n` to the
|
|
port; its arguments and results are on the data stack. `BYE` is the one
|
|
kernel word so far. A node can be stopped between any two instruction
|
|
words and is blocked while it waits at its port, which is what a system of
|
|
many users and tasks, preemptive and cooperative, needs of it
|
|
(`ENGINE.md` 3a).
|
|
|
|
**The two products have parted** (`ENGINE.md` 3c). They share the engine,
|
|
the FORTH-79 capsule and its POST; their nuclei may differ. The hosted
|
|
product is its own and may become a hosted SDK. The bare-metal product is
|
|
to be LithosAnanke with the node in the StarForth VM's place, and
|
|
everything about word records, the fleet and identity is its alone.
|
|
|
|
**Nodes that talk** (`docs/v4.0.0/MESH.md`) are the next step, ahead of
|
|
making v4 equal v3 on bare metal. Its first part is built and tested in
|
|
the engine: a node has `V4_PORTS` ports; a read from one blocks until the
|
|
neighbour writes and a write until the neighbour reads; a node is born
|
|
empty with `P` at its ports and executes what a neighbour sends it, which
|
|
is how each product's node now gets its nucleus, as a capsule; and
|
|
the fabric (`v4/src/fabric.c`) is the nodes there are and the table of how
|
|
their ports are wired, both of which change while they run. The products
|
|
below are still one node each; they become the five nodes at `MESH.md`
|
|
steps 8 and 9.
|
|
|
|
**Finding the way** (`MESH.md` step 4) is built: a node has a table of
|
|
which port leads toward which node (`ROUTE`, `DEFAULT-ROUTE`, `NO-ROUTES`),
|
|
passes on a message that is not for it, and `SEND`s text to another node;
|
|
`v4/tests/test_host_mesh.c` runs three StarForth nodes in a row.
|
|
`NEIGHBOUR ( node port -- )` tells a node who is on each port. What a node
|
|
cannot take or pass on it lets go and counts in `(LOST)`.
|
|
|
|
**Five nodes from nothing** (`MESH.md` step 5) is built and proven in the
|
|
fabric under test: Hera asks whoever holds the fabric for a node
|
|
(`v4/src/manage.c`: `NODE-BORN`, `NODE-WIRE` and the rest), sends it the
|
|
nucleus through the port that joins them, then FORTH-79 a line at a
|
|
time, and joins four such nodes in a square round her
|
|
(`capsules/v4/hera.4th`: `BIRTH`, `UNIT`). `v4/tests/test_host_unit.c`.
|
|
The two products are not yet this: they become the five nodes at steps 8
|
|
and 9.
|
|
A node she births is not POSTed: POST is the kernel's, once, on Hera.
|
|
|
|
**Refusals and waits** (`MESH.md` 7b, step 6c). A wait on a node that
|
|
never answers -- stuck, or gone -- is ended by Hera's killing it, which
|
|
tells the others it is gone, or from the console. A node with no
|
|
room for a message, or no way to pass it on, or that would be the
|
|
sixteenth to pass it on, sends a NACK to whoever waits on it; `(REFUSED)`
|
|
counts them. `AWAIT` ends in an error if the answer is not going to come:
|
|
"Message refused", "Node gone", or "Interrupted" when a line is typed at
|
|
the console. `n KILL` on Hera removes node n and tells every other node it
|
|
is gone; a node believes that only from its centre.
|
|
|
|
**Messages on the wire** (`MESH.md` 7d, step 6e). A node that is stuck
|
|
no longer stops its neighbours passing on, taking text or being typed
|
|
to. The fabric keeps a queue of whole messages each way on every wire
|
|
(`v4/src/wire.c`, 1,024 cells), and a node asks it, through six addresses
|
|
after its ports, to look at, take, move, drop or put a whole message:
|
|
each is one engine step, all of it or none. A node holds nothing in
|
|
transit. `SEND`, a NACK and a GONE go at once or are refused at once.
|
|
What a text prints, and its word of how the text ended, wait for room, so
|
|
that output is not lost. A node with nothing to do sleeps until a message
|
|
comes. A node removed, or a wire cut, at any step of an exchange leaves
|
|
the others at rest and answering (`v4/tests/test_host_mesh.c`). What it
|
|
costs is in `MESH.md` 7d.6: a burst beyond what a wire holds is refused;
|
|
text for a node that is stuck fills its wire and is refused from then on;
|
|
and a text whose printing must go by a stuck node waits, and cannot be
|
|
typed to, until that node is killed.
|
|
|
|
**Blocks** (`MESH.md` step 6). The block words are the kernel's, as they
|
|
are for a v3 VM: `BLOCK`, `BUFFER`, `UPDATE`, `SAVE-BUFFERS` and
|
|
`EMPTY-BUFFERS` each write one request to port 0, by block number only
|
|
(`v4/include/v4/blocks.h`). The node has a window of four slots in its
|
|
memory; the kernel copies blocks into it, keeps the record of what is in
|
|
it, and decides when a block is written (`v4/system/blocks.c`). The blocks
|
|
are the kernel's block subsystem's, which is v3's
|
|
(`v3/src/block_subsystem.c`):
|
|
one chain, the same blocks at the same numbers for every node. A hosted
|
|
program has the chain's fast RAM, blocks 1 to 2047. On bare metal the chain
|
|
is fast RAM, the ramdrive and the virtio disk; the disk is read and not yet
|
|
written, because nothing in v4 gives its owner's word that it may be
|
|
formatted. Who may have which block is not checked yet.
|
|
|
|
**This is still the lone node.** On bare metal `kernel_main.c` starts it
|
|
before the fleet tables, beside the kernel's own system and not in the VM's
|
|
place. `ENGINE.md` sets out the steps from here; step 1 is done and step 2 is
|
|
part done. `V3-PARITY.md` records the rulings it is built from.
|
|
|
|
**What this does not yet show.** `forth79.4th` holds two definitions, `U*`
|
|
and `U/MOD`. Every other word is still in the assembled nucleus, so POST is
|
|
so far a test of the assembled words. Moving them to the capsule, a group at
|
|
a time with POST after each, is the next step (`NUCLEUS.md` section 8).
|
|
|
|
## POST
|
|
|
|
POST is the kernel's, as in v3 (`MESH.md` step 6b, `NUCLEUS.md` 6.3). The
|
|
kernel holds the cases, `v4/system/post_cases.c`: 538 of them, covering 126
|
|
of the 130 words of the FORTH-79 Required Word Set. 431 are v3's cases with
|
|
what the hosted v3 binary did as the expected result; every other case,
|
|
and the v3 cases left out, are listed with reasons in
|
|
`docs/v4.0.0/POST79.md`.
|
|
|
|
- `make -C v4 post` boots the amd64 system: the quick check after a change.
|
|
- `make -C v4 hosted-check` boots all three hosted ISAs and compares them.
|
|
- `make -C v4 post79` writes the cases again (`tools/mkpost.py`,
|
|
`tools/post79_rules.py`); it needs the hosted v3 binary.
|
|
|
|
The runner, `v4/system/post.c`, feeds each case to the node a line at a
|
|
time, keeps what the node prints, reads its data stack from outside, and
|
|
judges: an error exactly if one is expected, and otherwise the stack and
|
|
every character printed. Nothing of POST is in the node's dictionary, and
|
|
POST leaves nothing: the system is sealed before it, and afterwards the
|
|
kernel puts the node back to the system as sealed, as v3 puts the
|
|
dictionary back after each of its suites. The boot then prints
|
|
`PARITY:V4_SYSTEM word_count=N dict_hash=0x...`, and passes only if no
|
|
case failed (`v4/system/boot.c`). A case the node does not come back from
|
|
ends POST there, named. `make -C v4 hosted-check` also boots a program
|
|
whose POST has failing cases and requires that the boot fails.
|
|
|
|
Not tested: `KEY`, `EXPECT`, `QUERY` (the keyboard) and `QUIT` (it returns
|
|
without ` ok`).
|
|
|
|
Open: `PAD 42 OVER !` faults on v4, because `PAD` is a byte address and `!`
|
|
takes a cell address (D-1). FORTH-79 expects it to work. The v3 case for it
|
|
is left out until that is ruled.
|
|
|
|
The Required Word Set list in `tools/mkpost.py` was written from memory of
|
|
the standard and has not been checked against the document.
|