test(chipbus): Phase 0 live proof - two real WASM chips exchange a byte

End-to-end proof of the chip-to-chip net-key fix through the real
ChipRuntime + PinManager (not a unit stub). Two chips compiled from C
with wasi-sdk:
- bus-driver.c: drives 0xA5 onto D0..D7 at setup.
- bus-reader.c: polls D0..D7 on a 1ms timer, mirrors onto OUT0..OUT7.
Wired chip-to-chip with no board; with the chipbus flag both chips' Dn
pins resolve to one shared net key, so the reader reproduces 0xA5.

- sdk/examples/bus-{driver,reader}.{c,chip.json}: the proof chips.
- __tests__/fixtures/chipbus/*.wasm: committed fixtures (regenerate with
  the test_intel/scripts/compile-chip.sh flags).
- __tests__/chipbus-twochip-integration.test.ts: loads the fixtures via a
  relative path; skipIf they are absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Montero Crespo 2026-06-05 02:10:08 -03:00
parent c050ae6e49
commit 474d132368
7 changed files with 176 additions and 0 deletions

View File

@ -0,0 +1,104 @@
/**
* Multi-chip digital bus Phase 0 LIVE proof with REAL compiled WASM chips.
*
* This is the "2 chips exchange a real byte" milestone (03-phases.md, D-008),
* end-to-end through the actual ChipRuntime + PinManager + the chipbus net-key
* resolver not a unit stub. Two chips compiled from C with wasi-sdk:
* - bus-driver: drives 0xA5 onto D0..D7 at setup (OUTPUT_HIGH/LOW).
* - bus-reader: polls D0..D7 on a 1ms timer, mirrors onto OUT0..OUT7.
* Wired chip-to-chip (D0..D7 straight across), no board. With the chipbus flag
* the two chips' Dn pins resolve to ONE shared net key, so the reader observes
* the driver's byte and reproduces 0xA5 on OUT.
*
* Sources: test/test_custom_chips/sdk/examples/{bus-driver,bus-reader}.c.
* Fixtures (.wasm) are committed alongside this test; regenerate with:
* clang --target=wasm32-unknown-wasip1 -O2 -nostartfiles -Wl,--import-memory \
* -Wl,--export-table -Wl,--no-entry -Wl,--export=chip_setup \
* -Wl,--allow-undefined -I test/test_custom_chips/sdk/include <src.c> -o <out.wasm>
* (same flags as test_intel/scripts/compile-chip.sh).
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { readFileSync, existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { PinManager } from '../simulation/PinManager';
import { ChipInstance } from '../simulation/customChips/ChipRuntime';
import {
resolveChipNetKey,
setChipBusEnabledForTest,
resetChipNetIndexForTest,
type ChipNetState,
} from '../simulation/customChips/chipNets';
import { syntheticChipPin } from '../simulation/customChips/syntheticPins';
const driverWasmPath = fileURLToPath(new URL('./fixtures/chipbus/bus-driver.wasm', import.meta.url));
const readerWasmPath = fileURLToPath(new URL('./fixtures/chipbus/bus-reader.wasm', import.meta.url));
const haveFixtures = existsSync(driverWasmPath) && existsSync(readerWasmPath);
const chip = (id: string) => ({ id, metadataId: 'custom-chip' });
const wire = (aId: string, aPin: string, bId: string, bPin: string) => ({
start: { componentId: aId, pinName: aPin },
end: { componentId: bId, pinName: bPin },
});
const range = (n: number) => Array.from({ length: n }, (_, i) => i);
// The data bus: driver.D0..D7 wired straight across to reader.D0..D7, no board.
const STATE: ChipNetState = {
wires: range(8).map((i) => wire('driver', `D${i}`, 'reader', `D${i}`)),
components: [chip('driver'), chip('reader')],
boards: [],
};
// Mirror what traceDetailed hands CustomChipPart's `wires` map: a chip-to-chip
// net resolves to the shared net key; an unwired chip pin (the reader's OUTn)
// falls back to its own synthetic key (rule 3).
function pinKey(chipId: string, pin: string): number {
return resolveChipNetKey(STATE, chipId, pin) ?? syntheticChipPin(chipId, pin);
}
describe.skipIf(!haveFixtures)('chipbus Phase 0 — two REAL chips exchange a byte', () => {
beforeAll(() => {
setChipBusEnabledForTest(true);
resetChipNetIndexForTest();
});
afterAll(() => {
setChipBusEnabledForTest(null);
resetChipNetIndexForTest();
});
it('reader reproduces the driver byte 0xA5 over the shared chip-to-chip bus', async () => {
const driverWasm = new Uint8Array(readFileSync(driverWasmPath));
const readerWasm = new Uint8Array(readFileSync(readerWasmPath));
// The bus pins resolve to ONE shared key for both chips (root cause A fix).
expect(pinKey('driver', 'D0')).toBe(pinKey('reader', 'D0'));
const pm = new PinManager();
const driverWires = new Map<string, number>(
range(8).map((i) => [`D${i}`, pinKey('driver', `D${i}`)] as [string, number]),
);
const readerWires = new Map<string, number>([
...range(8).map((i) => [`D${i}`, pinKey('reader', `D${i}`)] as [string, number]),
...range(8).map((i) => [`OUT${i}`, pinKey('reader', `OUT${i}`)] as [string, number]),
]);
const driver = await ChipInstance.create({ wasm: driverWasm, pinManager: pm, wires: driverWires });
driver.start(); // chip_setup drives 0xA5 onto the shared D0..D7 net keys.
const reader = await ChipInstance.create({ wasm: readerWasm, pinManager: pm, wires: readerWires });
reader.start();
// Fire the reader's 1ms polling timer (advance sim time well past it).
reader.tickTimers(5_000_000n);
// Reconstruct the byte the reader mirrored onto OUT0..OUT7.
let out = 0;
for (const i of range(8)) {
if (pm.getPinState(pinKey('reader', `OUT${i}`))) out |= 1 << i;
}
expect(out).toBe(0xa5);
driver.dispose();
reader.dispose();
});
});

View File

@ -0,0 +1,20 @@
/*
* bus-driver Phase 0 chip-to-chip bus proof (project/multichip-bus/ in the
* velxio-prod repo). Drives a fixed byte 0xA5 onto an 8-bit data bus D0..D7 at
* setup. No board involved; the only consumer is another custom chip
* (bus-reader) wired straight across. Proves a shared chip-to-chip net key lets
* one chip's write reach another chip's read.
*/
#include "velxio-chip.h"
void chip_setup(void) {
const int byte = 0xA5; /* 1010 0101 */
char name[4] = {'D', '0', 0, 0};
for (int i = 0; i < 8; i++) {
name[1] = (char)('0' + i);
int bit = (byte >> i) & 1;
/* OUTPUT_HIGH/LOW drives the level at registration time, so the byte is
* present on the shared net immediately no timer needed. */
vx_pin_register(name, bit ? VX_OUTPUT_HIGH : VX_OUTPUT_LOW);
}
}

