@celox-sim/celox 0.1.12 → 0.1.14

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.
@@ -9,104 +9,121 @@
9
9
  * - Creating bridge functions for Simulator.create() / Simulation.create()
10
10
  */
11
11
 
12
+ import type { NativeCreateSimulationFn } from "./simulation.js";
13
+ import type { NativeCreateFn } from "./simulator.js";
12
14
  import type {
13
- CreateResult,
14
- LoopBreak,
15
- NativeSimulatorHandle,
16
- NativeSimulationHandle,
17
- PortInfo,
18
- SignalLayout,
19
- SimulatorOptions,
20
- TrueLoopSpec,
15
+ CreateResult,
16
+ LoopBreak,
17
+ NativeSimulationHandle,
18
+ NativeSimulatorHandle,
19
+ PortInfo,
20
+ SignalLayout,
21
+ SimulatorOptions,
22
+ TrueLoopSpec,
21
23
  } from "./types.js";
22
- import type { NativeCreateFn } from "./simulator.js";
23
- import type { NativeCreateSimulationFn } from "./simulation.js";
24
24
 
25
25
  // ---------------------------------------------------------------------------
26
26
  // Raw NAPI handle shapes (what the .node addon actually exports)
27
27
  // ---------------------------------------------------------------------------
28
28
 
29
29
  export interface RawNapiSimulatorHandle {
30
- readonly layoutJson: string;
31
- readonly eventsJson: string;
32
- readonly hierarchyJson: string;
33
- readonly stableSize: number;
34
- readonly totalSize: number;
35
- tick(eventId: number): void;
36
- tickN(eventId: number, count: number): void;
37
- evalComb(): void;
38
- dump(timestamp: number): void;
39
- sharedMemory(): Uint8Array;
40
- dispose(): void;
30
+ readonly layoutJson: string;
31
+ readonly eventsJson: string;
32
+ readonly hierarchyJson: string;
33
+ readonly stableSize: number;
34
+ readonly totalSize: number;
35
+ tick(eventId: number): void;
36
+ tickN(eventId: number, count: number): void;
37
+ evalComb(): void;
38
+ dump(timestamp: number): void;
39
+ sharedMemory(): Uint8Array;
40
+ dispose(): void;
41
41
  }
42
42
 
43
43
  export interface RawNapiSimulationHandle {
44
- readonly layoutJson: string;
45
- readonly eventsJson: string;
46
- readonly hierarchyJson: string;
47
- readonly stableSize: number;
48
- readonly totalSize: number;
49
- readonly defaultMaxSteps: number | null;
50
- addClock(eventId: number, period: number, initialDelay: number): void;
51
- schedule(eventId: number, time: number, value: number): void;
52
- runUntil(endTime: number): void;
53
- step(): number | null;
54
- time(): number;
55
- nextEventTime(): number | null;
56
- evalComb(): void;
57
- dump(timestamp: number): void;
58
- sharedMemory(): Uint8Array;
59
- dispose(): void;
44
+ readonly layoutJson: string;
45
+ readonly eventsJson: string;
46
+ readonly hierarchyJson: string;
47
+ readonly stableSize: number;
48
+ readonly totalSize: number;
49
+ readonly defaultMaxSteps: number | null;
50
+ addClock(eventId: number, period: number, initialDelay: number): void;
51
+ schedule(eventId: number, time: number, value: number): void;
52
+ runUntil(endTime: number): void;
53
+ step(): number | null;
54
+ time(): number;
55
+ nextEventTime(): number | null;
56
+ evalComb(): void;
57
+ dump(timestamp: number): void;
58
+ sharedMemory(): Uint8Array;
59
+ dispose(): void;
60
60
  }
61
61
 
62
62
  export interface NapiFalseLoop {
63
- from: NapiSignalPath;
64
- to: NapiSignalPath;
63
+ from: NapiSignalPath;
64
+ to: NapiSignalPath;
65
65
  }
66
66
 
67
67
  export interface NapiTrueLoop {
68
- from: NapiSignalPath;
69
- to: NapiSignalPath;
70
- maxIter: number;
68
+ from: NapiSignalPath;
69
+ to: NapiSignalPath;
70
+ maxIter: number;
71
71
  }
72
72
 
73
73
  export interface NapiSignalPath {
74
- instancePath: NapiInstanceSegment[];
75
- varPath: string[];
74
+ instancePath: NapiInstanceSegment[];
75
+ varPath: string[];
76
76
  }
77
77
 
78
78
  export interface NapiInstanceSegment {
79
- name: string;
80
- index: number;
79
+ name: string;
80
+ index: number;
81
81
  }
82
82
 
83
83
  export interface NapiParamOverride {
84
- name: string;
85
- value: number;
84
+ name: string;
85
+ value: number;
86
86
  }
87
87
 
