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:
parent
c050ae6e49
commit
474d132368
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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": []
|
||||
}
|
||||
|
|
@ -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 */
|
||||
}
|
||||
|
|
@ -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": []
|
||||
}
|
||||
Loading…
Reference in New Issue