@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.
- package/dist/dut.d.ts +1 -1
- package/dist/dut.d.ts.map +1 -1
- package/dist/dut.js +32 -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 +1040 -661
- package/src/dut.ts +580 -520
- package/src/e2e.bench.ts +627 -310
- package/src/e2e.test.ts +1872 -1550
- 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/simulation.ts
CHANGED
|
@@ -5,24 +5,25 @@
|
|
|
5
5
|
* for clock-driven simulation with automatic scheduling.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { createDut, type DirtyState, readFourState } from "./dut.js";
|
|
9
|
+
import {
|
|
10
|
+
buildNapiOpts,
|
|
11
|
+
buildPortsFromLayout,
|
|
12
|
+
filterHierarchyForDse,
|
|
13
|
+
loadNativeAddon,
|
|
14
|
+
parseHierarchyLayout,
|
|
15
|
+
parseNapiLayout,
|
|
16
|
+
wrapDirectSimulationHandle,
|
|
17
|
+
} from "./napi-helpers.js";
|
|
8
18
|
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
CreateResult,
|
|
20
|
+
FourStateValue,
|
|
21
|
+
ModuleDefinition,
|
|
22
|
+
NativeSimulationHandle,
|
|
23
|
+
SignalLayout,
|
|
24
|
+
SimulatorOptions,
|
|
15
25
|
} from "./types.js";
|
|
16
26
|
import { SimulationTimeoutError } from "./types.js";
|
|
17
|
-
import { createDut, readFourState, type DirtyState } from "./dut.js";
|
|
18
|
-
import {
|
|
19
|
-
loadNativeAddon,
|
|
20
|
-
parseNapiLayout,
|
|
21
|
-
parseHierarchyLayout,
|
|
22
|
-
buildPortsFromLayout,
|
|
23
|
-
wrapDirectSimulationHandle,
|
|
24
|
-
buildNapiOpts,
|
|
25
|
-
} from "./napi-helpers.js";
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Placeholder for the NAPI binding's `createSimulation()`.
|
|
@@ -30,9 +31,9 @@ import {
|
|
|
30
31
|
* @internal
|
|
31
32
|
*/
|
|
32
33
|
export type NativeCreateSimulationFn = (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
source: string,
|
|
35
|
+
moduleName: string,
|
|
36
|
+
options: SimulatorOptions,
|
|
36
37
|
) => CreateResult<NativeSimulationHandle>;
|
|
37
38
|
|
|
38
39
|
let _nativeCreate: NativeCreateSimulationFn | undefined;
|
|
@@ -42,7 +43,7 @@ let _nativeCreate: NativeCreateSimulationFn | undefined;
|
|
|
42
43
|
* @internal
|
|
43
44
|
*/
|
|
44
45
|
export function setNativeSimulationCreate(fn: NativeCreateSimulationFn): void {
|
|
45
|
-
|
|
46
|
+
_nativeCreate = fn;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// ---------------------------------------------------------------------------
|
|
@@ -50,415 +51,486 @@ export function setNativeSimulationCreate(fn: NativeCreateSimulationFn): void {
|
|
|
50
51
|
// ---------------------------------------------------------------------------
|
|
51
52
|
|
|
52
53
|
export class Simulation<P = Record<string, unknown>> {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
54
|
+
private readonly _handle: NativeSimulationHandle;
|
|
55
|
+
private readonly _dut: P;
|
|
56
|
+
private readonly _events: Record<string, number>;
|
|
57
|
+
private readonly _state: DirtyState;
|
|
58
|
+
private readonly _buffer: ArrayBuffer | SharedArrayBuffer;
|
|
59
|
+
private readonly _layout: Record<
|
|
60
|
+
string,
|
|
61
|
+
SignalLayout & { typeKind?: string; associatedClock?: string }
|
|
62
|
+
>;
|
|
63
|
+
private readonly _clocks = new Map<
|
|
64
|
+
string,
|
|
65
|
+
{ period: number; eventId: number }
|
|
66
|
+
>();
|
|
67
|
+
private readonly _defaultMaxSteps: number | undefined;
|
|
68
|
+
private _disposed = false;
|
|
69
|
+
|
|
70
|
+
private constructor(
|
|
71
|
+
handle: NativeSimulationHandle,
|
|
72
|
+
dut: P,
|
|
73
|
+
events: Record<string, number>,
|
|
74
|
+
state: DirtyState,
|
|
75
|
+
buffer: ArrayBuffer | SharedArrayBuffer,
|
|
76
|
+
layout: Record<
|
|
77
|
+
string,
|
|
78
|
+
SignalLayout & { typeKind?: string; associatedClock?: string }
|
|
79
|
+
>,
|
|
80
|
+
defaultMaxSteps?: number,
|
|
81
|
+
) {
|
|
82
|
+
this._handle = handle;
|
|
83
|
+
this._dut = dut;
|
|
84
|
+
this._events = events;
|
|
85
|
+
this._state = state;
|
|
86
|
+
this._buffer = buffer;
|
|
87
|
+
this._layout = layout;
|
|
88
|
+
this._defaultMaxSteps = defaultMaxSteps;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a Simulation for the given module.
|
|
93
|
+
*
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { Top } from "./generated/Top.js";
|
|
96
|
+
* const sim = Simulation.create(Top);
|
|
97
|
+
* sim.addClock("clk", { period: 10 });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
static create<P>(
|
|
101
|
+
module: ModuleDefinition<P>,
|
|
102
|
+
options?: SimulatorOptions & {
|
|
103
|
+
__nativeCreate?: NativeCreateSimulationFn;
|
|
104
|
+
},
|
|
105
|
+
): Simulation<P> {
|
|
106
|
+
const merged = { ...module.defaultOptions, ...options };
|
|
107
|
+
|
|
108
|
+
// When the module was produced by the Vite plugin, delegate to fromProject()
|
|
109
|
+
if (module.projectPath && !merged?.__nativeCreate) {
|
|
110
|
+
return Simulation.fromProject<P>(
|
|
111
|
+
module.projectPath,
|
|
112
|
+
module.name,
|
|
113
|
+
merged,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const createFn = merged?.__nativeCreate ?? _nativeCreate;
|
|
118
|
+
if (!createFn) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
"Native simulator binding not loaded. " +
|
|
121
|
+
"Ensure @celox-sim/celox-napi is installed.",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const {
|
|
126
|
+
fourState,
|
|
127
|
+
vcd,
|
|
128
|
+
optimize,
|
|
129
|
+
falseLoops,
|
|
130
|
+
trueLoops,
|
|
131
|
+
clockType,
|
|
132
|
+
resetType,
|
|
133
|
+
parameters,
|
|
134
|
+
deadStorePolicy,
|
|
135
|
+
} = merged ?? {};
|
|
136
|
+
const result = createFn(module.source, module.name, {
|
|
137
|
+
fourState,
|
|
138
|
+
vcd,
|
|
139
|
+
optimize,
|
|
140
|
+
falseLoops,
|
|
141
|
+
trueLoops,
|
|
142
|
+
clockType,
|
|
143
|
+
resetType,
|
|
144
|
+
parameters,
|
|
145
|
+
deadStorePolicy,
|
|
146
|
+
});
|
|
147
|
+
const state: DirtyState = { dirty: false };
|
|
148
|
+
|
|
149
|
+
// Always prefer NAPI-derived ports over module.ports (see simulator.ts).
|
|
150
|
+
const hierarchy = result.hierarchy
|
|
151
|
+
? filterHierarchyForDse(result.hierarchy, deadStorePolicy)
|
|
152
|
+
: undefined;
|
|
153
|
+
const portDefs = hierarchy?.ports ?? module.ports;
|
|
154
|
+
const dut = createDut<P>(
|
|
155
|
+
result.buffer,
|
|
156
|
+
result.layout,
|
|
157
|
+
portDefs,
|
|
158
|
+
result.handle,
|
|
159
|
+
state,
|
|
160
|
+
hierarchy,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return new Simulation<P>(
|
|
164
|
+
result.handle,
|
|
165
|
+
dut,
|
|
166
|
+
result.events,
|
|
167
|
+
state,
|
|
168
|
+
result.buffer,
|
|
169
|
+
result.layout,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Create a Simulation directly from Veryl source code.
|
|
175
|
+
*
|
|
176
|
+
* Automatically discovers ports from the NAPI layout — no
|
|
177
|
+
* `ModuleDefinition` needed.
|
|
178
|
+
*
|
|
179
|
+
* ```ts
|
|
180
|
+
* const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
181
|
+
* sim.addClock("clk", { period: 10 });
|
|
182
|
+
* sim.runUntil(100);
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
static fromSource<P = Record<string, unknown>>(
|
|
186
|
+
source: string,
|
|
187
|
+
top: string,
|
|
188
|
+
options?: SimulatorOptions & { nativeAddonPath?: string },
|
|
189
|
+
): Simulation<P> {
|
|
190
|
+
const addon = loadNativeAddon(options?.nativeAddonPath);
|
|
191
|
+
const napiOpts = buildNapiOpts(options);
|
|
192
|
+
const raw = new addon.NativeSimulationHandle(source, top, napiOpts);
|
|
193
|
+
|
|
194
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
195
|
+
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
196
|
+
const rawHierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
|
|
197
|
+
const hierarchy = filterHierarchyForDse(rawHierarchy, options?.deadStorePolicy);
|
|
198
|
+
|
|
199
|
+
const ports = buildPortsFromLayout(hierarchy.signals, events);
|
|
200
|
+
|
|
201
|
+
const buf = raw.sharedMemory().buffer;
|
|
202
|
+
|
|
203
|
+
const state: DirtyState = { dirty: false };
|
|
204
|
+
const handle = wrapDirectSimulationHandle(raw);
|
|
205
|
+
const dut = createDut<P>(
|
|
206
|
+
buf,
|
|
207
|
+
layout.forDut,
|
|
208
|
+
ports,
|
|
209
|
+
handle,
|
|
210
|
+
state,
|
|
211
|
+
hierarchy,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
return new Simulation<P>(handle, dut, events, state, buf, layout.signals);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Create a Simulation from a Veryl project directory.
|
|
219
|
+
*
|
|
220
|
+
* Searches upward from `projectPath` for `Veryl.toml`, gathers all
|
|
221
|
+
* `.veryl` source files, and builds the simulation using the project's
|
|
222
|
+
* clock/reset settings.
|
|
223
|
+
*
|
|
224
|
+
* ```ts
|
|
225
|
+
* const sim = Simulation.fromProject<MyPorts>("./my-project", "Top");
|
|
226
|
+
* sim.addClock("clk", { period: 10 });
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
static fromProject<P = Record<string, unknown>>(
|
|
230
|
+
projectPath: string,
|
|
231
|
+
top: string,
|
|
232
|
+
options?: SimulatorOptions & { nativeAddonPath?: string },
|
|
233
|
+
): Simulation<P> {
|
|
234
|
+
const addon = loadNativeAddon(options?.nativeAddonPath);
|
|
235
|
+
const napiOpts = buildNapiOpts(options);
|
|
236
|
+
const raw = addon.NativeSimulationHandle.fromProject(
|
|
237
|
+
projectPath,
|
|
238
|
+
top,
|
|
239
|
+
napiOpts,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
243
|
+
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
244
|
+
const rawHierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
|
|
245
|
+
const hierarchy = filterHierarchyForDse(rawHierarchy, options?.deadStorePolicy);
|
|
246
|
+
|
|
247
|
+
const ports = buildPortsFromLayout(hierarchy.signals, events);
|
|
248
|
+
|
|
249
|
+
const buf = raw.sharedMemory().buffer;
|
|
250
|
+
const defaultMaxSteps = raw.defaultMaxSteps ?? undefined;
|
|
251
|
+
|
|
252
|
+
const state: DirtyState = { dirty: false };
|
|
253
|
+
const handle = wrapDirectSimulationHandle(raw);
|
|
254
|
+
const dut = createDut<P>(
|
|
255
|
+
buf,
|
|
256
|
+
layout.forDut,
|
|
257
|
+
ports,
|
|
258
|
+
handle,
|
|
259
|
+
state,
|
|
260
|
+
hierarchy,
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return new Simulation<P>(
|
|
264
|
+
handle,
|
|
265
|
+
dut,
|
|
266
|
+
events,
|
|
267
|
+
state,
|
|
268
|
+
buf,
|
|
269
|
+
layout.signals,
|
|
270
|
+
defaultMaxSteps,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** The DUT accessor object — read/write ports as plain properties. */
|
|
275
|
+
get dut(): P {
|
|
276
|
+
return this._dut;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Register a periodic clock.
|
|
281
|
+
*
|
|
282
|
+
* @param name Clock event name (must match a `clock` port).
|
|
283
|
+
* @param opts `period` in time units; optional `initialDelay`.
|
|
284
|
+
*/
|
|
285
|
+
addClock(
|
|
286
|
+
name: string,
|
|
287
|
+
opts: { period: number; initialDelay?: number },
|
|
288
|
+
): void {
|
|
289
|
+
this.ensureAlive();
|
|
290
|
+
const eventId = this.resolveEvent(name);
|
|
291
|
+
this._handle.addClock(eventId, opts.period, opts.initialDelay ?? 0);
|
|
292
|
+
this._clocks.set(name, { period: opts.period, eventId });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Schedule a one-shot value change for a signal.
|
|
297
|
+
*
|
|
298
|
+
* @param name Event/signal name.
|
|
299
|
+
* @param opts `time` — absolute time to apply; `value` — value to set.
|
|
300
|
+
*/
|
|
301
|
+
schedule(name: string, opts: { time: number; value: number }): void {
|
|
302
|
+
this.ensureAlive();
|
|
303
|
+
const eventId = this.resolveEvent(name);
|
|
304
|
+
this._handle.schedule(eventId, opts.time, opts.value);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Run the simulation until the given time.
|
|
309
|
+
* Processes all scheduled events up to and including `endTime`.
|
|
310
|
+
*
|
|
311
|
+
* When `maxSteps` is provided, steps are counted in TS and a
|
|
312
|
+
* `SimulationTimeoutError` is thrown if the budget is exhausted before
|
|
313
|
+
* reaching `endTime`. Without `maxSteps` the fast Rust path is used.
|
|
314
|
+
*/
|
|
315
|
+
runUntil(endTime: number, opts?: { maxSteps?: number }): void {
|
|
316
|
+
this.ensureAlive();
|
|
317
|
+
if (opts?.maxSteps == null) {
|
|
318
|
+
this._handle.runUntil(endTime);
|
|
319
|
+
this._state.dirty = false;
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const max = opts.maxSteps;
|
|
323
|
+
let steps = 0;
|
|
324
|
+
while (this._handle.time() < endTime) {
|
|
325
|
+
const t = this._handle.step();
|
|
326
|
+
if (t == null) break;
|
|
327
|
+
steps++;
|
|
328
|
+
if (steps >= max) {
|
|
329
|
+
this._state.dirty = false;
|
|
330
|
+
throw new SimulationTimeoutError(
|
|
331
|
+
`runUntil: exceeded ${max} steps at time ${this._handle.time()} (target ${endTime})`,
|
|
332
|
+
this._handle.time(),
|
|
333
|
+
steps,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
this._state.dirty = false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Advance to the next scheduled event.
|
|
342
|
+
*
|
|
343
|
+
* @returns The time of the processed event, or `null` if no events remain.
|
|
344
|
+
*/
|
|
345
|
+
step(): number | null {
|
|
346
|
+
this.ensureAlive();
|
|
347
|
+
const t = this._handle.step();
|
|
348
|
+
this._state.dirty = false;
|
|
349
|
+
return t;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Current simulation time. */
|
|
353
|
+
time(): number {
|
|
354
|
+
this.ensureAlive();
|
|
355
|
+
return this._handle.time();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Peek at the time of the next scheduled event without advancing.
|
|
360
|
+
*
|
|
361
|
+
* @returns The time of the next event, or `null` if no events are scheduled.
|
|
362
|
+
*/
|
|
363
|
+
nextEventTime(): number | null {
|
|
364
|
+
this.ensureAlive();
|
|
365
|
+
return this._handle.nextEventTime();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Step until `condition()` returns true.
|
|
370
|
+
*
|
|
371
|
+
* @returns The simulation time when the condition became true.
|
|
372
|
+
* @throws SimulationTimeoutError if `maxSteps` is exceeded.
|
|
373
|
+
*/
|
|
374
|
+
waitUntil(condition: () => boolean, opts?: { maxSteps?: number }): number {
|
|
375
|
+
this.ensureAlive();
|
|
376
|
+
const max = opts?.maxSteps ?? this._defaultMaxSteps ?? 100_000;
|
|
377
|
+
let steps = 0;
|
|
378
|
+
while (!condition()) {
|
|
379
|
+
const t = this._handle.step();
|
|
380
|
+
this._state.dirty = false;
|
|
381
|
+
if (t == null) break;
|
|
382
|
+
steps++;
|
|
383
|
+
if (steps >= max) {
|
|
384
|
+
throw new SimulationTimeoutError(
|
|
385
|
+
`waitUntil: condition not met after ${max} steps at time ${this._handle.time()}`,
|
|
386
|
+
this._handle.time(),
|
|
387
|
+
steps,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return this._handle.time();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Wait for `count` rising edges of the given clock.
|
|
396
|
+
*
|
|
397
|
+
* Detects actual 0→1 transitions by reading the clock signal directly
|
|
398
|
+
* from the shared buffer (clock ports are excluded from the DUT proxy).
|
|
399
|
+
* The clock must have been registered via `addClock`.
|
|
400
|
+
*
|
|
401
|
+
* @returns The simulation time after the edges are observed.
|
|
402
|
+
* @throws SimulationTimeoutError if `maxSteps` is exceeded.
|
|
403
|
+
*/
|
|
404
|
+
waitForCycles(
|
|
405
|
+
clock: string,
|
|
406
|
+
count: number,
|
|
407
|
+
opts?: { maxSteps?: number },
|
|
408
|
+
): number {
|
|
409
|
+
this.ensureAlive();
|
|
410
|
+
if (!this._clocks.has(clock)) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`No clock registered for '${clock}'. Call addClock() first.`,
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const sig = this._layout[clock];
|
|
416
|
+
if (!sig) {
|
|
417
|
+
throw new Error(`No layout entry for clock '${clock}'.`);
|
|
418
|
+
}
|
|
419
|
+
const view = new DataView(this._buffer);
|
|
420
|
+
const readClk = () => view.getUint8(sig.offset);
|
|
421
|
+
let prev = readClk();
|
|
422
|
+
let remaining = count;
|
|
423
|
+
return this.waitUntil(() => {
|
|
424
|
+
const curr = readClk();
|
|
425
|
+
if (prev === 0 && curr !== 0) remaining--;
|
|
426
|
+
prev = curr;
|
|
427
|
+
return remaining <= 0;
|
|
428
|
+
}, opts);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Assert and release a reset signal.
|
|
433
|
+
*
|
|
434
|
+
* The active level is determined automatically from the Veryl type:
|
|
435
|
+
* - `reset` / `reset_async_high` / `reset_sync_high` → active-high (1)
|
|
436
|
+
* - `reset_async_low` / `reset_sync_low` → active-low (0)
|
|
437
|
+
*
|
|
438
|
+
* For sync resets (with an associated clock from FfDeclaration), advances
|
|
439
|
+
* `activeCycles` worth of the associated clock's period using `runUntil`.
|
|
440
|
+
* For async resets without an associated clock, `duration` must be specified.
|
|
441
|
+
* An explicit `duration` overrides cycle-based calculation for either type.
|
|
442
|
+
*/
|
|
443
|
+
reset(
|
|
444
|
+
signal: string,
|
|
445
|
+
opts?: { activeCycles?: number; duration?: number },
|
|
446
|
+
): void {
|
|
447
|
+
this.ensureAlive();
|
|
448
|
+
const sig = this._layout[signal];
|
|
449
|
+
if (!sig) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`Unknown port '${signal}'. Available: ${Object.keys(this._layout).join(", ")}`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const typeKind = sig.typeKind ?? "";
|
|
455
|
+
if (!typeKind.startsWith("reset")) {
|
|
456
|
+
throw new Error(
|
|
457
|
+
`Port '${signal}' is not a reset signal (type_kind: '${typeKind}').`,
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
const isActiveLow =
|
|
461
|
+
typeKind === "reset_async_low" || typeKind === "reset_sync_low";
|
|
462
|
+
const activeValue = isActiveLow ? 0n : 1n;
|
|
463
|
+
const inactiveValue = isActiveLow ? 1n : 0n;
|
|
464
|
+
|
|
465
|
+
const dut = this._dut as Record<string, unknown>;
|
|
466
|
+
dut[signal] = activeValue;
|
|
467
|
+
|
|
468
|
+
const associatedClock = sig.associatedClock;
|
|
469
|
+
|
|
470
|
+
if (opts?.duration != null) {
|
|
471
|
+
// Explicit duration (works for both sync and async resets)
|
|
472
|
+
this._handle.runUntil(this._handle.time() + opts.duration);
|
|
473
|
+
} else if (associatedClock) {
|
|
474
|
+
// Clock-associated reset → advance by activeCycles
|
|
475
|
+
const cycles = opts?.activeCycles ?? 2;
|
|
476
|
+
this.waitForCycles(associatedClock, cycles);
|
|
477
|
+
} else {
|
|
478
|
+
// No associated clock and no explicit duration → error
|
|
479
|
+
throw new Error(
|
|
480
|
+
`Reset '${signal}' has no associated clock. Specify opts.duration.`,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
dut[signal] = inactiveValue;
|
|
485
|
+
this._state.dirty = false;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Read the raw 4-state (value + mask) pair for the named port.
|
|
490
|
+
*/
|
|
491
|
+
fourState(portName: string): FourStateValue {
|
|
492
|
+
this.ensureAlive();
|
|
493
|
+
const sig = this._layout[portName];
|
|
494
|
+
if (!sig) {
|
|
495
|
+
throw new Error(
|
|
496
|
+
`Unknown port '${portName}'. Available: ${Object.keys(this._layout).join(", ")}`,
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
const [value, mask] = readFourState(this._buffer, sig);
|
|
500
|
+
return { __fourState: true, value, mask };
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Write current signal values to VCD at the given timestamp. */
|
|
504
|
+
dump(timestamp: number): void {
|
|
505
|
+
this.ensureAlive();
|
|
506
|
+
this._handle.dump(timestamp);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** Release native resources. */
|
|
510
|
+
dispose(): void {
|
|
511
|
+
if (!this._disposed) {
|
|
512
|
+
this._disposed = true;
|
|
513
|
+
this._handle.dispose();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// -----------------------------------------------------------------------
|
|
518
|
+
// Internal
|
|
519
|
+
// -----------------------------------------------------------------------
|
|
520
|
+
|
|
521
|
+
private resolveEvent(name: string): number {
|
|
522
|
+
const id = this._events[name];
|
|
523
|
+
if (id === undefined) {
|
|
524
|
+
throw new Error(
|
|
525
|
+
`Unknown event '${name}'. Available: ${Object.keys(this._events).join(", ")}`,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
return id;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private ensureAlive(): void {
|
|
532
|
+
if (this._disposed) {
|
|
533
|
+
throw new Error("Simulation has been disposed");
|
|
534
|
+
}
|
|
535
|
+
}
|
|
464
536
|
}
|