feat(v4.0.0): nodes that talk -- ports, a node born empty, and the fabric
MESH.md step 1, in the engine, which knows nothing of StarForth or of any kernel. - node: V4_PORTS ports (8), a build parameter; "any port" and the port the last such read came from; a read blocks until the neighbour writes, as a write blocks until the neighbour reads; v4_node_born: empty, P at "any port" - exec: a fetch from a port -- @ @b @+ @p, or of an instruction word when P is a port -- waits for a word; a node executes what arrives at a port without advancing P; a blocked node goes on from the slot it stopped at - fabric: the nodes there are and the table of how their ports are wired, both changed while the nodes run; devices on a port; asleep and awake; a step is every unblocked node executing one instruction word, then every write with a reader waiting being handed over - DECOMPOSITION.md section 6: four named ports withdrawn for V4_PORTS numbered ones and wiring as data, as ruled Verified: tests/test_fabric.c, 53 checks at both widths: two nodes exchange words; an empty node is filled through its port by a device, and by another node, and runs what it was sent; a word is passed on by a node in between; a waiting node executes nothing; the wiring is changed while they run; a node is put to sleep, woken and removed while looping; a node is born while others run; the fabric is given more room. make -C v4 test and make -C v4 sanitize pass. The single-node products are unchanged: hosted-check on three ISAs, and clean qemu with STARFORTH_V4=1 on amd64, aarch64 and riscv64 with lines typed at each prompt (logs/20261006-074907, -075150, -075532). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2c5427d9c1
commit
9af442f793
@@ -1,5 +1,5 @@
|
||||
# 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. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
|
||||
@@ -1049,9 +1049,19 @@ The runtime governor moves into hardware as the multi-level Rolling Window of Tr
|
||||
|
||||
## 6. Messaging between nodes
|
||||
|
||||
Each node has four neighbour ports (`PORT-UP`, `PORT-DOWN`, `PORT-LEFT`, `PORT-RIGHT`) mapped into its
|
||||
address space. 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: a port is an address.
|
||||
Each node has ports mapped into its address space. 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: a port is an
|
||||
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)
|
||||
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
**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
|
||||
through its port (section 5).**
|
||||
3. **Messages (section 6):** a StarForth node that reads messages when
|
||||
|
||||
+1
-1
@@ -613,7 +613,7 @@ V4_DEFS := -DSTARFORTH_V4=1 -Iv4/include \
|
||||
KERNEL_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
|
||||
|
||||
LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
[2J[001;001H[=3h[2J[001;001H[2J[001;001H[8;042;160t[2J[001;001H[2J[001;001H[8;056;240t[2J[001;001HBdsDxe: 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)
|
||||
[2J[001;001H[=3h[2J[001;001HStarKernel 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)
|
||||
[2J[001;001H[=3h[2J[001;001H[2J[001;001H[8;031;100t[2J[001;001HBdsDxe: 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)
|
||||
[2J[001;001H[=3h[2J[001;001HStarKernel 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
|
||||
[2J[004D[=3h[2J[009D[2J[004D[8;031;100t[2J[016DRISC-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
|
||||
[2J[001;001H[=3h[2J[009DStarKernel 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
@@ -61,7 +61,7 @@ capsule_dir := -DV4_CAPSULE_DIR='"$(HERE)/capsule"'
|
||||
# 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.
|
||||
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
|
||||
$(BINDIR)/mkimage-$(1): $(HERE)/tools/mkimage.c $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/tests/host_map.h $(HERE)/Makefile
|
||||
|
||||
+13
-3
@@ -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:
|
||||
|
||||
```
|
||||
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_POST tests=550 pass=550 fail=0
|
||||
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=... dict_hash=...
|
||||
@@ -68,8 +68,8 @@ POST: PASSED
|
||||
ok>
|
||||
```
|
||||
|
||||
Bare-metal logs: `logs/20261005-193045/amd64/`, `logs/20261005-193307/aarch64/`,
|
||||
`logs/20261005-193636/riscv64/`. On each, seven lines were then typed at the
|
||||
Bare-metal logs: `logs/20261006-074907/amd64/`, `logs/20261006-075150/aarch64/`,
|
||||
`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
|
||||
`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
|
||||
@@ -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
|
||||
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
|
||||
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
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/* fabric.h -- the nodes there are, how their ports are wired, and time
|
||||
* passing for all of them at once. docs/v4.0.0/MESH.md 4.3.
|
||||
*
|
||||
* On the fabric every node runs at the same time and a node with nothing to
|
||||
* do is blocked at a port. Here that is: every node that is awake and not
|
||||
* blocked executes one instruction word, and then every write that has a
|
||||
* reader waiting on the other end of its wire is handed over. There is no
|
||||
* choice of whose turn it is.
|
||||
*
|
||||
* THE GEOMETRY IS DATA. Which port connects to what is a table, and it can
|
||||
* be changed while the nodes run; nodes can be added and removed while they
|
||||
* run. Nothing here knows what shape the wiring makes.
|
||||
*
|
||||
* Nothing here knows what the nodes are for, either: no message, no
|
||||
* capsule, no StarForth. And nothing here uses the C library: whoever owns
|
||||
* the fabric supplies the memory for it and for each node.
|
||||
*/
|
||||
#ifndef V4_FABRIC_H
|
||||
#define V4_FABRIC_H
|
||||
|
||||
#include "v4/exec.h"
|
||||
|
||||
/* What is on the other end of a port that is not a node: a console, a disk,
|
||||
* the kernel that serves a node's requests. Either function may be 0. */
|
||||
typedef struct {
|
||||
int (*take)(void *self, v4_cell value); /* the node wrote `value`: 1 if it is taken, 0 if not yet */
|
||||
int (*give)(void *self, v4_cell *value); /* the node is reading: 1 with a word in *value, 0 if not yet */
|
||||
void *self;
|
||||
} v4_device;
|
||||
|
||||
/* One end of a wire. */
|
||||
enum { V4_WIRE_NONE = 0, V4_WIRE_NODE, V4_WIRE_DEVICE };
|
||||
typedef struct {
|
||||
int kind;
|
||||
unsigned node, port; /* V4_WIRE_NODE: the other node and its port */
|
||||
const v4_device *device; /* V4_WIRE_DEVICE */
|
||||
} v4_wire;
|
||||
|
||||
/* A node, with what the engine needs to run it. Its owner supplies one of
|
||||
* these for every node there is to be. */
|
||||
typedef struct {
|
||||
v4_node n;
|
||||
v4_exec_state es;
|
||||
v4_heat h;
|
||||
} v4_fabric_node;
|
||||
|
||||
/* A place in the fabric: a node, or nothing. */
|
||||
typedef struct {
|
||||
v4_fabric_node *node; /* 0: no node here */
|
||||
int asleep;
|
||||
v4_wire wire[V4_PORTS];
|
||||
} v4_place;
|
||||
|
||||
typedef struct {
|
||||
v4_place *place;
|
||||
unsigned capacity;
|
||||
} v4_fabric;
|
||||
|
||||
/* An empty fabric with room for `capacity` nodes, in `places`. */
|
||||
void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity);
|
||||
|
||||
/* More room: `places`, of `capacity` no less than before, takes over; every
|
||||
* node keeps its number and its wiring. */
|
||||
void v4_fabric_grow(v4_fabric *f, v4_place *places, unsigned capacity);
|
||||
|
||||
/* A node is born (node.h, v4_node_born): empty, its ports at `port_base`,
|
||||
* wired to nothing, awake and blocked reading its ports. Returns its
|
||||
* number, or -1 if there is no room. */
|
||||
int v4_fabric_add(v4_fabric *f, v4_fabric_node *storage, v4_cell port_base);
|
||||
|
||||
/* A node is removed, whatever it was doing. Every wire to it is cut; a
|
||||
* neighbour blocked on one of them stays blocked, as it would on a port
|
||||
* with nothing there. Returns the storage it was given, or 0. */
|
||||
v4_fabric_node *v4_fabric_remove(v4_fabric *f, unsigned id);
|
||||
|
||||
/* The node numbered `id`, or 0 if there is none. */
|
||||
v4_fabric_node *v4_fabric_node_at(const v4_fabric *f, unsigned id);
|
||||
|
||||
/* Wire port `pa` of node `a` to port `pb` of node `b`, or to a device; or
|
||||
* cut whatever is on it. Whatever was on a port before is cut, at both
|
||||
* ends. Each returns 1, or 0 if a node or a port is not there. */
|
||||
int v4_fabric_wire(v4_fabric *f, unsigned a, unsigned pa, unsigned b, unsigned pb);
|
||||
int v4_fabric_wire_device(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device);
|
||||
int v4_fabric_unwire(v4_fabric *f, unsigned a, unsigned pa);
|
||||
|
||||
/* A node that is asleep executes nothing, and nothing is handed to it or
|
||||
* taken from it. It can be put to sleep and woken at any instruction
|
||||
* word. */
|
||||
void v4_fabric_sleep(v4_fabric *f, unsigned id);
|
||||
void v4_fabric_wake(v4_fabric *f, unsigned id);
|
||||
|
||||
/* Time passes: every node that is awake and not blocked executes one
|
||||
* instruction word; then every write with a reader waiting on the other end
|
||||
* of its wire is handed over, and every device is asked. Returns how many
|
||||
* instructions were executed and words handed over together: 0 means
|
||||
* nothing can happen until something outside changes. */
|
||||
unsigned v4_fabric_step(v4_fabric *f);
|
||||
|
||||
#endif /* V4_FABRIC_H */
|
||||
+68
-19
@@ -36,6 +36,14 @@
|
||||
#include "v4/stack.h"
|
||||
|
||||
/* Words of node memory. Overridable at build time. */
|
||||
/* How many ports a node has. A build parameter, as the cell width and the
|
||||
* node's memory are: the geometry the nodes are wired in is data, and a node
|
||||
* has as many ports as the geometry in use needs (docs/v4.0.0/MESH.md 4.1). */
|
||||
#ifndef V4_PORTS
|
||||
#define V4_PORTS 8u
|
||||
#endif
|
||||
#define V4_PORT_ANY V4_PORTS /* not a port: "whichever port has a neighbour writing" */
|
||||
|
||||
#ifndef V4_NODE_WORDS
|
||||
#define V4_NODE_WORDS 1024u
|
||||
#endif
|
||||
@@ -91,9 +99,16 @@ typedef struct {
|
||||
/* The port (DECOMPOSITION.md section 6): a write to it blocks the node
|
||||
* until its neighbour has taken what was written. See
|
||||
* v4_node_port_attach below. */
|
||||
v4_cell port; /* its word address, or -1: no port */
|
||||
int asking; /* non-zero: blocked, having written `request` */
|
||||
v4_cell port; /* word address of port 0, or -1: no ports */
|
||||
int asking; /* non-zero: blocked, having written `request` to port `ask_port` */
|
||||
v4_cell request; /* what it wrote */
|
||||
unsigned ask_port; /* to which port */
|
||||
int reading; /* non-zero: blocked, wanting a word from port `read_port` */
|
||||
unsigned read_port; /* a port, or V4_PORT_ANY */
|
||||
int given; /* non-zero: `given_value` has come from port `given_port` and not been taken yet */
|
||||
v4_cell given_value;
|
||||
unsigned given_port;
|
||||
unsigned last_from; /* the port the last read from "any" took its word from */
|
||||
|
||||
/* DSTACK-DEPTH and RSTACK-DEPTH (D-16). See v4_node_stack_regs_attach. */
|
||||
v4_cell dstack_reg; /* its word address, or -1 */
|
||||
@@ -156,28 +171,62 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value);
|
||||
* Passing -1 detaches. The address is the caller's choice. */
|
||||
void v4_node_console_attach(v4_node *n, v4_cell addr);
|
||||
|
||||
/* THE PORT. A node reaches its neighbour through a port, and a port is an
|
||||
* address (DECOMPOSITION.md section 6): "a write blocks until the neighbour
|
||||
* reads. This is the GA144 model. No instruction is added."
|
||||
/* THE PORTS. A node reaches its neighbours through its ports, and a port is
|
||||
* an address (DECOMPOSITION.md section 6): "a read from a port blocks until
|
||||
* the neighbour writes; a write blocks until the neighbour reads. This is
|
||||
* the GA144 model. No instruction is added." docs/v4.0.0/MESH.md 4.1.
|
||||
*
|
||||
* After v4_node_port_attach(n, addr), a store to word address `addr` does
|
||||
* not write memory: the node keeps the value in n->request, sets n->asking
|
||||
* and is blocked. A blocked node executes nothing -- v4_exec_step_word
|
||||
* returns 0 and changes nothing -- until its neighbour calls
|
||||
* v4_node_port_served(n). It then goes on from the opcode after the store,
|
||||
* in the same instruction word if there are any left.
|
||||
* After v4_node_port_attach(n, base) the node's V4_PORTS ports are the word
|
||||
* addresses base .. base + V4_PORTS - 1. Two more follow them:
|
||||
* base + V4_PORTS "any port": a read here takes from whichever port
|
||||
* has a neighbour writing
|
||||
* base + V4_PORTS + 1 which port the last read from "any" came from
|
||||
* (read only)
|
||||
* None of them is memory while the ports are attached.
|
||||
*
|
||||
* This is how a node asks its kernel for something (docs/v4.0.0/ENGINE.md
|
||||
* 3.3): the neighbour on the port is the kernel, what is written is the
|
||||
* number of the request, and the arguments and results are on the node's
|
||||
* data stack, which the kernel may use while the node is blocked. Only the
|
||||
* write is here. Nothing reads from a port yet, and a fetch from the
|
||||
* address is a fetch from memory.
|
||||
* A WRITE. A store to a port does not write memory: the node keeps the
|
||||
* value in n->request and the port in n->ask_port, sets n->asking and is
|
||||
* blocked. When its neighbour has taken the value -- v4_node_port_served --
|
||||
* it goes on from the opcode after the store, in the same instruction word
|
||||
* if there are any left.
|
||||
*
|
||||
* A node has no port until one is attached: v4_node_reset detaches it. */
|
||||
void v4_node_port_attach(v4_node *n, v4_cell addr);
|
||||
* A READ. A fetch from a port -- @ @b @+, the literal fetch @p, or the
|
||||
* fetch of an instruction word when P is a port -- does not happen until
|
||||
* there is a word to fetch. The node sets n->reading and n->read_port and
|
||||
* is blocked, the opcode not yet executed. When its neighbour writes --
|
||||
* v4_node_port_give -- the fetch is made, and it gets that word. When P is
|
||||
* a port it is not advanced by a fetch: the node goes on executing what
|
||||
* arrives there, as an F18 node does.
|
||||
*
|
||||
* A blocked node executes nothing: v4_exec_step_word returns 0 and changes
|
||||
* nothing. Who the neighbour is, and whether there is one, is not the
|
||||
* node's to know (fabric.h). A store to "any port" or to the "which port"
|
||||
* address is an address fault.
|
||||
*
|
||||
* A node has no ports until they are attached: v4_node_reset detaches them. */
|
||||
void v4_node_port_attach(v4_node *n, v4_cell base);
|
||||
|
||||
/* The neighbour has taken what the node wrote. */
|
||||
void v4_node_port_served(v4_node *n);
|
||||
|
||||
/* The neighbour on `port` writes `value`: the node, blocked reading that
|
||||
* port or "any", is unblocked and will fetch it. */
|
||||
void v4_node_port_give(v4_node *n, unsigned port, v4_cell value);
|
||||
|
||||
/* Which port `addr` is: 0 .. V4_PORTS - 1, V4_PORT_ANY, V4_PORTS + 1 for the
|
||||
* "which port" address, or -1 if it is none of them. */
|
||||
int v4_node_port_index(const v4_node *n, v4_cell addr);
|
||||
|
||||
/* 1 if a fetch from `addr` can be made now: it is not a port, or the word
|
||||
* has been given. 0 if it is a port with nothing given: the node is then
|
||||
* blocked reading it, and the fetch must not be made. */
|
||||
int v4_node_read_ready(v4_node *n, v4_cell addr);
|
||||
|
||||
/* A NODE NEWLY BORN (docs/v4.0.0/MESH.md 4.2). Reset, its ports attached at
|
||||
* `base`, and P at "any port": it is blocked reading its ports, and will
|
||||
* execute what a neighbour sends it. Nothing else is in it. */
|
||||
void v4_node_born(v4_node *n, v4_cell base);
|
||||
|
||||
/* CONSOLE-RX and CONSOLE-STATUS, the console's receive side, on the
|
||||
* single-node model.
|
||||
*
|
||||
|
||||
+31
-4
@@ -143,20 +143,30 @@ void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h,
|
||||
|
||||
case V4_OP_FETCH_P:
|
||||
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));
|
||||
n->p = add_wrap(n->p, 1);
|
||||
break;
|
||||
case V4_OP_FETCH_INC:
|
||||
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));
|
||||
n->a = add_wrap(n->a, 1);
|
||||
break;
|
||||
case V4_OP_FETCH_B:
|
||||
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));
|
||||
break;
|
||||
case V4_OP_FETCH_A:
|
||||
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));
|
||||
break;
|
||||
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
|
||||
* 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) {
|
||||
/* served: the rest of the word the store was in */
|
||||
/* unblocked: the rest of the word it was in */
|
||||
es->resume = 0;
|
||||
iw = es->resume_iw;
|
||||
slot = es->resume_slot;
|
||||
} else {
|
||||
/* P itself may be what is outside memory */
|
||||
if (faulted(n, n->p)) return 0;
|
||||
iw = fetch_iword(n, n->p);
|
||||
n->p = add_wrap(n->p, 1);
|
||||
if (v4_node_port_index(n, n->p) >= 0) {
|
||||
/* 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) {
|
||||
@@ -271,6 +288,16 @@ unsigned v4_exec_step_word(v4_node *n, v4_exec_state *es, v4_heat *h)
|
||||
unsigned faults = n->faults;
|
||||
|
||||
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 */
|
||||
/* 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++;
|
||||
|
||||
+171
@@ -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
@@ -120,9 +120,18 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value)
|
||||
n->console_dropped++;
|
||||
return;
|
||||
}
|
||||
/* The port: the node keeps what it wrote and is blocked until its
|
||||
* neighbour has taken it. -1 when no port is attached. */
|
||||
if (addr == n->port) { n->request = value; n->asking = 1; 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 >= 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. */
|
||||
if (addr == n->dstack_reg) { v4_dstack_clear(&n->ds); 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;
|
||||
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->rstack_reg) return (v4_cell)n->rs.depth;
|
||||
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;
|
||||
}
|
||||
|
||||
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->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)
|
||||
|
||||
+1
-1
@@ -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 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 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_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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
/* 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 HOME ((v4_cell)8) /* where a word returns to when the test called it */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user