From 11b5d402b13a167e7192646f4a08168cd8497afb Mon Sep 17 00:00:00 2001 From: rajames Date: Thu, 15 Oct 2026 19:46:00 -0400 Subject: [PATCH] feat(v4): the shared math in both ROMs, and a mesh node's ROM MACHINE.md 2.2. UM*, UM/MOD, D+, DNEGATE, Q.* and Q./ are entries 0-5 of the host ROM and of a mesh node's ROM, from one source (rom/math.f18) and one entry list (v4/rom_entries.h). The host ROM starts at entry 18. The mesh ROM is 277 words; the host ROM 2372. Co-Authored-By: Claude Opus 5.5 --- capsules/v4/rom/main.f18 | 31 ++++--- capsules/v4/rom/math.f18 | 143 +++++++++++++++++++++++++++++++ capsules/v4/rom/mesh.f18 | 10 +++ v4/Makefile | 18 ++-- v4/include/v4/rom_entries.h | 31 +++++++ v4/system/boot.c | 10 +-- v4/tests/rom_harness.h | 19 ++++- v4/tests/test_host_rom_asm.c | 2 +- v4/tests/test_host_rom_math.c | 156 ++++++++++++++++++++++++++++++++++ v4/tools/mkmap.c | 18 +++- v4/tools/mkrom.c | 31 +++---- 11 files changed, 426 insertions(+), 43 deletions(-) create mode 100644 capsules/v4/rom/math.f18 create mode 100644 capsules/v4/rom/mesh.f18 create mode 100644 v4/include/v4/rom_entries.h create mode 100644 v4/tests/test_host_rom_math.c diff --git a/capsules/v4/rom/main.f18 b/capsules/v4/rom/main.f18 index 461ef719..c44a173f 100644 --- a/capsules/v4/rom/main.f18 +++ b/capsules/v4/rom/main.f18 @@ -4,22 +4,25 @@ \ routines through this table and never to an address inside the ROM, so a \ rebuilt ROM breaks no payload. It is at the ROM's base, one jump a word, \ in a fixed order that only ever grows at its end. Entry i is at the ROM's -\ base plus i. DATASHEET-F18.md lists each routine's stack effect. +\ base plus i. Entries 0-5 are the shared math, the same in a mesh node's +\ ROM (mesh.f18, MACHINE.md 2.2). DATASHEET-F18.md lists each routine's +\ stack effect. : (ENTRIES) - jump (KEY) \ 0 ( -- c ) - jump (TOKEN) \ 1 ( -- len ) - jump (NUMBER) \ 2 ( -- n flag ) - jump (FIND) \ 3 ( -- rec | 0 ) - jump (DEFINE) \ 4 ( value kind -- ) - jump (OPCODE) \ 5 ( -- op | -1 ) - jump (OP) \ 6 ( op -- ) - jump (LIT) \ 7 ( v -- ) - jump (BRANCH) \ 8 ( op target -- ) - jump (BRANCH>) \ 9 ( op -- ref ) - jump (RESOLVE) \ 10 ( ref target -- ) - jump (LABEL) \ 11 ( -- addr ) - jump (ROM) \ 12 the ROM itself: where the machine starts + (MATH-ENTRIES) \ 0-5 the shared math (math.f18; v4/rom_entries.h) + jump (KEY) \ 6 ( -- c ) + jump (TOKEN) \ 7 ( -- len ) + jump (NUMBER) \ 8 ( -- n flag ) + jump (FIND) \ 9 ( -- rec | 0 ) + jump (DEFINE) \ 10 ( value kind -- ) + jump (OPCODE) \ 11 ( -- op | -1 ) + jump (OP) \ 12 ( op -- ) + jump (LIT) \ 13 ( v -- ) + jump (BRANCH) \ 14 ( op target -- ) + jump (BRANCH>) \ 15 ( op -- ref ) + jump (RESOLVE) \ 16 ( ref target -- ) + jump (LABEL) \ 17 ( -- addr ) + jump (ROM) \ 18 the ROM itself: where the machine starts (V4_ROM_START) \ ( -- ) the state the ROM begins from: code goes to ORIGIN, nothing read, \ nothing defined diff --git a/capsules/v4/rom/math.f18 b/capsules/v4/rom/math.f18 new file mode 100644 index 00000000..71df05d0 --- /dev/null +++ b/capsules/v4/rom/math.f18 @@ -0,0 +1,143 @@ +\ math.f18 -- the ROM's shared math (docs/v4.0.0/MACHINE.md 2.2). +\ +\ Six routines a payload calls through the ROM's entry table, as a GA144 +\ application calls its node ROM's multiply and divide steps, and their +\ helpers. Both ROMs carry them: the host node's and a mesh node's. They +\ were the system's (capsules/v4/sys: core.f18, numout.f18, qmath.f18) and +\ do exactly what they did there; the system's words of those names now call +\ these. No names, no headers, no FORTH. +\ +\ Constants from the map (tools/mkmap.c): N-1 NODE-ERROR MSB MAXHI 2N+15 +\ N-18 N-17, and (Q/), Q./'s five scratch cells, fixed in the memory map +\ (MACHINE.md 2.1). +\ +\ ERRORS. Q./ by zero leaves the result D-11 gives and stores 11, "Division +\ by zero", into NODE-ERROR (D-18). + +macro SWAP over push push drop pop pop endmacro +macro - push inv pop + inv endmacro \ x y -- x-y +macro NIP push drop pop endmacro +macro ROT push SWAP pop SWAP endmacro +macro OR over inv and xor endmacro + +\ ( u1 u2 -- ulo uhi ) entry 0 +: (UM*) + over 1 and inv 1 + over 2/ and \ u1 u2 t0 t0 = u2 2/ if u1 odd + over a! N-1 push \ A: u2 R: loop count + push over 2/ pop \ u1 u2 s t0 s = u1 2/ + L: +* unext \ u1 u2 s hi A: lo + push drop pop \ u1 u2 hi + 2* a -if L0 drop 1 + jump L1 L0: drop L1: push + over -if L2 drop dup jump L3 L2: drop 0 L3: push + dup -if L4 drop over 1 and jump L5 L4: drop 0 L5: + pop + pop + push + and 1 and a 2* + pop ; + +\ ( ulo uhi ud -- urem uquot ) entry 1. One step a bit, the divisor in +\ A. Exact for uhi < ud. +: (UM/MOD) + a! N-1 FOR + -if L0 + 2* over -if L1 drop 1 + jump L2 + L1: drop L2: push 2* pop + jump SUB + L0: + 2* over -if L3 drop 1 + jump L4 + L3: drop L4: push 2* pop + dup a xor -if L5 + drop -if NOSUB jump SUB + L5: drop dup inv a + inv -if L6 + drop jump NOSUB + L6: push drop pop jump SETBIT + SUB: inv a + inv + SETBIT: push 1 + pop + NOSUB: + NEXT + over push push drop pop pop ; + +\ ( d1 d2 -- d3 ) entry 2 +: (D+) + push over push push drop pop \ al bl R: bh ah + over over xor -if L1 + drop + -if C1 jump C0 + L1: drop over -if L2 + drop + jump C1 + L2: drop + + C0: pop pop + ; + C1: pop pop + 1 + ; + +\ ( d -- -d ) entry 3 +: (DNEGATE) + inv over if L1 drop push inv 1 + pop ; + L1: drop 1 + ; + +\ ( q -- n ) the system's Q.TO-INT, which Q.* needs +: (Q>INT) push a! 0 pop 15 FOR +* UNEXT drop drop a ; + +\ ( a b -- c ) entry 4 +: (Q.*) + SWAP push over over (UM*) drop + push push over pop + -if L1 SWAP jump L2 L1: SWAP drop 0 L2: + pop SWAP - push + push over pop (UM*) pop + + ROT pop dup push + over -if L3 drop dup jump L4 L3: drop 0 L4: + push (UM*) pop - (D+) + ROT pop (UM*) + SWAP push 0 (D+) pop + push over pop SWAP (Q>INT) push (Q>INT) pop SWAP ; + +: (QERR11) NODE-ERROR b! 11 !b ; + +: (D2*C) ( lo hi cin -- lo' hi' cout ) + a! dup -if L1 drop 1 jump L2 L1: drop 0 L2: push + over -if L3 drop 1 jump L4 L3: drop 0 L4: + over + + + push 2* a + pop pop ; + +: (UQ/) ( a0 a1 b0 b1 -- q0 q1 ) + (Q/)+2 a! SWAP !+ ! (Q/) a! SWAP !+ ! 0 0 0 + 2N+15 FOR + push (Q/) a! @+ @ pop (D2*C) + push (Q/)+1 a! ! (Q/) a! ! pop (D2*C) + if CMP drop jump TAKE + CMP: drop (Q/)+3 b! @b + over over xor if EQH -if SAMEH + drop drop -if NT1 jump TAKE NT1: jump NOTAKE + SAMEH: drop over SWAP push inv pop + inv + -if T2 drop jump NOTAKE T2: drop jump TAKE + EQH: drop drop over (Q/)+2 b! @b + over over xor -if SAMEL + drop drop -if NT3 drop jump TAKEL NT3: drop jump NOTAKEL + SAMEL: drop push inv pop + inv + -if T4 drop jump NOTAKEL T4: drop jump TAKEL + TAKEL: drop (Q/)+2 b! @b push inv pop + inv 0 1 jump END + NOTAKEL: 0 jump END + TAKE: push (Q/)+2 b! @b over over push inv pop + inv push + over over xor -if SB drop NIP -if NB drop 1 jump BD NB: drop 0 jump BD + SB: drop push inv pop + inv -if NB2 drop 1 jump BD NB2: drop 0 + BD: pop pop (Q/)+3 b! @b push inv pop + inv + push SWAP pop SWAP - 1 jump END + NOTAKE: 0 + END: + NEXT + push (Q/) a! @+ @ pop (D2*C) drop push push drop drop pop pop ; + +: (Q./) ( a b -- q ) \ entry 5 + over over OR if ZERO drop + push over pop SWAP over xor (Q/)+4 b! !b + -if L1 (DNEGATE) L1: push push -if L2 (DNEGATE) L2: pop pop + dup if CHK drop jump DIV + CHK: drop over -131072 and if CHK2 drop jump DIV + CHK2: drop push push dup pop dup push N-18 FOR 2* UNEXT + over over xor -if SAME drop drop -if NOOV jump OV + SAME: drop push inv pop + inv -if OV + NOOV: drop pop pop jump DIV + OV: drop drop drop pop pop drop drop + (Q/)+4 b! @b -if OVP drop 0 MSB ; OVP: drop -1 MAXHI ; + DIV: (UQ/) (Q/)+4 b! @b -if L3 drop (DNEGATE) ; L3: drop ; + ZERO: drop drop drop + over over OR if Z0 drop -if ZP drop drop 0 MSB jump (QERR11) + ZP: drop drop -1 MAXHI jump (QERR11) + Z0: drop jump (QERR11) diff --git a/capsules/v4/rom/mesh.f18 b/capsules/v4/rom/mesh.f18 new file mode 100644 index 00000000..6e69de22 --- /dev/null +++ b/capsules/v4/rom/mesh.f18 @@ -0,0 +1,10 @@ +\ mesh.f18 -- a mesh node's ROM: its entry table (docs/v4.0.0/MACHINE.md 2.2). +\ +\ A mesh node's ROM is the shared math (math.f18) and nothing else. It has +\ no start: a mesh node at reset blocks on its ports and executes what +\ arrives (docs/v4.0.0/MESH.md 4.2 and 5). Its entries are the first of +\ the host ROM's, at the same numbers: (MATH-ENTRIES), which tools/mkmap.c +\ writes from v4/include/v4/rom_entries.h. + +: (ENTRIES) + (MATH-ENTRIES) diff --git a/v4/Makefile b/v4/Makefile index 03473f9c..f4092d3a 100644 --- a/v4/Makefile +++ b/v4/Makefile @@ -99,15 +99,18 @@ ENGINE_SRCS := $(addprefix $(HERE)/src/,node.c exec.c stack.c iword.c heat.c gua # code in capsules/v4/rom, assembled off the node by tools/mkrom.c, as a # bitstream is built. tools/mkmap.c writes the memory map's constants it is # assembled with, from tests/host_map.h. main.f18 comes first: the ROM's -# entry table is at its base. Tests named test_host_rom_*.c, and +# entry table is at its base. math.f18, the shared math, is in both: tools/mkrom.c +# builds mesh-rom-.c from mesh.f18 and math.f18 (MACHINE.md 2.2). Tests named test_host_rom_*.c, and # test_host_unit.c, whose host builds the system with it, link the ROM of # their width and read its symbols. ROM_DIR := $(ROOT)/capsules/v4/rom -ROM_FILES := $(wildcard $(addprefix $(ROM_DIR)/,main.f18 in.f18 num.f18 sym.f18 pack.f18 word.f18 dir.f18)) +ROM_FILES := $(wildcard $(addprefix $(ROM_DIR)/,main.f18 in.f18 num.f18 sym.f18 pack.f18 word.f18 dir.f18 math.f18)) +# A mesh node's ROM (MACHINE.md 2.2): its entry table, then the shared math. +MESH_ROM_FILES := $(wildcard $(addprefix $(ROM_DIR)/,mesh.f18 math.f18)) MKROM_WORDS := $(shell expr $(HOST_WORDS) + $(HOST_ROM_WORDS)) uses_rom = $(or $(findstring /test_host_rom_,$(1)),$(findstring /test_host_unit,$(1))) -rom_src = $(if $(call uses_rom,$(1)),$(BINDIR)/rom-$(2).c) -rom_sym = $(if $(call uses_rom,$(1)),-DV4_ROM_SYM='"$(BINDIR)/rom-$(2).sym"') +rom_src = $(if $(call uses_rom,$(1)),$(BINDIR)/rom-$(2).c $(BINDIR)/mesh-rom-$(2).c) +rom_sym = $(if $(call uses_rom,$(1)),-DV4_ROM_SYM='"$(BINDIR)/rom-$(2).sym"' -DV4_MESH_ROM_SYM='"$(BINDIR)/mesh-rom-$(2).sym"') # THE SYSTEM'S SOURCE as the ROM reads it (MACHINE.md 3): capsules/v4/sys. Its # map, map-.f18, is written by tools/mkmap.c from tests/host_map.h # (`make sysmap`) and kept in the tree, as capsules/BLOCK_MAP.md is. @@ -132,12 +135,15 @@ $(BINDIR)/mkrom-$(1): $(HERE)/tools/mkrom.c $$(SRCS) $$(wildcard $(HERE)/include -DROM_ORIGIN=$(HOST_WORDS) -DROM_RANGE=$(HOST_ROM_WORDS) $(HERE)/tools/mkrom.c $$(SRCS) -o $$@ $(BINDIR)/rom-$(1).c $(BINDIR)/rom-$(1).sym: $(BINDIR)/mkrom-$(1) $(BINDIR)/rom-map-$(1).f18 $$(ROM_FILES) - $(BINDIR)/mkrom-$(1) $(BINDIR)/rom-$(1).c $(BINDIR)/rom-$(1).sym $(BINDIR)/rom-map-$(1).f18 $$(ROM_FILES) + $(BINDIR)/mkrom-$(1) rom $(BINDIR)/rom-$(1).c $(BINDIR)/rom-$(1).sym $(BINDIR)/rom-map-$(1).f18 $$(ROM_FILES) + +$(BINDIR)/mesh-rom-$(1).c $(BINDIR)/mesh-rom-$(1).sym: $(BINDIR)/mkrom-$(1) $(BINDIR)/rom-map-$(1).f18 $$(MESH_ROM_FILES) + $(BINDIR)/mkrom-$(1) mesh_rom $(BINDIR)/mesh-rom-$(1).c $(BINDIR)/mesh-rom-$(1).sym $(BINDIR)/rom-map-$(1).f18 $$(MESH_ROM_FILES) endef $(foreach w,$(WIDTHS),$(eval $(call ROM_RULE,$(w)))) .PHONY: rom -rom: $(foreach w,$(WIDTHS),$(BINDIR)/rom-$(w).c) +rom: $(foreach w,$(WIDTHS),$(BINDIR)/rom-$(w).c $(BINDIR)/mesh-rom-$(w).c) .PHONY: sysmap sysmap: $(foreach w,$(WIDTHS),$(SYS_DIR)/map-$(w).f18) diff --git a/v4/include/v4/rom_entries.h b/v4/include/v4/rom_entries.h new file mode 100644 index 00000000..bd7b4efb --- /dev/null +++ b/v4/include/v4/rom_entries.h @@ -0,0 +1,31 @@ +/* rom_entries.h -- the ROMs' shared entries (docs/v4.0.0/MACHINE.md 2.2). + * + * Both ROMs, the host node's and a mesh node's, begin with these entries, in + * this order: entry i is at V4_ROM_BASE + i, a jump to the routine. A + * payload calls an entry, never an address inside the ROM. This is the one + * list: tools/mkmap.c makes of it the (MATH-ENTRIES) macro both ROMs' entry + * tables begin with (capsules/v4/rom/main.f18, mesh.f18) and the constants + * ROM-UM* ... the system's source calls them by (tests/host_map.h). + * + * X(INDEX, ROUTINE, FORTH, EFFECT): ROUTINE is the ROM's name for the code + * (capsules/v4/rom/math.f18), FORTH the system's word that calls it, EFFECT + * its stack effect. An entry is added only at the end. + */ +#ifndef V4_ROM_ENTRIES_H +#define V4_ROM_ENTRIES_H + +#define V4_ROM_MATH_ENTRIES(X) \ + X(0, "(UM*)", "UM*", "( u1 u2 -- ulo uhi )") \ + X(1, "(UM/MOD)", "UM/MOD", "( ulo uhi ud -- urem uquot )") \ + X(2, "(D+)", "D+", "( d1 d2 -- d3 )") \ + X(3, "(DNEGATE)", "DNEGATE", "( d -- -d )") \ + X(4, "(Q.*)", "Q.*", "( a b -- c )") \ + X(5, "(Q./)", "Q./", "( a b -- q )") + +#define V4_ROM_MATH_COUNT 6u + +/* The host ROM's own entries follow: the assembler's twelve routines, then + * (ROM), where the host machine starts (capsules/v4/rom/main.f18). */ +#define V4_ROM_START (V4_ROM_MATH_COUNT + 12u) + +#endif diff --git a/v4/system/boot.c b/v4/system/boot.c index 1319bd86..c5559d16 100644 --- a/v4/system/boot.c +++ b/v4/system/boot.c @@ -6,6 +6,7 @@ * which only a whole system has. */ #include "v4/post.h" +#include "v4/rom_entries.h" #include "v4/blocks.h" #include "v4/boot.h" #include "v4/rom_errors.h" @@ -413,9 +414,9 @@ static int load_capsule(const v4_boot *b, const char *name) /* The ROM (docs/v4.0.0/MACHINE.md section 2): the machine's one binary, * built by v4/tools/mkrom.c for this cell width. The machine starts at its - * entry 12, (ROM) (capsules/v4/rom/main.f18), which reads the system's - * source from the console, assembles it into RAM and, at the source's - * `start`, runs it. */ + * entry V4_ROM_START (v4/rom_entries.h), (ROM) (capsules/v4/rom/main.f18), + * which reads the system's source from the console, assembles it into RAM + * and, at the source's `start`, runs it. */ #if V4_CELL_BITS == 64 extern const v4_cell v4_rom_64[]; extern const unsigned v4_rom_64_words; @@ -427,7 +428,6 @@ extern const unsigned v4_rom_32_words; #define BOOT_ROM v4_rom_32 #define BOOT_ROM_WORDS v4_rom_32_words #endif -#define ROM_START 12u /* the entry where the machine starts */ #define ROM_STEP_LIMIT 400000000ULL /* instruction words the ROM may take: it takes about 16 million */ #define EOT 4 /* the end of the source: the ROM's (KEY) sees no more */ @@ -502,7 +502,7 @@ static int load_system(const v4_boot *b) } v4_node_rom_attach(n, BOOT_ROM, BOOT_ROM_WORDS); - n->p = V4_ROM_BASE + (v4_cell)ROM_START; + n->p = V4_ROM_BASE + (v4_cell)V4_ROM_START; while (n->p >= V4_ROM_BASE) { /* the console is given more of the source whenever it has taken * all it had */ diff --git a/v4/tests/rom_harness.h b/v4/tests/rom_harness.h index f775bbff..16b46e8c 100644 --- a/v4/tests/rom_harness.h +++ b/v4/tests/rom_harness.h @@ -13,6 +13,7 @@ #include "v4/testcode.h" #include "host_map.h" #include "v4/sys_files.h" +#include "v4/rom_entries.h" #include #include #include @@ -29,11 +30,27 @@ extern const unsigned v4_rom_32_words; #define ROM_IMAGE_WORDS v4_rom_32_words #endif +/* A mesh node's ROM (MACHINE.md 2.2): the shared math only. */ +#if V4_CELL_BITS == 64 +extern const v4_cell v4_mesh_rom_64[]; +extern const unsigned v4_mesh_rom_64_words; +#define MESH_ROM v4_mesh_rom_64 +#define MESH_ROM_WORDS v4_mesh_rom_64_words +#else +extern const v4_cell v4_mesh_rom_32[]; +extern const unsigned v4_mesh_rom_32_words; +#define MESH_ROM v4_mesh_rom_32 +#define MESH_ROM_WORDS v4_mesh_rom_32_words +#endif + /* The entry table, in its fixed order: entry i is at V4_ROM_BASE + i. */ +#define ROM_MATH_NAME(i, routine, forth, effect) routine, static const char *const rom_entries[] = { + V4_ROM_MATH_ENTRIES(ROM_MATH_NAME) "(KEY)", "(TOKEN)", "(NUMBER)", "(FIND)", "(DEFINE)", "(OPCODE)", "(OP)", "(LIT)", "(BRANCH)", "(BRANCH>)", "(RESOLVE)", "(LABEL)", "(ROM)" }; +#undef ROM_MATH_NAME #define ROM_ENTRIES (sizeof rom_entries / sizeof rom_entries[0]) static v4_exec_state rom_es; @@ -153,7 +170,7 @@ static int rom_build_system(v4_node *n, long *words) if (!text) return 0; rom_boot(n); v4_node_stack_regs_attach(n, DSTACK_REG, RSTACK_REG); - n->p = V4_ROM_BASE + 12; /* entry 12, (ROM): where the machine starts */ + n->p = V4_ROM_BASE + (v4_cell)V4_ROM_START; /* (ROM): where the machine starts */ while (n->p >= V4_ROM_BASE && w < 400000000L && !n->stopped) { if (fed < total && n->input_pos == n->input_len) fed += v4_node_console_feed(n, text + fed, (unsigned)(total - fed)); (void)v4_exec_step_word(n, &rom_es, &rom_h); diff --git a/v4/tests/test_host_rom_asm.c b/v4/tests/test_host_rom_asm.c index c9f61745..5fc67239 100644 --- a/v4/tests/test_host_rom_asm.c +++ b/v4/tests/test_host_rom_asm.c @@ -69,7 +69,7 @@ static int rom_run(const char *text) failed_lo = lo; failed_hi = hi < 0 ? (long)V4_ROM_BASE + (long)ROM_IMAGE_WORDS : hi; } rom_boot(&b); - b.p = V4_ROM_BASE + 12; + b.p = V4_ROM_BASE + (v4_cell)V4_ROM_START; while (steps++ < 50000000L) { if (fed <= len) fed += v4_node_console_feed(&b, text + fed, (unsigned)(len + 1 - fed)); if (b.p < V4_ROM_BASE) return 1; diff --git a/v4/tests/test_host_rom_math.c b/v4/tests/test_host_rom_math.c new file mode 100644 index 00000000..63ec0e1d --- /dev/null +++ b/v4/tests/test_host_rom_math.c @@ -0,0 +1,156 @@ +/* test_host_rom_math.c -- the ROMs' shared math (docs/v4.0.0/MACHINE.md 2.2). + * + * Both ROMs begin with the same six entries (v4/include/v4/rom_entries.h): + * UM*, UM/MOD, D+, DNEGATE, Q.* and Q./. Each is called through its entry, + * on the host ROM and on the mesh ROM, with the values below, and must give + * what the system's words of those names gave before they moved into the + * ROM. The tables are those words' results as text.c assembled the system + * on 2026-10-15, before the move, at each width. + */ +#include "v4/rom_entries.h" +#include "v4/iword.h" +#include "rom_harness.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) + +#define M ((v4_cell)V4_MSB) /* the most negative cell */ +#define X ((v4_cell)~(v4_ucell)V4_MSB) /* the largest */ +#define ONES (~(v4_ucell)0) +#define CANARY ((v4_cell)0x0C0FFEE5) + +/* entry, arguments (deepest first), results (deepest first), NODE-ERROR after */ +typedef struct { unsigned entry, argc; v4_cell in[4]; unsigned outc; v4_cell out[2]; v4_cell error; } math_case; + +static const math_case cases[] = { + { 0, 2, {0, 0}, 2, {0, 0}, 0 }, + { 0, 2, {1, -1}, 2, {-1, 0}, 0 }, + { 0, 2, {-1, -1}, 2, {1, -2}, 0 }, + { 0, 2, {M, 2}, 2, {0, 1}, 0 }, + { 0, 2, {12345, 67890}, 2, {838102050, 0}, 0 }, + { 0, 2, {-1, M}, 2, {M, X}, 0 }, + { 0, 2, {X, X}, 2, {1, X / 2}, 0 }, + { 1, 3, {0, 0, 1}, 2, {0, 0}, 0 }, + { 1, 3, {100, 0, 7}, 2, {2, 14}, 0 }, + { 1, 3, {-1, -2, -1}, 2, {-2, -1}, 0 }, + { 1, 3, {0, 1, 3}, 2, {1, (v4_cell)(ONES / 3u)}, 0 }, + { 1, 3, {-1, 0, 10}, 2, {5, (v4_cell)(ONES / 10u)}, 0 }, +#if V4_CELL_BITS == 64 + { 1, 3, {12345, 6, 7}, 2, {2, -2635249153387077039LL}, 0 }, +#else + { 1, 3, {12345, 6, 7}, 2, {0, -613564993L}, 0 }, +#endif + { 1, 3, {M, X - 1, X}, 2, {X - 1, -2}, 0 }, + { 1, 3, {-1, X, M}, 2, {X, -1}, 0 }, + { 2, 4, {-1, 0, 1, 0}, 2, {0, 1}, 0 }, + { 2, 4, {-1, -1, 1, 0}, 2, {0, 0}, 0 }, + { 2, 4, {M, 0, M, 0}, 2, {0, 1}, 0 }, + { 2, 4, {5, -1, -6, 0}, 2, {-1, -1}, 0 }, + { 2, 4, {0, X, 0, 1}, 2, {0, M}, 0 }, + { 2, 4, {123, 4, -200, -5}, 2, {-77, -1}, 0 }, + { 3, 2, {0, 0}, 2, {0, 0}, 0 }, + { 3, 2, {1, 0}, 2, {-1, -1}, 0 }, + { 3, 2, {0, 1}, 2, {0, -1}, 0 }, + { 3, 2, {-1, -1}, 2, {1, 0}, 0 }, + { 3, 2, {0, M}, 2, {0, M}, 0 }, + { 3, 2, {5, 0}, 2, {-5, -1}, 0 }, + { 4, 4, {65536, 0, 65536, 0}, 2, {65536, 0}, 0 }, + { 4, 4, {163840, 0, -196608, -1}, 2, {-491520, -1}, 0 }, + { 4, 4, {-65536, -1, -65536, -1}, 2, {65536, 0}, 0 }, + { 4, 4, {32768, 0, 32768, 0}, 2, {16384, 0}, 0 }, + { 4, 4, {0, 0, 65536, 0}, 2, {0, 0}, 0 }, + { 4, 4, {0, 1, 0, 1}, 2, {0, (v4_cell)1 << (V4_CELL_BITS - 16)}, 0 }, + { 4, 4, {-1, X, 131072, 0}, 2, {-2, -1}, 0 }, + { 4, 4, {1, 0, 1, 0}, 2, {0, 0}, 0 }, + { 5, 4, {65536, 0, 196608, 0}, 2, {21845, 0}, 0 }, + { 5, 4, {-491520, -1, 131072, 0}, 2, {-245760, -1}, 0 }, + { 5, 4, {65536, 0, 0, 0}, 2, {-1, X}, 11 }, + { 5, 4, {-65536, -1, 0, 0}, 2, {0, M}, 11 }, + { 5, 4, {0, 0, 0, 0}, 2, {0, 0}, 11 }, + { 5, 4, {0, 1, 1, 0}, 2, {0, 65536}, 0 }, + { 5, 4, {-1, X, 32768, 0}, 2, {-1, X}, 0 }, + { 5, 4, {M, -1, -65536, -1}, 2, {M, 0}, 0 }, + { 5, 4, {1, 0, 65536, 0}, 2, {1, 0}, 0 }, +}; +#define CASES (sizeof cases / sizeof cases[0]) + +static v4_node n; + +/* Call entry c->entry of `rom`. 1 if it returned with the canary under its + * results; got[] the results, deepest first, *err NODE-ERROR. */ +static int run(const v4_cell *rom, unsigned words, const math_case *c, v4_cell *got, v4_cell *err) +{ + unsigned i; + rom_boot(&n); + v4_node_rom_attach(&n, rom, words); + v4_dstack_push(&n.ds, CANARY); + for (i = 0; i < c->argc; i++) v4_dstack_push(&n.ds, c->in[i]); + if (v4_test_call(&n, &rom_es, &rom_h, V4_ROM_BASE + (v4_cell)c->entry, 10000000L) <= 0) return 0; + for (i = c->outc; i-- > 0; ) got[i] = v4_dstack_pop(&n.ds); + *err = n.mem[NODE_ERROR]; + return v4_dstack_pop(&n.ds) == CANARY; +} + +static void cases_on(const char *which, const v4_cell *rom, unsigned words) +{ + unsigned k; + for (k = 0; k < CASES; k++) { + const math_case *c = &cases[k]; + v4_cell got[2] = { 0, 0 }, err = -99; + int ok = run(rom, words, c, got, &err); + CHECK(ok && got[0] == c->out[0] && got[1] == c->out[1] && err == c->error, + "%s ROM, entry %u, case %u: got %lld %lld error %lld, want %lld %lld error %lld", + which, c->entry, k, (long long)got[0], (long long)got[1], (long long)err, + (long long)c->out[0], (long long)c->out[1], (long long)c->error); + } +} + +/* The address of `name` in a ROM's symbol file, or -1. */ +static long sym_addr(const char *path, const char *name) +{ + FILE *f = fopen(path, "r"); + char sym[80]; + long addr, found = -1; + if (!f) { printf(" cannot read %s\n", path); return -1; } + while (fscanf(f, "%79s %ld", sym, &addr) == 2) if (strcmp(sym, name) == 0) found = addr; + fclose(f); + return found; +} + +/* Entry i of `rom` is a jump, in slot 0, to the routine `routine`. */ +static int entry_jumps_to(const v4_cell *rom, unsigned i, const char *sym, const char *routine) +{ + v4_iword w = (v4_iword)((v4_ucell)rom[i] & 0xFFFFFFFFu); + long target = sym_addr(sym, routine); + return target >= 0 && v4_iword_op(w, 0) == V4_OP_JUMP + && v4_iword_branch(V4_ROM_BASE + (v4_cell)i, w, 0) == (v4_cell)target; +} + +int main(void) +{ + printf("v4 ROM math tests: V4_CELL_BITS=%d\n", V4_CELL_BITS); + + /* ---- one entry list, in both ROMs ---- */ + { +#define CHECK_ENTRY(i, routine, forth, effect) \ + CHECK(entry_jumps_to(ROM_IMAGE, i, V4_ROM_SYM, routine), "host ROM entry %d jumps to %s", i, routine); \ + CHECK(entry_jumps_to(MESH_ROM, i, V4_MESH_ROM_SYM, routine), "mesh ROM entry %d jumps to %s", i, routine); + V4_ROM_MATH_ENTRIES(CHECK_ENTRY) +#undef CHECK_ENTRY + } + CHECK(entry_jumps_to(ROM_IMAGE, V4_ROM_START, V4_ROM_SYM, "(ROM)"), "the host ROM starts at entry %u, (ROM)", (unsigned)V4_ROM_START); + CHECK(entry_jumps_to(ROM_IMAGE, V4_ROM_MATH_COUNT, V4_ROM_SYM, "(KEY)"), "the assembler's entries follow the math's"); + + /* ---- the mesh ROM is small and holds nothing of the assembler ---- */ + CHECK(MESH_ROM_WORDS < 300u, "the mesh ROM is small: %u words", MESH_ROM_WORDS); + CHECK(sym_addr(V4_MESH_ROM_SYM, "(TOKEN)") < 0 && sym_addr(V4_MESH_ROM_SYM, "(KEY)") < 0 && sym_addr(V4_MESH_ROM_SYM, "(ROM)") < 0, + "the mesh ROM has none of the assembler's routines"); + printf(" the host ROM: %u words; the mesh ROM: %u words\n", ROM_IMAGE_WORDS, MESH_ROM_WORDS); + + /* ---- each entry, on each ROM, gives what the system's words gave ---- */ + cases_on("host", ROM_IMAGE, ROM_IMAGE_WORDS); + cases_on("mesh", MESH_ROM, MESH_ROM_WORDS); + + printf(" %d checks, %d failures\n", checks, failures); + return failures != 0; +} diff --git a/v4/tools/mkmap.c b/v4/tools/mkmap.c index 315d97ad..82231ed6 100644 --- a/v4/tools/mkmap.c +++ b/v4/tools/mkmap.c @@ -8,10 +8,12 @@ * host stack sizes) at one cell width. It writes, as `constant` lines in * the text assembler's notation, the addresses and codes of * v4/tests/host_map.h that the ROM's source (capsules/v4/rom) uses: the - * console's receive registers and the ROM's state area (MACHINE.md 2.1). + * console's receive registers, the ROM's state area (MACHINE.md 2.1), the + * math's constants and its shared entries (MACHINE.md 2.2). * The map has one source, host_map.h; this is how the ROM reads it. */ #include "../tests/host_map.h" +#include "../include/v4/rom_entries.h" #include static void constant(const char *name, long value) @@ -147,5 +149,19 @@ int main(int argc, char **argv) constant("DEC-LIMIT", (long)(v4_cell)(~(v4_ucell)0 / 10u)); constant("DEC-NLAST", -(long)(~(v4_ucell)0 % 10u) - 1); constant("HEX-HIGH", (long)(v4_cell)~(~(v4_ucell)0 >> 4)); + /* the math's constants (capsules/v4/rom/math.f18) */ + constant("N-1", (long)(V4_CELL_BITS - 1)); + constant("NODE-ERROR", (long)NODE_ERROR); + constant("(Q/)", (long)QBASE); + constant("MAXHI", (long)(v4_cell)(V4_MSB - 1u)); + constant("2N+15", (long)(2 * V4_CELL_BITS + 15)); + constant("N-18", (long)(V4_CELL_BITS - 18)); + constant("N-17", (long)(V4_CELL_BITS - 17)); + /* the shared entries, the start of both ROMs' tables (v4/rom_entries.h) */ + printf("macro (MATH-ENTRIES)"); +#define MATH_ENTRY_JUMP(i, routine, forth, effect) printf(" jump %s", routine); + V4_ROM_MATH_ENTRIES(MATH_ENTRY_JUMP) +#undef MATH_ENTRY_JUMP + printf(" endmacro\n"); return 0; } diff --git a/v4/tools/mkrom.c b/v4/tools/mkrom.c index 415bfa69..5bf0875c 100644 --- a/v4/tools/mkrom.c +++ b/v4/tools/mkrom.c @@ -1,13 +1,14 @@ /* mkrom.c -- build the ROM, off the node, as a bitstream is built. * - * mkrom OUT.c OUT.sym MAP.f18 SOURCE.f18... + * mkrom NAME OUT.c OUT.sym MAP.f18 SOURCE.f18... * * docs/v4.0.0/MACHINE.md section 2. The ROM is F18 code, written in the * text assembler's notation (include/v4/text.h) in capsules/v4/rom, and this * is the only thing built off the node. It assembles MAP.f18 (the memory * map's constants, from tools/mkmap.c) and then each SOURCE in the order * given, at the ROM's base address, and writes: - * OUT.c the ROM: const v4_cell v4_rom_[] and its length + * OUT.c the ROM: const v4_cell v4__[] and its length (NAME + * rom: the host node's ROM; mesh_rom: a mesh node's, MACHINE.md 2.2) * OUT.sym every word of it, "NAME ADDRESS" a line, for the tests * * This tool is built with a node whose RAM reaches over the ROM's range @@ -56,37 +57,37 @@ int main(int argc, char **argv) int i; unsigned s; - if (argc < 5) die("usage: mkrom OUT.c OUT.sym MAP.f18 SOURCE.f18...", NULL); + if (argc < 6) die("usage: mkrom NAME OUT.c OUT.sym MAP.f18 SOURCE.f18...", NULL); if ((v4_ucell)V4_NODE_WORDS != (v4_ucell)ROM_ORIGIN + (v4_ucell)ROM_RANGE) die("built with the wrong node size: RAM must reach over the ROM's range", NULL); v4_node_reset(&n); v4_text_begin(&tx, &n, ROM_ORIGIN); - for (i = 3; i < argc; i++) + for (i = 4; i < argc; i++) if (!v4_text_assemble_file(&tx, argv[i])) die("does not assemble", v4_text_error(&tx)); if (!v4_text_finish(&tx)) die("not everything is defined", v4_text_error(&tx)); here = v4_text_here(&tx); if (here - (v4_cell)ROM_ORIGIN > (v4_cell)ROM_RANGE) die("the ROM is larger than its range", NULL); - out = fopen(argv[1], "w"); - if (!out) die("cannot write", argv[1]); + out = fopen(argv[2], "w"); + if (!out) die("cannot write", argv[2]); fprintf(out, "/* Generated by v4/tools/mkrom.c from capsules/v4/rom -- do not edit. */\n#include \"v4/cell.h\"\n\n"); - fprintf(out, "const v4_cell v4_rom_%d[] = {\n", V4_CELL_BITS); + fprintf(out, "const v4_cell v4_%s_%d[] = {\n", argv[1], V4_CELL_BITS); for (a = ROM_ORIGIN; a < here; a++) { fprintf(out, " "); put_cell(out, n.mem[a]); fprintf(out, ",\n"); } if (here == ROM_ORIGIN) fprintf(out, " 0\n"); - fprintf(out, "};\nconst unsigned v4_rom_%d_words = %ldu;\n", V4_CELL_BITS, (long)(here - (v4_cell)ROM_ORIGIN)); - if (fclose(out) != 0) die("cannot write", argv[1]); - - out = fopen(argv[2], "w"); - if (!out) die("cannot write", argv[2]); - for (s = 0; s < tx.nsym; s++) - if (tx.sym[s].kind == 1) fprintf(out, "%s %ld\n", tx.pool + tx.sym[s].name, (long)tx.sym[s].value); + fprintf(out, "};\nconst unsigned v4_%s_%d_words = %ldu;\n", argv[1], V4_CELL_BITS, (long)(here - (v4_cell)ROM_ORIGIN)); if (fclose(out) != 0) die("cannot write", argv[2]); - fprintf(stderr, "mkrom: a ROM of %ld words, %d-bit cells -> %s\n", (long)(here - (v4_cell)ROM_ORIGIN), V4_CELL_BITS, argv[1]); + out = fopen(argv[3], "w"); + if (!out) die("cannot write", argv[3]); + for (s = 0; s < tx.nsym; s++) + if (tx.sym[s].kind == 1) fprintf(out, "%s %ld\n", tx.pool + tx.sym[s].name, (long)tx.sym[s].value); + if (fclose(out) != 0) die("cannot write", argv[3]); + + fprintf(stderr, "mkrom: %s, %ld words, %d-bit cells -> %s\n", argv[1], (long)(here - (v4_cell)ROM_ORIGIN), V4_CELL_BITS, argv[2]); return 0; }