@celox-sim/celox 0.1.13 → 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.
- package/dist/dut.d.ts +1 -1
- package/dist/dut.d.ts.map +1 -1
- package/dist/dut.js +23 -10
- package/dist/dut.js.map +1 -1
- package/dist/index.d.ts +8 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -10
- package/dist/index.js.map +1 -1
- package/dist/napi-bridge.d.ts +1 -1
- package/dist/napi-bridge.d.ts.map +1 -1
- package/dist/napi-bridge.js +1 -1
- package/dist/napi-bridge.js.map +1 -1
- package/dist/napi-helpers.d.ts +11 -2
- package/dist/napi-helpers.d.ts.map +1 -1
- package/dist/napi-helpers.js +23 -0
- package/dist/napi-helpers.js.map +1 -1
- package/dist/simulation.d.ts.map +1 -1
- package/dist/simulation.js +29 -13
- package/dist/simulation.js.map +1 -1
- package/dist/simulator.d.ts.map +1 -1
- package/dist/simulator.js +28 -12
- package/dist/simulator.js.map +1 -1
- package/dist/types.d.ts +6 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/dut.test.ts +1037 -742
- package/src/dut.ts +580 -528
- package/src/e2e.bench.ts +512 -509
- package/src/e2e.test.ts +1847 -1574
- package/src/index.ts +46 -46
- package/src/matchers.ts +81 -81
- package/src/napi-bridge.ts +5 -5
- package/src/napi-helpers.ts +475 -400
- package/src/simulation.test.ts +395 -368
- package/src/simulation.ts +502 -430
- package/src/simulator.test.ts +201 -168
- package/src/simulator.ts +317 -267
- package/src/types.ts +102 -98
package/src/napi-helpers.ts
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
64
|
-
|
|
63
|
+
from: NapiSignalPath;
|
|
64
|
+
to: NapiSignalPath;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
export interface NapiTrueLoop {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
from: NapiSignalPath;
|
|
69
|
+
to: NapiSignalPath;
|
|
70
|
+
maxIter: number;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export interface NapiSignalPath {
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
instancePath: NapiInstanceSegment[];
|
|
75
|
+
varPath: string[];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
export interface NapiInstanceSegment {
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
name: string;
|
|
80
|
+
index: number;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export interface NapiParamOverride {
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
name: string;
|
|
85
|
+
value: number;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export interface NapiOptions {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
311
|
-
|
|
355
|
+
signals: Record<
|
|
356
|
+
string,
|
|
357
|
+
SignalLayout & { typeKind: string; arrayDims?: number[] }
|
|
358
|
+
>,
|
|
359
|
+
_events: Record<string, number>,
|
|
312
360
|
): Record<string, PortInfo> {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
397
|
-
|
|
447
|
+
json: string,
|
|
448
|
+
events: Record<string, number>,
|
|
398
449
|
): HierarchyNode {
|
|
399
|
-
|
|
400
|
-
|
|
450
|
+
const raw: RawHierarchyNode = JSON.parse(json);
|
|
451
|
+
return convertHierarchyNode(raw, events);
|
|
401
452
|
}
|
|
402
453
|
|
|
403
454
|
function convertHierarchyNode(
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
527
|
+
raw: RawNapiSimulatorHandle,
|
|
455
528
|
): NativeSimulatorHandle {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
552
|
+
raw: RawNapiSimulationHandle,
|
|
480
553
|
): NativeSimulationHandle {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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(
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
}
|