- Text after start is refused, as text.c refuses it: before it jumps, the ROM reads on to the end of the source; blanks, comments and the ends of blobs may follow, a token is ERR-AFTER (26), "text after start". - A token too long where a name, a target or a value is wanted is "name too long", as text.c says: (MISSING) reports "missing" only when no error came first. `", keeps "missing", which is text.c's word for both. - text.c's "address out of range" names the address, not the value. - The system's source files are one list, v4/sys_files.h: boot.c, the harness and the nucleus test are made from it. - The ROM's error codes and words are one list, v4/rom_errors.h: boot.c's messages, host_map.h's ROM_ERR_* and mkmap's ERR-* are made from it. - node.h says what v4_node_store does with a ROM address (nothing, as for any address outside RAM, D-14); test_host_rom_range pins it. ROM: 2095 words, rom_hash 0x06d0dcb7a0cb41a1. dict_hash unchanged, 0xe5a57f3eb72ff94a. make -C v4 test test-32 hosted-check pass. QEMU: amd64, aarch64, riscv64 to ok>, POST 538/538, POST: PASSED. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
453 lines
25 KiB
C
453 lines
25 KiB
C
/* node.h -- the v4 node: registers, stacks, and word-addressed memory.
|
|
*
|
|
* DECOMPOSITION.md section 1.1:
|
|
*
|
|
* | Cell | 32 bits (mesh node). Host node's width is a build
|
|
* | parameter; see D-5. |
|
|
* | Addressing | Word-addressed (D-1). |
|
|
* | Registers | T (top of data stack), S (second), R (top of return
|
|
* | stack), P (program counter), A and B (address
|
|
* | registers). |
|
|
* | Stacks | F18 circular hardware stacks, not visible to code
|
|
* | (D-2) ... data stack 10 deep, return stack 9 deep. |
|
|
* | Instruction | Six 5-bit slots, plus 2 spare bits. |
|
|
*
|
|
* T, S and R are not fields here: they live inside v4_dstack and v4_rstack,
|
|
* because on the F18 they are the topmost slots of the circular stacks rather
|
|
* than registers that happen to shadow them, and the golden model has to model
|
|
* the mechanism and not just the values. P, A and B are genuinely separate
|
|
* registers and live here.
|
|
*
|
|
* MEMORY SIZE IS A BUILD PARAMETER, not a constant of the design.
|
|
* JUSTIFICATION.md section 10 names cell width, node count and node memory as
|
|
* the three parameters of the golden model, and no word count is fixed anywhere
|
|
* in DECOMPOSITION.md -- D-4, which is where a node memory map would be pinned
|
|
* down, is deferred to step 2. The default here is a round number that suits
|
|
* a host; the datapoint worth knowing is JUSTIFICATION.md section 4, that a
|
|
* GA144 node had 64 words of RAM and 64 of ROM. v4 keeps the same split --
|
|
* dictionary and interpreter live in the compiler capsule on the host and are
|
|
* streamed to nodes, so node memory is not sized by capsule size.
|
|
*/
|
|
#ifndef V4_NODE_H
|
|
#define V4_NODE_H
|
|
|
|
#include "v4/cell.h"
|
|
#include "v4/guard.h"
|
|
#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" */
|
|
|
|
/* The indexes, after the ports, of what a node asks of the fabric about whole
|
|
* messages on its wires (docs/v4.0.0/MESH.md 7d.3); see v4_node_port_attach. */
|
|
#define V4_PORT_WIRE (V4_PORTS + 4u) /* store the number of a port: what follows is about that wire */
|
|
#define V4_PORT_A (V4_PORTS + 5u) /* store a value an operation needs */
|
|
#define V4_PORT_B (V4_PORTS + 6u) /* store a second */
|
|
#define V4_PORT_DO (V4_PORTS + 7u) /* store the number of an operation: the node is blocked until it is done */
|
|
#define V4_PORT_HOW (V4_PORTS + 8u) /* fetch how it went */
|
|
#define V4_PORT_HAVE (V4_PORTS + 9u) /* fetch which wires have a message waiting: bit k for port k */
|
|
|
|
#ifndef V4_NODE_WORDS
|
|
#define V4_NODE_WORDS 1024u
|
|
#endif
|
|
|
|
/* THE ROM (docs/v4.0.0/MACHINE.md section 2, DATASHEET-F18.md section 5).
|
|
* A read-only range of V4_ROM_WORDS words at V4_ROM_BASE, just above RAM.
|
|
* An instruction fetch, an @p literal and a data fetch there read the ROM; a
|
|
* running programme's store there is an address fault. C code's
|
|
* v4_node_store there does nothing and counts no fault, as for any address
|
|
* outside RAM (D-14, at v4_node_store below). In the model it is
|
|
* attached, as every device is, and v4_node_reset detaches it: the owner
|
|
* attaches it again after a reset. The range's size is a build parameter,
|
|
* as RAM's is; 0 (the default) is a machine with no ROM. */
|
|
#ifndef V4_ROM_WORDS
|
|
#define V4_ROM_WORDS 0u
|
|
#endif
|
|
#define V4_ROM_BASE ((v4_cell)V4_NODE_WORDS)
|
|
|
|
/* What the ROM reads at the end of each blob of the source it is fed (ASCII
|
|
* FS, the file separator), after the blob's new line: the end of a file, as
|
|
* text.c ends one -- a comment or macro still open there is an error, and the
|
|
* word being assembled ends. The end of the whole source is EOT (4). */
|
|
#define V4_ROM_BLOB_END 28
|
|
|
|
/* 5% safety boundary at each end of node memory, per guard.h. Note the
|
|
* arithmetic: at 1024 words that is 52 words either side, 5.2% of memory
|
|
* spent on a boundary. That is the cost of the convention on a resource that
|
|
* is scarce by construction, and it is worth naming rather than discovering
|
|
* later -- on a real mesh node the memory is 32-bit words on silicon with no
|
|
* second chance. A deployment that cannot afford it sets V4_GUARD_PCT to 0 at
|
|
* build time, which is why the percentage is a macro and not baked in. */
|
|
#define V4_MEM_BOUND V4_GUARD_ELEMS(V4_NODE_WORDS)
|
|
|
|
/* Characters the console capture holds. Overridable at build time. */
|
|
#ifndef V4_CONSOLE_CAP
|
|
#define V4_CONSOLE_CAP 4096u
|
|
#endif
|
|
|
|
typedef struct {
|
|
v4_cell p; /* P -- program counter */
|
|
v4_cell a; /* A -- address register */
|
|
v4_cell b; /* B -- address register */
|
|
|
|
v4_dstack ds; /* holds T and S */
|
|
v4_rstack rs; /* holds R */
|
|
|
|
v4_cell mem_guard_head[V4_MEM_BOUND];
|
|
v4_cell mem[V4_NODE_WORDS];
|
|
v4_cell mem_guard_tail[V4_MEM_BOUND];
|
|
|
|
/* The ROM: its words, and how many there are (0: none). See
|
|
* v4_node_rom_attach. */
|
|
const v4_cell *rom;
|
|
unsigned rom_words;
|
|
|
|
/* CONSOLE-TX capture (DECOMPOSITION.md section 7). See
|
|
* v4_node_console_attach below. */
|
|
v4_cell console_tx; /* its word address, or -1: no console */
|
|
unsigned console_len; /* characters captured */
|
|
unsigned console_dropped; /* characters that did not fit */
|
|
unsigned char console[V4_CONSOLE_CAP];
|
|
|
|
/* CONSOLE-RX and CONSOLE-STATUS (section 7). See
|
|
* v4_node_console_input_attach below. */
|
|
v4_cell console_rx; /* its word address, or -1: no input */
|
|
v4_cell console_status; /* its word address, or -1 */
|
|
unsigned input_len; /* characters fed */
|
|
unsigned input_pos; /* characters taken; input_len - input_pos are pending */
|
|
unsigned char input[V4_CONSOLE_CAP];
|
|
|
|
/* Faults (D-14, D-16). See v4_node_fault_attach below. */
|
|
v4_cell fault_vector; /* word address of the handler table, or -1: none */
|
|
v4_cell fault_addr; /* the address of the latest address fault */
|
|
unsigned fault_kind; /* which kind the latest fault was (V4_FAULT_*) */
|
|
unsigned faults; /* how many there have been */
|
|
int stopped; /* non-zero: faulted with no handler */
|
|
|
|
/* 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; /* 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 */
|
|
unsigned writers; /* bit k: the neighbour on port k is blocked writing to this node */
|
|
unsigned readers; /* bit k: the neighbour on port k is blocked reading from this node */
|
|
|
|
/* Whole messages on the wires (MESH.md 7d.3). See v4_node_port_attach. */
|
|
unsigned wire; /* the port that A, B and Do are about; V4_PORTS if what was stored is not a port */
|
|
v4_cell wire_a, wire_b; /* A and B */
|
|
int doing; /* non-zero: blocked, having stored `do_op` to Do */
|
|
int spoke; /* non-zero: it has asked for a whole-message operation since it was born: it is a node that
|
|
* speaks whole messages, and not one that words are written to (fabric.h) */
|
|
v4_cell do_op; /* the operation */
|
|
v4_cell how; /* how the last one went */
|
|
unsigned have; /* which wires had a message waiting, as the fabric last said: bit k for port k */
|
|
int have_fetched; /* non-zero: `have` has been fetched since the fabric last cleared what had arrived */
|
|
|
|
/* DSTACK-DEPTH and RSTACK-DEPTH (D-16). See v4_node_stack_regs_attach. */
|
|
v4_cell dstack_reg; /* its word address, or -1 */
|
|
v4_cell rstack_reg; /* its word address, or -1 */
|
|
|
|
|
|
/* NODE-ERROR as a trap (D-18). See v4_node_error_attach. */
|
|
v4_cell error_reg; /* its word address, or -1 */
|
|
} v4_node;
|
|
|
|
/* Zero P, A and B, empty the stacks, and clear memory. Installs every
|
|
* boundary (memory and both stacks) and leaves the node in a defined state. */
|
|
void v4_node_reset(v4_node *n);
|
|
|
|
/* 1 if every boundary in the node is intact -- memory and both stacks -- else
|
|
* 0. This is the single call a caller needs to detect that an index went
|
|
* wrong somewhere; see guard.h for what it catches that AddressSanitizer
|
|
* cannot. */
|
|
int v4_node_guards_intact(const v4_node *n);
|
|
|
|
/* Read and write one word. D-1: word-addressed, so `addr` counts words and
|
|
* CELLS is a no-op. `addr` is a v4_cell because A, B and P are all v4_cells
|
|
* and a narrower parameter would put a conversion between the register and the
|
|
* access on every one of the eight memory opcodes.
|
|
*
|
|
* An address outside 0 .. V4_NODE_WORDS-1 is never used to index memory
|
|
* (D-14, ruled 2026-10-04): v4_node_load gives 0 for it and v4_node_store
|
|
* does nothing. That is all these two do. They are what C code uses -- the
|
|
* assemblers, the tests -- and they do not count a fault; the executor does,
|
|
* for the addresses a running programme uses (see v4_node_fault_attach).
|
|
*
|
|
* The 5% band on each side of mem is a different protection: it is where a
|
|
* linear index error in the model's own C -- mem[-1], mem[V4_NODE_WORDS] --
|
|
* would land, inside the struct where AddressSanitizer cannot see it, and
|
|
* v4_node_guards_intact() reports it. */
|
|
v4_cell v4_node_load(const v4_node *n, v4_cell addr);
|
|
void v4_node_store(v4_node *n, v4_cell addr, v4_cell value);
|
|
|
|
/* CONSOLE-TX, the console's transmit register, on the single-node model.
|
|
*
|
|
* EMIT is a device service: one character sent to the console node
|
|
* (DECOMPOSITION.md 5.10). The mesh and its ports are development step 2,
|
|
* and the memory map is open (D-4), so until then the model stands in for
|
|
* the console with one memory-mapped register and a capture buffer. That is
|
|
* enough to run every printing word and compare what it prints with v3.
|
|
*
|
|
* After v4_node_console_attach(n, addr), a store to word address `addr`
|
|
* appends the low 8 bits of the value to n->console instead of writing
|
|
* memory; n->mem[addr] is never changed by it, and a load from `addr` reads
|
|
* that memory word as before. All four store opcodes go through
|
|
* v4_node_store, so all four reach it. Once V4_CONSOLE_CAP characters are
|
|
* held, further ones are counted in n->console_dropped and discarded.
|
|
*
|
|
* A node has no console until one is attached: v4_node_reset detaches it
|
|
* (console_tx = -1) and empties the capture. Attaching empties it too.
|
|
* Passing -1 detaches. The address is the caller's choice. */
|
|
void v4_node_console_attach(v4_node *n, v4_cell addr);
|
|
|
|
/* 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, 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
|
|
* base + V4_PORTS + 2 which ports have a neighbour waiting to write to this
|
|
* node: bit k for port k
|
|
* base + V4_PORTS + 3 which ports have a neighbour waiting to read from it,
|
|
* bit k for port k; and, V4_PORTS bits higher, which
|
|
* ports have anything on them at all: bit V4_PORTS + k
|
|
* for port k. A port with nothing on it can never
|
|
* be read from or written to.
|
|
* base + V4_PORTS + 4 .. + 9 whole messages on the wires (MESH.md 7d.3), the
|
|
* indexes V4_PORT_WIRE, V4_PORT_A, V4_PORT_B, V4_PORT_DO,
|
|
* V4_PORT_HOW and V4_PORT_HAVE:
|
|
* Wire, A, B store: what an operation is about and
|
|
* needs; fetch gives back what was stored
|
|
* Do store: the number of an operation. The
|
|
* node is blocked until whoever connects
|
|
* its ports has done it (v4_node_doing,
|
|
* v4_node_done); fetch gives the number
|
|
* How fetch: how the last one went
|
|
* Have fetch: which wires have a message
|
|
* waiting, bit k for port k, as the fabric
|
|
* last said (v4_node_have)
|
|
* The two before them let a node that writes a word at a time see whether a
|
|
* neighbour is waiting (MESH.md 7a), as the F18's io register does; a node that
|
|
* speaks whole messages has no use for them. They are what whoever connects the ports last
|
|
* said, v4_node_port_status; fetching them waits for nothing and changes
|
|
* nothing. Of the whole-message addresses How and Have are read only: a
|
|
* store to either is an address fault, as to "any port".
|
|
* None of them is memory while the ports are attached.
|
|
*
|
|
* 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 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);
|
|
|
|
/* WHOLE MESSAGES (MESH.md 7d.3). A store to Do records the operation in
|
|
* n->do_op and blocks the node, as a store to a port does: n->doing. For
|
|
* whoever connects the node's ports: 1 if the node is blocked at one; the
|
|
* operation is done, `how` is what the node will fetch from How, and the
|
|
* node goes on from the opcode after the store; and `mask` is which wires
|
|
* have a message waiting, as the node will fetch it from Have. Fetching
|
|
* Have also sets n->have_fetched, for whoever connects the ports to see and
|
|
* clear. */
|
|
int v4_node_doing(const v4_node *n);
|
|
void v4_node_done(v4_node *n, v4_cell how);
|
|
void v4_node_have(v4_node *n, unsigned mask);
|
|
|
|
/* THERE IS NO ONE ON THE PORT the node is blocked at -- writing to it, or
|
|
* reading from that one port -- and there will not be. A node that can
|
|
* take an error (it has an error register and a fault table: node.h,
|
|
* v4_node_error_attach, v4_node_fault_attach) is let go and the error
|
|
* `code` is raised on it, as if it had stored the code to its error
|
|
* register: what it was doing ends there. Returns 1. A node that cannot
|
|
* stays blocked, as on the fabric, and 0 is returned; so is 0 for a node
|
|
* that is not blocked at a port, or is reading "any port". */
|
|
int v4_node_port_gone(v4_node *n, v4_cell code);
|
|
|
|
/* 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);
|
|
|
|
/* What is on the other end of the node's ports now: bit k of `writers` if
|
|
* the neighbour on port k is blocked writing to this node, bit k of
|
|
* `readers` if it is blocked reading from it and would take a word. */
|
|
void v4_node_port_status(v4_node *n, unsigned writers, unsigned readers);
|
|
|
|
/* 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.
|
|
*
|
|
* KEY and ?TERMINAL are device services too (DECOMPOSITION.md 5.10), and for
|
|
* the same reason as CONSOLE-TX the model stands in for the console with two
|
|
* memory-mapped registers and a queue of characters the test feeds.
|
|
*
|
|
* After v4_node_console_input_attach(n, rx, status), a data fetch (the
|
|
* opcodes @, @+ and @b, which go through v4_node_fetch) from word address
|
|
* `status` gives -1 when a character is pending and 0 when none is;
|
|
* `rx` gives the next pending character, 0 .. 255, and takes it off
|
|
* the queue; with none pending it gives -1 and takes nothing.
|
|
* Fetching the status changes nothing. Neither fetch reads or writes
|
|
* n->mem, a store to either address is an ordinary memory store, and
|
|
* v4_node_load -- which the executor uses to fetch instruction words and
|
|
* literals, and the assembler to patch them -- always reads memory, so code
|
|
* and literals are never mistaken for the registers.
|
|
*
|
|
* v4_node_console_feed appends characters to the queue and returns how many
|
|
* it took; the queue holds V4_CONSOLE_CAP characters that have not been
|
|
* read. A node has no input until it is attached: v4_node_reset detaches it
|
|
* (both addresses -1) and empties the queue. Attaching empties it too.
|
|
* Passing -1 for both detaches. The addresses are the caller's choice and
|
|
* must differ from each other and from CONSOLE-TX.
|
|
*
|
|
* Nothing here blocks. A KEY that must wait polls CONSOLE-STATUS, so on
|
|
* this model a wait is a loop that runs until the test feeds a character. */
|
|
void v4_node_console_input_attach(v4_node *n, v4_cell rx, v4_cell status);
|
|
unsigned v4_node_console_feed(v4_node *n, const void *chars, unsigned len);
|
|
|
|
/* A data fetch: what @, @+ and @b read at `addr`. The two receive registers
|
|
* above when attached, memory otherwise; 0 outside memory, as v4_node_load. */
|
|
v4_cell v4_node_fetch(v4_node *n, v4_cell addr);
|
|
|
|
/* 1 if `addr` is a word of node memory. */
|
|
int v4_node_addr_ok(v4_cell addr);
|
|
|
|
/* 1 if `addr` can be fetched from: a word of node memory, or of the ROM. */
|
|
int v4_node_addr_readable(const v4_node *n, v4_cell addr);
|
|
|
|
/* Give the machine its ROM: `words` words at V4_ROM_BASE, read from `rom`,
|
|
* which must outlive the node. More than V4_ROM_WORDS words, or a null
|
|
* `rom`, attaches none. v4_node_reset detaches it. */
|
|
void v4_node_rom_attach(v4_node *n, const v4_cell *rom, unsigned words);
|
|
|
|
/* FAULTS (DECOMPOSITION.md D-14 and D-16, ruled 2026-10-04: an address
|
|
* outside the node's memory, and a stack pushed when full or popped when
|
|
* empty, are guarded; an error is shown; the node returns to its prompt).
|
|
*
|
|
* The executor checks before every opcode (exec.h):
|
|
* - that the stacks hold what the opcode takes and have room for what it
|
|
* leaves;
|
|
* - every address a programme uses: P when an instruction word or an `@p`
|
|
* literal is fetched or `!p` stores, A for `@ @+ ! !+`, B for `@b !b`.
|
|
* If a check fails there is a fault:
|
|
* - the opcode does nothing, and the rest of its instruction word is not
|
|
* executed; A and B are untouched;
|
|
* - fault_kind says which, fault_addr is the address for an address fault,
|
|
* and faults is one more;
|
|
* - both stacks are emptied. The handler does not return to the
|
|
* programme, and what a word stopped in the middle of its work has left
|
|
* on the data stack is not something its caller can use. (A raised
|
|
* error, below, empties only the return stack: the word that raised it
|
|
* chose to, after taking its own arguments off);
|
|
* - P becomes fault_vector + fault_kind. The handler table is six words,
|
|
* one for each kind in the order below, each a jump to that kind's
|
|
* handler (capsules/v4/sys/quit.f18, (FAULTS)).
|
|
* With no table attached (fault_vector -1) the node stops instead: `stopped`
|
|
* is set and v4_exec_step_word does nothing more until the node is reset or
|
|
* a table is attached.
|
|
*
|
|
* v4_node_reset detaches the table and clears the count. Attaching clears
|
|
* `stopped` and the count; all six words of the table must be in memory,
|
|
* or `table` -1 to detach. */
|
|
#define V4_FAULT_ADDRESS 0u /* an address outside memory (D-14) */
|
|
#define V4_FAULT_DATA_OVER 1u /* a push onto a full data stack */
|
|
#define V4_FAULT_DATA_UNDER 2u /* the data stack holds too little for the opcode */
|
|
#define V4_FAULT_RET_OVER 3u /* a push onto a full return stack */
|
|
#define V4_FAULT_RET_UNDER 4u /* the return stack holds too little for the opcode */
|
|
#define V4_FAULT_RAISED 5u /* a programme stored an error code in NODE-ERROR (D-18) */
|
|
#define V4_FAULT_KINDS 6u
|
|
|
|
void v4_node_fault_attach(v4_node *n, v4_cell table);
|
|
|
|
/* For the executor: record a fault of `kind` (at `addr`, for an address
|
|
* fault) and redirect or stop the node, as above. */
|
|
void v4_node_fault(v4_node *n, unsigned kind, v4_cell addr);
|
|
|
|
/* DSTACK-DEPTH and RSTACK-DEPTH, the stack registers (D-16).
|
|
*
|
|
* After v4_node_stack_regs_attach(n, d, r):
|
|
* a data fetch from `d` gives how many values the data stack holds (the
|
|
* fetch's own push not counted), and from `r` how many entries the return
|
|
* stack holds;
|
|
* a store to `d` empties the data stack (the value stored is taken off
|
|
* first and ignored), and a store to `r` empties the return stack.
|
|
* Neither reads or writes n->mem. This is all a programme can see of the
|
|
* stacks: there is still no stack pointer and no address for a stack cell.
|
|
* DEPTH reads the first; QUIT and ABORT store to them. v4_node_reset
|
|
* detaches both (-1); the addresses are the caller's choice (D-4). */
|
|
void v4_node_stack_regs_attach(v4_node *n, v4_cell d, v4_cell r);
|
|
|
|
/* A BLOCK (DECOMPOSITION.md D-19, 5.11). Mass storage is the kernel's: a
|
|
* node asks it for a block by a request on port 0 (docs/v4.0.0/MESH.md
|
|
* section 8; blocks.h), and has nothing of it but these. A block is 1024 bytes, and a byte
|
|
* address is four times a word address plus 0 .. 3 (D-1), so a block is 256
|
|
* cells, four bytes to a cell, the first byte lowest, whatever the cell
|
|
* width. */
|
|
#define V4_BLOCK_BYTES 1024u
|
|
#define V4_BLOCK_CELLS 256u
|
|
|
|
/* NODE-ERROR as a trap (DECOMPOSITION.md D-18, ruled 2026-10-04: every error
|
|
* is guarded, shown, and returns the node to its prompt).
|
|
*
|
|
* A word that finds an error stores a code, which is not zero, in NODE-ERROR.
|
|
* After v4_node_error_attach(n, addr) a store of a non-zero value to `addr`
|
|
* writes that memory word, as before, and is then a fault of kind
|
|
* V4_FAULT_RAISED: the rest of the instruction word is not executed, the
|
|
* return stack is emptied, fault_addr is the code, and P becomes the sixth
|
|
* word of the fault table. The data stack is left alone. A store of zero
|
|
* -- which is how the flag is cleared -- is an ordinary store.
|
|
*
|
|
* Not attached (-1, as after v4_node_reset), NODE-ERROR is ordinary memory
|
|
* and a word that stores to it simply goes on: that is how a word can be run
|
|
* and its error seen with no prompt to return to. */
|
|
void v4_node_error_attach(v4_node *n, v4_cell addr);
|
|
|
|
#endif /* V4_NODE_H */
|