@celox-sim/celox 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/e2e.test.ts CHANGED
@@ -9,22 +9,25 @@
9
9
  */
10
10
 
11
11
  import path from "node:path";
12
- import { describe, test, expect, afterEach } from "vitest";
13
- import { Simulator } from "./simulator.js";
14
- import { Simulation } from "./simulation.js";
12
+ import { afterEach, describe, expect, test } from "vitest";
15
13
  import { readFourState } from "./dut.js";
16
- import { X, FourState, SimulationTimeoutError } from "./types.js";
17
14
  import {
18
- createSimulatorBridge,
19
- loadNativeAddon,
20
- parseNapiLayout,
21
- parseSignalPath,
22
- type RawNapiAddon,
23
- type RawNapiSimulatorHandle,
15
+ createSimulatorBridge,
16
+ loadNativeAddon,
17
+ parseNapiLayout,
18
+ parseSignalPath,
19
+ type RawNapiAddon,
20
+ type RawNapiSimulatorHandle,
24
21
  } from "./napi-helpers.js";
22
+ import { Simulation } from "./simulation.js";
23
+ import { Simulator } from "./simulator.js";
24
+ import { FourState, SimulationTimeoutError, X } from "./types.js";
25
25
 
26
26
  // Fixture project directories
27
- const FIXTURES_DIR = path.resolve(import.meta.dirname ?? __dirname, "../fixtures");
27
+ const FIXTURES_DIR = path.resolve(
28
+ import.meta.dirname ?? __dirname,
29
+ "../fixtures",
30
+ );
28
31
  const ADDER_PROJECT = path.join(FIXTURES_DIR, "adder");
29
32
  const COUNTER_PROJECT = path.join(FIXTURES_DIR, "counter_project");
30
33
  const CELOX_TOML_PROJECT = path.join(FIXTURES_DIR, "celox_toml");