View File

@ -0,0 +1,9 @@
{
"schema": "velxio-chip/v1",
"name": "Bus Driver (0xA5)",
"author": "Velxio",
"license": "MIT",
"description": "Drives the constant byte 0xA5 onto an 8-bit data bus D0..D7. Phase 0 chip-to-chip bus proof.",
"pins": ["D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7"],
"attributes": []
}

View File

@ -0,0 +1,34 @@
/*
* bus-reader Phase 0 chip-to-chip bus proof (project/multichip-bus/ in the
* velxio-prod repo). Reads the 8-bit data bus D0..D7 and mirrors it onto
* OUT0..OUT7 (which would drive 8 LEDs in the app). Polls on a 1 ms timer so it
* is order-independent w.r.t. the driver's setup. If chip-to-chip keying works,
* OUT == whatever the driver put on the bus (0xA5).
*/
#include "velxio-chip.h"
static vx_pin D[8];
static vx_pin OUT[8];
static void poll(void* ud) {
(void)ud;
for (int i = 0; i < 8; i++) {
int v = vx_pin_read(D[i]);
vx_pin_write(OUT[i], v);
}
}
void chip_setup(void) {
char dn[4] = {'D', '0', 0, 0};
for (int i = 0; i < 8; i++) {
dn[1] = (char)('0' + i);
D[i] = vx_pin_register(dn, VX_INPUT);
}
char on[5] = {'O', 'U', 'T', '0', 0};
for (int i = 0; i < 8; i++) {
on[3] = (char)('0' + i);
OUT[i] = vx_pin_register(on, VX_OUTPUT_LOW);
}
vx_timer t = vx_timer_create(poll, (void*)0);
vx_timer_start(t, 1000000ULL, true); /* 1 ms, repeating */
}

View File

@ -0,0 +1,9 @@
{
"schema": "velxio-chip/v1",
"name": "Bus Reader (8 LEDs)",
"author": "Velxio",
"license": "MIT",
"description": "Reads an 8-bit data bus D0..D7 and mirrors it onto OUT0..OUT7. Phase 0 chip-to-chip bus proof.",
"pins": ["D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "OUT0", "OUT1", "OUT2", "OUT3", "OUT4", "OUT5", "OUT6", "OUT7"],
"attributes": []
}