@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/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: number;
100
- a: number;
101
- b: number;
102
- readonly sum: number;
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 = 100;
108
- sim.dut.b = 200;
108
+ sim.dut.a = 100n;
109
+ sim.dut.b = 200n;
109
110
  sim.tick();
110
- expect(sim.dut.sum).toBe(300);
111
+ expect(sim.dut.sum).toBe(300n);
111
112
 
112
- sim.dut.a = 0xFFFF;
113
- sim.dut.b = 1;
113
+ sim.dut.a = 0xFFFFn;
114
+ sim.dut.b = 1n;
114
115
  sim.tick();
115
- expect(sim.dut.sum).toBe(0x10000);
116
+ expect(sim.dut.sum).toBe(0x10000n);
116
117
 
117
- sim.dut.a = 0;
118
- sim.dut.b = 0;
118
+ sim.dut.a = 0n;
119
+ sim.dut.b = 0n;
119
120
  sim.tick();
120
- expect(sim.dut.sum).toBe(0);
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: number;
128
- a: number;
129
- b: number;
130
- readonly sum: number;
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 = 42;
136
- sim.dut.b = 58;
137
- expect(sim.dut.sum).toBe(100);
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: number;
145
- en: number;
146
- readonly count: number;
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 = 1;
152
+ // Reset the counter (default async_low: rst=0 is active)
153
+ sim.dut.rst = 0n;
153
154
  sim.tick();
154
- sim.dut.rst = 0;
155
+ sim.dut.rst = 1n;
155
156
  sim.tick();
156
- expect(sim.dut.count).toBe(0);
157
+ expect(sim.dut.count).toBe(0n);
157
158
 
158
159
  // Enable counting
159
- sim.dut.en = 1;
160
+ sim.dut.en = 1n;
160
161
  sim.tick();
161
- expect(sim.dut.count).toBe(1);
162
+ expect(sim.dut.count).toBe(1n);
162
163
 
163
164
  sim.tick();
164
- expect(sim.dut.count).toBe(2);
165
+ expect(sim.dut.count).toBe(2n);
165
166
 
166
167
  sim.tick();
167
- expect(sim.dut.count).toBe(3);
168
+ expect(sim.dut.count).toBe(3n);
168
169
 
169
170
  // Disable counting
170
- sim.dut.en = 0;
171
+ sim.dut.en = 0n;
171
172
  sim.tick();
172
- expect(sim.dut.count).toBe(3);
173
+ expect(sim.dut.count).toBe(3n);
173
174
 
174
175
  // Re-enable
175
- sim.dut.en = 1;
176
+ sim.dut.en = 1n;
176
177
  sim.tick(5);
177
- expect(sim.dut.count).toBe(8);
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: number;
185
- d0: number;
186
- d1: number;
187
- d2: number;
188
- d3: number;
189
- readonly y: number;
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 = 0xAA;
195
- sim.dut.d1 = 0xBB;
196
- sim.dut.d2 = 0xCC;
197
- sim.dut.d3 = 0xDD;
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 = 0;
200
- expect(sim.dut.y).toBe(0xAA);
235
+ sim.dut.sel = 0n;
236
+ expect(sim.dut.y).toBe(0xAAn);
201
237
 
202
- sim.dut.sel = 1;
203
- expect(sim.dut.y).toBe(0xBB);
238
+ sim.dut.sel = 1n;
239
+ expect(sim.dut.y).toBe(0xBBn);
204
240
 
205
- sim.dut.sel = 2;
206
- expect(sim.dut.y).toBe(0xCC);
241
+ sim.dut.sel = 2n;
242
+ expect(sim.dut.y).toBe(0xCCn);
207
243
 
208
- sim.dut.sel = 3;
209
- expect(sim.dut.y).toBe(0xDD);
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: number;
223
- en: number;
224
- readonly count: number;
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 = 1;
268
+ // Reset (default async_low: rst=0 is active)
269
+ sim.dut.rst = 0n;
234
270
  sim.runUntil(20);
235
- sim.dut.rst = 0;
236
- sim.dut.en = 1;
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(0);
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: number;
256
- a: number;
257
- b: number;
258
- readonly sum: number;
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 = 100;
264
- sim.dut.b = 200;
299
+ sim.dut.a = 100n;
300
+ sim.dut.b = 200n;
265
301
  sim.tick();
266
- expect(sim.dut.sum).toBe(300);
302
+ expect(sim.dut.sum).toBe(300n);
267
303
 
268
- sim.dut.a = 0xFFFF;
269
- sim.dut.b = 1;
304
+ sim.dut.a = 0xFFFFn;
305
+ sim.dut.b = 1n;
270
306
  sim.tick();
271
- expect(sim.dut.sum).toBe(0x10000);
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: number;
279
- en: number;
280
- readonly count: number;
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 = 1;
321
+ // Reset the counter (default async_low: rst=0 is active)
322
+ sim.dut.rst = 0n;
287
323
  sim.tick();
288
- sim.dut.rst = 0;
324
+ sim.dut.rst = 1n;
289
325
  sim.tick();
290
- expect(sim.dut.count).toBe(0);
326
+ expect(sim.dut.count).toBe(0n);
291
327
 
292
328
  // Enable counting
293
- sim.dut.en = 1;
329
+ sim.dut.en = 1n;
294
330
  sim.tick();
295
- expect(sim.dut.count).toBe(1);
331
+ expect(sim.dut.count).toBe(1n);
296
332
 
297
333
  sim.tick();
