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:
rajames
2026-10-06 07:58:00 -04:00
co-authored by Claude Opus 5.5
parent 2c5427d9c1
commit 9af442f793
17 changed files with 1170 additions and 39 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated # Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-10-05T23:35:59Z --> <!-- Generated by mkcapsule --manifest 2026-10-06T11:54:53Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. --> <!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live --> <!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. --> <!-- in MANIFEST.md alongside this auto-generated index. -->
+13 -3
View File
@@ -1049,9 +1049,19 @@ The runtime governor moves into hardware as the multi-level Rolling Window of Tr
## 6. Messaging between nodes ## 6. Messaging between nodes
Each node has four neighbour ports (`PORT-UP`, `PORT-DOWN`, `PORT-LEFT`, `PORT-RIGHT`) mapped into its Each node has ports mapped into its address space. A read from a port blocks until the neighbour writes;
address space. A read from a port blocks until the neighbour writes; a write blocks until the neighbour a write blocks until the neighbour reads. This is the GA144 model. No instruction is added: a port is an
reads. This is the GA144 model. No instruction is added: a port is an address. address.
**How many ports, and what they connect to (ruled 2026-10-05 and -06; `MESH.md`).** This section first
gave a node four ports, `PORT-UP`, `PORT-DOWN`, `PORT-LEFT` and `PORT-RIGHT`: a flat grid. That is
withdrawn. The number of ports is a build parameter of the node (`V4_PORTS`), as cell width and node
memory are, and which port connects to what is a table that can change while the system runs — "not
constrained by a 3D world; other geometries might be better." The ports are numbered, not named for
directions. Two addresses follow them: "any port", a read of which takes from whichever port has a
neighbour writing, and the port the last such read came from. A node at reset has `P` at "any port" and
executes what a neighbour sends it. Built and tested in the golden model 2026-10-06 (`v4/src/node.c`,
`exec.c`, `fabric.c`; `v4/tests/test_fabric.c`).
### 6.1 Packet format (proposal) ### 6.1 Packet format (proposal)
+10
View File
@@ -252,6 +252,16 @@ Each is tested, committed and pushed before the next.
words; an empty node is filled through its port and runs what it was words; an empty node is filled through its port and runs what it was
sent; a word is passed on by a node in between; a node waiting executes sent; a word is passed on by a node in between; a node waiting executes
nothing; a node is put to sleep, woken, and removed while looping. nothing; a node is put to sleep, woken, and removed while looping.
**Done 2026-10-06.** `v4/src/node.c` (the ports, `v4_node_born`),
`v4/src/exec.c` (a fetch from a port waits; execution from a port; the
slot a blocked node goes on from), `v4/src/fabric.c`. `V4_PORTS` is 8.
`v4/tests/test_fabric.c`, 53 checks at both cell widths and under ASan
and UBSan: all of the above, with the empty node filled once by a device
and once by another node holding the capsule in its own memory; the
fabric given more room while nodes run; what cannot be wired refused.
The single-node products are unchanged by it: `hosted-check` on three
ISAs, and the three bare-metal boots with lines typed at each prompt
(`logs/20261006-074907`, `-075150`, `-075532`).
2. **The nucleus as a capsule, and an empty node made a StarForth node 2. **The nucleus as a capsule, and an empty node made a StarForth node
through its port (section 5).** through its port (section 5).**
3. **Messages (section 6):** a StarForth node that reads messages when 3. **Messages (section 6):** a StarForth node that reads messages when
+1 -1
View File
@@ -613,7 +613,7 @@ V4_DEFS := -DSTARFORTH_V4=1 -Iv4/include \
KERNEL_CFLAGS += $(V4_DEFS) KERNEL_CFLAGS += $(V4_DEFS)
LOADER_CFLAGS += $(V4_DEFS) LOADER_CFLAGS += $(V4_DEFS)
V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c) V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c)
V4_IMAGE_C := $(BUILD_DIR)/v4_image_64.c V4_IMAGE_C := $(BUILD_DIR)/v4_image_64.c
LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c
@@ -0,0 +1,129 @@
[=3hBdsDxe: loading Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
BdsDxe: starting Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
RAW SERIAL UP
[CKPT 002] Serial (COM1) initialized
Monolithic build - kernel linked directly
Collecting boot information...
CmdLine: parsed OK
[CKPT 004] Command line parsed
[CKPT 005] Kernel stack allocation decided
[CKPT 006] Boot info collected (ACPI table located)
GOP: linear framebuffer found
[CKPT 007] GOP: linear framebuffer found
[CKPT 008] About to enter ExitBootServices retry loop
EBS...
EBS OK
Calling kernel_main (monolithic)...
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: amd64
Build: Oct 6 2026 07:48:38
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 131
Total memory: 1023 MB
Usable memory: 968 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 249243
Free pages : 247720
Used pages : 1523
Total MB : 973
Free MB : 967
Used MB : 5
VMM initialized (mapped RAM, CR3 switched)
VMM initialized (mapped RAM, CR3 switched)
VMM self-test: mapped OK at 0xffff800000000000
VMM self-test complete.
IDT installed.
APIC: init...
APIC: IA32_APIC_BASE MSR=0x00000000fee00900
APIC: stale-ISR drain: 0 EOI(s) issued
APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear)
APIC: init done
I/O APIC: init...
I/O APIC: base=0xfec00000, gsi_base=0, overrides=5
override: source=00 gsi=2 flags=0x0000
override: source=05 gsi=5 flags=0x000d
override: source=09 gsi=9 flags=0x000d
override: source=0a gsi=10 flags=0x000d
override: source=0b gsi=11 flags=0x000d
i8042: keyboard ACKed enable-scanning
i8042: IRQ1 enabled in controller config byte
I/O APIC: keyboard IRQ1 routed (masked)
Timer: init...
Timer: init start
Timer: PM_TMR_BLK discovered from FADT at port 1544
Timer: VM mode detected (hypervisor present).
Timer: HPET calibration disabled (VM-exit MMIO would poison timing).
Timer: WARNING: invariant TSC not present under hypervisor.
Timer: continuing in RELATIVE mode (no determinism guarantees).
Timer: RDTSCP not present; using RDTSC (less serialized).
Timer: CPUID frequency unavailable; trying PM Timer...
Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=2101992017 Hz
Timer: init done
Kernel heap initialized.
Heap statistics:
Total bytes: 536870872
Free bytes: 536870872
Used bytes: 0
Peak bytes: 0
Heap base addr: 24641536
Heap end addr: 561512448
Heartbeat: init...
APIC Timer: calibrating...
APIC Timer: apic_hz=1007298008, tick_hz=100, initial_count=10072980
APIC Timer: configured (masked, ready to start)
Heartbeat: init done
Kernel initialization complete.
Boot successful!
StarForth v4: one host node, the F18-derived engine
PARITY:V4_NUCLEUS words=298 image_hash=0xd982cf093aadb8b9
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x88fb8dcbb051f7b9
V4: capsule v4:post79.4th signature: missing (unsigned)
PARITY:V4_POST tests=550 pass=550 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x843716689a31b60a
PARITY:OK
POST: PASSED
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
ok> COLD
FORTH-79 Cold Start
System initialized.
ok
ok> 3 4 U* . .
0 12 ok
ok> FORGET U*
Protected word
ERROR
ok> 9 KERNEL-WORD ASK9 ASK9
Argument out of range
ERROR
ok> BYE
BYE: cold restart
@@ -0,0 +1,103 @@
UEFI firmware (version 2025.11-3ubuntu7.3 built at 15:40:26 on Sep 23 2026)
[=3hBdsDxe: failed to load Boot0002 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x2,0x0): Not Found
BdsDxe: failed to load Boot0003 "UEFI QEMU QEMU USB HARDDRIVE 1-0000:00:04.0-1" from PciRoot(0x0)/Pci(0x4,0x0)/USB(0x0,0x0): Not Found
BdsDxe: loading Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0)
BdsDxe: starting Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0)
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
Collecting boot information...
[CKPT 004] Command line parsed
[CKPT 005] Kernel stack allocation decided
[CKPT 006] Boot info collected (ACPI table located)
[CKPT 007] GOP: linear framebuffer found
[CKPT 008] About to enter ExitBootServices retry loop
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: aarch64
Build: Oct 6 2026 07:51:22
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 108
Total memory: 4093 MB
Usable memory: 4056 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 1039860
Free pages : 1038449
Used pages : 1411
Total MB : 4061
Free MB : 4056
Used MB : 5
VMM initialized (mapped RAM, CR3 switched)
VMM initialized (mapped RAM, CR3 switched)
VMM self-test: mapped OK at 0xffff800000000000
VMM self-test complete.
AArch64: running at EL1
IDT installed.
APIC: init...
PSCI: no DTB -- using HVC (QEMU virt-machine default)
GICv2: no DTB GIC node -- using QEMU virt-machine defaults
GICv2: distributor+CPU interface enabled, PPI 30
APIC: init done
Timer: init...
Timer: AArch64 generic timer initialised.
Timer: init done
Kernel heap initialized.
Heap statistics:
Total bytes: 2147483608
Free bytes: 2147483608
Used bytes: 0
Peak bytes: 0
Heap base addr: 1207959552
Heap end addr: 3355443200
Heartbeat: init...
Heartbeat: init done
Kernel initialization complete.
Boot successful!
StarForth v4: one host node, the F18-derived engine
PARITY:V4_NUCLEUS words=298 image_hash=0xd982cf093aadb8b9
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x88fb8dcbb051f7b9
V4: capsule v4:post79.4th signature: missing (unsigned)
PARITY:V4_POST tests=550 pass=550 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x843716689a31b60a
PARITY:OK
POST: PASSED
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
ok> COLD
FORTH-79 Cold Start
System initialized.
ok
ok> 3 4 U* . .
0 12 ok
ok> FORGET U*
Protected word
ERROR
ok> 9 KERNEL-WORD ASK9 ASK9
Argument out of range
ERROR
ok> BYE
BYE: cold restart
@@ -0,0 +1,173 @@
OpenSBI v1.8
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
Platform Name : riscv-virtio,qemu
Platform Features : medeleg
Platform HART Count : 1
Platform HART Protection : pmp
Platform IPI Device : aclint-mswi
Platform Timer Device : aclint-mtimer @ 10000000Hz
Platform Console Device : uart8250
Platform HSM Device : ---
Platform PMU Device : ---
Platform Reboot Device : syscon-reboot
Platform Shutdown Device : syscon-poweroff
Platform Suspend Device : ---
Platform CPPC Device : ---
Firmware Base : 0x80000000
Firmware Size : 321 KB
Firmware RW Offset : 0x40000
Firmware RW Size : 65 KB
Firmware Heap Offset : 0x47000
Firmware Heap Size : 37 KB (total), 0 KB (reserved), 12 KB (used), 23 KB (free)
Firmware Scratch Size : 4096 B (total), 1464 B (used), 2632 B (free)
Runtime SBI Version : 3.0
Standard SBI Extensions : ipi,pmu,srst,sse,hsm,rfnc,fwft,time,base,legacy,dbcn,dbtr
Experimental SBI Extensions : none
Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080040000-0x000000008005ffff M: (F,R,W) S/U: ()
Domain0 Region01 : 0x0000000080000000-0x000000008003ffff M: (F,R,X) S/U: ()
Domain0 Region02 : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
Domain0 Region03 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region04 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region05 : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06 : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address : 0x0000000020000000
Domain0 Next Arg1 : 0x00000000bfe00000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes
Domain0 SysSuspend : yes
Boot HART ID : 0
Boot HART Domain : root
Boot HART Priv Version : v1.12
Boot HART Base ISA : rv64imafdch
Boot HART ISA Extensions : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu
Boot HART PMP Count : 16
Boot HART PMP Granularity : 2 bits
Boot HART PMP Address Bits : 54
Boot HART MHPM Info : 16 (0x0007fff8)
Boot HART Debug Triggers : 2 triggers
Boot HART MIDELEG : 0x0000000000001666
Boot HART MEDELEG : 0x0000000000f4b509
[=3hRISC-V EDK2 firmware version 2025.11-3ubuntu7.3
Press ESCAPE within 5 seconds for boot options ERROR: C40000002:V03051002 I0 6D33944A-EC75-4855-A54D-809C75241F6C 83FFF850
BdsDxe: failed to load Boot0001 "UEFI Misc Device" fr
om PciRoot(0x0)/Pci(0x1,0x0): Not Found
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
Monolithic build - kernel linked directly
Collecting boot information...
CmdLine: parsed OK
[CKPT 004] Command line parsed
[CKPT 005] Kernel stack allocation decided
[CKPT 006] Boot info collected (ACPI table located)
GOP: linear framebuffer found
[CKPT 007] GOP: linear framebuffer found
[CKPT 008] About to enter ExitBootServices retry loop
Calling kernel_main (monolithic)...
riscv64 item 4.3.5a: satp state at kernel entry (before switch)
satp.MODE = 0x000000000000000a
satp.PPN = 0x00000000000bf868
__kernel_start = 0x00000000bde958f8
riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a)
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: riscv64
Build: Oct 6 2026 07:54:53
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 100
Total memory: 1020 MB
Usable memory: 977 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 250773
Free pages : 250121
Used pages : 652
Total MB : 979
Free MB : 977
Used MB : 2
VMM initialized (mapped RAM, CR3 switched)
VMM initialized (mapped RAM, CR3 switched)
VMM self-test: mapped OK at 0xffff800000000000
VMM self-test complete.
IDT installed.
APIC: init...
PLIC: no DTB PLIC node -- using QEMU virt-machine default (base=0x0c000000)
PLIC: init (S-mode context 1, threshold=0)
APIC: init done
Timer: init...
Timer: RISC-V time CSR @ 10000000 Hz (FALLBACK, no devicetree)
Timer: init done
Kernel heap initialized.
Heap statistics:
Total bytes: 536870872
Free bytes: 536870872
Used bytes: 0
Peak bytes: 0
Heap base addr: 2214588416
Heap end addr: 2751459328
Heartbeat: init...
Heartbeat: init done
Kernel initialization complete.
Boot successful!
StarForth v4: one host node, the F18-derived engine
PARITY:V4_NUCLEUS words=298 image_hash=0xd982cf093aadb8b9
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x88fb8dcbb051f7b9
V4: capsule v4:post79.4th signature: missing (unsigned)
PARITY:V4_POST tests=550 pass=550 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x843716689a31b60a
PARITY:OK
POST: PASSED
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
ok> COLD
FORTH-79 Cold Start
System initialized.
ok
ok> 3 4 U* . .
0 12 ok
ok> FORGET U*
Protected word
ERROR
ok> 9 KERNEL-WORD ASK9 ASK9
Argument out of range
ERROR
ok> BYE
BYE: cold restart
+1 -1
View File
@@ -61,7 +61,7 @@ capsule_dir := -DV4_CAPSULE_DIR='"$(HERE)/capsule"'
# The hosted system below and the bare-metal kernel (kernel/Makefile, # The hosted system below and the bare-metal kernel (kernel/Makefile,
# STARFORTH_V4=1) link the 64-bit one. docs/v4.0.0/NUCLEUS.md. # STARFORTH_V4=1) link the 64-bit one. docs/v4.0.0/NUCLEUS.md.
HOST_DEFS := -DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING) HOST_DEFS := -DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING)
ENGINE_SRCS := $(addprefix $(HERE)/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c) ENGINE_SRCS := $(addprefix $(HERE)/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c)
define IMAGE_RULE define IMAGE_RULE
$(BINDIR)/mkimage-$(1): $(HERE)/tools/mkimage.c $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/tests/host_map.h $(HERE)/Makefile $(BINDIR)/mkimage-$(1): $(HERE)/tools/mkimage.c $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/tests/host_map.h $(HERE)/Makefile
+13 -3
View File
@@ -59,7 +59,7 @@ State, 2026-10-05, after `ENGINE.md` step 1: all six builds start the nucleus,
load `v4:forth79.4th`, pass POST and reach `ok>`, with the same lines: load `v4:forth79.4th`, pass POST and reach `ok>`, with the same lines:
``` ```
PARITY:V4_NUCLEUS words=298 image_hash=0x2e02147b54f9ff47 PARITY:V4_NUCLEUS words=298 image_hash=0xd982cf093aadb8b9
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=... PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=...
PARITY:V4_POST tests=550 pass=550 fail=0 PARITY:V4_POST tests=550 pass=550 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=... dict_hash=... PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=... dict_hash=...
@@ -68,8 +68,8 @@ POST: PASSED
ok> ok>
``` ```
Bare-metal logs: `logs/20261005-193045/amd64/`, `logs/20261005-193307/aarch64/`, Bare-metal logs: `logs/20261006-074907/amd64/`, `logs/20261006-075150/aarch64/`,
`logs/20261005-193636/riscv64/`. On each, seven lines were then typed at the `logs/20261006-075532/riscv64/`. On each, seven lines were then typed at the
prompt through the serial port — a definition, its use with the capsule's prompt through the serial port — a definition, its use with the capsule's
`U*`, `COLD`, the capsule word again, a `FORGET` of it (refused), a kernel `U*`, `COLD`, the capsule word again, a `FORGET` of it (refused), a kernel
word no one serves, and `BYE` — and each was answered as the hosted binary word no one serves, and `BYE` — and each was answered as the hosted binary
@@ -101,6 +101,16 @@ product is its own and may become a hosted SDK. The bare-metal product is
to be LithosAnanke with the node in the StarForth VM's place, and to be LithosAnanke with the node in the StarForth VM's place, and
everything about word records, the fleet and identity is its alone. everything about word records, the fleet and identity is its alone.
**Nodes that talk** (`docs/v4.0.0/MESH.md`) are the next step, ahead of
making v4 equal v3 on bare metal. Its first part is built and tested in
the engine: a node has `V4_PORTS` ports; a read from one blocks until the
neighbour writes and a write until the neighbour reads; a node is born
empty with `P` at its ports and executes what a neighbour sends it; and
the fabric (`v4/src/fabric.c`) is the nodes there are and the table of how
their ports are wired, both of which change while they run. The products
below are still one node each; they become the five nodes at `MESH.md`
steps 8 and 9.
**This is still the lone node.** On bare metal `kernel_main.c` starts it **This is still the lone node.** On bare metal `kernel_main.c` starts it
before the fleet tables, beside the kernel's own system and not in the VM's before the fleet tables, beside the kernel's own system and not in the VM's
place. `ENGINE.md` sets out the steps from here; step 1 is done and step 2 is place. `ENGINE.md` sets out the steps from here; step 1 is done and step 2 is
+99
View File
@@ -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
View File
@@ -36,6 +36,14 @@
#include "v4/stack.h" #include "v4/stack.h"
/* Words of node memory. Overridable at build time. */ /* 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 #ifndef V4_NODE_WORDS
#define V4_NODE_WORDS 1024u #define V4_NODE_WORDS 1024u
#endif #endif
@@ -91,9 +99,16 @@ typedef struct {
/* The port (DECOMPOSITION.md section 6): a write to it blocks the node /* The port (DECOMPOSITION.md section 6): a write to it blocks the node
* until its neighbour has taken what was written. See * until its neighbour has taken what was written. See
* v4_node_port_attach below. */ * v4_node_port_attach below. */
v4_cell port; /* its word address, or -1: no port */ v4_cell port; /* word address of port 0, or -1: no ports */
int asking; /* non-zero: blocked, having written `request` */ int asking; /* non-zero: blocked, having written `request` to port `ask_port` */
v4_cell request; /* what it wrote */ 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. */ /* DSTACK-DEPTH and RSTACK-DEPTH (D-16). See v4_node_stack_regs_attach. */
v4_cell dstack_reg; /* its word address, or -1 */ 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. */ * Passing -1 detaches. The address is the caller's choice. */
void v4_node_console_attach(v4_node *n, v4_cell addr); 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 /* THE PORTS. A node reaches its neighbours through its ports, and a port is
* address (DECOMPOSITION.md section 6): "a write blocks until the neighbour * an address (DECOMPOSITION.md section 6): "a read from a port blocks until
* reads. This is the GA144 model. No instruction is added." * 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 * After v4_node_port_attach(n, base) the node's V4_PORTS ports are the word
* not write memory: the node keeps the value in n->request, sets n->asking * addresses base .. base + V4_PORTS - 1. Two more follow them:
* and is blocked. A blocked node executes nothing -- v4_exec_step_word * base + V4_PORTS "any port": a read here takes from whichever port
* returns 0 and changes nothing -- until its neighbour calls * has a neighbour writing
* v4_node_port_served(n). It then goes on from the opcode after the store, * base + V4_PORTS + 1 which port the last read from "any" came from
* in the same instruction word if there are any left. * (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 * A WRITE. A store to a port does not write memory: the node keeps the
* 3.3): the neighbour on the port is the kernel, what is written is the * value in n->request and the port in n->ask_port, sets n->asking and is
* number of the request, and the arguments and results are on the node's * blocked. When its neighbour has taken the value -- v4_node_port_served --
* data stack, which the kernel may use while the node is blocked. Only the * it goes on from the opcode after the store, in the same instruction word
* write is here. Nothing reads from a port yet, and a fetch from the * if there are any left.
* address is a fetch from memory.
* *
* A node has no port until one is attached: v4_node_reset detaches it. */ * A READ. A fetch from a port -- @ @b @+, the literal fetch @p, or the
void v4_node_port_attach(v4_node *n, v4_cell addr); * 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); 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 /* CONSOLE-RX and CONSOLE-STATUS, the console's receive side, on the
* single-node model. * single-node model.
* *
+31 -4
View File
@@ -143,20 +143,30 @@ void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h,
case V4_OP_FETCH_P: case V4_OP_FETCH_P:
if (faulted(n, n->p)) return; if (faulted(n, n->p)) return;
if (v4_node_port_index(n, n->p) >= 0) {
/* P is a port: the literal is the next word its neighbour
* writes, and P stays where it is */
if (!v4_node_read_ready(n, n->p)) return;
v4_dstack_push(ds, v4_node_fetch(n, n->p));
break;
}
v4_dstack_push(ds, v4_node_load(n, n->p)); v4_dstack_push(ds, v4_node_load(n, n->p));
n->p = add_wrap(n->p, 1); n->p = add_wrap(n->p, 1);
break; break;
case V4_OP_FETCH_INC: case V4_OP_FETCH_INC:
if (faulted(n, n->a)) return; if (faulted(n, n->a)) return;
if (!v4_node_read_ready(n, n->a)) return; /* a port with nothing written to it yet */
v4_dstack_push(ds, v4_node_fetch(n, n->a)); v4_dstack_push(ds, v4_node_fetch(n, n->a));
n->a = add_wrap(n->a, 1); n->a = add_wrap(n->a, 1);
break; break;
case V4_OP_FETCH_B: case V4_OP_FETCH_B:
if (faulted(n, n->b)) return; if (faulted(n, n->b)) return;
if (!v4_node_read_ready(n, n->b)) return; /* a port with nothing written to it yet */
v4_dstack_push(ds, v4_node_fetch(n, n->b)); v4_dstack_push(ds, v4_node_fetch(n, n->b));
break; break;
case V4_OP_FETCH_A: case V4_OP_FETCH_A:
if (faulted(n, n->a)) return; if (faulted(n, n->a)) return;
if (!v4_node_read_ready(n, n->a)) return; /* a port with nothing written to it yet */
v4_dstack_push(ds, v4_node_fetch(n, n->a)); v4_dstack_push(ds, v4_node_fetch(n, n->a));
break; break;
case V4_OP_STORE_P: case V4_OP_STORE_P:
@@ -250,18 +260,25 @@ unsigned v4_exec_step_word(v4_node *n, v4_exec_state *es, v4_heat *h)
/* D-14: a node that faulted with no handler has stopped. A node that /* D-14: a node that faulted with no handler has stopped. A node that
* has written to its port is blocked until it has been served. */ * has written to its port is blocked until it has been served. */
if (n->stopped || n->asking) return 0; if (n->stopped || n->asking || n->reading) return 0;
if (es->resume && es->resume_faults != n->faults) es->resume = 0; /* it faulted while blocked: P is the handler */ if (es->resume && es->resume_faults != n->faults) es->resume = 0; /* it faulted while blocked: P is the handler */
if (es->resume) { if (es->resume) {
/* served: the rest of the word the store was in */ /* unblocked: the rest of the word it was in */
es->resume = 0; es->resume = 0;
iw = es->resume_iw; iw = es->resume_iw;
slot = es->resume_slot; slot = es->resume_slot;
} else { } else {
/* P itself may be what is outside memory */ /* P itself may be what is outside memory */
if (faulted(n, n->p)) return 0; if (faulted(n, n->p)) return 0;
iw = fetch_iword(n, n->p); if (v4_node_port_index(n, n->p) >= 0) {
n->p = add_wrap(n->p, 1); /* P is a port: the node executes what its neighbour writes
* there, and P stays where it is */
if (!v4_node_read_ready(n, n->p)) return 0;
iw = (v4_iword)((v4_ucell)v4_node_fetch(n, n->p) & 0xFFFFFFFFu);
} else {
iw = fetch_iword(n, n->p);
n->p = add_wrap(n->p, 1);
}
} }
while (slot < V4_SLOT_COUNT) { while (slot < V4_SLOT_COUNT) {
@@ -271,6 +288,16 @@ unsigned v4_exec_step_word(v4_node *n, v4_exec_state *es, v4_heat *h)
unsigned faults = n->faults; unsigned faults = n->faults;
v4_exec_op(n, es, h, op, iw, slot); v4_exec_op(n, es, h, op, iw, slot);
if (n->reading) {
/* The fetch was from a port with nothing written to it yet. The
* opcode has not executed: the node waits here, and this slot
* is where it goes on from. */
es->resume = 1;
es->resume_iw = iw;
es->resume_slot = slot;
es->resume_faults = n->faults;
break;
}
if (n->faults != faults) { /* P is the handler now, or the node has stopped */ if (n->faults != faults) { /* P is the handler now, or the node has stopped */
/* a store that raised an error (D-18) did execute; any other fault means the opcode did not */ /* a store that raised an error (D-18) did execute; any other fault means the opcode did not */
if (n->fault_kind == V4_FAULT_RAISED) executed++; if (n->fault_kind == V4_FAULT_RAISED) executed++;
+171
View File
@@ -0,0 +1,171 @@
/* fabric.c -- nodes, wiring, and time passing for all of them. See fabric.h.
*
* Nothing here uses the C library: the bare-metal kernel links this file.
*/
#include "v4/fabric.h"
static void clear_place(v4_place *p)
{
unsigned k;
p->node = 0;
p->asleep = 0;
for (k = 0; k < V4_PORTS; k++) { p->wire[k].kind = V4_WIRE_NONE; p->wire[k].node = 0; p->wire[k].port = 0; p->wire[k].device = 0; }
}
void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity)
{
unsigned i;
f->place = places;
f->capacity = capacity;
for (i = 0; i < capacity; i++) clear_place(&places[i]);
}
void v4_fabric_grow(v4_fabric *f, v4_place *places, unsigned capacity)
{
unsigned i;
if (capacity < f->capacity) return;
for (i = 0; i < f->capacity; i++) places[i] = f->place[i];
for (; i < capacity; i++) clear_place(&places[i]);
f->place = places;
f->capacity = capacity;
}
int v4_fabric_add(v4_fabric *f, v4_fabric_node *storage, v4_cell port_base)
{
unsigned i;
for (i = 0; i < f->capacity && f->place[i].node; i++) { }
if (i == f->capacity || !storage) return -1;
clear_place(&f->place[i]);
f->place[i].node = storage;
v4_node_born(&storage->n, port_base);
v4_exec_reset(&storage->es);
v4_heat_reset(&storage->h);
return (int)i;
}
v4_fabric_node *v4_fabric_node_at(const v4_fabric *f, unsigned id)
{
return id < f->capacity ? f->place[id].node : 0;
}
static int there(const v4_fabric *f, unsigned id, unsigned port)
{
return id < f->capacity && f->place[id].node && port < V4_PORTS;
}
/* cut what is on a port, at both ends */
static void cut(v4_fabric *f, unsigned a, unsigned pa)
{
v4_wire *w = &f->place[a].wire[pa];
if (w->kind == V4_WIRE_NODE && w->node < f->capacity && w->port < V4_PORTS) {
v4_wire *other = &f->place[w->node].wire[w->port];
if (other->kind == V4_WIRE_NODE && other->node == a && other->port == pa) { other->kind = V4_WIRE_NONE; other->device = 0; }
}
w->kind = V4_WIRE_NONE;
w->device = 0;
}
int v4_fabric_wire(v4_fabric *f, unsigned a, unsigned pa, unsigned b, unsigned pb)
{
if (!there(f, a, pa) || !there(f, b, pb) || (a == b && pa == pb)) return 0;
cut(f, a, pa);
cut(f, b, pb);
f->place[a].wire[pa].kind = V4_WIRE_NODE; f->place[a].wire[pa].node = b; f->place[a].wire[pa].port = pb;
f->place[b].wire[pb].kind = V4_WIRE_NODE; f->place[b].wire[pb].node = a; f->place[b].wire[pb].port = pa;
return 1;
}
int v4_fabric_wire_device(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device)
{
if (!there(f, a, pa) || !device) return 0;
cut(f, a, pa);
f->place[a].wire[pa].kind = V4_WIRE_DEVICE;
f->place[a].wire[pa].device = device;
return 1;
}
int v4_fabric_unwire(v4_fabric *f, unsigned a, unsigned pa)
{
if (!there(f, a, pa)) return 0;
cut(f, a, pa);
return 1;
}
v4_fabric_node *v4_fabric_remove(v4_fabric *f, unsigned id)
{
v4_fabric_node *storage;
unsigned k;
if (id >= f->capacity || !f->place[id].node) return 0;
for (k = 0; k < V4_PORTS; k++) cut(f, id, k);
storage = f->place[id].node;
clear_place(&f->place[id]);
return storage;
}
void v4_fabric_sleep(v4_fabric *f, unsigned id)
{
if (id < f->capacity && f->place[id].node) f->place[id].asleep = 1;
}
void v4_fabric_wake(v4_fabric *f, unsigned id)
{
if (id < f->capacity && f->place[id].node) f->place[id].asleep = 0;
}
static int awake(const v4_fabric *f, unsigned id)
{
return id < f->capacity && f->place[id].node && !f->place[id].asleep;
}
/* What node `a` wrote to one of its ports, handed to whatever is there. */
static unsigned hand_over_write(v4_fabric *f, unsigned a)
{
v4_node *n = &f->place[a].node->n;
const v4_wire *w = &f->place[a].wire[n->ask_port];
if (w->kind == V4_WIRE_DEVICE) {
if (w->device->take && w->device->take(w->device->self, n->request)) { v4_node_port_served(n); return 1; }
} else if (w->kind == V4_WIRE_NODE && awake(f, w->node)) {
v4_node *r = &f->place[w->node].node->n;
if (r->reading && !r->given && (r->read_port == w->port || r->read_port == V4_PORT_ANY)) {
v4_node_port_give(r, w->port, n->request);
v4_node_port_served(n);
return 1;
}
}
return 0;
}
/* Node `a` is reading: a device on that port, or on any of them, is asked. */
static unsigned hand_over_read(v4_fabric *f, unsigned a)
{
v4_node *n = &f->place[a].node->n;
unsigned first = n->read_port == V4_PORT_ANY ? 0 : n->read_port;
unsigned last = n->read_port == V4_PORT_ANY ? V4_PORTS - 1 : n->read_port;
unsigned k;
v4_cell value;
for (k = first; k <= last; k++) {
const v4_wire *w = &f->place[a].wire[k];
if (w->kind == V4_WIRE_DEVICE && w->device->give && w->device->give(w->device->self, &value)) {
v4_node_port_give(n, k, value);
return 1;
}
}
return 0;
}
unsigned v4_fabric_step(v4_fabric *f)
{
unsigned i, done = 0;
for (i = 0; i < f->capacity; i++)
if (awake(f, i)) done += v4_exec_step_word(&f->place[i].node->n, &f->place[i].node->es, &f->place[i].node->h);
for (i = 0; i < f->capacity; i++)
if (awake(f, i) && f->place[i].node->n.asking) done += hand_over_write(f, i);
for (i = 0; i < f->capacity; i++)
if (awake(f, i) && f->place[i].node->n.reading && !f->place[i].node->n.given) done += hand_over_read(f, i);
return done;
}
+63 -5
View File
@@ -120,9 +120,18 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value)
n->console_dropped++; n->console_dropped++;
return; return;
} }
/* The port: the node keeps what it wrote and is blocked until its /* A port: the node keeps what it wrote and is blocked until its
* neighbour has taken it. -1 when no port is attached. */ * neighbour has taken it. */
if (addr == n->port) { n->request = value; n->asking = 1; return; } {
int port = v4_node_port_index(n, addr);
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" and "which port" are not written to */
}
/* The stack registers: a store empties the stack. -1 when not attached. */ /* 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->dstack_reg) { v4_dstack_clear(&n->ds); return; }
if (addr == n->rstack_reg) { v4_rstack_clear(&n->rs); return; } if (addr == n->rstack_reg) { v4_rstack_clear(&n->rs); return; }
@@ -147,6 +156,17 @@ v4_cell v4_node_fetch(v4_node *n, v4_cell addr)
if (n->input_pos == n->input_len) n->input_pos = n->input_len = 0; if (n->input_pos == n->input_len) n->input_pos = n->input_len = 0;
return c; return c;
} }
{
int port = v4_node_port_index(n, addr);
if (port == (int)V4_PORTS + 1) return (v4_cell)n->last_from;
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->dstack_reg) return (v4_cell)n->ds.depth;
if (addr == n->rstack_reg) return (v4_cell)n->rs.depth; if (addr == n->rstack_reg) return (v4_cell)n->rs.depth;
return v4_node_load(n, addr); return v4_node_load(n, addr);
@@ -182,11 +202,49 @@ void v4_node_console_attach(v4_node *n, v4_cell addr)
n->console_dropped = 0; n->console_dropped = 0;
} }
void v4_node_port_attach(v4_node *n, v4_cell addr) void v4_node_port_attach(v4_node *n, v4_cell base)
{ {
n->port = addr; n->port = base;
n->asking = 0; n->asking = 0;
n->request = 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;
}
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_PORTS + 1) 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_PORTS + 1) 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) void v4_node_port_served(v4_node *n)
+1 -1
View File
@@ -68,7 +68,7 @@
#define EMIT_HOOK (BUF0_W - 7) /* the xt of a word EMIT gives its character to, or 0: the console */ #define EMIT_HOOK (BUF0_W - 7) /* the xt of a word EMIT gives its character to, or 0: the console */
#define CATCH (BUF0_W - 8) /* non-zero: a line's error is recorded here, -1, and the line ends " ok" */ #define CATCH (BUF0_W - 8) /* non-zero: a line's error is recorded here, -1, and the line ends " ok" */
#define LINE_STATUS (BUF0_W - 9) /* how the last line ended: 0 QUIT, 1 completed, 2 an error */ #define LINE_STATUS (BUF0_W - 9) /* how the last line ended: 0 QUIT, 1 completed, 2 an error */
#define PORT (BUF0_W - 10) /* the node's port: a write to it blocks until its neighbour, the kernel, has served it */ #define PORT (BUF0_W - 32) /* the node's ports (node.h): V4_PORTS of them, then "any port" and "which port". Port 0 is where its requests go. */
#define WORD_DEFINED (BUF0_W - 11) /* the xt of the word that is told of each new entry, or 0 */ #define WORD_DEFINED (BUF0_W - 11) /* the xt of the word that is told of each new entry, or 0 */
#define WORD_FORGOTTEN (BUF0_W - 12) /* the xt of the word that is told when entries go, or 0 */ #define WORD_FORGOTTEN (BUF0_W - 12) /* the xt of the word that is told when entries go, or 0 */
#define LOG_LEVEL (BUF0_W - 5) /* the level at or below which a message is printed */ #define LOG_LEVEL (BUF0_W - 5) /* the level at or below which a message is printed */
+292
View File
@@ -0,0 +1,292 @@
/* 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/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 */
/* 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;
}
/* 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 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");
/* ---- 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");
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}
+1 -1
View File
@@ -12,7 +12,7 @@ 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) #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. */ /* The memory map is open (D-4); the test chooses the address. */
#define PORT ((v4_cell)(V4_NODE_WORDS - 9u)) #define PORT ((v4_cell)(V4_NODE_WORDS - 16u))
#define MARK ((v4_cell)0x1234) #define MARK ((v4_cell)0x1234)
#define HOME ((v4_cell)8) /* where a word returns to when the test called it */ #define HOME ((v4_cell)8) /* where a word returns to when the test called it */