feat(v4): the machine has a ROM -- read and run, never written
A read-only range of V4_ROM_WORDS words at V4_ROM_BASE, above RAM (MACHINE.md 2, DATASHEET-F18.md 5). Instruction fetch, @p and data fetches read it; a running programme's store there is an address fault. Attached by the owner like every device; a reset detaches it. The host node's range is 4096 words. text.c places branches by the whole address space. The nucleus image is byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
42f8a13914
commit
32753f5479
@@ -98,7 +98,7 @@ registers (§10.3).
|
||||
the console receive addresses, where a store goes to RAM.
|
||||
- **ROM.** Designed, not built: `MACHINE.md` §2, and plan Task 1. It is a read-only range at
|
||||
`V4_NODE_WORDS` and above. It can be fetched and executed. A store there is an address fault.
|
||||
A reset keeps it.
|
||||
In the model it is attached by the owner, as every device is, and a reset detaches it.
|
||||
- Its routines are shared code, GA144-style (`MACHINE.md` 2.1). Payloads call them through
|
||||
the entry table at the ROM's base. Their state lives in a reserved RAM area. This sheet will
|
||||
list each entry's stack effect and the registers it uses once the ROM is built.
|
||||
|
||||
+6
-2
@@ -47,7 +47,11 @@ BINDIR := $(HERE)/build
|
||||
HOST_WORDS := 16384
|
||||
HOST_DATA_RING := 30
|
||||
HOST_RET_RING := 31
|
||||
node_size = $(if $(findstring /test_host_,$(1)),-DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING))
|
||||
# The host node's ROM range (docs/v4.0.0/MACHINE.md section 2): words above
|
||||
# RAM that the machine can fetch and run but never write. Its size is
|
||||
# whatever the ROM takes; this is the range it may use.
|
||||
HOST_ROM_WORDS := 4096
|
||||
node_size = $(if $(findstring /test_host_,$(1)),-DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING) -DV4_ROM_WORDS=$(HOST_ROM_WORDS))
|
||||
|
||||
# BLOCKS (docs/v4.0.0/MESH.md section 8). A node asks its kernel for a
|
||||
# block, and system/blocks.c serves it from the kernel's block subsystem,
|
||||
@@ -97,7 +101,7 @@ capsule_dir := -DV4_CAPSULE_DIR='"$(HERE)/capsule"'
|
||||
ROOT := $(abspath $(HERE)/..)
|
||||
NUCLEUS_64 := $(ROOT)/capsules/v4/nucleus-64.f18
|
||||
NUCLEUS_32 := $(BINDIR)/nucleus-32.f18
|
||||
HOST_DEFS := -DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING)
|
||||
HOST_DEFS := -DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING) -DV4_ROM_WORDS=$(HOST_ROM_WORDS)
|
||||
ENGINE_SRCS := $(addprefix $(HERE)/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c wire.c)
|
||||
|
||||
define IMAGE_RULE
|
||||
|
||||
@@ -57,6 +57,18 @@
|
||||
#define V4_NODE_WORDS 1024u
|
||||
#endif
|
||||
|
||||
/* THE ROM (docs/v4.0.0/MACHINE.md section 2, DATASHEET-F18.md section 5).
|
||||
* A read-only range of V4_ROM_WORDS words at V4_ROM_BASE, just above RAM.
|
||||
* An instruction fetch, an @p literal and a data fetch there read the ROM; a
|
||||
* running programme's store there is an address fault. In the model it is
|
||||
* attached, as every device is, and v4_node_reset detaches it: the owner
|
||||
* attaches it again after a reset. The range's size is a build parameter,
|
||||
* as RAM's is; 0 (the default) is a machine with no ROM. */
|
||||
#ifndef V4_ROM_WORDS
|
||||
#define V4_ROM_WORDS 0u
|
||||
#endif
|
||||
#define V4_ROM_BASE ((v4_cell)V4_NODE_WORDS)
|
||||
|
||||
/* 5% safety boundary at each end of node memory, per guard.h. Note the
|
||||
* arithmetic: at 1024 words that is 52 words either side, 5.2% of memory
|
||||
* spent on a boundary. That is the cost of the convention on a resource that
|
||||
@@ -83,6 +95,11 @@ typedef struct {
|
||||
v4_cell mem[V4_NODE_WORDS];
|
||||
v4_cell mem_guard_tail[V4_MEM_BOUND];
|
||||
|
||||
/* The ROM: its words, and how many there are (0: none). See
|
||||
* v4_node_rom_attach. */
|
||||
const v4_cell *rom;
|
||||
unsigned rom_words;
|
||||
|
||||
/* CONSOLE-TX capture (DECOMPOSITION.md section 7). See
|
||||
* v4_node_console_attach below. */
|
||||
v4_cell console_tx; /* its word address, or -1: no console */
|
||||
@@ -334,6 +351,14 @@ v4_cell v4_node_fetch(v4_node *n, v4_cell addr);
|
||||
/* 1 if `addr` is a word of node memory. */
|
||||
int v4_node_addr_ok(v4_cell addr);
|
||||
|
||||
/* 1 if `addr` can be fetched from: a word of node memory, or of the ROM. */
|
||||
int v4_node_addr_readable(const v4_node *n, v4_cell addr);
|
||||
|
||||
/* Give the machine its ROM: `words` words at V4_ROM_BASE, read from `rom`,
|
||||
* which must outlive the node. More than V4_ROM_WORDS words, or a null
|
||||
* `rom`, attaches none. v4_node_reset detaches it. */
|
||||
void v4_node_rom_attach(v4_node *n, const v4_cell *rom, unsigned words);
|
||||
|
||||
/* FAULTS (DECOMPOSITION.md D-14 and D-16, ruled 2026-10-04: an address
|
||||
* outside the node's memory, and a stack pushed when full or popped when
|
||||
* empty, are guarded; an error is shown; the node returns to its prompt).
|
||||
|
||||
+13
-5
@@ -43,6 +43,14 @@ static int faulted(v4_node *n, v4_cell addr)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The same for a fetch, which may also read the ROM (node.h). */
|
||||
static int faulted_read(v4_node *n, v4_cell addr)
|
||||
{
|
||||
if (v4_node_addr_readable(n, addr)) return 0;
|
||||
v4_node_fault(n, V4_FAULT_ADDRESS, addr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* D-16: what each opcode takes from the stacks and leaves on them. If the
|
||||
* data stack holds fewer than `dneed`, or the return stack fewer than
|
||||
* `rneed`, or a stack the opcode leaves one more on is full, it is a fault
|
||||
@@ -142,7 +150,7 @@ void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h,
|
||||
break;
|
||||
|
||||
case V4_OP_FETCH_P:
|
||||
if (faulted(n, n->p)) return;
|
||||
if (faulted_read(n, n->p)) return;
|
||||
if (v4_node_port_index(n, n->p) >= 0) {
|
||||
/* P is a port: the literal is the next word its neighbour
|
||||
* writes, and P stays where it is */
|
||||
@@ -154,18 +162,18 @@ void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h,
|
||||
n->p = add_wrap(n->p, 1);
|
||||
break;
|
||||
case V4_OP_FETCH_INC:
|
||||
if (faulted(n, n->a)) return;
|
||||
if (faulted_read(n, n->a)) return;
|
||||
if (!v4_node_read_ready(n, n->a)) return; /* a port with nothing written to it yet */
|
||||
v4_dstack_push(ds, v4_node_fetch(n, n->a));
|
||||
n->a = add_wrap(n->a, 1);
|
||||
break;
|
||||
case V4_OP_FETCH_B:
|
||||
if (faulted(n, n->b)) return;
|
||||
if (faulted_read(n, n->b)) return;
|
||||
if (!v4_node_read_ready(n, n->b)) return; /* a port with nothing written to it yet */
|
||||
v4_dstack_push(ds, v4_node_fetch(n, n->b));
|
||||
break;
|
||||
case V4_OP_FETCH_A:
|
||||
if (faulted(n, n->a)) return;
|
||||
if (faulted_read(n, n->a)) return;
|
||||
if (!v4_node_read_ready(n, n->a)) return; /* a port with nothing written to it yet */
|
||||
v4_dstack_push(ds, v4_node_fetch(n, n->a));
|
||||
break;
|
||||
@@ -269,7 +277,7 @@ unsigned v4_exec_step_word(v4_node *n, v4_exec_state *es, v4_heat *h)
|
||||
slot = es->resume_slot;
|
||||
} else {
|
||||
/* P itself may be what is outside memory */
|
||||
if (faulted(n, n->p)) return 0;
|
||||
if (faulted_read(n, n->p)) return 0;
|
||||
if (v4_node_port_index(n, n->p) >= 0) {
|
||||
/* P is a port: the node executes what its neighbour writes
|
||||
* there, and P stays where it is */
|
||||
|
||||
+18
-2
@@ -4,6 +4,8 @@
|
||||
|
||||
void v4_node_reset(v4_node *n)
|
||||
{
|
||||
n->rom = NULL; /* attached again by the owner, as every device is */
|
||||
n->rom_words = 0;
|
||||
n->p = 0;
|
||||
n->a = 0;
|
||||
n->b = 0;
|
||||
@@ -66,10 +68,24 @@ int v4_node_guards_intact(const v4_node *n)
|
||||
&& v4_rstack_guards_intact(&n->rs);
|
||||
}
|
||||
|
||||
int v4_node_addr_readable(const v4_node *n, v4_cell addr)
|
||||
{
|
||||
return v4_node_addr_ok(addr)
|
||||
|| ((v4_ucell)addr - (v4_ucell)V4_ROM_BASE) < (v4_ucell)n->rom_words;
|
||||
}
|
||||
|
||||
void v4_node_rom_attach(v4_node *n, const v4_cell *rom, unsigned words)
|
||||
{
|
||||
if (!rom || words > V4_ROM_WORDS) { n->rom = NULL; n->rom_words = 0; return; }
|
||||
n->rom = rom;
|
||||
n->rom_words = words;
|
||||
}
|
||||
|
||||
v4_cell v4_node_load(const v4_node *n, v4_cell addr)
|
||||
{
|
||||
if (!v4_node_addr_ok(addr)) return 0;
|
||||
return n->mem[(unsigned)addr];
|
||||
if (v4_node_addr_ok(addr)) return n->mem[(unsigned)addr];
|
||||
if (v4_node_addr_readable(n, addr)) return n->rom[(v4_ucell)addr - (v4_ucell)V4_ROM_BASE];
|
||||
return 0;
|
||||
}
|
||||
|
||||
void v4_node_store(v4_node *n, v4_cell addr, v4_cell value)
|
||||
|
||||
+1
-1
@@ -216,7 +216,7 @@ static void place_branch(v4_text *t)
|
||||
v4_asm *as = &t->as;
|
||||
if (as->slot == 0) return;
|
||||
if (!v4_iword_branch_legal(as->slot)
|
||||
|| (v4_ucell)v4_iword_slot_mask(as->slot) < (v4_ucell)(V4_NODE_WORDS - 1u))
|
||||
|| (v4_ucell)v4_iword_slot_mask(as->slot) < (v4_ucell)(V4_NODE_WORDS + V4_ROM_WORDS - 1u))
|
||||
(void)v4_asm_label(as);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/* test_host_rom_range.c -- the ROM is the machine's: read and run, never written.
|
||||
*
|
||||
* docs/v4.0.0/MACHINE.md section 2, DATASHEET-F18.md section 5. The ROM is a
|
||||
* read-only range at V4_ROM_BASE, above RAM. An instruction fetch, an @p
|
||||
* literal and a data fetch there read the ROM; a running programme's store
|
||||
* there is an address fault. Like every device in the model it is attached
|
||||
* by the node's owner, and a reset detaches it.
|
||||
*/
|
||||
#include "v4/asm.h"
|
||||
#include "v4/testcode.h"
|
||||
#include <stdio.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 n, scratch;
|
||||
static v4_exec_state es;
|
||||
static v4_heat h;
|
||||
static v4_cell rom[16];
|
||||
|
||||
/* ROM words 0..: "7 ;" at ROM+0, then a cell holding 42 at ROM+8 */
|
||||
static void build_rom(void)
|
||||
{
|
||||
v4_asm ra;
|
||||
unsigned i;
|
||||
v4_node_reset(&scratch);
|
||||
v4_asm_begin(&ra, &scratch, 0);
|
||||
v4_asm_lit(&ra, 7);
|
||||
v4_asm_op(&ra, V4_OP_SEMI);
|
||||
CHECK(v4_asm_ok(&ra), "the ROM's code assembles");
|
||||
for (i = 0; i < 16; i++) rom[i] = scratch.mem[i];
|
||||
rom[8] = 42;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
v4_asm as;
|
||||
long r;
|
||||
|
||||
build_rom();
|
||||
v4_node_reset(&n);
|
||||
v4_exec_reset(&es);
|
||||
v4_heat_reset(&h);
|
||||
CHECK(V4_ROM_BASE == (v4_cell)V4_NODE_WORDS, "the ROM starts where RAM ends");
|
||||
CHECK(v4_node_load(&n, V4_ROM_BASE) == 0, "no ROM attached: nothing there");
|
||||
v4_node_rom_attach(&n, rom, 16);
|
||||
CHECK(v4_node_load(&n, V4_ROM_BASE) == rom[0], "the ROM reads");
|
||||
CHECK(v4_node_load(&n, V4_ROM_BASE + 16) == 0, "past the ROM's words: nothing");
|
||||
|
||||
/* RAM 16: call the ROM's word, then return */
|
||||
v4_asm_begin(&as, &n, 16);
|
||||
v4_asm_branch(&as, V4_OP_CALL, V4_ROM_BASE);
|
||||
v4_asm_op(&as, V4_OP_SEMI);
|
||||
CHECK(v4_asm_ok(&as), "a call into the ROM assembles");
|
||||
r = v4_test_call(&n, &es, &h, 16, 100);
|
||||
CHECK(r > 0, "the call returns");
|
||||
CHECK(n.ds.depth == 1 && n.ds.t == 7, "code in the ROM runs: depth %u T %ld", n.ds.depth, (long)n.ds.t);
|
||||
|
||||
/* RAM 32: fetch the ROM's cell 8 through A */
|
||||
v4_dstack_reset(&n.ds);
|
||||
v4_asm_begin(&as, &n, 32);
|
||||
v4_asm_lit(&as, V4_ROM_BASE + 8);
|
||||
v4_asm_op(&as, V4_OP_BANG_A);
|
||||
v4_asm_op(&as, V4_OP_FETCH_A);
|
||||
v4_asm_op(&as, V4_OP_SEMI);
|
||||
CHECK(v4_asm_ok(&as), "a fetch from the ROM assembles");
|
||||
r = v4_test_call(&n, &es, &h, 32, 100);
|
||||
CHECK(r > 0 && n.ds.depth == 1 && n.ds.t == 42, "a data fetch reads the ROM: T %ld", (long)n.ds.t);
|
||||
|
||||
/* RAM 48: store 99 into the ROM's cell 8 through B */
|
||||
v4_dstack_reset(&n.ds);
|
||||
v4_asm_begin(&as, &n, 48);
|
||||
v4_asm_lit(&as, V4_ROM_BASE + 8);
|
||||
v4_asm_op(&as, V4_OP_BANG_B);
|
||||
v4_asm_lit(&as, 99);
|
||||
v4_asm_op(&as, V4_OP_STORE_B);
|
||||
v4_asm_op(&as, V4_OP_SEMI);
|
||||
CHECK(v4_asm_ok(&as), "a store into the ROM assembles");
|
||||
r = v4_test_call(&n, &es, &h, 48, 100);
|
||||
CHECK(r < 0, "a store into the ROM does not return");
|
||||
CHECK(n.fault_kind == V4_FAULT_ADDRESS && n.fault_addr == V4_ROM_BASE + 8,
|
||||
"it is an address fault at the ROM's address: kind %u addr %ld", n.fault_kind, (long)n.fault_addr);
|
||||
CHECK(v4_node_load(&n, V4_ROM_BASE + 8) == 42, "and the ROM is unchanged");
|
||||
|
||||
/* C code's store does nothing there either */
|
||||
v4_node_store(&n, V4_ROM_BASE + 8, 99);
|
||||
CHECK(v4_node_load(&n, V4_ROM_BASE + 8) == 42, "v4_node_store leaves the ROM alone");
|
||||
|
||||
/* a reset detaches it, as it does every device */
|
||||
v4_node_reset(&n);
|
||||
CHECK(v4_node_load(&n, V4_ROM_BASE) == 0, "a reset detaches the ROM");
|
||||
v4_node_rom_attach(&n, rom, 16);
|
||||
CHECK(v4_node_load(&n, V4_ROM_BASE) == rom[0], "and the owner attaches it again");
|
||||
|
||||
/* more words than the range holds are refused: nothing attached */
|
||||
v4_node_rom_attach(&n, rom, V4_ROM_WORDS + 1u);
|
||||
CHECK(v4_node_load(&n, V4_ROM_BASE) == 0, "a ROM larger than the range is not attached");
|
||||
|
||||
printf("test_host_rom_range: %d checks, %d failures\n", checks, failures);
|
||||
return failures != 0;
|
||||
}
|
||||
Reference in New Issue
Block a user