@celox-sim/celox 0.1.5 → 0.1.7
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 +14 -7
- package/dist/dut.d.ts.map +1 -1
- package/dist/dut.js +103 -27
- 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 +35 -14
- package/dist/simulator.js.map +1 -1
- package/dist/types.d.ts +36 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/dut.test.ts +66 -66
- package/src/dut.ts +124 -31
- package/src/e2e.bench.ts +176 -13
- package/src/e2e.test.ts +632 -195
- package/src/index.ts +10 -2
- package/src/matchers.ts +4 -7
- package/src/napi-helpers.ts +224 -22
- package/src/simulation.test.ts +220 -12
- package/src/simulation.ts +198 -13
- package/src/simulator.test.ts +8 -8
- package/src/simulator.ts +43 -11
- package/src/types.ts +54 -3
package/src/e2e.test.ts
CHANGED
|
@@ -13,11 +13,12 @@ import { describe, test, expect, afterEach } from "vitest";
|
|
|
13
13
|
import { Simulator } from "./simulator.js";
|
|
14
14
|
import { Simulation } from "./simulation.js";
|
|
15
15
|
import { readFourState } from "./dut.js";
|
|
16
|
-
import { X, FourState } from "./types.js";
|
|
16
|
+
import { X, FourState, SimulationTimeoutError } from "./types.js";
|
|
17
17
|
import {
|
|
18
18
|
createSimulatorBridge,
|
|
19
19
|
loadNativeAddon,
|
|
20
20
|
parseNapiLayout,
|
|
21
|
+
parseSignalPath,
|
|
21
22
|
type RawNapiAddon,
|
|
22
23
|
type RawNapiSimulatorHandle,
|
|
23
24
|
} from "./napi-helpers.js";
|
|
@@ -96,117 +97,152 @@ module Mux4 (
|
|
|
96
97
|
describe("E2E: Simulator.fromSource (event-based)", () => {
|
|
97
98
|
test("combinational adder: a + b = sum", () => {
|
|
98
99
|
interface AdderPorts {
|
|
99
|
-
rst:
|
|
100
|
-
a:
|
|
101
|
-
b:
|
|
102
|
-
readonly sum:
|
|
100
|
+
rst: bigint;
|
|
101
|
+
a: bigint;
|
|
102
|
+
b: bigint;
|
|
103
|
+
readonly sum: bigint;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder");
|
|
106
107
|
|
|
107
|
-
sim.dut.a =
|
|
108
|
-
sim.dut.b =
|
|
108
|
+
sim.dut.a = 100n;
|
|
109
|
+
sim.dut.b = 200n;
|
|
109
110
|
sim.tick();
|
|
110
|
-
expect(sim.dut.sum).toBe(
|
|
111
|
+
expect(sim.dut.sum).toBe(300n);
|
|
111
112
|
|
|
112
|
-
sim.dut.a =
|
|
113
|
-
sim.dut.b =
|
|
113
|
+
sim.dut.a = 0xFFFFn;
|
|
114
|
+
sim.dut.b = 1n;
|
|
114
115
|
sim.tick();
|
|
115
|
-
expect(sim.dut.sum).toBe(
|
|
116
|
+
expect(sim.dut.sum).toBe(0x10000n);
|
|
116
117
|
|
|
117
|
-
sim.dut.a =
|
|
118
|
-
sim.dut.b =
|
|
118
|
+
sim.dut.a = 0n;
|
|
119
|
+
sim.dut.b = 0n;
|
|
119
120
|
sim.tick();
|
|
120
|
-
expect(sim.dut.sum).toBe(
|
|
121
|
+
expect(sim.dut.sum).toBe(0n);
|
|
121
122
|
|
|
122
123
|
sim.dispose();
|
|
123
124
|
});
|
|
124
125
|
|
|
125
126
|
test("combinational adder: lazy evalComb on output read", () => {
|
|
126
127
|
interface AdderPorts {
|
|
127
|
-
rst:
|
|
128
|
-
a:
|
|
129
|
-
b:
|
|
130
|
-
readonly sum:
|
|
128
|
+
rst: bigint;
|
|
129
|
+
a: bigint;
|
|
130
|
+
b: bigint;
|
|
131
|
+
readonly sum: bigint;
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder");
|
|
134
135
|
|
|
135
|
-
sim.dut.a =
|
|
136
|
-
sim.dut.b =
|
|
137
|
-
expect(sim.dut.sum).toBe(
|
|
136
|
+
sim.dut.a = 42n;
|
|
137
|
+
sim.dut.b = 58n;
|
|
138
|
+
expect(sim.dut.sum).toBe(100n);
|
|
138
139
|
|
|
139
140
|
sim.dispose();
|
|
140
141
|
});
|
|
141
142
|
|
|
142
143
|
test("sequential counter: counts on clock edges", () => {
|
|
143
144
|
interface CounterPorts {
|
|
144
|
-
rst:
|
|
145
|
-
en:
|
|
146
|
-
readonly count:
|
|
145
|
+
rst: bigint;
|
|
146
|
+
en: bigint;
|
|
147
|
+
readonly count: bigint;
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
150
151
|
|
|
151
|
-
// Reset the counter
|
|
152
|
-
sim.dut.rst =
|
|
152
|
+
// Reset the counter (default async_low: rst=0 is active)
|
|
153
|
+
sim.dut.rst = 0n;
|
|
153
154
|
sim.tick();
|
|
154
|
-
sim.dut.rst =
|
|
155
|
+
sim.dut.rst = 1n;
|
|
155
156
|
sim.tick();
|
|
156
|
-
expect(sim.dut.count).toBe(
|
|
157
|
+
expect(sim.dut.count).toBe(0n);
|
|
157
158
|
|
|
158
159
|
// Enable counting
|
|
159
|
-
sim.dut.en =
|
|
160
|
+
sim.dut.en = 1n;
|
|
160
161
|
sim.tick();
|
|
161
|
-
expect(sim.dut.count).toBe(
|
|
162
|
+
expect(sim.dut.count).toBe(1n);
|
|
162
163
|
|
|
163
164
|
sim.tick();
|
|
164
|
-
expect(sim.dut.count).toBe(
|
|
165
|
+
expect(sim.dut.count).toBe(2n);
|
|
165
166
|
|
|
166
167
|
sim.tick();
|
|
167
|
-
expect(sim.dut.count).toBe(
|
|
168
|
+
expect(sim.dut.count).toBe(3n);
|
|
168
169
|
|
|
169
170
|
// Disable counting
|
|
170
|
-
sim.dut.en =
|
|
171
|
+
sim.dut.en = 0n;
|
|
171
172
|
sim.tick();
|
|
172
|
-
expect(sim.dut.count).toBe(
|
|
173
|
+
expect(sim.dut.count).toBe(3n);
|
|
173
174
|
|
|
174
175
|
// Re-enable
|
|
175
|
-
sim.dut.en =
|
|
176
|
+
sim.dut.en = 1n;
|
|
176
177
|
sim.tick(5);
|
|
177
|
-
expect(sim.dut.count).toBe(
|
|
178
|
+
expect(sim.dut.count).toBe(8n);
|
|
179
|
+
|
|
180
|
+
sim.dispose();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("always_comb evaluated before FF on tick (no prior output read)", () => {
|
|
184
|
+
interface CounterPorts {
|
|
185
|
+
rst: number;
|
|
186
|
+
en: number;
|
|
187
|
+
readonly count: number;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
191
|
+
|
|
192
|
+
// Reset (default async_low: rst=0 is active)
|
|
193
|
+
sim.dut.rst = 0;
|
|
194
|
+
sim.tick();
|
|
195
|
+
sim.dut.rst = 1;
|
|
196
|
+
sim.tick();
|
|
197
|
+
|
|
198
|
+
// Write input and tick WITHOUT reading any output first.
|
|
199
|
+
// This catches the stale-comb bug where always_ff reads stale
|
|
200
|
+
// combinational values because eval_comb was skipped before FF.
|
|
201
|
+
sim.dut.en = 1;
|
|
202
|
+
sim.tick();
|
|
203
|
+
expect(Number(sim.dut.count)).toBe(1);
|
|
204
|
+
|
|
205
|
+
// Again: set input, tick, verify — no intermediate reads
|
|
206
|
+
sim.dut.en = 1;
|
|
207
|
+
sim.tick();
|
|
208
|
+
expect(Number(sim.dut.count)).toBe(2);
|
|
209
|
+
|
|
210
|
+
// Disable, tick, verify
|
|
211
|
+
sim.dut.en = 0;
|
|
212
|
+
sim.tick();
|
|
213
|
+
expect(Number(sim.dut.count)).toBe(2);
|
|
178
214
|
|
|
179
215
|
sim.dispose();
|
|
180
216
|
});
|
|
181
217
|
|
|
182
218
|
test("combinational multiplexer", () => {
|
|
183
219
|
interface Mux4Ports {
|
|
184
|
-
sel:
|
|
185
|
-
d0:
|
|
186
|
-
d1:
|
|
187
|
-
d2:
|
|
188
|
-
d3:
|
|
189
|
-
readonly y:
|
|
220
|
+
sel: bigint;
|
|
221
|
+
d0: bigint;
|
|
222
|
+
d1: bigint;
|
|
223
|
+
d2: bigint;
|
|
224
|
+
d3: bigint;
|
|
225
|
+
readonly y: bigint;
|
|
190
226
|
}
|
|
191
227
|
|
|
192
228
|
const sim = Simulator.fromSource<Mux4Ports>(MULTIPLEXER_SOURCE, "Mux4");
|
|
193
229
|
|
|
194
|
-
sim.dut.d0 =
|
|
195
|
-
sim.dut.d1 =
|
|
196
|
-
sim.dut.d2 =
|
|
197
|
-
sim.dut.d3 =
|
|
230
|
+
sim.dut.d0 = 0xAAn;
|
|
231
|
+
sim.dut.d1 = 0xBBn;
|
|
232
|
+
sim.dut.d2 = 0xCCn;
|
|
233
|
+
sim.dut.d3 = 0xDDn;
|
|
198
234
|
|
|
199
|
-
sim.dut.sel =
|
|
200
|
-
expect(sim.dut.y).toBe(
|
|
235
|
+
sim.dut.sel = 0n;
|
|
236
|
+
expect(sim.dut.y).toBe(0xAAn);
|
|
201
237
|
|
|
202
|
-
sim.dut.sel =
|
|
203
|
-
expect(sim.dut.y).toBe(
|
|
238
|
+
sim.dut.sel = 1n;
|
|
239
|
+
expect(sim.dut.y).toBe(0xBBn);
|
|
204
240
|
|
|
205
|
-
sim.dut.sel =
|
|
206
|
-
expect(sim.dut.y).toBe(
|
|
241
|
+
sim.dut.sel = 2n;
|
|
242
|
+
expect(sim.dut.y).toBe(0xCCn);
|
|
207
243
|
|
|
208
|
-
sim.dut.sel =
|
|
209
|
-
expect(sim.dut.y).toBe(
|
|
244
|
+
sim.dut.sel = 3n;
|
|
245
|
+
expect(sim.dut.y).toBe(0xDDn);
|
|
210
246
|
|
|
211
247
|
sim.dispose();
|
|
212
248
|
});
|
|
@@ -219,9 +255,9 @@ describe("E2E: Simulator.fromSource (event-based)", () => {
|
|
|
219
255
|
describe("E2E: Simulation.fromSource (time-based)", () => {
|
|
220
256
|
test("counter with timed clock: step-by-step", () => {
|
|
221
257
|
interface CounterPorts {
|
|
222
|
-
rst:
|
|
223
|
-
en:
|
|
224
|
-
readonly count:
|
|
258
|
+
rst: bigint;
|
|
259
|
+
en: bigint;
|
|
260
|
+
readonly count: bigint;
|
|
225
261
|
}
|
|
226
262
|
|
|
227
263
|
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
@@ -229,16 +265,16 @@ describe("E2E: Simulation.fromSource (time-based)", () => {
|
|
|
229
265
|
sim.addClock("clk", { period: 10 });
|
|
230
266
|
expect(sim.time()).toBe(0);
|
|
231
267
|
|
|
232
|
-
// Reset
|
|
233
|
-
sim.dut.rst =
|
|
268
|
+
// Reset (default async_low: rst=0 is active)
|
|
269
|
+
sim.dut.rst = 0n;
|
|
234
270
|
sim.runUntil(20);
|
|
235
|
-
sim.dut.rst =
|
|
236
|
-
sim.dut.en =
|
|
271
|
+
sim.dut.rst = 1n;
|
|
272
|
+
sim.dut.en = 1n;
|
|
237
273
|
|
|
238
274
|
sim.runUntil(100);
|
|
239
275
|
|
|
240
276
|
const count = sim.dut.count;
|
|
241
|
-
expect(count).toBeGreaterThan(
|
|
277
|
+
expect(count).toBeGreaterThan(0n);
|
|
242
278
|
expect(sim.time()).toBe(100);
|
|
243
279
|
|
|
244
280
|
sim.dispose();
|
|
@@ -252,53 +288,53 @@ describe("E2E: Simulation.fromSource (time-based)", () => {
|
|
|
252
288
|
describe("E2E: Simulator.fromProject (event-based)", () => {
|
|
253
289
|
test("combinational adder from project directory", () => {
|
|
254
290
|
interface AdderPorts {
|
|
255
|
-
rst:
|
|
256
|
-
a:
|
|
257
|
-
b:
|
|
258
|
-
readonly sum:
|
|
291
|
+
rst: bigint;
|
|
292
|
+
a: bigint;
|
|
293
|
+
b: bigint;
|
|
294
|
+
readonly sum: bigint;
|
|
259
295
|
}
|
|
260
296
|
|
|
261
297
|
const sim = Simulator.fromProject<AdderPorts>(ADDER_PROJECT, "Adder");
|
|
262
298
|
|
|
263
|
-
sim.dut.a =
|
|
264
|
-
sim.dut.b =
|
|
299
|
+
sim.dut.a = 100n;
|
|
300
|
+
sim.dut.b = 200n;
|
|
265
301
|
sim.tick();
|
|
266
|
-
expect(sim.dut.sum).toBe(
|
|
302
|
+
expect(sim.dut.sum).toBe(300n);
|
|
267
303
|
|
|
268
|
-
sim.dut.a =
|
|
269
|
-
sim.dut.b =
|
|
304
|
+
sim.dut.a = 0xFFFFn;
|
|
305
|
+
sim.dut.b = 1n;
|
|
270
306
|
sim.tick();
|
|
271
|
-
expect(sim.dut.sum).toBe(
|
|
307
|
+
expect(sim.dut.sum).toBe(0x10000n);
|
|
272
308
|
|
|
273
309
|
sim.dispose();
|
|
274
310
|
});
|
|
275
311
|
|
|
276
312
|
test("sequential counter from project directory", () => {
|
|
277
313
|
interface CounterPorts {
|
|
278
|
-
rst:
|
|
279
|
-
en:
|
|
280
|
-
readonly count:
|
|
314
|
+
rst: bigint;
|
|
315
|
+
en: bigint;
|
|
316
|
+
readonly count: bigint;
|
|
281
317
|
}
|
|
282
318
|
|
|
283
319
|
const sim = Simulator.fromProject<CounterPorts>(COUNTER_PROJECT, "Counter");
|
|
284
320
|
|
|
285
|
-
// Reset the counter
|
|
286
|
-
sim.dut.rst =
|
|
321
|
+
// Reset the counter (default async_low: rst=0 is active)
|
|
322
|
+
sim.dut.rst = 0n;
|
|
287
323
|
sim.tick();
|
|
288
|
-
sim.dut.rst =
|
|
324
|
+
sim.dut.rst = 1n;
|
|
289
325
|
sim.tick();
|
|
290
|
-
expect(sim.dut.count).toBe(
|
|
326
|
+
expect(sim.dut.count).toBe(0n);
|
|
291
327
|
|
|
292
328
|
// Enable counting
|
|
293
|
-
sim.dut.en =
|
|
329
|
+
sim.dut.en = 1n;
|
|
294
330
|
sim.tick();
|
|
295
|
-
expect(sim.dut.count).toBe(
|
|
331
|
+
expect(sim.dut.count).toBe(1n);
|
|
296
332
|
|
|
297
333
|
sim.tick();
|
|
298
|
-
expect(sim.dut.count).toBe(
|
|
334
|
+
expect(sim.dut.count).toBe(2n);
|
|
299
335
|
|
|
300
336
|
sim.tick();
|
|
301
|
-
expect(sim.dut.count).toBe(
|
|
337
|
+
expect(sim.dut.count).toBe(3n);
|
|
302
338
|
|
|
303
339
|
sim.dispose();
|
|
304
340
|
});
|
|
@@ -311,9 +347,9 @@ describe("E2E: Simulator.fromProject (event-based)", () => {
|
|
|
311
347
|
describe("E2E: Simulation.fromProject (time-based)", () => {
|
|
312
348
|
test("counter with timed clock from project directory", () => {
|
|
313
349
|
interface CounterPorts {
|
|
314
|
-
rst:
|
|
315
|
-
en:
|
|
316
|
-
readonly count:
|
|
350
|
+
rst: bigint;
|
|
351
|
+
en: bigint;
|
|
352
|
+
readonly count: bigint;
|
|
317
353
|
}
|
|
318
354
|
|
|
319
355
|
const sim = Simulation.fromProject<CounterPorts>(COUNTER_PROJECT, "Counter");
|
|
@@ -321,16 +357,16 @@ describe("E2E: Simulation.fromProject (time-based)", () => {
|
|
|
321
357
|
sim.addClock("clk", { period: 10 });
|
|
322
358
|
expect(sim.time()).toBe(0);
|
|
323
359
|
|
|
324
|
-
// Reset
|
|
325
|
-
sim.dut.rst =
|
|
360
|
+
// Reset (default async_low: rst=0 is active)
|
|
361
|
+
sim.dut.rst = 0n;
|
|
326
362
|
sim.runUntil(20);
|
|
327
|
-
sim.dut.rst =
|
|
328
|
-
sim.dut.en =
|
|
363
|
+
sim.dut.rst = 1n;
|
|
364
|
+
sim.dut.en = 1n;
|
|
329
365
|
|
|
330
366
|
sim.runUntil(100);
|
|
331
367
|
|
|
332
368
|
const count = sim.dut.count;
|
|
333
|
-
expect(count).toBeGreaterThan(
|
|
369
|
+
expect(count).toBeGreaterThan(0n);
|
|
334
370
|
expect(sim.time()).toBe(100);
|
|
335
371
|
|
|
336
372
|
sim.dispose();
|
|
@@ -344,10 +380,10 @@ describe("E2E: Simulation.fromProject (time-based)", () => {
|
|
|
344
380
|
describe("E2E: Simulator.create (backward compat)", () => {
|
|
345
381
|
test("combinational adder via Simulator.create()", () => {
|
|
346
382
|
interface AdderPorts {
|
|
347
|
-
rst:
|
|
348
|
-
a:
|
|
349
|
-
b:
|
|
350
|
-
readonly sum:
|
|
383
|
+
rst: bigint;
|
|
384
|
+
a: bigint;
|
|
385
|
+
b: bigint;
|
|
386
|
+
readonly sum: bigint;
|
|
351
387
|
}
|
|
352
388
|
|
|
353
389
|
const addon = loadNativeAddon();
|
|
@@ -370,10 +406,10 @@ describe("E2E: Simulator.create (backward compat)", () => {
|
|
|
370
406
|
{ __nativeCreate: nativeCreateSimulator },
|
|
371
407
|
);
|
|
372
408
|
|
|
373
|
-
sim.dut.a =
|
|
374
|
-
sim.dut.b =
|
|
409
|
+
sim.dut.a = 100n;
|
|
410
|
+
sim.dut.b = 200n;
|
|
375
411
|
sim.tick();
|
|
376
|
-
expect(sim.dut.sum).toBe(
|
|
412
|
+
expect(sim.dut.sum).toBe(300n);
|
|
377
413
|
|
|
378
414
|
sim.dispose();
|
|
379
415
|
});
|
|
@@ -462,8 +498,8 @@ module InitTest (
|
|
|
462
498
|
|
|
463
499
|
// logic port should have mask=0xFF (all X)
|
|
464
500
|
const [valA, maskA] = readFourState(buf, layout.forDut.a);
|
|
465
|
-
expect(valA).toBe(
|
|
466
|
-
expect(maskA).toBe(
|
|
501
|
+
expect(valA).toBe(0n);
|
|
502
|
+
expect(maskA).toBe(0xFFn);
|
|
467
503
|
|
|
468
504
|
// bit port should have mask=0 (defined)
|
|
469
505
|
// bit is not 4-state, so no mask — reading its value should be 0
|
|
@@ -472,8 +508,8 @@ module InitTest (
|
|
|
472
508
|
|
|
473
509
|
test("writing X clears value and sets mask", () => {
|
|
474
510
|
interface Ports {
|
|
475
|
-
a:
|
|
476
|
-
readonly y_and:
|
|
511
|
+
a: bigint;
|
|
512
|
+
readonly y_and: bigint;
|
|
477
513
|
}
|
|
478
514
|
|
|
479
515
|
const sim = Simulator.fromSource<Ports>(AND_OR_SOURCE, "AndOr", { fourState: true });
|
|
@@ -512,13 +548,13 @@ module InitTest (
|
|
|
512
548
|
|
|
513
549
|
// 0 & X = 0 (mask should be 0 — dominant zero)
|
|
514
550
|
const [vAnd, mAnd] = readFourState(buf, sigYAnd);
|
|
515
|
-
expect(vAnd).toBe(
|
|
516
|
-
expect(mAnd).toBe(
|
|
551
|
+
expect(vAnd).toBe(0n);
|
|
552
|
+
expect(mAnd).toBe(0n);
|
|
517
553
|
|
|
518
554
|
// 0 | X = X (mask should be 1)
|
|
519
555
|
const [vOr, mOr] = readFourState(buf, sigYOr);
|
|
520
|
-
expect(vOr).toBe(
|
|
521
|
-
expect(mOr).toBe(
|
|
556
|
+
expect(vOr).toBe(0n);
|
|
557
|
+
expect(mOr).toBe(1n);
|
|
522
558
|
});
|
|
523
559
|
|
|
524
560
|
test("OR: 1 | X = 1 (dominant one)", () => {
|
|
@@ -543,8 +579,8 @@ module InitTest (
|
|
|
543
579
|
|
|
544
580
|
// 1 | X = 1 (mask should be 0 — dominant one)
|
|
545
581
|
const [vOr, mOr] = readFourState(buf, sigYOr);
|
|
546
|
-
expect(vOr).toBe(
|
|
547
|
-
expect(mOr).toBe(
|
|
582
|
+
expect(vOr).toBe(1n);
|
|
583
|
+
expect(mOr).toBe(0n);
|
|
548
584
|
});
|
|
549
585
|
|
|
550
586
|
test("logic-to-bit assignment strips X mask", () => {
|
|
@@ -582,8 +618,8 @@ module InitTest (
|
|
|
582
618
|
|
|
583
619
|
// y_logic_from_bit should be 0xAA with mask=0
|
|
584
620
|
const [vLogic, mLogic] = readFourState(buf, sigYLogicFromBit);
|
|
585
|
-
expect(vLogic).toBe(
|
|
586
|
-
expect(mLogic).toBe(
|
|
621
|
+
expect(vLogic).toBe(0xAAn);
|
|
622
|
+
expect(mLogic).toBe(0n);
|
|
587
623
|
});
|
|
588
624
|
|
|
589
625
|
test("arithmetic with X produces all-X output", () => {
|
|
@@ -607,7 +643,7 @@ module InitTest (
|
|
|
607
643
|
|
|
608
644
|
// a + X = all-X
|
|
609
645
|
const [, mY] = readFourState(buf, sigY);
|
|
610
|
-
expect(mY).toBe(
|
|
646
|
+
expect(mY).toBe(0xFFn);
|
|
611
647
|
});
|
|
612
648
|
|
|
613
649
|
test("defined inputs in 4-state mode behave like 2-state", () => {
|
|
@@ -630,8 +666,8 @@ module InitTest (
|
|
|
630
666
|
raw.evalComb();
|
|
631
667
|
|
|
632
668
|
const [vY, mY] = readFourState(buf, sigY);
|
|
633
|
-
expect(vY).toBe(
|
|
634
|
-
expect(mY).toBe(
|
|
669
|
+
expect(vY).toBe(155n);
|
|
670
|
+
expect(mY).toBe(0n);
|
|
635
671
|
});
|
|
636
672
|
|
|
637
673
|
test("FF captures X from input, reset clears X", () => {
|
|
@@ -646,8 +682,8 @@ module InitTest (
|
|
|
646
682
|
const sigQ = layout.forDut.q;
|
|
647
683
|
const clkEventId = events.clk;
|
|
648
684
|
|
|
649
|
-
// 1.
|
|
650
|
-
view.setUint8(sigRst.offset,
|
|
685
|
+
// 1. Assert reset (default async_low: rst=0 is active), d=X
|
|
686
|
+
view.setUint8(sigRst.offset, 0);
|
|
651
687
|
view.setUint8(sigRst.offset + sigRst.byteSize, 0); // rst is defined
|
|
652
688
|
|
|
653
689
|
view.setUint8(sigD.offset, 0);
|
|
@@ -657,11 +693,11 @@ module InitTest (
|
|
|
657
693
|
|
|
658
694
|
// After reset, q should be 0 with mask=0
|
|
659
695
|
const [vQ1, mQ1] = readFourState(buf, sigQ);
|
|
660
|
-
expect(vQ1).toBe(
|
|
661
|
-
expect(mQ1).toBe(
|
|
696
|
+
expect(vQ1).toBe(0n);
|
|
697
|
+
expect(mQ1).toBe(0n);
|
|
662
698
|
|
|
663
|
-
// 2. Release reset, d = partial X (value=0xA5, mask=0x0F)
|
|
664
|
-
view.setUint8(sigRst.offset,
|
|
699
|
+
// 2. Release reset (rst=1 is inactive), d = partial X (value=0xA5, mask=0x0F)
|
|
700
|
+
view.setUint8(sigRst.offset, 1);
|
|
665
701
|
view.setUint8(sigRst.offset + sigRst.byteSize, 0);
|
|
666
702
|
|
|
667
703
|
view.setUint8(sigD.offset, 0xA5);
|
|
@@ -671,17 +707,17 @@ module InitTest (
|
|
|
671
707
|
|
|
672
708
|
// FF should capture X mask from d
|
|
673
709
|
const [, mQ2] = readFourState(buf, sigQ);
|
|
674
|
-
expect(mQ2).toBe(
|
|
710
|
+
expect(mQ2).toBe(0x0Fn);
|
|
675
711
|
|
|
676
|
-
// 3.
|
|
677
|
-
view.setUint8(sigRst.offset,
|
|
712
|
+
// 3. Assert reset again (rst=0): should clear X
|
|
713
|
+
view.setUint8(sigRst.offset, 0);
|
|
678
714
|
view.setUint8(sigRst.offset + sigRst.byteSize, 0);
|
|
679
715
|
|
|
680
716
|
raw.tick(clkEventId);
|
|
681
717
|
|
|
682
718
|
const [vQ3, mQ3] = readFourState(buf, sigQ);
|
|
683
|
-
expect(vQ3).toBe(
|
|
684
|
-
expect(mQ3).toBe(
|
|
719
|
+
expect(vQ3).toBe(0n);
|
|
720
|
+
expect(mQ3).toBe(0n);
|
|
685
721
|
});
|
|
686
722
|
|
|
687
723
|
test("FourState write through DUT sets value and mask", () => {
|
|
@@ -698,8 +734,8 @@ module InitTest (
|
|
|
698
734
|
view.setUint8(sigA.offset + sigA.byteSize, 0x0F);
|
|
699
735
|
|
|
700
736
|
const [vA, mA] = readFourState(buf, sigA);
|
|
701
|
-
expect(vA).toBe(
|
|
702
|
-
expect(mA).toBe(
|
|
737
|
+
expect(vA).toBe(0xA5n);
|
|
738
|
+
expect(mA).toBe(0x0Fn);
|
|
703
739
|
});
|
|
704
740
|
|
|
705
741
|
test("setting defined value clears X mask", () => {
|
|
@@ -715,42 +751,42 @@ module InitTest (
|
|
|
715
751
|
view.setUint8(sigA.offset + sigA.byteSize, 0xFF);
|
|
716
752
|
|
|
717
753
|
const [, mBefore] = readFourState(buf, sigA);
|
|
718
|
-
expect(mBefore).toBe(
|
|
754
|
+
expect(mBefore).toBe(0xFFn);
|
|
719
755
|
|
|
720
756
|
// Write a defined value (clear mask)
|
|
721
757
|
view.setUint8(sigA.offset, 42);
|
|
722
758
|
view.setUint8(sigA.offset + sigA.byteSize, 0);
|
|
723
759
|
|
|
724
760
|
const [vAfter, mAfter] = readFourState(buf, sigA);
|
|
725
|
-
expect(vAfter).toBe(
|
|
726
|
-
expect(mAfter).toBe(
|
|
761
|
+
expect(vAfter).toBe(42n);
|
|
762
|
+
expect(mAfter).toBe(0n);
|
|
727
763
|
});
|
|
728
764
|
|
|
729
765
|
test("4-state through DUT high-level API (fromSource with fourState)", () => {
|
|
730
766
|
interface Ports {
|
|
731
|
-
a:
|
|
732
|
-
b:
|
|
733
|
-
readonly y:
|
|
767
|
+
a: bigint;
|
|
768
|
+
b: bigint;
|
|
769
|
+
readonly y: bigint;
|
|
734
770
|
}
|
|
735
771
|
|
|
736
772
|
const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
|
|
737
773
|
|
|
738
774
|
// Write defined values — should behave like 2-state
|
|
739
|
-
sim.dut.a =
|
|
740
|
-
sim.dut.b =
|
|
741
|
-
expect(sim.dut.y).toBe(
|
|
775
|
+
sim.dut.a = 100n;
|
|
776
|
+
sim.dut.b = 55n;
|
|
777
|
+
expect(sim.dut.y).toBe(155n);
|
|
742
778
|
|
|
743
779
|
// Write X to a — output should propagate X (value reads as 0)
|
|
744
780
|
(sim.dut as any).a = X;
|
|
745
781
|
// After writing X, the value part of 'y' is implementation-defined
|
|
746
782
|
// but the read should not throw
|
|
747
783
|
const _yVal = sim.dut.y;
|
|
748
|
-
expect(typeof _yVal).toBe("
|
|
784
|
+
expect(typeof _yVal).toBe("bigint");
|
|
749
785
|
|
|
750
786
|
// Write FourState with partial X
|
|
751
787
|
(sim.dut as any).a = FourState(0xA0, 0x0F);
|
|
752
788
|
const _yVal2 = sim.dut.y;
|
|
753
|
-
expect(typeof _yVal2).toBe("
|
|
789
|
+
expect(typeof _yVal2).toBe("bigint");
|
|
754
790
|
|
|
755
791
|
sim.dispose();
|
|
756
792
|
});
|
|
@@ -763,9 +799,9 @@ module InitTest (
|
|
|
763
799
|
describe("E2E: 4-state high-level DUT API", () => {
|
|
764
800
|
test("counter in 4-state mode: reset clears X, counting works", () => {
|
|
765
801
|
interface CounterPorts {
|
|
766
|
-
rst:
|
|
767
|
-
en:
|
|
768
|
-
readonly count:
|
|
802
|
+
rst: bigint;
|
|
803
|
+
en: bigint;
|
|
804
|
+
readonly count: bigint;
|
|
769
805
|
}
|
|
770
806
|
|
|
771
807
|
const sim = Simulator.fromSource<CounterPorts>(
|
|
@@ -773,22 +809,23 @@ describe("E2E: 4-state high-level DUT API", () => {
|
|
|
773
809
|
);
|
|
774
810
|
|
|
775
811
|
// In 4-state mode, count starts as X. Reset should clear it.
|
|
776
|
-
|
|
812
|
+
// (default async_low: rst=0 is active)
|
|
813
|
+
sim.dut.rst = 0n;
|
|
777
814
|
sim.tick();
|
|
778
|
-
sim.dut.rst =
|
|
815
|
+
sim.dut.rst = 1n;
|
|
779
816
|
sim.tick();
|
|
780
|
-
expect(sim.dut.count).toBe(
|
|
817
|
+
expect(sim.dut.count).toBe(0n);
|
|
781
818
|
|
|
782
819
|
// Enable counting — should work exactly like 2-state
|
|
783
|
-
sim.dut.en =
|
|
820
|
+
sim.dut.en = 1n;
|
|
784
821
|
sim.tick();
|
|
785
|
-
expect(sim.dut.count).toBe(
|
|
822
|
+
expect(sim.dut.count).toBe(1n);
|
|
786
823
|
|
|
787
824
|
sim.tick();
|
|
788
|
-
expect(sim.dut.count).toBe(
|
|
825
|
+
expect(sim.dut.count).toBe(2n);
|
|
789
826
|
|
|
790
827
|
sim.tick();
|
|
791
|
-
expect(sim.dut.count).toBe(
|
|
828
|
+
expect(sim.dut.count).toBe(3n);
|
|
792
829
|
|
|
793
830
|
sim.dispose();
|
|
794
831
|
});
|
|
@@ -815,64 +852,64 @@ describe("E2E: 4-state high-level DUT API", () => {
|
|
|
815
852
|
raw.evalComb();
|
|
816
853
|
|
|
817
854
|
// With X selector, output should be all-X
|
|
818
|
-
const [,
|
|
819
|
-
expect(
|
|
855
|
+
const [, mY2] = readFourState(buf, sigY);
|
|
856
|
+
expect(mY2).toBe(0xFFn);
|
|
820
857
|
|
|
821
858
|
raw.dispose();
|
|
822
859
|
});
|
|
823
860
|
|
|
824
861
|
test("FF via DUT API: write X input, tick, read output", () => {
|
|
825
862
|
interface FFPorts {
|
|
826
|
-
rst:
|
|
827
|
-
d:
|
|
828
|
-
readonly q:
|
|
863
|
+
rst: bigint;
|
|
864
|
+
d: bigint;
|
|
865
|
+
readonly q: bigint;
|
|
829
866
|
}
|
|
830
867
|
|
|
831
868
|
const sim = Simulator.fromSource<FFPorts>(FF_SOURCE, "FF", { fourState: true });
|
|
832
869
|
|
|
833
|
-
// Reset to clear initial X
|
|
834
|
-
sim.dut.rst =
|
|
870
|
+
// Reset to clear initial X (default async_low: rst=0 is active)
|
|
871
|
+
sim.dut.rst = 0n;
|
|
835
872
|
sim.tick();
|
|
836
|
-
sim.dut.rst =
|
|
837
|
-
expect(sim.dut.q).toBe(
|
|
873
|
+
sim.dut.rst = 1n;
|
|
874
|
+
expect(sim.dut.q).toBe(0n);
|
|
838
875
|
|
|
839
876
|
// Write a defined value
|
|
840
|
-
sim.dut.d =
|
|
877
|
+
sim.dut.d = 0x42n;
|
|
841
878
|
sim.tick();
|
|
842
|
-
expect(sim.dut.q).toBe(
|
|
879
|
+
expect(sim.dut.q).toBe(0x42n);
|
|
843
880
|
|
|
844
|
-
// Write X to d, tick — q should capture it (value read still returns a
|
|
881
|
+
// Write X to d, tick — q should capture it (value read still returns a bigint)
|
|
845
882
|
(sim.dut as any).d = X;
|
|
846
883
|
sim.tick();
|
|
847
|
-
expect(typeof sim.dut.q).toBe("
|
|
884
|
+
expect(typeof sim.dut.q).toBe("bigint");
|
|
848
885
|
|
|
849
886
|
// Write defined value again — q should recover
|
|
850
|
-
sim.dut.d =
|
|
887
|
+
sim.dut.d = 0x99n;
|
|
851
888
|
sim.tick();
|
|
852
|
-
expect(sim.dut.q).toBe(
|
|
889
|
+
expect(sim.dut.q).toBe(0x99n);
|
|
853
890
|
|
|
854
891
|
sim.dispose();
|
|
855
892
|
});
|
|
856
893
|
|
|
857
894
|
test("X to defined transition: adder recovers from X", () => {
|
|
858
895
|
interface Ports {
|
|
859
|
-
a:
|
|
860
|
-
b:
|
|
861
|
-
readonly y:
|
|
896
|
+
a: bigint;
|
|
897
|
+
b: bigint;
|
|
898
|
+
readonly y: bigint;
|
|
862
899
|
}
|
|
863
900
|
|
|
864
901
|
const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
|
|
865
902
|
|
|
866
903
|
// Start with X
|
|
867
904
|
(sim.dut as any).a = X;
|
|
868
|
-
sim.dut.b =
|
|
905
|
+
sim.dut.b = 10n;
|
|
869
906
|
// Output has X — just verify it doesn't crash
|
|
870
|
-
expect(typeof sim.dut.y).toBe("
|
|
907
|
+
expect(typeof sim.dut.y).toBe("bigint");
|
|
871
908
|
|
|
872
909
|
// Clear X by writing defined values
|
|
873
|
-
sim.dut.a =
|
|
874
|
-
sim.dut.b =
|
|
875
|
-
expect(sim.dut.y).toBe(
|
|
910
|
+
sim.dut.a = 20n;
|
|
911
|
+
sim.dut.b = 30n;
|
|
912
|
+
expect(sim.dut.y).toBe(50n);
|
|
876
913
|
|
|
877
914
|
sim.dispose();
|
|
878
915
|
});
|
|
@@ -885,9 +922,9 @@ describe("E2E: 4-state high-level DUT API", () => {
|
|
|
885
922
|
describe("E2E: 4-state Simulation (time-based)", () => {
|
|
886
923
|
test("FF with clock-driven 4-state: reset clears X, captures defined values", () => {
|
|
887
924
|
interface FFPorts {
|
|
888
|
-
rst:
|
|
889
|
-
d:
|
|
890
|
-
readonly q:
|
|
925
|
+
rst: bigint;
|
|
926
|
+
d: bigint;
|
|
927
|
+
readonly q: bigint;
|
|
891
928
|
}
|
|
892
929
|
|
|
893
930
|
const sim = Simulation.fromSource<FFPorts>(FF_SOURCE, "FF", { fourState: true });
|
|
@@ -895,30 +932,30 @@ describe("E2E: 4-state Simulation (time-based)", () => {
|
|
|
895
932
|
sim.addClock("clk", { period: 10 });
|
|
896
933
|
expect(sim.time()).toBe(0);
|
|
897
934
|
|
|
898
|
-
// Reset to clear initial X on q
|
|
899
|
-
sim.dut.rst =
|
|
935
|
+
// Reset to clear initial X on q (default async_low: rst=0 is active)
|
|
936
|
+
sim.dut.rst = 0n;
|
|
900
937
|
sim.runUntil(20);
|
|
901
|
-
sim.dut.rst =
|
|
902
|
-
expect(sim.dut.q).toBe(
|
|
938
|
+
sim.dut.rst = 1n;
|
|
939
|
+
expect(sim.dut.q).toBe(0n);
|
|
903
940
|
|
|
904
941
|
// Drive d with defined value
|
|
905
|
-
sim.dut.d =
|
|
942
|
+
sim.dut.d = 0x55n;
|
|
906
943
|
sim.runUntil(40);
|
|
907
|
-
expect(sim.dut.q).toBe(
|
|
944
|
+
expect(sim.dut.q).toBe(0x55n);
|
|
908
945
|
|
|
909
946
|
// Drive d with different value
|
|
910
|
-
sim.dut.d =
|
|
947
|
+
sim.dut.d = 0xAAn;
|
|
911
948
|
sim.runUntil(60);
|
|
912
|
-
expect(sim.dut.q).toBe(
|
|
949
|
+
expect(sim.dut.q).toBe(0xAAn);
|
|
913
950
|
|
|
914
951
|
sim.dispose();
|
|
915
952
|
});
|
|
916
953
|
|
|
917
954
|
test("counter in 4-state time-based mode", () => {
|
|
918
955
|
interface CounterPorts {
|
|
919
|
-
rst:
|
|
920
|
-
en:
|
|
921
|
-
readonly count:
|
|
956
|
+
rst: bigint;
|
|
957
|
+
en: bigint;
|
|
958
|
+
readonly count: bigint;
|
|
922
959
|
}
|
|
923
960
|
|
|
924
961
|
const sim = Simulation.fromSource<CounterPorts>(
|
|
@@ -927,16 +964,16 @@ describe("E2E: 4-state Simulation (time-based)", () => {
|
|
|
927
964
|
|
|
928
965
|
sim.addClock("clk", { period: 10 });
|
|
929
966
|
|
|
930
|
-
// Reset
|
|
931
|
-
sim.dut.rst =
|
|
967
|
+
// Reset (default async_low: rst=0 is active)
|
|
968
|
+
sim.dut.rst = 0n;
|
|
932
969
|
sim.runUntil(20);
|
|
933
|
-
sim.dut.rst =
|
|
934
|
-
sim.dut.en =
|
|
970
|
+
sim.dut.rst = 1n;
|
|
971
|
+
sim.dut.en = 1n;
|
|
935
972
|
|
|
936
973
|
sim.runUntil(100);
|
|
937
974
|
|
|
938
975
|
const count = sim.dut.count;
|
|
939
|
-
expect(count).toBeGreaterThan(
|
|
976
|
+
expect(count).toBeGreaterThan(0n);
|
|
940
977
|
expect(sim.time()).toBe(100);
|
|
941
978
|
|
|
942
979
|
sim.dispose();
|
|
@@ -944,9 +981,9 @@ describe("E2E: 4-state Simulation (time-based)", () => {
|
|
|
944
981
|
|
|
945
982
|
test("4-state combinational in time-based simulation", () => {
|
|
946
983
|
interface Ports {
|
|
947
|
-
a:
|
|
948
|
-
b:
|
|
949
|
-
readonly y:
|
|
984
|
+
a: bigint;
|
|
985
|
+
b: bigint;
|
|
986
|
+
readonly y: bigint;
|
|
950
987
|
}
|
|
951
988
|
|
|
952
989
|
const sim = Simulation.fromSource<Ports>(
|
|
@@ -954,12 +991,412 @@ describe("E2E: 4-state Simulation (time-based)", () => {
|
|
|
954
991
|
);
|
|
955
992
|
|
|
956
993
|
// No clock needed for pure combinational — just set values and read
|
|
957
|
-
sim.dut.a =
|
|
958
|
-
sim.dut.b =
|
|
994
|
+
sim.dut.a = 100n;
|
|
995
|
+
sim.dut.b = 55n;
|
|
959
996
|
// runUntil(0) to force eval
|
|
960
997
|
sim.runUntil(0);
|
|
961
|
-
expect(sim.dut.y).toBe(
|
|
998
|
+
expect(sim.dut.y).toBe(155n);
|
|
999
|
+
|
|
1000
|
+
sim.dispose();
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
// ---------------------------------------------------------------------------
|
|
1005
|
+
// Phase 3b: testbench helpers — Simulation API
|
|
1006
|
+
// ---------------------------------------------------------------------------
|
|
1007
|
+
|
|
1008
|
+
describe("E2E: Simulation testbench helpers", () => {
|
|
1009
|
+
test("waitForCycles: advances correct number of clock cycles", () => {
|
|
1010
|
+
interface CounterPorts {
|
|
1011
|
+
rst: bigint;
|
|
1012
|
+
en: bigint;
|
|
1013
|
+
readonly count: bigint;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
1017
|
+
sim.addClock("clk", { period: 10 });
|
|
1018
|
+
|
|
1019
|
+
// Reset (default async_low: rst=0 is active)
|
|
1020
|
+
sim.dut.rst = 0n;
|
|
1021
|
+
sim.runUntil(20);
|
|
1022
|
+
sim.dut.rst = 1n;
|
|
1023
|
+
sim.dut.en = 1n;
|
|
1024
|
+
|
|
1025
|
+
const beforeTime = sim.time();
|
|
1026
|
+
const afterTime = sim.waitForCycles("clk", 5);
|
|
1027
|
+
|
|
1028
|
+
expect(afterTime).toBeGreaterThan(beforeTime);
|
|
1029
|
+
// Each cycle = 2 steps with period 10, so 5 cycles ≈ 50 time units
|
|
1030
|
+
expect(afterTime - beforeTime).toBe(50);
|
|
1031
|
+
|
|
1032
|
+
sim.dispose();
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
test("waitUntil: waits for condition to be met", () => {
|
|
1036
|
+
interface CounterPorts {
|
|
1037
|
+
rst: bigint;
|
|
1038
|
+
en: bigint;
|
|
1039
|
+
readonly count: bigint;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
1043
|
+
sim.addClock("clk", { period: 10 });
|
|
1044
|
+
|
|
1045
|
+
// Reset (default async_low: rst=0 is active)
|
|
1046
|
+
sim.dut.rst = 0n;
|
|
1047
|
+
sim.runUntil(20);
|
|
1048
|
+
sim.dut.rst = 1n;
|
|
1049
|
+
sim.dut.en = 1n;
|
|
1050
|
+
|
|
1051
|
+
const t = sim.waitUntil(() => sim.dut.count >= 3n);
|
|
1052
|
+
expect(sim.dut.count).toBeGreaterThanOrEqual(3n);
|
|
1053
|
+
expect(t).toBeGreaterThan(20);
|
|
1054
|
+
|
|
1055
|
+
sim.dispose();
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
test("waitUntil: throws SimulationTimeoutError on timeout", () => {
|
|
1059
|
+
interface CounterPorts {
|
|
1060
|
+
rst: bigint;
|
|
1061
|
+
en: bigint;
|
|
1062
|
+
readonly count: bigint;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
1066
|
+
sim.addClock("clk", { period: 10 });
|
|
1067
|
+
|
|
1068
|
+
// Reset (default async_low: rst=0 is active)
|
|
1069
|
+
sim.dut.rst = 0n;
|
|
1070
|
+
sim.runUntil(20);
|
|
1071
|
+
sim.dut.rst = 1n;
|
|
1072
|
+
sim.dut.en = 0n; // disabled — count won't increase
|
|
1073
|
+
|
|
1074
|
+
expect(() =>
|
|
1075
|
+
sim.waitUntil(() => sim.dut.count >= 100n, { maxSteps: 20 }),
|
|
1076
|
+
).toThrow(SimulationTimeoutError);
|
|
962
1077
|
|
|
963
1078
|
sim.dispose();
|
|
964
1079
|
});
|
|
1080
|
+
|
|
1081
|
+
test("reset: asserts and releases reset on counter", () => {
|
|
1082
|
+
interface CounterPorts {
|
|
1083
|
+
rst: bigint;
|
|
1084
|
+
en: bigint;
|
|
1085
|
+
readonly count: bigint;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
1089
|
+
sim.addClock("clk", { period: 10 });
|
|
1090
|
+
|
|
1091
|
+
// Count up a bit (default async_low: rst=0 is active)
|
|
1092
|
+
sim.dut.rst = 0n;
|
|
1093
|
+
sim.runUntil(20);
|
|
1094
|
+
sim.dut.rst = 1n;
|
|
1095
|
+
sim.dut.en = 1n;
|
|
1096
|
+
sim.runUntil(100);
|
|
1097
|
+
expect(sim.dut.count).toBeGreaterThan(0n);
|
|
1098
|
+
|
|
1099
|
+
// Reset using the helper
|
|
1100
|
+
sim.reset("rst");
|
|
1101
|
+
expect(sim.dut.count).toBe(0n);
|
|
1102
|
+
|
|
1103
|
+
sim.dispose();
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
test("reset: explicit async_low resetType activates with 0", () => {
|
|
1107
|
+
interface CounterPorts {
|
|
1108
|
+
rst: bigint;
|
|
1109
|
+
en: bigint;
|
|
1110
|
+
readonly count: bigint;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
|
|
1114
|
+
resetType: "async_low",
|
|
1115
|
+
});
|
|
1116
|
+
sim.addClock("clk", { period: 10 });
|
|
1117
|
+
|
|
1118
|
+
// With async_low, rst=0 is active, rst=1 is inactive
|
|
1119
|
+
sim.reset("rst");
|
|
1120
|
+
|
|
1121
|
+
sim.dut.en = 1n;
|
|
1122
|
+
sim.runUntil(100);
|
|
1123
|
+
expect(sim.dut.count).toBeGreaterThan(0n);
|
|
1124
|
+
|
|
1125
|
+
// Reset again using helper, verify it resets the counter
|
|
1126
|
+
sim.reset("rst");
|
|
1127
|
+
expect(sim.dut.count).toBe(0n);
|
|
1128
|
+
|
|
1129
|
+
sim.dispose();
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
test("reset: explicit async_high resetType activates with 1", () => {
|
|
1133
|
+
interface CounterPorts {
|
|
1134
|
+
rst: bigint;
|
|
1135
|
+
en: bigint;
|
|
1136
|
+
readonly count: bigint;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
|
|
1140
|
+
resetType: "async_high",
|
|
1141
|
+
});
|
|
1142
|
+
sim.addClock("clk", { period: 10 });
|
|
1143
|
+
|
|
1144
|
+
// With async_high, rst=1 is active, rst=0 is inactive
|
|
1145
|
+
sim.reset("rst");
|
|
1146
|
+
|
|
1147
|
+
sim.dut.en = 1n;
|
|
1148
|
+
sim.runUntil(100);
|
|
1149
|
+
expect(sim.dut.count).toBeGreaterThan(0n);
|
|
1150
|
+
|
|
1151
|
+
// Reset again
|
|
1152
|
+
sim.reset("rst");
|
|
1153
|
+
expect(sim.dut.count).toBe(0n);
|
|
1154
|
+
|
|
1155
|
+
sim.dispose();
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
test("Simulator.fromSource: resetType option works", () => {
|
|
1159
|
+
interface CounterPorts {
|
|
1160
|
+
rst: bigint;
|
|
1161
|
+
en: bigint;
|
|
1162
|
+
readonly count: bigint;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
|
|
1166
|
+
resetType: "async_high",
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
// With async_high, assert reset with 1
|
|
1170
|
+
sim.dut.rst = 1n;
|
|
1171
|
+
sim.tick();
|
|
1172
|
+
sim.dut.rst = 0n;
|
|
1173
|
+
sim.tick();
|
|
1174
|
+
expect(sim.dut.count).toBe(0n);
|
|
1175
|
+
|
|
1176
|
+
// Count up
|
|
1177
|
+
sim.dut.en = 1n;
|
|
1178
|
+
sim.tick();
|
|
1179
|
+
expect(sim.dut.count).toBe(1n);
|
|
1180
|
+
|
|
1181
|
+
sim.dispose();
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
test("runUntil with maxSteps: succeeds within budget", () => {
|
|
1185
|
+
interface CounterPorts {
|
|
1186
|
+
rst: bigint;
|
|
1187
|
+
en: bigint;
|
|
1188
|
+
readonly count: bigint;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
1192
|
+
sim.addClock("clk", { period: 10 });
|
|
1193
|
+
|
|
1194
|
+
// Reset (default async_low: rst=0 is active)
|
|
1195
|
+
sim.dut.rst = 0n;
|
|
1196
|
+
sim.runUntil(20);
|
|
1197
|
+
sim.dut.rst = 1n;
|
|
1198
|
+
sim.dut.en = 1n;
|
|
1199
|
+
|
|
1200
|
+
// 100 time units with period 10 = 10 events, should fit in 100 steps
|
|
1201
|
+
sim.runUntil(120, { maxSteps: 100 });
|
|
1202
|
+
expect(sim.time()).toBe(120);
|
|
1203
|
+
|
|
1204
|
+
sim.dispose();
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
test("runUntil with maxSteps: throws on exceeded budget", () => {
|
|
1208
|
+
interface CounterPorts {
|
|
1209
|
+
rst: bigint;
|
|
1210
|
+
en: bigint;
|
|
1211
|
+
readonly count: bigint;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
1215
|
+
sim.addClock("clk", { period: 10 });
|
|
1216
|
+
|
|
1217
|
+
// Release reset so counter can count (default async_low: rst=1 is inactive)
|
|
1218
|
+
sim.dut.rst = 1n;
|
|
1219
|
+
sim.dut.en = 1n;
|
|
1220
|
+
|
|
1221
|
+
// Very small budget for a long run
|
|
1222
|
+
expect(() => sim.runUntil(100000, { maxSteps: 5 })).toThrow(
|
|
1223
|
+
SimulationTimeoutError,
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
sim.dispose();
|
|
1227
|
+
});
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
// ---------------------------------------------------------------------------
|
|
1231
|
+
// Phase 3b: fourState() method
|
|
1232
|
+
// ---------------------------------------------------------------------------
|
|
1233
|
+
|
|
1234
|
+
describe("E2E: fourState() method", () => {
|
|
1235
|
+
test("Simulator.fourState: reads 4-state value and mask", () => {
|
|
1236
|
+
interface Ports {
|
|
1237
|
+
a: bigint;
|
|
1238
|
+
b: bigint;
|
|
1239
|
+
readonly y: bigint;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
|
|
1243
|
+
fourState: true,
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
sim.dut.a = 100n;
|
|
1247
|
+
sim.dut.b = 55n;
|
|
1248
|
+
// Trigger evalComb via output read (Adder4S is purely combinational)
|
|
1249
|
+
expect(sim.dut.y).toBe(155n);
|
|
1250
|
+
|
|
1251
|
+
const fs = sim.fourState("y");
|
|
1252
|
+
expect(fs.__fourState).toBe(true);
|
|
1253
|
+
expect(fs.value).toBe(155n);
|
|
1254
|
+
expect(fs.mask).toBe(0n);
|
|
1255
|
+
|
|
1256
|
+
sim.dispose();
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
test("Simulator.fourState: reads X mask when input is X", () => {
|
|
1260
|
+
interface Ports {
|
|
1261
|
+
a: bigint;
|
|
1262
|
+
b: bigint;
|
|
1263
|
+
readonly y: bigint;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
|
|
1267
|
+
fourState: true,
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
(sim.dut as any).a = X;
|
|
1271
|
+
sim.dut.b = 10n;
|
|
1272
|
+
// Trigger evalComb via output read
|
|
1273
|
+
sim.dut.y;
|
|
1274
|
+
|
|
1275
|
+
const fs = sim.fourState("y");
|
|
1276
|
+
expect(fs.mask).toBe(0xFFn); // all X from arithmetic propagation
|
|
1277
|
+
|
|
1278
|
+
sim.dispose();
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
test("Simulation.fourState: reads 4-state value", () => {
|
|
1282
|
+
interface Ports {
|
|
1283
|
+
a: bigint;
|
|
1284
|
+
b: bigint;
|
|
1285
|
+
readonly y: bigint;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const sim = Simulation.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
|
|
1289
|
+
fourState: true,
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
sim.dut.a = 50n;
|
|
1293
|
+
sim.dut.b = 25n;
|
|
1294
|
+
sim.runUntil(0);
|
|
1295
|
+
|
|
1296
|
+
const fs = sim.fourState("y");
|
|
1297
|
+
expect(fs.value).toBe(75n);
|
|
1298
|
+
expect(fs.mask).toBe(0n);
|
|
1299
|
+
|
|
1300
|
+
sim.dispose();
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
test("fourState: throws for unknown port", () => {
|
|
1304
|
+
interface Ports {
|
|
1305
|
+
a: bigint;
|
|
1306
|
+
b: bigint;
|
|
1307
|
+
readonly y: bigint;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
|
|
1311
|
+
fourState: true,
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
expect(() => sim.fourState("nonexistent")).toThrow("Unknown port");
|
|
1315
|
+
|
|
1316
|
+
sim.dispose();
|
|
1317
|
+
});
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
// ---------------------------------------------------------------------------
|
|
1321
|
+
// Phase 3b: optimize flag
|
|
1322
|
+
// ---------------------------------------------------------------------------
|
|
1323
|
+
|
|
1324
|
+
describe("E2E: optimize flag", () => {
|
|
1325
|
+
test("Simulator.fromSource with optimize: true", () => {
|
|
1326
|
+
interface AdderPorts {
|
|
1327
|
+
rst: bigint;
|
|
1328
|
+
a: bigint;
|
|
1329
|
+
b: bigint;
|
|
1330
|
+
readonly sum: bigint;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder", {
|
|
1334
|
+
optimize: true,
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
sim.dut.a = 100n;
|
|
1338
|
+
sim.dut.b = 200n;
|
|
1339
|
+
sim.tick();
|
|
1340
|
+
expect(sim.dut.sum).toBe(300n);
|
|
1341
|
+
|
|
1342
|
+
sim.dispose();
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
test("Simulation.fromSource with optimize: true", () => {
|
|
1346
|
+
interface CounterPorts {
|
|
1347
|
+
rst: bigint;
|
|
1348
|
+
en: bigint;
|
|
1349
|
+
readonly count: bigint;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
|
|
1353
|
+
optimize: true,
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
sim.addClock("clk", { period: 10 });
|
|
1357
|
+
|
|
1358
|
+
// Reset (default async_low: rst=0 is active)
|
|
1359
|
+
sim.dut.rst = 0n;
|
|
1360
|
+
sim.runUntil(20);
|
|
1361
|
+
sim.dut.rst = 1n;
|
|
1362
|
+
sim.dut.en = 1n;
|
|
1363
|
+
sim.runUntil(100);
|
|
1364
|
+
|
|
1365
|
+
expect(sim.dut.count).toBeGreaterThan(0n);
|
|
1366
|
+
|
|
1367
|
+
sim.dispose();
|
|
1368
|
+
});
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
// ---------------------------------------------------------------------------
|
|
1372
|
+
// parseSignalPath unit tests
|
|
1373
|
+
// ---------------------------------------------------------------------------
|
|
1374
|
+
|
|
1375
|
+
describe("parseSignalPath", () => {
|
|
1376
|
+
test("simple variable path", () => {
|
|
1377
|
+
const result = parseSignalPath("v");
|
|
1378
|
+
expect(result.instancePath).toEqual([]);
|
|
1379
|
+
expect(result.varPath).toEqual(["v"]);
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
test("instance:variable split", () => {
|
|
1383
|
+
const result = parseSignalPath("p2:i");
|
|
1384
|
+
expect(result.instancePath).toEqual([{ name: "p2", index: 0 }]);
|
|
1385
|
+
expect(result.varPath).toEqual(["i"]);
|
|
1386
|
+
});
|
|
1387
|
+
|
|
1388
|
+
test("nested instance with array index", () => {
|
|
1389
|
+
const result = parseSignalPath("a.b[3]:x.y");
|
|
1390
|
+
expect(result.instancePath).toEqual([
|
|
1391
|
+
{ name: "a", index: 0 },
|
|
1392
|
+
{ name: "b", index: 3 },
|
|
1393
|
+
]);
|
|
1394
|
+
expect(result.varPath).toEqual(["x", "y"]);
|
|
1395
|
+
});
|
|
1396
|
+
|
|
1397
|
+
test("dotted variable path without instance", () => {
|
|
1398
|
+
const result = parseSignalPath("foo.bar.baz");
|
|
1399
|
+
expect(result.instancePath).toEqual([]);
|
|
1400
|
+
expect(result.varPath).toEqual(["foo", "bar", "baz"]);
|
|
1401
|
+
});
|
|
965
1402
|
});
|