A read-only range of V4_ROM_WORDS words at V4_ROM_BASE, above RAM (MACHINE.md 2, DATASHEET-F18.md 5). Instruction fetch, @p and data fetches read it; a running programme's store there is an address fault. Attached by the owner like every device; a reset detaches it. The host node's range is 4096 words. text.c places branches by the whole address space. The nucleus image is byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
286 lines
8.8 KiB
C
286 lines
8.8 KiB
C
/* node.c -- the v4 node. See node.h. */
|
|
#include "v4/node.h"
|
|
#include <stddef.h>
|
|
|
|
void v4_node_reset(v4_node *n)
|
|
{
|
|
n->rom = NULL; /* attached again by the owner, as every device is */
|
|
n->rom_words = 0;
|
|
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);
|
|
v4_node_console_input_attach(n, -1, -1);
|
|
v4_node_fault_attach(n, -1);
|
|
v4_node_stack_regs_attach(n, -1, -1);
|
|
v4_node_error_attach(n, -1);
|
|
v4_node_port_attach(n, -1);
|
|
}
|
|
|
|
void v4_node_error_attach(v4_node *n, v4_cell addr)
|
|
{
|
|
n->error_reg = addr;
|
|
}
|
|
|
|
int v4_node_addr_ok(v4_cell addr)
|
|
{
|
|
/* One unsigned compare covers both ends: a negative address is a very
|
|
* large unsigned one. */
|
|
return (v4_ucell)addr < (v4_ucell)V4_NODE_WORDS;
|
|
}
|
|
|
|
void v4_node_stack_regs_attach(v4_node *n, v4_cell d, v4_cell r)
|
|
{
|
|
n->dstack_reg = d;
|
|
n->rstack_reg = r;
|
|
}
|
|
|
|
void v4_node_fault_attach(v4_node *n, v4_cell table)
|
|
{
|
|
n->fault_vector = (v4_node_addr_ok(table) && v4_node_addr_ok(table + (v4_cell)(V4_FAULT_KINDS - 1u))) ? table : (v4_cell)-1;
|
|
n->fault_addr = 0;
|
|
n->fault_kind = 0;
|
|
n->faults = 0;
|
|
n->stopped = 0;
|
|
}
|
|
|
|
void v4_node_fault(v4_node *n, unsigned kind, v4_cell addr)
|
|
{
|
|
n->fault_kind = kind;
|
|
n->fault_addr = addr;
|
|
n->faults++;
|
|
v4_rstack_clear(&n->rs);
|
|
if (kind != V4_FAULT_RAISED) v4_dstack_clear(&n->ds);
|
|
if (n->fault_vector >= 0) n->p = n->fault_vector + (v4_cell)kind;
|
|
else n->stopped = 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);
|
|
}
|
|
|
|
int v4_node_addr_readable(const v4_node *n, v4_cell addr)
|
|
{
|
|
return v4_node_addr_ok(addr)
|
|
|| ((v4_ucell)addr - (v4_ucell)V4_ROM_BASE) < (v4_ucell)n->rom_words;
|
|
}
|
|
|
|
void v4_node_rom_attach(v4_node *n, const v4_cell *rom, unsigned words)
|
|
{
|
|
if (!rom || words > V4_ROM_WORDS) { n->rom = NULL; n->rom_words = 0; return; }
|
|
n->rom = rom;
|
|
n->rom_words = words;
|
|
}
|
|
|
|
v4_cell v4_node_load(const v4_node *n, v4_cell addr)
|
|
{
|
|
if (v4_node_addr_ok(addr)) return n->mem[(unsigned)addr];
|
|
if (v4_node_addr_readable(n, addr)) return n->rom[(v4_ucell)addr - (v4_ucell)V4_ROM_BASE];
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
/* A port: the node keeps what it wrote and is blocked until its
|
|
* neighbour has taken it. */
|
|
{
|
|
int port = v4_node_port_index(n, addr);
|
|
if (port == (int)V4_PORT_WIRE) { n->wire = (value >= 0 && value < (v4_cell)V4_PORTS) ? (unsigned)value : V4_PORTS; return; }
|
|
if (port == (int)V4_PORT_A) { n->wire_a = value; return; }
|
|
if (port == (int)V4_PORT_B) { n->wire_b = value; return; }
|
|
if (port == (int)V4_PORT_DO) { n->do_op = value; n->doing = 1; n->spoke = 1; return; }
|
|
if (port >= 0 && port < (int)V4_PORTS) {
|
|
n->request = value;
|
|
n->ask_port = (unsigned)port;
|
|
n->asking = 1;
|
|
return;
|
|
}
|
|
if (port >= 0) { v4_node_fault(n, V4_FAULT_ADDRESS, addr); return; } /* "any", "which port", How and Have are not written to */
|
|
}
|
|
/* The stack registers: a store empties the stack. -1 when not attached. */
|
|
if (addr == n->dstack_reg) { v4_dstack_clear(&n->ds); return; }
|
|
if (addr == n->rstack_reg) { v4_rstack_clear(&n->rs); return; }
|
|
if (!v4_node_addr_ok(addr)) return;
|
|
n->mem[(unsigned)addr] = value;
|
|
/* NODE-ERROR: a non-zero code raises an error. -1 when not attached. */
|
|
if (addr == n->error_reg && value != 0) v4_node_fault(n, V4_FAULT_RAISED, value);
|
|
}
|
|
|
|
v4_cell v4_node_fetch(v4_node *n, v4_cell addr)
|
|
{
|
|
/* console_rx and console_status are -1 when no input is attached, which
|
|
* no valid address equals. */
|
|
if (addr == n->console_status)
|
|
return n->input_pos < n->input_len ? (v4_cell)-1 : (v4_cell)0;
|
|
if (addr == n->console_rx) {
|
|
v4_cell c;
|
|
if (n->input_pos >= n->input_len) return (v4_cell)-1;
|
|
c = (v4_cell)n->input[n->input_pos++];
|
|
if (n->input_pos == n->input_len) n->input_pos = n->input_len = 0;
|
|
return c;
|
|
}
|
|
{
|
|
int port = v4_node_port_index(n, addr);
|
|
if (port == (int)V4_PORTS + 1) return (v4_cell)n->last_from;
|
|
if (port == (int)V4_PORTS + 2) return (v4_cell)n->writers;
|
|
if (port == (int)V4_PORTS + 3) return (v4_cell)n->readers;
|
|
if (port == (int)V4_PORT_WIRE) return (v4_cell)n->wire;
|
|
if (port == (int)V4_PORT_A) return n->wire_a;
|
|
if (port == (int)V4_PORT_B) return n->wire_b;
|
|
if (port == (int)V4_PORT_DO) return n->do_op;
|
|
if (port == (int)V4_PORT_HOW) return n->how;
|
|
if (port == (int)V4_PORT_HAVE) { n->have_fetched = 1; return (v4_cell)n->have; }
|
|
if (port >= 0) {
|
|
/* the word its neighbour wrote; v4_node_read_ready has said it is there */
|
|
if (!n->given) return 0;
|
|
n->given = 0;
|
|
if (port == (int)V4_PORT_ANY) n->last_from = n->given_port;
|
|
return n->given_value;
|
|
}
|
|
}
|
|
if (addr == n->dstack_reg) return (v4_cell)n->ds.depth;
|
|
if (addr == n->rstack_reg) return (v4_cell)n->rs.depth;
|
|
return v4_node_load(n, addr);
|
|
}
|
|
|
|
void v4_node_console_input_attach(v4_node *n, v4_cell rx, v4_cell status)
|
|
{
|
|
n->console_rx = rx;
|
|
n->console_status = status;
|
|
n->input_len = 0;
|
|
n->input_pos = 0;
|
|
}
|
|
|
|
unsigned v4_node_console_feed(v4_node *n, const void *chars, unsigned len)
|
|
{
|
|
const unsigned char *c = (const unsigned char *)chars;
|
|
unsigned i, pending = n->input_len - n->input_pos, took = 0;
|
|
|
|
/* Slide what is pending to the front so the whole buffer is usable. */
|
|
if (n->input_pos > 0) {
|
|
for (i = 0; i < pending; i++) n->input[i] = n->input[n->input_pos + i];
|
|
n->input_pos = 0;
|
|
n->input_len = pending;
|
|
}
|
|
while (took < len && n->input_len < V4_CONSOLE_CAP) n->input[n->input_len++] = c[took++];
|
|
return took;
|
|
}
|
|
|
|
void v4_node_console_attach(v4_node *n, v4_cell addr)
|
|
{
|
|
n->console_tx = addr;
|
|
n->console_len = 0;
|
|
n->console_dropped = 0;
|
|
}
|
|
|
|
void v4_node_port_attach(v4_node *n, v4_cell base)
|
|
{
|
|
n->port = base;
|
|
n->asking = 0;
|
|
n->request = 0;
|
|
n->ask_port = 0;
|
|
n->reading = 0;
|
|
n->read_port = 0;
|
|
n->given = 0;
|
|
n->given_value = 0;
|
|
n->given_port = 0;
|
|
n->last_from = 0;
|
|
n->writers = 0;
|
|
n->readers = 0;
|
|
n->wire = 0;
|
|
n->wire_a = 0;
|
|
n->wire_b = 0;
|
|
n->doing = 0;
|
|
n->spoke = 0;
|
|
n->do_op = 0;
|
|
n->how = 0;
|
|
n->have = 0;
|
|
n->have_fetched = 0;
|
|
}
|
|
|
|
void v4_node_port_status(v4_node *n, unsigned writers, unsigned readers)
|
|
{
|
|
n->writers = writers;
|
|
n->readers = readers;
|
|
}
|
|
|
|
int v4_node_port_index(const v4_node *n, v4_cell addr)
|
|
{
|
|
if (n->port < 0 || addr < n->port || addr > n->port + (v4_cell)V4_PORT_HAVE) return -1;
|
|
return (int)(addr - n->port);
|
|
}
|
|
|
|
int v4_node_read_ready(v4_node *n, v4_cell addr)
|
|
{
|
|
int port = v4_node_port_index(n, addr);
|
|
if (port < 0 || port > (int)V4_PORT_ANY) return 1;
|
|
if (n->given && (port == (int)V4_PORT_ANY || (unsigned)port == n->given_port)) return 1;
|
|
n->reading = 1;
|
|
n->read_port = (unsigned)port;
|
|
return 0;
|
|
}
|
|
|
|
void v4_node_port_give(v4_node *n, unsigned port, v4_cell value)
|
|
{
|
|
n->given = 1;
|
|
n->given_value = value;
|
|
n->given_port = port;
|
|
n->reading = 0;
|
|
}
|
|
|
|
void v4_node_born(v4_node *n, v4_cell base)
|
|
{
|
|
v4_node_reset(n);
|
|
v4_node_port_attach(n, base);
|
|
n->p = base + (v4_cell)V4_PORT_ANY;
|
|
}
|
|
|
|
void v4_node_port_served(v4_node *n)
|
|
{
|
|
n->asking = 0;
|
|
}
|
|
|
|
int v4_node_doing(const v4_node *n)
|
|
{
|
|
return n->doing;
|
|
}
|
|
|
|
void v4_node_done(v4_node *n, v4_cell how)
|
|
{
|
|
n->how = how;
|
|
n->doing = 0;
|
|
}
|
|
|
|
void v4_node_have(v4_node *n, unsigned mask)
|
|
{
|
|
n->have = mask;
|
|
}
|
|
|
|
int v4_node_port_gone(v4_node *n, v4_cell code)
|
|
{
|
|
if (code == 0 || n->error_reg < 0 || n->fault_vector < 0 || n->stopped) return 0;
|
|
if (n->asking) n->asking = 0;
|
|
else if (n->reading && !n->given && n->read_port != V4_PORT_ANY) n->reading = 0;
|
|
else return 0;
|
|
v4_node_store(n, n->error_reg, code); /* raises it: P is the node's handler now */
|
|
return 1;
|
|
}
|