@celox-sim/celox 0.1.4 → 0.1.6
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 +13 -6
- package/dist/dut.d.ts.map +1 -1
- package/dist/dut.js +79 -6
- package/dist/dut.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/napi-helpers.d.ts +62 -1
- package/dist/napi-helpers.d.ts.map +1 -1
- package/dist/napi-helpers.js +154 -19
- package/dist/napi-helpers.js.map +1 -1
- package/dist/simulation.d.ts +59 -3
- package/dist/simulation.d.ts.map +1 -1
- package/dist/simulation.js +162 -16
- package/dist/simulation.js.map +1 -1
- package/dist/simulator.d.ts +7 -1
- package/dist/simulator.d.ts.map +1 -1
- package/dist/simulator.js +31 -13
- package/dist/simulator.js.map +1 -1
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/dut.ts +93 -5
- package/src/e2e.bench.ts +164 -1
- package/src/e2e.test.ts +432 -30
- package/src/index.ts +10 -2
- package/src/napi-helpers.ts +224 -22
- package/src/simulation.test.ts +213 -5
- package/src/simulation.ts +198 -13
- package/src/simulator.ts +38 -10
- package/src/types.ts +51 -0
package/src/simulation.ts
CHANGED
|
@@ -7,16 +7,21 @@
|
|
|
7
7
|
|
|
8
8
|
import type {
|
|
9
9
|
CreateResult,
|
|
10
|
+
FourStateValue,
|
|
10
11
|
ModuleDefinition,
|
|
11
12
|
NativeSimulationHandle,
|
|
13
|
+
SignalLayout,
|
|
12
14
|
SimulatorOptions,
|
|
13
15
|
} from "./types.js";
|
|
14
|
-
import {
|
|
16
|
+
import { SimulationTimeoutError } from "./types.js";
|
|
17
|
+
import { createDut, readFourState, type DirtyState } from "./dut.js";
|
|
15
18
|
import {
|
|
16
19
|
loadNativeAddon,
|
|
17
20
|
parseNapiLayout,
|
|
21
|
+
parseHierarchyLayout,
|
|
18
22
|
buildPortsFromLayout,
|
|
19
23
|
wrapDirectSimulationHandle,
|
|
24
|
+
buildNapiOpts,
|
|
20
25
|
} from "./napi-helpers.js";
|
|
21
26
|
|
|
22
27
|
/**
|
|
@@ -49,6 +54,9 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
49
54
|
private readonly _dut: P;
|
|
50
55
|
private readonly _events: Record<string, number>;
|
|
51
56
|
private readonly _state: DirtyState;
|
|
57
|
+
private readonly _buffer: ArrayBuffer | SharedArrayBuffer;
|
|
58
|
+
private readonly _layout: Record<string, SignalLayout & { typeKind?: string; associatedClock?: string }>;
|
|
59
|
+
private readonly _clocks = new Map<string, { period: number; eventId: number }>();
|
|
52
60
|
private _disposed = false;
|
|
53
61
|
|
|
54
62
|
private constructor(
|
|
@@ -56,11 +64,15 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
56
64
|
dut: P,
|
|
57
65
|
events: Record<string, number>,
|
|
58
66
|
state: DirtyState,
|
|
67
|
+
buffer: ArrayBuffer | SharedArrayBuffer,
|
|
68
|
+
layout: Record<string, SignalLayout & { typeKind?: string; associatedClock?: string }>,
|
|
59
69
|
) {
|
|
60
70
|
this._handle = handle;
|
|
61
71
|
this._dut = dut;
|
|
62
72
|
this._events = events;
|
|
63
73
|
this._state = state;
|
|
74
|
+
this._buffer = buffer;
|
|
75
|
+
this._layout = layout;
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
/**
|
|
@@ -91,8 +103,8 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
91
103
|
);
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
const { fourState, vcd } = options ?? {};
|
|
95
|
-
const result = createFn(module.source, module.name, { fourState, vcd });
|
|
106
|
+
const { fourState, vcd, optimize, falseLoops, trueLoops, clockType, resetType } = options ?? {};
|
|
107
|
+
const result = createFn(module.source, module.name, { fourState, vcd, optimize, falseLoops, trueLoops, clockType, resetType });
|
|
96
108
|
const state: DirtyState = { dirty: false };
|
|
97
109
|
|
|
98
110
|
const dut = createDut<P>(
|
|
@@ -101,9 +113,10 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
101
113
|
module.ports,
|
|
102
114
|
result.handle,
|
|
103
115
|
state,
|
|
116
|
+
result.hierarchy,
|
|
104
117
|
);
|
|
105
118
|
|
|
106
|
-
return new Simulation<P>(result.handle, dut, result.events, state);
|
|
119
|
+
return new Simulation<P>(result.handle, dut, result.events, state, result.buffer, result.layout);
|
|
107
120
|
}
|
|
108
121
|
|
|
109
122
|
/**
|
|
@@ -124,11 +137,12 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
124
137
|
options?: SimulatorOptions & { nativeAddonPath?: string },
|
|
125
138
|
): Simulation<P> {
|
|
126
139
|
const addon = loadNativeAddon(options?.nativeAddonPath);
|
|
127
|
-
const napiOpts = options
|
|
140
|
+
const napiOpts = buildNapiOpts(options);
|
|
128
141
|
const raw = new addon.NativeSimulationHandle(source, top, napiOpts);
|
|
129
142
|
|
|
130
143
|
const layout = parseNapiLayout(raw.layoutJson);
|
|
131
144
|
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
145
|
+
const hierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
|
|
132
146
|
|
|
133
147
|
const ports = buildPortsFromLayout(layout.signals, events);
|
|
134
148
|
|
|
@@ -136,9 +150,9 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
136
150
|
|
|
137
151
|
const state: DirtyState = { dirty: false };
|
|
138
152
|
const handle = wrapDirectSimulationHandle(raw);
|
|
139
|
-
const dut = createDut<P>(buf, layout.forDut, ports, handle, state);
|
|
153
|
+
const dut = createDut<P>(buf, layout.forDut, ports, handle, state, hierarchy);
|
|
140
154
|
|
|
141
|
-
return new Simulation<P>(handle, dut, events, state);
|
|
155
|
+
return new Simulation<P>(handle, dut, events, state, buf, layout.signals);
|
|
142
156
|
}
|
|
143
157
|
|
|
144
158
|
/**
|
|
@@ -159,11 +173,12 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
159
173
|
options?: SimulatorOptions & { nativeAddonPath?: string },
|
|
160
174
|
): Simulation<P> {
|
|
161
175
|
const addon = loadNativeAddon(options?.nativeAddonPath);
|
|
162
|
-
const napiOpts = options
|
|
176
|
+
const napiOpts = buildNapiOpts(options);
|
|
163
177
|
const raw = addon.NativeSimulationHandle.fromProject(projectPath, top, napiOpts);
|
|
164
178
|
|
|
165
179
|
const layout = parseNapiLayout(raw.layoutJson);
|
|
166
180
|
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
181
|
+
const hierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
|
|
167
182
|
|
|
168
183
|
const ports = buildPortsFromLayout(layout.signals, events);
|
|
169
184
|
|
|
@@ -171,9 +186,9 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
171
186
|
|
|
172
187
|
const state: DirtyState = { dirty: false };
|
|
173
188
|
const handle = wrapDirectSimulationHandle(raw);
|
|
174
|
-
const dut = createDut<P>(buf, layout.forDut, ports, handle, state);
|
|
189
|
+
const dut = createDut<P>(buf, layout.forDut, ports, handle, state, hierarchy);
|
|
175
190
|
|
|
176
|
-
return new Simulation<P>(handle, dut, events, state);
|
|
191
|
+
return new Simulation<P>(handle, dut, events, state, buf, layout.signals);
|
|
177
192
|
}
|
|
178
193
|
|
|
179
194
|
/** The DUT accessor object — read/write ports as plain properties. */
|
|
@@ -194,6 +209,7 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
194
209
|
this.ensureAlive();
|
|
195
210
|
const eventId = this.resolveEvent(name);
|
|
196
211
|
this._handle.addClock(eventId, opts.period, opts.initialDelay ?? 0);
|
|
212
|
+
this._clocks.set(name, { period: opts.period, eventId });
|
|
197
213
|
}
|
|
198
214
|
|
|
199
215
|
/**
|
|
@@ -211,11 +227,33 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
211
227
|
/**
|
|
212
228
|
* Run the simulation until the given time.
|
|
213
229
|
* Processes all scheduled events up to and including `endTime`.
|
|
214
|
-
*
|
|
230
|
+
*
|
|
231
|
+
* When `maxSteps` is provided, steps are counted in TS and a
|
|
232
|
+
* `SimulationTimeoutError` is thrown if the budget is exhausted before
|
|
233
|
+
* reaching `endTime`. Without `maxSteps` the fast Rust path is used.
|
|
215
234
|
*/
|
|
216
|
-
runUntil(endTime: number): void {
|
|
235
|
+
runUntil(endTime: number, opts?: { maxSteps?: number }): void {
|
|
217
236
|
this.ensureAlive();
|
|
218
|
-
|
|
237
|
+
if (opts?.maxSteps == null) {
|
|
238
|
+
this._handle.runUntil(endTime);
|
|
239
|
+
this._state.dirty = false;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const max = opts.maxSteps;
|
|
243
|
+
let steps = 0;
|
|
244
|
+
while (this._handle.time() < endTime) {
|
|
245
|
+
const t = this._handle.step();
|
|
246
|
+
if (t == null) break;
|
|
247
|
+
steps++;
|
|
248
|
+
if (steps >= max) {
|
|
249
|
+
this._state.dirty = false;
|
|
250
|
+
throw new SimulationTimeoutError(
|
|
251
|
+
`runUntil: exceeded ${max} steps at time ${this._handle.time()} (target ${endTime})`,
|
|
252
|
+
this._handle.time(),
|
|
253
|
+
steps,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
219
257
|
this._state.dirty = false;
|
|
220
258
|
}
|
|
221
259
|
|
|
@@ -237,6 +275,153 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
237
275
|
return this._handle.time();
|
|
238
276
|
}
|
|
239
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Peek at the time of the next scheduled event without advancing.
|
|
280
|
+
*
|
|
281
|
+
* @returns The time of the next event, or `null` if no events are scheduled.
|
|
282
|
+
*/
|
|
283
|
+
nextEventTime(): number | null {
|
|
284
|
+
this.ensureAlive();
|
|
285
|
+
return this._handle.nextEventTime();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Step until `condition()` returns true.
|
|
290
|
+
*
|
|
291
|
+
* @returns The simulation time when the condition became true.
|
|
292
|
+
* @throws SimulationTimeoutError if `maxSteps` is exceeded.
|
|
293
|
+
*/
|
|
294
|
+
waitUntil(
|
|
295
|
+
condition: () => boolean,
|
|
296
|
+
opts?: { maxSteps?: number },
|
|
297
|
+
): number {
|
|
298
|
+
this.ensureAlive();
|
|
299
|
+
const max = opts?.maxSteps ?? 100_000;
|
|
300
|
+
let steps = 0;
|
|
301
|
+
while (!condition()) {
|
|
302
|
+
const t = this._handle.step();
|
|
303
|
+
this._state.dirty = false;
|
|
304
|
+
if (t == null) break;
|
|
305
|
+
steps++;
|
|
306
|
+
if (steps >= max) {
|
|
307
|
+
throw new SimulationTimeoutError(
|
|
308
|
+
`waitUntil: condition not met after ${max} steps at time ${this._handle.time()}`,
|
|
309
|
+
this._handle.time(),
|
|
310
|
+
steps,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return this._handle.time();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Wait for `count` rising edges of the given clock.
|
|
319
|
+
*
|
|
320
|
+
* Detects actual 0→1 transitions by reading the clock signal directly
|
|
321
|
+
* from the shared buffer (clock ports are excluded from the DUT proxy).
|
|
322
|
+
* The clock must have been registered via `addClock`.
|
|
323
|
+
*
|
|
324
|
+
* @returns The simulation time after the edges are observed.
|
|
325
|
+
* @throws SimulationTimeoutError if `maxSteps` is exceeded.
|
|
326
|
+
*/
|
|
327
|
+
waitForCycles(
|
|
328
|
+
clock: string,
|
|
329
|
+
count: number,
|
|
330
|
+
opts?: { maxSteps?: number },
|
|
331
|
+
): number {
|
|
332
|
+
this.ensureAlive();
|
|
333
|
+
if (!this._clocks.has(clock)) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`No clock registered for '${clock}'. Call addClock() first.`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
const sig = this._layout[clock];
|
|
339
|
+
if (!sig) {
|
|
340
|
+
throw new Error(`No layout entry for clock '${clock}'.`);
|
|
341
|
+
}
|
|
342
|
+
const view = new DataView(this._buffer);
|
|
343
|
+
const readClk = () => view.getUint8(sig.offset);
|
|
344
|
+
let prev = readClk();
|
|
345
|
+
let remaining = count;
|
|
346
|
+
return this.waitUntil(() => {
|
|
347
|
+
const curr = readClk();
|
|
348
|
+
if (prev === 0 && curr !== 0) remaining--;
|
|
349
|
+
prev = curr;
|
|
350
|
+
return remaining <= 0;
|
|
351
|
+
}, opts);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Assert and release a reset signal.
|
|
356
|
+
*
|
|
357
|
+
* The active level is determined automatically from the Veryl type:
|
|
358
|
+
* - `reset` / `reset_async_high` / `reset_sync_high` → active-high (1)
|
|
359
|
+
* - `reset_async_low` / `reset_sync_low` → active-low (0)
|
|
360
|
+
*
|
|
361
|
+
* For sync resets (with an associated clock from FfDeclaration), advances
|
|
362
|
+
* `activeCycles` worth of the associated clock's period using `runUntil`.
|
|
363
|
+
* For async resets without an associated clock, `duration` must be specified.
|
|
364
|
+
* An explicit `duration` overrides cycle-based calculation for either type.
|
|
365
|
+
*/
|
|
366
|
+
reset(
|
|
367
|
+
signal: string,
|
|
368
|
+
opts?: { activeCycles?: number; duration?: number },
|
|
369
|
+
): void {
|
|
370
|
+
this.ensureAlive();
|
|
371
|
+
const sig = this._layout[signal];
|
|
372
|
+
if (!sig) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`Unknown port '${signal}'. Available: ${Object.keys(this._layout).join(", ")}`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
const typeKind = sig.typeKind ?? "";
|
|
378
|
+
if (!typeKind.startsWith("reset")) {
|
|
379
|
+
throw new Error(
|
|
380
|
+
`Port '${signal}' is not a reset signal (type_kind: '${typeKind}').`,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
const isActiveLow = typeKind === "reset_async_low" || typeKind === "reset_sync_low";
|
|
384
|
+
const activeValue = isActiveLow ? 0 : 1;
|
|
385
|
+
const inactiveValue = isActiveLow ? 1 : 0;
|
|
386
|
+
|
|
387
|
+
const dut = this._dut as Record<string, unknown>;
|
|
388
|
+
dut[signal] = activeValue;
|
|
389
|
+
|
|
390
|
+
const associatedClock = sig.associatedClock;
|
|
391
|
+
|
|
392
|
+
if (opts?.duration != null) {
|
|
393
|
+
// Explicit duration (works for both sync and async resets)
|
|
394
|
+
this._handle.runUntil(this._handle.time() + opts.duration);
|
|
395
|
+
} else if (associatedClock) {
|
|
396
|
+
// Clock-associated reset → advance by activeCycles
|
|
397
|
+
const cycles = opts?.activeCycles ?? 2;
|
|
398
|
+
this.waitForCycles(associatedClock, cycles);
|
|
399
|
+
} else {
|
|
400
|
+
// No associated clock and no explicit duration → error
|
|
401
|
+
throw new Error(
|
|
402
|
+
`Reset '${signal}' has no associated clock. Specify opts.duration.`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
dut[signal] = inactiveValue;
|
|
407
|
+
this._state.dirty = false;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Read the raw 4-state (value + mask) pair for the named port.
|
|
412
|
+
*/
|
|
413
|
+
fourState(portName: string): FourStateValue {
|
|
414
|
+
this.ensureAlive();
|
|
415
|
+
const sig = this._layout[portName];
|
|
416
|
+
if (!sig) {
|
|
417
|
+
throw new Error(
|
|
418
|
+
`Unknown port '${portName}'. Available: ${Object.keys(this._layout).join(", ")}`,
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
const [value, mask] = readFourState(this._buffer, sig);
|
|
422
|
+
return { __fourState: true, value, mask };
|
|
423
|
+
}
|
|
424
|
+
|
|
240
425
|
/** Write current signal values to VCD at the given timestamp. */
|
|
241
426
|
dump(timestamp: number): void {
|
|
242
427
|
this.ensureAlive();
|
package/src/simulator.ts
CHANGED
|
@@ -8,16 +8,20 @@
|
|
|
8
8
|
import type {
|
|
9
9
|
CreateResult,
|
|
10
10
|
EventHandle,
|
|
11
|
+
FourStateValue,
|
|
11
12
|
ModuleDefinition,
|
|
12
13
|
NativeSimulatorHandle,
|
|
14
|
+
SignalLayout,
|
|
13
15
|
SimulatorOptions,
|
|
14
16
|
} from "./types.js";
|
|
15
|
-
import { createDut, type DirtyState } from "./dut.js";
|
|
17
|
+
import { createDut, readFourState, type DirtyState } from "./dut.js";
|
|
16
18
|
import {
|
|
17
19
|
loadNativeAddon,
|
|
18
20
|
parseNapiLayout,
|
|
21
|
+
parseHierarchyLayout,
|
|
19
22
|
buildPortsFromLayout,
|
|
20
23
|
wrapDirectSimulatorHandle,
|
|
24
|
+
buildNapiOpts,
|
|
21
25
|
} from "./napi-helpers.js";
|
|
22
26
|
|
|
23
27
|
/**
|
|
@@ -54,6 +58,8 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
54
58
|
private readonly _events: Record<string, number>;
|
|
55
59
|
private readonly _defaultEventId: number;
|
|
56
60
|
private readonly _state: DirtyState;
|
|
61
|
+
private readonly _buffer: ArrayBuffer | SharedArrayBuffer;
|
|
62
|
+
private readonly _layout: Record<string, SignalLayout>;
|
|
57
63
|
private _disposed = false;
|
|
58
64
|
|
|
59
65
|
private constructor(
|
|
@@ -61,11 +67,15 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
61
67
|
dut: P,
|
|
62
68
|
events: Record<string, number>,
|
|
63
69
|
state: DirtyState,
|
|
70
|
+
buffer: ArrayBuffer | SharedArrayBuffer,
|
|
71
|
+
layout: Record<string, SignalLayout>,
|
|
64
72
|
) {
|
|
65
73
|
this._handle = handle;
|
|
66
74
|
this._dut = dut;
|
|
67
75
|
this._events = events;
|
|
68
76
|
this._state = state;
|
|
77
|
+
this._buffer = buffer;
|
|
78
|
+
this._layout = layout;
|
|
69
79
|
const keys = Object.keys(events);
|
|
70
80
|
this._defaultEventId = keys.length > 0 ? events[keys[0]!]! : -1;
|
|
71
81
|
}
|
|
@@ -98,8 +108,8 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
98
108
|
);
|
|
99
109
|
}
|
|
100
110
|
|
|
101
|
-
const { fourState, vcd } = options ?? {};
|
|
102
|
-
const result = createFn(module.source, module.name, { fourState, vcd });
|
|
111
|
+
const { fourState, vcd, optimize, falseLoops, trueLoops, clockType, resetType } = options ?? {};
|
|
112
|
+
const result = createFn(module.source, module.name, { fourState, vcd, optimize, falseLoops, trueLoops, clockType, resetType });
|
|
103
113
|
const state: DirtyState = { dirty: false };
|
|
104
114
|
|
|
105
115
|
const dut = createDut<P>(
|
|
@@ -108,9 +118,10 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
108
118
|
module.ports,
|
|
109
119
|
result.handle,
|
|
110
120
|
state,
|
|
121
|
+
result.hierarchy,
|
|
111
122
|
);
|
|
112
123
|
|
|
113
|
-
return new Simulator<P>(result.handle, dut, result.events, state);
|
|
124
|
+
return new Simulator<P>(result.handle, dut, result.events, state, result.buffer, result.layout);
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
/**
|
|
@@ -133,11 +144,12 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
133
144
|
options?: SimulatorOptions & { nativeAddonPath?: string },
|
|
134
145
|
): Simulator<P> {
|
|
135
146
|
const addon = loadNativeAddon(options?.nativeAddonPath);
|
|
136
|
-
const napiOpts = options
|
|
147
|
+
const napiOpts = buildNapiOpts(options);
|
|
137
148
|
const raw = new addon.NativeSimulatorHandle(source, top, napiOpts);
|
|
138
149
|
|
|
139
150
|
const layout = parseNapiLayout(raw.layoutJson);
|
|
140
151
|
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
152
|
+
const hierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
|
|
141
153
|
|
|
142
154
|
const ports = buildPortsFromLayout(layout.signals, events);
|
|
143
155
|
|
|
@@ -145,9 +157,9 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
145
157
|
|
|
146
158
|
const state: DirtyState = { dirty: false };
|
|
147
159
|
const handle = wrapDirectSimulatorHandle(raw);
|
|
148
|
-
const dut = createDut<P>(buf, layout.forDut, ports, handle, state);
|
|
160
|
+
const dut = createDut<P>(buf, layout.forDut, ports, handle, state, hierarchy);
|
|
149
161
|
|
|
150
|
-
return new Simulator<P>(handle, dut, events, state);
|
|
162
|
+
return new Simulator<P>(handle, dut, events, state, buf, layout.forDut);
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
/**
|
|
@@ -167,11 +179,12 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
167
179
|
options?: SimulatorOptions & { nativeAddonPath?: string },
|
|
168
180
|
): Simulator<P> {
|
|
169
181
|
const addon = loadNativeAddon(options?.nativeAddonPath);
|
|
170
|
-
const napiOpts = options
|
|
182
|
+
const napiOpts = buildNapiOpts(options);
|
|
171
183
|
const raw = addon.NativeSimulatorHandle.fromProject(projectPath, top, napiOpts);
|
|
172
184
|
|
|
173
185
|
const layout = parseNapiLayout(raw.layoutJson);
|
|
174
186
|
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
187
|
+
const hierarchy = parseHierarchyLayout(raw.hierarchyJson, events);
|
|
175
188
|
|
|
176
189
|
const ports = buildPortsFromLayout(layout.signals, events);
|
|
177
190
|
|
|
@@ -179,9 +192,9 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
179
192
|
|
|
180
193
|
const state: DirtyState = { dirty: false };
|
|
181
194
|
const handle = wrapDirectSimulatorHandle(raw);
|
|
182
|
-
const dut = createDut<P>(buf, layout.forDut, ports, handle, state);
|
|
195
|
+
const dut = createDut<P>(buf, layout.forDut, ports, handle, state, hierarchy);
|
|
183
196
|
|
|
184
|
-
return new Simulator<P>(handle, dut, events, state);
|
|
197
|
+
return new Simulator<P>(handle, dut, events, state, buf, layout.forDut);
|
|
185
198
|
}
|
|
186
199
|
|
|
187
200
|
/** The DUT accessor object — read/write ports as plain properties. */
|
|
@@ -240,6 +253,21 @@ export class Simulator<P = Record<string, unknown>> {
|
|
|
240
253
|
return { name, id };
|
|
241
254
|
}
|
|
242
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Read the raw 4-state (value + mask) pair for the named port.
|
|
258
|
+
*/
|
|
259
|
+
fourState(portName: string): FourStateValue {
|
|
260
|
+
this.ensureAlive();
|
|
261
|
+
const sig = this._layout[portName];
|
|
262
|
+
if (!sig) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`Unknown port '${portName}'. Available: ${Object.keys(this._layout).join(", ")}`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const [value, mask] = readFourState(this._buffer, sig);
|
|
268
|
+
return { __fourState: true, value, mask };
|
|
269
|
+
}
|
|
270
|
+
|
|
243
271
|
/** Write current signal values to VCD at the given timestamp. */
|
|
244
272
|
dump(timestamp: number): void {
|
|
245
273
|
this.ensureAlive();
|
package/src/types.ts
CHANGED
|
@@ -58,6 +58,10 @@ export interface SignalLayout {
|
|
|
58
58
|
/** If true, an equal-sized mask follows immediately after the value. */
|
|
59
59
|
readonly is4state: boolean;
|
|
60
60
|
readonly direction: "input" | "output" | "inout";
|
|
61
|
+
/** The Veryl type kind (e.g. "clock", "reset_async_high", "logic"). */
|
|
62
|
+
readonly typeKind?: string;
|
|
63
|
+
/** For reset signals, the name of the associated clock (from FfDeclaration). */
|
|
64
|
+
readonly associatedClock?: string;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
// ---------------------------------------------------------------------------
|
|
@@ -86,6 +90,7 @@ export interface NativeSimulationHandle {
|
|
|
86
90
|
runUntil(endTime: number): void;
|
|
87
91
|
step(): number | null;
|
|
88
92
|
time(): number;
|
|
93
|
+
nextEventTime(): number | null;
|
|
89
94
|
evalComb(): void;
|
|
90
95
|
dump(timestamp: number): void;
|
|
91
96
|
dispose(): void;
|
|
@@ -110,6 +115,8 @@ export interface CreateResult<H extends NativeHandle = NativeHandle> {
|
|
|
110
115
|
readonly events: Record<string, number>;
|
|
111
116
|
/** Native control handle. */
|
|
112
117
|
readonly handle: H;
|
|
118
|
+
/** Full instance hierarchy (optional — present when NAPI provides it). */
|
|
119
|
+
readonly hierarchy?: import("./napi-helpers.js").HierarchyNode;
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
// ---------------------------------------------------------------------------
|
|
@@ -121,6 +128,16 @@ export interface SimulatorOptions {
|
|
|
121
128
|
fourState?: boolean;
|
|
122
129
|
/** Path to write VCD waveform output. */
|
|
123
130
|
vcd?: string;
|
|
131
|
+
/** Enable Cranelift optimization passes. */
|
|
132
|
+
optimize?: boolean;
|
|
133
|
+
/** False-loop declarations to ignore during compilation. */
|
|
134
|
+
falseLoops?: LoopBreak[];
|
|
135
|
+
/** True-loop declarations with convergence limits. */
|
|
136
|
+
trueLoops?: TrueLoopSpec[];
|
|
137
|
+
/** Clock polarity. Default: "posedge". */
|
|
138
|
+
clockType?: "posedge" | "negedge";
|
|
139
|
+
/** Reset type. Default: "async_low". */
|
|
140
|
+
resetType?: "async_high" | "async_low" | "sync_high" | "sync_low";
|
|
124
141
|
}
|
|
125
142
|
|
|
126
143
|
// ---------------------------------------------------------------------------
|
|
@@ -162,3 +179,37 @@ export function isFourStateValue(v: unknown): v is FourStateValue {
|
|
|
162
179
|
(v as FourStateValue).__fourState === true
|
|
163
180
|
);
|
|
164
181
|
}
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Simulation timeout error
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Thrown when a simulation helper exceeds its step budget.
|
|
189
|
+
*/
|
|
190
|
+
export class SimulationTimeoutError extends Error {
|
|
191
|
+
readonly time: number;
|
|
192
|
+
readonly steps: number;
|
|
193
|
+
|
|
194
|
+
constructor(message: string, time: number, steps: number) {
|
|
195
|
+
super(message);
|
|
196
|
+
this.name = "SimulationTimeoutError";
|
|
197
|
+
this.time = time;
|
|
198
|
+
this.steps = steps;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Loop-break types (for Phase 3c)
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
/** Specifies a false-loop to ignore during compilation. */
|
|
207
|
+
export interface LoopBreak {
|
|
208
|
+
from: string;
|
|
209
|
+
to: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Specifies a true-loop with a convergence iteration limit. */
|
|
213
|
+
export interface TrueLoopSpec extends LoopBreak {
|
|
214
|
+
maxIter: number;
|
|
215
|
+
}
|