velxio/frontend/src/__tests__/chipbus-twochip-integration...

117 lines
4.7 KiB
TypeScript

/**
* 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]),
]);
// componentId must be distinct so each chip's bus drivers are keyed apart
// (the real app always passes it; busNets keys drivers by `${id}::${pin}`).
const driver = await ChipInstance.create({
wasm: driverWasm,
componentId: 'driver',
pinManager: pm,
wires: driverWires,
});
driver.start(); // chip_setup drives 0xA5 onto the shared D0..D7 net keys.
const reader = await ChipInstance.create({
wasm: readerWasm,
componentId: 'reader',
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();
});
});