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:
rajames
2026-10-15 11:25:24 -04:00
co-authored by Claude Opus 5.5
parent 42f8a13914
commit 32753f5479
7 changed files with 165 additions and 11 deletions
+13 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}