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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2c5427d9c1
commit
9af442f793
@@ -0,0 +1,99 @@
|
||||
/* fabric.h -- the nodes there are, how their ports are wired, and time
|
||||
* passing for all of them at once. docs/v4.0.0/MESH.md 4.3.
|
||||
*
|
||||
* On the fabric every node runs at the same time and a node with nothing to
|
||||
* do is blocked at a port. Here that is: every node that is awake and not
|
||||
* blocked executes one instruction word, and then every write that has a
|
||||
* reader waiting on the other end of its wire is handed over. There is no
|
||||
* choice of whose turn it is.
|
||||
*
|
||||
* THE GEOMETRY IS DATA. Which port connects to what is a table, and it can
|
||||
* be changed while the nodes run; nodes can be added and removed while they
|
||||
* run. Nothing here knows what shape the wiring makes.
|
||||
*
|
||||
* Nothing here knows what the nodes are for, either: no message, no
|
||||
* capsule, no StarForth. And nothing here uses the C library: whoever owns
|
||||
* the fabric supplies the memory for it and for each node.
|
||||
*/
|
||||
#ifndef V4_FABRIC_H
|
||||
#define V4_FABRIC_H
|
||||
|
||||
#include "v4/exec.h"
|
||||
|
||||
/* What is on the other end of a port that is not a node: a console, a disk,
|
||||
* the kernel that serves a node's requests. Either function may be 0. */
|
||||
typedef struct {
|
||||
int (*take)(void *self, v4_cell value); /* the node wrote `value`: 1 if it is taken, 0 if not yet */
|
||||
int (*give)(void *self, v4_cell *value); /* the node is reading: 1 with a word in *value, 0 if not yet */
|
||||
void *self;
|
||||
} v4_device;
|
||||
|
||||
/* One end of a wire. */
|
||||
enum { V4_WIRE_NONE = 0, V4_WIRE_NODE, V4_WIRE_DEVICE };
|
||||
typedef struct {
|
||||
int kind;
|
||||
unsigned node, port; /* V4_WIRE_NODE: the other node and its port */
|
||||
const v4_device *device; /* V4_WIRE_DEVICE */
|
||||
} v4_wire;
|
||||
|
||||
/* A node, with what the engine needs to run it. Its owner supplies one of
|
||||
* these for every node there is to be. */
|
||||
typedef struct {
|
||||
v4_node n;
|
||||
v4_exec_state es;
|
||||
v4_heat h;
|
||||
} v4_fabric_node;
|
||||
|
||||
/* A place in the fabric: a node, or nothing. */
|
||||
typedef struct {
|
||||
v4_fabric_node *node; /* 0: no node here */
|
||||
int asleep;
|
||||
v4_wire wire[V4_PORTS];
|
||||
} v4_place;
|
||||
|
||||
typedef struct {
|
||||
v4_place *place;
|
||||
unsigned capacity;
|
||||
} v4_fabric;
|
||||
|
||||
/* An empty fabric with room for `capacity` nodes, in `places`. */
|
||||
void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity);
|
||||
|
||||
/* More room: `places`, of `capacity` no less than before, takes over; every
|
||||
* node keeps its number and its wiring. */
|
||||
void v4_fabric_grow(v4_fabric *f, v4_place *places, unsigned capacity);
|
||||
|
||||
/* A node is born (node.h, v4_node_born): empty, its ports at `port_base`,
|
||||
* wired to nothing, awake and blocked reading its ports. Returns its
|
||||
* number, or -1 if there is no room. */
|
||||
int v4_fabric_add(v4_fabric *f, v4_fabric_node *storage, v4_cell port_base);
|
||||
|
||||
/* A node is removed, whatever it was doing. Every wire to it is cut; a
|
||||
* neighbour blocked on one of them stays blocked, as it would on a port
|
||||
* with nothing there. Returns the storage it was given, or 0. */
|
||||
v4_fabric_node *v4_fabric_remove(v4_fabric *f, unsigned id);
|
||||
|
||||
/* The node numbered `id`, or 0 if there is none. */
|
||||
v4_fabric_node *v4_fabric_node_at(const v4_fabric *f, unsigned id);
|
||||
|
||||
/* Wire port `pa` of node `a` to port `pb` of node `b`, or to a device; or
|
||||
* cut whatever is on it. Whatever was on a port before is cut, at both
|
||||
* ends. Each returns 1, or 0 if a node or a port is not there. */
|
||||
int v4_fabric_wire(v4_fabric *f, unsigned a, unsigned pa, unsigned b, unsigned pb);
|
||||
int v4_fabric_wire_device(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device);
|
||||
int v4_fabric_unwire(v4_fabric *f, unsigned a, unsigned pa);
|
||||
|
||||
/* A node that is asleep executes nothing, and nothing is handed to it or
|
||||
* taken from it. It can be put to sleep and woken at any instruction
|
||||
* word. */
|
||||
void v4_fabric_sleep(v4_fabric *f, unsigned id);
|
||||
void v4_fabric_wake(v4_fabric *f, unsigned id);
|
||||
|
||||
/* Time passes: every node that is awake and not blocked executes one
|
||||
* instruction word; then every write with a reader waiting on the other end
|
||||
* of its wire is handed over, and every device is asked. Returns how many
|
||||
* instructions were executed and words handed over together: 0 means
|
||||
* nothing can happen until something outside changes. */
|
||||
unsigned v4_fabric_step(v4_fabric *f);
|
||||
|
||||
#endif /* V4_FABRIC_H */
|
||||
+68
-19
@@ -36,6 +36,14 @@
|
||||
#include "v4/stack.h"
|
||||
|
||||
/* Words of node memory. Overridable at build time. */
|
||||
/* How many ports a node has. A build parameter, as the cell width and the
|
||||
* node's memory are: the geometry the nodes are wired in is data, and a node
|
||||
* has as many ports as the geometry in use needs (docs/v4.0.0/MESH.md 4.1). */
|
||||
#ifndef V4_PORTS
|
||||
#define V4_PORTS 8u
|
||||
#endif
|
||||
#define V4_PORT_ANY V4_PORTS /* not a port: "whichever port has a neighbour writing" */
|
||||
|
||||
#ifndef V4_NODE_WORDS
|
||||
#define V4_NODE_WORDS 1024u
|
||||
#endif
|
||||
@@ -91,9 +99,16 @@ typedef struct {
|
||||
/* The port (DECOMPOSITION.md section 6): a write to it blocks the node
|
||||
* until its neighbour has taken what was written. See
|
||||
* v4_node_port_attach below. */
|
||||
v4_cell port; /* its word address, or -1: no port */
|
||||
int asking; /* non-zero: blocked, having written `request` */
|
||||
v4_cell port; /* word address of port 0, or -1: no ports */
|
||||
int asking; /* non-zero: blocked, having written `request` to port `ask_port` */
|
||||
v4_cell request; /* what it wrote */
|
||||
unsigned ask_port; /* to which port */
|
||||
int reading; /* non-zero: blocked, wanting a word from port `read_port` */
|
||||
unsigned read_port; /* a port, or V4_PORT_ANY */
|
||||
int given; /* non-zero: `given_value` has come from port `given_port` and not been taken yet */
|
||||
v4_cell given_value;
|
||||
unsigned given_port;
|
||||
unsigned last_from; /* the port the last read from "any" took its word from */
|
||||
|
||||
/* DSTACK-DEPTH and RSTACK-DEPTH (D-16). See v4_node_stack_regs_attach. */
|
||||
v4_cell dstack_reg; /* its word address, or -1 */
|
||||
@@ -156,28 +171,62 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value);
|
||||
* Passing -1 detaches. The address is the caller's choice. */
|
||||
void v4_node_console_attach(v4_node *n, v4_cell addr);
|
||||
|
||||
/* THE PORT. A node reaches its neighbour through a port, and a port is an
|
||||
* address (DECOMPOSITION.md section 6): "a write blocks until the neighbour
|
||||
* reads. This is the GA144 model. No instruction is added."
|
||||
/* THE PORTS. A node reaches its neighbours through its ports, and a port is
|
||||
* an address (DECOMPOSITION.md section 6): "a read from a port blocks until
|
||||
* the neighbour writes; a write blocks until the neighbour reads. This is
|
||||
* the GA144 model. No instruction is added." docs/v4.0.0/MESH.md 4.1.
|
||||
*
|
||||
* After v4_node_port_attach(n, addr), a store to word address `addr` does
|
||||
* not write memory: the node keeps the value in n->request, sets n->asking
|
||||
* and is blocked. A blocked node executes nothing -- v4_exec_step_word
|
||||
* returns 0 and changes nothing -- until its neighbour calls
|
||||
* v4_node_port_served(n). It then goes on from the opcode after the store,
|
||||
* in the same instruction word if there are any left.
|
||||
* After v4_node_port_attach(n, base) the node's V4_PORTS ports are the word
|
||||
* addresses base .. base + V4_PORTS - 1. Two more follow them:
|
||||
* base + V4_PORTS "any port": a read here takes from whichever port
|
||||
* has a neighbour writing
|
||||
* base + V4_PORTS + 1 which port the last read from "any" came from
|
||||
* (read only)
|
||||
* None of them is memory while the ports are attached.
|
||||
*
|
||||
* This is how a node asks its kernel for something (docs/v4.0.0/ENGINE.md
|
||||
* 3.3): the neighbour on the port is the kernel, what is written is the
|
||||
* number of the request, and the arguments and results are on the node's
|
||||
* data stack, which the kernel may use while the node is blocked. Only the
|
||||
* write is here. Nothing reads from a port yet, and a fetch from the
|
||||
* address is a fetch from memory.
|
||||
* A WRITE. A store to a port does not write memory: the node keeps the
|
||||
* value in n->request and the port in n->ask_port, sets n->asking and is
|
||||
* blocked. When its neighbour has taken the value -- v4_node_port_served --
|
||||
* it goes on from the opcode after the store, in the same instruction word
|
||||
* if there are any left.
|
||||
*
|
||||
* A node has no port until one is attached: v4_node_reset detaches it. */
|
||||
void v4_node_port_attach(v4_node *n, v4_cell addr);
|
||||
* A READ. A fetch from a port -- @ @b @+, the literal fetch @p, or the
|
||||
* fetch of an instruction word when P is a port -- does not happen until
|
||||
* there is a word to fetch. The node sets n->reading and n->read_port and
|
||||
* is blocked, the opcode not yet executed. When its neighbour writes --
|
||||
* v4_node_port_give -- the fetch is made, and it gets that word. When P is
|
||||
* a port it is not advanced by a fetch: the node goes on executing what
|
||||
* arrives there, as an F18 node does.
|
||||
*
|
||||
* A blocked node executes nothing: v4_exec_step_word returns 0 and changes
|
||||
* nothing. Who the neighbour is, and whether there is one, is not the
|
||||
* node's to know (fabric.h). A store to "any port" or to the "which port"
|
||||
* address is an address fault.
|
||||
*
|
||||
* A node has no ports until they are attached: v4_node_reset detaches them. */
|
||||
void v4_node_port_attach(v4_node *n, v4_cell base);
|
||||
|
||||
/* The neighbour has taken what the node wrote. */
|
||||
void v4_node_port_served(v4_node *n);
|
||||
|
||||
/* The neighbour on `port` writes `value`: the node, blocked reading that
|
||||
* port or "any", is unblocked and will fetch it. */
|
||||
void v4_node_port_give(v4_node *n, unsigned port, v4_cell value);
|
||||
|
||||
/* Which port `addr` is: 0 .. V4_PORTS - 1, V4_PORT_ANY, V4_PORTS + 1 for the
|
||||
* "which port" address, or -1 if it is none of them. */
|
||||
int v4_node_port_index(const v4_node *n, v4_cell addr);
|
||||
|
||||
/* 1 if a fetch from `addr` can be made now: it is not a port, or the word
|
||||
* has been given. 0 if it is a port with nothing given: the node is then
|
||||
* blocked reading it, and the fetch must not be made. */
|
||||
int v4_node_read_ready(v4_node *n, v4_cell addr);
|
||||
|
||||
/* A NODE NEWLY BORN (docs/v4.0.0/MESH.md 4.2). Reset, its ports attached at
|
||||
* `base`, and P at "any port": it is blocked reading its ports, and will
|
||||
* execute what a neighbour sends it. Nothing else is in it. */
|
||||
void v4_node_born(v4_node *n, v4_cell base);
|
||||
|
||||
/* CONSOLE-RX and CONSOLE-STATUS, the console's receive side, on the
|
||||
* single-node model.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user