88
88
  export interface NapiOptions {
89
- fourState?: boolean;
90
- vcd?: string;
91
- optimize?: boolean;
92
- falseLoops?: NapiFalseLoop[];
93
- trueLoops?: NapiTrueLoop[];
94
- clockType?: string;
95
- resetType?: string;
96
- extraSource?: string;
97
- parameters?: NapiParamOverride[];
89
+ fourState?: boolean;
90
+ vcd?: string;
91
+ optimize?: boolean;
92
+ falseLoops?: NapiFalseLoop[];
93
+ trueLoops?: NapiTrueLoop[];
94
+ clockType?: string;
95
+ resetType?: string;
96
+ extraSource?: string;
97
+ parameters?: NapiParamOverride[];
98
+ deadStorePolicy?: string;
98
99
  }
99
100
 
100
101
  export interface RawNapiAddon {
101
- NativeSimulatorHandle: {
102
- new (code: string, top: string, options?: NapiOptions): RawNapiSimulatorHandle;
103
- fromProject(projectPath: string, top: string, options?: NapiOptions): RawNapiSimulatorHandle;
104
- };
105
- NativeSimulationHandle: {
106
- new (code: string, top: string, options?: NapiOptions): RawNapiSimulationHandle;
107
- fromProject(projectPath: string, top: string, options?: NapiOptions): RawNapiSimulationHandle;
108
- };
109
- genTs(projectPath: string): string;
102
+ NativeSimulatorHandle: {
103
+ new (
104
+ code: string,
105
+ top: string,
106
+ options?: NapiOptions,
107
+ ): RawNapiSimulatorHandle;
108
+ fromProject(
109
+ projectPath: string,
110
+ top: string,
111
+ options?: NapiOptions,
112
+ ): RawNapiSimulatorHandle;
113
+ };
114
+ NativeSimulationHandle: {
115
+ new (
116
+ code: string,
117
+ top: string,
118
+ options?: NapiOptions,
119
+ ): RawNapiSimulationHandle;
120
+ fromProject(
121
+ projectPath: string,
122
+ top: string,
123
+ options?: NapiOptions,
124
+ ): RawNapiSimulationHandle;
125
+ };
126
+ genTs(projectPath: string): string;
110
127
  }
111
128
 
112
129
  // ---------------------------------------------------------------------------
@@ -125,21 +142,21 @@ import { createRequire } from "node:module";
125
142
  * @param addonPath Explicit path to the `.node` file (overrides auto-detection).
126
143
  */
127
144
  export function loadNativeAddon(addonPath?: string): RawNapiAddon {
128
- const require = createRequire(import.meta.url);
129
-
130
- if (addonPath) {
131
- return require(addonPath) as RawNapiAddon;
132
- }
133
-
134
- try {
135
- return require("@celox-sim/celox-napi") as RawNapiAddon;
136
- } catch (e) {
137
- throw new Error(
138
- `Failed to load NAPI addon from @celox-sim/celox-napi. ` +
139
- `Build it with: pnpm run build:napi`,
140
- { cause: e },
141
- );
142
- }
145
+ const require = createRequire(import.meta.url);
146
+
147
+ if (addonPath) {
148
+ return require(addonPath) as RawNapiAddon;
149
+ }
150
+
151
+ try {
152
+ return require("@celox-sim/celox-napi") as RawNapiAddon;
153
+ } catch (e) {
154
+ throw new Error(
155
+ `Failed to load NAPI addon from @celox-sim/celox-napi. ` +
156
+ `Build it with: pnpm run build:napi`,
157
+ { cause: e },
158
+ );
159
+ }
143
160
  }
144
161
 
145
162
  // ---------------------------------------------------------------------------
