feat(v4): the ROM assembles the system's .f18 to the nucleus, word for word

capsules/v4/sys: the nucleus's fourteen sources as .f18 blobs (made once by
v4/tools/v4tof18.py: header becomes the ROM's directives, every other token
and line kept), the memory map as map-<width>.f18 (mkmap sys, from one
table in host_map.h that host_load reads too), and end.f18, the switch-on
state mkimage.c set in C, the image record, and start (IDLE).

test_host_rom_nucleus.c: the ROM, fed those blobs through the console,
builds RAM identical word for word to mkimage.c's, outside its own state
and the image record, and starts at (IDLE), at both widths.  The cost:
60.7 million instructions, 1.5 s on the C model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-15 12:27:50 -04:00
co-authored by Claude Opus 5.5
parent 084c520a54
commit 1bf19d6224
22 changed files with 4058 additions and 117 deletions
+141
View File
@@ -0,0 +1,141 @@
/* test_host_rom_nucleus.c -- the proof: the ROM rebuilds today's nucleus, word for word.
*
* Plan Task 8; MACHINE.md section 6, step 1. Node A is built as
* v4/tools/mkimage.c builds the nucleus: text.c assembles v4/capsule/ *.v4,
* then the switch-on state is set in C. Node B is the machine with its ROM,
* fed the system's blobs -- capsules/v4/sys/map-<width>.f18, the fourteen
* .f18 files made from the .v4 ones, and end.f18 -- through the console.
* When the ROM starts the system, every word of B's RAM must be A's, except
* the ROM's own state area and the image record (which A does not have, and
* which must hold A's addresses). It also reports what the assembly cost:
* instructions retired and seconds (Captain Bob: watch the startup cost).
*/
#include "rom_harness.h"
#include "v4/text.h"
#include <stdlib.h>
#include <time.h>
static int failures = 0, checks = 0;
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
static v4_node a, b;
static v4_text tx;
static const char *const files[] = { "core", "input", "dict", "codegen", "compile", "quit", "forth",
"numout", "words", "system", "qmath", "blocks", "log", "acl" };
/* node A: mkimage.c's main, as it is */
static int build_a(void)
{
static const char *const v4files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4", "compile.v4", "quit.v4", "forth.v4",
"numout.v4", "words.v4", "system.v4", "qmath.v4", "blocks.v4", "log.v4", "acl.v4" };
v4_cell k;
if (!host_load(&tx, &a, v4files, 14) || !v4_text_finish(&tx)) return 0;
a.mem[DP] = DICT_W * 4;
a.mem[LATEST] = v4_text_latest(&tx);
a.mem[STATE] = 0;
a.mem[CFP] = CFS_W;
a.mem[BASE] = 10;
a.mem[FENCE] = DICT_W;
a.mem[LOG_LEVEL] = 2;
a.mem[CONTEXT] = LATEST;
a.mem[CURRENT] = LATEST;
a.mem[SRC] = TIB;
a.mem[BOOT_CELLS] = a.mem[DP];
a.mem[BOOT_CELLS + 1] = a.mem[LATEST];
a.mem[FENCE] = (a.mem[DP] + 3) / 4;
a.mem[NODE_ERROR] = 0;
a.mem[BLK] = 0; a.mem[SCR] = 0; a.mem[SRC] = TIB; a.mem[SRC_HOOK] = 0;
for (k = BVARS; k < BVARS + 6; k++) a.mem[k] = 0;
for (k = BUF0_W; k < BUF0_W + (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS); k++) a.mem[k] = 0;
for (k = TIB_W; k < TIB_W + 260; k++) a.mem[k] = 0;
for (k = WBUF_W; k < WBUF_W + WBUF_CELLS; k++) a.mem[k] = 0;
for (k = PAD_W; k < PAD_W + 21; k++) a.mem[k] = 0;
a.mem[TO_IN] = 0; a.mem[SPAN] = 0;
a.mem[LINE_STATUS] = 1;
HOST_MESSAGES_EMPTY(&a);
return 1;
}
/* the ROM's own cells, and the image record: not compared */
static int excluded(v4_cell w)
{
return (w >= OUT_W - 144 && w < OUT_W - 64)
|| (w >= BUF0_W - 32 && w < BUF0_W - 22)
|| (w >= BUF0_W - 122 && w < BUF0_W - 119)
|| (w >= ROM_IMG && w < ROM_IMG + 4);
}
static char *slurp(const char *name, size_t *len)
{
char path[512];
FILE *f;
char *t;
long n;
snprintf(path, sizeof path, "%s/%s", V4_SYS_DIR, name);
f = fopen(path, "rb");
if (!f) { printf(" cannot read %s\n", path); return NULL; }
fseek(f, 0, SEEK_END); n = ftell(f); fseek(f, 0, SEEK_SET);
t = malloc((size_t)n + 2);
if (fread(t, 1, (size_t)n, f) != (size_t)n) { fclose(f); free(t); return NULL; }
fclose(f);
t[n] = '\n'; t[n + 1] = 0; /* a blob that does not end with a new line still ends a token */
*len = (size_t)n + 1;
return t;
}
int main(void)
{
static char *text;
size_t total = 0, cap = 1u << 20, len, fed = 0;
char name[32];
unsigned i;
long words = 0;
clock_t t0, t1;
v4_cell w, idle_a;
CHECK(build_a(), "node A builds as mkimage.c builds it: %s", v4_text_error(&tx));
idle_a = v4_text_word(&tx, "(IDLE)");
/* the system's source, as the loader will feed it: map, the fourteen, end, EOT */
text = malloc(cap);
snprintf(name, sizeof name, "map-%d.f18", V4_CELL_BITS);
for (i = 0; i < 16; i++) {
char *t;
if (i == 0) t = slurp(name, &len);
else if (i <= 14) { char f18[32]; snprintf(f18, sizeof f18, "%s.f18", files[i - 1]); t = slurp(f18, &len); }
else t = slurp("end.f18", &len);
if (!t) { CHECK(0, "a blob is missing"); return 1; }
memcpy(text + total, t, len); total += len; free(t);
}
text[total++] = 4;
rom_boot(&b);
v4_node_stack_regs_attach(&b, DSTACK_REG, RSTACK_REG);
b.p = V4_ROM_BASE + 12;
t0 = clock();
while (b.p >= V4_ROM_BASE && words < 400000000L && !b.stopped) {
if (fed < total) fed += v4_node_console_feed(&b, text + fed, (unsigned)(total - fed));
(void)v4_exec_step_word(&b, &rom_es, &rom_h);
words++;
if (words % 1000 == 0 && rom_in(&b, "(ROM-FAILED)")) break;
}
t1 = clock();
CHECK(b.p < V4_ROM_BASE, "the ROM started the system: P %ld, failed %ld at line %ld", (long)b.p,
(long)b.mem[ROM_FAILED], (long)b.mem[ROM_ERRLINE] + 1);
printf(" the ROM assembled the system: %lu instructions, %lu instruction words, %.2f s (%d-bit cells)\n",
(unsigned long)rom_es.anticlock, (unsigned long)words, (double)(t1 - t0) / CLOCKS_PER_SEC, V4_CELL_BITS);
CHECK(b.p == idle_a, "it starts at (IDLE): %ld, mkimage's %ld", (long)b.p, (long)idle_a);
CHECK(b.mem[ROM_IMG] == idle_a && b.mem[ROM_IMG + 1] == v4_text_word(&tx, "(FAULTS)")
&& b.mem[ROM_IMG + 2] == v4_text_word(&tx, "KEY") && b.mem[ROM_IMG + 3] == v4_text_word(&tx, "CR"),
"the image record holds mkimage's addresses");
for (w = 0; w < (v4_cell)V4_NODE_WORDS; w++) {
if (excluded(w) || a.mem[w] == b.mem[w]) continue;
CHECK(0, "RAM word %ld: mkimage %lx, ROM %lx", (long)w, (unsigned long)a.mem[w], (unsigned long)b.mem[w]);
break;
}
CHECK(w == (v4_cell)V4_NODE_WORDS, "every word of RAM is the same");
printf("test_host_rom_nucleus: %d checks, %d failures\n", checks, failures);
return failures != 0;
}