Files
LithosAnanke/v4/src/node.c
T
rajamesandClaude Opus 5.5 7e3932a4ad feat(v4.0.0): CONSOLE-TX capture register on the single-node model
EMIT is a device service: a character sent to the console node. The
mesh and its ports are development step 2 and the memory map is open
(D-4), so the single-node model now stands in for the console with one
memory-mapped register, so that printing words can be run and their
output compared with v3's.

v4_node_console_attach(n, addr): after it, a store to word address
`addr` appends the low 8 bits of the value to a buffer on the node
(V4_CONSOLE_CAP characters, 4096 by default) and does not write memory;
a load from `addr` reads the memory word as before. Characters past the
capacity are counted in console_dropped and discarded. The hook is in
v4_node_store, which all four store opcodes use.

It is off by default: v4_node_reset detaches the console (address -1)
and empties the capture, so the ISA's behaviour and every existing test
are unchanged unless a console is attached. The address is the
caller's choice.

New test v4/tests/test_console.c, 22 checks per width: direct stores,
!b, ! and !+ through the executor printing "Hi!", memory at and around
the register untouched, the low byte only, overflow, detach, a console
at word 0, and reset. At 32- and 64-bit cells, optimised and ASan+UBSan
(`make test`, `make sanitize`); all other v4 tests still pass. Four
mutations of the hook each fail.

DECOMPOSITION.md section 7 gains the CONSOLE-TX row. No printing word is
defined here; EMIT and the words on it are still to do.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:57:49 -04:00

53 lines
1.6 KiB
C

/* node.c -- the v4 node. See node.h. */
#include "v4/node.h"
void v4_node_reset(v4_node *n)
{
n->p = 0;
n->a = 0;
n->b = 0;
for (unsigned i = 0; i < V4_NODE_WORDS; i++) n->mem[i] = 0;
v4_guard_fill(n->mem_guard_head, V4_MEM_BOUND, V4_GUARD_PATTERN_HEAD);
v4_guard_fill(n->mem_guard_tail, V4_MEM_BOUND, V4_GUARD_PATTERN_TAIL);
v4_dstack_reset(&n->ds);
v4_rstack_reset(&n->rs);
v4_node_console_attach(n, -1);
}
int v4_node_guards_intact(const v4_node *n)
{
return v4_guard_intact(n->mem_guard_head, V4_MEM_BOUND, V4_GUARD_PATTERN_HEAD)
&& v4_guard_intact(n->mem_guard_tail, V4_MEM_BOUND, V4_GUARD_PATTERN_TAIL)
&& v4_dstack_guards_intact(&n->ds)
&& v4_rstack_guards_intact(&n->rs);
}
v4_cell v4_node_load(const v4_node *n, v4_cell addr)
{
/* Precondition checked by the caller; see node.h. The unsigned compare is
* deliberate: `addr` is signed, and a negative address must not wrap into
* a large positive one and read the top of memory instead. */
return n->mem[(unsigned)addr];
}
void v4_node_store(v4_node *n, v4_cell addr, v4_cell value)
{
/* CONSOLE-TX: capture the character, leave memory alone. console_tx is
* -1 when no console is attached, which no valid address equals. */
if (addr == n->console_tx) {
if (n->console_len < V4_CONSOLE_CAP)
n->console[n->console_len++] = (unsigned char)((v4_ucell)value & 0xFFu);
else
n->console_dropped++;
return;
}
n->mem[(unsigned)addr] = value;
}
void v4_node_console_attach(v4_node *n, v4_cell addr)
{
n->console_tx = addr;
n->console_len = 0;
n->console_dropped = 0;
}