@celox-sim/celox 0.1.30 → 0.1.32
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 +130 -7
- 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
package/src/simulator.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
SimulatorOptions,
|
|
26
26
|
SourceFile,
|
|
27
27
|
} from "./types.js";
|
|
28
|
+
import { createWasmSimulatorBridge, isWasmHandle } from "./wasm-bridge.js";
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Placeholder for the NAPI binding's `createSimulator()`.
|
|
@@ -202,19 +203,34 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
202
203
|
options?.deadStorePolicy,
|
|
203
204
|
);
|
|
204
205
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
// When hierarchy is populated, use its signals for port detection.
|
|
207
|
+
// When hierarchy is empty (e.g. WASM-compiled addon), fall back to
|
|
208
|
+
// the flat layout signals which always have full signal info.
|
|
209
|
+
const hasHierarchySignals = Object.keys(hierarchy.signals).length > 0;
|
|
210
|
+
const ports = hasHierarchySignals
|
|
211
|
+
? buildPortsFromLayout(hierarchy.signals, events)
|
|
212
|
+
: buildPortsFromLayout(layout.signals, events);
|
|
213
|
+
|
|
214
|
+
// Detect WASM-compiled addon and use the bridge
|
|
215
|
+
let buf: ArrayBuffer | SharedArrayBuffer;
|
|
216
|
+
let handle: NativeSimulatorHandle;
|
|
217
|
+
if (isWasmHandle(raw)) {
|
|
218
|
+
const bridge = createWasmSimulatorBridge(raw);
|
|
219
|
+
buf = bridge.sharedMemory.buffer;
|
|
220
|
+
handle = bridge.handle;
|
|
221
|
+
} else {
|
|
222
|
+
buf = raw.sharedMemory!().buffer;
|
|
223
|
+
handle = wrapDirectSimulatorHandle(raw);
|
|
224
|
+
}
|
|
208
225
|
|
|
209
226
|
const state: DirtyState = { dirty: false };
|
|
210
|
-
const handle = wrapDirectSimulatorHandle(raw);
|
|
211
227
|
const dut = createDut<P>(
|
|
212
228
|
buf,
|
|
213
229
|
layout.forDut,
|
|
214
230
|
ports,
|
|
215
231
|
handle,
|
|
216
232
|
state,
|
|
217
|
-
hierarchy,
|
|
233
|
+
hasHierarchySignals ? hierarchy : undefined,
|
|
218
234
|
);
|
|
219
235
|
|
|
220
236
|
const warnings: string[] = JSON.parse(raw.warningsJson ?? "[]");
|
|
@@ -262,19 +278,31 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
262
278
|
options?.deadStorePolicy,
|
|
263
279
|
);
|
|
264
280
|
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
281
|
+
const hasHierarchySignals = Object.keys(hierarchy.signals).length > 0;
|
|
282
|
+
const ports = hasHierarchySignals
|
|
283
|
+
? buildPortsFromLayout(hierarchy.signals, events)
|
|
284
|
+
: buildPortsFromLayout(layout.signals, events);
|
|
285
|
+
|
|
286
|
+
// Detect WASM-compiled addon and use the bridge
|
|
287
|
+
let buf: ArrayBuffer | SharedArrayBuffer;
|
|
288
|
+
let handle: NativeSimulatorHandle;
|
|
289
|
+
if (isWasmHandle(raw)) {
|
|
290
|
+
const bridge = createWasmSimulatorBridge(raw);
|
|
291
|
+
buf = bridge.sharedMemory.buffer;
|
|
292
|
+
handle = bridge.handle;
|
|
293
|
+
} else {
|
|
294
|
+
buf = raw.sharedMemory!().buffer;
|
|
295
|
+
handle = wrapDirectSimulatorHandle(raw);
|
|
296
|
+
}
|
|
268
297
|
|
|
269
298
|
const state: DirtyState = { dirty: false };
|
|
270
|
-
const handle = wrapDirectSimulatorHandle(raw);
|
|
271
299
|
const dut = createDut<P>(
|
|
272
300
|
buf,
|
|
273
301
|
layout.forDut,
|
|
274
302
|
ports,
|
|
275
303
|
handle,
|
|
276
304
|
state,
|
|
277
|
-
hierarchy,
|
|
305
|
+
hasHierarchySignals ? hierarchy : undefined,
|
|
278
306
|
);
|
|
279
307
|
|
|
280
308
|
const warnings: string[] = JSON.parse(raw.warningsJson ?? "[]");
|
package/src/types.ts
CHANGED
|
@@ -153,6 +153,8 @@ export interface OptimizeOptions {
|
|
|
153
153
|
eliminateDeadWorkingStores?: boolean;
|
|
154
154
|
/** Reorders instructions for better Cranelift code generation. */
|
|
155
155
|
reschedule?: boolean;
|
|
156
|
+
/** Merges consecutive narrow stores into wider Concat+Store operations. */
|
|
157
|
+
coalesceStores?: boolean;
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
export interface SimulatorOptions {
|
|
@@ -160,12 +162,37 @@ export interface SimulatorOptions {
|
|
|
160
162
|
fourState?: boolean;
|
|
161
163
|
/** Path to write VCD waveform output. */
|
|
162
164
|
vcd?: string;
|
|
165
|
+
/**
|
|
166
|
+
* Optimization level preset. Default: "O1".
|
|
167
|
+
* - "O0": No SIR optimizations (except TailCallSplit). Fast compilation.
|
|
168
|
+
* - "O1": All SIR optimizations enabled. Default.
|
|
169
|
+
* - "O2": O1 + dead store elimination (PreserveTopPorts).
|
|
170
|
+
*/
|
|
171
|
+
optLevel?: "O0" | "O1" | "O2";
|
|
172
|
+
/**
|
|
173
|
+
* Per-pass overrides applied on top of `optLevel`.
|
|
174
|
+
* Each entry: "+sir:<pass_name>" to enable, "-sir:<pass_name>" to disable.
|
|
175
|
+
*
|
|
176
|
+
* Valid SIR passes: store_load_forwarding, hoist_common_branch_loads,
|
|
177
|
+
* bit_extract_peephole, optimize_blocks, split_wide_commits, commit_sinking,
|
|
178
|
+
* inline_commit_forwarding, eliminate_dead_working_stores, reschedule,
|
|
179
|
+
* coalesce_stores, gvn, concat_folding, xor_chain_folding, vectorize_concat,
|
|
180
|
+
* split_coalesced_stores, partial_forward, identity_store_bypass, tail_call_split
|
|
181
|
+
*
|
|
182
|
+
* @example ["-sir:reschedule", "+sir:gvn"]
|
|
183
|
+
*/
|
|
184
|
+
passOverrides?: string[];
|
|
163
185
|
/**
|
|
164
186
|
* Shorthand to enable/disable all SIRT optimization passes.
|
|
165
|
-
* `true` = all on, `false` = all off
|
|
187
|
+
* `true` = all on (O1), `false` = all off (O0).
|
|
188
|
+
* Overridden by `optLevel` or `optimizeOptions` if set.
|
|
189
|
+
* @deprecated Use `optLevel` instead.
|
|
166
190
|
*/
|
|
167
191
|
optimize?: boolean;
|
|
168
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* Per-pass SIRT optimizer flags. Takes precedence over `optimize`.
|
|
194
|
+
* @deprecated Use `optLevel` + `passOverrides` instead.
|
|
195
|
+
*/
|
|
169
196
|
optimizeOptions?: OptimizeOptions;
|
|
170
197
|
/** Cranelift backend optimization level. Default: "speed". */
|
|
171
198
|
craneliftOptLevel?: "none" | "speed" | "speedAndSize";
|
|
@@ -197,7 +224,10 @@ export interface SimulatorOptions {
|
|
|
197
224
|
extraSource?: string;
|
|
198
225
|
/** Top-level module parameter overrides. */
|
|
199
226
|
parameters?: ParamOverride[];
|
|
200
|
-
/**
|
|
227
|
+
/**
|
|
228
|
+
* Dead store elimination policy. Default: "off".
|
|
229
|
+
* When `optLevel` is "O2", defaults to "preserveTopPorts" unless explicitly set.
|
|
230
|
+
*/
|
|
201
231
|
deadStorePolicy?: "off" | "preserveTopPorts" | "preserveAllPorts";
|
|
202
232
|
}
|
|
203
233
|
|
|
@@ -0,0 +1,228 @@
|
|
|
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
|
+
import type { NativeSimulatorHandle } from "./types.js";
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Minimal WebAssembly type declarations for environments without DOM lib.
|
|
17
|
+
// These mirror the subset of the WebAssembly API that we use.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
20
|
+
declare namespace WebAssembly {
|
|
21
|
+
interface MemoryDescriptor {
|
|
22
|
+
initial: number;
|
|
23
|
+
maximum?: number;
|
|
24
|
+
shared?: boolean;
|
|
25
|
+
}
|
|
26
|
+
class Memory {
|
|
27
|
+
constructor(descriptor: MemoryDescriptor);
|
|
28
|
+
readonly buffer: ArrayBuffer;
|
|
29
|
+
}
|
|
30
|
+
class Module {
|
|
31
|
+
constructor(bytes: ArrayBuffer | ArrayBufferView);
|
|
32
|
+
}
|
|
33
|
+
class Instance {
|
|
34
|
+
constructor(module: Module, importObject?: Record<string, unknown>);
|
|
35
|
+
readonly exports: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
function compile(bytes: ArrayBuffer | ArrayBufferView): Promise<Module>;
|
|
38
|
+
function instantiate(
|
|
39
|
+
module: Module,
|
|
40
|
+
importObject?: Record<string, unknown>,
|
|
41
|
+
): Promise<Instance>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Raw WASM handle shape (what the wasm32-compiled addon exposes)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Handle shape returned by a wasm32-compiled NativeSimulatorHandle.
|
|
50
|
+
* Extends the standard metadata getters with WASM bytecode accessors.
|
|
51
|
+
*/
|
|
52
|
+
export interface RawWasmSimulatorHandle {
|
|
53
|
+
readonly layoutJson: string;
|
|
54
|
+
readonly eventsJson: string;
|
|
55
|
+
readonly hierarchyJson: string;
|
|
56
|
+
readonly warningsJson: string;
|
|
57
|
+
readonly stableSize: number;
|
|
58
|
+
readonly totalSize: number;
|
|
59
|
+
combWasmBytes(): Uint8Array | number[];
|
|
60
|
+
eventWasmBytes(name: string): Uint8Array | number[];
|
|
61
|
+
dispose(): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Detection
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Detect if a handle was produced by a wasm32-compiled addon.
|
|
70
|
+
*
|
|
71
|
+
* WASM handles expose `combWasmBytes()` instead of `tick()`.
|
|
72
|
+
*/
|
|
73
|
+
export function isWasmHandle(
|
|
74
|
+
handle: unknown,
|
|
75
|
+
): handle is RawWasmSimulatorHandle {
|
|
76
|
+
return (
|
|
77
|
+
typeof handle === "object" &&
|
|
78
|
+
handle !== null &&
|
|
79
|
+
typeof (handle as Record<string, unknown>).combWasmBytes === "function" &&
|
|
80
|
+
typeof (handle as Record<string, unknown>).tick !== "function"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Bridge
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/** Result of creating a WASM simulator bridge. */
|
|
89
|
+
export interface WasmBridgeResult {
|
|
90
|
+
/** Handle compatible with the existing Simulator code. */
|
|
91
|
+
handle: NativeSimulatorHandle;
|
|
92
|
+
/** The raw shared memory as a Uint8Array (backed by WebAssembly.Memory). */
|
|
93
|
+
sharedMemory: Uint8Array;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create a NativeSimulatorHandle-compatible wrapper from a wasm32-compiled
|
|
98
|
+
* NAPI handle.
|
|
99
|
+
*
|
|
100
|
+
* 1. Reads metadata (layout, events, sizes) from the raw handle.
|
|
101
|
+
* 2. Creates a WebAssembly.Memory large enough for the simulation state.
|
|
102
|
+
* 3. Synchronously compiles and instantiates the combinational and event
|
|
103
|
+
* WASM modules, importing the shared memory.
|
|
104
|
+
* 4. Returns a handle whose `tick()` / `evalComb()` drive the WASM instances.
|
|
105
|
+
*
|
|
106
|
+
* @returns A bridge result with the wrapped handle and shared memory view.
|
|
107
|
+
*/
|
|
108
|
+
export function createWasmSimulatorBridge(
|
|
109
|
+
raw: RawWasmSimulatorHandle,
|
|
110
|
+
): WasmBridgeResult {
|
|
111
|
+
const totalSize = raw.totalSize;
|
|
112
|
+
const stableSize = raw.stableSize;
|
|
113
|
+
|
|
114
|
+
// Create shared WebAssembly.Memory
|
|
115
|
+
const pages = Math.max(1, Math.ceil(totalSize / 65536));
|
|
116
|
+
const memory = new WebAssembly.Memory({ initial: pages });
|
|
117
|
+
|
|
118
|
+
// Compile and instantiate comb WASM module (synchronous)
|
|
119
|
+
const combBytes = new Uint8Array(raw.combWasmBytes());
|
|
120
|
+
const combModule = new WebAssembly.Module(combBytes);
|
|
121
|
+
const combInstance = new WebAssembly.Instance(combModule, {
|
|
122
|
+
env: { memory },
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Parse events and instantiate per-event WASM modules
|
|
126
|
+
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
127
|
+
const eventInstances = new Map<number, WebAssembly.Instance>();
|
|
128
|
+
|
|
129
|
+
for (const [name, id] of Object.entries(events)) {
|
|
130
|
+
const bytes = new Uint8Array(raw.eventWasmBytes(name));
|
|
131
|
+
const mod = new WebAssembly.Module(bytes);
|
|
132
|
+
const inst = new WebAssembly.Instance(mod, { env: { memory } });
|
|
133
|
+
eventInstances.set(id, inst);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const sharedMemory = new Uint8Array(memory.buffer, 0, stableSize);
|
|
137
|
+
|
|
138
|
+
const handle: NativeSimulatorHandle = {
|
|
139
|
+
tick(eventId: number): void {
|
|
140
|
+
// eval_comb → eval_apply_ff → eval_comb
|
|
141
|
+
(combInstance.exports.run as CallableFunction)();
|
|
142
|
+
const evInst = eventInstances.get(eventId);
|
|
143
|
+
if (evInst) (evInst.exports.run as CallableFunction)();
|
|
144
|
+
(combInstance.exports.run as CallableFunction)();
|
|
145
|
+
},
|
|
146
|
+
tickN(eventId: number, count: number): void {
|
|
147
|
+
for (let i = 0; i < count; i++) {
|
|
148
|
+
this.tick(eventId);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
evalComb(): void {
|
|
152
|
+
(combInstance.exports.run as CallableFunction)();
|
|
153
|
+
},
|
|
154
|
+
dump(_timestamp: number): void {
|
|
155
|
+
// No VCD support in browser WASM mode
|
|
156
|
+
},
|
|
157
|
+
dispose(): void {
|
|
158
|
+
raw.dispose();
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return { handle, sharedMemory };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Asynchronous version of `createWasmSimulatorBridge` that uses
|
|
167
|
+
* `WebAssembly.compile()` + `WebAssembly.instantiate()`.
|
|
168
|
+
*
|
|
169
|
+
* Preferred for large modules in browsers where synchronous compilation
|
|
170
|
+
* may be rejected.
|
|
171
|
+
*/
|
|
172
|
+
export async function createWasmSimulatorBridgeAsync(
|
|
173
|
+
raw: RawWasmSimulatorHandle,
|
|
174
|
+
): Promise<WasmBridgeResult> {
|
|
175
|
+
const totalSize = raw.totalSize;
|
|
176
|
+
const stableSize = raw.stableSize;
|
|
177
|
+
|
|
178
|
+
const pages = Math.max(1, Math.ceil(totalSize / 65536));
|
|
179
|
+
const memory = new WebAssembly.Memory({ initial: pages });
|
|
180
|
+
|
|
181
|
+
// Compile comb module
|
|
182
|
+
const combBytes = new Uint8Array(raw.combWasmBytes());
|
|
183
|
+
const combModule = await WebAssembly.compile(combBytes);
|
|
184
|
+
const combInstance = await WebAssembly.instantiate(combModule, {
|
|
185
|
+
env: { memory },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Parse events and compile per-event modules
|
|
189
|
+
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
190
|
+
const eventInstances = new Map<number, WebAssembly.Instance>();
|
|
191
|
+
|
|
192
|
+
const eventEntries = Object.entries(events);
|
|
193
|
+
await Promise.all(
|
|
194
|
+
eventEntries.map(async ([name, id]) => {
|
|
195
|
+
const bytes = new Uint8Array(raw.eventWasmBytes(name));
|
|
196
|
+
const mod = await WebAssembly.compile(bytes);
|
|
197
|
+
const inst = await WebAssembly.instantiate(mod, { env: { memory } });
|
|
198
|
+
eventInstances.set(id, inst);
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const sharedMemory = new Uint8Array(memory.buffer, 0, stableSize);
|
|
203
|
+
|
|
204
|
+
const handle: NativeSimulatorHandle = {
|
|
205
|
+
tick(eventId: number): void {
|
|
206
|
+
(combInstance.exports.run as CallableFunction)();
|
|
207
|
+
const evInst = eventInstances.get(eventId);
|
|
208
|
+
if (evInst) (evInst.exports.run as CallableFunction)();
|
|
209
|
+
(combInstance.exports.run as CallableFunction)();
|
|
210
|
+
},
|
|
211
|
+
tickN(eventId: number, count: number): void {
|
|
212
|
+
for (let i = 0; i < count; i++) {
|
|
213
|
+
this.tick(eventId);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
evalComb(): void {
|
|
217
|
+
(combInstance.exports.run as CallableFunction)();
|
|
218
|
+
},
|
|
219
|
+
dump(_timestamp: number): void {
|
|
220
|
+
// No VCD support in browser WASM mode
|
|
221
|
+
},
|
|
222
|
+
dispose(): void {
|
|
223
|
+
raw.dispose();
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return { handle, sharedMemory };
|
|
228
|
+
}
|