Files
LithosAnanake/v4/tests/test_port.c
T
rajamesandClaude Opus 5.5 9af442f793 feat(v4.0.0): nodes that talk -- ports, a node born empty, and the fabric
MESH.md step 1, in the engine, which knows nothing of StarForth or of any
kernel.

- node: V4_PORTS ports (8), a build parameter; "any port" and the port the
  last such read came from; a read blocks until the neighbour writes, as a
  write blocks until the neighbour reads; v4_node_born: empty, P at "any
  port"
- exec: a fetch from a port -- @ @b @+ @p, or of an instruction word when P
  is a port -- waits for a word; a node executes what arrives at a port
  without advancing P; a blocked node goes on from the slot it stopped at
- fabric: the nodes there are and the table of how their ports are wired,
  both changed while the nodes run; devices on a port; asleep and awake; a
  step is every unblocked node executing one instruction word, then every
  write with a reader waiting being handed over
- DECOMPOSITION.md section 6: four named ports withdrawn for V4_PORTS
  numbered ones and wiring as data, as ruled

Verified: tests/test_fabric.c, 53 checks at both widths: two nodes exchange
words; an empty node is filled through its port by a device, and by another
node, and runs what it was sent; a word is passed on by a node in between;
a waiting node executes nothing; the wiring is changed while they run; a
node is put to sleep, woken and removed while looping; a node is born while
others run; the fabric is given more room.  make -C v4 test and make -C v4
sanitize pass.  The single-node products are unchanged: hosted-check on
three ISAs, and clean qemu with STARFORTH_V4=1 on amd64, aarch64 and
riscv64 with lines typed at each prompt (logs/20261006-074907, -075150,
-075532).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 07:58:00 -04:00

143 lines
5.5 KiB
C

/* test_port.c -- the node's port: a write blocks the node until its
* neighbour has served it (node.h; DECOMPOSITION.md section 6).
*
* At the level of opcodes: no word of the vocabulary is defined here.
*/
#include "v4/asm.h"
#include "v4/testcode.h"
#include <stdio.h>
#include <string.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)
/* The memory map is open (D-4); the test chooses the address. */
#define PORT ((v4_cell)(V4_NODE_WORDS - 16u))
#define MARK ((v4_cell)0x1234)
#define HOME ((v4_cell)8) /* where a word returns to when the test called it */
static v4_node n;
static v4_exec_state es;
static v4_heat h;
static v4_asm as;
#define O(name) v4_asm_op(&as, V4_OP_##name)
#define LIT(v) v4_asm_lit(&as, (v4_cell)(v))
static void start(v4_cell word)
{
v4_dstack_reset(&n.ds);
v4_rstack_reset(&n.rs);
v4_exec_reset(&es);
v4_heat_reset(&h);
v4_rstack_push(&n.rs, HOME);
n.p = word;
}
/* Run until the node is blocked, is back at HOME, or `max` words. */
static unsigned run(unsigned max)
{
unsigned words = 0;
while (words < max && !n.asking && n.p != HOME) { (void)v4_exec_step_word(&n, &es, &h); words++; }
return words;
}
int main(void)
{
v4_cell w_ask, w_mid, w_two;
v4_uheat_t clock;
printf("v4 port tests: V4_CELL_BITS=%d\n", V4_CELL_BITS);
v4_asm_begin(&as, &n, 16);
/* what KERNEL-WORD lays down: the request, the port, b! !b ; */
w_ask = v4_asm_label(&as);
LIT(7); LIT(PORT); O(BANG_B); O(STORE_B); O(SEMI);
/* a write with more of the instruction word after it: dup + */
w_mid = v4_asm_label(&as);
LIT(5); LIT(PORT); O(BANG_B); O(STORE_B); O(DUP); O(ADD); O(SEMI);
/* two writes one after the other */
w_two = v4_asm_label(&as);
LIT(PORT); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); O(SEMI);
CHECK(v4_asm_ok(&as), "the test words assemble");
/* ---- no port: the address is memory ---- */
v4_node_reset(&n);
v4_asm_begin(&as, &n, 16);
w_ask = v4_asm_label(&as);
LIT(7); LIT(PORT); O(BANG_B); O(STORE_B); O(SEMI);
w_mid = v4_asm_label(&as);
LIT(5); LIT(PORT); O(BANG_B); O(STORE_B); O(DUP); O(ADD); O(SEMI);
w_two = v4_asm_label(&as);
LIT(PORT); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); O(SEMI);
CHECK(n.port == -1 && !n.asking, "a node has no port until one is attached");
n.mem[PORT] = MARK;
start(w_ask);
(void)run(100);
CHECK(n.p == HOME && !n.asking && n.mem[PORT] == 7, "with none, a store to the address writes memory and the word returns");
/* ---- the write blocks ---- */
v4_node_port_attach(&n, PORT);
n.mem[PORT] = MARK;
start(w_ask);
v4_dstack_push(&n.ds, 11);
v4_dstack_push(&n.ds, 22);
(void)run(100);
CHECK(n.asking && n.request == 7, "a store to the port blocks the node, with what it wrote: %ld", (long)n.request);
CHECK(n.mem[PORT] == MARK, "and does not write memory");
CHECK(n.p != HOME, "the word has not returned");
CHECK(n.ds.depth == 2, "what was on the data stack is there for its neighbour");
clock = es.anticlock;
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && v4_exec_step_word(&n, &es, &h) == 0, "a blocked node executes nothing");
CHECK(es.anticlock == clock && n.asking, "however often it is stepped: the anti-clock does not move");
/* the neighbour takes two values and leaves one, then says it is served */
{
v4_cell b = v4_dstack_pop(&n.ds), a = v4_dstack_pop(&n.ds);
v4_dstack_push(&n.ds, a + b);
}
v4_node_port_served(&n);
(void)run(100);
CHECK(n.p == HOME && !n.asking, "served, the word goes on and returns");
CHECK(n.ds.depth == 1 && v4_dstack_pop(&n.ds) == 33, "with what its neighbour left on the stack");
/* ---- the rest of the instruction word waits ---- */
start(w_mid);
v4_dstack_push(&n.ds, 100); /* to be replaced while the node is blocked */
(void)run(100);
CHECK(n.asking && n.request == 5, "blocked in the middle of an instruction word");
(void)v4_dstack_pop(&n.ds);
v4_dstack_push(&n.ds, 21); /* the neighbour's result */
v4_node_port_served(&n);
(void)run(100);
CHECK(n.p == HOME && n.ds.depth == 1 && v4_dstack_pop(&n.ds) == 42,
"the opcodes after the store run when it is served, on what the neighbour left: dup + of 21");
/* ---- one write after another ---- */
start(w_two);
(void)run(100);
CHECK(n.asking && n.request == 1, "the first write blocks");
v4_node_port_served(&n);
(void)run(100);
CHECK(n.asking && n.request == 2, "served, the node runs to the second and blocks again");
v4_node_port_served(&n);
(void)run(100);
CHECK(n.p == HOME && !n.asking, "and returns when that is served");
/* ---- a fault while blocked abandons the rest of the word ---- */
start(w_mid);
v4_dstack_push(&n.ds, 1);
(void)run(100);
CHECK(n.asking, "blocked again");
v4_node_port_served(&n);
v4_node_fault(&n, V4_FAULT_RAISED, 12); /* no handler attached: the node stops */
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && n.stopped, "a node its neighbour faults does not go on with the word");
/* ---- detaching ---- */
v4_node_reset(&n);
CHECK(n.port == -1 && !n.asking, "reset detaches the port");
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}