@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/dist/dut.d.ts +1 -1
- package/dist/dut.d.ts.map +1 -1
- package/dist/dut.js +23 -10
- package/dist/dut.js.map +1 -1
- package/dist/index.d.ts +8 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -10
- package/dist/index.js.map +1 -1
- package/dist/napi-bridge.d.ts +1 -1
- package/dist/napi-bridge.d.ts.map +1 -1
- package/dist/napi-bridge.js +1 -1
- package/dist/napi-bridge.js.map +1 -1
- package/dist/napi-helpers.d.ts +17 -4
- package/dist/napi-helpers.d.ts.map +1 -1
- package/dist/napi-helpers.js +36 -4
- package/dist/napi-helpers.js.map +1 -1
- package/dist/simulation.d.ts +2 -2
- package/dist/simulation.d.ts.map +1 -1
- package/dist/simulation.js +30 -14
- package/dist/simulation.js.map +1 -1
- package/dist/simulator.d.ts +2 -2
- package/dist/simulator.d.ts.map +1 -1
- package/dist/simulator.js +29 -13
- package/dist/simulator.js.map +1 -1
- package/dist/types.d.ts +12 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/dut.test.ts +1037 -742
- package/src/dut.ts +580 -528
- package/src/e2e.bench.ts +512 -509
- package/src/e2e.test.ts +2056 -1574
- package/src/index.ts +47 -46
- package/src/matchers.ts +81 -81
- package/src/napi-bridge.ts +5 -5
- package/src/napi-helpers.ts +498 -400
- package/src/simulation.test.ts +395 -368
- package/src/simulation.ts +509 -430
- package/src/simulator.test.ts +201 -168
- package/src/simulator.ts +328 -267
- package/src/types.ts +108 -98
package/src/e2e.test.ts
CHANGED
|
@@ -9,22 +9,25 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import path from "node:path";
|
|
12
|
-
import {
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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(
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
267
|
+
const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
|
|
265
268
|
|
|
266
|
-
|
|
267
|
-
|
|
269
|
+
sim.addClock("clk", { period: 10 });
|
|
270
|
+
expect(sim.time()).toBe(0);
|
|
268
271
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
278
|
+
sim.runUntil(100);
|
|
276
279
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
+
const count = sim.dut.count;
|
|
281
|
+
expect(count).toBeGreaterThan(0n);
|
|
282
|
+
expect(sim.time()).toBe(100);
|
|
280
283
|
|
|
281
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
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
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
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
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
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
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
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
|
-
|
|
1490
|
-
|
|
1569
|
+
test("Simulator: read child instance ports via dut.u_sub", () => {
|
|
1570
|
+
const sim = Simulator.fromSource(HIERARCHY_SOURCE, "Top");
|
|
1491
1571
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1572
|
+
sim.dut.top_in = 0xabn;
|
|
1573
|
+
sim.tick();
|
|
1574
|
+
expect(sim.dut.top_out).toBe(0xabn);
|
|
1495
1575
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
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
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
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
|
-
|
|
1507
|
-
|
|
1586
|
+
sim.dispose();
|
|
1587
|
+
});
|
|
1508
1588
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
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
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1593
|
+
sim.dut.rst = 0n;
|
|
1594
|
+
sim.runUntil(20);
|
|
1595
|
+
sim.dut.rst = 1n;
|
|
1516
1596
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
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
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
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
|
-
|
|
1527
|
-
|
|
1606
|
+
sim.dispose();
|
|
1607
|
+
});
|
|
1528
1608
|
});
|
|
1529
1609
|
|
|
1530
1610
|
describe("E2E: parameter override — DUT correctness", () => {
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
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
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
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
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
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
|
-
|
|
1919
|
+
const sim = Simulator.fromSource<TopPorts>(INTERFACE_PORT_SOURCE, "Top");
|
|
1812
1920
|
|
|
1813
|
-
|
|
1814
|
-
|
|
1921
|
+
sim.dut.sig.data = 0x42n;
|
|
1922
|
+
expect(sim.dut.out).toBe(0x42n);
|
|
1815
1923
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1924
|
+
sim.dut.sig.data = 0xffn;
|
|
1925
|
+
expect(sim.dut.out).toBe(0xffn);
|
|
1818
1926
|
|
|
1819
|
-
|
|
1820
|
-
|
|
1927
|
+
sim.dut.sig.data = 0x00n;
|
|
1928
|
+
expect(sim.dut.out).toBe(0x00n);
|
|
1821
1929
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1930
|
+
sim.dispose();
|
|
1931
|
+
});
|
|
1824
1932
|
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
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
|
-
|
|
1952
|
+
const sim = Simulator.fromSource(PRODUCER_ONLY, "Top");
|
|
1845
1953
|
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1954
|
+
expect(() => {
|
|
1955
|
+
(sim.dut as any).bus.data = 0n;
|
|
1956
|
+
}).toThrow("Cannot write to output port 'data'");
|
|
1849
1957
|
|
|
1850
|
-
|
|
1851
|
-
|
|
1958
|
+
sim.dispose();
|
|
1959
|
+
});
|
|
1852
1960
|
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
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
|
-
|
|
1968
|
+
const sim = Simulator.fromSource<TopPorts>(
|
|
1969
|
+
INTERFACE_HIERARCHY_SOURCE,
|
|
1970
|
+
"Top",
|
|
1971
|
+
);
|
|
1861
1972
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1973
|
+
sim.dut.val = 0xabn;
|
|
1974
|
+
expect(sim.dut.out).toBe(0xabn);
|
|
1975
|
+
expect(sim.dut.got).toBe(1n);
|
|
1865
1976
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1977
|
+
sim.dut.val = 0x00n;
|
|
1978
|
+
expect(sim.dut.out).toBe(0x00n);
|
|
1868
1979
|
|
|
1869
|
-
|
|
1870
|
-
|
|
1980
|
+
sim.dispose();
|
|
1981
|
+
});
|
|
1871
1982
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
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
|
-
|
|
1989
|
+
const sim = Simulator.fromSource<TopPorts>(INTERFACE_PORT_SOURCE, "Top");
|
|
1879
1990
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1991
|
+
sim.dut.sig.data = 0x77n;
|
|
1992
|
+
sim.dut.sig.valid = 1n;
|
|
1993
|
+
expect(sim.dut.out).toBe(0x77n);
|
|
1883
1994
|
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1995
|
+
sim.dut.sig.data = 0x33n;
|
|
1996
|
+
sim.dut.sig.valid = 0n;
|
|
1997
|
+
expect(sim.dut.out).toBe(0x33n);
|
|
1887
1998
|
|
|
1888
|
-
|
|
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
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
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
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
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
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
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
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
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
|
-
|
|
2132
|
+
// Reset
|
|
2133
|
+
sim.dut.rst = 0n;
|
|
2134
|
+
sim.tick();
|
|
2135
|
+
sim.dut.rst = 1n;
|
|
2136
|
+
sim.tick();
|
|
2042
2137
|
|
|
2043
|
-
|
|
2044
|
-
|
|
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
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
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
|
-
|
|
2052
|
-
|
|
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
|
-
|
|
2057
|
-
|
|
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
|
-
|
|
2061
|
-
|
|
2062
|
-
expect(sim.fourState("y0").mask).toBe(0n);
|
|
2063
|
-
expect(sim.fourState("y3").mask).toBe(0n);
|
|
2152
|
+
sim.dispose();
|
|
2153
|
+
});
|
|
2064
2154
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2155
|
+
test("child instance internal var is accessible", () => {
|
|
2156
|
+
const sim = Simulator.fromSource(
|
|
2157
|
+
INTERNAL_VAR_HIERARCHY_SOURCE,
|
|
2158
|
+
"TopCounter",
|
|
2159
|
+
);
|
|
2068
2160
|
|
|
2069
|
-
|
|
2070
|
-
|
|
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
|
-
|
|
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
|
});
|