@@ -96,157 +99,157 @@ module Mux4 (
96
99
  // ---------------------------------------------------------------------------
97
100
 
98
101
  describe("E2E: Simulator.fromSource (event-based)", () => {
99
- test("combinational adder: a + b = sum", () => {
100
- interface AdderPorts {
101
- rst: bigint;
102
- a: bigint;
103
- b: bigint;
104
- readonly sum: bigint;
105
- }
106
-
107
- const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder");
108
-
109
- sim.dut.a = 100n;
110
- sim.dut.b = 200n;
111
- sim.tick();
112
- expect(sim.dut.sum).toBe(300n);
113
-
114
- sim.dut.a = 0xFFFFn;
115
- sim.dut.b = 1n;
116
- sim.tick();
117
- expect(sim.dut.sum).toBe(0x10000n);
118
-
119
- sim.dut.a = 0n;
120
- sim.dut.b = 0n;
121
- sim.tick();
122
- expect(sim.dut.sum).toBe(0n);
123
-
124
- sim.dispose();
125
- });
126
-
127
- test("combinational adder: lazy evalComb on output read", () => {
128
- interface AdderPorts {
129
- rst: bigint;
130
- a: bigint;
131
- b: bigint;
132
- readonly sum: bigint;
133
- }
134
-
135
- const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder");
136
-
137
- sim.dut.a = 42n;
138
- sim.dut.b = 58n;
139
- expect(sim.dut.sum).toBe(100n);
140
-
141
- sim.dispose();
142
- });
143
-
144
- test("sequential counter: counts on clock edges", () => {
145
- interface CounterPorts {
146
- rst: bigint;
147
- en: bigint;
148
- readonly count: bigint;
149
- }
150
-
151
- const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
152
-
153
- // Reset the counter (default async_low: rst=0 is active)
154
- sim.dut.rst = 0n;
155
- sim.tick();
156
- sim.dut.rst = 1n;
157
- sim.tick();
158
- expect(sim.dut.count).toBe(0n);
159
-
160
- // Enable counting
161
- sim.dut.en = 1n;
162
- sim.tick();
163
- expect(sim.dut.count).toBe(1n);
164
-
165
- sim.tick();
166
- expect(sim.dut.count).toBe(2n);
167
-
168
- sim.tick();
169
- expect(sim.dut.count).toBe(3n);
170
-
171
- // Disable counting
172
- sim.dut.en = 0n;
173
- sim.tick();
174
- expect(sim.dut.count).toBe(3n);
175
-
176
- // Re-enable
177
- sim.dut.en = 1n;
178
- sim.tick(5);
179
- expect(sim.dut.count).toBe(8n);
180
-
181
- sim.dispose();
182
- });
183
-
184
- test("always_comb evaluated before FF on tick (no prior output read)", () => {
185
- interface CounterPorts {
186
- rst: number;
187
- en: number;
188
- readonly count: number;
189
- }
190
-
191
- const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
192
-
193
- // Reset (default async_low: rst=0 is active)
194
- sim.dut.rst = 0;
195
- sim.tick();
196
- sim.dut.rst = 1;
197
- sim.tick();
198
-
199
- // Write input and tick WITHOUT reading any output first.
200
- // This catches the stale-comb bug where always_ff reads stale
201
- // combinational values because eval_comb was skipped before FF.
202
- sim.dut.en = 1;
203
- sim.tick();
204
- expect(Number(sim.dut.count)).toBe(1);
205
-
206
- // Again: set input, tick, verify — no intermediate reads
207
- sim.dut.en = 1;
208
- sim.tick();
209
- expect(Number(sim.dut.count)).toBe(2);
210
-
211
- // Disable, tick, verify
212
- sim.dut.en = 0;
213
- sim.tick();
214
- expect(Number(sim.dut.count)).toBe(2);
215
-
216
- sim.dispose();
217
- });
218
-
219
- test("combinational multiplexer", () => {
220
- interface Mux4Ports {
221
- sel: bigint;
222
- d0: bigint;
223
- d1: bigint;
224
- d2: bigint;
225
- d3: bigint;
226
- readonly y: bigint;
227
- }
228
-
229
- const sim = Simulator.fromSource<Mux4Ports>(MULTIPLEXER_SOURCE, "Mux4");
230
-
231
- sim.dut.d0 = 0xAAn;
232
- sim.dut.d1 = 0xBBn;
233
- sim.dut.d2 = 0xCCn;
234
- sim.dut.d3 = 0xDDn;
235
-
236
- sim.dut.sel = 0n;
237
- expect(sim.dut.y).toBe(0xAAn);
238
-
239
- sim.dut.sel = 1n;
240
- expect(sim.dut.y).toBe(0xBBn);
241
-
242
- sim.dut.sel = 2n;
243
- expect(sim.dut.y).toBe(0xCCn);
244
-
245
- sim.dut.sel = 3n;
246
- expect(sim.dut.y).toBe(0xDDn);
247
-
248
- sim.dispose();
249
- });
102
+ test("combinational adder: a + b = sum", () => {
103
+ interface AdderPorts {
104
+ rst: bigint;
105
+ a: bigint;
106
+ b: bigint;
107
+ readonly sum: bigint;
108
+ }
109
+
110
+ const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder");
111
+
112
+ sim.dut.a = 100n;
113
+ sim.dut.b = 200n;
114
+ sim.tick();
115
+ expect(sim.dut.sum).toBe(300n);
116
+
117
+ sim.dut.a = 0xffffn;
118
+ sim.dut.b = 1n;
119
+ sim.tick();
120
+ expect(sim.dut.sum).toBe(0x10000n);
121
+
122
+ sim.dut.a = 0n;
123
+ sim.dut.b = 0n;
124
+ sim.tick();
125
+ expect(sim.dut.sum).toBe(0n);
126
+
127
+ sim.dispose();
128
+ });
129
+
130
+ test("combinational adder: lazy evalComb on output read", () => {
131
+ interface AdderPorts {
132
+ rst: bigint;
133
+ a: bigint;
134
+ b: bigint;
135
+ readonly sum: bigint;
136
+ }
137
+
138
+ const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder");
139
+
140
+ sim.dut.a = 42n;
141
+ sim.dut.b = 58n;
142
+ expect(sim.dut.sum).toBe(100n);
143
+
144
+ sim.dispose();
145
+ });
146
+
147
+ test("sequential counter: counts on clock edges", () => {
148
+ interface CounterPorts {
149
+ rst: bigint;
150
+ en: bigint;
151
+ readonly count: bigint;
152
+ }
153
+
154
+ const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
155
+
156
+ // Reset the counter (default async_low: rst=0 is active)
157
+ sim.dut.rst = 0n;
158
+ sim.tick();
159
+ sim.dut.rst = 1n;
160
+ sim.tick();
161
+ expect(sim.dut.count).toBe(0n);
162
+
163
+ // Enable counting
164
+ sim.dut.en = 1n;
165
+ sim.tick();
166
+ expect(sim.dut.count).toBe(1n);
167
+
168
+ sim.tick();
169
+ expect(sim.dut.count).toBe(2n);
170
+
171
+ sim.tick();
172
+ expect(sim.dut.count).toBe(3n);
173
+
174
+ // Disable counting
175
+ sim.dut.en = 0n;
176
+ sim.tick();
177
+ expect(sim.dut.count).toBe(3n);
178
+
179
+ // Re-enable
180
+ sim.dut.en = 1n;
181
+ sim.tick(5);
182
+ expect(sim.dut.count).toBe(8n);
183
+
184
+ sim.dispose();
185
+ });
186
+
187
+ test("always_comb evaluated before FF on tick (no prior output read)", () => {
188
+ interface CounterPorts {
189
+ rst: number;
190
+ en: number;
191
+ readonly count: number;
192
+ }
193
+
194
+ const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
195
+
196
+ // Reset (default async_low: rst=0 is active)
197
+ sim.dut.rst = 0;
198
+ sim.tick();
199
+ sim.dut.rst = 1;
200
+ sim.tick();
201
+
202
+ // Write input and tick WITHOUT reading any output first.
203
+ // This catches the stale-comb bug where always_ff reads stale
204
+ // combinational values because eval_comb was skipped before FF.
205
+ sim.dut.en = 1;
206
+ sim.tick();
207
+ expect(Number(sim.dut.count)).toBe(1);
208
+
209
+ // Again: set input, tick, verify — no intermediate reads
210
+ sim.dut.en = 1;
211
+ sim.tick();
212
+ expect(Number(sim.dut.count)).toBe(2);
213
+
214
+ // Disable, tick, verify
215
+ sim.dut.en = 0;
216
+ sim.tick();
217
+ expect(Number(sim.dut.count)).toBe(2);
218
+
219
+ sim.dispose();
220
+ });
221
+
222
+ test("combinational multiplexer", () => {
223
+ interface Mux4Ports {
224
+ sel: bigint;
225
+ d0: bigint;
226
+ d1: bigint;
227
+ d2: bigint;
228
+ d3: bigint;
229
+ readonly y: bigint;
230
+ }
231
+
232
+ const sim = Simulator.fromSource<Mux4Ports>(MULTIPLEXER_SOURCE, "Mux4");
233
+
234
+ sim.dut.d0 = 0xaan;
235
+ sim.dut.d1 = 0xbbn;
236
+ sim.dut.d2 = 0xccn;
237
+ sim.dut.d3 = 0xddn;
238
+
239
+ sim.dut.sel = 0n;
240
+ expect(sim.dut.y).toBe(0xaan);
241
+
242
+ sim.dut.sel = 1n;
243
+ expect(sim.dut.y).toBe(0xbbn);
244
+
245
+ sim.dut.sel = 2n;
246
+ expect(sim.dut.y).toBe(0xccn);
247
+
248
+ sim.dut.sel = 3n;
249
+ expect(sim.dut.y).toBe(0xddn);
250
+
251
+ sim.dispose();
252
+ });
250
253
  });
251
254
 
252
255
  // ---------------------------------------------------------------------------
@@ -254,32 +257,32 @@ describe("E2E: Simulator.fromSource (event-based)", () => {
254
257
  // ---------------------------------------------------------------------------
255
258
 
256
259
  describe("E2E: Simulation.fromSource (time-based)", () => {
257
- test("counter with timed clock: step-by-step", () => {
258
- interface CounterPorts {
259
- rst: bigint;
260
- en: bigint;
261
- readonly count: bigint;
262
- }
260
+ test("counter with timed clock: step-by-step", () => {
261
+ interface CounterPorts {
262
+ rst: bigint;
263
+ en: bigint;
264
+ readonly count: bigint;
265
+ }
263
266
 
264
- const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
267
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
265
268
 
266
- sim.addClock("clk", { period: 10 });
267
- expect(sim.time()).toBe(0);
269
+ sim.addClock("clk", { period: 10 });
270
+ expect(sim.time()).toBe(0);
268
271
 
269
- // Reset (default async_low: rst=0 is active)
270
- sim.dut.rst = 0n;
271
- sim.runUntil(20);
272
- sim.dut.rst = 1n;
273
- sim.dut.en = 1n;
272
+ // Reset (default async_low: rst=0 is active)
273
+ sim.dut.rst = 0n;
274
+ sim.runUntil(20);
275
+ sim.dut.rst = 1n;
276
+ sim.dut.en = 1n;
274
277
 
275
- sim.runUntil(100);
278
+ sim.runUntil(100);
276
279
 
277
- const count = sim.dut.count;
278
- expect(count).toBeGreaterThan(0n);
279
- expect(sim.time()).toBe(100);
280
+ const count = sim.dut.count;
281
+ expect(count).toBeGreaterThan(0n);
282
+ expect(sim.time()).toBe(100);
280
283
 
281
- sim.dispose();
282
- });
284
+ sim.dispose();
285
+ });
283
286
  });
284
287
 
285
288
  // ---------------------------------------------------------------------------
@@ -287,58 +290,58 @@ describe("E2E: Simulation.fromSource (time-based)", () => {
287
290
  // ---------------------------------------------------------------------------
288
291
 
289
292
  describe("E2E: Simulator.fromProject (event-based)", () => {
290
- test("combinational adder from project directory", () => {
291
- interface AdderPorts {
292
- rst: bigint;
293
- a: bigint;
294
- b: bigint;
295
- readonly sum: bigint;
296
- }
297
-
298
- const sim = Simulator.fromProject<AdderPorts>(ADDER_PROJECT, "Adder");
299
-
300
- sim.dut.a = 100n;
301
- sim.dut.b = 200n;
302
- sim.tick();
303
- expect(sim.dut.sum).toBe(300n);
304
-
305
- sim.dut.a = 0xFFFFn;
306
- sim.dut.b = 1n;
307
- sim.tick();
308
- expect(sim.dut.sum).toBe(0x10000n);
309
-
310
- sim.dispose();
311
- });
312
-
313
- test("sequential counter from project directory", () => {
314
- interface CounterPorts {
315
- rst: bigint;
316
- en: bigint;
317
- readonly count: bigint;
318
- }
319
-
320
- const sim = Simulator.fromProject<CounterPorts>(COUNTER_PROJECT, "Counter");
321
-
322
- // Reset the counter (default async_low: rst=0 is active)
323
- sim.dut.rst = 0n;
324
- sim.tick();
325
- sim.dut.rst = 1n;
326
- sim.tick();
327
- expect(sim.dut.count).toBe(0n);
328
-
329
- // Enable counting
330
- sim.dut.en = 1n;
331
- sim.tick();
332
- expect(sim.dut.count).toBe(1n);
333
-
334
- sim.tick();
335
- expect(sim.dut.count).toBe(2n);
336
-
337
- sim.tick();
338
- expect(sim.dut.count).toBe(3n);
339
-
340
- sim.dispose();
341
- });
293
+ test("combinational adder from project directory", () => {
294
+ interface AdderPorts {
295
+ rst: bigint;
296
+ a: bigint;
297
+ b: bigint;
298
+ readonly sum: bigint;
299
+ }
300
+
301
+ const sim = Simulator.fromProject<AdderPorts>(ADDER_PROJECT, "Adder");
302
+
303
+ sim.dut.a = 100n;
304
+ sim.dut.b = 200n;
305
+ sim.tick();
306
+ expect(sim.dut.sum).toBe(300n);
307
+
308
+ sim.dut.a = 0xffffn;
309
+ sim.dut.b = 1n;
310
+ sim.tick();
311
+ expect(sim.dut.sum).toBe(0x10000n);
312
+
313
+ sim.dispose();
314
+ });
315
+
316
+ test("sequential counter from project directory", () => {
317
+ interface CounterPorts {
318
+ rst: bigint;
319
+ en: bigint;
320
+ readonly count: bigint;
321
+ }
322
+
323
+ const sim = Simulator.fromProject<CounterPorts>(COUNTER_PROJECT, "Counter");
324
+
325
+ // Reset the counter (default async_low: rst=0 is active)
326
+ sim.dut.rst = 0n;
327
+ sim.tick();
328
+ sim.dut.rst = 1n;
329
+ sim.tick();
330
+ expect(sim.dut.count).toBe(0n);
331
+
332
+ // Enable counting
333
+ sim.dut.en = 1n;
334
+ sim.tick();
335
+ expect(sim.dut.count).toBe(1n);
336
+
337
+ sim.tick();
338
+ expect(sim.dut.count).toBe(2n);
339
+
340
+ sim.tick();
341
+ expect(sim.dut.count).toBe(3n);
342
+
343
+ sim.dispose();
344
+ });
342
345
  });
343
346
 
344
347
  // ---------------------------------------------------------------------------
@@ -346,32 +349,35 @@ describe("E2E: Simulator.fromProject (event-based)", () => {
346
349
  // ---------------------------------------------------------------------------
347
350
 
348
351
  describe("E2E: Simulation.fromProject (time-based)", () => {
349
- test("counter with timed clock from project directory", () => {
350
- interface CounterPorts {
351
- rst: bigint;
352
- en: bigint;
353
- readonly count: bigint;
354
- }
355
-
356
- const sim = Simulation.fromProject<CounterPorts>(COUNTER_PROJECT, "Counter");
357
-
358
- sim.addClock("clk", { period: 10 });
359
- expect(sim.time()).toBe(0);
360
-
361
- // Reset (default async_low: rst=0 is active)
362
- sim.dut.rst = 0n;
363
- sim.runUntil(20);
364
- sim.dut.rst = 1n;
365
- sim.dut.en = 1n;
366
-
367
- sim.runUntil(100);
368
-
369
- const count = sim.dut.count;
370
- expect(count).toBeGreaterThan(0n);
371
- expect(sim.time()).toBe(100);
372
-
373
- sim.dispose();
374
- });
352
+ test("counter with timed clock from project directory", () => {
353
+ interface CounterPorts {
354
+ rst: bigint;
355
+ en: bigint;
356
+ readonly count: bigint;
357
+ }
358
+
359
+ const sim = Simulation.fromProject<CounterPorts>(
360
+ COUNTER_PROJECT,
361
+ "Counter",
362
+ );
363
+
364
+ sim.addClock("clk", { period: 10 });
365
+ expect(sim.time()).toBe(0);
366
+
367
+ // Reset (default async_low: rst=0 is active)
368
+ sim.dut.rst = 0n;
369
+ sim.runUntil(20);
370
+ sim.dut.rst = 1n;
371
+ sim.dut.en = 1n;
372
+
373
+ sim.runUntil(100);
374
+
375
+ const count = sim.dut.count;
376
+ expect(count).toBeGreaterThan(0n);
377
+ expect(sim.time()).toBe(100);
378
+
379
+ sim.dispose();
380
+ });
375
381
  });
376
382
 
377
383
  // ---------------------------------------------------------------------------
@@ -379,41 +385,41 @@ describe("E2E: Simulation.fromProject (time-based)", () => {
379
385
  // ---------------------------------------------------------------------------
380
386
 
381
387
  describe("E2E: Simulator.create (backward compat)", () => {
382
- test("combinational adder via Simulator.create()", () => {
383
- interface AdderPorts {
384
- rst: bigint;
385
- a: bigint;
386
- b: bigint;
387
- readonly sum: bigint;
388
- }
389
-
390
- const addon = loadNativeAddon();
391
- const nativeCreateSimulator = createSimulatorBridge(addon);
392
-
393
- const sim = Simulator.create<AdderPorts>(
394
- {
395
- __celox_module: true,
396
- name: "Adder",
397
- source: ADDER_SOURCE,
398
- ports: {
399
- clk: { direction: "input", type: "clock", width: 1 },
400
- rst: { direction: "input", type: "reset", width: 1 },
401
- a: { direction: "input", type: "logic", width: 16 },
402
- b: { direction: "input", type: "logic", width: 16 },
403
- sum: { direction: "output", type: "logic", width: 17 },
404
- },
405
- events: ["clk"],
406
- },
407
- { __nativeCreate: nativeCreateSimulator },
408
- );
409
-
410
- sim.dut.a = 100n;
411
- sim.dut.b = 200n;
412
- sim.tick();
413
- expect(sim.dut.sum).toBe(300n);
414
-
415
- sim.dispose();
416
- });
388
+ test("combinational adder via Simulator.create()", () => {
389
+ interface AdderPorts {
390
+ rst: bigint;
391
+ a: bigint;
392
+ b: bigint;
393
+ readonly sum: bigint;
394
+ }
395
+
396
+ const addon = loadNativeAddon();
397
+ const nativeCreateSimulator = createSimulatorBridge(addon);
398
+
399
+ const sim = Simulator.create<AdderPorts>(
400
+ {
401
+ __celox_module: true,
402
+ name: "Adder",
403
+ sources: [{ path: "", content: ADDER_SOURCE }],
404
+ ports: {
405
+ clk: { direction: "input", type: "clock", width: 1 },
406
+ rst: { direction: "input", type: "reset", width: 1 },
407
+ a: { direction: "input", type: "logic", width: 16 },
408
+ b: { direction: "input", type: "logic", width: 16 },
409
+ sum: { direction: "output", type: "logic", width: 17 },
410
+ },
411
+ events: ["clk"],
412
+ },
413
+ { __nativeCreate: nativeCreateSimulator },
414
+ );
415
+
416
+ sim.dut.a = 100n;
417
+ sim.dut.b = 200n;
418
+ sim.tick();
419
+ expect(sim.dut.sum).toBe(300n);
420
+
421
+ sim.dispose();
422
+ });
417
423
  });
418
424
 
419
425
  // ---------------------------------------------------------------------------
@@ -439,7 +445,7 @@ module LogicBitMix (
439
445
  y_logic_from_bit: output logic<8>,
440
446
  y_bit_from_logic: output bit<8>,
441
447
  ) {
442
- assign y_bit_from_logic = a_logic;
448
+ assign y_bit_from_logic = a_logic as u8;
443
449
  assign y_logic_from_bit = b_bit;
444
450
  }
445
451
  `;
@@ -472,325 +478,387 @@ module Adder4S (
472
478
  `;
473
479
 
474
480
  describe("E2E: 4-state simulation", () => {
475
- let raw: RawNapiSimulatorHandle | undefined;
476
- let addon: RawNapiAddon;
477
-
478
- try {
479
- addon = loadNativeAddon();
480
- } catch (e) {
481
- throw new Error(`Failed to load NAPI addon for 4-state tests: ${e}`);
482
- }
483
-
484
- afterEach(() => {
485
- raw?.dispose();
486
- raw = undefined;
487
- });
488
-
489
- test("initial values: logic ports start as X, bit ports start as 0", () => {
490
- const source = `
481
+ let raw: RawNapiSimulatorHandle | undefined;
482
+ let addon: RawNapiAddon;
483
+
484
+ try {
485
+ addon = loadNativeAddon();
486
+ } catch (e) {
487
+ throw new Error(`Failed to load NAPI addon for 4-state tests: ${e}`);
488
+ }
489
+
490
+ afterEach(() => {
491
+ raw?.dispose();
492
+ raw = undefined;
493
+ });
494
+
495
+ test("initial values: logic ports start as X, bit ports start as 0", () => {
496
+ const source = `
491
497
  module InitTest (
492
498
  a: input logic<8>,
493
499
  b: input bit<8>,
494
500
  ) {}
495
501
  `;
496
- raw = new addon.NativeSimulatorHandle(source, "InitTest", { fourState: true });
497
- const layout = parseNapiLayout(raw.layoutJson);
498
- const buf = raw.sharedMemory().buffer;
499
-
500
- // logic port should have mask=0xFF (all X), X encoding: v=1, m=1
501
- const [valA, maskA] = readFourState(buf, layout.forDut.a);
502
- expect(valA).toBe(0xFFn);
503
- expect(maskA).toBe(0xFFn);
504
-
505
- // bit port should have mask=0 (defined)
506
- // bit is not 4-state, so no mask reading its value should be 0
507
- expect(layout.forDut.b.is4state).toBe(false);
508
- });
509
-
510
- test("writing X clears value and sets mask", () => {
511
- interface Ports {
512
- a: bigint;
513
- readonly y_and: bigint;
514
- }
515
-
516
- const sim = Simulator.fromSource<Ports>(AND_OR_SOURCE, "AndOr", { fourState: true });
517
- raw = undefined; // sim manages its own handle
518
-
519
- // Write X to input 'a' via DUT
520
- (sim.dut as any).a = X;
521
-
522
- // We can't inspect mask through DUT getter (it only returns value),
523
- // so this test verifies X write doesn't throw and propagation works.
524
- // For detailed mask inspection, see the raw NAPI tests below.
525
- sim.dispose();
526
- });
527
-
528
- test("AND: 0 & X = 0 (dominant zero)", () => {
529
- raw = new addon.NativeSimulatorHandle(AND_OR_SOURCE, "AndOr", { fourState: true });
530
- const layout = parseNapiLayout(raw.layoutJson);
531
- const buf = raw.sharedMemory().buffer;
532
- const view = new DataView(buf);
533
- const events: Record<string, number> = JSON.parse(raw.eventsJson);
534
-
535
- const sigA = layout.forDut.a;
536
- const sigB = layout.forDut.b;
537
- const sigYAnd = layout.forDut.y_and;
538
- const sigYOr = layout.forDut.y_or;
539
-
540
- // a = 0 (value=0, mask=0)
541
- view.setUint8(sigA.offset, 0);
542
- view.setUint8(sigA.offset + sigA.byteSize, 0);
543
-
544
- // b = X (value=0, mask=1)
545
- view.setUint8(sigB.offset, 0);
546
- view.setUint8(sigB.offset + sigB.byteSize, 1);
547
-
548
- raw.evalComb();
549
-
550
- // 0 & X = 0 (mask should be 0 — dominant zero)
551
- const [vAnd, mAnd] = readFourState(buf, sigYAnd);
552
- expect(vAnd).toBe(0n);
553
- expect(mAnd).toBe(0n);
554
-
555
- // 0 | X = X (mask should be 1)
556
- const [vOr, mOr] = readFourState(buf, sigYOr);
557
- expect(vOr).toBe(0n);
558
- expect(mOr).toBe(1n);
559
- });
560
-
561
- test("OR: 1 | X = 1 (dominant one)", () => {
562
- raw = new addon.NativeSimulatorHandle(AND_OR_SOURCE, "AndOr", { fourState: true });
563
- const layout = parseNapiLayout(raw.layoutJson);
564
- const buf = raw.sharedMemory().buffer;
565
- const view = new DataView(buf);
566
-
567
- const sigA = layout.forDut.a;
568
- const sigB = layout.forDut.b;
569
- const sigYOr = layout.forDut.y_or;
570
-
571
- // a = 1 (value=1, mask=0)
572
- view.setUint8(sigA.offset, 1);
573
- view.setUint8(sigA.offset + sigA.byteSize, 0);
574
-
575
- // b = X (value=0, mask=1)
576
- view.setUint8(sigB.offset, 0);
577
- view.setUint8(sigB.offset + sigB.byteSize, 1);
578
-
579
- raw.evalComb();
580
-
581
- // 1 | X = 1 (mask should be 0 — dominant one)
582
- const [vOr, mOr] = readFourState(buf, sigYOr);
583
- expect(vOr).toBe(1n);
584
- expect(mOr).toBe(0n);
585
- });
586
-
587
- test("logic-to-bit assignment strips X mask", () => {
588
- raw = new addon.NativeSimulatorHandle(LOGIC_BIT_MIX_SOURCE, "LogicBitMix", { fourState: true });
589
- const layout = parseNapiLayout(raw.layoutJson);
590
- const buf = raw.sharedMemory().buffer;
591
- const view = new DataView(buf);
592
-
593
- const sigALogic = layout.forDut.a_logic;
594
- const sigYBitFromLogic = layout.forDut.y_bit_from_logic;
595
-
596
- // a_logic = all-X (value=0, mask=0xFF)
597
- view.setUint8(sigALogic.offset, 0);
598
- view.setUint8(sigALogic.offset + sigALogic.byteSize, 0xFF);
599
-
600
- raw.evalComb();
601
-
602
- // y_bit_from_logic is bit type — X should be stripped (mask=0)
603
- expect(sigYBitFromLogic.is4state).toBe(false);
604
- });
605
-
606
- test("bit-to-logic assignment has no X", () => {
607
- raw = new addon.NativeSimulatorHandle(LOGIC_BIT_MIX_SOURCE, "LogicBitMix", { fourState: true });
608
- const layout = parseNapiLayout(raw.layoutJson);
609
- const buf = raw.sharedMemory().buffer;
610
- const view = new DataView(buf);
611
-
612
- const sigBBit = layout.forDut.b_bit;
613
- const sigYLogicFromBit = layout.forDut.y_logic_from_bit;
614
-
615
- // b_bit = 0xAA (bit type, always defined)
616
- view.setUint8(sigBBit.offset, 0xAA);
617
-
618
- raw.evalComb();
619
-
620
- // y_logic_from_bit should be 0xAA with mask=0
621
- const [vLogic, mLogic] = readFourState(buf, sigYLogicFromBit);
622
- expect(vLogic).toBe(0xAAn);
623
- expect(mLogic).toBe(0n);
624
- });
625
-
626
- test("arithmetic with X produces all-X output", () => {
627
- raw = new addon.NativeSimulatorHandle(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
628
- const layout = parseNapiLayout(raw.layoutJson);
629
- const buf = raw.sharedMemory().buffer;
630
- const view = new DataView(buf);
631
-
632
- const sigA = layout.forDut.a;
633
- const sigB = layout.forDut.b;
634
- const sigY = layout.forDut.y;
635
-
636
- // a = 42 (defined), b = X (all X)
637
- view.setUint8(sigA.offset, 42);
638
- view.setUint8(sigA.offset + sigA.byteSize, 0); // mask=0
639
-
640
- view.setUint8(sigB.offset, 0);
641
- view.setUint8(sigB.offset + sigB.byteSize, 0xFF); // mask=0xFF
642
-
643
- raw.evalComb();
644
-
645
- // a + X = all-X
646
- const [, mY] = readFourState(buf, sigY);
647
- expect(mY).toBe(0xFFn);
648
- });
649
-
650
- test("defined inputs in 4-state mode behave like 2-state", () => {
651
- raw = new addon.NativeSimulatorHandle(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
652
- const layout = parseNapiLayout(raw.layoutJson);
653
- const buf = raw.sharedMemory().buffer;
654
- const view = new DataView(buf);
655
-
656
- const sigA = layout.forDut.a;
657
- const sigB = layout.forDut.b;
658
- const sigY = layout.forDut.y;
659
-
660
- // a = 100 (defined), b = 55 (defined)
661
- view.setUint8(sigA.offset, 100);
662
- view.setUint8(sigA.offset + sigA.byteSize, 0);
663
-
664
- view.setUint8(sigB.offset, 55);
665
- view.setUint8(sigB.offset + sigB.byteSize, 0);
666
-
667
- raw.evalComb();
668
-
669
- const [vY, mY] = readFourState(buf, sigY);
670
- expect(vY).toBe(155n);
671
- expect(mY).toBe(0n);
672
- });
673
-
674
- test("FF captures X from input, reset clears X", () => {
675
- raw = new addon.NativeSimulatorHandle(FF_SOURCE, "FF", { fourState: true });
676
- const layout = parseNapiLayout(raw.layoutJson);
677
- const buf = raw.sharedMemory().buffer;
678
- const view = new DataView(buf);
679
- const events: Record<string, number> = JSON.parse(raw.eventsJson);
680
-
681
- const sigRst = layout.forDut.rst;
682
- const sigD = layout.forDut.d;
683
- const sigQ = layout.forDut.q;
684
- const clkEventId = events.clk;
685
-
686
- // 1. Assert reset (default async_low: rst=0 is active), d=X
687
- view.setUint8(sigRst.offset, 0);
688
- view.setUint8(sigRst.offset + sigRst.byteSize, 0); // rst is defined
689
-
690
- view.setUint8(sigD.offset, 0);
691
- view.setUint8(sigD.offset + sigD.byteSize, 0xFF); // d = all-X
692
-
693
- raw.tick(clkEventId);
694
-
695
- // After reset, q should be 0 with mask=0
696
- const [vQ1, mQ1] = readFourState(buf, sigQ);
697
- expect(vQ1).toBe(0n);
698
- expect(mQ1).toBe(0n);
699
-
700
- // 2. Release reset (rst=1 is inactive), d = partial X (value=0xA5, mask=0x0F)
701
- view.setUint8(sigRst.offset, 1);
702
- view.setUint8(sigRst.offset + sigRst.byteSize, 0);
703
-
704
- view.setUint8(sigD.offset, 0xA5);
705
- view.setUint8(sigD.offset + sigD.byteSize, 0x0F);
706
-
707
- raw.tick(clkEventId);
708
-
709
- // FF should capture X mask from d
710
- const [, mQ2] = readFourState(buf, sigQ);
711
- expect(mQ2).toBe(0x0Fn);
712
-
713
- // 3. Assert reset again (rst=0): should clear X
714
- view.setUint8(sigRst.offset, 0);
715
- view.setUint8(sigRst.offset + sigRst.byteSize, 0);
716
-
717
- raw.tick(clkEventId);
718
-
719
- const [vQ3, mQ3] = readFourState(buf, sigQ);
720
- expect(vQ3).toBe(0n);
721
- expect(mQ3).toBe(0n);
722
- });
723
-
724
- test("FourState write through DUT sets value and mask", () => {
725
- raw = new addon.NativeSimulatorHandle(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
726
- const layout = parseNapiLayout(raw.layoutJson);
727
- const buf = raw.sharedMemory().buffer;
728
- const view = new DataView(buf);
729
-
730
- const sigA = layout.forDut.a;
731
-
732
- // Write via DUT-style: FourState(0b1010_0101, 0b0000_1111)
733
- // value=0xA5, mask=0x0F means lower 4 bits are X
734
- view.setUint8(sigA.offset, 0xA5);
735
- view.setUint8(sigA.offset + sigA.byteSize, 0x0F);
736
-
737
- const [vA, mA] = readFourState(buf, sigA);
738
- expect(vA).toBe(0xA5n);
739
- expect(mA).toBe(0x0Fn);
740
- });
741
-
742
- test("setting defined value clears X mask", () => {
743
- raw = new addon.NativeSimulatorHandle(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
744
- const layout = parseNapiLayout(raw.layoutJson);
745
- const buf = raw.sharedMemory().buffer;
746
- const view = new DataView(buf);
747
-
748
- const sigA = layout.forDut.a;
749
-
750
- // Start with X
751
- view.setUint8(sigA.offset, 0);
752
- view.setUint8(sigA.offset + sigA.byteSize, 0xFF);
753
-
754
- const [, mBefore] = readFourState(buf, sigA);
755
- expect(mBefore).toBe(0xFFn);
756
-
757
- // Write a defined value (clear mask)
758
- view.setUint8(sigA.offset, 42);
759
- view.setUint8(sigA.offset + sigA.byteSize, 0);
760
-
761
- const [vAfter, mAfter] = readFourState(buf, sigA);
762
- expect(vAfter).toBe(42n);
763
- expect(mAfter).toBe(0n);
764
- });
765
-
766
- test("4-state through DUT high-level API (fromSource with fourState)", () => {
767
- interface Ports {
768
- a: bigint;
769
- b: bigint;
770
- readonly y: bigint;
771
- }
772
-
773
- const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
774
-
775
- // Write defined values — should behave like 2-state
776
- sim.dut.a = 100n;
777
- sim.dut.b = 55n;
778
- expect(sim.dut.y).toBe(155n);
779
-
780
- // Write X to a — output should propagate X (value reads as 0)
781
- (sim.dut as any).a = X;
782
- // After writing X, the value part of 'y' is implementation-defined
783
- // but the read should not throw
784
- const _yVal = sim.dut.y;
785
- expect(typeof _yVal).toBe("bigint");
786
-
787
- // Write FourState with partial X
788
- (sim.dut as any).a = FourState(0xA0, 0x0F);
789
- const _yVal2 = sim.dut.y;
790
- expect(typeof _yVal2).toBe("bigint");
791
-
792
- sim.dispose();
793
- });
502
+ raw = new addon.NativeSimulatorHandle(
503
+ [{ content: source, path: "" }],
504
+ "InitTest",
505
+ {
506
+ fourState: true,
507
+ },
508
+ );
509
+ const layout = parseNapiLayout(raw.layoutJson);
510
+ const buf = raw.sharedMemory().buffer;
511
+
512
+ // logic port should have mask=0xFF (all X), X encoding: v=1, m=1
513
+ const [valA, maskA] = readFourState(buf, layout.forDut.a);
514
+ expect(valA).toBe(0xffn);
515
+ expect(maskA).toBe(0xffn);
516
+
517
+ // bit port should have mask=0 (defined)
518
+ // bit is not 4-state, so no mask — reading its value should be 0
519
+ expect(layout.forDut.b.is4state).toBe(false);
520
+ });
521
+
522
+ test("writing X clears value and sets mask", () => {
523
+ interface Ports {
524
+ a: bigint;
525
+ readonly y_and: bigint;
526
+ }
527
+
528
+ const sim = Simulator.fromSource<Ports>(AND_OR_SOURCE, "AndOr", {
529
+ fourState: true,
530
+ });
531
+ raw = undefined; // sim manages its own handle
532
+
533
+ // Write X to input 'a' via DUT
534
+ (sim.dut as any).a = X;
535
+
536
+ // We can't inspect mask through DUT getter (it only returns value),
537
+ // so this test verifies X write doesn't throw and propagation works.
538
+ // For detailed mask inspection, see the raw NAPI tests below.
539
+ sim.dispose();
540
+ });
541
+
542
+ test("AND: 0 & X = 0 (dominant zero)", () => {
543
+ raw = new addon.NativeSimulatorHandle(
544
+ [{ content: AND_OR_SOURCE, path: "" }],
545
+ "AndOr",
546
+ {
547
+ fourState: true,
548
+ },
549
+ );
550
+ const layout = parseNapiLayout(raw.layoutJson);
551
+ const buf = raw.sharedMemory().buffer;
552
+ const view = new DataView(buf);
553
+ const _events: Record<string, number> = JSON.parse(raw.eventsJson);
554
+
555
+ const sigA = layout.forDut.a;
556
+ const sigB = layout.forDut.b;
557
+ const sigYAnd = layout.forDut.y_and;
558
+ const sigYOr = layout.forDut.y_or;
559
+
560
+ // a = 0 (value=0, mask=0)
561
+ view.setUint8(sigA.offset, 0);
562
+ view.setUint8(sigA.offset + sigA.byteSize, 0);
563
+
564
+ // b = X (value=0, mask=1)
565
+ view.setUint8(sigB.offset, 0);
566
+ view.setUint8(sigB.offset + sigB.byteSize, 1);
567
+
568
+ raw.evalComb();
569
+
570
+ // 0 & X = 0 (mask should be 0 — dominant zero)
571
+ const [vAnd, mAnd] = readFourState(buf, sigYAnd);
572
+ expect(vAnd).toBe(0n);
573
+ expect(mAnd).toBe(0n);
574
+
575
+ // 0 | X = X (mask should be 1)
576
+ const [vOr, mOr] = readFourState(buf, sigYOr);
577
+ expect(vOr).toBe(0n);
578
+ expect(mOr).toBe(1n);
579
+ });
580
+
581
+ test("OR: 1 | X = 1 (dominant one)", () => {
582
+ raw = new addon.NativeSimulatorHandle(
583
+ [{ content: AND_OR_SOURCE, path: "" }],
584
+ "AndOr",
585
+ {
586
+ fourState: true,
587
+ },
588
+ );
589
+ const layout = parseNapiLayout(raw.layoutJson);
590
+ const buf = raw.sharedMemory().buffer;
591
+ const view = new DataView(buf);
592
+
593
+ const sigA = layout.forDut.a;
594
+ const sigB = layout.forDut.b;
595
+ const sigYOr = layout.forDut.y_or;
596
+
597
+ // a = 1 (value=1, mask=0)
598
+ view.setUint8(sigA.offset, 1);
599
+ view.setUint8(sigA.offset + sigA.byteSize, 0);
600
+
601
+ // b = X (value=0, mask=1)
602
+ view.setUint8(sigB.offset, 0);
603
+ view.setUint8(sigB.offset + sigB.byteSize, 1);
604
+
605
+ raw.evalComb();
606
+
607
+ // 1 | X = 1 (mask should be 0 — dominant one)
608
+ const [vOr, mOr] = readFourState(buf, sigYOr);
609
+ expect(vOr).toBe(1n);
610
+ expect(mOr).toBe(0n);
611
+ });
612
+
613
+ test("logic-to-bit assignment strips X mask", () => {
614
+ raw = new addon.NativeSimulatorHandle(
615
+ [{ content: LOGIC_BIT_MIX_SOURCE, path: "" }],
616
+ "LogicBitMix",
617
+ {
618
+ fourState: true,
619
+ },
620
+ );
621
+ const layout = parseNapiLayout(raw.layoutJson);
622
+ const buf = raw.sharedMemory().buffer;
623
+ const view = new DataView(buf);
624
+
625
+ const sigALogic = layout.forDut.a_logic;
626
+ const sigYBitFromLogic = layout.forDut.y_bit_from_logic;
627
+
628
+ // a_logic = all-X (value=0, mask=0xFF)
629
+ view.setUint8(sigALogic.offset, 0);
630
+ view.setUint8(sigALogic.offset + sigALogic.byteSize, 0xff);
631
+
632
+ raw.evalComb();
633
+
634
+ // y_bit_from_logic is bit type — X should be stripped (mask=0)
635
+ expect(sigYBitFromLogic.is4state).toBe(false);
636
+ });
637
+
638
+ test("bit-to-logic assignment has no X", () => {
639
+ raw = new addon.NativeSimulatorHandle(
640
+ [{ content: LOGIC_BIT_MIX_SOURCE, path: "" }],
641
+ "LogicBitMix",
642
+ {
643
+ fourState: true,
644
+ },
645
+ );
646
+ const layout = parseNapiLayout(raw.layoutJson);
647
+ const buf = raw.sharedMemory().buffer;
648
+ const view = new DataView(buf);
649
+
650
+ const sigBBit = layout.forDut.b_bit;
651
+ const sigYLogicFromBit = layout.forDut.y_logic_from_bit;
652
+
653
+ // b_bit = 0xAA (bit type, always defined)
654
+ view.setUint8(sigBBit.offset, 0xaa);
655
+
656
+ raw.evalComb();
657
+
658
+ // y_logic_from_bit should be 0xAA with mask=0
659
+ const [vLogic, mLogic] = readFourState(buf, sigYLogicFromBit);
660
+ expect(vLogic).toBe(0xaan);
661
+ expect(mLogic).toBe(0n);
662
+ });
663
+
664
+ test("arithmetic with X produces all-X output", () => {
665
+ raw = new addon.NativeSimulatorHandle(
666
+ [{ content: ADDER_4STATE_SOURCE, path: "" }],
667
+ "Adder4S",
668
+ {
669
+ fourState: true,
670
+ },
671
+ );
672
+ const layout = parseNapiLayout(raw.layoutJson);
673
+ const buf = raw.sharedMemory().buffer;
674
+ const view = new DataView(buf);
675
+
676
+ const sigA = layout.forDut.a;
677
+ const sigB = layout.forDut.b;
678
+ const sigY = layout.forDut.y;
679
+
680
+ // a = 42 (defined), b = X (all X)
681
+ view.setUint8(sigA.offset, 42);
682
+ view.setUint8(sigA.offset + sigA.byteSize, 0); // mask=0
683
+
684
+ view.setUint8(sigB.offset, 0);
685
+ view.setUint8(sigB.offset + sigB.byteSize, 0xff); // mask=0xFF
686
+
687
+ raw.evalComb();
688
+
689
+ // a + X = all-X
690
+ const [, mY] = readFourState(buf, sigY);
691
+ expect(mY).toBe(0xffn);
692
+ });
693
+
694
+ test("defined inputs in 4-state mode behave like 2-state", () => {
695
+ raw = new addon.NativeSimulatorHandle(
696
+ [{ content: ADDER_4STATE_SOURCE, path: "" }],
697
+ "Adder4S",
698
+ {
699
+ fourState: true,
700
+ },
701
+ );
702
+ const layout = parseNapiLayout(raw.layoutJson);
703
+ const buf = raw.sharedMemory().buffer;
704
+ const view = new DataView(buf);
705
+
706
+ const sigA = layout.forDut.a;
707
+ const sigB = layout.forDut.b;
708
+ const sigY = layout.forDut.y;
709
+
710
+ // a = 100 (defined), b = 55 (defined)
711
+ view.setUint8(sigA.offset, 100);
712
+ view.setUint8(sigA.offset + sigA.byteSize, 0);
713
+
714
+ view.setUint8(sigB.offset, 55);
715
+ view.setUint8(sigB.offset + sigB.byteSize, 0);
716
+
717
+ raw.evalComb();
718
+
719
+ const [vY, mY] = readFourState(buf, sigY);
720
+ expect(vY).toBe(155n);
721
+ expect(mY).toBe(0n);
722
+ });
723
+
724
+ test("FF captures X from input, reset clears X", () => {
725
+ raw = new addon.NativeSimulatorHandle(
726
+ [{ content: FF_SOURCE, path: "" }],
727
+ "FF",
728
+ { fourState: true },
729
+ );
730
+ const layout = parseNapiLayout(raw.layoutJson);
731
+ const buf = raw.sharedMemory().buffer;
732
+ const view = new DataView(buf);
733
+ const events: Record<string, number> = JSON.parse(raw.eventsJson);
734
+
735
+ const sigRst = layout.forDut.rst;
736
+ const sigD = layout.forDut.d;
737
+ const sigQ = layout.forDut.q;
738
+ const clkEventId = events.clk;
739
+
740
+ // 1. Assert reset (default async_low: rst=0 is active), d=X
741
+ view.setUint8(sigRst.offset, 0);
742
+ view.setUint8(sigRst.offset + sigRst.byteSize, 0); // rst is defined
743
+
744
+ view.setUint8(sigD.offset, 0);
745
+ view.setUint8(sigD.offset + sigD.byteSize, 0xff); // d = all-X
746
+
747
+ raw.tick(clkEventId);
748
+
749
+ // After reset, q should be 0 with mask=0
750
+ const [vQ1, mQ1] = readFourState(buf, sigQ);
751
+ expect(vQ1).toBe(0n);
752
+ expect(mQ1).toBe(0n);
753
+
754
+ // 2. Release reset (rst=1 is inactive), d = partial X (value=0xA5, mask=0x0F)
755
+ view.setUint8(sigRst.offset, 1);
756
+ view.setUint8(sigRst.offset + sigRst.byteSize, 0);
757
+
758
+ view.setUint8(sigD.offset, 0xa5);
759
+ view.setUint8(sigD.offset + sigD.byteSize, 0x0f);
760
+
761
+ raw.tick(clkEventId);
762
+
763
+ // FF should capture X mask from d
764
+ const [, mQ2] = readFourState(buf, sigQ);
765
+ expect(mQ2).toBe(0x0fn);
766
+
767
+ // 3. Assert reset again (rst=0): should clear X
768
+ view.setUint8(sigRst.offset, 0);
769
+ view.setUint8(sigRst.offset + sigRst.byteSize, 0);
770
+
771
+ raw.tick(clkEventId);
772
+
773
+ const [vQ3, mQ3] = readFourState(buf, sigQ);
774
+ expect(vQ3).toBe(0n);
775
+ expect(mQ3).toBe(0n);
776
+ });
777
+
778
+ test("FourState write through DUT sets value and mask", () => {
779
+ raw = new addon.NativeSimulatorHandle(
780
+ [{ content: ADDER_4STATE_SOURCE, path: "" }],
781
+ "Adder4S",
782
+ {
783
+ fourState: true,
784
+ },
785
+ );
786
+ const layout = parseNapiLayout(raw.layoutJson);
787
+ const buf = raw.sharedMemory().buffer;
788
+ const view = new DataView(buf);
789
+
790
+ const sigA = layout.forDut.a;
791
+
792
+ // Write via DUT-style: FourState(0b1010_0101, 0b0000_1111)
793
+ // value=0xA5, mask=0x0F means lower 4 bits are X
794
+ view.setUint8(sigA.offset, 0xa5);
795
+ view.setUint8(sigA.offset + sigA.byteSize, 0x0f);
796
+
797
+ const [vA, mA] = readFourState(buf, sigA);
798
+ expect(vA).toBe(0xa5n);
799
+ expect(mA).toBe(0x0fn);
800
+ });
801
+
802
+ test("setting defined value clears X mask", () => {
803
+ raw = new addon.NativeSimulatorHandle(
804
+ [{ content: ADDER_4STATE_SOURCE, path: "" }],
805
+ "Adder4S",
806
+ {
807
+ fourState: true,
808
+ },
809
+ );
810
+ const layout = parseNapiLayout(raw.layoutJson);
811
+ const buf = raw.sharedMemory().buffer;
812
+ const view = new DataView(buf);
813
+
814
+ const sigA = layout.forDut.a;
815
+
816
+ // Start with X
817
+ view.setUint8(sigA.offset, 0);
818
+ view.setUint8(sigA.offset + sigA.byteSize, 0xff);
819
+
820
+ const [, mBefore] = readFourState(buf, sigA);
821
+ expect(mBefore).toBe(0xffn);
822
+
823
+ // Write a defined value (clear mask)
824
+ view.setUint8(sigA.offset, 42);
825
+ view.setUint8(sigA.offset + sigA.byteSize, 0);
826
+
827
+ const [vAfter, mAfter] = readFourState(buf, sigA);
828
+ expect(vAfter).toBe(42n);
829
+ expect(mAfter).toBe(0n);
830
+ });
831
+
832
+ test("4-state through DUT high-level API (fromSource with fourState)", () => {
833
+ interface Ports {
834
+ a: bigint;
835
+ b: bigint;
836
+ readonly y: bigint;
837
+ }
838
+
839
+ const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
840
+ fourState: true,
841
+ });
842
+
843
+ // Write defined values — should behave like 2-state
844
+ sim.dut.a = 100n;
845
+ sim.dut.b = 55n;
846
+ expect(sim.dut.y).toBe(155n);
847
+
848
+ // Write X to a — output should propagate X (value reads as 0)
849
+ (sim.dut as any).a = X;
850
+ // After writing X, the value part of 'y' is implementation-defined
851
+ // but the read should not throw
852
+ const _yVal = sim.dut.y;
853
+ expect(typeof _yVal).toBe("bigint");
854
+
855
+ // Write FourState with partial X
856
+ (sim.dut as any).a = FourState(0xa0, 0x0f);
857
+ const _yVal2 = sim.dut.y;
858
+ expect(typeof _yVal2).toBe("bigint");
859
+
860
+ sim.dispose();
861
+ });
794
862
  });
795
863
 
796
864
  // ---------------------------------------------------------------------------
@@ -798,127 +866,137 @@ module InitTest (
798
866
  // ---------------------------------------------------------------------------
799
867
 
800
868
  describe("E2E: 4-state high-level DUT API", () => {
801
- test("counter in 4-state mode: reset clears X, counting works", () => {
802
- interface CounterPorts {
803
- rst: bigint;
804
- en: bigint;
805
- readonly count: bigint;
806
- }
807
-
808
- const sim = Simulator.fromSource<CounterPorts>(
809
- COUNTER_SOURCE, "Counter", { fourState: true },
810
- );
811
-
812
- // In 4-state mode, count starts as X. Reset should clear it.
813
- // (default async_low: rst=0 is active)
814
- sim.dut.rst = 0n;
815
- // Clear X on en before deactivating reset. In 4-state mode, en starts
816
- // as X (v=1, m=1). The FF branch condition currently uses only the value
817
- // bit, so X with v=1 is treated as "true" — a known limitation in FF
818
- // conditional handling (combinational mux handles X correctly).
819
- sim.dut.en = 0n;
820
- sim.tick();
821
- sim.dut.rst = 1n;
822
- sim.tick();
823
- expect(sim.dut.count).toBe(0n);
824
-
825
- // Enable counting — should work exactly like 2-state
826
- sim.dut.en = 1n;
827
- sim.tick();
828
- expect(sim.dut.count).toBe(1n);
829
-
830
- sim.tick();
831
- expect(sim.dut.count).toBe(2n);
832
-
833
- sim.tick();
834
- expect(sim.dut.count).toBe(3n);
835
-
836
- sim.dispose();
837
- });
838
-
839
- test("multiplexer with X selector produces X output", () => {
840
- const addon = loadNativeAddon();
841
- const raw = new addon.NativeSimulatorHandle(MULTIPLEXER_SOURCE, "Mux4", { fourState: true });
842
- const layout = parseNapiLayout(raw.layoutJson);
843
- const buf = raw.sharedMemory().buffer;
844
- const view = new DataView(buf);
845
-
846
- const sigSel = layout.forDut.sel;
847
- const sigD0 = layout.forDut.d0;
848
- const sigY = layout.forDut.y;
849
-
850
- // Set d0 = 0xAA (defined)
851
- view.setUint8(sigD0.offset, 0xAA);
852
- view.setUint8(sigD0.offset + sigD0.byteSize, 0);
853
-
854
- // Set sel = X
855
- view.setUint8(sigSel.offset, 0);
856
- view.setUint8(sigSel.offset + sigSel.byteSize, 0x03);
857
-
858
- raw.evalComb();
859
-
860
- // With X selector, output should be all-X
861
- const [, mY2] = readFourState(buf, sigY);
862
- expect(mY2).toBe(0xFFn);
863
-
864
- raw.dispose();
865
- });
866
-
867
- test("FF via DUT API: write X input, tick, read output", () => {
868
- interface FFPorts {
869
- rst: bigint;
870
- d: bigint;
871
- readonly q: bigint;
872
- }
873
-
874
- const sim = Simulator.fromSource<FFPorts>(FF_SOURCE, "FF", { fourState: true });
875
-
876
- // Reset to clear initial X (default async_low: rst=0 is active)
877
- sim.dut.rst = 0n;
878
- sim.tick();
879
- sim.dut.rst = 1n;
880
- expect(sim.dut.q).toBe(0n);
881
-
882
- // Write a defined value
883
- sim.dut.d = 0x42n;
884
- sim.tick();
885
- expect(sim.dut.q).toBe(0x42n);
886
-
887
- // Write X to d, tick — q should capture it (value read still returns a bigint)
888
- (sim.dut as any).d = X;
889
- sim.tick();
890
- expect(typeof sim.dut.q).toBe("bigint");
891
-
892
- // Write defined value again — q should recover
893
- sim.dut.d = 0x99n;
894
- sim.tick();
895
- expect(sim.dut.q).toBe(0x99n);
896
-
897
- sim.dispose();
898
- });
899
-
900
- test("X to defined transition: adder recovers from X", () => {
901
- interface Ports {
902
- a: bigint;
903
- b: bigint;
904
- readonly y: bigint;
905
- }
906
-
907
- const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
908
-
909
- // Start with X
910
- (sim.dut as any).a = X;
911
- sim.dut.b = 10n;
912
- // Output has X — just verify it doesn't crash
913
- expect(typeof sim.dut.y).toBe("bigint");
914
-
915
- // Clear X by writing defined values
916
- sim.dut.a = 20n;
917
- sim.dut.b = 30n;
918
- expect(sim.dut.y).toBe(50n);
919
-
920
- sim.dispose();
921
- });
869
+ test("counter in 4-state mode: reset clears X, counting works", () => {
870
+ interface CounterPorts {
871
+ rst: bigint;
872
+ en: bigint;
873
+ readonly count: bigint;
874
+ }
875
+
876
+ const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
877
+ fourState: true,
878
+ });
879
+
880
+ // In 4-state mode, count starts as X. Reset should clear it.
881
+ // (default async_low: rst=0 is active)
882
+ sim.dut.rst = 0n;
883
+ // Clear X on en before deactivating reset. In 4-state mode, en starts
884
+ // as X (v=1, m=1). The FF branch condition currently uses only the value
885
+ // bit, so X with v=1 is treated as "true" — a known limitation in FF
886
+ // conditional handling (combinational mux handles X correctly).
887
+ sim.dut.en = 0n;
888
+ sim.tick();
889
+ sim.dut.rst = 1n;
890
+ sim.tick();
891
+ expect(sim.dut.count).toBe(0n);
892
+
893
+ // Enable counting — should work exactly like 2-state
894
+ sim.dut.en = 1n;
895
+ sim.tick();
896
+ expect(sim.dut.count).toBe(1n);
897
+
898
+ sim.tick();
899
+ expect(sim.dut.count).toBe(2n);
900
+
901
+ sim.tick();
902
+ expect(sim.dut.count).toBe(3n);
903
+
904
+ sim.dispose();
905
+ });
906
+
907
+ test("multiplexer with X selector produces X output", () => {
908
+ const addon = loadNativeAddon();
909
+ const raw = new addon.NativeSimulatorHandle(
910
+ [{ content: MULTIPLEXER_SOURCE, path: "" }],
911
+ "Mux4",
912
+ {
913
+ fourState: true,
914
+ },
915
+ );
916
+ const layout = parseNapiLayout(raw.layoutJson);
917
+ const buf = raw.sharedMemory().buffer;
918
+ const view = new DataView(buf);
919
+
920
+ const sigSel = layout.forDut.sel;
921
+ const sigD0 = layout.forDut.d0;
922
+ const sigY = layout.forDut.y;
923
+
924
+ // Set d0 = 0xAA (defined)
925
+ view.setUint8(sigD0.offset, 0xaa);
926
+ view.setUint8(sigD0.offset + sigD0.byteSize, 0);
927
+
928
+ // Set sel = X
929
+ view.setUint8(sigSel.offset, 0);
930
+ view.setUint8(sigSel.offset + sigSel.byteSize, 0x03);
931
+
932
+ raw.evalComb();
933
+
934
+ // With X selector, output should be all-X
935
+ const [, mY2] = readFourState(buf, sigY);
936
+ expect(mY2).toBe(0xffn);
937
+
938
+ raw.dispose();
939
+ });
940
+
941
+ test("FF via DUT API: write X input, tick, read output", () => {
942
+ interface FFPorts {
943
+ rst: bigint;
944
+ d: bigint;
945
+ readonly q: bigint;
946
+ }
947
+
948
+ const sim = Simulator.fromSource<FFPorts>(FF_SOURCE, "FF", {
949
+ fourState: true,
950
+ });
951
+
952
+ // Reset to clear initial X (default async_low: rst=0 is active)
953
+ sim.dut.rst = 0n;
954
+ sim.tick();
955
+ sim.dut.rst = 1n;
956
+ expect(sim.dut.q).toBe(0n);
957
+
958
+ // Write a defined value
959
+ sim.dut.d = 0x42n;
960
+ sim.tick();
961
+ expect(sim.dut.q).toBe(0x42n);
962
+
963
+ // Write X to d, tick — q should capture it (value read still returns a bigint)
964
+ (sim.dut as any).d = X;
965
+ sim.tick();
966
+ expect(typeof sim.dut.q).toBe("bigint");
967
+
968
+ // Write defined value again q should recover
969
+ sim.dut.d = 0x99n;
970
+ sim.tick();
971
+ expect(sim.dut.q).toBe(0x99n);
972
+
973
+ sim.dispose();
974
+ });
975
+
976
+ test("X to defined transition: adder recovers from X", () => {
977
+ interface Ports {
978
+ a: bigint;
979
+ b: bigint;
980
+ readonly y: bigint;
981
+ }
982
+
983
+ const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
984
+ fourState: true,
985
+ });
986
+
987
+ // Start with X
988
+ (sim.dut as any).a = X;
989
+ sim.dut.b = 10n;
990
+ // Output has X — just verify it doesn't crash
991
+ expect(typeof sim.dut.y).toBe("bigint");
992
+
993
+ // Clear X by writing defined values
994
+ sim.dut.a = 20n;
995
+ sim.dut.b = 30n;
996
+ expect(sim.dut.y).toBe(50n);
997
+
998
+ sim.dispose();
999
+ });
922
1000
  });
923
1001
 
924
1002
  // ---------------------------------------------------------------------------
@@ -926,85 +1004,87 @@ describe("E2E: 4-state high-level DUT API", () => {
926
1004
  // ---------------------------------------------------------------------------
927
1005
 
928
1006
  describe("E2E: 4-state Simulation (time-based)", () => {
929
- test("FF with clock-driven 4-state: reset clears X, captures defined values", () => {
930
- interface FFPorts {
931
- rst: bigint;
932
- d: bigint;
933
- readonly q: bigint;
934
- }
935
-
936
- const sim = Simulation.fromSource<FFPorts>(FF_SOURCE, "FF", { fourState: true });
937
-
938
- sim.addClock("clk", { period: 10 });
939
- expect(sim.time()).toBe(0);
940
-
941
- // Reset to clear initial X on q (default async_low: rst=0 is active)
942
- sim.dut.rst = 0n;
943
- sim.runUntil(20);
944
- sim.dut.rst = 1n;
945
- expect(sim.dut.q).toBe(0n);
946
-
947
- // Drive d with defined value
948
- sim.dut.d = 0x55n;
949
- sim.runUntil(40);
950
- expect(sim.dut.q).toBe(0x55n);
951
-
952
- // Drive d with different value
953
- sim.dut.d = 0xAAn;
954
- sim.runUntil(60);
955
- expect(sim.dut.q).toBe(0xAAn);
956
-
957
- sim.dispose();
958
- });
959
-
960
- test("counter in 4-state time-based mode", () => {
961
- interface CounterPorts {
962
- rst: bigint;
963
- en: bigint;
964
- readonly count: bigint;
965
- }
966
-
967
- const sim = Simulation.fromSource<CounterPorts>(
968
- COUNTER_SOURCE, "Counter", { fourState: true },
969
- );
970
-
971
- sim.addClock("clk", { period: 10 });
972
-
973
- // Reset (default async_low: rst=0 is active)
974
- sim.dut.rst = 0n;
975
- sim.runUntil(20);
976
- sim.dut.rst = 1n;
977
- sim.dut.en = 1n;
978
-
979
- sim.runUntil(100);
980
-
981
- const count = sim.dut.count;
982
- expect(count).toBeGreaterThan(0n);
983
- expect(sim.time()).toBe(100);
984
-
985
- sim.dispose();
986
- });
987
-
988
- test("4-state combinational in time-based simulation", () => {
989
- interface Ports {
990
- a: bigint;
991
- b: bigint;
992
- readonly y: bigint;
993
- }
994
-
995
- const sim = Simulation.fromSource<Ports>(
996
- ADDER_4STATE_SOURCE, "Adder4S", { fourState: true },
997
- );
998
-
999
- // No clock needed for pure combinational — just set values and read
1000
- sim.dut.a = 100n;
1001
- sim.dut.b = 55n;
1002
- // runUntil(0) to force eval
1003
- sim.runUntil(0);
1004
- expect(sim.dut.y).toBe(155n);
1005
-
1006
- sim.dispose();
1007
- });
1007
+ test("FF with clock-driven 4-state: reset clears X, captures defined values", () => {
1008
+ interface FFPorts {
1009
+ rst: bigint;
1010
+ d: bigint;
1011
+ readonly q: bigint;
1012
+ }
1013
+
1014
+ const sim = Simulation.fromSource<FFPorts>(FF_SOURCE, "FF", {
1015
+ fourState: true,
1016
+ });
1017
+
1018
+ sim.addClock("clk", { period: 10 });
1019
+ expect(sim.time()).toBe(0);
1020
+
1021
+ // Reset to clear initial X on q (default async_low: rst=0 is active)
1022
+ sim.dut.rst = 0n;
1023
+ sim.runUntil(20);
1024
+ sim.dut.rst = 1n;
1025
+ expect(sim.dut.q).toBe(0n);
1026
+
1027
+ // Drive d with defined value
1028
+ sim.dut.d = 0x55n;
1029
+ sim.runUntil(40);
1030
+ expect(sim.dut.q).toBe(0x55n);
1031
+
1032
+ // Drive d with different value
1033
+ sim.dut.d = 0xaan;
1034
+ sim.runUntil(60);
1035
+ expect(sim.dut.q).toBe(0xaan);
1036
+
1037
+ sim.dispose();
1038
+ });
1039
+
1040
+ test("counter in 4-state time-based mode", () => {
1041
+ interface CounterPorts {
1042
+ rst: bigint;
1043
+ en: bigint;
1044
+ readonly count: bigint;
1045
+ }
1046
+
1047
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1048
+ fourState: true,
1049
+ });
1050
+
1051
+ sim.addClock("clk", { period: 10 });
1052
+
1053
+ // Reset (default async_low: rst=0 is active)
1054
+ sim.dut.rst = 0n;
1055
+ sim.runUntil(20);
1056
+ sim.dut.rst = 1n;
1057
+ sim.dut.en = 1n;
1058
+
1059
+ sim.runUntil(100);
1060
+
1061
+ const count = sim.dut.count;
1062
+ expect(count).toBeGreaterThan(0n);
1063
+ expect(sim.time()).toBe(100);
1064
+
1065
+ sim.dispose();
1066
+ });
1067
+
1068
+ test("4-state combinational in time-based simulation", () => {
1069
+ interface Ports {
1070
+ a: bigint;
1071
+ b: bigint;
1072
+ readonly y: bigint;
1073
+ }
1074
+
1075
+ const sim = Simulation.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1076
+ fourState: true,
1077
+ });
1078
+
1079
+ // No clock needed for pure combinational — just set values and read
1080
+ sim.dut.a = 100n;
1081
+ sim.dut.b = 55n;
1082
+ // runUntil(0) to force eval
1083
+ sim.runUntil(0);
1084
+ expect(sim.dut.y).toBe(155n);
1085
+
1086
+ sim.dispose();
1087
+ });
1008
1088
  });
1009
1089
 
1010
1090
  // ---------------------------------------------------------------------------
@@ -1012,225 +1092,225 @@ describe("E2E: 4-state Simulation (time-based)", () => {
1012
1092
  // ---------------------------------------------------------------------------
1013
1093
 
1014
1094
  describe("E2E: Simulation testbench helpers", () => {
1015
- test("waitForCycles: advances correct number of clock cycles", () => {
1016
- interface CounterPorts {
1017
- rst: bigint;
1018
- en: bigint;
1019
- readonly count: bigint;
1020
- }
1021
-
1022
- const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1023
- sim.addClock("clk", { period: 10 });
1024
-
1025
- // Reset (default async_low: rst=0 is active)
1026
- sim.dut.rst = 0n;
1027
- sim.runUntil(20);
1028
- sim.dut.rst = 1n;
1029
- sim.dut.en = 1n;
1030
-
1031
- const beforeTime = sim.time();
1032
- const afterTime = sim.waitForCycles("clk", 5);
1033
-
1034
- expect(afterTime).toBeGreaterThan(beforeTime);
1035
- // Each cycle = 2 steps with period 10, so 5 cycles ≈ 50 time units
1036
- expect(afterTime - beforeTime).toBe(50);
1037
-
1038
- sim.dispose();
1039
- });
1040
-
1041
- test("waitUntil: waits for condition to be met", () => {
1042
- interface CounterPorts {
1043
- rst: bigint;
1044
- en: bigint;
1045
- readonly count: bigint;
1046
- }
1047
-
1048
- const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1049
- sim.addClock("clk", { period: 10 });
1050
-
1051
- // Reset (default async_low: rst=0 is active)
1052
- sim.dut.rst = 0n;
1053
- sim.runUntil(20);
1054
- sim.dut.rst = 1n;
1055
- sim.dut.en = 1n;
1056
-
1057
- const t = sim.waitUntil(() => sim.dut.count >= 3n);
1058
- expect(sim.dut.count).toBeGreaterThanOrEqual(3n);
1059
- expect(t).toBeGreaterThan(20);
1060
-
1061
- sim.dispose();
1062
- });
1063
-
1064
- test("waitUntil: throws SimulationTimeoutError on timeout", () => {
1065
- interface CounterPorts {
1066
- rst: bigint;
1067
- en: bigint;
1068
- readonly count: bigint;
1069
- }
1070
-
1071
- const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1072
- sim.addClock("clk", { period: 10 });
1073
-
1074
- // Reset (default async_low: rst=0 is active)
1075
- sim.dut.rst = 0n;
1076
- sim.runUntil(20);
1077
- sim.dut.rst = 1n;
1078
- sim.dut.en = 0n; // disabled — count won't increase
1079
-
1080
- expect(() =>
1081
- sim.waitUntil(() => sim.dut.count >= 100n, { maxSteps: 20 }),
1082
- ).toThrow(SimulationTimeoutError);
1083
-
1084
- sim.dispose();
1085
- });
1086
-
1087
- test("reset: asserts and releases reset on counter", () => {
1088
- interface CounterPorts {
1089
- rst: bigint;
1090
- en: bigint;
1091
- readonly count: bigint;
1092
- }
1093
-
1094
- const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1095
- sim.addClock("clk", { period: 10 });
1096
-
1097
- // Count up a bit (default async_low: rst=0 is active)
1098
- sim.dut.rst = 0n;
1099
- sim.runUntil(20);
1100
- sim.dut.rst = 1n;
1101
- sim.dut.en = 1n;
1102
- sim.runUntil(100);
1103
- expect(sim.dut.count).toBeGreaterThan(0n);
1104
-
1105
- // Reset using the helper
1106
- sim.reset("rst");
1107
- expect(sim.dut.count).toBe(0n);
1108
-
1109
- sim.dispose();
1110
- });
1111
-
1112
- test("reset: explicit async_low resetType activates with 0", () => {
1113
- interface CounterPorts {
1114
- rst: bigint;
1115
- en: bigint;
1116
- readonly count: bigint;
1117
- }
1118
-
1119
- const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1120
- resetType: "async_low",
1121
- });
1122
- sim.addClock("clk", { period: 10 });
1123
-
1124
- // With async_low, rst=0 is active, rst=1 is inactive
1125
- sim.reset("rst");
1126
-
1127
- sim.dut.en = 1n;
1128
- sim.runUntil(100);
1129
- expect(sim.dut.count).toBeGreaterThan(0n);
1130
-
1131
- // Reset again using helper, verify it resets the counter
1132
- sim.reset("rst");
1133
- expect(sim.dut.count).toBe(0n);
1134
-
1135
- sim.dispose();
1136
- });
1137
-
1138
- test("reset: explicit async_high resetType activates with 1", () => {
1139
- interface CounterPorts {
1140
- rst: bigint;
1141
- en: bigint;
1142
- readonly count: bigint;
1143
- }
1144
-
1145
- const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1146
- resetType: "async_high",
1147
- });
1148
- sim.addClock("clk", { period: 10 });
1149
-
1150
- // With async_high, rst=1 is active, rst=0 is inactive
1151
- sim.reset("rst");
1152
-
1153
- sim.dut.en = 1n;
1154
- sim.runUntil(100);
1155
- expect(sim.dut.count).toBeGreaterThan(0n);
1156
-
1157
- // Reset again
1158
- sim.reset("rst");
1159
- expect(sim.dut.count).toBe(0n);
1160
-
1161
- sim.dispose();
1162
- });
1163
-
1164
- test("Simulator.fromSource: resetType option works", () => {
1165
- interface CounterPorts {
1166
- rst: bigint;
1167
- en: bigint;
1168
- readonly count: bigint;
1169
- }
1170
-
1171
- const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1172
- resetType: "async_high",
1173
- });
1174
-
1175
- // With async_high, assert reset with 1
1176
- sim.dut.rst = 1n;
1177
- sim.tick();
1178
- sim.dut.rst = 0n;
1179
- sim.tick();
1180
- expect(sim.dut.count).toBe(0n);
1181
-
1182
- // Count up
1183
- sim.dut.en = 1n;
1184
- sim.tick();
1185
- expect(sim.dut.count).toBe(1n);
1186
-
1187
- sim.dispose();
1188
- });
1189
-
1190
- test("runUntil with maxSteps: succeeds within budget", () => {
1191
- interface CounterPorts {
1192
- rst: bigint;
1193
- en: bigint;
1194
- readonly count: bigint;
1195
- }
1196
-
1197
- const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1198
- sim.addClock("clk", { period: 10 });
1199
-
1200
- // Reset (default async_low: rst=0 is active)
1201
- sim.dut.rst = 0n;
1202
- sim.runUntil(20);
1203
- sim.dut.rst = 1n;
1204
- sim.dut.en = 1n;
1205
-
1206
- // 100 time units with period 10 = 10 events, should fit in 100 steps
1207
- sim.runUntil(120, { maxSteps: 100 });
1208
- expect(sim.time()).toBe(120);
1209
-
1210
- sim.dispose();
1211
- });
1212
-
1213
- test("runUntil with maxSteps: throws on exceeded budget", () => {
1214
- interface CounterPorts {
1215
- rst: bigint;
1216
- en: bigint;
1217
- readonly count: bigint;
1218
- }
1219
-
1220
- const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1221
- sim.addClock("clk", { period: 10 });
1222
-
1223
- // Release reset so counter can count (default async_low: rst=1 is inactive)
1224
- sim.dut.rst = 1n;
1225
- sim.dut.en = 1n;
1226
-
1227
- // Very small budget for a long run
1228
- expect(() => sim.runUntil(100000, { maxSteps: 5 })).toThrow(
1229
- SimulationTimeoutError,
1230
- );
1231
-
1232
- sim.dispose();
1233
- });
1095
+ test("waitForCycles: advances correct number of clock cycles", () => {
1096
+ interface CounterPorts {
1097
+ rst: bigint;
1098
+ en: bigint;
1099
+ readonly count: bigint;
1100
+ }
1101
+
1102
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1103
+ sim.addClock("clk", { period: 10 });
1104
+
1105
+ // Reset (default async_low: rst=0 is active)
1106
+ sim.dut.rst = 0n;
1107
+ sim.runUntil(20);
1108
+ sim.dut.rst = 1n;
1109
+ sim.dut.en = 1n;
1110
+
1111
+ const beforeTime = sim.time();
1112
+ const afterTime = sim.waitForCycles("clk", 5);
1113
+
1114
+ expect(afterTime).toBeGreaterThan(beforeTime);
1115
+ // Each cycle = 2 steps with period 10, so 5 cycles ≈ 50 time units
1116
+ expect(afterTime - beforeTime).toBe(50);
1117
+
1118
+ sim.dispose();
1119
+ });
1120
+
1121
+ test("waitUntil: waits for condition to be met", () => {
1122
+ interface CounterPorts {
1123
+ rst: bigint;
1124
+ en: bigint;
1125
+ readonly count: bigint;
1126
+ }
1127
+
1128
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1129
+ sim.addClock("clk", { period: 10 });
1130
+
1131
+ // Reset (default async_low: rst=0 is active)
1132
+ sim.dut.rst = 0n;
1133
+ sim.runUntil(20);
1134
+ sim.dut.rst = 1n;
1135
+ sim.dut.en = 1n;
1136
+
1137
+ const t = sim.waitUntil(() => sim.dut.count >= 3n);
1138
+ expect(sim.dut.count).toBeGreaterThanOrEqual(3n);
1139
+ expect(t).toBeGreaterThan(20);
1140
+
1141
+ sim.dispose();
1142
+ });
1143
+
1144
+ test("waitUntil: throws SimulationTimeoutError on timeout", () => {
1145
+ interface CounterPorts {
1146
+ rst: bigint;
1147
+ en: bigint;
1148
+ readonly count: bigint;
1149
+ }
1150
+
1151
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1152
+ sim.addClock("clk", { period: 10 });
1153
+
1154
+ // Reset (default async_low: rst=0 is active)
1155
+ sim.dut.rst = 0n;
1156
+ sim.runUntil(20);
1157
+ sim.dut.rst = 1n;
1158
+ sim.dut.en = 0n; // disabled — count won't increase
1159
+
1160
+ expect(() =>
1161
+ sim.waitUntil(() => sim.dut.count >= 100n, { maxSteps: 20 }),
1162
+ ).toThrow(SimulationTimeoutError);
1163
+
1164
+ sim.dispose();
1165
+ });
1166
+
1167
+ test("reset: asserts and releases reset on counter", () => {
1168
+ interface CounterPorts {
1169
+ rst: bigint;
1170
+ en: bigint;
1171
+ readonly count: bigint;
1172
+ }
1173
+
1174
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1175
+ sim.addClock("clk", { period: 10 });
1176
+
1177
+ // Count up a bit (default async_low: rst=0 is active)
1178
+ sim.dut.rst = 0n;
1179
+ sim.runUntil(20);
1180
+ sim.dut.rst = 1n;
1181
+ sim.dut.en = 1n;
1182
+ sim.runUntil(100);
1183
+ expect(sim.dut.count).toBeGreaterThan(0n);
1184
+
1185
+ // Reset using the helper
1186
+ sim.reset("rst");
1187
+ expect(sim.dut.count).toBe(0n);
1188
+
1189
+ sim.dispose();
1190
+ });
1191
+
1192
+ test("reset: explicit async_low resetType activates with 0", () => {
1193
+ interface CounterPorts {
1194
+ rst: bigint;
1195
+ en: bigint;
1196
+ readonly count: bigint;
1197
+ }
1198
+
1199
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1200
+ resetType: "async_low",
1201
+ });
1202
+ sim.addClock("clk", { period: 10 });
1203
+
1204
+ // With async_low, rst=0 is active, rst=1 is inactive
1205
+ sim.reset("rst");
1206
+
1207
+ sim.dut.en = 1n;
1208
+ sim.runUntil(100);
1209
+ expect(sim.dut.count).toBeGreaterThan(0n);
1210
+
1211
+ // Reset again using helper, verify it resets the counter
1212
+ sim.reset("rst");
1213
+ expect(sim.dut.count).toBe(0n);
1214
+
1215
+ sim.dispose();
1216
+ });
1217
+
1218
+ test("reset: explicit async_high resetType activates with 1", () => {
1219
+ interface CounterPorts {
1220
+ rst: bigint;
1221
+ en: bigint;
1222
+ readonly count: bigint;
1223
+ }
1224
+
1225
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1226
+ resetType: "async_high",
1227
+ });
1228
+ sim.addClock("clk", { period: 10 });
1229
+
1230
+ // With async_high, rst=1 is active, rst=0 is inactive
1231
+ sim.reset("rst");
1232
+
1233
+ sim.dut.en = 1n;
1234
+ sim.runUntil(100);
1235
+ expect(sim.dut.count).toBeGreaterThan(0n);
1236
+
1237
+ // Reset again
1238
+ sim.reset("rst");
1239
+ expect(sim.dut.count).toBe(0n);
1240
+
1241
+ sim.dispose();
1242
+ });
1243
+
1244
+ test("Simulator.fromSource: resetType option works", () => {
1245
+ interface CounterPorts {
1246
+ rst: bigint;
1247
+ en: bigint;
1248
+ readonly count: bigint;
1249
+ }
1250
+
1251
+ const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1252
+ resetType: "async_high",
1253
+ });
1254
+
1255
+ // With async_high, assert reset with 1
1256
+ sim.dut.rst = 1n;
1257
+ sim.tick();
1258
+ sim.dut.rst = 0n;
1259
+ sim.tick();
1260
+ expect(sim.dut.count).toBe(0n);
1261
+
1262
+ // Count up
1263
+ sim.dut.en = 1n;
1264
+ sim.tick();
1265
+ expect(sim.dut.count).toBe(1n);
1266
+
1267
+ sim.dispose();
1268
+ });
1269
+
1270
+ test("runUntil with maxSteps: succeeds within budget", () => {
1271
+ interface CounterPorts {
1272
+ rst: bigint;
1273
+ en: bigint;
1274
+ readonly count: bigint;
1275
+ }
1276
+
1277
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1278
+ sim.addClock("clk", { period: 10 });
1279
+
1280
+ // Reset (default async_low: rst=0 is active)
1281
+ sim.dut.rst = 0n;
1282
+ sim.runUntil(20);
1283
+ sim.dut.rst = 1n;
1284
+ sim.dut.en = 1n;
1285
+
1286
+ // 100 time units with period 10 = 10 events, should fit in 100 steps
1287
+ sim.runUntil(120, { maxSteps: 100 });
1288
+ expect(sim.time()).toBe(120);
1289
+
1290
+ sim.dispose();
1291
+ });
1292
+
1293
+ test("runUntil with maxSteps: throws on exceeded budget", () => {
1294
+ interface CounterPorts {
1295
+ rst: bigint;
1296
+ en: bigint;
1297
+ readonly count: bigint;
1298
+ }
1299
+
1300
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1301
+ sim.addClock("clk", { period: 10 });
1302
+
1303
+ // Release reset so counter can count (default async_low: rst=1 is inactive)
1304
+ sim.dut.rst = 1n;
1305
+ sim.dut.en = 1n;
1306
+
1307
+ // Very small budget for a long run
1308
+ expect(() => sim.runUntil(100000, { maxSteps: 5 })).toThrow(
1309
+ SimulationTimeoutError,
1310
+ );
1311
+
1312
+ sim.dispose();
1313
+ });
1234
1314
  });
1235
1315
 
1236
1316
  // ---------------------------------------------------------------------------
@@ -1238,89 +1318,89 @@ describe("E2E: Simulation testbench helpers", () => {
1238
1318
  // ---------------------------------------------------------------------------
1239
1319
 
1240
1320
  describe("E2E: fourState() method", () => {
1241
- test("Simulator.fourState: reads 4-state value and mask", () => {
1242
- interface Ports {
1243
- a: bigint;
1244
- b: bigint;
1245
- readonly y: bigint;
1246
- }
1247
-
1248
- const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1249
- fourState: true,
1250
- });
1251
-
1252
- sim.dut.a = 100n;
1253
- sim.dut.b = 55n;
1254
- // Trigger evalComb via output read (Adder4S is purely combinational)
1255
- expect(sim.dut.y).toBe(155n);
1256
-
1257
- const fs = sim.fourState("y");
1258
- expect(fs.__fourState).toBe(true);
1259
- expect(fs.value).toBe(155n);
1260
- expect(fs.mask).toBe(0n);
1261
-
1262
- sim.dispose();
1263
- });
1264
-
1265
- test("Simulator.fourState: reads X mask when input is X", () => {
1266
- interface Ports {
1267
- a: bigint;
1268
- b: bigint;
1269
- readonly y: bigint;
1270
- }
1271
-
1272
- const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1273
- fourState: true,
1274
- });
1275
-
1276
- (sim.dut as any).a = X;
1277
- sim.dut.b = 10n;
1278
- // Trigger evalComb via output read
1279
- sim.dut.y;
1280
-
1281
- const fs = sim.fourState("y");
1282
- expect(fs.mask).toBe(0xFFn); // all X from arithmetic propagation
1283
-
1284
- sim.dispose();
1285
- });
1286
-
1287
- test("Simulation.fourState: reads 4-state value", () => {
1288
- interface Ports {
1289
- a: bigint;
1290
- b: bigint;
1291
- readonly y: bigint;
1292
- }
1293
-
1294
- const sim = Simulation.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1295
- fourState: true,
1296
- });
1297
-
1298
- sim.dut.a = 50n;
1299
- sim.dut.b = 25n;
1300
- sim.runUntil(0);
1301
-
1302
- const fs = sim.fourState("y");
1303
- expect(fs.value).toBe(75n);
1304
- expect(fs.mask).toBe(0n);
1305
-
1306
- sim.dispose();
1307
- });
1308
-
1309
- test("fourState: throws for unknown port", () => {
1310
- interface Ports {
1311
- a: bigint;
1312
- b: bigint;
1313
- readonly y: bigint;
1314
- }
1315
-
1316
- const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1317
- fourState: true,
1318
- });
1319
-
1320
- expect(() => sim.fourState("nonexistent")).toThrow("Unknown port");
1321
-
1322
- sim.dispose();
1323
- });
1321
+ test("Simulator.fourState: reads 4-state value and mask", () => {
1322
+ interface Ports {
1323
+ a: bigint;
1324
+ b: bigint;
1325
+ readonly y: bigint;
1326
+ }
1327
+
1328
+ const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1329
+ fourState: true,
1330
+ });
1331
+
1332
+ sim.dut.a = 100n;
1333
+ sim.dut.b = 55n;
1334
+ // Trigger evalComb via output read (Adder4S is purely combinational)
1335
+ expect(sim.dut.y).toBe(155n);
1336
+
1337
+ const fs = sim.fourState("y");
1338
+ expect(fs.__fourState).toBe(true);
1339
+ expect(fs.value).toBe(155n);
1340
+ expect(fs.mask).toBe(0n);
1341
+
1342
+ sim.dispose();
1343
+ });
1344
+
1345
+ test("Simulator.fourState: reads X mask when input is X", () => {
1346
+ interface Ports {
1347
+ a: bigint;
1348
+ b: bigint;
1349
+ readonly y: bigint;
1350
+ }
1351
+
1352
+ const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1353
+ fourState: true,
1354
+ });
1355
+
1356
+ (sim.dut as any).a = X;
1357
+ sim.dut.b = 10n;
1358
+ // Trigger evalComb via output read
1359
+ sim.dut.y;
1360
+
1361
+ const fs = sim.fourState("y");
1362
+ expect(fs.mask).toBe(0xffn); // all X from arithmetic propagation
1363
+
1364
+ sim.dispose();
1365
+ });
1366
+
1367
+ test("Simulation.fourState: reads 4-state value", () => {
1368
+ interface Ports {
1369
+ a: bigint;
1370
+ b: bigint;
1371
+ readonly y: bigint;
1372
+ }
1373
+
1374
+ const sim = Simulation.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1375
+ fourState: true,
1376
+ });
1377
+
1378
+ sim.dut.a = 50n;
1379
+ sim.dut.b = 25n;
1380
+ sim.runUntil(0);
1381
+
1382
+ const fs = sim.fourState("y");
1383
+ expect(fs.value).toBe(75n);
1384
+ expect(fs.mask).toBe(0n);
1385
+
1386
+ sim.dispose();
1387
+ });
1388
+
1389
+ test("fourState: throws for unknown port", () => {
1390
+ interface Ports {
1391
+ a: bigint;
1392
+ b: bigint;
1393
+ readonly y: bigint;
1394
+ }
1395
+
1396
+ const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1397
+ fourState: true,
1398
+ });
1399
+
1400
+ expect(() => sim.fourState("nonexistent")).toThrow("Unknown port");
1401
+
1402
+ sim.dispose();
1403
+ });
1324
1404
  });
1325
1405
 
1326
1406
  // ---------------------------------------------------------------------------
@@ -1328,50 +1408,50 @@ describe("E2E: fourState() method", () => {
1328
1408
  // ---------------------------------------------------------------------------
1329
1409
 
1330
1410
  describe("E2E: optimize flag", () => {
1331
- test("Simulator.fromSource with optimize: true", () => {
1332
- interface AdderPorts {
1333
- rst: bigint;
1334
- a: bigint;
1335
- b: bigint;
1336
- readonly sum: bigint;
1337
- }
1338
-
1339
- const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder", {
1340
- optimize: true,
1341
- });
1342
-
1343
- sim.dut.a = 100n;
1344
- sim.dut.b = 200n;
1345
- sim.tick();
1346
- expect(sim.dut.sum).toBe(300n);
1347
-
1348
- sim.dispose();
1349
- });
1350
-
1351
- test("Simulation.fromSource with optimize: true", () => {
1352
- interface CounterPorts {
1353
- rst: bigint;
1354
- en: bigint;
1355
- readonly count: bigint;
1356
- }
1357
-
1358
- const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1359
- optimize: true,
1360
- });
1361
-
1362
- sim.addClock("clk", { period: 10 });
1363
-
1364
- // Reset (default async_low: rst=0 is active)
1365
- sim.dut.rst = 0n;
1366
- sim.runUntil(20);
1367
- sim.dut.rst = 1n;
1368
- sim.dut.en = 1n;
1369
- sim.runUntil(100);
1370
-
1371
- expect(sim.dut.count).toBeGreaterThan(0n);
1372
-
1373
- sim.dispose();
1374
- });
1411
+ test("Simulator.fromSource with optimize: true", () => {
1412
+ interface AdderPorts {
1413
+ rst: bigint;
1414
+ a: bigint;
1415
+ b: bigint;
1416
+ readonly sum: bigint;
1417
+ }
1418
+
1419
+ const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder", {
1420
+ optimize: true,
1421
+ });
1422
+
1423
+ sim.dut.a = 100n;
1424
+ sim.dut.b = 200n;
1425
+ sim.tick();
1426
+ expect(sim.dut.sum).toBe(300n);
1427
+
1428
+ sim.dispose();
1429
+ });
1430
+
1431
+ test("Simulation.fromSource with optimize: true", () => {
1432
+ interface CounterPorts {
1433
+ rst: bigint;
1434
+ en: bigint;
1435
+ readonly count: bigint;
1436
+ }
1437
+
1438
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1439
+ optimize: true,
1440
+ });
1441
+
1442
+ sim.addClock("clk", { period: 10 });
1443
+
1444
+ // Reset (default async_low: rst=0 is active)
1445
+ sim.dut.rst = 0n;
1446
+ sim.runUntil(20);
1447
+ sim.dut.rst = 1n;
1448
+ sim.dut.en = 1n;
1449
+ sim.runUntil(100);
1450
+
1451
+ expect(sim.dut.count).toBeGreaterThan(0n);
1452
+
1453
+ sim.dispose();
1454
+ });
1375
1455
  });
1376
1456
 
1377
1457
  // ---------------------------------------------------------------------------
@@ -1379,32 +1459,32 @@ describe("E2E: optimize flag", () => {
1379
1459
  // ---------------------------------------------------------------------------
1380
1460
 
1381
1461
  describe("parseSignalPath", () => {
1382
- test("simple variable path", () => {
1383
- const result = parseSignalPath("v");
1384
- expect(result.instancePath).toEqual([]);
1385
- expect(result.varPath).toEqual(["v"]);
1386
- });
1387
-
1388
- test("instance:variable split", () => {
1389
- const result = parseSignalPath("p2:i");
1390
- expect(result.instancePath).toEqual([{ name: "p2", index: 0 }]);
1391
- expect(result.varPath).toEqual(["i"]);
1392
- });
1393
-
1394
- test("nested instance with array index", () => {
1395
- const result = parseSignalPath("a.b[3]:x.y");
1396
- expect(result.instancePath).toEqual([
1397
- { name: "a", index: 0 },
1398
- { name: "b", index: 3 },
1399
- ]);
1400
- expect(result.varPath).toEqual(["x", "y"]);
1401
- });
1402
-
1403
- test("dotted variable path without instance", () => {
1404
- const result = parseSignalPath("foo.bar.baz");
1405
- expect(result.instancePath).toEqual([]);
1406
- expect(result.varPath).toEqual(["foo", "bar", "baz"]);
1407
- });
1462
+ test("simple variable path", () => {
1463
+ const result = parseSignalPath("v");
1464
+ expect(result.instancePath).toEqual([]);
1465
+ expect(result.varPath).toEqual(["v"]);
1466
+ });
1467
+
1468
+ test("instance:variable split", () => {
1469
+ const result = parseSignalPath("p2:i");
1470
+ expect(result.instancePath).toEqual([{ name: "p2", index: 0 }]);
1471
+ expect(result.varPath).toEqual(["i"]);
1472
+ });
1473
+
1474
+ test("nested instance with array index", () => {
1475
+ const result = parseSignalPath("a.b[3]:x.y");
1476
+ expect(result.instancePath).toEqual([
1477
+ { name: "a", index: 0 },
1478
+ { name: "b", index: 3 },
1479
+ ]);
1480
+ expect(result.varPath).toEqual(["x", "y"]);
1481
+ });
1482
+
1483
+ test("dotted variable path without instance", () => {
1484
+ const result = parseSignalPath("foo.bar.baz");
1485
+ expect(result.instancePath).toEqual([]);
1486
+ expect(result.varPath).toEqual(["foo", "bar", "baz"]);
1487
+ });
1408
1488
  });
1409
1489
 
1410
1490
  // ---------------------------------------------------------------------------
@@ -1486,179 +1566,195 @@ module ParamChild #(
1486
1566
  `;
1487
1567
 
1488
1568
  describe("E2E: child instance DUT access", () => {
1489
- test("Simulator: read child instance ports via dut.u_sub", () => {
1490
- const sim = Simulator.fromSource(HIERARCHY_SOURCE, "Top");
1569
+ test("Simulator: read child instance ports via dut.u_sub", () => {
1570
+ const sim = Simulator.fromSource(HIERARCHY_SOURCE, "Top");
1491
1571
 
1492
- sim.dut.top_in = 0xABn;
1493
- sim.tick();
1494
- expect(sim.dut.top_out).toBe(0xABn);
1572
+ sim.dut.top_in = 0xabn;
1573
+ sim.tick();
1574
+ expect(sim.dut.top_out).toBe(0xabn);
1495
1575
 
1496
- // Read the same value through the child instance accessor
1497
- expect((sim.dut as any).u_sub.o_data).toBe(0xABn);
1498
- expect((sim.dut as any).u_sub.i_data).toBe(0xABn);
1576
+ // Read the same value through the child instance accessor
1577
+ expect((sim.dut as any).u_sub.o_data).toBe(0xabn);
1578
+ expect((sim.dut as any).u_sub.i_data).toBe(0xabn);
1499
1579
 
1500
- // Change top-level input and verify child reflects it
1501
- sim.dut.top_in = 0x42n;
1502
- sim.tick();
1503
- expect((sim.dut as any).u_sub.i_data).toBe(0x42n);
1504
- expect((sim.dut as any).u_sub.o_data).toBe(0x42n);
1580
+ // Change top-level input and verify child reflects it
1581
+ sim.dut.top_in = 0x42n;
1582
+ sim.tick();
1583
+ expect((sim.dut as any).u_sub.i_data).toBe(0x42n);
1584
+ expect((sim.dut as any).u_sub.o_data).toBe(0x42n);
1505
1585
 
1506
- sim.dispose();
1507
- });
1586
+ sim.dispose();
1587
+ });
1508
1588
 
1509
- test("Simulation: read child instance ports via dut.u_sub", () => {
1510
- const sim = Simulation.fromSource(HIERARCHY_SOURCE, "Top");
1511
- sim.addClock("clk", { period: 10 });
1589
+ test("Simulation: read child instance ports via dut.u_sub", () => {
1590
+ const sim = Simulation.fromSource(HIERARCHY_SOURCE, "Top");
1591
+ sim.addClock("clk", { period: 10 });
1512
1592
 
1513
- sim.dut.rst = 0n;
1514
- sim.runUntil(20);
1515
- sim.dut.rst = 1n;
1593
+ sim.dut.rst = 0n;
1594
+ sim.runUntil(20);
1595
+ sim.dut.rst = 1n;
1516
1596
 
1517
- sim.dut.top_in = 0x55n;
1518
- sim.runUntil(40);
1519
- expect((sim.dut as any).u_sub.o_data).toBe(0x55n);
1520
- expect((sim.dut as any).u_sub.i_data).toBe(0x55n);
1597
+ sim.dut.top_in = 0x55n;
1598
+ sim.runUntil(40);
1599
+ expect((sim.dut as any).u_sub.o_data).toBe(0x55n);
1600
+ expect((sim.dut as any).u_sub.i_data).toBe(0x55n);
1521
1601
 
1522
- sim.dut.top_in = 0xFFn;
1523
- sim.runUntil(60);
1524
- expect((sim.dut as any).u_sub.o_data).toBe(0xFFn);
1602
+ sim.dut.top_in = 0xffn;
1603
+ sim.runUntil(60);
1604
+ expect((sim.dut as any).u_sub.o_data).toBe(0xffn);
1525
1605
 
1526
- sim.dispose();
1527
- });
1606
+ sim.dispose();
1607
+ });
1528
1608
  });
1529
1609
 
1530
1610
  describe("E2E: parameter override — DUT correctness", () => {
1531
- test("scalar width override: WIDTH 8→16 via fromSource", () => {
1532
- interface Ports {
1533
- a: bigint;
1534
- readonly b: bigint;
1535
- }
1536
-
1537
- // Default WIDTH=8 (purely combinational — read output to trigger evalComb)
1538
- const sim8 = Simulator.fromSource<Ports>(PARAM_WIDTH_SOURCE, "ParamWidth");
1539
- sim8.dut.a = 0xABn;
1540
- expect(sim8.dut.b).toBe(0xABn);
1541
- // Writing a 16-bit value to an 8-bit port should truncate
1542
- sim8.dut.a = 0xABCDn;
1543
- expect(sim8.dut.b).toBe(0xCDn); // truncated to 8 bits
1544
- sim8.dispose();
1545
-
1546
- // Override WIDTH=16 — DUT should handle 16-bit values
1547
- const sim16 = Simulator.fromSource<Ports>(PARAM_WIDTH_SOURCE, "ParamWidth", {
1548
- parameters: [{ name: "WIDTH", value: 16 }],
1549
- });
1550
- sim16.dut.a = 0xABCDn;
1551
- expect(sim16.dut.b).toBe(0xABCDn); // full 16-bit value preserved
1552
- sim16.dispose();
1553
- });
1554
-
1555
- test("scalar width override: WIDTH 8→32 via fromSource", () => {
1556
- interface Ports {
1557
- a: bigint;
1558
- readonly b: bigint;
1559
- }
1560
-
1561
- const sim = Simulator.fromSource<Ports>(PARAM_WIDTH_SOURCE, "ParamWidth", {
1562
- parameters: [{ name: "WIDTH", value: 32 }],
1563
- });
1564
- sim.dut.a = 0xDEAD_BEEFn;
1565
- expect(sim.dut.b).toBe(0xDEAD_BEEFn);
1566
- sim.dispose();
1567
- });
1568
-
1569
- test.skip("param value reflected in logic via fromSource", () => { // blocked by upstream Veryl IR bug
1570
- interface Ports {
1571
- a: bigint;
1572
- readonly b: bigint;
1573
- }
1574
-
1575
- // Default OFFSET=10
1576
- const sim10 = Simulator.fromSource<Ports>(PARAM_OFFSET_SOURCE, "ParamOffset");
1577
- sim10.dut.a = 5n;
1578
- expect(sim10.dut.b).toBe(15n);
1579
- sim10.dispose();
1580
-
1581
- // Override OFFSET=100
1582
- const sim100 = Simulator.fromSource<Ports>(PARAM_OFFSET_SOURCE, "ParamOffset", {
1583
- parameters: [{ name: "OFFSET", value: 100 }],
1584
- });
1585
- sim100.dut.a = 5n;
1586
- expect(sim100.dut.b).toBe(105n);
1587
- sim100.dispose();
1588
- });
1589
-
1590
- test("child module param propagation via fromSource", () => {
1591
- interface Ports {
1592
- a: bigint;
1593
- readonly b: bigint;
1594
- }
1595
-
1596
- // Default WIDTH=8
1597
- const sim8 = Simulator.fromSource<Ports>(PARAM_CHILD_SOURCE, "ParamChild");
1598
- sim8.dut.a = 0xABn;
1599
- expect(sim8.dut.b).toBe(0xABn);
1600
- sim8.dispose();
1601
-
1602
- // Override WIDTH=16 child also gets 16-bit ports
1603
- const sim16 = Simulator.fromSource<Ports>(PARAM_CHILD_SOURCE, "ParamChild", {
1604
- parameters: [{ name: "WIDTH", value: 16 }],
1605
- });
1606
- sim16.dut.a = 0xABCDn;
1607
- expect(sim16.dut.b).toBe(0xABCDn);
1608
- sim16.dispose();
1609
- });
1610
-
1611
- test("Simulator.create() with stale ModuleDefinition + param override", () => {
1612
- // This is the most dangerous case: ModuleDefinition has width=8 (stale),
1613
- // but we override WIDTH=16. The DUT must use runtime-derived ports
1614
- // from hierarchy, not the stale module.ports.
1615
- interface Ports {
1616
- a: bigint;
1617
- readonly b: bigint;
1618
- }
1619
-
1620
- const addon = loadNativeAddon();
1621
- const nativeCreate = createSimulatorBridge(addon);
1622
-
1623
- const sim = Simulator.create<Ports>(
1624
- {
1625
- __celox_module: true,
1626
- name: "ParamWidth",
1627
- source: PARAM_WIDTH_SOURCE,
1628
- ports: {
1629
- // STALE: these say width=8, but we'll override to 16
1630
- a: { direction: "input", type: "logic", width: 8 },
1631
- b: { direction: "output", type: "logic", width: 8 },
1632
- },
1633
- events: [],
1634
- },
1635
- {
1636
- __nativeCreate: nativeCreate,
1637
- parameters: [{ name: "WIDTH", value: 16 }],
1638
- },
1639
- );
1640
-
1641
- // If DUT used stale ports (width=8), this would truncate or corrupt
1642
- sim.dut.a = 0xABCDn;
1643
- expect(sim.dut.b).toBe(0xABCDn); // full 16-bit value preserved
1644
- sim.dispose();
1645
- });
1646
-
1647
- test("Simulation.fromSource with param override", () => {
1648
- interface Ports {
1649
- a: bigint;
1650
- readonly b: bigint;
1651
- }
1652
-
1653
- const sim = Simulation.fromSource<Ports>(PARAM_WIDTH_SOURCE, "ParamWidth", {
1654
- parameters: [{ name: "WIDTH", value: 16 }],
1655
- });
1656
-
1657
- sim.dut.a = 0xFEDCn;
1658
- sim.runUntil(0);
1659
- expect(sim.dut.b).toBe(0xFEDCn);
1660
- sim.dispose();
1661
- });
1611
+ test("scalar width override: WIDTH 8→16 via fromSource", () => {
1612
+ interface Ports {
1613
+ a: bigint;
1614
+ readonly b: bigint;
1615
+ }
1616
+
1617
+ // Default WIDTH=8 (purely combinational — read output to trigger evalComb)
1618
+ const sim8 = Simulator.fromSource<Ports>(PARAM_WIDTH_SOURCE, "ParamWidth");
1619
+ sim8.dut.a = 0xabn;
1620
+ expect(sim8.dut.b).toBe(0xabn);
1621
+ // Writing a 16-bit value to an 8-bit port should truncate
1622
+ sim8.dut.a = 0xabcdn;
1623
+ expect(sim8.dut.b).toBe(0xcdn); // truncated to 8 bits
1624
+ sim8.dispose();
1625
+
1626
+ // Override WIDTH=16 — DUT should handle 16-bit values
1627
+ const sim16 = Simulator.fromSource<Ports>(
1628
+ PARAM_WIDTH_SOURCE,
1629
+ "ParamWidth",
1630
+ {
1631
+ parameters: [{ name: "WIDTH", value: 16 }],
1632
+ },
1633
+ );
1634
+ sim16.dut.a = 0xabcdn;
1635
+ expect(sim16.dut.b).toBe(0xabcdn); // full 16-bit value preserved
1636
+ sim16.dispose();
1637
+ });
1638
+
1639
+ test("scalar width override: WIDTH 8→32 via fromSource", () => {
1640
+ interface Ports {
1641
+ a: bigint;
1642
+ readonly b: bigint;
1643
+ }
1644
+
1645
+ const sim = Simulator.fromSource<Ports>(PARAM_WIDTH_SOURCE, "ParamWidth", {
1646
+ parameters: [{ name: "WIDTH", value: 32 }],
1647
+ });
1648
+ sim.dut.a = 0xdead_beefn;
1649
+ expect(sim.dut.b).toBe(0xdead_beefn);
1650
+ sim.dispose();
1651
+ });
1652
+
1653
+ test.skip("param value reflected in logic via fromSource", () => {
1654
+ // blocked by upstream Veryl IR bug
1655
+ interface Ports {
1656
+ a: bigint;
1657
+ readonly b: bigint;
1658
+ }
1659
+
1660
+ // Default OFFSET=10
1661
+ const sim10 = Simulator.fromSource<Ports>(
1662
+ PARAM_OFFSET_SOURCE,
1663
+ "ParamOffset",
1664
+ );
1665
+ sim10.dut.a = 5n;
1666
+ expect(sim10.dut.b).toBe(15n);
1667
+ sim10.dispose();
1668
+
1669
+ // Override OFFSET=100
1670
+ const sim100 = Simulator.fromSource<Ports>(
1671
+ PARAM_OFFSET_SOURCE,
1672
+ "ParamOffset",
1673
+ {
1674
+ parameters: [{ name: "OFFSET", value: 100 }],
1675
+ },
1676
+ );
1677
+ sim100.dut.a = 5n;
1678
+ expect(sim100.dut.b).toBe(105n);
1679
+ sim100.dispose();
1680
+ });
1681
+
1682
+ test("child module param propagation via fromSource", () => {
1683
+ interface Ports {
1684
+ a: bigint;
1685
+ readonly b: bigint;
1686
+ }
1687
+
1688
+ // Default WIDTH=8
1689
+ const sim8 = Simulator.fromSource<Ports>(PARAM_CHILD_SOURCE, "ParamChild");
1690
+ sim8.dut.a = 0xabn;
1691
+ expect(sim8.dut.b).toBe(0xabn);
1692
+ sim8.dispose();
1693
+
1694
+ // Override WIDTH=16 child also gets 16-bit ports
1695
+ const sim16 = Simulator.fromSource<Ports>(
1696
+ PARAM_CHILD_SOURCE,
1697
+ "ParamChild",
1698
+ {
1699
+ parameters: [{ name: "WIDTH", value: 16 }],
1700
+ },
1701
+ );
1702
+ sim16.dut.a = 0xabcdn;
1703
+ expect(sim16.dut.b).toBe(0xabcdn);
1704
+ sim16.dispose();
1705
+ });
1706
+
1707
+ test("Simulator.create() with stale ModuleDefinition + param override", () => {
1708
+ // This is the most dangerous case: ModuleDefinition has width=8 (stale),
1709
+ // but we override WIDTH=16. The DUT must use runtime-derived ports
1710
+ // from hierarchy, not the stale module.ports.
1711
+ interface Ports {
1712
+ a: bigint;
1713
+ readonly b: bigint;
1714
+ }
1715
+
1716
+ const addon = loadNativeAddon();
1717
+ const nativeCreate = createSimulatorBridge(addon);
1718
+
1719
+ const sim = Simulator.create<Ports>(
1720
+ {
1721
+ __celox_module: true,
1722
+ name: "ParamWidth",
1723
+ sources: [{ path: "", content: PARAM_WIDTH_SOURCE }],
1724
+ ports: {
1725
+ // STALE: these say width=8, but we'll override to 16
1726
+ a: { direction: "input", type: "logic", width: 8 },
1727
+ b: { direction: "output", type: "logic", width: 8 },
1728
+ },
1729
+ events: [],
1730
+ },
1731
+ {
1732
+ __nativeCreate: nativeCreate,
1733
+ parameters: [{ name: "WIDTH", value: 16 }],
1734
+ },
1735
+ );
1736
+
1737
+ // If DUT used stale ports (width=8), this would truncate or corrupt
1738
+ sim.dut.a = 0xabcdn;
1739
+ expect(sim.dut.b).toBe(0xabcdn); // full 16-bit value preserved
1740
+ sim.dispose();
1741
+ });
1742
+
1743
+ test("Simulation.fromSource with param override", () => {
1744
+ interface Ports {
1745
+ a: bigint;
1746
+ readonly b: bigint;
1747
+ }
1748
+
1749
+ const sim = Simulation.fromSource<Ports>(PARAM_WIDTH_SOURCE, "ParamWidth", {
1750
+ parameters: [{ name: "WIDTH", value: 16 }],
1751
+ });
1752
+
1753
+ sim.dut.a = 0xfedcn;
1754
+ sim.runUntil(0);
1755
+ expect(sim.dut.b).toBe(0xfedcn);
1756
+ sim.dispose();
1757
+ });
1662
1758
  });
1663
1759
 
1664
1760
  // ---------------------------------------------------------------------------
@@ -1666,65 +1762,77 @@ describe("E2E: parameter override — DUT correctness", () => {
1666
1762
  // ---------------------------------------------------------------------------
1667
1763
 
1668
1764
  describe("E2E: celox.toml test sources", () => {
1669
- test("fromProject loads test-only module declared in celox.toml", () => {
1670
- interface RegPorts {
1671
- rst: bigint;
1672
- d: bigint;
1673
- readonly q: bigint;
1674
- }
1675
-
1676
- // `Reg` lives in test_veryl/ — not in Veryl.toml sources — but celox.toml
1677
- // declares test_veryl/ as an additional test source directory.
1678
- const sim = Simulator.fromProject<RegPorts>(CELOX_TOML_PROJECT, "Reg");
1679
-
1680
- // Deassert async_low reset (rst=0 is active; rst=1 is normal operation)
1681
- sim.dut.rst = 1n;
1682
- sim.dut.d = 0xABn;
1683
- sim.tick();
1684
- expect(sim.dut.q).toBe(0xABn);
1685
-
1686
- sim.dut.d = 0xCDn;
1687
- expect(sim.dut.q).toBe(0xABn); // not yet clocked
1688
- sim.tick();
1689
- expect(sim.dut.q).toBe(0xCDn);
1690
-
1691
- sim.dispose();
1692
- });
1693
-
1694
- test("genTs includes test-only module declared in celox.toml", () => {
1695
- const addon = loadNativeAddon();
1696
- const json = addon.genTs(CELOX_TOML_PROJECT);
1697
- const output = JSON.parse(json) as { modules: Array<{ moduleName: string }> };
1698
- const names = output.modules.map((m) => m.moduleName);
1699
- // Both the regular source (Adder) and the test-only source (Reg) must appear
1700
- expect(names).toContain("Adder");
1701
- expect(names).toContain("Reg");
1702
- });
1703
-
1704
- test("[simulation] max_steps from celox.toml is used as waitUntil default", () => {
1705
- // celox.toml sets [simulation] max_steps = 20; waitUntil should honour it
1706
- // when no per-call maxSteps is provided.
1707
- const sim = Simulation.fromProject(CELOX_TOML_PROJECT, "Reg");
1708
- sim.addClock("clk", { period: 10 });
1709
- expect(() => sim.waitUntil(() => false)).toThrow(SimulationTimeoutError);
1710
- const err = (() => {
1711
- try { sim.waitUntil(() => false); } catch (e) { return e; }
1712
- })() as SimulationTimeoutError;
1713
- expect(err.steps).toBe(20);
1714
- sim.dispose();
1715
- });
1716
-
1717
- test("[simulation] max_steps per-call override takes precedence over celox.toml", () => {
1718
- const sim = Simulation.fromProject(CELOX_TOML_PROJECT, "Reg");
1719
- sim.addClock("clk", { period: 10 });
1720
- // Per-call maxSteps=5 is lower than the toml default of 20
1721
- expect(() => sim.waitUntil(() => false, { maxSteps: 5 })).toThrow(SimulationTimeoutError);
1722
- const err = (() => {
1723
- try { sim.waitUntil(() => false, { maxSteps: 5 }); } catch (e) { return e; }
1724
- })() as SimulationTimeoutError;
1725
- expect(err.steps).toBe(5);
1726
- sim.dispose();
1727
- });
1765
+ test("fromProject loads test-only module declared in celox.toml", () => {
1766
+ interface RegPorts {
1767
+ rst: bigint;
1768
+ d: bigint;
1769
+ readonly q: bigint;
1770
+ }
1771
+
1772
+ // `Reg` lives in test_veryl/ — not in Veryl.toml sources — but celox.toml
1773
+ // declares test_veryl/ as an additional test source directory.
1774
+ const sim = Simulator.fromProject<RegPorts>(CELOX_TOML_PROJECT, "Reg");
1775
+
1776
+ // Deassert async_low reset (rst=0 is active; rst=1 is normal operation)
1777
+ sim.dut.rst = 1n;
1778
+ sim.dut.d = 0xabn;
1779
+ sim.tick();
1780
+ expect(sim.dut.q).toBe(0xabn);
1781
+
1782
+ sim.dut.d = 0xcdn;
1783
+ expect(sim.dut.q).toBe(0xabn); // not yet clocked
1784
+ sim.tick();
1785
+ expect(sim.dut.q).toBe(0xcdn);
1786
+
1787
+ sim.dispose();
1788
+ });
1789
+
1790
+ test("genTs includes test-only module declared in celox.toml", () => {
1791
+ const addon = loadNativeAddon();
1792
+ const json = addon.genTs(CELOX_TOML_PROJECT);
1793
+ const output = JSON.parse(json) as {
1794
+ modules: Array<{ moduleName: string }>;
1795
+ };
1796
+ const names = output.modules.map((m) => m.moduleName);
1797
+ // Both the regular source (Adder) and the test-only source (Reg) must appear
1798
+ expect(names).toContain("Adder");
1799
+ expect(names).toContain("Reg");
1800
+ });
1801
+
1802
+ test("[simulation] max_steps from celox.toml is used as waitUntil default", () => {
1803
+ // celox.toml sets [simulation] max_steps = 20; waitUntil should honour it
1804
+ // when no per-call maxSteps is provided.
1805
+ const sim = Simulation.fromProject(CELOX_TOML_PROJECT, "Reg");
1806
+ sim.addClock("clk", { period: 10 });
1807
+ expect(() => sim.waitUntil(() => false)).toThrow(SimulationTimeoutError);
1808
+ const err = (() => {
1809
+ try {
1810
+ sim.waitUntil(() => false);
1811
+ } catch (e) {
1812
+ return e;
1813
+ }
1814
+ })() as SimulationTimeoutError;
1815
+ expect(err.steps).toBe(20);
1816
+ sim.dispose();
1817
+ });
1818
+
1819
+ test("[simulation] max_steps per-call override takes precedence over celox.toml", () => {
1820
+ const sim = Simulation.fromProject(CELOX_TOML_PROJECT, "Reg");
1821
+ sim.addClock("clk", { period: 10 });
1822
+ // Per-call maxSteps=5 is lower than the toml default of 20
1823
+ expect(() => sim.waitUntil(() => false, { maxSteps: 5 })).toThrow(
1824
+ SimulationTimeoutError,
1825
+ );
1826
+ const err = (() => {
1827
+ try {
1828
+ sim.waitUntil(() => false, { maxSteps: 5 });
1829
+ } catch (e) {
1830
+ return e;
1831
+ }
1832
+ })() as SimulationTimeoutError;
1833
+ expect(err.steps).toBe(5);
1834
+ sim.dispose();
1835
+ });
1728
1836
  });
1729
1837
 
1730
1838
  // ---------------------------------------------------------------------------
@@ -1802,29 +1910,29 @@ module Top (
1802
1910
  `;
1803
1911
 
1804
1912
  describe("E2E: interface port DUT accessor", () => {
1805
- test("top-level interface port members accessible as nested dut properties", () => {
1806
- interface TopPorts {
1807
- sig: { data: bigint; valid: bigint };
1808
- readonly out: bigint;
1809
- }
1913
+ test("top-level interface port members accessible as nested dut properties", () => {
1914
+ interface TopPorts {
1915
+ sig: { data: bigint; valid: bigint };
1916
+ readonly out: bigint;
1917
+ }
1810
1918
 
1811
- const sim = Simulator.fromSource<TopPorts>(INTERFACE_PORT_SOURCE, "Top");
1919
+ const sim = Simulator.fromSource<TopPorts>(INTERFACE_PORT_SOURCE, "Top");
1812
1920
 
1813
- sim.dut.sig.data = 0x42n;
1814
- expect(sim.dut.out).toBe(0x42n);
1921
+ sim.dut.sig.data = 0x42n;
1922
+ expect(sim.dut.out).toBe(0x42n);
1815
1923
 
1816
- sim.dut.sig.data = 0xFFn;
1817
- expect(sim.dut.out).toBe(0xFFn);
1924
+ sim.dut.sig.data = 0xffn;
1925
+ expect(sim.dut.out).toBe(0xffn);
1818
1926
 
1819
- sim.dut.sig.data = 0x00n;
1820
- expect(sim.dut.out).toBe(0x00n);
1927
+ sim.dut.sig.data = 0x00n;
1928
+ expect(sim.dut.out).toBe(0x00n);
1821
1929
 
1822
- sim.dispose();
1823
- });
1930
+ sim.dispose();
1931
+ });
1824
1932
 
1825
- test("writing to interface output member throws", () => {
1826
- // bus.data / bus.valid are outputs of Top — writing should throw.
1827
- const PRODUCER_ONLY = `
1933
+ test("writing to interface output member throws", () => {
1934
+ // bus.data / bus.valid are outputs of Top — writing should throw.
1935
+ const PRODUCER_ONLY = `
1828
1936
  interface Bus {
1829
1937
  var data: logic<8>;
1830
1938
  var valid: logic;
@@ -1841,52 +1949,55 @@ module Top (
1841
1949
  assign bus.valid = 1'b1;
1842
1950
  }
1843
1951
  `;
1844
- const sim = Simulator.fromSource(PRODUCER_ONLY, "Top");
1952
+ const sim = Simulator.fromSource(PRODUCER_ONLY, "Top");
1845
1953
 
1846
- expect(() => {
1847
- (sim.dut as any).bus.data = 0n;
1848
- }).toThrow("Cannot write to output port 'data'");
1954
+ expect(() => {
1955
+ (sim.dut as any).bus.data = 0n;
1956
+ }).toThrow("Cannot write to output port 'data'");
1849
1957
 
1850
- sim.dispose();
1851
- });
1958
+ sim.dispose();
1959
+ });
1852
1960
 
1853
- test("interface port propagates through sub-module instances", () => {
1854
- interface TopPorts {
1855
- val: bigint;
1856
- readonly out: bigint;
1857
- readonly got: bigint;
1858
- }
1961
+ test("interface port propagates through sub-module instances", () => {
1962
+ interface TopPorts {
1963
+ val: bigint;
1964
+ readonly out: bigint;
1965
+ readonly got: bigint;
1966
+ }
1859
1967
 
1860
- const sim = Simulator.fromSource<TopPorts>(INTERFACE_HIERARCHY_SOURCE, "Top");
1968
+ const sim = Simulator.fromSource<TopPorts>(
1969
+ INTERFACE_HIERARCHY_SOURCE,
1970
+ "Top",
1971
+ );
1861
1972
 
1862
- sim.dut.val = 0xABn;
1863
- expect(sim.dut.out).toBe(0xABn);
1864
- expect(sim.dut.got).toBe(1n);
1973
+ sim.dut.val = 0xabn;
1974
+ expect(sim.dut.out).toBe(0xabn);
1975
+ expect(sim.dut.got).toBe(1n);
1865
1976
 
1866
- sim.dut.val = 0x00n;
1867
- expect(sim.dut.out).toBe(0x00n);
1977
+ sim.dut.val = 0x00n;
1978
+ expect(sim.dut.out).toBe(0x00n);
1868
1979
 
1869
- sim.dispose();
1870
- });
1980
+ sim.dispose();
1981
+ });
1871
1982
 
1872
- test("multiple interface port members are independently accessible", () => {
1873
- interface TopPorts {
1874
- sig: { data: bigint; valid: bigint };
1875
- readonly out: bigint;
1876
- }
1983
+ test("multiple interface port members are independently accessible", () => {
1984
+ interface TopPorts {
1985
+ sig: { data: bigint; valid: bigint };
1986
+ readonly out: bigint;
1987
+ }
1877
1988
 
1878
- const sim = Simulator.fromSource<TopPorts>(INTERFACE_PORT_SOURCE, "Top");
1989
+ const sim = Simulator.fromSource<TopPorts>(INTERFACE_PORT_SOURCE, "Top");
1879
1990
 
1880
- sim.dut.sig.data = 0x77n;
1881
- sim.dut.sig.valid = 1n;
1882
- expect(sim.dut.out).toBe(0x77n);
1991
+ sim.dut.sig.data = 0x77n;
1992
+ sim.dut.sig.valid = 1n;
1993
+ expect(sim.dut.out).toBe(0x77n);
1883
1994
 
1884
- sim.dut.sig.data = 0x33n;
1885
- sim.dut.sig.valid = 0n;
1886
- expect(sim.dut.out).toBe(0x33n);
1995
+ sim.dut.sig.data = 0x33n;
1996
+ sim.dut.sig.valid = 0n;
1997
+ expect(sim.dut.out).toBe(0x33n);
1887
1998
 
1888
- sim.dispose();
1889
- });
1999
+ sim.dispose();
2000
+ });
1890
2001
  });
1891
2002
 
1892
2003
  // ---------------------------------------------------------------------------
@@ -1912,30 +2023,38 @@ module Top (
1912
2023
  `;
1913
2024
 
1914
2025
  describe("E2E: interface array port (modport [N])", () => {
1915
- test("interface array members accessible via .at()/.set()", () => {
1916
- interface TopPorts {
1917
- bus: {
1918
- data: { at(i: number): bigint; set(i: number, v: bigint): void; length: number };
1919
- valid: { at(i: number): bigint; set(i: number, v: bigint): void; length: number };
1920
- };
1921
- readonly out: bigint;
1922
- }
1923
-
1924
- const sim = Simulator.fromSource<TopPorts>(INTERFACE_ARRAY_SOURCE, "Top");
1925
-
1926
- sim.dut.bus.data.set(0, 0x10n);
1927
- sim.dut.bus.data.set(1, 0x20n);
1928
- expect(sim.dut.out).toBe(0x30n);
1929
-
1930
- sim.dut.bus.data.set(0, 0xAAn);
1931
- sim.dut.bus.data.set(1, 0x11n);
1932
- expect(sim.dut.out).toBe(0xBBn);
1933
-
1934
- expect(sim.dut.bus.data.length).toBe(2);
1935
- expect(sim.dut.bus.valid.length).toBe(2);
1936
-
1937
- sim.dispose();
1938
- });
2026
+ test("interface array members accessible via .at()/.set()", () => {
2027
+ interface TopPorts {
2028
+ bus: {
2029
+ data: {
2030
+ at(i: number): bigint;
2031
+ set(i: number, v: bigint): void;
2032
+ length: number;
2033
+ };
2034
+ valid: {
2035
+ at(i: number): bigint;
2036
+ set(i: number, v: bigint): void;
2037
+ length: number;
2038
+ };
2039
+ };
2040
+ readonly out: bigint;
2041
+ }
2042
+
2043
+ const sim = Simulator.fromSource<TopPorts>(INTERFACE_ARRAY_SOURCE, "Top");
2044
+
2045
+ sim.dut.bus.data.set(0, 0x10n);
2046
+ sim.dut.bus.data.set(1, 0x20n);
2047
+ expect(sim.dut.out).toBe(0x30n);
2048
+
2049
+ sim.dut.bus.data.set(0, 0xaan);
2050
+ sim.dut.bus.data.set(1, 0x11n);
2051
+ expect(sim.dut.out).toBe(0xbbn);
2052
+
2053
+ expect(sim.dut.bus.data.length).toBe(2);
2054
+ expect(sim.dut.bus.valid.length).toBe(2);
2055
+
2056
+ sim.dispose();
2057
+ });
1939
2058
  });
1940
2059
 
1941
2060
  // ---------------------------------------------------------------------------
@@ -1961,114 +2080,477 @@ module ArrayPassThrough (
1961
2080
  }
1962
2081
  `;
1963
2082
 
1964
- describe("E2E: 1-bit unpacked array port (logic [N])", () => {
1965
- test("each element is independently visible in hardware (issue #6)", () => {
1966
- interface Ports {
1967
- rst: bigint;
1968
- en: { at(i: number): bigint; set(i: number, v: bigint): void; length: number };
1969
- readonly y0: bigint;
1970
- readonly y1: bigint;
1971
- readonly y2: bigint;
1972
- readonly y3: bigint;
1973
- }
2083
+ // ---------------------------------------------------------------------------
2084
+ // Internal var access tests
2085
+ // ---------------------------------------------------------------------------
1974
2086
 
1975
- const sim = Simulator.fromSource<Ports>(ARRAY_1BIT_SOURCE, "ArrayPassThrough");
1976
-
1977
- // Default: all inputs 0
1978
- sim.tick();
1979
- expect(sim.dut.y0).toBe(0n);
1980
- expect(sim.dut.y1).toBe(0n);
1981
- expect(sim.dut.y2).toBe(0n);
1982
- expect(sim.dut.y3).toBe(0n);
1983
-
1984
- // Set element 0 only
1985
- sim.dut.en.set(0, 1n);
1986
- sim.tick();
1987
- expect(sim.dut.y0).toBe(1n);
1988
- expect(sim.dut.y1).toBe(0n);
1989
- expect(sim.dut.y2).toBe(0n);
1990
- expect(sim.dut.y3).toBe(0n);
1991
-
1992
- // Set element 1 (was silently ignored before the fix)
1993
- sim.dut.en.set(0, 0n);
1994
- sim.dut.en.set(1, 1n);
1995
- sim.tick();
1996
- expect(sim.dut.y0).toBe(0n);
1997
- expect(sim.dut.y1).toBe(1n);
1998
- expect(sim.dut.y2).toBe(0n);
1999
- expect(sim.dut.y3).toBe(0n);
2000
-
2001
- // Set elements 2 and 3
2002
- sim.dut.en.set(1, 0n);
2003
- sim.dut.en.set(2, 1n);
2004
- sim.dut.en.set(3, 1n);
2005
- sim.tick();
2006
- expect(sim.dut.y0).toBe(0n);
2007
- expect(sim.dut.y1).toBe(0n);
2008
- expect(sim.dut.y2).toBe(1n);
2009
- expect(sim.dut.y3).toBe(1n);
2010
-
2011
- sim.dispose();
2012
- });
2013
-
2014
- test("length property is correct", () => {
2015
- interface Ports {
2016
- rst: bigint;
2017
- en: { at(i: number): bigint; set(i: number, v: bigint): void; length: number };
2018
- readonly y0: bigint;
2019
- readonly y1: bigint;
2020
- readonly y2: bigint;
2021
- readonly y3: bigint;
2087
+ const INTERNAL_VAR_HIERARCHY_SOURCE = `
2088
+ module SubCounter (
2089
+ clk: input clock,
2090
+ rst: input reset,
2091
+ en: input logic,
2092
+ count: output logic<8>,
2093
+ ) {
2094
+ var count_r: logic<8>;
2095
+ always_ff (clk, rst) {
2096
+ if_reset {
2097
+ count_r = 0;
2098
+ } else if en {
2099
+ count_r = count_r + 1;
2100
+ }
2022
2101
  }
2102
+ assign count = count_r;
2103
+ }
2023
2104
 
2024
- const sim = Simulator.fromSource<Ports>(ARRAY_1BIT_SOURCE, "ArrayPassThrough");
2025
- expect(sim.dut.en.length).toBe(4);
2026
- sim.dispose();
2027
- });
2028
-
2029
- test("4-state mode: X written to element i propagates only to output yi (verifies maskBase layout)", () => {
2030
- // This test validates that the maskBase = baseOffset + totalValueBytes assumption
2031
- // matches the actual JIT memory layout when fourState: true is used.
2032
- interface Ports {
2033
- rst: bigint;
2034
- en: { at(i: number): bigint; set(i: number, v: unknown): void; length: number };
2035
- readonly y0: bigint;
2036
- readonly y1: bigint;
2037
- readonly y2: bigint;
2038
- readonly y3: bigint;
2105
+ module TopCounter (
2106
+ clk: input clock,
2107
+ rst: input reset,
2108
+ en: input logic,
2109
+ top_count: output logic<8>,
2110
+ ) {
2111
+ var top_reg: logic<8>;
2112
+ inst u_sub: SubCounter (
2113
+ clk,
2114
+ rst,
2115
+ en,
2116
+ count: top_count,
2117
+ );
2118
+ always_ff (clk, rst) {
2119
+ if_reset {
2120
+ top_reg = 0;
2121
+ } else {
2122
+ top_reg = top_count;
2123
+ }
2039
2124
  }
2125
+ }
2126
+ `;
2127
+
2128
+ describe("E2E: internal var access", () => {
2129
+ test("top-level internal var is accessible and tracks values", () => {
2130
+ const sim = Simulator.fromSource(COUNTER_SOURCE, "Counter");
2040
2131
 
2041
- const sim = Simulator.fromSource<Ports>(ARRAY_1BIT_SOURCE, "ArrayPassThrough", { fourState: true });
2132
+ // Reset
2133
+ sim.dut.rst = 0n;
2134
+ sim.tick();
2135
+ sim.dut.rst = 1n;
2136
+ sim.tick();
2042
2137
 
2043
- // Start with all defined zeros
2044
- for (let i = 0; i < 4; i++) sim.dut.en.set(i, 0n);
2045
- sim.tick();
2138
+ // Internal var count_r should be accessible and start at 0
2139
+ expect((sim.dut as any).count_r).toBe(0n);
2046
2140
 
2047
- // Set element 1 to X; only y1 should become X
2048
- sim.dut.en.set(1, X);
2049
- sim.tick();
2141
+ // Enable counting and tick
2142
+ sim.dut.en = 1n;
2143
+ sim.tick();
2144
+ expect((sim.dut as any).count_r).toBe(1n);
2050
2145
 
2051
- expect(sim.fourState("y1").mask).not.toBe(0n); // y1 = X
2052
- expect(sim.fourState("y0").mask).toBe(0n); // y0 defined
2053
- expect(sim.fourState("y2").mask).toBe(0n); // y2 defined
2054
- expect(sim.fourState("y3").mask).toBe(0n); // y3 defined
2146
+ sim.tick();
2147
+ expect((sim.dut as any).count_r).toBe(2n);
2055
2148
 
2056
- // Set element 2 to X; y2 becomes X, y1 remains X
2057
- sim.dut.en.set(2, X);
2058
- sim.tick();
2149
+ // count_r should equal count (output)
2150
+ expect((sim.dut as any).count_r).toBe(sim.dut.count);
2059
2151
 
2060
- expect(sim.fourState("y1").mask).not.toBe(0n);
2061
- expect(sim.fourState("y2").mask).not.toBe(0n);
2062
- expect(sim.fourState("y0").mask).toBe(0n);
2063
- expect(sim.fourState("y3").mask).toBe(0n);
2152
+ sim.dispose();
2153
+ });
2064
2154
 
2065
- // Clear element 1 to defined 0; y1 becomes defined again
2066
- sim.dut.en.set(1, 0n);
2067
- sim.tick();
2155
+ test("child instance internal var is accessible", () => {
2156
+ const sim = Simulator.fromSource(
2157
+ INTERNAL_VAR_HIERARCHY_SOURCE,
2158
+ "TopCounter",
2159
+ );
2068
2160
 
2069
- expect(sim.fourState("y1").mask).toBe(0n);
2070
- expect(sim.fourState("y2").mask).not.toBe(0n);
2161
+ // Reset
2162
+ sim.dut.rst = 0n;
2163
+ sim.tick();
2164
+ sim.dut.rst = 1n;
2165
+ sim.tick();
2166
+
2167
+ // Top-level internal var
2168
+ expect((sim.dut as any).top_reg).toBe(0n);
2169
+
2170
+ // Child instance internal var
2171
+ expect((sim.dut as any).u_sub.count_r).toBe(0n);
2172
+
2173
+ // Enable counting
2174
+ sim.dut.en = 1n;
2175
+ sim.tick();
2176
+ expect((sim.dut as any).u_sub.count_r).toBe(1n);
2177
+
2178
+ sim.tick();
2179
+ expect((sim.dut as any).u_sub.count_r).toBe(2n);
2180
+
2181
+ sim.dispose();
2182
+ });
2183
+ });
2184
+
2185
+ describe("E2E: 1-bit unpacked array port (logic [N])", () => {
2186
+ test("each element is independently visible in hardware (issue #6)", () => {
2187
+ interface Ports {
2188
+ rst: bigint;
2189
+ en: {
2190
+ at(i: number): bigint;
2191
+ set(i: number, v: bigint): void;
2192
+ length: number;
2193
+ };
2194
+ readonly y0: bigint;
2195
+ readonly y1: bigint;
2196
+ readonly y2: bigint;
2197
+ readonly y3: bigint;
2198
+ }
2199
+
2200
+ const sim = Simulator.fromSource<Ports>(
2201
+ ARRAY_1BIT_SOURCE,
2202
+ "ArrayPassThrough",
2203
+ );
2204
+
2205
+ // Default: all inputs 0
2206
+ sim.tick();
2207
+ expect(sim.dut.y0).toBe(0n);
2208
+ expect(sim.dut.y1).toBe(0n);
2209
+ expect(sim.dut.y2).toBe(0n);
2210
+ expect(sim.dut.y3).toBe(0n);
2211
+
2212
+ // Set element 0 only
2213
+ sim.dut.en.set(0, 1n);
2214
+ sim.tick();
2215
+ expect(sim.dut.y0).toBe(1n);
2216
+ expect(sim.dut.y1).toBe(0n);
2217
+ expect(sim.dut.y2).toBe(0n);
2218
+ expect(sim.dut.y3).toBe(0n);
2219
+
2220
+ // Set element 1 (was silently ignored before the fix)
2221
+ sim.dut.en.set(0, 0n);
2222
+ sim.dut.en.set(1, 1n);
2223
+ sim.tick();
2224
+ expect(sim.dut.y0).toBe(0n);
2225
+ expect(sim.dut.y1).toBe(1n);
2226
+ expect(sim.dut.y2).toBe(0n);
2227
+ expect(sim.dut.y3).toBe(0n);
2228
+
2229
+ // Set elements 2 and 3
2230
+ sim.dut.en.set(1, 0n);
2231
+ sim.dut.en.set(2, 1n);
2232
+ sim.dut.en.set(3, 1n);
2233
+ sim.tick();
2234
+ expect(sim.dut.y0).toBe(0n);
2235
+ expect(sim.dut.y1).toBe(0n);
2236
+ expect(sim.dut.y2).toBe(1n);
2237
+ expect(sim.dut.y3).toBe(1n);
2238
+
2239
+ sim.dispose();
2240
+ });
2241
+
2242
+ test("length property is correct", () => {
2243
+ interface Ports {
2244
+ rst: bigint;
2245
+ en: {
2246
+ at(i: number): bigint;
2247
+ set(i: number, v: bigint): void;
2248
+ length: number;
2249
+ };
2250
+ readonly y0: bigint;
2251
+ readonly y1: bigint;
2252
+ readonly y2: bigint;
2253
+ readonly y3: bigint;
2254
+ }
2255
+
2256
+ const sim = Simulator.fromSource<Ports>(
2257
+ ARRAY_1BIT_SOURCE,
2258
+ "ArrayPassThrough",
2259
+ );
2260
+ expect(sim.dut.en.length).toBe(4);
2261
+ sim.dispose();
2262
+ });
2263
+
2264
+ test("4-state mode: X written to element i propagates only to output yi (verifies maskBase layout)", () => {
2265
+ // This test validates that the maskBase = baseOffset + totalValueBytes assumption
2266
+ // matches the actual JIT memory layout when fourState: true is used.
2267
+ interface Ports {
2268
+ rst: bigint;
2269
+ en: {
2270
+ at(i: number): bigint;
2271
+ set(i: number, v: unknown): void;
2272
+ length: number;
2273
+ };
2274
+ readonly y0: bigint;
2275
+ readonly y1: bigint;
2276
+ readonly y2: bigint;
2277
+ readonly y3: bigint;
2278
+ }
2279
+
2280
+ const sim = Simulator.fromSource<Ports>(
2281
+ ARRAY_1BIT_SOURCE,
2282
+ "ArrayPassThrough",
2283
+ { fourState: true },
2284
+ );
2285
+
2286
+ // Start with all defined zeros
2287
+ for (let i = 0; i < 4; i++) sim.dut.en.set(i, 0n);
2288
+ sim.tick();
2289
+
2290
+ // Set element 1 to X; only y1 should become X
2291
+ sim.dut.en.set(1, X);
2292
+ sim.tick();
2293
+
2294
+ expect(sim.fourState("y1").mask).not.toBe(0n); // y1 = X
2295
+ expect(sim.fourState("y0").mask).toBe(0n); // y0 defined
2296
+ expect(sim.fourState("y2").mask).toBe(0n); // y2 defined
2297
+ expect(sim.fourState("y3").mask).toBe(0n); // y3 defined
2298
+
2299
+ // Set element 2 to X; y2 becomes X, y1 remains X
2300
+ sim.dut.en.set(2, X);
2301
+ sim.tick();
2302
+
2303
+ expect(sim.fourState("y1").mask).not.toBe(0n);
2304
+ expect(sim.fourState("y2").mask).not.toBe(0n);
2305
+ expect(sim.fourState("y0").mask).toBe(0n);
2306
+ expect(sim.fourState("y3").mask).toBe(0n);
2307
+
2308
+ // Clear element 1 to defined 0; y1 becomes defined again
2309
+ sim.dut.en.set(1, 0n);
2310
+ sim.tick();
2311
+
2312
+ expect(sim.fourState("y1").mask).toBe(0n);
2313
+ expect(sim.fourState("y2").mask).not.toBe(0n);
2314
+
2315
+ sim.dispose();
2316
+ });
2317
+ });
2318
+
2319
+ // ---------------------------------------------------------------------------
2320
+ // Dead Store Elimination (DSE) tests
2321
+ // ---------------------------------------------------------------------------
2322
+
2323
+ describe("E2E: deadStorePolicy option", () => {
2324
+ test("preserveTopPorts: top ports work, child instances stripped from DUT", () => {
2325
+ const sim = Simulator.fromSource(HIERARCHY_SOURCE, "Top", {
2326
+ deadStorePolicy: "preserveTopPorts",
2327
+ });
2328
+
2329
+ sim.dut.top_in = 0xabn;
2330
+ sim.tick();
2331
+ expect(sim.dut.top_out).toBe(0xabn);
2332
+
2333
+ // With preserveTopPorts, children should be stripped from hierarchy
2334
+ expect((sim.dut as any).u_sub).toBeUndefined();
2335
+
2336
+ sim.dispose();
2337
+ });
2338
+
2339
+ test("preserveAllPorts: top ports AND child instance ports accessible", () => {
2340
+ const sim = Simulator.fromSource(HIERARCHY_SOURCE, "Top", {
2341
+ deadStorePolicy: "preserveAllPorts",
2342
+ });
2343
+
2344
+ sim.dut.top_in = 0x42n;
2345
+ sim.tick();
2346
+ expect(sim.dut.top_out).toBe(0x42n);
2347
+
2348
+ // With preserveAllPorts, child instance ports should remain accessible
2349
+ expect((sim.dut as any).u_sub).toBeDefined();
2350
+ expect((sim.dut as any).u_sub.o_data).toBe(0x42n);
2351
+
2352
+ sim.dispose();
2353
+ });
2354
+
2355
+ test("Simulation: preserveTopPorts strips children", () => {
2356
+ const sim = Simulation.fromSource(HIERARCHY_SOURCE, "Top", {
2357
+ deadStorePolicy: "preserveTopPorts",
2358
+ });
2359
+ sim.addClock("clk", { period: 10 });
2360
+
2361
+ sim.dut.rst = 0n;
2362
+ sim.runUntil(20);
2363
+ sim.dut.rst = 1n;
2364
+
2365
+ sim.dut.top_in = 0x55n;
2366
+ sim.runUntil(40);
2367
+ expect(sim.dut.top_out).toBe(0x55n);
2368
+ expect((sim.dut as any).u_sub).toBeUndefined();
2369
+
2370
+ sim.dispose();
2371
+ });
2372
+
2373
+ test("Simulation: preserveAllPorts keeps children", () => {
2374
+ const sim = Simulation.fromSource(HIERARCHY_SOURCE, "Top", {
2375
+ deadStorePolicy: "preserveAllPorts",
2376
+ });
2377
+ sim.addClock("clk", { period: 10 });
2378
+
2379
+ sim.dut.rst = 0n;
2380
+ sim.runUntil(20);
2381
+ sim.dut.rst = 1n;
2382
+
2383
+ sim.dut.top_in = 0xffn;
2384
+ sim.runUntil(40);
2385
+ expect(sim.dut.top_out).toBe(0xffn);
2386
+ expect((sim.dut as any).u_sub).toBeDefined();
2387
+ expect((sim.dut as any).u_sub.o_data).toBe(0xffn);
2388
+
2389
+ sim.dispose();
2390
+ });
2391
+ });
2392
+
2393
+ // ---------------------------------------------------------------------------
2394
+ // E2E: Proto package ordering (issue #22)
2395
+ // ---------------------------------------------------------------------------
2396
+
2397
+ const PROTO_PKG_SOURCE = `
2398
+ pub proto package ItemProto {
2399
+ type Item;
2400
+ }
2401
+
2402
+ pub package ItemPlain::<WIDTH: u32 = 16> for ItemProto {
2403
+ type Item = logic<WIDTH>;
2404
+ }
2405
+ `;
2406
+
2407
+ const GENERIC_MODULE_SOURCE = `
2408
+ pub module GenericModule::<PKG: ItemProto> #(
2409
+ param DEPTH: u32 = 4,
2410
+ ) (
2411
+ clk : input clock ,
2412
+ rst : input reset ,
2413
+ d_in : input PKG::Item ,
2414
+ d_out: output PKG::Item ,
2415
+ ) {
2416
+ var store: PKG::Item;
2417
+ always_ff (clk, rst) {
2418
+ if_reset { store = '0; }
2419
+ else { store = d_in; }
2420
+ }
2421
+ assign d_out = store;
2422
+ }
2423
+
2424
+ pub module ConcreteModule #(
2425
+ param DEPTH: u32 = 4,
2426
+ ) (
2427
+ clk : input clock ,
2428
+ rst : input reset ,
2429
+ d_in : input logic<16> ,
2430
+ d_out: output logic<16> ,
2431
+ ) {
2432
+ inst u_inner: GenericModule::<ItemPlain::<16>> #(DEPTH: DEPTH) (
2433
+ clk, rst, d_in, d_out,
2434
+ );
2435
+ }
2436
+ `;
2437
+
2438
+ const UNRELATED_MODULE_SOURCE = `
2439
+ pub module UnrelatedModule (
2440
+ clk : input clock ,
2441
+ rst : input reset ,
2442
+ d_in : input logic<8> ,
2443
+ d_out: output logic<8> ,
2444
+ ) {
2445
+ var data_r: logic<8>;
2446
+ always_ff (clk, rst) {
2447
+ if_reset { data_r = 0; }
2448
+ else { data_r = d_in; }
2449
+ }
2450
+ assign d_out = data_r;
2451
+ }
2452
+ `;
2071
2453
 
2072
- sim.dispose();
2073
- });
2454
+ describe("E2E: Proto package ordering (issue #22)", () => {
2455
+ test("single-file source fails when project has proto packages in wrong order", () => {
2456
+ // Reproduce the original bug: when proto_pkg definitions come after
2457
+ // the code that references them (as a single concatenated source),
2458
+ // the analyzer fails with "referred before it is defined".
2459
+ expect(() =>
2460
+ Simulator.fromSource(
2461
+ GENERIC_MODULE_SOURCE + PROTO_PKG_SOURCE + UNRELATED_MODULE_SOURCE,
2462
+ "ConcreteModule",
2463
+ ),
2464
+ ).toThrow();
2465
+ });
2466
+
2467
+ test("multi-source: unrelated module compiles with proto packages in project", () => {
2468
+ // New behavior: all source files are passed as separate entries.
2469
+ // The Rust from_sources() handles dependency ordering correctly.
2470
+ const sim = Simulator.fromSource<{
2471
+ d_in: bigint;
2472
+ readonly d_out: bigint;
2473
+ }>(
2474
+ PROTO_PKG_SOURCE + GENERIC_MODULE_SOURCE + UNRELATED_MODULE_SOURCE,
2475
+ "UnrelatedModule",
2476
+ );
2477
+
2478
+ // Reset sequence
2479
+ sim.dut.rst = 0n;
2480
+ sim.tick();
2481
+ sim.tick();
2482
+ sim.dut.rst = 1n;
2483
+
2484
+ sim.dut.d_in = 42n;
2485
+ sim.tick();
2486
+ sim.tick();
2487
+
2488
+ expect(sim.dut.d_out).toBe(42n);
2489
+ sim.dispose();
2490
+ });
2491
+
2492
+ test("multi-source: ConcreteModule using proto package works", () => {
2493
+ // The concrete module that actually uses the proto package should
2494
+ // also compile and simulate correctly.
2495
+ const sim = Simulator.fromSource<{
2496
+ d_in: bigint;
2497
+ readonly d_out: bigint;
2498
+ }>(
2499
+ PROTO_PKG_SOURCE + GENERIC_MODULE_SOURCE + UNRELATED_MODULE_SOURCE,
2500
+ "ConcreteModule",
2501
+ );
2502
+
2503
+ // Reset sequence
2504
+ sim.dut.rst = 0n;
2505
+ sim.tick();
2506
+ sim.tick();
2507
+ sim.dut.rst = 1n;
2508
+
2509
+ sim.dut.d_in = 0xabcdn;
2510
+ sim.tick();
2511
+ sim.tick();
2512
+
2513
+ expect(sim.dut.d_out).toBe(0xabcdn);
2514
+ sim.dispose();
2515
+ });
2516
+
2517
+ test("Simulator.create() with multi-source ModuleDefinition", () => {
2518
+ // Simulates what celox-ts-gen now generates: a ModuleDefinition with
2519
+ // sources[] containing all project files.
2520
+ const addon = loadNativeAddon();
2521
+ const nativeCreate = createSimulatorBridge(addon);
2522
+
2523
+ const sim = Simulator.create(
2524
+ {
2525
+ __celox_module: true,
2526
+ name: "UnrelatedModule",
2527
+ sources: [
2528
+ { path: "proto_pkg.veryl", content: PROTO_PKG_SOURCE },
2529
+ { path: "generic_module.veryl", content: GENERIC_MODULE_SOURCE },
2530
+ { path: "unrelated_module.veryl", content: UNRELATED_MODULE_SOURCE },
2531
+ ],
2532
+ ports: {
2533
+ clk: { direction: "input", type: "clock", width: 1 },
2534
+ rst: { direction: "input", type: "reset", width: 1 },
2535
+ d_in: { direction: "input", type: "logic", width: 8 },
2536
+ d_out: { direction: "output", type: "logic", width: 8 },
2537
+ },
2538
+ events: ["clk"],
2539
+ },
2540
+ { __nativeCreate: nativeCreate },
2541
+ );
2542
+
2543
+ // Reset sequence
2544
+ sim.dut.rst = 0n;
2545
+ sim.tick();
2546
+ sim.tick();
2547
+ sim.dut.rst = 1n;
2548
+
2549
+ sim.dut.d_in = 99n;
2550
+ sim.tick();
2551
+ sim.tick();
2552
+
2553
+ expect(sim.dut.d_out).toBe(99n);
2554
+ sim.dispose();
2555
+ });
2074
2556
  });