@celox-sim/celox 0.0.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dut.d.ts +35 -0
- package/dist/dut.d.ts.map +1 -0
- package/dist/dut.js +342 -0
- package/dist/dut.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/napi-bridge.d.ts +7 -0
- package/dist/napi-bridge.d.ts.map +1 -0
- package/dist/napi-bridge.js +7 -0
- package/dist/napi-bridge.js.map +1 -0
- package/dist/napi-helpers.d.ts +104 -0
- package/dist/napi-helpers.d.ts.map +1 -0
- package/dist/napi-helpers.js +207 -0
- package/dist/napi-helpers.js.map +1 -0
- package/dist/simulation.d.ts +111 -0
- package/dist/simulation.d.ts.map +1 -0
- package/dist/simulation.js +187 -0
- package/dist/simulation.js.map +1 -0
- package/dist/simulator.d.ts +92 -0
- package/dist/simulator.d.ts.map +1 -0
- package/dist/simulator.js +171 -0
- package/dist/simulator.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -7
- package/src/dut.test.ts +534 -0
- package/src/dut.ts +449 -0
- package/src/e2e.bench.ts +240 -0
- package/src/e2e.test.ts +965 -0
- package/src/index.ts +57 -0
- package/src/matchers.ts +154 -0
- package/src/napi-bridge.ts +13 -0
- package/src/napi-helpers.ts +326 -0
- package/src/simulation.test.ts +185 -0
- package/src/simulation.ts +273 -0
- package/src/simulator.test.ts +191 -0
- package/src/simulator.ts +266 -0
- package/src/types.ts +164 -0
- package/README.md +0 -45
package/src/e2e.test.ts
ADDED
|
@@ -0,0 +1,965 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end tests for the TypeScript testbench.
|
|
3
|
+
*
|
|
4
|
+
* These tests exercise the full pipeline:
|
|
5
|
+
* Veryl source → Rust JIT (via NAPI) → SharedArrayBuffer bridge → TS DUT → verify
|
|
6
|
+
*
|
|
7
|
+
* Unlike the unit tests which use mock handles, these tests use the real
|
|
8
|
+
* `celox-napi` native addon compiled from the Rust simulator.
|
|
9
|
+
*/
|
|
10
|
+
|
|
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";
|
|
15
|
+
import { readFourState } from "./dut.js";
|
|
16
|
+
import { X, FourState } from "./types.js";
|
|
17
|
+
import {
|
|
18
|
+
createSimulatorBridge,
|
|
19
|
+
loadNativeAddon,
|
|
20
|
+
parseNapiLayout,
|
|
21
|
+
type RawNapiAddon,
|
|
22
|
+
type RawNapiSimulatorHandle,
|
|
23
|
+
} from "./napi-helpers.js";
|
|
24
|
+
|
|
25
|
+
// Fixture project directories
|
|
26
|
+
const FIXTURES_DIR = path.resolve(import.meta.dirname ?? __dirname, "../fixtures");
|
|
27
|
+
const ADDER_PROJECT = path.join(FIXTURES_DIR, "adder");
|
|
28
|
+
const COUNTER_PROJECT = path.join(FIXTURES_DIR, "counter_project");
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Test Veryl sources
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
const ADDER_SOURCE = `
|
|
35
|
+
module Adder (
|
|
36
|
+
clk: input clock,
|
|
37
|
+
rst: input reset,
|
|
38
|
+
a: input logic<16>,
|
|
39
|
+
b: input logic<16>,
|
|
40
|
+
sum: output logic<17>,
|
|
41
|
+
) {
|
|
42
|
+
always_comb {
|
|
43
|
+
sum = a + b;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const COUNTER_SOURCE = `
|
|
49
|
+
module Counter (
|
|
50
|
+
clk: input clock,
|
|
51
|
+
rst: input reset,
|
|
52
|
+
en: input logic,
|
|
53
|
+
count: output logic<8>,
|
|
54
|
+
) {
|
|
55
|
+
var count_r: logic<8>;
|
|
56
|
+
|
|
57
|
+
always_ff (clk, rst) {
|
|
58
|
+
if_reset {
|
|
59
|
+
count_r = 0;
|
|
60
|
+
} else if en {
|
|
61
|
+
count_r = count_r + 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
always_comb {
|
|
66
|
+
count = count_r;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const MULTIPLEXER_SOURCE = `
|
|
72
|
+
module Mux4 (
|
|
73
|
+
sel: input logic<2>,
|
|
74
|
+
d0: input logic<8>,
|
|
75
|
+
d1: input logic<8>,
|
|
76
|
+
d2: input logic<8>,
|
|
77
|
+
d3: input logic<8>,
|
|
78
|
+
y: output logic<8>,
|
|
79
|
+
) {
|
|
80
|
+
always_comb {
|
|
81
|
+
case sel {
|
|
82
|
+
2'd0: y = d0;
|
|
83
|
+
2'd1: y = d1;
|
|
84
|
+
2'd2: y = d2;
|
|
85
|
+
2'd3: y = d3;
|
|
86
|
+
default: y = 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Simulator (event-based) e2e tests — fromSource API
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
describe("E2E: Simulator.fromSource (event-based)", () => {
|
|
97
|
+
test("combinational adder: a + b = sum", () => {
|
|
98
|
+
interface AdderPorts {
|
|
99
|
+
rst: number;
|
|
100
|
+
a: number;
|
|
101
|
+
b: number;
|
|
102
|
+
readonly sum: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder");
|
|
106
|
+
|
|
107
|
+
sim.dut.a = 100;
|
|
108
|
+
sim.dut.b = 200;
|
|
109
|
+
sim.tick();
|
|
110
|
+
expect(sim.dut.sum).toBe(300);
|
|
111
|
+
|
|
112
|
+
sim.dut.a = 0xFFFF;
|
|
113
|
+
sim.dut.b = 1;
|
|
114
|
+
sim.tick();
|
|
115
|
+
expect(sim.dut.sum).toBe(0x10000);
|
|
116
|
+
|
|
117
|
+
sim.dut.a = 0;
|
|
118
|
+
sim.dut.b = 0;
|
|
119
|
+
sim.tick();
|
|
120
|
+
expect(sim.dut.sum).toBe(0);
|
|
121
|
+
|
|
122
|
+
sim.dispose();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("combinational adder: lazy evalComb on output read", () => {
|
|
126
|
+
interface AdderPorts {
|
|
127
|
+
rst: number;
|
|
128
|
+
a: number;
|
|
129
|
+
b: number;
|
|
130
|
+
readonly sum: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder");
|
|
134
|
+
|
|
135
|
+
sim.dut.a = 42;
|
|
136
|
+
sim.dut.b = 58;
|
|
137
|
+
expect(sim.dut.sum).toBe(100);
|
|
138
|
+
|
|
139
|
+
sim.dispose();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("sequential counter: counts on clock edges", () => {
|
|
143
|
+
interface CounterPorts {
|
|
144
|
+
rst: number;
|
|
145
|
+
en: number;
|
|
146
|
+
readonly count: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
150
|
+
|
|
151
|
+
// Reset the counter
|
|
152
|
+
sim.dut.rst = 1;
|
|
153
|
+
sim.tick();
|
|
154
|
+
sim.dut.rst = 0;
|
|
155
|
+
sim.tick();
|
|
156
|
+
expect(sim.dut.count).toBe(0);
|
|
157
|
+
|
|
158
|
+
// Enable counting
|
|
159
|
+
sim.dut.en = 1;
|
|
160
|
+
sim.tick();
|
|
161
|
+
expect(sim.dut.count).toBe(1);
|
|
162
|
+
|
|
163
|
+
sim.tick();
|
|
164
|
+
expect(sim.dut.count).toBe(2);
|
|
165
|
+
|
|
166
|
+
sim.tick();
|
|
167
|
+
expect(sim.dut.count).toBe(3);
|
|
168
|
+
|
|
169
|
+
// Disable counting
|
|
170
|
+
sim.dut.en = 0;
|
|
171
|
+
sim.tick();
|
|
172
|
+
expect(sim.dut.count).toBe(3);
|
|
173
|
+
|
|
174
|
+
// Re-enable
|
|
175
|
+
sim.dut.en = 1;
|
|
176
|
+
sim.tick(5);
|
|
177
|
+
expect(sim.dut.count).toBe(8);
|
|
178
|
+
|
|
179
|
+
sim.dispose();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("combinational multiplexer", () => {
|
|
183
|
+
interface Mux4Ports {
|
|
184
|
+
sel: number;
|
|
185
|
+
d0: number;
|
|
186
|
+
d1: number;
|
|
187
|
+
d2: number;
|
|
188
|
+
d3: number;
|
|
189
|
+
readonly y: number;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const sim = Simulator.fromSource<Mux4Ports>(MULTIPLEXER_SOURCE, "Mux4");
|
|
193
|
+
|
|
194
|
+
sim.dut.d0 = 0xAA;
|
|
195
|
+
sim.dut.d1 = 0xBB;
|
|
196
|
+
sim.dut.d2 = 0xCC;
|
|
197
|
+
sim.dut.d3 = 0xDD;
|
|
198
|
+
|
|
199
|
+
sim.dut.sel = 0;
|
|
200
|
+
expect(sim.dut.y).toBe(0xAA);
|
|
201
|
+
|
|
202
|
+
sim.dut.sel = 1;
|
|
203
|
+
expect(sim.dut.y).toBe(0xBB);
|
|
204
|
+
|
|
205
|
+
sim.dut.sel = 2;
|
|
206
|
+
expect(sim.dut.y).toBe(0xCC);
|
|
207
|
+
|
|
208
|
+
sim.dut.sel = 3;
|
|
209
|
+
expect(sim.dut.y).toBe(0xDD);
|
|
210
|
+
|
|
211
|
+
sim.dispose();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Simulation (time-based) e2e tests — fromSource API
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
describe("E2E: Simulation.fromSource (time-based)", () => {
|
|
220
|
+
test("counter with timed clock: step-by-step", () => {
|
|
221
|
+
interface CounterPorts {
|
|
222
|
+
rst: number;
|
|
223
|
+
en: number;
|
|
224
|
+
readonly count: number;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
228
|
+
|
|
229
|
+
sim.addClock("clk", { period: 10 });
|
|
230
|
+
expect(sim.time()).toBe(0);
|
|
231
|
+
|
|
232
|
+
// Reset
|
|
233
|
+
sim.dut.rst = 1;
|
|
234
|
+
sim.runUntil(20);
|
|
235
|
+
sim.dut.rst = 0;
|
|
236
|
+
sim.dut.en = 1;
|
|
237
|
+
|
|
238
|
+
sim.runUntil(100);
|
|
239
|
+
|
|
240
|
+
const count = sim.dut.count;
|
|
241
|
+
expect(count).toBeGreaterThan(0);
|
|
242
|
+
expect(sim.time()).toBe(100);
|
|
243
|
+
|
|
244
|
+
sim.dispose();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// Simulator (event-based) e2e tests — fromProject API
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
describe("E2E: Simulator.fromProject (event-based)", () => {
|
|
253
|
+
test("combinational adder from project directory", () => {
|
|
254
|
+
interface AdderPorts {
|
|
255
|
+
rst: number;
|
|
256
|
+
a: number;
|
|
257
|
+
b: number;
|
|
258
|
+
readonly sum: number;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const sim = Simulator.fromProject<AdderPorts>(ADDER_PROJECT, "Adder");
|
|
262
|
+
|
|
263
|
+
sim.dut.a = 100;
|
|
264
|
+
sim.dut.b = 200;
|
|
265
|
+
sim.tick();
|
|
266
|
+
expect(sim.dut.sum).toBe(300);
|
|
267
|
+
|
|
268
|
+
sim.dut.a = 0xFFFF;
|
|
269
|
+
sim.dut.b = 1;
|
|
270
|
+
sim.tick();
|
|
271
|
+
expect(sim.dut.sum).toBe(0x10000);
|
|
272
|
+
|
|
273
|
+
sim.dispose();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("sequential counter from project directory", () => {
|
|
277
|
+
interface CounterPorts {
|
|
278
|
+
rst: number;
|
|
279
|
+
en: number;
|
|
280
|
+
readonly count: number;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const sim = Simulator.fromProject<CounterPorts>(COUNTER_PROJECT, "Counter");
|
|
284
|
+
|
|
285
|
+
// Reset the counter
|
|
286
|
+
sim.dut.rst = 1;
|
|
287
|
+
sim.tick();
|
|
288
|
+
sim.dut.rst = 0;
|
|
289
|
+
sim.tick();
|
|
290
|
+
expect(sim.dut.count).toBe(0);
|
|
291
|
+
|
|
292
|
+
// Enable counting
|
|
293
|
+
sim.dut.en = 1;
|
|
294
|
+
sim.tick();
|
|
295
|
+
expect(sim.dut.count).toBe(1);
|
|
296
|
+
|
|
297
|
+
sim.tick();
|
|
298
|
+
expect(sim.dut.count).toBe(2);
|
|
299
|
+
|
|
300
|
+
sim.tick();
|
|
301
|
+
expect(sim.dut.count).toBe(3);
|
|
302
|
+
|
|
303
|
+
sim.dispose();
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
// Simulation (time-based) e2e tests — fromProject API
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
describe("E2E: Simulation.fromProject (time-based)", () => {
|
|
312
|
+
test("counter with timed clock from project directory", () => {
|
|
313
|
+
interface CounterPorts {
|
|
314
|
+
rst: number;
|
|
315
|
+
en: number;
|
|
316
|
+
readonly count: number;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const sim = Simulation.fromProject<CounterPorts>(COUNTER_PROJECT, "Counter");
|
|
320
|
+
|
|
321
|
+
sim.addClock("clk", { period: 10 });
|
|
322
|
+
expect(sim.time()).toBe(0);
|
|
323
|
+
|
|
324
|
+
// Reset
|
|
325
|
+
sim.dut.rst = 1;
|
|
326
|
+
sim.runUntil(20);
|
|
327
|
+
sim.dut.rst = 0;
|
|
328
|
+
sim.dut.en = 1;
|
|
329
|
+
|
|
330
|
+
sim.runUntil(100);
|
|
331
|
+
|
|
332
|
+
const count = sim.dut.count;
|
|
333
|
+
expect(count).toBeGreaterThan(0);
|
|
334
|
+
expect(sim.time()).toBe(100);
|
|
335
|
+
|
|
336
|
+
sim.dispose();
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
// Backward compat: Simulator.create() with manual ModuleDefinition
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
describe("E2E: Simulator.create (backward compat)", () => {
|
|
345
|
+
test("combinational adder via Simulator.create()", () => {
|
|
346
|
+
interface AdderPorts {
|
|
347
|
+
rst: number;
|
|
348
|
+
a: number;
|
|
349
|
+
b: number;
|
|
350
|
+
readonly sum: number;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const addon = loadNativeAddon();
|
|
354
|
+
const nativeCreateSimulator = createSimulatorBridge(addon);
|
|
355
|
+
|
|
356
|
+
const sim = Simulator.create<AdderPorts>(
|
|
357
|
+
{
|
|
358
|
+
__celox_module: true,
|
|
359
|
+
name: "Adder",
|
|
360
|
+
source: ADDER_SOURCE,
|
|
361
|
+
ports: {
|
|
362
|
+
clk: { direction: "input", type: "clock", width: 1 },
|
|
363
|
+
rst: { direction: "input", type: "reset", width: 1 },
|
|
364
|
+
a: { direction: "input", type: "logic", width: 16 },
|
|
365
|
+
b: { direction: "input", type: "logic", width: 16 },
|
|
366
|
+
sum: { direction: "output", type: "logic", width: 17 },
|
|
367
|
+
},
|
|
368
|
+
events: ["clk"],
|
|
369
|
+
},
|
|
370
|
+
{ __nativeCreate: nativeCreateSimulator },
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
sim.dut.a = 100;
|
|
374
|
+
sim.dut.b = 200;
|
|
375
|
+
sim.tick();
|
|
376
|
+
expect(sim.dut.sum).toBe(300);
|
|
377
|
+
|
|
378
|
+
sim.dispose();
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
// 4-state simulation e2e tests
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
|
|
386
|
+
const AND_OR_SOURCE = `
|
|
387
|
+
module AndOr (
|
|
388
|
+
a: input logic,
|
|
389
|
+
b: input logic,
|
|
390
|
+
y_and: output logic,
|
|
391
|
+
y_or: output logic,
|
|
392
|
+
) {
|
|
393
|
+
assign y_and = a & b;
|
|
394
|
+
assign y_or = a | b;
|
|
395
|
+
}
|
|
396
|
+
`;
|
|
397
|
+
|
|
398
|
+
const LOGIC_BIT_MIX_SOURCE = `
|
|
399
|
+
module LogicBitMix (
|
|
400
|
+
a_logic: input logic<8>,
|
|
401
|
+
b_bit: input bit<8>,
|
|
402
|
+
y_logic_from_bit: output logic<8>,
|
|
403
|
+
y_bit_from_logic: output bit<8>,
|
|
404
|
+
) {
|
|
405
|
+
assign y_bit_from_logic = a_logic;
|
|
406
|
+
assign y_logic_from_bit = b_bit;
|
|
407
|
+
}
|
|
408
|
+
`;
|
|
409
|
+
|
|
410
|
+
const FF_SOURCE = `
|
|
411
|
+
module FF (
|
|
412
|
+
clk: input clock,
|
|
413
|
+
rst: input reset,
|
|
414
|
+
d: input logic<8>,
|
|
415
|
+
q: output logic<8>,
|
|
416
|
+
) {
|
|
417
|
+
always_ff (clk, rst) {
|
|
418
|
+
if_reset {
|
|
419
|
+
q = 8'd0;
|
|
420
|
+
} else {
|
|
421
|
+
q = d;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
`;
|
|
426
|
+
|
|
427
|
+
const ADDER_4STATE_SOURCE = `
|
|
428
|
+
module Adder4S (
|
|
429
|
+
a: input logic<8>,
|
|
430
|
+
b: input logic<8>,
|
|
431
|
+
y: output logic<8>,
|
|
432
|
+
) {
|
|
433
|
+
assign y = a + b;
|
|
434
|
+
}
|
|
435
|
+
`;
|
|
436
|
+
|
|
437
|
+
describe("E2E: 4-state simulation", () => {
|
|
438
|
+
let raw: RawNapiSimulatorHandle | undefined;
|
|
439
|
+
let addon: RawNapiAddon;
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
addon = loadNativeAddon();
|
|
443
|
+
} catch (e) {
|
|
444
|
+
throw new Error(`Failed to load NAPI addon for 4-state tests: ${e}`);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
afterEach(() => {
|
|
448
|
+
raw?.dispose();
|
|
449
|
+
raw = undefined;
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("initial values: logic ports start as X, bit ports start as 0", () => {
|
|
453
|
+
const source = `
|
|
454
|
+
module InitTest (
|
|
455
|
+
a: input logic<8>,
|
|
456
|
+
b: input bit<8>,
|
|
457
|
+
) {}
|
|
458
|
+
`;
|
|
459
|
+
raw = new addon.NativeSimulatorHandle(source, "InitTest", { fourState: true });
|
|
460
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
461
|
+
const buf = raw.sharedMemory().buffer;
|
|
462
|
+
|
|
463
|
+
// logic port should have mask=0xFF (all X)
|
|
464
|
+
const [valA, maskA] = readFourState(buf, layout.forDut.a);
|
|
465
|
+
expect(valA).toBe(0);
|
|
466
|
+
expect(maskA).toBe(0xFF);
|
|
467
|
+
|
|
468
|
+
// bit port should have mask=0 (defined)
|
|
469
|
+
// bit is not 4-state, so no mask — reading its value should be 0
|
|
470
|
+
expect(layout.forDut.b.is4state).toBe(false);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("writing X clears value and sets mask", () => {
|
|
474
|
+
interface Ports {
|
|
475
|
+
a: number;
|
|
476
|
+
readonly y_and: number;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const sim = Simulator.fromSource<Ports>(AND_OR_SOURCE, "AndOr", { fourState: true });
|
|
480
|
+
raw = undefined; // sim manages its own handle
|
|
481
|
+
|
|
482
|
+
// Write X to input 'a' via DUT
|
|
483
|
+
(sim.dut as any).a = X;
|
|
484
|
+
|
|
485
|
+
// We can't inspect mask through DUT getter (it only returns value),
|
|
486
|
+
// so this test verifies X write doesn't throw and propagation works.
|
|
487
|
+
// For detailed mask inspection, see the raw NAPI tests below.
|
|
488
|
+
sim.dispose();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("AND: 0 & X = 0 (dominant zero)", () => {
|
|
492
|
+
raw = new addon.NativeSimulatorHandle(AND_OR_SOURCE, "AndOr", { fourState: true });
|
|
493
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
494
|
+
const buf = raw.sharedMemory().buffer;
|
|
495
|
+
const view = new DataView(buf);
|
|
496
|
+
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
497
|
+
|
|
498
|
+
const sigA = layout.forDut.a;
|
|
499
|
+
const sigB = layout.forDut.b;
|
|
500
|
+
const sigYAnd = layout.forDut.y_and;
|
|
501
|
+
const sigYOr = layout.forDut.y_or;
|
|
502
|
+
|
|
503
|
+
// a = 0 (value=0, mask=0)
|
|
504
|
+
view.setUint8(sigA.offset, 0);
|
|
505
|
+
view.setUint8(sigA.offset + sigA.byteSize, 0);
|
|
506
|
+
|
|
507
|
+
// b = X (value=0, mask=1)
|
|
508
|
+
view.setUint8(sigB.offset, 0);
|
|
509
|
+
view.setUint8(sigB.offset + sigB.byteSize, 1);
|
|
510
|
+
|
|
511
|
+
raw.evalComb();
|
|
512
|
+
|
|
513
|
+
// 0 & X = 0 (mask should be 0 — dominant zero)
|
|
514
|
+
const [vAnd, mAnd] = readFourState(buf, sigYAnd);
|
|
515
|
+
expect(vAnd).toBe(0);
|
|
516
|
+
expect(mAnd).toBe(0);
|
|
517
|
+
|
|
518
|
+
// 0 | X = X (mask should be 1)
|
|
519
|
+
const [vOr, mOr] = readFourState(buf, sigYOr);
|
|
520
|
+
expect(vOr).toBe(0);
|
|
521
|
+
expect(mOr).toBe(1);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test("OR: 1 | X = 1 (dominant one)", () => {
|
|
525
|
+
raw = new addon.NativeSimulatorHandle(AND_OR_SOURCE, "AndOr", { fourState: true });
|
|
526
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
527
|
+
const buf = raw.sharedMemory().buffer;
|
|
528
|
+
const view = new DataView(buf);
|
|
529
|
+
|
|
530
|
+
const sigA = layout.forDut.a;
|
|
531
|
+
const sigB = layout.forDut.b;
|
|
532
|
+
const sigYOr = layout.forDut.y_or;
|
|
533
|
+
|
|
534
|
+
// a = 1 (value=1, mask=0)
|
|
535
|
+
view.setUint8(sigA.offset, 1);
|
|
536
|
+
view.setUint8(sigA.offset + sigA.byteSize, 0);
|
|
537
|
+
|
|
538
|
+
// b = X (value=0, mask=1)
|
|
539
|
+
view.setUint8(sigB.offset, 0);
|
|
540
|
+
view.setUint8(sigB.offset + sigB.byteSize, 1);
|
|
541
|
+
|
|
542
|
+
raw.evalComb();
|
|
543
|
+
|
|
544
|
+
// 1 | X = 1 (mask should be 0 — dominant one)
|
|
545
|
+
const [vOr, mOr] = readFourState(buf, sigYOr);
|
|
546
|
+
expect(vOr).toBe(1);
|
|
547
|
+
expect(mOr).toBe(0);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("logic-to-bit assignment strips X mask", () => {
|
|
551
|
+
raw = new addon.NativeSimulatorHandle(LOGIC_BIT_MIX_SOURCE, "LogicBitMix", { fourState: true });
|
|
552
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
553
|
+
const buf = raw.sharedMemory().buffer;
|
|
554
|
+
const view = new DataView(buf);
|
|
555
|
+
|
|
556
|
+
const sigALogic = layout.forDut.a_logic;
|
|
557
|
+
const sigYBitFromLogic = layout.forDut.y_bit_from_logic;
|
|
558
|
+
|
|
559
|
+
// a_logic = all-X (value=0, mask=0xFF)
|
|
560
|
+
view.setUint8(sigALogic.offset, 0);
|
|
561
|
+
view.setUint8(sigALogic.offset + sigALogic.byteSize, 0xFF);
|
|
562
|
+
|
|
563
|
+
raw.evalComb();
|
|
564
|
+
|
|
565
|
+
// y_bit_from_logic is bit type — X should be stripped (mask=0)
|
|
566
|
+
expect(sigYBitFromLogic.is4state).toBe(false);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
test("bit-to-logic assignment has no X", () => {
|
|
570
|
+
raw = new addon.NativeSimulatorHandle(LOGIC_BIT_MIX_SOURCE, "LogicBitMix", { fourState: true });
|
|
571
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
572
|
+
const buf = raw.sharedMemory().buffer;
|
|
573
|
+
const view = new DataView(buf);
|
|
574
|
+
|
|
575
|
+
const sigBBit = layout.forDut.b_bit;
|
|
576
|
+
const sigYLogicFromBit = layout.forDut.y_logic_from_bit;
|
|
577
|
+
|
|
578
|
+
// b_bit = 0xAA (bit type, always defined)
|
|
579
|
+
view.setUint8(sigBBit.offset, 0xAA);
|
|
580
|
+
|
|
581
|
+
raw.evalComb();
|
|
582
|
+
|
|
583
|
+
// y_logic_from_bit should be 0xAA with mask=0
|
|
584
|
+
const [vLogic, mLogic] = readFourState(buf, sigYLogicFromBit);
|
|
585
|
+
expect(vLogic).toBe(0xAA);
|
|
586
|
+
expect(mLogic).toBe(0);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test("arithmetic with X produces all-X output", () => {
|
|
590
|
+
raw = new addon.NativeSimulatorHandle(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
|
|
591
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
592
|
+
const buf = raw.sharedMemory().buffer;
|
|
593
|
+
const view = new DataView(buf);
|
|
594
|
+
|
|
595
|
+
const sigA = layout.forDut.a;
|
|
596
|
+
const sigB = layout.forDut.b;
|
|
597
|
+
const sigY = layout.forDut.y;
|
|
598
|
+
|
|
599
|
+
// a = 42 (defined), b = X (all X)
|
|
600
|
+
view.setUint8(sigA.offset, 42);
|
|
601
|
+
view.setUint8(sigA.offset + sigA.byteSize, 0); // mask=0
|
|
602
|
+
|
|
603
|
+
view.setUint8(sigB.offset, 0);
|
|
604
|
+
view.setUint8(sigB.offset + sigB.byteSize, 0xFF); // mask=0xFF
|
|
605
|
+
|
|
606
|
+
raw.evalComb();
|
|
607
|
+
|
|
608
|
+
// a + X = all-X
|
|
609
|
+
const [, mY] = readFourState(buf, sigY);
|
|
610
|
+
expect(mY).toBe(0xFF);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
test("defined inputs in 4-state mode behave like 2-state", () => {
|
|
614
|
+
raw = new addon.NativeSimulatorHandle(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
|
|
615
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
616
|
+
const buf = raw.sharedMemory().buffer;
|
|
617
|
+
const view = new DataView(buf);
|
|
618
|
+
|
|
619
|
+
const sigA = layout.forDut.a;
|
|
620
|
+
const sigB = layout.forDut.b;
|
|
621
|
+
const sigY = layout.forDut.y;
|
|
622
|
+
|
|
623
|
+
// a = 100 (defined), b = 55 (defined)
|
|
624
|
+
view.setUint8(sigA.offset, 100);
|
|
625
|
+
view.setUint8(sigA.offset + sigA.byteSize, 0);
|
|
626
|
+
|
|
627
|
+
view.setUint8(sigB.offset, 55);
|
|
628
|
+
view.setUint8(sigB.offset + sigB.byteSize, 0);
|
|
629
|
+
|
|
630
|
+
raw.evalComb();
|
|
631
|
+
|
|
632
|
+
const [vY, mY] = readFourState(buf, sigY);
|
|
633
|
+
expect(vY).toBe(155);
|
|
634
|
+
expect(mY).toBe(0);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test("FF captures X from input, reset clears X", () => {
|
|
638
|
+
raw = new addon.NativeSimulatorHandle(FF_SOURCE, "FF", { fourState: true });
|
|
639
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
640
|
+
const buf = raw.sharedMemory().buffer;
|
|
641
|
+
const view = new DataView(buf);
|
|
642
|
+
const events: Record<string, number> = JSON.parse(raw.eventsJson);
|
|
643
|
+
|
|
644
|
+
const sigRst = layout.forDut.rst;
|
|
645
|
+
const sigD = layout.forDut.d;
|
|
646
|
+
const sigQ = layout.forDut.q;
|
|
647
|
+
const clkEventId = events.clk;
|
|
648
|
+
|
|
649
|
+
// 1. Reset: rst=1, d=X
|
|
650
|
+
view.setUint8(sigRst.offset, 1);
|
|
651
|
+
view.setUint8(sigRst.offset + sigRst.byteSize, 0); // rst is defined
|
|
652
|
+
|
|
653
|
+
view.setUint8(sigD.offset, 0);
|
|
654
|
+
view.setUint8(sigD.offset + sigD.byteSize, 0xFF); // d = all-X
|
|
655
|
+
|
|
656
|
+
raw.tick(clkEventId);
|
|
657
|
+
|
|
658
|
+
// After reset, q should be 0 with mask=0
|
|
659
|
+
const [vQ1, mQ1] = readFourState(buf, sigQ);
|
|
660
|
+
expect(vQ1).toBe(0);
|
|
661
|
+
expect(mQ1).toBe(0);
|
|
662
|
+
|
|
663
|
+
// 2. Release reset, d = partial X (value=0xA5, mask=0x0F)
|
|
664
|
+
view.setUint8(sigRst.offset, 0);
|
|
665
|
+
view.setUint8(sigRst.offset + sigRst.byteSize, 0);
|
|
666
|
+
|
|
667
|
+
view.setUint8(sigD.offset, 0xA5);
|
|
668
|
+
view.setUint8(sigD.offset + sigD.byteSize, 0x0F);
|
|
669
|
+
|
|
670
|
+
raw.tick(clkEventId);
|
|
671
|
+
|
|
672
|
+
// FF should capture X mask from d
|
|
673
|
+
const [, mQ2] = readFourState(buf, sigQ);
|
|
674
|
+
expect(mQ2).toBe(0x0F);
|
|
675
|
+
|
|
676
|
+
// 3. Reset again: should clear X
|
|
677
|
+
view.setUint8(sigRst.offset, 1);
|
|
678
|
+
view.setUint8(sigRst.offset + sigRst.byteSize, 0);
|
|
679
|
+
|
|
680
|
+
raw.tick(clkEventId);
|
|
681
|
+
|
|
682
|
+
const [vQ3, mQ3] = readFourState(buf, sigQ);
|
|
683
|
+
expect(vQ3).toBe(0);
|
|
684
|
+
expect(mQ3).toBe(0);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
test("FourState write through DUT sets value and mask", () => {
|
|
688
|
+
raw = new addon.NativeSimulatorHandle(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
|
|
689
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
690
|
+
const buf = raw.sharedMemory().buffer;
|
|
691
|
+
const view = new DataView(buf);
|
|
692
|
+
|
|
693
|
+
const sigA = layout.forDut.a;
|
|
694
|
+
|
|
695
|
+
// Write via DUT-style: FourState(0b1010_0101, 0b0000_1111)
|
|
696
|
+
// value=0xA5, mask=0x0F means lower 4 bits are X
|
|
697
|
+
view.setUint8(sigA.offset, 0xA5);
|
|
698
|
+
view.setUint8(sigA.offset + sigA.byteSize, 0x0F);
|
|
699
|
+
|
|
700
|
+
const [vA, mA] = readFourState(buf, sigA);
|
|
701
|
+
expect(vA).toBe(0xA5);
|
|
702
|
+
expect(mA).toBe(0x0F);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
test("setting defined value clears X mask", () => {
|
|
706
|
+
raw = new addon.NativeSimulatorHandle(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
|
|
707
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
708
|
+
const buf = raw.sharedMemory().buffer;
|
|
709
|
+
const view = new DataView(buf);
|
|
710
|
+
|
|
711
|
+
const sigA = layout.forDut.a;
|
|
712
|
+
|
|
713
|
+
// Start with X
|
|
714
|
+
view.setUint8(sigA.offset, 0);
|
|
715
|
+
view.setUint8(sigA.offset + sigA.byteSize, 0xFF);
|
|
716
|
+
|
|
717
|
+
const [, mBefore] = readFourState(buf, sigA);
|
|
718
|
+
expect(mBefore).toBe(0xFF);
|
|
719
|
+
|
|
720
|
+
// Write a defined value (clear mask)
|
|
721
|
+
view.setUint8(sigA.offset, 42);
|
|
722
|
+
view.setUint8(sigA.offset + sigA.byteSize, 0);
|
|
723
|
+
|
|
724
|
+
const [vAfter, mAfter] = readFourState(buf, sigA);
|
|
725
|
+
expect(vAfter).toBe(42);
|
|
726
|
+
expect(mAfter).toBe(0);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
test("4-state through DUT high-level API (fromSource with fourState)", () => {
|
|
730
|
+
interface Ports {
|
|
731
|
+
a: number;
|
|
732
|
+
b: number;
|
|
733
|
+
readonly y: number;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
|
|
737
|
+
|
|
738
|
+
// Write defined values — should behave like 2-state
|
|
739
|
+
sim.dut.a = 100;
|
|
740
|
+
sim.dut.b = 55;
|
|
741
|
+
expect(sim.dut.y).toBe(155);
|
|
742
|
+
|
|
743
|
+
// Write X to a — output should propagate X (value reads as 0)
|
|
744
|
+
(sim.dut as any).a = X;
|
|
745
|
+
// After writing X, the value part of 'y' is implementation-defined
|
|
746
|
+
// but the read should not throw
|
|
747
|
+
const _yVal = sim.dut.y;
|
|
748
|
+
expect(typeof _yVal).toBe("number");
|
|
749
|
+
|
|
750
|
+
// Write FourState with partial X
|
|
751
|
+
(sim.dut as any).a = FourState(0xA0, 0x0F);
|
|
752
|
+
const _yVal2 = sim.dut.y;
|
|
753
|
+
expect(typeof _yVal2).toBe("number");
|
|
754
|
+
|
|
755
|
+
sim.dispose();
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// ---------------------------------------------------------------------------
|
|
760
|
+
// 4-state: high-level DUT API (Simulator.fromSource)
|
|
761
|
+
// ---------------------------------------------------------------------------
|
|
762
|
+
|
|
763
|
+
describe("E2E: 4-state high-level DUT API", () => {
|
|
764
|
+
test("counter in 4-state mode: reset clears X, counting works", () => {
|
|
765
|
+
interface CounterPorts {
|
|
766
|
+
rst: number;
|
|
767
|
+
en: number;
|
|
768
|
+
readonly count: number;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const sim = Simulator.fromSource<CounterPorts>(
|
|
772
|
+
COUNTER_SOURCE, "Counter", { fourState: true },
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
// In 4-state mode, count starts as X. Reset should clear it.
|
|
776
|
+
sim.dut.rst = 1;
|
|
777
|
+
sim.tick();
|
|
778
|
+
sim.dut.rst = 0;
|
|
779
|
+
sim.tick();
|
|
780
|
+
expect(sim.dut.count).toBe(0);
|
|
781
|
+
|
|
782
|
+
// Enable counting — should work exactly like 2-state
|
|
783
|
+
sim.dut.en = 1;
|
|
784
|
+
sim.tick();
|
|
785
|
+
expect(sim.dut.count).toBe(1);
|
|
786
|
+
|
|
787
|
+
sim.tick();
|
|
788
|
+
expect(sim.dut.count).toBe(2);
|
|
789
|
+
|
|
790
|
+
sim.tick();
|
|
791
|
+
expect(sim.dut.count).toBe(3);
|
|
792
|
+
|
|
793
|
+
sim.dispose();
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("multiplexer with X selector produces X output", () => {
|
|
797
|
+
const addon = loadNativeAddon();
|
|
798
|
+
const raw = new addon.NativeSimulatorHandle(MULTIPLEXER_SOURCE, "Mux4", { fourState: true });
|
|
799
|
+
const layout = parseNapiLayout(raw.layoutJson);
|
|
800
|
+
const buf = raw.sharedMemory().buffer;
|
|
801
|
+
const view = new DataView(buf);
|
|
802
|
+
|
|
803
|
+
const sigSel = layout.forDut.sel;
|
|
804
|
+
const sigD0 = layout.forDut.d0;
|
|
805
|
+
const sigY = layout.forDut.y;
|
|
806
|
+
|
|
807
|
+
// Set d0 = 0xAA (defined)
|
|
808
|
+
view.setUint8(sigD0.offset, 0xAA);
|
|
809
|
+
view.setUint8(sigD0.offset + sigD0.byteSize, 0);
|
|
810
|
+
|
|
811
|
+
// Set sel = X
|
|
812
|
+
view.setUint8(sigSel.offset, 0);
|
|
813
|
+
view.setUint8(sigSel.offset + sigSel.byteSize, 0x03);
|
|
814
|
+
|
|
815
|
+
raw.evalComb();
|
|
816
|
+
|
|
817
|
+
// With X selector, output should be all-X
|
|
818
|
+
const [, mY] = readFourState(buf, sigY);
|
|
819
|
+
expect(mY).toBe(0xFF);
|
|
820
|
+
|
|
821
|
+
raw.dispose();
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
test("FF via DUT API: write X input, tick, read output", () => {
|
|
825
|
+
interface FFPorts {
|
|
826
|
+
rst: number;
|
|
827
|
+
d: number;
|
|
828
|
+
readonly q: number;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const sim = Simulator.fromSource<FFPorts>(FF_SOURCE, "FF", { fourState: true });
|
|
832
|
+
|
|
833
|
+
// Reset to clear initial X
|
|
834
|
+
sim.dut.rst = 1;
|
|
835
|
+
sim.tick();
|
|
836
|
+
sim.dut.rst = 0;
|
|
837
|
+
expect(sim.dut.q).toBe(0);
|
|
838
|
+
|
|
839
|
+
// Write a defined value
|
|
840
|
+
sim.dut.d = 0x42;
|
|
841
|
+
sim.tick();
|
|
842
|
+
expect(sim.dut.q).toBe(0x42);
|
|
843
|
+
|
|
844
|
+
// Write X to d, tick — q should capture it (value read still returns a number)
|
|
845
|
+
(sim.dut as any).d = X;
|
|
846
|
+
sim.tick();
|
|
847
|
+
expect(typeof sim.dut.q).toBe("number");
|
|
848
|
+
|
|
849
|
+
// Write defined value again — q should recover
|
|
850
|
+
sim.dut.d = 0x99;
|
|
851
|
+
sim.tick();
|
|
852
|
+
expect(sim.dut.q).toBe(0x99);
|
|
853
|
+
|
|
854
|
+
sim.dispose();
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
test("X to defined transition: adder recovers from X", () => {
|
|
858
|
+
interface Ports {
|
|
859
|
+
a: number;
|
|
860
|
+
b: number;
|
|
861
|
+
readonly y: number;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", { fourState: true });
|
|
865
|
+
|
|
866
|
+
// Start with X
|
|
867
|
+
(sim.dut as any).a = X;
|
|
868
|
+
sim.dut.b = 10;
|
|
869
|
+
// Output has X — just verify it doesn't crash
|
|
870
|
+
expect(typeof sim.dut.y).toBe("number");
|
|
871
|
+
|
|
872
|
+
// Clear X by writing defined values
|
|
873
|
+
sim.dut.a = 20;
|
|
874
|
+
sim.dut.b = 30;
|
|
875
|
+
expect(sim.dut.y).toBe(50);
|
|
876
|
+
|
|
877
|
+
sim.dispose();
|
|
878
|
+
});
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
// ---------------------------------------------------------------------------
|
|
882
|
+
// 4-state: Simulation (time-based) tests
|
|
883
|
+
// ---------------------------------------------------------------------------
|
|
884
|
+
|
|
885
|
+
describe("E2E: 4-state Simulation (time-based)", () => {
|
|
886
|
+
test("FF with clock-driven 4-state: reset clears X, captures defined values", () => {
|
|
887
|
+
interface FFPorts {
|
|
888
|
+
rst: number;
|
|
889
|
+
d: number;
|
|
890
|
+
readonly q: number;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const sim = Simulation.fromSource<FFPorts>(FF_SOURCE, "FF", { fourState: true });
|
|
894
|
+
|
|
895
|
+
sim.addClock("clk", { period: 10 });
|
|
896
|
+
expect(sim.time()).toBe(0);
|
|
897
|
+
|
|
898
|
+
// Reset to clear initial X on q
|
|
899
|
+
sim.dut.rst = 1;
|
|
900
|
+
sim.runUntil(20);
|
|
901
|
+
sim.dut.rst = 0;
|
|
902
|
+
expect(sim.dut.q).toBe(0);
|
|
903
|
+
|
|
904
|
+
// Drive d with defined value
|
|
905
|
+
sim.dut.d = 0x55;
|
|
906
|
+
sim.runUntil(40);
|
|
907
|
+
expect(sim.dut.q).toBe(0x55);
|
|
908
|
+
|
|
909
|
+
// Drive d with different value
|
|
910
|
+
sim.dut.d = 0xAA;
|
|
911
|
+
sim.runUntil(60);
|
|
912
|
+
expect(sim.dut.q).toBe(0xAA);
|
|
913
|
+
|
|
914
|
+
sim.dispose();
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
test("counter in 4-state time-based mode", () => {
|
|
918
|
+
interface CounterPorts {
|
|
919
|
+
rst: number;
|
|
920
|
+
en: number;
|
|
921
|
+
readonly count: number;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const sim = Simulation.fromSource<CounterPorts>(
|
|
925
|
+
COUNTER_SOURCE, "Counter", { fourState: true },
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
sim.addClock("clk", { period: 10 });
|
|
929
|
+
|
|
930
|
+
// Reset
|
|
931
|
+
sim.dut.rst = 1;
|
|
932
|
+
sim.runUntil(20);
|
|
933
|
+
sim.dut.rst = 0;
|
|
934
|
+
sim.dut.en = 1;
|
|
935
|
+
|
|
936
|
+
sim.runUntil(100);
|
|
937
|
+
|
|
938
|
+
const count = sim.dut.count;
|
|
939
|
+
expect(count).toBeGreaterThan(0);
|
|
940
|
+
expect(sim.time()).toBe(100);
|
|
941
|
+
|
|
942
|
+
sim.dispose();
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
test("4-state combinational in time-based simulation", () => {
|
|
946
|
+
interface Ports {
|
|
947
|
+
a: number;
|
|
948
|
+
b: number;
|
|
949
|
+
readonly y: number;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const sim = Simulation.fromSource<Ports>(
|
|
953
|
+
ADDER_4STATE_SOURCE, "Adder4S", { fourState: true },
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
// No clock needed for pure combinational — just set values and read
|
|
957
|
+
sim.dut.a = 100;
|
|
958
|
+
sim.dut.b = 55;
|
|
959
|
+
// runUntil(0) to force eval
|
|
960
|
+
sim.runUntil(0);
|
|
961
|
+
expect(sim.dut.y).toBe(155);
|
|
962
|
+
|
|
963
|
+
sim.dispose();
|
|
964
|
+
});
|
|
965
|
+
});
|