Ruled 2026-10-08. The engine, the nucleus and their tests are as they
were at dfabfa46, before step 6d; the code of the wait and its tests are
in history at 075385ab..2cf37aab. The limit of MESH.md 7b.7 stands: a
node that is stuck can hold up its neighbours until Hera kills it.
MESH.md 7c is kept as the record of the design as approved, and 7c.6 says
what was built, what the two reviews found, the cause the findings share
-- a node's message machinery runs on the stacks its text is using, and a
fault abandons whatever was in progress -- and what a second attempt
must settle before anything is built.
Kept from it, in hosted-check: a line that leaves 29 values on the stack,
and WORDS with 26 values on it. Both hung the products at some commit of
the wait.
make -C v4 test and sanitize at both widths, hosted-check, lint; three
bare-metal boots typing both. POST 538 of 538, word_count=317,
dict_hash=0xc0769523a47b7dc3 on all six: the hash of step 6c's code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
429 lines
23 KiB
C
429 lines
23 KiB
C
/* test_fabric.c -- nodes that talk: ports that block on a read as well as a
|
|
* write, a node born empty that executes what arrives at its port, and the
|
|
* fabric of nodes and wiring that changes while they run.
|
|
* docs/v4.0.0/MESH.md section 4 and step 1.
|
|
*
|
|
* At the level of opcodes: no word of any vocabulary is defined here.
|
|
*/
|
|
#include "v4/asm.h"
|
|
#include "v4/capsule.h"
|
|
#include "v4/fabric.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 where the ports are. */
|
|
#define PB ((v4_cell)(V4_NODE_WORDS - 16u)) /* port 0 */
|
|
#define ANY (PB + (v4_cell)V4_PORT_ANY)
|
|
#define ORG ((v4_cell)16) /* where a test's programme starts */
|
|
#define OUT ((v4_cell)900) /* where it leaves what it found */
|
|
|
|
#define NODES 8
|
|
static v4_fabric_node pool[NODES];
|
|
static v4_place places[4], more_places[NODES];
|
|
static v4_fabric f;
|
|
static v4_asm as;
|
|
|
|
/* the node with that number in the fabric */
|
|
#define ND(id) (v4_fabric_node_at(&f, (id)))
|
|
|
|
#define O(name) v4_asm_op(&as, V4_OP_##name)
|
|
#define LIT(v) v4_asm_lit(&as, (v4_cell)(v))
|
|
|
|
/* A node with a programme already in it, started at ORG: for the tests that
|
|
* are about talking and not about being born. */
|
|
static unsigned loaded(unsigned k)
|
|
{
|
|
int id = v4_fabric_add(&f, &pool[k], PB);
|
|
pool[k].n.p = ORG;
|
|
v4_asm_begin(&as, &pool[k].n, ORG);
|
|
return (unsigned)id;
|
|
}
|
|
|
|
/* the programme ends by waiting at a port nothing is wired to */
|
|
static void wait_for_ever(void) { LIT(PB + 7); O(BANG_B); O(FETCH_B); (void)v4_asm_label(&as); } /* and the last word is written out */
|
|
|
|
/* a device that says whether it has something pending */
|
|
static int *dev_has;
|
|
static int device_has(void *self) { (void)self; return *dev_has; }
|
|
|
|
/* Let time pass until nothing more can happen, or `max` steps. */
|
|
static unsigned settle(unsigned max)
|
|
{
|
|
unsigned steps = 0;
|
|
while (steps < max && v4_fabric_step(&f) != 0) steps++;
|
|
return steps;
|
|
}
|
|
|
|
/* ---- a device: a neighbour that is not a node ---- */
|
|
typedef struct { const v4_cell *words; unsigned count, at; v4_cell took[16]; unsigned took_n; } stream;
|
|
static int stream_give(void *self, v4_cell *value)
|
|
{
|
|
stream *s = (stream *)self;
|
|
if (s->at >= s->count) return 0;
|
|
*value = s->words[s->at++];
|
|
return 1;
|
|
}
|
|
static int stream_take(void *self, v4_cell value)
|
|
{
|
|
stream *s = (stream *)self;
|
|
if (s->took_n >= 16) return 0;
|
|
s->took[s->took_n++] = value;
|
|
return 1;
|
|
}
|
|
|
|
/* for v4_capsule_write: each word of the capsule, into the stream's own list */
|
|
static void collect(void *self, v4_cell word)
|
|
{
|
|
stream *s = (stream *)self;
|
|
((v4_cell *)s->words)[s->count++] = word;
|
|
}
|
|
|
|
/* What a neighbour sends an empty node to put `count` words at `addr` and
|
|
* start there (MESH.md section 5): for the F18, and for this node, code a
|
|
* node executes straight from its port. */
|
|
static unsigned capsule(v4_cell *out, const v4_cell *words, unsigned count, v4_cell addr)
|
|
{
|
|
static const unsigned set_up[V4_SLOT_COUNT] = { V4_OP_FETCH_P, V4_OP_BANG_A, V4_OP_FETCH_P, V4_OP_PUSH, V4_OP_NOP, V4_OP_NOP };
|
|
static const unsigned pump[V4_SLOT_COUNT] = { V4_OP_FETCH_P, V4_OP_STORE_INC, V4_OP_UNEXT, V4_OP_NOP, V4_OP_NOP, V4_OP_NOP };
|
|
static v4_node scratch;
|
|
v4_asm j;
|
|
unsigned at = 0, i;
|
|
|
|
out[at++] = (v4_cell)v4_iword_assemble(set_up, 0); /* @p a! @p push */
|
|
out[at++] = addr;
|
|
out[at++] = (v4_cell)(count - 1);
|
|
out[at++] = (v4_cell)v4_iword_assemble(pump, 0); /* @p !+ unext */
|
|
for (i = 0; i < count; i++) out[at++] = words[i];
|
|
v4_node_reset(&scratch);
|
|
v4_asm_begin(&j, &scratch, 0);
|
|
v4_asm_branch(&j, V4_OP_JUMP, addr); /* jump addr */
|
|
(void)v4_asm_label(&j);
|
|
out[at++] = scratch.mem[0];
|
|
return at;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
unsigned a, b, c, i, steps;
|
|
|
|
printf("v4 fabric tests: V4_CELL_BITS=%d, %u ports to a node\n", V4_CELL_BITS, (unsigned)V4_PORTS);
|
|
|
|
/* ---- a node newly born ---- */
|
|
v4_fabric_init(&f, places, 4);
|
|
CHECK(v4_fabric_node_at(&f, 0) == 0 && v4_fabric_step(&f) == 0, "an empty fabric: no nodes, and nothing happens");
|
|
a = (unsigned)v4_fabric_add(&f, &pool[0], PB);
|
|
CHECK(a == 0 && v4_fabric_node_at(&f, 0) == &pool[0], "a node is born and has a number");
|
|
CHECK(pool[0].n.p == ANY && pool[0].n.ds.depth == 0 && pool[0].n.rs.depth == 0, "its P is at \"any port\" and its stacks are empty");
|
|
for (i = 0; i < V4_NODE_WORDS && pool[0].n.mem[i] == 0; i++) { }
|
|
CHECK(i == V4_NODE_WORDS, "and there is nothing in its memory");
|
|
CHECK(v4_fabric_step(&f) == 0 && pool[0].n.reading && pool[0].n.read_port == V4_PORT_ANY, "it is blocked reading its ports");
|
|
CHECK(v4_fabric_step(&f) == 0 && pool[0].es.anticlock == 0, "and executes nothing while it waits: its anti-clock has not moved");
|
|
CHECK(v4_fabric_remove(&f, a) == &pool[0] && v4_fabric_node_at(&f, 0) == 0, "it is removed, and its storage comes back");
|
|
|
|
/* ---- two nodes exchange words ---- */
|
|
v4_fabric_init(&f, places, 4);
|
|
a = loaded(0); /* writes three words, then reads one */
|
|
LIT(PB + 2); O(BANG_B); LIT(10); O(STORE_B); LIT(20); O(STORE_B); LIT(12); O(STORE_B);
|
|
O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A);
|
|
wait_for_ever();
|
|
CHECK(v4_asm_ok(&as), "the writer assembles");
|
|
b = loaded(1); /* reads three, writes their sum back */
|
|
LIT(PB + 5); O(BANG_B); O(FETCH_B); O(FETCH_B); O(FETCH_B); O(ADD); O(ADD); O(STORE_B);
|
|
wait_for_ever();
|
|
CHECK(v4_asm_ok(&as), "the reader assembles");
|
|
|
|
steps = settle(1000);
|
|
CHECK(ND(a)->n.asking && ND(a)->n.ask_port == 2 && ND(a)->n.request == 10, "with no wire, the writer is blocked at its first write");
|
|
CHECK(ND(b)->n.reading && ND(b)->n.read_port == 5, "and the reader at its first read");
|
|
CHECK(v4_fabric_wire(&f, a, 2, b, 5), "port 2 of one is wired to port 5 of the other, while they wait");
|
|
steps = settle(1000);
|
|
CHECK(steps < 1000, "then everything that can happen happens, and stops: %u steps", steps);
|
|
CHECK(ND(a)->n.mem[OUT] == 42, "three words went one way and their sum came back: %ld", (long)ND(a)->n.mem[OUT]);
|
|
CHECK(ND(a)->n.reading && ND(b)->n.reading, "and both are waiting again");
|
|
{
|
|
v4_uheat_t ca = ND(a)->es.anticlock, cb = ND(b)->es.anticlock;
|
|
for (i = 0; i < 20; i++) (void)v4_fabric_step(&f);
|
|
CHECK(ND(a)->es.anticlock == ca && ND(b)->es.anticlock == cb, "a node that is waiting executes nothing, however long");
|
|
}
|
|
|
|
/* ---- an empty node is filled through its port, and runs what it was sent ---- */
|
|
{
|
|
static v4_node src;
|
|
static v4_cell prog[64], sent[96];
|
|
static stream s;
|
|
static v4_device dev;
|
|
unsigned len, count;
|
|
|
|
v4_node_reset(&src); /* the programme, assembled somewhere to be sent */
|
|
v4_asm_begin(&as, &src, ORG);
|
|
LIT(77); LIT(OUT); O(BANG_A); O(STORE_A);
|
|
LIT(PB + 1); O(BANG_B); LIT(99); O(STORE_B);
|
|
wait_for_ever();
|
|
count = (unsigned)(v4_asm_label(&as) - ORG);
|
|
CHECK(v4_asm_ok(&as) && count < 64, "a programme to send: %u words", count);
|
|
for (i = 0; i < count; i++) prog[i] = src.mem[ORG + i];
|
|
len = capsule(sent, prog, count, ORG);
|
|
|
|
/* sent by a device */
|
|
v4_fabric_init(&f, places, 4);
|
|
memset(&s, 0, sizeof s);
|
|
s.words = sent; s.count = len;
|
|
dev.give = stream_give; dev.take = stream_take; dev.self = &s;
|
|
b = (unsigned)v4_fabric_add(&f, &pool[2], PB);
|
|
CHECK(v4_fabric_wire_device(&f, b, 3, &dev), "a device is wired to port 3 of a node newly born");
|
|
steps = settle(5000);
|
|
CHECK(s.at == len, "the node took every word it was sent: %u of %u", s.at, len);
|
|
for (i = 0; i < count && ND(b)->n.mem[ORG + i] == prog[i]; i++) { }
|
|
CHECK(i == count, "the programme is in its memory, where it was sent to");
|
|
CHECK(ND(b)->n.mem[OUT] == 77, "and it ran: it left its mark");
|
|
CHECK(ND(b)->n.asking && ND(b)->n.ask_port == 1 && ND(b)->n.request == 99, "and is now writing to its port 1, which nothing is wired to");
|
|
CHECK(v4_fabric_wire_device(&f, b, 1, &dev) && settle(100) < 100 && s.took_n == 1 && s.took[0] == 99,
|
|
"a device wired there takes what it wrote");
|
|
|
|
/* sent by another node, which holds it in its own memory */
|
|
v4_fabric_init(&f, places, 4);
|
|
a = loaded(0);
|
|
for (i = 0; i < len; i++) ND(a)->n.mem[200 + i] = sent[i];
|
|
LIT(200); O(BANG_A); LIT(len - 1); O(PUSH); LIT(PB + 4); O(BANG_B);
|
|
(void)v4_asm_label(&as);
|
|
O(FETCH_INC); O(STORE_B); O(UNEXT); /* @+ !b unext: each write blocks in the middle of the word */
|
|
wait_for_ever();
|
|
CHECK(v4_asm_ok(&as), "a node that sends what is in its memory");
|
|
b = (unsigned)v4_fabric_add(&f, &pool[2], PB);
|
|
CHECK(v4_fabric_wire(&f, a, 4, b, 0), "is wired to a node newly born");
|
|
steps = settle(5000);
|
|
CHECK(ND(b)->n.mem[OUT] == 77 && ND(b)->n.asking && ND(b)->n.request == 99, "which takes it in and runs it, as from the device");
|
|
CHECK(ND(b)->n.last_from == 0, "it was listening to any port, and knows which it heard from: %u", ND(b)->n.last_from);
|
|
}
|
|
|
|
/* ---- a capsule of F18 code: any node's memory, sent to an empty node (capsule.h) ---- */
|
|
{
|
|
static v4_node src;
|
|
static v4_cell sent[400];
|
|
static stream s;
|
|
static v4_device dev;
|
|
unsigned k;
|
|
|
|
v4_node_reset(&src);
|
|
v4_asm_begin(&as, &src, 40); /* a programme, not at the start of memory */
|
|
LIT(123); LIT(OUT); O(BANG_A); O(STORE_A);
|
|
wait_for_ever();
|
|
CHECK(v4_asm_ok(&as), "a programme for the capsule");
|
|
src.mem[3] = 5; /* and words here and there: the first of memory, */
|
|
src.mem[200] = -1; src.mem[202] = 7; /* two with one zero between them, */
|
|
src.mem[300] = 9; src.mem[310] = 11; /* two with many, */
|
|
src.mem[PB - 1] = 13; /* and the last before the ports */
|
|
|
|
memset(&s, 0, sizeof s);
|
|
s.words = sent;
|
|
dev.give = stream_give; dev.take = stream_take; dev.self = &s;
|
|
s.count = 0;
|
|
s.count = v4_capsule_write(&src, 40, collect, &s);
|
|
CHECK(s.count > 0 && s.count < 400, "the capsule is %u words for a memory of %u", s.count, (unsigned)V4_NODE_WORDS);
|
|
s.at = 0;
|
|
|
|
v4_fabric_init(&f, places, 4);
|
|
b = (unsigned)v4_fabric_add(&f, &pool[0], PB);
|
|
CHECK(v4_fabric_wire_device(&f, b, 5, &dev), "it is sent to a node newly born");
|
|
steps = settle(5000);
|
|
CHECK(s.at == s.count, "which takes all of it: %u of %u", s.at, s.count);
|
|
for (k = 0; k < V4_NODE_WORDS && ND(b)->n.mem[k] == (k == (unsigned)OUT ? 123 : src.mem[k]); k++) { }
|
|
CHECK(k == V4_NODE_WORDS, "its memory is then the same, word for word (first difference at %u)", k);
|
|
CHECK(ND(b)->n.mem[OUT] == 123 && ND(b)->n.reading, "and it was started where the capsule says, and ran");
|
|
}
|
|
|
|
/* ---- a word is passed on by a node in between ---- */
|
|
v4_fabric_init(&f, places, 4);
|
|
a = loaded(0);
|
|
LIT(PB + 0); O(BANG_B); LIT(7); O(STORE_B); LIT(8); O(STORE_B); LIT(9); O(STORE_B);
|
|
wait_for_ever();
|
|
b = loaded(1); /* whatever comes on any port goes out on port 6 */
|
|
LIT(ANY); O(BANG_A); LIT(PB + 6); O(BANG_B);
|
|
{
|
|
v4_cell again = v4_asm_label(&as);
|
|
O(FETCH_A); O(STORE_B);
|
|
v4_asm_branch(&as, V4_OP_JUMP, again);
|
|
}
|
|
c = loaded(2);
|
|
LIT(PB + 1); O(BANG_B); LIT(OUT); O(BANG_A); O(FETCH_B); O(STORE_INC); O(FETCH_B); O(STORE_INC); O(FETCH_B); O(STORE_INC);
|
|
wait_for_ever();
|
|
CHECK(v4_asm_ok(&as), "three nodes in a row assemble");
|
|
CHECK(v4_fabric_wire(&f, a, 0, b, 2) && v4_fabric_wire(&f, b, 6, c, 1), "the first is wired to the second, the second to the third");
|
|
steps = settle(2000);
|
|
CHECK(ND(c)->n.mem[OUT] == 7 && ND(c)->n.mem[OUT + 1] == 8 && ND(c)->n.mem[OUT + 2] == 9,
|
|
"what the first wrote reached the third, in order, through the one in between");
|
|
CHECK(ND(b)->n.last_from == 2, "the one in between heard it on its port 2");
|
|
|
|
/* ---- the wiring is changed while they run ---- */
|
|
v4_fabric_init(&f, places, 4);
|
|
a = loaded(0); /* writes 1 2 3 4 ... to its port 0 for ever */
|
|
LIT(PB + 0); O(BANG_B); LIT(0);
|
|
{
|
|
v4_cell again = v4_asm_label(&as);
|
|
LIT(1); O(ADD); O(DUP); O(STORE_B);
|
|
v4_asm_branch(&as, V4_OP_JUMP, again);
|
|
}
|
|
b = loaded(1); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
|
|
c = loaded(2); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
|
|
CHECK(v4_asm_ok(&as), "a writer and two readers assemble");
|
|
CHECK(v4_fabric_wire(&f, a, 0, b, 3), "the writer is wired to the first reader");
|
|
(void)settle(500);
|
|
CHECK(ND(b)->n.mem[OUT] == 1 && ND(c)->n.mem[OUT] == 0, "who gets its first word");
|
|
CHECK(v4_fabric_wire(&f, a, 0, c, 3), "the same port is wired to the second reader instead");
|
|
(void)settle(500);
|
|
CHECK(ND(c)->n.mem[OUT] == 2, "who gets its next: %ld", (long)ND(c)->n.mem[OUT]);
|
|
CHECK(ND(a)->n.asking && ND(a)->n.request == 3, "and the writer waits with its third");
|
|
CHECK(v4_fabric_unwire(&f, a, 0) && settle(100) == 0 && ND(a)->n.asking, "with the wire cut it goes on waiting");
|
|
v4_fabric_gone_error(&f, 18);
|
|
CHECK(settle(100) == 0 && ND(a)->n.asking && ND(a)->n.faults == 0, "and still does when the fabric has an error for it: a bare node cannot take one");
|
|
v4_node_error_attach(&ND(a)->n, OUT + 20);
|
|
v4_node_fault_attach(&ND(a)->n, 900);
|
|
CHECK(v4_fabric_step(&f) == 1 && !ND(a)->n.asking && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 18
|
|
&& ND(a)->n.p == 900 + (v4_cell)V4_FAULT_RAISED && ND(a)->n.mem[OUT + 20] == 18,
|
|
"a node that can take an error is let go, and the error is raised on it: its P is its handler");
|
|
CHECK(!v4_node_port_gone(&ND(c)->n, 18), "a node that is not blocked at a port is not touched");
|
|
|
|
/* ---- asleep, awake, and removed, while looping; and a node born while others run ---- */
|
|
v4_fabric_init(&f, places, 4);
|
|
a = loaded(0); /* counts in OUT for ever */
|
|
LIT(OUT); O(BANG_A);
|
|
{
|
|
v4_cell again = v4_asm_label(&as);
|
|
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
|
|
v4_asm_branch(&as, V4_OP_JUMP, again);
|
|
}
|
|
b = loaded(1); /* and so does this one */
|
|
LIT(OUT); O(BANG_A);
|
|
{
|
|
v4_cell again = v4_asm_label(&as);
|
|
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
|
|
v4_asm_branch(&as, V4_OP_JUMP, again);
|
|
}
|
|
CHECK(v4_asm_ok(&as), "two nodes that never stop assemble");
|
|
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
|
|
{
|
|
v4_cell ca = ND(a)->n.mem[OUT], cb = ND(b)->n.mem[OUT];
|
|
v4_uheat_t clock = ND(a)->es.anticlock;
|
|
CHECK(ca > 0 && ca == cb, "each has had as much time as the other: %ld and %ld", (long)ca, (long)cb);
|
|
v4_fabric_sleep(&f, a);
|
|
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
|
|
CHECK(ND(a)->n.mem[OUT] == ca && ND(a)->es.anticlock == clock, "put to sleep in the middle of its loop, one executes nothing");
|
|
CHECK(ND(b)->n.mem[OUT] == 2 * cb, "and the other goes on as before: %ld", (long)ND(b)->n.mem[OUT]);
|
|
v4_fabric_wake(&f, a);
|
|
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
|
|
CHECK(ND(a)->n.mem[OUT] == 2 * ca, "woken, it goes on from where it was: %ld", (long)ND(a)->n.mem[OUT]);
|
|
CHECK(v4_fabric_remove(&f, a) != 0 && v4_fabric_node_at(&f, a) == 0, "it is removed while it loops");
|
|
cb = ND(b)->n.mem[OUT];
|
|
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
|
|
CHECK(ND(b)->n.mem[OUT] > cb, "and the other goes on");
|
|
c = (unsigned)v4_fabric_add(&f, &pool[3], PB);
|
|
CHECK(c == a && pool[3].n.p == ANY, "a node is born while the other runs, into the place that was freed");
|
|
for (i = 0; i < 10; i++) (void)v4_fabric_step(&f);
|
|
CHECK(pool[3].n.reading && pool[3].es.anticlock == 0, "and waits, empty, while the other runs");
|
|
}
|
|
|
|
/* ---- more room, while they run ---- */
|
|
v4_fabric_init(&f, places, 4);
|
|
a = loaded(0); LIT(PB + 0); O(BANG_B); LIT(5); O(STORE_B); wait_for_ever();
|
|
b = loaded(1); LIT(PB + 0); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
|
|
(void)loaded(2); wait_for_ever();
|
|
(void)loaded(3); wait_for_ever();
|
|
CHECK(v4_fabric_add(&f, &pool[4], PB) == -1, "a fabric with no room left takes no more nodes");
|
|
CHECK(v4_fabric_wire(&f, a, 0, b, 0), "two of its nodes are wired");
|
|
v4_fabric_grow(&f, more_places, NODES);
|
|
CHECK(v4_fabric_add(&f, &pool[4], PB) == 4, "given more room, it takes another");
|
|
(void)settle(500);
|
|
CHECK(ND(b)->n.mem[OUT] == 5, "and the nodes that were there are wired as they were");
|
|
|
|
/* ---- what cannot be wired ---- */
|
|
CHECK(!v4_fabric_wire(&f, a, V4_PORTS, b, 0) && !v4_fabric_wire(&f, a, 0, 7, 0) && !v4_fabric_wire(&f, a, 1, a, 1),
|
|
"a port that is not there, a node that is not there, a port to itself: refused");
|
|
CHECK(v4_fabric_wire(&f, a, 1, a, 2), "but one port of a node may be wired to another of the same node");
|
|
|
|
/* ---- a node looks at its neighbours before it writes (MESH.md 7a) ---- */
|
|
v4_fabric_init(&f, places, 4);
|
|
a = loaded(0); LIT(PB + 0); O(BANG_B); LIT(5); O(STORE_B); wait_for_ever(); /* writes to its port 0 */
|
|
c = loaded(1); LIT(PB + 1); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever(); /* reads its port 1 */
|
|
for (i = 0; i < 10; i++) (void)v4_fabric_step(&f);
|
|
CHECK(ND(a)->n.asking && ND(c)->n.reading, "one node is waiting to write and another to read");
|
|
b = loaded(2);
|
|
LIT(PB + (v4_cell)V4_PORTS + 2); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A);
|
|
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); LIT(OUT + 1); O(BANG_A); O(STORE_A);
|
|
wait_for_ever();
|
|
CHECK(v4_fabric_wire(&f, b, 2, a, 0) && v4_fabric_wire(&f, b, 4, c, 1), "a third is wired to both");
|
|
{
|
|
static stream none;
|
|
static v4_device sink;
|
|
sink.take = stream_take; sink.give = 0; sink.self = &none;
|
|
CHECK(v4_fabric_wire_device(&f, b, 6, &sink), "and to a device that takes what is written to it");
|
|
}
|
|
for (i = 0; i < 20; i++) (void)v4_fabric_step(&f);
|
|
CHECK(ND(b)->n.mem[OUT] == (1 << 2), "it sees which of its ports has a neighbour waiting to write to it: %ld", (long)ND(b)->n.mem[OUT]);
|
|
CHECK((ND(b)->n.mem[OUT + 1] & ((1 << V4_PORTS) - 1)) == ((1 << 4) | (1 << 6)), "and which have one waiting to read from it, or a device: %ld", (long)(ND(b)->n.mem[OUT + 1] & ((1 << V4_PORTS) - 1)));
|
|
CHECK((ND(b)->n.mem[OUT + 1] >> V4_PORTS) == ((1 << 2) | (1 << 4) | (1 << 6)), "and, in the bits above those, which of its ports have anything on them at all: %ld", (long)(ND(b)->n.mem[OUT + 1] >> V4_PORTS));
|
|
CHECK(ND(a)->n.asking && ND(a)->n.request == 5 && ND(c)->n.reading && ND(c)->n.mem[OUT] == 0, "looking takes nothing and gives nothing: both wait as they did");
|
|
CHECK(!ND(b)->n.stopped && ND(b)->n.reading, "and the one that looked did not wait to look");
|
|
|
|
/* ---- a node blocked on the first word of a message, while a device of its own has something for it ---- */
|
|
{
|
|
static int has; /* what the device says it has pending */
|
|
static stream none;
|
|
static v4_device dev;
|
|
v4_fabric_init(&f, places, 4);
|
|
for (i = 0; i < 3; i++) v4_node_reset(&pool[i].n);
|
|
a = loaded(0); /* looks about, then writes two words to its port 0 */
|
|
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); O(DROP);
|
|
LIT(PB + 0); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); wait_for_ever();
|
|
b = loaded(1); /* never reads */
|
|
LIT(OUT); O(BANG_A);
|
|
{
|
|
v4_cell again = v4_asm_label(&as);
|
|
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
|
|
v4_asm_branch(&as, V4_OP_JUMP, again);
|
|
}
|
|
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a writer that looks first, wired to a node that never reads");
|
|
dev.take = stream_take; dev.give = 0; dev.self = &none; dev.pending = device_has; has = 1; dev_has = &has;
|
|
CHECK(v4_fabric_wire_device(&f, a, 5, &dev), "and to a device, which has something for it");
|
|
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
|
|
CHECK(ND(a)->n.asking && ND(a)->n.request == 1 && v4_node_words_since_look(&ND(a)->n) == 0, "the writer is blocked on its first word, and waits: the fabric has no error for it");
|
|
v4_fabric_interrupt_error(&f, 21);
|
|
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
|
|
CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "nor is a bare node let go when it has: it cannot take an error");
|
|
v4_node_error_attach(&ND(a)->n, OUT + 20);
|
|
v4_node_fault_attach(&ND(a)->n, 900);
|
|
has = 0;
|
|
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
|
|
CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "a node that can take one waits all the same while the device has nothing for it");
|
|
has = 1;
|
|
(void)v4_fabric_step(&f);
|
|
CHECK(!ND(a)->n.asking && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 21 && ND(a)->n.mem[OUT + 20] == 21,
|
|
"when it has, the writer is let go and the error raised on it");
|
|
|
|
/* once the first word has gone, it is not let go: the message must be finished */
|
|
v4_fabric_init(&f, places, 4);
|
|
for (i = 0; i < 3; i++) v4_node_reset(&pool[i].n);
|
|
a = loaded(0);
|
|
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); O(DROP);
|
|
LIT(PB + 0); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); wait_for_ever();
|
|
b = loaded(1); /* reads one word, and no more */
|
|
LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A);
|
|
{
|
|
v4_cell again = v4_asm_label(&as);
|
|
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
|
|
v4_asm_branch(&as, V4_OP_JUMP, again);
|
|
}
|
|
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire_device(&f, a, 5, &dev), "the same, to a node that reads one word and no more");
|
|
v4_node_error_attach(&ND(a)->n, OUT + 20);
|
|
v4_node_fault_attach(&ND(a)->n, 900);
|
|
v4_fabric_interrupt_error(&f, 21);
|
|
for (i = 0; i < 60; i++) (void)v4_fabric_step(&f);
|
|
CHECK(ND(a)->n.asking && ND(a)->n.request == 2 && ND(a)->n.faults == 0, "blocked on its second word with the device still pending, the writer waits");
|
|
}
|
|
|
|
printf(" %d checks, %d failures\n", checks, failures);
|
|
return failures != 0;
|
|
}
|