@celox-sim/celox 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/napi-helpers.d.ts +23 -5
- package/dist/napi-helpers.d.ts.map +1 -1
- package/dist/napi-helpers.js +43 -2
- package/dist/napi-helpers.js.map +1 -1
- package/dist/simulation.d.ts.map +1 -1
- package/dist/simulation.js +26 -0
- package/dist/simulation.js.map +1 -1
- package/dist/simulator.d.ts.map +1 -1
- package/dist/simulator.js +38 -8
- package/dist/simulator.js.map +1 -1
- package/dist/types.d.ts +33 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wasm-bridge.d.ts +62 -0
- package/dist/wasm-bridge.d.ts.map +1 -0
- package/dist/wasm-bridge.js +140 -0
- package/dist/wasm-bridge.js.map +1 -0
- package/package.json +2 -2
- package/src/e2e.test.ts +129 -6
- package/src/index.ts +12 -0
- package/src/napi-helpers.ts +85 -11
- package/src/simulation.ts +39 -0
- package/src/simulator.ts +38 -10
- package/src/types.ts +33 -3
- package/src/wasm-bridge.ts +228 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM bridge for running simulation via browser WebAssembly.
|
|
3
|
+
*
|
|
4
|
+
* When the NAPI addon is compiled for wasm32 (no JIT backend), the
|
|
5
|
+
* NativeSimulatorHandle exposes `combWasmBytes()` and `eventWasmBytes(name)`
|
|
6
|
+
* instead of `tick()` / `evalComb()`. This bridge instantiates those WASM
|
|
7
|
+
* modules and presents a handle that is compatible with the existing
|
|
8
|
+
* Simulator / DUT code.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import type { NativeSimulatorHandle } from "./types.js";
|
|
13
|
+
/**
|
|
14
|
+
* Handle shape returned by a wasm32-compiled NativeSimulatorHandle.
|
|
15
|
+
* Extends the standard metadata getters with WASM bytecode accessors.
|
|
16
|
+
*/
|
|
17
|
+
export interface RawWasmSimulatorHandle {
|
|
18
|
+
readonly layoutJson: string;
|
|
19
|
+
readonly eventsJson: string;
|
|
20
|
+
readonly hierarchyJson: string;
|
|
21
|
+
readonly warningsJson: string;
|
|
22
|
+
readonly stableSize: number;
|
|
23
|
+
readonly totalSize: number;
|
|
24
|
+
combWasmBytes(): Uint8Array | number[];
|
|
25
|
+
eventWasmBytes(name: string): Uint8Array | number[];
|
|
26
|
+
dispose(): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Detect if a handle was produced by a wasm32-compiled addon.
|
|
30
|
+
*
|
|
31
|
+
* WASM handles expose `combWasmBytes()` instead of `tick()`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isWasmHandle(handle: unknown): handle is RawWasmSimulatorHandle;
|
|
34
|
+
/** Result of creating a WASM simulator bridge. */
|
|
35
|
+
export interface WasmBridgeResult {
|
|
36
|
+
/** Handle compatible with the existing Simulator code. */
|
|
37
|
+
handle: NativeSimulatorHandle;
|
|
38
|
+
/** The raw shared memory as a Uint8Array (backed by WebAssembly.Memory). */
|
|
39
|
+
sharedMemory: Uint8Array;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create a NativeSimulatorHandle-compatible wrapper from a wasm32-compiled
|
|
43
|
+
* NAPI handle.
|
|
44
|
+
*
|
|
45
|
+
* 1. Reads metadata (layout, events, sizes) from the raw handle.
|
|
46
|
+
* 2. Creates a WebAssembly.Memory large enough for the simulation state.
|
|
47
|
+
* 3. Synchronously compiles and instantiates the combinational and event
|
|
48
|
+
* WASM modules, importing the shared memory.
|
|
49
|
+
* 4. Returns a handle whose `tick()` / `evalComb()` drive the WASM instances.
|
|
50
|
+
*
|
|
51
|
+
* @returns A bridge result with the wrapped handle and shared memory view.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createWasmSimulatorBridge(raw: RawWasmSimulatorHandle): WasmBridgeResult;
|
|
54
|
+
/**
|
|
55
|
+
* Asynchronous version of `createWasmSimulatorBridge` that uses
|
|
56
|
+
* `WebAssembly.compile()` + `WebAssembly.instantiate()`.
|
|
57
|
+
*
|
|
58
|
+
* Preferred for large modules in browsers where synchronous compilation
|
|
59
|
+
* may be rejected.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createWasmSimulatorBridgeAsync(raw: RawWasmSimulatorHandle): Promise<WasmBridgeResult>;
|
|
62
|
+
//# sourceMappingURL=wasm-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasm-bridge.d.ts","sourceRoot":"","sources":["../src/wasm-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAmCxD;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,aAAa,IAAI,UAAU,GAAG,MAAM,EAAE,CAAC;IACvC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,EAAE,CAAC;IACpD,OAAO,IAAI,IAAI,CAAC;CAChB;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,OAAO,GACb,MAAM,IAAI,sBAAsB,CAOlC;AAMD,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAChC,0DAA0D;IAC1D,MAAM,EAAE,qBAAqB,CAAC;IAC9B,4EAA4E;IAC5E,YAAY,EAAE,UAAU,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACxC,GAAG,EAAE,sBAAsB,GACzB,gBAAgB,CAqDlB;AAED;;;;;;GAMG;AACH,wBAAsB,8BAA8B,CACnD,GAAG,EAAE,sBAAsB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAsD3B"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM bridge for running simulation via browser WebAssembly.
|
|
3
|
+
*
|
|
4
|
+
* When the NAPI addon is compiled for wasm32 (no JIT backend), the
|
|
5
|
+
* NativeSimulatorHandle exposes `combWasmBytes()` and `eventWasmBytes(name)`
|
|
6
|
+
* instead of `tick()` / `evalComb()`. This bridge instantiates those WASM
|
|
7
|
+
* modules and presents a handle that is compatible with the existing
|
|
8
|
+
* Simulator / DUT code.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Detection
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* Detect if a handle was produced by a wasm32-compiled addon.
|
|
17
|
+
*
|
|
18
|
+
* WASM handles expose `combWasmBytes()` instead of `tick()`.
|
|
19
|
+
*/
|
|
20
|
+
export function isWasmHandle(handle) {
|
|
21
|
+
return (typeof handle === "object" &&
|
|
22
|
+
handle !== null &&
|
|
23
|
+
typeof handle.combWasmBytes === "function" &&
|
|
24
|
+
typeof handle.tick !== "function");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a NativeSimulatorHandle-compatible wrapper from a wasm32-compiled
|
|
28
|
+
* NAPI handle.
|
|
29
|
+
*
|
|
30
|
+
* 1. Reads metadata (layout, events, sizes) from the raw handle.
|
|
31
|
+
* 2. Creates a WebAssembly.Memory large enough for the simulation state.
|
|
32
|
+
* 3. Synchronously compiles and instantiates the combinational and event
|
|
33
|
+
* WASM modules, importing the shared memory.
|
|
34
|
+
* 4. Returns a handle whose `tick()` / `evalComb()` drive the WASM instances.
|
|
35
|
+
*
|
|
36
|
+
* @returns A bridge result with the wrapped handle and shared memory view.
|
|
37
|
+
*/
|
|
38
|
+
export function createWasmSimulatorBridge(raw) {
|
|
39
|
+
const totalSize = raw.totalSize;
|
|
40
|
+
const stableSize = raw.stableSize;
|
|
41
|
+
// Create shared WebAssembly.Memory
|
|
42
|
+
const pages = Math.max(1, Math.ceil(totalSize / 65536));
|
|
43
|
+
const memory = new WebAssembly.Memory({ initial: pages });
|
|
44
|
+
// Compile and instantiate comb WASM module (synchronous)
|
|
45
|
+
const combBytes = new Uint8Array(raw.combWasmBytes());
|
|
46
|
+
const combModule = new WebAssembly.Module(combBytes);
|
|
47
|
+
const combInstance = new WebAssembly.Instance(combModule, {
|
|
48
|
+
env: { memory },
|
|
49
|
+
});
|
|
50
|
+
// Parse events and instantiate per-event WASM modules
|
|
51
|
+
const events = JSON.parse(raw.eventsJson);
|
|
52
|
+
const eventInstances = new Map();
|
|
53
|
+
for (const [name, id] of Object.entries(events)) {
|
|
54
|
+
const bytes = new Uint8Array(raw.eventWasmBytes(name));
|
|
55
|
+
const mod = new WebAssembly.Module(bytes);
|
|
56
|
+
const inst = new WebAssembly.Instance(mod, { env: { memory } });
|
|
57
|
+
eventInstances.set(id, inst);
|
|
58
|
+
}
|
|
59
|
+
const sharedMemory = new Uint8Array(memory.buffer, 0, stableSize);
|
|
60
|
+
const handle = {
|
|
61
|
+
tick(eventId) {
|
|
62
|
+
// eval_comb → eval_apply_ff → eval_comb
|
|
63
|
+
combInstance.exports.run();
|
|
64
|
+
const evInst = eventInstances.get(eventId);
|
|
65
|
+
if (evInst)
|
|
66
|
+
evInst.exports.run();
|
|
67
|
+
combInstance.exports.run();
|
|
68
|
+
},
|
|
69
|
+
tickN(eventId, count) {
|
|
70
|
+
for (let i = 0; i < count; i++) {
|
|
71
|
+
this.tick(eventId);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
evalComb() {
|
|
75
|
+
combInstance.exports.run();
|
|
76
|
+
},
|
|
77
|
+
dump(_timestamp) {
|
|
78
|
+
// No VCD support in browser WASM mode
|
|
79
|
+
},
|
|
80
|
+
dispose() {
|
|
81
|
+
raw.dispose();
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
return { handle, sharedMemory };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Asynchronous version of `createWasmSimulatorBridge` that uses
|
|
88
|
+
* `WebAssembly.compile()` + `WebAssembly.instantiate()`.
|
|
89
|
+
*
|
|
90
|
+
* Preferred for large modules in browsers where synchronous compilation
|
|
91
|
+
* may be rejected.
|
|
92
|
+
*/
|
|
93
|
+
export async function createWasmSimulatorBridgeAsync(raw) {
|
|
94
|
+
const totalSize = raw.totalSize;
|
|
95
|
+
const stableSize = raw.stableSize;
|
|
96
|
+
const pages = Math.max(1, Math.ceil(totalSize / 65536));
|
|
97
|
+
const memory = new WebAssembly.Memory({ initial: pages });
|
|
98
|
+
// Compile comb module
|
|
99
|
+
const combBytes = new Uint8Array(raw.combWasmBytes());
|
|
100
|
+
const combModule = await WebAssembly.compile(combBytes);
|
|
101
|
+
const combInstance = await WebAssembly.instantiate(combModule, {
|
|
102
|
+
env: { memory },
|
|
103
|
+
});
|
|
104
|
+
// Parse events and compile per-event modules
|
|
105
|
+
const events = JSON.parse(raw.eventsJson);
|
|
106
|
+
const eventInstances = new Map();
|
|
107
|
+
const eventEntries = Object.entries(events);
|
|
108
|
+
await Promise.all(eventEntries.map(async ([name, id]) => {
|
|
109
|
+
const bytes = new Uint8Array(raw.eventWasmBytes(name));
|
|
110
|
+
const mod = await WebAssembly.compile(bytes);
|
|
111
|
+
const inst = await WebAssembly.instantiate(mod, { env: { memory } });
|
|
112
|
+
eventInstances.set(id, inst);
|
|
113
|
+
}));
|
|
114
|
+
const sharedMemory = new Uint8Array(memory.buffer, 0, stableSize);
|
|
115
|
+
const handle = {
|
|
116
|
+
tick(eventId) {
|
|
117
|
+
combInstance.exports.run();
|
|
118
|
+
const evInst = eventInstances.get(eventId);
|
|
119
|
+
if (evInst)
|
|
120
|
+
evInst.exports.run();
|
|
121
|
+
combInstance.exports.run();
|
|
122
|
+
},
|
|
123
|
+
tickN(eventId, count) {
|
|
124
|
+
for (let i = 0; i < count; i++) {
|
|
125
|
+
this.tick(eventId);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
evalComb() {
|
|
129
|
+
combInstance.exports.run();
|
|
130
|
+
},
|
|
131
|
+
dump(_timestamp) {
|
|
132
|
+
// No VCD support in browser WASM mode
|
|
133
|
+
},
|
|
134
|
+
dispose() {
|
|
135
|
+
raw.dispose();
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
return { handle, sharedMemory };
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=wasm-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasm-bridge.js","sourceRoot":"","sources":["../src/wasm-bridge.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celox-sim/celox",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "TypeScript runtime for Celox HDL simulation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@celox-sim/celox-napi": "0.1.
|
|
32
|
+
"@celox-sim/celox-napi": "0.1.31"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"vitest": ">=1.0.0"
|
package/src/e2e.test.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { readFourState } from "./dut.js";
|
|
|
14
14
|
import {
|
|
15
15
|
createSimulatorBridge,
|
|
16
16
|
loadNativeAddon,
|
|
17
|
+
type NapiTestResult,
|
|
17
18
|
parseNapiLayout,
|
|
18
19
|
parseSignalPath,
|
|
19
20
|
type RawNapiAddon,
|
|
@@ -561,8 +562,8 @@ module InitTest (
|
|
|
561
562
|
view.setUint8(sigA.offset, 0);
|
|
562
563
|
view.setUint8(sigA.offset + sigA.byteSize, 0);
|
|
563
564
|
|
|
564
|
-
// b = X (value=
|
|
565
|
-
view.setUint8(sigB.offset,
|
|
565
|
+
// b = X (value=1, mask=1) — X encoding: v=1, m=1
|
|
566
|
+
view.setUint8(sigB.offset, 1);
|
|
566
567
|
view.setUint8(sigB.offset + sigB.byteSize, 1);
|
|
567
568
|
|
|
568
569
|
raw.evalComb();
|
|
@@ -572,9 +573,9 @@ module InitTest (
|
|
|
572
573
|
expect(vAnd).toBe(0n);
|
|
573
574
|
expect(mAnd).toBe(0n);
|
|
574
575
|
|
|
575
|
-
// 0 | X = X (mask
|
|
576
|
+
// 0 | X = X (value=1, mask=1 in X encoding)
|
|
576
577
|
const [vOr, mOr] = readFourState(buf, sigYOr);
|
|
577
|
-
expect(vOr).toBe(
|
|
578
|
+
expect(vOr).toBe(1n);
|
|
578
579
|
expect(mOr).toBe(1n);
|
|
579
580
|
});
|
|
580
581
|
|
|
@@ -598,8 +599,8 @@ module InitTest (
|
|
|
598
599
|
view.setUint8(sigA.offset, 1);
|
|
599
600
|
view.setUint8(sigA.offset + sigA.byteSize, 0);
|
|
600
601
|
|
|
601
|
-
// b = X (value=
|
|
602
|
-
view.setUint8(sigB.offset,
|
|
602
|
+
// b = X (value=1, mask=1) — X encoding: v=1, m=1
|
|
603
|
+
view.setUint8(sigB.offset, 1);
|
|
603
604
|
view.setUint8(sigB.offset + sigB.byteSize, 1);
|
|
604
605
|
|
|
605
606
|
raw.evalComb();
|
|
@@ -1889,6 +1890,36 @@ describe("E2E: celox.toml test sources", () => {
|
|
|
1889
1890
|
expect(err.steps).toBe(5);
|
|
1890
1891
|
sim.dispose();
|
|
1891
1892
|
});
|
|
1893
|
+
|
|
1894
|
+
test("exclude patterns filter out matching files", () => {
|
|
1895
|
+
// The fixture has test_veryl/Excluded.veryl with invalid syntax,
|
|
1896
|
+
// but celox.toml excludes it via `exclude = ["test_veryl/Excluded.veryl"]`.
|
|
1897
|
+
// If exclude works, fromProject succeeds (the broken file is skipped).
|
|
1898
|
+
// If exclude is broken, the parse error will cause an exception.
|
|
1899
|
+
const sim = Simulator.fromProject<{
|
|
1900
|
+
rst: bigint;
|
|
1901
|
+
d: bigint;
|
|
1902
|
+
readonly q: bigint;
|
|
1903
|
+
}>(CELOX_TOML_PROJECT, "Reg");
|
|
1904
|
+
sim.dut.rst = 1n;
|
|
1905
|
+
sim.dut.d = 42n;
|
|
1906
|
+
sim.tick();
|
|
1907
|
+
expect(sim.dut.q).toBe(42n);
|
|
1908
|
+
sim.dispose();
|
|
1909
|
+
});
|
|
1910
|
+
|
|
1911
|
+
test("genTs excludes files matching exclude patterns", () => {
|
|
1912
|
+
const addon = loadNativeAddon();
|
|
1913
|
+
const json = addon.genTs(CELOX_TOML_PROJECT);
|
|
1914
|
+
const output = JSON.parse(json) as {
|
|
1915
|
+
modules: Array<{ moduleName: string }>;
|
|
1916
|
+
};
|
|
1917
|
+
const names = output.modules.map((m) => m.moduleName);
|
|
1918
|
+
expect(names).toContain("Adder");
|
|
1919
|
+
expect(names).toContain("Reg");
|
|
1920
|
+
// Excluded.veryl is excluded by the glob pattern
|
|
1921
|
+
expect(names).not.toContain("Excluded");
|
|
1922
|
+
});
|
|
1892
1923
|
});
|
|
1893
1924
|
|
|
1894
1925
|
// ---------------------------------------------------------------------------
|
|
@@ -2657,3 +2688,95 @@ describe("E2E: non-byte-aligned unpacked array port", () => {
|
|
|
2657
2688
|
sim.dispose();
|
|
2658
2689
|
});
|
|
2659
2690
|
});
|
|
2691
|
+
|
|
2692
|
+
// ---------------------------------------------------------------------------
|
|
2693
|
+
// Native testbench (run_test / run_test_detailed)
|
|
2694
|
+
// ---------------------------------------------------------------------------
|
|
2695
|
+
|
|
2696
|
+
const TB_COUNTER_SOURCE = `
|
|
2697
|
+
module Counter_tb (
|
|
2698
|
+
clk: input clock ,
|
|
2699
|
+
rst: input reset ,
|
|
2700
|
+
cnt: output logic<32>,
|
|
2701
|
+
) {
|
|
2702
|
+
always_ff {
|
|
2703
|
+
if_reset {
|
|
2704
|
+
cnt = 0;
|
|
2705
|
+
} else {
|
|
2706
|
+
cnt += 1;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
`;
|
|
2711
|
+
|
|
2712
|
+
const TB_PASS_SOURCE = `
|
|
2713
|
+
${TB_COUNTER_SOURCE}
|
|
2714
|
+
#[test(t)]
|
|
2715
|
+
module CounterTbPass {
|
|
2716
|
+
inst clk: $tb::clock_gen;
|
|
2717
|
+
inst rst: $tb::reset_gen;
|
|
2718
|
+
var cnt: logic<32>;
|
|
2719
|
+
inst dut: Counter_tb (clk, rst, cnt);
|
|
2720
|
+
initial {
|
|
2721
|
+
rst.assert(clk);
|
|
2722
|
+
clk.next (10);
|
|
2723
|
+
$assert (cnt == 32'd10);
|
|
2724
|
+
$finish ();
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
`;
|
|
2728
|
+
|
|
2729
|
+
const TB_FAIL_SOURCE = `
|
|
2730
|
+
${TB_COUNTER_SOURCE}
|
|
2731
|
+
#[test(t)]
|
|
2732
|
+
module CounterTbFail {
|
|
2733
|
+
inst clk: $tb::clock_gen;
|
|
2734
|
+
inst rst: $tb::reset_gen;
|
|
2735
|
+
var cnt: logic<32>;
|
|
2736
|
+
inst dut: Counter_tb (clk, rst, cnt);
|
|
2737
|
+
initial {
|
|
2738
|
+
rst.assert(clk);
|
|
2739
|
+
clk.next (5);
|
|
2740
|
+
$assert (cnt == 32'd99);
|
|
2741
|
+
$assert (cnt == 32'd5);
|
|
2742
|
+
$finish ();
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
`;
|
|
2746
|
+
|
|
2747
|
+
describe("E2E: native testbench (runTest)", () => {
|
|
2748
|
+
const addon = loadNativeAddon();
|
|
2749
|
+
|
|
2750
|
+
test("passing testbench returns passed=true with all assertions passed", () => {
|
|
2751
|
+
const result: NapiTestResult = addon.runTest(
|
|
2752
|
+
[{ content: TB_PASS_SOURCE, path: "test.veryl" }],
|
|
2753
|
+
"CounterTbPass",
|
|
2754
|
+
);
|
|
2755
|
+
expect(result.passed).toBe(true);
|
|
2756
|
+
expect(result.assertions.length).toBe(1);
|
|
2757
|
+
expect(result.assertions[0]!.passed).toBe(true);
|
|
2758
|
+
});
|
|
2759
|
+
|
|
2760
|
+
test("failing testbench returns passed=false and collects all assertions", () => {
|
|
2761
|
+
const result: NapiTestResult = addon.runTest(
|
|
2762
|
+
[{ content: TB_FAIL_SOURCE, path: "test.veryl" }],
|
|
2763
|
+
"CounterTbFail",
|
|
2764
|
+
);
|
|
2765
|
+
expect(result.passed).toBe(false);
|
|
2766
|
+
// Both assertions are collected (not stopped at first failure)
|
|
2767
|
+
expect(result.assertions.length).toBe(2);
|
|
2768
|
+
expect(result.assertions[0]!.passed).toBe(false);
|
|
2769
|
+
expect(result.assertions[1]!.passed).toBe(true);
|
|
2770
|
+
});
|
|
2771
|
+
|
|
2772
|
+
test("assertion results include source location", () => {
|
|
2773
|
+
const result: NapiTestResult = addon.runTest(
|
|
2774
|
+
[{ content: TB_PASS_SOURCE, path: "test.veryl" }],
|
|
2775
|
+
"CounterTbPass",
|
|
2776
|
+
);
|
|
2777
|
+
const a = result.assertions[0]!;
|
|
2778
|
+
expect(a.file).toBeDefined();
|
|
2779
|
+
expect(a.line).toBeGreaterThan(0);
|
|
2780
|
+
expect(a.column).toBeGreaterThan(0);
|
|
2781
|
+
});
|
|
2782
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -66,5 +66,17 @@ export {
|
|
|
66
66
|
Z,
|
|
67
67
|
} from "./types.js";
|
|
68
68
|
|
|
69
|
+
// WASM bridge for browser simulation
|
|
70
|
+
/** @internal */
|
|
71
|
+
export type {
|
|
72
|
+
RawWasmSimulatorHandle,
|
|
73
|
+
WasmBridgeResult,
|
|
74
|
+
} from "./wasm-bridge.js";
|
|
75
|
+
export {
|
|
76
|
+
createWasmSimulatorBridge,
|
|
77
|
+
createWasmSimulatorBridgeAsync,
|
|
78
|
+
isWasmHandle,
|
|
79
|
+
} from "./wasm-bridge.js";
|
|
80
|
+
|
|
69
81
|
// NAPI bridge (backward compat — re-exports from napi-helpers)
|
|
70
82
|
// Consumers that import from "./napi-bridge.js" still work.
|
package/src/napi-helpers.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
SourceFile,
|
|
23
23
|
TrueLoopSpec,
|
|
24
24
|
} from "./types.js";
|
|
25
|
+
import { createWasmSimulatorBridge, isWasmHandle } from "./wasm-bridge.js";
|
|
25
26
|
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
27
28
|
// Raw NAPI handle shapes (what the .node addon actually exports)
|
|
@@ -34,11 +35,15 @@ export interface RawNapiSimulatorHandle {
|
|
|
34
35
|
readonly warningsJson: string;
|
|
35
36
|
readonly stableSize: number;
|
|
36
37
|
readonly totalSize: number;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
// Native (JIT) methods — present when built for native target
|
|
39
|
+
tick?(eventId: number): void;
|
|
40
|
+
tickN?(eventId: number, count: number): void;
|
|
41
|
+
evalComb?(): void;
|
|
42
|
+
dump?(timestamp: number): void;
|
|
43
|
+
sharedMemory?(): Uint8Array;
|
|
44
|
+
// WASM methods — present when built for wasm32 target
|
|
45
|
+
combWasmBytes?(): Uint8Array | number[];
|
|
46
|
+
eventWasmBytes?(name: string): Uint8Array | number[];
|
|
42
47
|
dispose(): void;
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -98,11 +103,14 @@ export interface NapiOptimizeOptions {
|
|
|
98
103
|
inlineCommitForwarding?: boolean;
|
|
99
104
|
eliminateDeadWorkingStores?: boolean;
|
|
100
105
|
reschedule?: boolean;
|
|
106
|
+
coalesceStores?: boolean;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
export interface NapiOptions {
|
|
104
110
|
fourState?: boolean;
|
|
105
111
|
vcd?: string;
|
|
112
|
+
optLevel?: string;
|
|
113
|
+
passOverrides?: string[];
|
|
106
114
|
optimize?: boolean;
|
|
107
115
|
optimizeOptions?: NapiOptimizeOptions;
|
|
108
116
|
craneliftOptLevel?: string;
|
|
@@ -123,6 +131,19 @@ export interface NapiSourceFile {
|
|
|
123
131
|
path: string;
|
|
124
132
|
}
|
|
125
133
|
|
|
134
|
+
export interface NapiAssertionResult {
|
|
135
|
+
passed: boolean;
|
|
136
|
+
message?: string;
|
|
137
|
+
file?: string;
|
|
138
|
+
line?: number;
|
|
139
|
+
column?: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface NapiTestResult {
|
|
143
|
+
passed: boolean;
|
|
144
|
+
assertions: NapiAssertionResult[];
|
|
145
|
+
}
|
|
146
|
+
|
|
126
147
|
export interface RawNapiAddon {
|
|
127
148
|
NativeSimulatorHandle: {
|
|
128
149
|
new (
|
|
@@ -150,6 +171,16 @@ export interface RawNapiAddon {
|
|
|
150
171
|
};
|
|
151
172
|
genTs(projectPath: string): string;
|
|
152
173
|
clearJitCache(): void;
|
|
174
|
+
runTest(
|
|
175
|
+
sources: NapiSourceFile[],
|
|
176
|
+
top: string,
|
|
177
|
+
options?: NapiOptions,
|
|
178
|
+
): NapiTestResult;
|
|
179
|
+
runTestFromProject(
|
|
180
|
+
projectPath: string,
|
|
181
|
+
top: string,
|
|
182
|
+
options?: NapiOptions,
|
|
183
|
+
): NapiTestResult;
|
|
153
184
|
}
|
|
154
185
|
|
|
155
186
|
// ---------------------------------------------------------------------------
|
|
@@ -248,6 +279,18 @@ export function buildNapiOpts(
|
|
|
248
279
|
napiOpts.vcd = options.vcd;
|
|
249
280
|
hasOpt = true;
|
|
250
281
|
}
|
|
282
|
+
if (options.optLevel) {
|
|
283
|
+
napiOpts.optLevel = options.optLevel;
|
|
284
|
+
hasOpt = true;
|
|
285
|
+
}
|
|
286
|
+
if (options.passOverrides && options.passOverrides.length > 0) {
|
|
287
|
+
// Convert camelCase pass names to snake_case for NAPI
|
|
288
|
+
napiOpts.passOverrides = options.passOverrides.map((s) => {
|
|
289
|
+
// Pass through +/- prefix and sir: prefix as-is; the Rust side parses them.
|
|
290
|
+
return s;
|
|
291
|
+
});
|
|
292
|
+
hasOpt = true;
|
|
293
|
+
}
|
|
251
294
|
if (options.optimize != null) {
|
|
252
295
|
napiOpts.optimize = options.optimize;
|
|
253
296
|
hasOpt = true;
|
|
@@ -331,6 +374,7 @@ export function buildNapiOpts(
|
|
|
331
374
|
if (oo.eliminateDeadWorkingStores != null)
|
|
332
375
|
napiOo.eliminateDeadWorkingStores = oo.eliminateDeadWorkingStores;
|
|
333
376
|
if (oo.reschedule != null) napiOo.reschedule = oo.reschedule;
|
|
377
|
+
if (oo.coalesceStores != null) napiOo.coalesceStores = oo.coalesceStores;
|
|
334
378
|
napiOpts.optimizeOptions = napiOo;
|
|
335
379
|
hasOpt = true;
|
|
336
380
|
}
|
|
@@ -523,6 +567,19 @@ export function parseHierarchyLayout(
|
|
|
523
567
|
events: Record<string, number>,
|
|
524
568
|
): HierarchyNode {
|
|
525
569
|
const raw: RawHierarchyNode = JSON.parse(json);
|
|
570
|
+
|
|
571
|
+
// WASM-compiled addon may return an empty object `{}` for hierarchy.
|
|
572
|
+
// Treat it as an empty hierarchy node.
|
|
573
|
+
if (!raw.signals && !raw.module_name) {
|
|
574
|
+
return {
|
|
575
|
+
moduleName: "",
|
|
576
|
+
signals: {},
|
|
577
|
+
forDut: {},
|
|
578
|
+
ports: {},
|
|
579
|
+
children: {},
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
526
583
|
return convertHierarchyNode(raw, events);
|
|
527
584
|
}
|
|
528
585
|
|
|
@@ -603,16 +660,16 @@ export function wrapDirectSimulatorHandle(
|
|
|
603
660
|
): NativeSimulatorHandle {
|
|
604
661
|
return {
|
|
605
662
|
tick(eventId: number): void {
|
|
606
|
-
raw.tick(eventId);
|
|
663
|
+
raw.tick!(eventId);
|
|
607
664
|
},
|
|
608
665
|
tickN(eventId: number, count: number): void {
|
|
609
|
-
raw.tickN(eventId, count);
|
|
666
|
+
raw.tickN!(eventId, count);
|
|
610
667
|
},
|
|
611
668
|
evalComb(): void {
|
|
612
|
-
raw.evalComb();
|
|
669
|
+
raw.evalComb!();
|
|
613
670
|
},
|
|
614
671
|
dump(timestamp: number): void {
|
|
615
|
-
raw.dump(timestamp);
|
|
672
|
+
raw.dump!(timestamp);
|
|
616
673
|
},
|
|
617
674
|
dispose(): void {
|
|
618
675
|
raw.dispose();
|
|
@@ -705,8 +762,17 @@ export function createSimulatorBridge(addon: RawNapiAddon): NativeCreateFn {
|
|
|
705
762
|
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
706
763
|
const hierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
|
|
707
764
|
|
|
708
|
-
|
|
709
|
-
|
|
765
|
+
// Detect WASM-compiled addon and use the bridge
|
|
766
|
+
let buf: ArrayBuffer | SharedArrayBuffer;
|
|
767
|
+
let handle: NativeSimulatorHandle;
|
|
768
|
+
if (isWasmHandle(raw)) {
|
|
769
|
+
const bridge = createWasmSimulatorBridge(raw);
|
|
770
|
+
buf = bridge.sharedMemory.buffer;
|
|
771
|
+
handle = bridge.handle;
|
|
772
|
+
} else {
|
|
773
|
+
buf = raw.sharedMemory!().buffer;
|
|
774
|
+
handle = wrapDirectSimulatorHandle(raw);
|
|
775
|
+
}
|
|
710
776
|
|
|
711
777
|
const warnings: string[] = JSON.parse(raw.warningsJson ?? "[]");
|
|
712
778
|
|
|
@@ -726,6 +792,14 @@ export function createSimulationBridge(
|
|
|
726
792
|
moduleName: string,
|
|
727
793
|
options: SimulatorOptions,
|
|
728
794
|
): CreateResult<NativeSimulationHandle> => {
|
|
795
|
+
// WASM addon does not export NativeSimulationHandle
|
|
796
|
+
if (!addon.NativeSimulationHandle) {
|
|
797
|
+
throw new Error(
|
|
798
|
+
"Simulation is not supported in WASM mode. " +
|
|
799
|
+
"The WASM-compiled addon only supports event-based Simulator, not time-based Simulation.",
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
|
|
729
803
|
const napiOpts = buildNapiOpts(options);
|
|
730
804
|
const napiSources = sources.map((s) => ({
|
|
731
805
|
content: s.content,
|
package/src/simulation.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
SourceFile,
|
|
26
26
|
} from "./types.js";
|
|
27
27
|
import { SimulationTimeoutError } from "./types.js";
|
|
28
|
+
import { isWasmHandle } from "./wasm-bridge.js";
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Placeholder for the NAPI binding's `createSimulation()`.
|
|
@@ -190,12 +191,31 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
190
191
|
): Simulation<P> {
|
|
191
192
|
const addon = loadNativeAddon(options?.nativeAddonPath);
|
|
192
193
|
const napiOpts = buildNapiOpts(options);
|
|
194
|
+
// WASM addon does not support time-based Simulation (no NativeSimulationHandle).
|
|
195
|
+
// Check before attempting to construct the handle.
|
|
196
|
+
if (!addon.NativeSimulationHandle) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
"Simulation is not supported in WASM mode. " +
|
|
199
|
+
"The WASM-compiled addon only supports event-based Simulator, not time-based Simulation. " +
|
|
200
|
+
"Use Simulator.fromSource() instead.",
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
193
204
|
const raw = new addon.NativeSimulationHandle(
|
|
194
205
|
[{ content: source, path: "" }],
|
|
195
206
|
top,
|
|
196
207
|
napiOpts,
|
|
197
208
|
);
|
|
198
209
|
|
|
210
|
+
if (isWasmHandle(raw)) {
|
|
211
|
+
raw.dispose();
|
|
212
|
+
throw new Error(
|
|
213
|
+
"Simulation is not supported in WASM mode. " +
|
|
214
|
+
"The WASM-compiled addon only supports event-based Simulator, not time-based Simulation. " +
|
|
215
|
+
"Use Simulator.fromSource() instead.",
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
199
219
|
const layout = parseNapiLayout(raw.layoutJson);
|
|
200
220
|
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
201
221
|
const rawHierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
|
|
@@ -251,12 +271,31 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
251
271
|
): Simulation<P> {
|
|
252
272
|
const addon = loadNativeAddon(options?.nativeAddonPath);
|
|
253
273
|
const napiOpts = buildNapiOpts(options);
|
|
274
|
+
|
|
275
|
+
// WASM addon does not support time-based Simulation (no NativeSimulationHandle).
|
|
276
|
+
if (!addon.NativeSimulationHandle) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
"Simulation is not supported in WASM mode. " +
|
|
279
|
+
"The WASM-compiled addon only supports event-based Simulator, not time-based Simulation. " +
|
|
280
|
+
"Use Simulator.fromProject() instead.",
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
254
284
|
const raw = addon.NativeSimulationHandle.fromProject(
|
|
255
285
|
projectPath,
|
|
256
286
|
top,
|
|
257
287
|
napiOpts,
|
|
258
288
|
);
|
|
259
289
|
|
|
290
|
+
if (isWasmHandle(raw)) {
|
|
291
|
+
raw.dispose();
|
|
292
|
+
throw new Error(
|
|
293
|
+
"Simulation is not supported in WASM mode. " +
|
|
294
|
+
"The WASM-compiled addon only supports event-based Simulator, not time-based Simulation. " +
|
|
295
|
+
"Use Simulator.fromProject() instead.",
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
260
299
|
const layout = parseNapiLayout(raw.layoutJson);
|
|
261
300
|
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
262
301
|
const rawHierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
|