@celox-sim/celox 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/e2e.test.ts CHANGED
@@ -13,11 +13,12 @@ import { describe, test, expect, afterEach } from "vitest";
13
13
  import { Simulator } from "./simulator.js";
14
14
  import { Simulation } from "./simulation.js";
15
15
  import { readFourState } from "./dut.js";
16
- import { X, FourState } from "./types.js";
16
+ import { X, FourState, SimulationTimeoutError } from "./types.js";
17
17
  import {
18
18
  createSimulatorBridge,
19
19
  loadNativeAddon,
20
20
  parseNapiLayout,
21
+ parseSignalPath,
21
22
  type RawNapiAddon,
22
23
  type RawNapiSimulatorHandle,
23
24
  } from "./napi-helpers.js";
@@ -148,11 +149,11 @@ describe("E2E: Simulator.fromSource (event-based)", () => {
148
149
 
149
150
  const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
150
151
 
151
- // Reset the counter
152
- sim.dut.rst = 1;
153
- sim.tick();
152
+ // Reset the counter (default async_low: rst=0 is active)
154
153
  sim.dut.rst = 0;
155
154
  sim.tick();
155
+ sim.dut.rst = 1;
156
+ sim.tick();
156
157
  expect(sim.dut.count).toBe(0);
157
158
 
158
159
  // Enable counting
@@ -229,10 +230,10 @@ describe("E2E: Simulation.fromSource (time-based)", () => {
229
230
  sim.addClock("clk", { period: 10 });
230
231
  expect(sim.time()).toBe(0);
231
232
 
232
- // Reset
233
- sim.dut.rst = 1;
234
- sim.runUntil(20);
233
+ // Reset (default async_low: rst=0 is active)
235
234
  sim.dut.rst = 0;
235
+ sim.runUntil(20);
236
+ sim.dut.rst = 1;
236
237
  sim.dut.en = 1;
237
238
 
238
239
  sim.runUntil(100);
@@ -282,11 +283,11 @@ describe("E2E: Simulator.fromProject (event-based)", () => {
282
283
 
283
284
  const sim = Simulator.fromProject<CounterPorts>(COUNTER_PROJECT, "Counter");
284
285
 
285
- // Reset the counter
286
- sim.dut.rst = 1;
287
- sim.tick();
286
+ // Reset the counter (default async_low: rst=0 is active)
288
287
  sim.dut.rst = 0;
289
288
  sim.tick();
289
+ sim.dut.rst = 1;
290
+ sim.tick();
290
291
  expect(sim.dut.count).toBe(0);
291
292
 
292
293
  // Enable counting
@@ -321,10 +322,10 @@ describe("E2E: Simulation.fromProject (time-based)", () => {
321
322
  sim.addClock("clk", { period: 10 });
322
323
  expect(sim.time()).toBe(0);
323
324
 
324
- // Reset
325
- sim.dut.rst = 1;
326
- sim.runUntil(20);
325
+ // Reset (default async_low: rst=0 is active)
327
326
  sim.dut.rst = 0;
327
+ sim.runUntil(20);
328
+ sim.dut.rst = 1;
328
329
  sim.dut.en = 1;
329
330
 
330
331
  sim.runUntil(100);
@@ -646,8 +647,8 @@ module InitTest (
646
647
  const sigQ = layout.forDut.q;
647
648
  const clkEventId = events.clk;
648
649
 
649
- // 1. Reset: rst=1, d=X
650
- view.setUint8(sigRst.offset, 1);
650
+ // 1. Assert reset (default async_low: rst=0 is active), d=X
651
+ view.setUint8(sigRst.offset, 0);
651
652
  view.setUint8(sigRst.offset + sigRst.byteSize, 0); // rst is defined
652
653
 
653
654
  view.setUint8(sigD.offset, 0);
@@ -660,8 +661,8 @@ module InitTest (
660
661
  expect(vQ1).toBe(0);
661
662
  expect(mQ1).toBe(0);
662
663
 
663
- // 2. Release reset, d = partial X (value=0xA5, mask=0x0F)
664
- view.setUint8(sigRst.offset, 0);
664
+ // 2. Release reset (rst=1 is inactive), d = partial X (value=0xA5, mask=0x0F)
665
+ view.setUint8(sigRst.offset, 1);
665
666
  view.setUint8(sigRst.offset + sigRst.byteSize, 0);
666
667
 
667
668
  view.setUint8(sigD.offset, 0xA5);
@@ -673,8 +674,8 @@ module InitTest (
673
674
  const [, mQ2] = readFourState(buf, sigQ);
674
675
  expect(mQ2).toBe(0x0F);
675
676
 
676
- // 3. Reset again: should clear X
677
- view.setUint8(sigRst.offset, 1);
677
+ // 3. Assert reset again (rst=0): should clear X
678
+ view.setUint8(sigRst.offset, 0);
678
679
  view.setUint8(sigRst.offset + sigRst.byteSize, 0);
679
680
 
680
681
  raw.tick(clkEventId);
@@ -773,10 +774,11 @@ describe("E2E: 4-state high-level DUT API", () => {
773
774
  );
774
775
 
775
776
  // In 4-state mode, count starts as X. Reset should clear it.
776
- sim.dut.rst = 1;
777
- sim.tick();
777
+ // (default async_low: rst=0 is active)
778
778
  sim.dut.rst = 0;
779
779
  sim.tick();
780
+ sim.dut.rst = 1;
781
+ sim.tick();
780
782
  expect(sim.dut.count).toBe(0);
781
783
 
782
784
  // Enable counting — should work exactly like 2-state
@@ -830,10 +832,10 @@ describe("E2E: 4-state high-level DUT API", () => {
830
832
 
831
833
  const sim = Simulator.fromSource<FFPorts>(FF_SOURCE, "FF", { fourState: true });
832
834
 
833
- // Reset to clear initial X
834
- sim.dut.rst = 1;
835
- sim.tick();
835
+ // Reset to clear initial X (default async_low: rst=0 is active)
836
836
  sim.dut.rst = 0;
837
+ sim.tick();
838
+ sim.dut.rst = 1;
837
839
  expect(sim.dut.q).toBe(0);
838
840
 
839
841
  // Write a defined value
@@ -895,10 +897,10 @@ describe("E2E: 4-state Simulation (time-based)", () => {
895
897
  sim.addClock("clk", { period: 10 });
896
898
  expect(sim.time()).toBe(0);
897
899
 
898
- // Reset to clear initial X on q
899
- sim.dut.rst = 1;
900
- sim.runUntil(20);
900
+ // Reset to clear initial X on q (default async_low: rst=0 is active)
901
901
  sim.dut.rst = 0;
902
+ sim.runUntil(20);
903
+ sim.dut.rst = 1;
902
904
  expect(sim.dut.q).toBe(0);
903
905
 
904
906
  // Drive d with defined value
@@ -927,10 +929,10 @@ describe("E2E: 4-state Simulation (time-based)", () => {
927
929
 
928
930
  sim.addClock("clk", { period: 10 });
929
931
 
930
- // Reset
931
- sim.dut.rst = 1;
932
- sim.runUntil(20);
932
+ // Reset (default async_low: rst=0 is active)
933
933
  sim.dut.rst = 0;
934
+ sim.runUntil(20);
935
+ sim.dut.rst = 1;
934
936
  sim.dut.en = 1;
935
937
 
936
938
  sim.runUntil(100);
@@ -963,3 +965,403 @@ describe("E2E: 4-state Simulation (time-based)", () => {
963
965
  sim.dispose();
964
966
  });
965
967
  });
968
+
969
+ // ---------------------------------------------------------------------------
970
+ // Phase 3b: testbench helpers — Simulation API
971
+ // ---------------------------------------------------------------------------
972
+
973
+ describe("E2E: Simulation testbench helpers", () => {
974
+ test("waitForCycles: advances correct number of clock cycles", () => {
975
+ interface CounterPorts {
976
+ rst: number;
977
+ en: number;
978
+ readonly count: number;
979
+ }
980
+
981
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
982
+ sim.addClock("clk", { period: 10 });
983
+
984
+ // Reset (default async_low: rst=0 is active)
985
+ sim.dut.rst = 0;
986
+ sim.runUntil(20);
987
+ sim.dut.rst = 1;
988
+ sim.dut.en = 1;
989
+
990
+ const beforeTime = sim.time();
991
+ const afterTime = sim.waitForCycles("clk", 5);
992
+
993
+ expect(afterTime).toBeGreaterThan(beforeTime);
994
+ // Each cycle = 2 steps with period 10, so 5 cycles ≈ 50 time units
995
+ expect(afterTime - beforeTime).toBe(50);
996
+
997
+ sim.dispose();
998
+ });
999
+
1000
+ test("waitUntil: waits for condition to be met", () => {
1001
+ interface CounterPorts {
1002
+ rst: number;
1003
+ en: number;
1004
+ readonly count: number;
1005
+ }
1006
+
1007
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1008
+ sim.addClock("clk", { period: 10 });
1009
+
1010
+ // Reset (default async_low: rst=0 is active)
1011
+ sim.dut.rst = 0;
1012
+ sim.runUntil(20);
1013
+ sim.dut.rst = 1;
1014
+ sim.dut.en = 1;
1015
+
1016
+ const t = sim.waitUntil(() => sim.dut.count >= 3);
1017
+ expect(sim.dut.count).toBeGreaterThanOrEqual(3);
1018
+ expect(t).toBeGreaterThan(20);
1019
+
1020
+ sim.dispose();
1021
+ });
1022
+
1023
+ test("waitUntil: throws SimulationTimeoutError on timeout", () => {
1024
+ interface CounterPorts {
1025
+ rst: number;
1026
+ en: number;
1027
+ readonly count: number;
1028
+ }
1029
+
1030
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1031
+ sim.addClock("clk", { period: 10 });
1032
+
1033
+ // Reset (default async_low: rst=0 is active)
1034
+ sim.dut.rst = 0;
1035
+ sim.runUntil(20);
1036
+ sim.dut.rst = 1;
1037
+ sim.dut.en = 0; // disabled — count won't increase
1038
+
1039
+ expect(() =>
1040
+ sim.waitUntil(() => sim.dut.count >= 100, { maxSteps: 20 }),
1041
+ ).toThrow(SimulationTimeoutError);
1042
+
1043
+ sim.dispose();
1044
+ });
1045
+
1046
+ test("reset: asserts and releases reset on counter", () => {
1047
+ interface CounterPorts {
1048
+ rst: number;
1049
+ en: number;
1050
+ readonly count: number;
1051
+ }
1052
+
1053
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1054
+ sim.addClock("clk", { period: 10 });
1055
+
1056
+ // Count up a bit (default async_low: rst=0 is active)
1057
+ sim.dut.rst = 0;
1058
+ sim.runUntil(20);
1059
+ sim.dut.rst = 1;
1060
+ sim.dut.en = 1;
1061
+ sim.runUntil(100);
1062
+ expect(sim.dut.count).toBeGreaterThan(0);
1063
+
1064
+ // Reset using the helper
1065
+ sim.reset("rst");
1066
+ expect(sim.dut.count).toBe(0);
1067
+
1068
+ sim.dispose();
1069
+ });
1070
+
1071
+ test("reset: explicit async_low resetType activates with 0", () => {
1072
+ interface CounterPorts {
1073
+ rst: number;
1074
+ en: number;
1075
+ readonly count: number;
1076
+ }
1077
+
1078
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1079
+ resetType: "async_low",
1080
+ });
1081
+ sim.addClock("clk", { period: 10 });
1082
+
1083
+ // With async_low, rst=0 is active, rst=1 is inactive
1084
+ sim.reset("rst");
1085
+
1086
+ sim.dut.en = 1;
1087
+ sim.runUntil(100);
1088
+ expect(sim.dut.count).toBeGreaterThan(0);
1089
+
1090
+ // Reset again using helper, verify it resets the counter
1091
+ sim.reset("rst");
1092
+ expect(sim.dut.count).toBe(0);
1093
+
1094
+ sim.dispose();
1095
+ });
1096
+
1097
+ test("reset: explicit async_high resetType activates with 1", () => {
1098
+ interface CounterPorts {
1099
+ rst: number;
1100
+ en: number;
1101
+ readonly count: number;
1102
+ }
1103
+
1104
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1105
+ resetType: "async_high",
1106
+ });
1107
+ sim.addClock("clk", { period: 10 });
1108
+
1109
+ // With async_high, rst=1 is active, rst=0 is inactive
1110
+ sim.reset("rst");
1111
+
1112
+ sim.dut.en = 1;
1113
+ sim.runUntil(100);
1114
+ expect(sim.dut.count).toBeGreaterThan(0);
1115
+
1116
+ // Reset again
1117
+ sim.reset("rst");
1118
+ expect(sim.dut.count).toBe(0);
1119
+
1120
+ sim.dispose();
1121
+ });
1122
+
1123
+ test("Simulator.fromSource: resetType option works", () => {
1124
+ interface CounterPorts {
1125
+ rst: number;
1126
+ en: number;
1127
+ readonly count: number;
1128
+ }
1129
+
1130
+ const sim = Simulator.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1131
+ resetType: "async_high",
1132
+ });
1133
+
1134
+ // With async_high, assert reset with 1
1135
+ sim.dut.rst = 1;
1136
+ sim.tick();
1137
+ sim.dut.rst = 0;
1138
+ sim.tick();
1139
+ expect(sim.dut.count).toBe(0);
1140
+
1141
+ // Count up
1142
+ sim.dut.en = 1;
1143
+ sim.tick();
1144
+ expect(sim.dut.count).toBe(1);
1145
+
1146
+ sim.dispose();
1147
+ });
1148
+
1149
+ test("runUntil with maxSteps: succeeds within budget", () => {
1150
+ interface CounterPorts {
1151
+ rst: number;
1152
+ en: number;
1153
+ readonly count: number;
1154
+ }
1155
+
1156
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1157
+ sim.addClock("clk", { period: 10 });
1158
+
1159
+ // Reset (default async_low: rst=0 is active)
1160
+ sim.dut.rst = 0;
1161
+ sim.runUntil(20);
1162
+ sim.dut.rst = 1;
1163
+ sim.dut.en = 1;
1164
+
1165
+ // 100 time units with period 10 = 10 events, should fit in 100 steps
1166
+ sim.runUntil(120, { maxSteps: 100 });
1167
+ expect(sim.time()).toBe(120);
1168
+
1169
+ sim.dispose();
1170
+ });
1171
+
1172
+ test("runUntil with maxSteps: throws on exceeded budget", () => {
1173
+ interface CounterPorts {
1174
+ rst: number;
1175
+ en: number;
1176
+ readonly count: number;
1177
+ }
1178
+
1179
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter");
1180
+ sim.addClock("clk", { period: 10 });
1181
+
1182
+ // Release reset so counter can count (default async_low: rst=1 is inactive)
1183
+ sim.dut.rst = 1;
1184
+ sim.dut.en = 1;
1185
+
1186
+ // Very small budget for a long run
1187
+ expect(() => sim.runUntil(100000, { maxSteps: 5 })).toThrow(
1188
+ SimulationTimeoutError,
1189
+ );
1190
+
1191
+ sim.dispose();
1192
+ });
1193
+ });
1194
+
1195
+ // ---------------------------------------------------------------------------
1196
+ // Phase 3b: fourState() method
1197
+ // ---------------------------------------------------------------------------
1198
+
1199
+ describe("E2E: fourState() method", () => {
1200
+ test("Simulator.fourState: reads 4-state value and mask", () => {
1201
+ interface Ports {
1202
+ a: number;
1203
+ b: number;
1204
+ readonly y: number;
1205
+ }
1206
+
1207
+ const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1208
+ fourState: true,
1209
+ });
1210
+
1211
+ sim.dut.a = 100;
1212
+ sim.dut.b = 55;
1213
+ // Trigger evalComb via output read (Adder4S is purely combinational)
1214
+ expect(sim.dut.y).toBe(155);
1215
+
1216
+ const fs = sim.fourState("y");
1217
+ expect(fs.__fourState).toBe(true);
1218
+ expect(fs.value).toBe(155);
1219
+ expect(fs.mask).toBe(0);
1220
+
1221
+ sim.dispose();
1222
+ });
1223
+
1224
+ test("Simulator.fourState: reads X mask when input is X", () => {
1225
+ interface Ports {
1226
+ a: number;
1227
+ b: number;
1228
+ readonly y: number;
1229
+ }
1230
+
1231
+ const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1232
+ fourState: true,
1233
+ });
1234
+
1235
+ (sim.dut as any).a = X;
1236
+ sim.dut.b = 10;
1237
+ // Trigger evalComb via output read
1238
+ sim.dut.y;
1239
+
1240
+ const fs = sim.fourState("y");
1241
+ expect(fs.mask).toBe(0xFF); // all X from arithmetic propagation
1242
+
1243
+ sim.dispose();
1244
+ });
1245
+
1246
+ test("Simulation.fourState: reads 4-state value", () => {
1247
+ interface Ports {
1248
+ a: number;
1249
+ b: number;
1250
+ readonly y: number;
1251
+ }
1252
+
1253
+ const sim = Simulation.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1254
+ fourState: true,
1255
+ });
1256
+
1257
+ sim.dut.a = 50;
1258
+ sim.dut.b = 25;
1259
+ sim.runUntil(0);
1260
+
1261
+ const fs = sim.fourState("y");
1262
+ expect(fs.value).toBe(75);
1263
+ expect(fs.mask).toBe(0);
1264
+
1265
+ sim.dispose();
1266
+ });
1267
+
1268
+ test("fourState: throws for unknown port", () => {
1269
+ interface Ports {
1270
+ a: number;
1271
+ b: number;
1272
+ readonly y: number;
1273
+ }
1274
+
1275
+ const sim = Simulator.fromSource<Ports>(ADDER_4STATE_SOURCE, "Adder4S", {
1276
+ fourState: true,
1277
+ });
1278
+
1279
+ expect(() => sim.fourState("nonexistent")).toThrow("Unknown port");
1280
+
1281
+ sim.dispose();
1282
+ });
1283
+ });
1284
+
1285
+ // ---------------------------------------------------------------------------
1286
+ // Phase 3b: optimize flag
1287
+ // ---------------------------------------------------------------------------
1288
+
1289
+ describe("E2E: optimize flag", () => {
1290
+ test("Simulator.fromSource with optimize: true", () => {
1291
+ interface AdderPorts {
1292
+ rst: number;
1293
+ a: number;
1294
+ b: number;
1295
+ readonly sum: number;
1296
+ }
1297
+
1298
+ const sim = Simulator.fromSource<AdderPorts>(ADDER_SOURCE, "Adder", {
1299
+ optimize: true,
1300
+ });
1301
+
1302
+ sim.dut.a = 100;
1303
+ sim.dut.b = 200;
1304
+ sim.tick();
1305
+ expect(sim.dut.sum).toBe(300);
1306
+
1307
+ sim.dispose();
1308
+ });
1309
+
1310
+ test("Simulation.fromSource with optimize: true", () => {
1311
+ interface CounterPorts {
1312
+ rst: number;
1313
+ en: number;
1314
+ readonly count: number;
1315
+ }
1316
+
1317
+ const sim = Simulation.fromSource<CounterPorts>(COUNTER_SOURCE, "Counter", {
1318
+ optimize: true,
1319
+ });
1320
+
1321
+ sim.addClock("clk", { period: 10 });
1322
+
1323
+ // Reset (default async_low: rst=0 is active)
1324
+ sim.dut.rst = 0;
1325
+ sim.runUntil(20);
1326
+ sim.dut.rst = 1;
1327
+ sim.dut.en = 1;
1328
+ sim.runUntil(100);
1329
+
1330
+ expect(sim.dut.count).toBeGreaterThan(0);
1331
+
1332
+ sim.dispose();
1333
+ });
1334
+ });
1335
+
1336
+ // ---------------------------------------------------------------------------
1337
+ // parseSignalPath unit tests
1338
+ // ---------------------------------------------------------------------------
1339
+
1340
+ describe("parseSignalPath", () => {
1341
+ test("simple variable path", () => {
1342
+ const result = parseSignalPath("v");
1343
+ expect(result.instancePath).toEqual([]);
1344
+ expect(result.varPath).toEqual(["v"]);
1345
+ });
1346
+
1347
+ test("instance:variable split", () => {
1348
+ const result = parseSignalPath("p2:i");
1349
+ expect(result.instancePath).toEqual([{ name: "p2", index: 0 }]);
1350
+ expect(result.varPath).toEqual(["i"]);
1351
+ });
1352
+
1353
+ test("nested instance with array index", () => {
1354
+ const result = parseSignalPath("a.b[3]:x.y");
1355
+ expect(result.instancePath).toEqual([
1356
+ { name: "a", index: 0 },
1357
+ { name: "b", index: 3 },
1358
+ ]);
1359
+ expect(result.varPath).toEqual(["x", "y"]);
1360
+ });
1361
+
1362
+ test("dotted variable path without instance", () => {
1363
+ const result = parseSignalPath("foo.bar.baz");
1364
+ expect(result.instancePath).toEqual([]);
1365
+ expect(result.varPath).toEqual(["foo", "bar", "baz"]);
1366
+ });
1367
+ });
package/src/index.ts CHANGED
@@ -15,6 +15,8 @@ export type {
15
15
  SimulatorOptions,
16
16
  EventHandle,
17
17
  FourStateValue,
18
+ LoopBreak,
19
+ TrueLoopSpec,
18
20
  } from "./types.js";
19
21
 
20
22
  /** @internal */
@@ -29,6 +31,9 @@ export type {
29
31
  // 4-state helpers
30
32
  export { X, FourState, isFourStateValue } from "./types.js";
31
33
 
34
+ // Error types
35
+ export { SimulationTimeoutError } from "./types.js";
36
+
32
37
  // Simulator (event-based)
33
38
  export { Simulator } from "./simulator.js";
34
39
 
@@ -36,7 +41,7 @@ export { Simulator } from "./simulator.js";
36
41
  export { Simulation } from "./simulation.js";
37
42
 
38
43
  /** @internal */
39
- export { createDut, readFourState } from "./dut.js";
44
+ export { createDut, createChildDut, readFourState } from "./dut.js";
40
45
  /** @internal */
41
46
  export type { DirtyState } from "./dut.js";
42
47
 
@@ -44,14 +49,17 @@ export type { DirtyState } from "./dut.js";
44
49
  export {
45
50
  loadNativeAddon,
46
51
  parseNapiLayout,
52
+ parseHierarchyLayout,
47
53
  buildPortsFromLayout,
48
54
  wrapDirectSimulatorHandle,
49
55
  wrapDirectSimulationHandle,
50
56
  createSimulatorBridge,
51
57
  createSimulationBridge,
58
+ parseSignalPath,
59
+ buildNapiOpts,
52
60
  } from "./napi-helpers.js";
53
61
  /** @internal */
54
- export type { RawNapiAddon, RawNapiSimulatorHandle, RawNapiSimulationHandle } from "./napi-helpers.js";
62
+ export type { HierarchyNode, RawNapiAddon, RawNapiSimulatorHandle, RawNapiSimulationHandle } from "./napi-helpers.js";
55
63
 
56
64
  // NAPI bridge (backward compat — re-exports from napi-helpers)
57
65
  // Consumers that import from "./napi-bridge.js" still work.