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