diff --git a/frontend/src/__tests__/chipbus-twochip-integration.test.ts b/frontend/src/__tests__/chipbus-twochip-integration.test.ts new file mode 100644 index 00000000..9255f51f --- /dev/null +++ b/frontend/src/__tests__/chipbus-twochip-integration.test.ts @@ -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 -o + * (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( + range(8).map((i) => [`D${i}`, pinKey('driver', `D${i}`)] as [string, number]), + ); + const readerWires = new Map([ + ...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(); + }); +}); diff --git a/frontend/src/__tests__/fixtures/chipbus/bus-driver.wasm b/frontend/src/__tests__/fixtures/chipbus/bus-driver.wasm new file mode 100644 index 00000000..8fe492d3 Binary files /dev/null and b/frontend/src/__tests__/fixtures/chipbus/bus-driver.wasm differ diff --git a/frontend/src/__tests__/fixtures/chipbus/bus-reader.wasm b/frontend/src/__tests__/fixtures/chipbus/bus-reader.wasm new file mode 100644 index 00000000..7b31fc38 Binary files /dev/null and b/frontend/src/__tests__/fixtures/chipbus/bus-reader.wasm differ diff --git a/test/test_custom_chips/sdk/examples/bus-driver.c b/test/test_custom_chips/sdk/examples/bus-driver.c new file mode 100644 index 00000000..c01d727a --- /dev/null +++ b/test/test_custom_chips/sdk/examples/bus-driver.c @@ -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); + } +} diff --git a/test/test_custom_chips/sdk/examples/bus-driver.chip.json b/test/test_custom_chips/sdk/examples/bus-driver.chip.json new file mode 100644 index 00000000..0287d56e --- /dev/null +++ b/test/test_custom_chips/sdk/examples/bus-driver.chip.json @@ -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": [] +} diff --git a/test/test_custom_chips/sdk/examples/bus-reader.c b/test/test_custom_chips/sdk/examples/bus-reader.c new file mode 100644 index 00000000..b9e10303 --- /dev/null +++ b/test/test_custom_chips/sdk/examples/bus-reader.c @@ -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 */ +} diff --git a/test/test_custom_chips/sdk/examples/bus-reader.chip.json b/test/test_custom_chips/sdk/examples/bus-reader.chip.json new file mode 100644 index 00000000..54124cfd --- /dev/null +++ b/test/test_custom_chips/sdk/examples/bus-reader.chip.json @@ -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": [] +}