298
- expect(sim.dut.count).toBe(2);
334
+ expect(sim.dut.count).toBe(2n);
299
335
 
300
336
  sim.tick();
301
- expect(sim.dut.count).toBe(3);
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: number;
315
- en: number;
316
- readonly count: number;
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 = 1;
360
+ // Reset (default async_low: rst=0 is active)
361
+ sim.dut.rst = 0n;
326
362
  sim.runUntil(20);
327
- sim.dut.rst = 0;
328
- sim.dut.en = 1;
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(0);
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: number;
348
- a: number;
349
- b: number;
350
- readonly sum: number;
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 = 100;
374
- sim.dut.b = 200;
409
+ sim.dut.a = 100n;
410
+ sim.dut.b = 200n;
375
411
  sim.tick();
376
- expect(sim.dut.sum).toBe(300);
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(0);
466
- expect(maskA).toBe(0xFF);
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: number;
476
- readonly y_and: number;
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(0);
516
- expect(mAnd).toBe(0);
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(0);
521
- expect(mOr).toBe(1);
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(1);
547
- expect(mOr).toBe(0);
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(0xAA);
586
- expect(mLogic).toBe(0);
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(0xFF);
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(155);
634
- expect(mY).toBe(0);
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. Reset: rst=1, d=X
650
- view.setUint8(sigRst.offset, 1);
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(0);
661
- expect(mQ1).toBe(0);
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, 0);
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(0x0F);
710
+ expect(mQ2).toBe(0x0Fn);
675
711
 
676
- // 3. Reset again: should clear X
677
- view.setUint8(sigRst.offset, 1);
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(0);
684
- expect(mQ3).toBe(0);
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(0xA5);
702
- expect(mA).toBe(0x0F);
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(0xFF);
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(42);
726
- expect(mAfter).toBe(0);
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: number;
732
- b: number;
733
- readonly y: number;
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 = 100;
740
- sim.dut.b = 55;
741
- expect(sim.dut.y).toBe(155);
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("number");
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("number");
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: number;
767
- en: number;
768
- readonly count: number;
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
- sim.dut.rst = 1;
812
+ // (default async_low: rst=0 is active)
813
+ sim.dut.rst = 0n;
777
814
  sim.tick();
778
- sim.dut.rst = 0;
815
+ sim.dut.rst = 1n;
779
816
  sim.tick();
780
- expect(sim.dut.count).toBe(0);
817
+ expect(sim.dut.count).toBe(0n);
781
818
 
782
819
  // Enable counting — should work exactly like 2-state
783
- sim.dut.en = 1;
820
+ sim.dut.en = 1n;
784
821
  sim.tick();
785
- expect(sim.dut.count).toBe(1);
822
+ expect(sim.dut.count).toBe(1n);
786
823
 
787
824
  sim.tick();
788
- expect(sim.dut.count).toBe(2);
825
+ expect(sim.dut.count).toBe(2n);
789
826
 
790
827
  sim.tick();
791
- expect(sim.dut.count).toBe(3);
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 [, mY] = readFourState(buf, sigY);
819
- expect(mY).toBe(0xFF);
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: number;
827
- d: number;
828
- readonly q: number;
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 = 1;
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 = 0;
837
- expect(sim.dut.q).toBe(0);
873
+ sim.dut.rst = 1n;
874
+ expect(sim.dut.q).toBe(0n);
838
875
 
839
876
  // Write a defined value
840
- sim.dut.d = 0x42;
877
+ sim.dut.d = 0x42n;
841
878
  sim.tick();
842
- expect(sim.dut.q).toBe(0x42);
879
+ expect(sim.dut.q).toBe(0x42n);
843
880
 
844
- // Write X to d, tick — q should capture it (value read still returns a number)
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("number");
884
+ expect(typeof sim.dut.q).toBe("bigint");
848
885
 
849
886
  // Write defined value again — q should recover
850
- sim.dut.d = 0x99;
887
+ sim.dut.d = 0x99n;
851
888
  sim.tick();
852
- expect(sim.dut.q).toBe(0x99);
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: number;
860
- b: number;
861
- readonly y: number;
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 = 10;
905
+ sim.dut.b = 10n;
869
906
  // Output has X — just verify it doesn't crash
870
- expect(typeof sim.dut.y).toBe("number");
907
+ expect(typeof sim.dut.y).toBe("bigint");
871
908
 
872
909
  // Clear X by writing defined values
873
- sim.dut.a = 20;
874
- sim.dut.b = 30;
875
- expect(sim.dut.y).toBe(50);
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: number;
889
- d: number;
890
- readonly q: number;
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 = 1;
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 = 0;
902
- expect(sim.dut.q).toBe(0);
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 = 0x55;
942
+ sim.dut.d = 0x55n;
906
943
  sim.runUntil(40);
907
- expect(sim.dut.q).toBe(0x55);
944
+ expect(sim.dut.q).toBe(0x55n);
908
945
 
909
946
  // Drive d with different value
910
- sim.dut.d = 0xAA;
947
+ sim.dut.d = 0xAAn;
911
948
  sim.runUntil(60);
912
- expect(sim.dut.q).toBe(0xAA);
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: number;
920
- en: number;
921
- readonly count: number;
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 = 1;
967
+ // Reset (default async_low: rst=0 is active)
968
+ sim.dut.rst = 0n;
932
969
  sim.runUntil(20);
933
- sim.dut.rst = 0;
934
- sim.dut.en = 1;
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(0);
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: number;
948
- b: number;
949
- readonly y: number;
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 = 100;
958
- sim.dut.b = 55;
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(155);
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
  });