@@ -160,89 +177,99 @@ export function loadNativeAddon(addonPath?: string): RawNapiAddon {
160
177
  * - `"a.b[3]:x.y"` → { instancePath: [{name:"a",index:0},{name:"b",index:3}], varPath: ["x","y"] }
161
178
  */
162
179
  export function parseSignalPath(path: string): NapiSignalPath {
163
- const colonIdx = path.indexOf(":");
164
- if (colonIdx < 0) {
165
- return { instancePath: [], varPath: path.split(".") };
166
- }
167
-
168
- const instPart = path.slice(0, colonIdx);
169
- const varPart = path.slice(colonIdx + 1);
170
-
171
- const instancePath: NapiInstanceSegment[] = [];
172
- if (instPart.length > 0) {
173
- for (const seg of instPart.split(".")) {
174
- const bracketIdx = seg.indexOf("[");
175
- if (bracketIdx >= 0) {
176
- const name = seg.slice(0, bracketIdx);
177
- const index = Number.parseInt(seg.slice(bracketIdx + 1, -1), 10);
178
- instancePath.push({ name, index });
179
- } else {
180
- instancePath.push({ name: seg, index: 0 });
181
- }
182
- }
183
- }
184
-
185
- return { instancePath, varPath: varPart.split(".") };
180
+ const colonIdx = path.indexOf(":");
181
+ if (colonIdx < 0) {
182
+ return { instancePath: [], varPath: path.split(".") };
183
+ }
184
+
185
+ const instPart = path.slice(0, colonIdx);
186
+ const varPart = path.slice(colonIdx + 1);
187
+
188
+ const instancePath: NapiInstanceSegment[] = [];
189
+ if (instPart.length > 0) {
190
+ for (const seg of instPart.split(".")) {
191
+ const bracketIdx = seg.indexOf("[");
192
+ if (bracketIdx >= 0) {
193
+ const name = seg.slice(0, bracketIdx);
194
+ const index = Number.parseInt(seg.slice(bracketIdx + 1, -1), 10);
195
+ instancePath.push({ name, index });
196
+ } else {
197
+ instancePath.push({ name: seg, index: 0 });
198
+ }
199
+ }
200
+ }
201
+
202
+ return { instancePath, varPath: varPart.split(".") };
186
203
  }
187
204
 
188
205
  /**
189
206
  * Build NapiOptions from SimulatorOptions.
190
207
  * Returns `undefined` when no options are set (to skip the NAPI options arg).
191
208
  */
192
- export function buildNapiOpts(options?: SimulatorOptions): NapiOptions | undefined {
193
- if (!options) return undefined;
194
-
195
- const napiOpts: NapiOptions = {};
196
- let hasOpt = false;
197
-
198
- if (options.fourState) {
199
- napiOpts.fourState = options.fourState;
200
- hasOpt = true;
201
- }
202
- if (options.vcd) {
203
- napiOpts.vcd = options.vcd;
204
- hasOpt = true;
205
- }
206
- if (options.optimize != null) {
207
- napiOpts.optimize = options.optimize;
208
- hasOpt = true;
209
- }
210
- if (options.falseLoops && options.falseLoops.length > 0) {
211
- napiOpts.falseLoops = options.falseLoops.map((lb: LoopBreak) => ({
212
- from: parseSignalPath(lb.from),
213
- to: parseSignalPath(lb.to),
214
- }));
215
- hasOpt = true;
216
- }
217
- if (options.trueLoops && options.trueLoops.length > 0) {
218
- napiOpts.trueLoops = options.trueLoops.map((tl: TrueLoopSpec) => ({
219
- from: parseSignalPath(tl.from),
220
- to: parseSignalPath(tl.to),
221
- maxIter: tl.maxIter,
222
- }));
223
- hasOpt = true;
224
- }
225
- if (options.clockType) {
226
- napiOpts.clockType = options.clockType;
227
- hasOpt = true;
228
- }
229
- if (options.resetType) {
230
- napiOpts.resetType = options.resetType;
231
- hasOpt = true;
232
- }
233
- if (options.extraSource) {
234
- napiOpts.extraSource = options.extraSource;
235
- hasOpt = true;
236
- }
237
- if (options.parameters && options.parameters.length > 0) {
238
- napiOpts.parameters = options.parameters.map((p) => ({
239
- name: p.name,
240
- value: typeof p.value === "bigint" ? Number(p.value) : p.value,
241
- }));
242
- hasOpt = true;
243
- }
244
-
245
- return hasOpt ? napiOpts : undefined;
209
+ export function buildNapiOpts(
210
+ options?: SimulatorOptions,
211
+ ): NapiOptions | undefined {
212
+ if (!options) return undefined;
213
+
214
+ const napiOpts: NapiOptions = {};
215
+ let hasOpt = false;
216
+
217
+ if (options.fourState) {
218
+ napiOpts.fourState = options.fourState;
219
+ hasOpt = true;
220
+ }
221
+ if (options.vcd) {
222
+ napiOpts.vcd = options.vcd;
223
+ hasOpt = true;
224
+ }
225
+ if (options.optimize != null) {
226
+ napiOpts.optimize = options.optimize;
227
+ hasOpt = true;
228
+ }
229
+ if (options.falseLoops && options.falseLoops.length > 0) {
230
+ napiOpts.falseLoops = options.falseLoops.map((lb: LoopBreak) => ({
231
+ from: parseSignalPath(lb.from),
232
+ to: parseSignalPath(lb.to),
233
+ }));
234
+ hasOpt = true;
235
+ }
236
+ if (options.trueLoops && options.trueLoops.length > 0) {
237
+ napiOpts.trueLoops = options.trueLoops.map((tl: TrueLoopSpec) => ({
238
+ from: parseSignalPath(tl.from),
239
+ to: parseSignalPath(tl.to),
240
+ maxIter: tl.maxIter,
241
+ }));
242
+ hasOpt = true;
243
+ }
244
+ if (options.clockType) {
245
+ napiOpts.clockType = options.clockType;
246
+ hasOpt = true;
247
+ }
248
+ if (options.resetType) {
249
+ napiOpts.resetType = options.resetType;
250
+ hasOpt = true;
251
+ }
252
+ if (options.extraSource) {
253
+ napiOpts.extraSource = options.extraSource;
254
+ hasOpt = true;
255
+ }
256
+ if (options.parameters && options.parameters.length > 0) {
257
+ napiOpts.parameters = options.parameters.map((p) => ({
258
+ name: p.name,
259
+ value: typeof p.value === "bigint" ? Number(p.value) : p.value,
260
+ }));
261
+ hasOpt = true;
262
+ }
263
+ if (options.deadStorePolicy && options.deadStorePolicy !== "off") {
264
+ const map: Record<string, string> = {
265
+ preserveTopPorts: "preserve_top_ports",
266
+ preserveAllPorts: "preserve_all_ports",
267
+ };
268
+ napiOpts.deadStorePolicy = map[options.deadStorePolicy] ?? options.deadStorePolicy;
269
+ hasOpt = true;
270
+ }
271
+
272
+ return hasOpt ? napiOpts : undefined;
246
273
  }
247
274
 
248
275
  // ---------------------------------------------------------------------------
@@ -250,14 +277,14 @@ export function buildNapiOpts(options?: SimulatorOptions): NapiOptions | undefin
250
277
  // ---------------------------------------------------------------------------
251
278
 
252
279
  interface RawSignalLayout {
253
- offset: number;
254
- width: number;
255
- byte_size: number;
256
- is_4state: boolean;
257
- direction: string;
258
- type_kind: string;
259
- array_dims?: number[];
260
- associated_clock?: string;
280
+ offset: number;
281
+ width: number;
282
+ byte_size: number;
283
+ is_4state: boolean;
284
+ direction: string;
285
+ type_kind: string;
286
+ array_dims?: number[];
287
+ associated_clock?: string;
261
288
  }
262
289
 
263
290
  /**
@@ -266,36 +293,54 @@ interface RawSignalLayout {
266
293
  * the DUT-compatible layout (without type_kind).
267
294
  */
268
295
  export function parseNapiLayout(json: string): {
269
- signals: Record<string, SignalLayout & { typeKind: string; arrayDims?: number[]; associatedClock?: string }>;
270
- forDut: Record<string, SignalLayout>;
296
+ signals: Record<
297
+ string,
298
+ SignalLayout & {
299
+ typeKind: string;
300
+ arrayDims?: number[];
301
+ associatedClock?: string;
302
+ }
303
+ >;
304
+ forDut: Record<string, SignalLayout>;
271
305
  } {
272
- const raw: Record<string, RawSignalLayout> = JSON.parse(json);
273
- const signals: Record<string, SignalLayout & { typeKind: string; arrayDims?: number[]; associatedClock?: string }> = {};
274
- const forDut: Record<string, SignalLayout> = {};
275
-
276
- for (const [name, r] of Object.entries(raw)) {
277
- const sl: SignalLayout = {
278
- offset: r.offset,
279
- width: r.width,
280
- byteSize: r.byte_size > 0 ? r.byte_size : Math.ceil(r.width / 8),
281
- is4state: r.is_4state,
282
- direction: r.direction as "input" | "output" | "inout",
283
- };
284
- const entry: SignalLayout & { typeKind: string; arrayDims?: number[]; associatedClock?: string } = {
285
- ...sl,
286
- typeKind: r.type_kind,
287
- };
288
- if (r.array_dims && r.array_dims.length > 0) {
289
- entry.arrayDims = r.array_dims;
290
- }
291
- if (r.associated_clock) {
292
- entry.associatedClock = r.associated_clock;
293
- }
294
- signals[name] = entry;
295
- forDut[name] = sl;
296
- }
297
-
298
- return { signals, forDut };
306
+ const raw: Record<string, RawSignalLayout> = JSON.parse(json);
307
+ const signals: Record<
308
+ string,
309
+ SignalLayout & {
310
+ typeKind: string;
311
+ arrayDims?: number[];
312
+ associatedClock?: string;
313
+ }
314
+ > = {};
315
+ const forDut: Record<string, SignalLayout> = {};
316
+
317
+ for (const [name, r] of Object.entries(raw)) {
318
+ const sl: SignalLayout = {
319
+ offset: r.offset,
320
+ width: r.width,
321
+ byteSize: r.byte_size > 0 ? r.byte_size : Math.ceil(r.width / 8),
322
+ is4state: r.is_4state,
323
+ direction: r.direction as SignalLayout["direction"],
324
+ };
325
+ const entry: SignalLayout & {
326
+ typeKind: string;
327
+ arrayDims?: number[];
328
+ associatedClock?: string;
329
+ } = {
330
+ ...sl,
331
+ typeKind: r.type_kind,
332
+ };
333
+ if (r.array_dims && r.array_dims.length > 0) {
334
+ entry.arrayDims = r.array_dims;
335
+ }
336
+ if (r.associated_clock) {
337
+ entry.associatedClock = r.associated_clock;
338
+ }
339
+ signals[name] = entry;
340
+ forDut[name] = sl;
341
+ }
342
+
343
+ return { signals, forDut };
299
344
  }
300
345
 
301
346
  /**
@@ -307,67 +352,70 @@ export function parseNapiLayout(json: string): {
307
352
  * expose them as `dut.bus.data` and `dut.bus.valid`.
308
353
  */
309
354
  export function buildPortsFromLayout(
310
- signals: Record<string, SignalLayout & { typeKind: string; arrayDims?: number[] }>,
311
- _events: Record<string, number>,
355
+ signals: Record<
356
+ string,
357
+ SignalLayout & { typeKind: string; arrayDims?: number[] }
358
+ >,
359
+ _events: Record<string, number>,
312
360
  ): Record<string, PortInfo> {
313
- // Step 1: Build flat PortInfo for every signal name
314
- const flat: Record<string, PortInfo> = {};
315
-
316
- for (const [name, sig] of Object.entries(signals)) {
317
- const typeKind = sig.typeKind;
318
- let portType: "clock" | "reset" | "logic" | "bit";
319
- if (typeKind === "clock") {
320
- portType = "clock";
321
- } else if (typeKind.startsWith("reset")) {
322
- portType = "reset";
323
- } else if (typeKind === "bit") {
324
- portType = "bit";
325
- } else {
326
- portType = "logic";
327
- }
328
-
329
- const port: PortInfo = {
330
- direction: sig.direction,
331
- type: portType,
332
- width: sig.width,
333
- is4state: sig.is4state,
334
- };
335
- if (sig.arrayDims && sig.arrayDims.length > 0) {
336
- (port as { arrayDims: readonly number[] }).arrayDims = sig.arrayDims;
337
- }
338
- flat[name] = port;
339
- }
340
-
341
- // Step 2: Group hierarchical signals (e.g. "bus.data") into nested PortInfo.
342
- // Veryl interface ports are exactly one level deep, so "bus.data" → parent "bus",
343
- // member "data". The parent entry receives an `interface` map of its members;
344
- // the flat layout keys are kept as-is so createNestedDut can look them up.
345
- const ports: Record<string, PortInfo> = {};
346
- const ifMaps = new Map<string, Record<string, PortInfo>>();
347
-
348
- for (const [name, port] of Object.entries(flat)) {
349
- const dotIdx = name.indexOf(".");
350
- if (dotIdx < 0) {
351
- ports[name] = port;
352
- continue;
353
- }
354
- // Hierarchical signal: first segment is the interface port name
355
- const parentName = name.slice(0, dotIdx);
356
- const memberName = name.slice(dotIdx + 1);
357
- if (!ifMaps.has(parentName)) {
358
- const ifMap: Record<string, PortInfo> = {};
359
- ifMaps.set(parentName, ifMap);
360
- ports[parentName] = {
361
- direction: "inout",
362
- type: "logic",
363
- width: 0,
364
- interface: ifMap,
365
- };
366
- }
367
- ifMaps.get(parentName)![memberName] = port;
368
- }
369
-
370
- return ports;
361
+ // Step 1: Build flat PortInfo for every signal name
362
+ const flat: Record<string, PortInfo> = {};
363
+
364
+ for (const [name, sig] of Object.entries(signals)) {
365
+ const typeKind = sig.typeKind;
366
+ let portType: "clock" | "reset" | "logic" | "bit";
367
+ if (typeKind === "clock") {
368
+ portType = "clock";
369
+ } else if (typeKind.startsWith("reset")) {
370
+ portType = "reset";
371
+ } else if (typeKind === "bit") {
372
+ portType = "bit";
373
+ } else {
374
+ portType = "logic";
375
+ }
376
+
377
+ const port: PortInfo = {
378
+ direction: sig.direction,
379
+ type: portType,
380
+ width: sig.width,
381
+ is4state: sig.is4state,
382
+ };
383
+ if (sig.arrayDims && sig.arrayDims.length > 0) {
384
+ (port as { arrayDims: readonly number[] }).arrayDims = sig.arrayDims;
385
+ }
386
+ flat[name] = port;
387
+ }
388
+
389
+ // Step 2: Group hierarchical signals (e.g. "bus.data") into nested PortInfo.
390
+ // Veryl interface ports are exactly one level deep, so "bus.data" → parent "bus",
391
+ // member "data". The parent entry receives an `interface` map of its members;
392
+ // the flat layout keys are kept as-is so createNestedDut can look them up.
393
+ const ports: Record<string, PortInfo> = {};
394
+ const ifMaps = new Map<string, Record<string, PortInfo>>();
395
+
396
+ for (const [name, port] of Object.entries(flat)) {
397
+ const dotIdx = name.indexOf(".");
398
+ if (dotIdx < 0) {
399
+ ports[name] = port;
400
+ continue;
401
+ }
402
+ // Hierarchical signal: first segment is the interface port name
403
+ const parentName = name.slice(0, dotIdx);
404
+ const memberName = name.slice(dotIdx + 1);
405
+ if (!ifMaps.has(parentName)) {
406
+ const ifMap: Record<string, PortInfo> = {};
407
+ ifMaps.set(parentName, ifMap);
408
+ ports[parentName] = {
409
+ direction: "inout",
410
+ type: "logic",
411
+ width: 0,
412
+ interface: ifMap,
413
+ };
414
+ }
415
+ ifMaps.get(parentName)![memberName] = port;
416
+ }
417
+
418
+ return ports;
371
419
  }
372
420
 
373
421
  // ---------------------------------------------------------------------------
@@ -375,17 +423,20 @@ export function buildPortsFromLayout(
375
423
  // ---------------------------------------------------------------------------
376
424
 
377
425
  export interface HierarchyNode {
378
- moduleName: string;
379
- signals: Record<string, SignalLayout & { typeKind: string; arrayDims?: number[] }>;
380
- forDut: Record<string, SignalLayout>;
381
- ports: Record<string, PortInfo>;
382
- children: Record<string, HierarchyNode[]>;
426
+ moduleName: string;
427
+ signals: Record<
428
+ string,
429
+ SignalLayout & { typeKind: string; arrayDims?: number[] }
430
+ >;
431
+ forDut: Record<string, SignalLayout>;
432
+ ports: Record<string, PortInfo>;
433
+ children: Record<string, HierarchyNode[]>;
383
434
  }
384
435
 
385
436
  interface RawHierarchyNode {
386
- module_name: string;
387
- signals: Record<string, RawSignalLayout>;
388
- children: Record<string, RawHierarchyNode[]>;
437
+ module_name: string;
438
+ signals: Record<string, RawSignalLayout>;
439
+ children: Record<string, RawHierarchyNode[]>;
389
440
  }
390
441
 
391
442
  /**
@@ -393,53 +444,75 @@ interface RawHierarchyNode {
393
444
  * Converts snake_case keys to camelCase and auto-detects ports.
394
445
  */
395
446
  export function parseHierarchyLayout(
396
- json: string,
397
- events: Record<string, number>,
447
+ json: string,
448
+ events: Record<string, number>,
398
449
  ): HierarchyNode {
399
- const raw: RawHierarchyNode = JSON.parse(json);
400
- return convertHierarchyNode(raw, events);
450
+ const raw: RawHierarchyNode = JSON.parse(json);
451
+ return convertHierarchyNode(raw, events);
401
452
  }
402
453
 
403
454
  function convertHierarchyNode(
404
- raw: RawHierarchyNode,
405
- events: Record<string, number>,
455
+ raw: RawHierarchyNode,
456
+ events: Record<string, number>,
457
+ ): HierarchyNode {
458
+ const signals: Record<
459
+ string,
460
+ SignalLayout & { typeKind: string; arrayDims?: number[] }
461
+ > = {};
462
+ const forDut: Record<string, SignalLayout> = {};
463
+
464
+ for (const [name, r] of Object.entries(raw.signals)) {
465
+ const sl: SignalLayout = {
466
+ offset: r.offset,
467
+ width: r.width,
468
+ byteSize: r.byte_size > 0 ? r.byte_size : Math.ceil(r.width / 8),
469
+ is4state: r.is_4state,
470
+ direction: r.direction as SignalLayout["direction"],
471
+ };
472
+ const entry: SignalLayout & { typeKind: string; arrayDims?: number[] } = {
473
+ ...sl,
474
+ typeKind: r.type_kind,
475
+ };
476
+ if (r.array_dims && r.array_dims.length > 0) {
477
+ entry.arrayDims = r.array_dims;
478
+ }
479
+ signals[name] = entry;
480
+ forDut[name] = sl;
481
+ }
482
+
483
+ const ports = buildPortsFromLayout(signals, events);
484
+
485
+ const children: Record<string, HierarchyNode[]> = {};
486
+ for (const [name, instances] of Object.entries(raw.children)) {
487
+ children[name] = instances.map((inst) =>
488
+ convertHierarchyNode(inst, events),
489
+ );
490
+ }
491
+
492
+ return {
493
+ moduleName: raw.module_name,
494
+ signals,
495
+ forDut,
496
+ ports,
497
+ children,
498
+ };
499
+ }
500
+
501
+ /**
502
+ * Filter a hierarchy tree based on the dead store elimination policy.
503
+ *
504
+ * - `"off"` / undefined → full hierarchy (no filtering)
505
+ * - `"preserveTopPorts"` → strip children (only top-module ports survive DSE)
506
+ * - `"preserveAllPorts"` → hierarchy intact (all instance ports survive DSE)
507
+ */
508
+ export function filterHierarchyForDse(
509
+ hierarchy: HierarchyNode,
510
+ policy?: "off" | "preserveTopPorts" | "preserveAllPorts",
406
511
  ): HierarchyNode {
407
- const signals: Record<string, SignalLayout & { typeKind: string; arrayDims?: number[] }> = {};
408
- const forDut: Record<string, SignalLayout> = {};
409
-
410
- for (const [name, r] of Object.entries(raw.signals)) {
411
- const sl: SignalLayout = {
412
- offset: r.offset,
413
- width: r.width,
414
- byteSize: r.byte_size > 0 ? r.byte_size : Math.ceil(r.width / 8),
415
- is4state: r.is_4state,
416
- direction: r.direction as "input" | "output" | "inout",
417
- };
418
- const entry: SignalLayout & { typeKind: string; arrayDims?: number[] } = {
419
- ...sl,
420
- typeKind: r.type_kind,
421
- };
422
- if (r.array_dims && r.array_dims.length > 0) {
423
- entry.arrayDims = r.array_dims;
424
- }
425
- signals[name] = entry;
426
- forDut[name] = sl;
427
- }
428
-
429
- const ports = buildPortsFromLayout(signals, events);
430
-
431
- const children: Record<string, HierarchyNode[]> = {};
432
- for (const [name, instances] of Object.entries(raw.children)) {
433
- children[name] = instances.map((inst) => convertHierarchyNode(inst, events));
434
- }
435
-
436
- return {
437
- moduleName: raw.module_name,
438
- signals,
439
- forDut,
440
- ports,
441
- children,
442
- };
512
+ if (!policy || policy === "off") return hierarchy;
513
+ if (policy === "preserveTopPorts") return { ...hierarchy, children: {} };
514
+ // "preserveAllPorts" — hierarchy intact, Rust-side ensures only ports survive
515
+ return hierarchy;
443
516
  }
444
517
 
445
518
  // ---------------------------------------------------------------------------
@@ -451,62 +524,62 @@ function convertHierarchyNode(
451
524
  * The buffer is shared between JS and Rust — no copies per tick.
452
525
  */
453
526
  export function wrapDirectSimulatorHandle(
454
- raw: RawNapiSimulatorHandle,
527
+ raw: RawNapiSimulatorHandle,
455
528
  ): NativeSimulatorHandle {
456
- return {
457
- tick(eventId: number): void {
458
- raw.tick(eventId);
459
- },
460
- tickN(eventId: number, count: number): void {
461
- raw.tickN(eventId, count);
462
- },
463
- evalComb(): void {
464
- raw.evalComb();
465
- },
466
- dump(timestamp: number): void {
467
- raw.dump(timestamp);
468
- },
469
- dispose(): void {
470
- raw.dispose();
471
- },
472
- };
529
+ return {
530
+ tick(eventId: number): void {
531
+ raw.tick(eventId);
532
+ },
533
+ tickN(eventId: number, count: number): void {
534
+ raw.tickN(eventId, count);
535
+ },
536
+ evalComb(): void {
537
+ raw.evalComb();
538
+ },
539
+ dump(timestamp: number): void {
540
+ raw.dump(timestamp);
541
+ },
542
+ dispose(): void {
543
+ raw.dispose();
544
+ },
545
+ };
473
546
  }
474
547
 
475
548
  /**
476
549
  * Wrap a raw NAPI simulation handle with direct (zero-copy) operations.
477
550
  */
478
551
  export function wrapDirectSimulationHandle(
479
- raw: RawNapiSimulationHandle,
552
+ raw: RawNapiSimulationHandle,
480
553
  ): NativeSimulationHandle {
481
- return {
482
- addClock(eventId: number, period: number, initialDelay: number): void {
483
- raw.addClock(eventId, period, initialDelay);
484
- },
485
- schedule(eventId: number, time: number, value: number): void {
486
- raw.schedule(eventId, time, value);
487
- },
488
- runUntil(endTime: number): void {
489
- raw.runUntil(endTime);
490
- },
491
- step(): number | null {
492
- return raw.step();
493
- },
494
- time(): number {
495
- return raw.time();
496
- },
497
- nextEventTime(): number | null {
498
- return raw.nextEventTime();
499
- },
500
- evalComb(): void {
501
- raw.evalComb();
502
- },
503
- dump(timestamp: number): void {
504
- raw.dump(timestamp);
505
- },
506
- dispose(): void {
507
- raw.dispose();
508
- },
509
- };
554
+ return {
555
+ addClock(eventId: number, period: number, initialDelay: number): void {
556
+ raw.addClock(eventId, period, initialDelay);
557
+ },
558
+ schedule(eventId: number, time: number, value: number): void {
559
+ raw.schedule(eventId, time, value);
560
+ },
561
+ runUntil(endTime: number): void {
562
+ raw.runUntil(endTime);
563
+ },
564
+ step(): number | null {
565
+ return raw.step();
566
+ },
567
+ time(): number {
568
+ return raw.time();
569
+ },
570
+ nextEventTime(): number | null {
571
+ return raw.nextEventTime();
572
+ },
573
+ evalComb(): void {
574
+ raw.evalComb();
575
+ },
576
+ dump(timestamp: number): void {
577
+ raw.dump(timestamp);
578
+ },
579
+ dispose(): void {
580
+ raw.dispose();
581
+ },
582
+ };
510
583
  }
511
584
 
512
585
  // ---------------------------------------------------------------------------
@@ -514,18 +587,18 @@ export function wrapDirectSimulationHandle(
514
587
  // ---------------------------------------------------------------------------
515
588
 
516
589
  function parseLegacyLayout(json: string): Record<string, SignalLayout> {
517
- const raw: Record<string, RawSignalLayout> = JSON.parse(json);
518
- const result: Record<string, SignalLayout> = {};
519
- for (const [name, r] of Object.entries(raw)) {
520
- result[name] = {
521
- offset: r.offset,
522
- width: r.width,
523
- byteSize: r.byte_size > 0 ? r.byte_size : Math.ceil(r.width / 8),
524
- is4state: r.is_4state,
525
- direction: r.direction as "input" | "output" | "inout",
526
- };
527
- }
528
- return result;
590
+ const raw: Record<string, RawSignalLayout> = JSON.parse(json);
591
+ const result: Record<string, SignalLayout> = {};
592
+ for (const [name, r] of Object.entries(raw)) {
593
+ result[name] = {
594
+ offset: r.offset,
595
+ width: r.width,
596
+ byteSize: r.byte_size > 0 ? r.byte_size : Math.ceil(r.width / 8),
597
+ is4state: r.is_4state,
598
+ direction: r.direction as SignalLayout["direction"],
599
+ };
600
+ }
601
+ return result;
529
602
  }
530
603
 
531
604
  // ---------------------------------------------------------------------------
@@ -537,45 +610,47 @@ function parseLegacyLayout(json: string): Record<string, SignalLayout> {
537
610
  * `Simulator.create(module, { __nativeCreate: ... })`.
538
611
  */
539
612
  export function createSimulatorBridge(addon: RawNapiAddon): NativeCreateFn {
540
- return (
541
- source: string,
542
- moduleName: string,
543
- options: SimulatorOptions,
544
- ): CreateResult<NativeSimulatorHandle> => {
545
- const napiOpts = buildNapiOpts(options);
546
- const raw = new addon.NativeSimulatorHandle(source, moduleName, napiOpts);
547
-
548
- const layout = parseLegacyLayout(raw.layoutJson);
549
- const events: Record<string, number> = JSON.parse(raw.eventsJson);
550
- const hierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
551
-
552
- const buf = raw.sharedMemory().buffer;
553
- const handle = wrapDirectSimulatorHandle(raw);
554
-
555
- return { buffer: buf, layout, events, handle, hierarchy };
556
- };
613
+ return (
614
+ source: string,
615
+ moduleName: string,
616
+ options: SimulatorOptions,
617
+ ): CreateResult<NativeSimulatorHandle> => {
618
+ const napiOpts = buildNapiOpts(options);
619
+ const raw = new addon.NativeSimulatorHandle(source, moduleName, napiOpts);
620
+
621
+ const layout = parseLegacyLayout(raw.layoutJson);
622
+ const events: Record<string, number> = JSON.parse(raw.eventsJson);
623
+ const hierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
624
+
625
+ const buf = raw.sharedMemory().buffer;
626
+ const handle = wrapDirectSimulatorHandle(raw);
627
+
628
+ return { buffer: buf, layout, events, handle, hierarchy };
629
+ };
557
630
  }
558
631
 
559
632
  /**
560
633
  * Create a `NativeCreateSimulationFn` from a raw NAPI addon, suitable for
561
634
  * `Simulation.create(module, { __nativeCreate: ... })`.
562
635
  */
563
- export function createSimulationBridge(addon: RawNapiAddon): NativeCreateSimulationFn {
564
- return (
565
- source: string,
566
- moduleName: string,
567
- options: SimulatorOptions,
568
- ): CreateResult<NativeSimulationHandle> => {
569
- const napiOpts = buildNapiOpts(options);
570
- const raw = new addon.NativeSimulationHandle(source, moduleName, napiOpts);
571
-
572
- const layout = parseLegacyLayout(raw.layoutJson);
573
- const events: Record<string, number> = JSON.parse(raw.eventsJson);
574
- const hierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
575
-
576
- const buf = raw.sharedMemory().buffer;
577
- const handle = wrapDirectSimulationHandle(raw);
578
-
579
- return { buffer: buf, layout, events, handle, hierarchy };
580
- };
636
+ export function createSimulationBridge(
637
+ addon: RawNapiAddon,
638
+ ): NativeCreateSimulationFn {
639
+ return (
640
+ source: string,
641
+ moduleName: string,
642
+ options: SimulatorOptions,
643
+ ): CreateResult<NativeSimulationHandle> => {
644
+ const napiOpts = buildNapiOpts(options);
645
+ const raw = new addon.NativeSimulationHandle(source, moduleName, napiOpts);
646
+
647
+ const layout = parseLegacyLayout(raw.layoutJson);
648
+ const events: Record<string, number> = JSON.parse(raw.eventsJson);
649
+ const hierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
650
+
651
+ const buf = raw.sharedMemory().buffer;
652
+ const handle = wrapDirectSimulationHandle(raw);
653
+
654
+ return { buffer: buf, layout, events, handle, hierarchy };
655
+ };
581
656
  }