@celox-sim/celox 0.1.12 → 0.1.14

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.
@@ -1,9 +1,9 @@
1
- import { describe, test, expect, vi } from "vitest";
2
- import { Simulation, type NativeCreateSimulationFn } from "./simulation.js";
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import { type NativeCreateSimulationFn, Simulation } from "./simulation.js";
3
3
  import type {
4
- CreateResult,
5
- ModuleDefinition,
6
- NativeSimulationHandle,
4
+ CreateResult,
5
+ ModuleDefinition,
6
+ NativeSimulationHandle,
7
7
  } from "./types.js";
8
8
  import { SimulationTimeoutError } from "./types.js";
9
9
 
@@ -12,78 +12,107 @@ import { SimulationTimeoutError } from "./types.js";
12
12
  // ---------------------------------------------------------------------------
13
13
 
14
14
  interface TopPorts {
15
- rst: bigint;
16
- d: bigint;
17
- readonly q: bigint;
15
+ rst: bigint;
16
+ d: bigint;
17
+ readonly q: bigint;
18
18
  }
19
19
 
20
20
  const TopModule: ModuleDefinition<TopPorts> = {
21
- __celox_module: true,
22
- name: "Top",
23
- source: "module Top ...",
24
- ports: {
25
- clk: { direction: "input", type: "clock", width: 1 },
26
- rst: { direction: "input", type: "reset", width: 1 },
27
- d: { direction: "input", type: "logic", width: 8 },
28
- q: { direction: "output", type: "logic", width: 8 },
29
- },
30
- events: ["clk"],
21
+ __celox_module: true,
22
+ name: "Top",
23
+ source: "module Top ...",
24
+ ports: {
25
+ clk: { direction: "input", type: "clock", width: 1 },
26
+ rst: { direction: "input", type: "reset", width: 1 },
27
+ d: { direction: "input", type: "logic", width: 8 },
28
+ q: { direction: "output", type: "logic", width: 8 },
29
+ },
30
+ events: ["clk"],
31
31
  };
32
32
 
33
33
  function createMockNative(opts?: {
34
- resetTypeKind?: string;
35
- associatedClock?: string;
34
+ resetTypeKind?: string;
35
+ associatedClock?: string;
36
36
  }): {
37
- create: NativeCreateSimulationFn;
38
- handle: NativeSimulationHandle;
39
- buffer: SharedArrayBuffer;
37
+ create: NativeCreateSimulationFn;
38
+ handle: NativeSimulationHandle;
39
+ buffer: SharedArrayBuffer;
40
40
  } {
41
- const buffer = new SharedArrayBuffer(64);
42
- let currentTime = 0;
43
-
44
- const handle: NativeSimulationHandle = {
45
- addClock: vi.fn(),
46
- schedule: vi.fn(),
47
- runUntil: vi.fn().mockImplementation((endTime: number) => {
48
- // Simulate: q = d after running
49
- const view = new DataView(buffer);
50
- view.setUint8(4, view.getUint8(2));
51
- currentTime = endTime;
52
- }),
53
- step: vi.fn().mockImplementation(() => {
54
- currentTime += 5;
55
- const view = new DataView(buffer);
56
- view.setUint8(4, view.getUint8(2));
57
- // Toggle clock (offset 6) on each step to simulate half-period=5
58
- view.setUint8(6, view.getUint8(6) === 0 ? 1 : 0);
59
- return currentTime;
60
- }),
61
- time: vi.fn().mockImplementation(() => currentTime),
62
- nextEventTime: vi.fn().mockReturnValue(null),
63
- evalComb: vi.fn().mockImplementation(() => {
64
- const view = new DataView(buffer);
65
- view.setUint8(4, view.getUint8(2));
66
- }),
67
- dump: vi.fn(),
68
- dispose: vi.fn(),
69
- };
70
-
71
- const resetTypeKind = opts?.resetTypeKind ?? "reset_async_high";
72
- const associatedClock = opts?.associatedClock;
73
-
74
- const create: NativeCreateSimulationFn = vi.fn().mockReturnValue({
75
- buffer,
76
- layout: {
77
- clk: { offset: 6, width: 1, byteSize: 1, is4state: false, direction: "input", typeKind: "clock" },
78
- rst: { offset: 0, width: 1, byteSize: 1, is4state: false, direction: "input", typeKind: resetTypeKind, ...(associatedClock ? { associatedClock } : {}) },
79
- d: { offset: 2, width: 8, byteSize: 1, is4state: false, direction: "input", typeKind: "logic" },
80
- q: { offset: 4, width: 8, byteSize: 1, is4state: false, direction: "output", typeKind: "logic" },
81
- },
82
- events: { clk: 0 },
83
- handle,
84
- } satisfies CreateResult<NativeSimulationHandle>);
85
-
86
- return { create, handle, buffer };
41
+ const buffer = new SharedArrayBuffer(64);
42
+ let currentTime = 0;
43
+
44
+ const handle: NativeSimulationHandle = {
45
+ addClock: vi.fn(),
46
+ schedule: vi.fn(),
47
+ runUntil: vi.fn().mockImplementation((endTime: number) => {
48
+ // Simulate: q = d after running
49
+ const view = new DataView(buffer);
50
+ view.setUint8(4, view.getUint8(2));
51
+ currentTime = endTime;
52
+ }),
53
+ step: vi.fn().mockImplementation(() => {
54
+ currentTime += 5;
55
+ const view = new DataView(buffer);
56
+ view.setUint8(4, view.getUint8(2));
57
+ // Toggle clock (offset 6) on each step to simulate half-period=5
58
+ view.setUint8(6, view.getUint8(6) === 0 ? 1 : 0);
59
+ return currentTime;
60
+ }),
61
+ time: vi.fn().mockImplementation(() => currentTime),
62
+ nextEventTime: vi.fn().mockReturnValue(null),
63
+ evalComb: vi.fn().mockImplementation(() => {
64
+ const view = new DataView(buffer);
65
+ view.setUint8(4, view.getUint8(2));
66
+ }),
67
+ dump: vi.fn(),
68
+ dispose: vi.fn(),
69
+ };
70
+
71
+ const resetTypeKind = opts?.resetTypeKind ?? "reset_async_high";
72
+ const associatedClock = opts?.associatedClock;
73
+
74
+ const create: NativeCreateSimulationFn = vi.fn().mockReturnValue({
75
+ buffer,
76
+ layout: {
77
+ clk: {
78
+ offset: 6,
79
+ width: 1,
80
+ byteSize: 1,
81
+ is4state: false,
82
+ direction: "input",
83
+ typeKind: "clock",
84
+ },
85
+ rst: {
86
+ offset: 0,
87
+ width: 1,
88
+ byteSize: 1,
89
+ is4state: false,
90
+ direction: "input",
91
+ typeKind: resetTypeKind,
92
+ ...(associatedClock ? { associatedClock } : {}),
93
+ },
94
+ d: {
95
+ offset: 2,
96
+ width: 8,
97
+ byteSize: 1,
98
+ is4state: false,
99
+ direction: "input",
100
+ typeKind: "logic",
101
+ },
102
+ q: {
103
+ offset: 4,
104
+ width: 8,
105
+ byteSize: 1,
106
+ is4state: false,
107
+ direction: "output",
108
+ typeKind: "logic",
109
+ },
110
+ },
111
+ events: { clk: 0 },
112
+ handle,
113
+ } satisfies CreateResult<NativeSimulationHandle>);
114
+
115
+ return { create, handle, buffer };
87
116
  }
88
117
 
89
118
  // ---------------------------------------------------------------------------
@@ -91,303 +120,301 @@ function createMockNative(opts?: {
91
120
  // ---------------------------------------------------------------------------
92
121
 
93
122
  describe("Simulation", () => {
94
- test("create and addClock", () => {
95
- const mock = createMockNative();
96
- const sim = Simulation.create(TopModule, {
97
- __nativeCreate: mock.create,
98
- });
99
-
100
- sim.addClock("clk", { period: 10 });
101
- expect(mock.handle.addClock).toHaveBeenCalledWith(0, 10, 0);
102
- });
103
-
104
- test("addClock with initialDelay", () => {
105
- const mock = createMockNative();
106
- const sim = Simulation.create(TopModule, {
107
- __nativeCreate: mock.create,
108
- });
109
-
110
- sim.addClock("clk", { period: 10, initialDelay: 5 });
111
- expect(mock.handle.addClock).toHaveBeenCalledWith(0, 10, 5);
112
- });
113
-
114
- test("schedule", () => {
115
- const mock = createMockNative();
116
- const sim = Simulation.create(TopModule, {
117
- __nativeCreate: mock.create,
118
- });
119
-
120
- sim.schedule("clk", { time: 50, value: 1 });
121
- expect(mock.handle.schedule).toHaveBeenCalledWith(0, 50, 1);
122
- });
123
-
124
- test("runUntil", () => {
125
- const mock = createMockNative();
126
- const sim = Simulation.create(TopModule, {
127
- __nativeCreate: mock.create,
128
- });
129
-
130
- sim.dut.d = 42n;
131
- sim.runUntil(100);
132
-
133
- expect(mock.handle.runUntil).toHaveBeenCalledWith(100);
134
- expect(sim.dut.q).toBe(42n);
135
- });
136
-
137
- test("step", () => {
138
- const mock = createMockNative();
139
- const sim = Simulation.create(TopModule, {
140
- __nativeCreate: mock.create,
141
- });
142
-
143
- sim.dut.d = 0xABn;
144
- const t = sim.step();
145
-
146
- expect(t).toBe(5);
147
- expect(sim.dut.q).toBe(0xABn);
148
- });
149
-
150
- test("time", () => {
151
- const mock = createMockNative();
152
- const sim = Simulation.create(TopModule, {
153
- __nativeCreate: mock.create,
154
- });
155
-
156
- expect(sim.time()).toBe(0);
157
- sim.runUntil(100);
158
- expect(sim.time()).toBe(100);
159
- });
160
-
161
- test("unknown event throws", () => {
162
- const mock = createMockNative();
163
- const sim = Simulation.create(TopModule, {
164
- __nativeCreate: mock.create,
165
- });
166
-
167
- expect(() => sim.addClock("bad", { period: 10 })).toThrow("Unknown event");
168
- });
169
-
170
- test("dispose prevents further operations", () => {
171
- const mock = createMockNative();
172
- const sim = Simulation.create(TopModule, {
173
- __nativeCreate: mock.create,
174
- });
175
-
176
- sim.dispose();
177
- expect(() => sim.runUntil(100)).toThrow("disposed");
178
- });
179
-
180
- test("dump delegates to handle", () => {
181
- const mock = createMockNative();
182
- const sim = Simulation.create(TopModule, {
183
- __nativeCreate: mock.create,
184
- });
185
-
186
- sim.dump(99);
187
- expect(mock.handle.dump).toHaveBeenCalledWith(99);
188
- });
189
-
190
- test("create throws without native binding", () => {
191
- expect(() => {
192
- Simulation.create(TopModule);
193
- }).toThrow("Native simulator binding not loaded");
194
- });
195
-
196
- test("runUntil with maxSteps: fast path when omitted", () => {
197
- const mock = createMockNative();
198
- const sim = Simulation.create(TopModule, {
199
- __nativeCreate: mock.create,
200
- });
201
-
202
- sim.runUntil(100);
203
- // Without maxSteps, the Rust fast-path should be used
204
- expect(mock.handle.runUntil).toHaveBeenCalledWith(100);
205
- });
206
-
207
- test("runUntil with maxSteps: throws SimulationTimeoutError", () => {
208
- const mock = createMockNative();
209
- const sim = Simulation.create(TopModule, {
210
- __nativeCreate: mock.create,
211
- });
212
-
213
- // step mock increments time by 5 each call, so reaching 10000 requires 2000 steps
214
- // With maxSteps=10 we'll exhaust before getting there
215
- expect(() => sim.runUntil(10000, { maxSteps: 10 })).toThrow(
216
- SimulationTimeoutError,
217
- );
218
- });
219
-
220
- test("runUntil with maxSteps: timeout error has correct properties", () => {
221
- const mock = createMockNative();
222
- const sim = Simulation.create(TopModule, {
223
- __nativeCreate: mock.create,
224
- });
225
-
226
- try {
227
- sim.runUntil(10000, { maxSteps: 5 });
228
- expect.unreachable();
229
- } catch (e) {
230
- expect(e).toBeInstanceOf(SimulationTimeoutError);
231
- const err = e as SimulationTimeoutError;
232
- expect(err.steps).toBe(5);
233
- expect(err.time).toBeGreaterThan(0);
234
- }
235
- });
236
-
237
- test("waitUntil: returns time when condition is met", () => {
238
- const mock = createMockNative();
239
- const sim = Simulation.create(TopModule, {
240
- __nativeCreate: mock.create,
241
- });
242
-
243
- sim.dut.d = 42n;
244
- let callCount = 0;
245
- const t = sim.waitUntil(() => {
246
- callCount++;
247
- return callCount >= 3;
248
- });
249
-
250
- expect(t).toBeGreaterThan(0);
251
- expect(callCount).toBe(3);
252
- });
253
-
254
- test("waitUntil: throws on timeout", () => {
255
- const mock = createMockNative();
256
- const sim = Simulation.create(TopModule, {
257
- __nativeCreate: mock.create,
258
- });
259
-
260
- expect(() =>
261
- sim.waitUntil(() => false, { maxSteps: 5 }),
262
- ).toThrow(SimulationTimeoutError);
263
- });
264
-
265
- test("waitForCycles: counts rising edges", () => {
266
- const mock = createMockNative();
267
- const sim = Simulation.create(TopModule, {
268
- __nativeCreate: mock.create,
269
- });
270
-
271
- sim.addClock("clk", { period: 10 });
272
- const t = sim.waitForCycles("clk", 3);
273
- // clk toggles each step: 0→1→0→1→0→1 (5 steps for 3 rising edges)
274
- expect(mock.handle.step).toHaveBeenCalledTimes(5);
275
- expect(t).toBe(25);
276
- });
277
-
278
- test("waitForCycles: throws without addClock", () => {
279
- const mock = createMockNative();
280
- const sim = Simulation.create(TopModule, {
281
- __nativeCreate: mock.create,
282
- });
283
-
284
- expect(() => sim.waitForCycles("clk", 3)).toThrow("No clock registered");
285
- });
286
-
287
- test("reset: active-high with associatedClock steps until target time", () => {
288
- const mock = createMockNative({ associatedClock: "clk" });
289
- const sim = Simulation.create(TopModule, {
290
- __nativeCreate: mock.create,
291
- });
292
-
293
- sim.addClock("clk", { period: 10 });
294
- sim.reset("rst");
295
- // Default activeCycles=2: 3 steps for 2 rising edges (0→1→0→1)
296
- expect(mock.handle.step).toHaveBeenCalledTimes(3);
297
- // Released to inactive value (0 for active-high)
298
- const view = new DataView(mock.buffer);
299
- expect(view.getUint8(0)).toBe(0);
300
- });
301
-
302
- test("reset: custom activeCycles with associatedClock", () => {
303
- const mock = createMockNative({ associatedClock: "clk" });
304
- const sim = Simulation.create(TopModule, {
305
- __nativeCreate: mock.create,
306
- });
307
-
308
- sim.addClock("clk", { period: 10 });
309
- sim.reset("rst", { activeCycles: 3 });
310
- // 3 cycles: 5 steps for 3 rising edges (0→1→0→1→0→1)
311
- expect(mock.handle.step).toHaveBeenCalledTimes(5);
312
- });
313
-
314
- test("reset: explicit duration overrides cycle calculation", () => {
315
- const mock = createMockNative({ associatedClock: "clk" });
316
- const sim = Simulation.create(TopModule, {
317
- __nativeCreate: mock.create,
318
- });
319
-
320
- sim.addClock("clk", { period: 10 });
321
- sim.reset("rst", { duration: 50 });
322
- // Explicit duration → runUntil(0 + 50 = 50)
323
- expect(mock.handle.runUntil).toHaveBeenCalledWith(50);
324
- });
325
-
326
- test("reset: active-low with associatedClock asserts 0 then releases to 1", () => {
327
- const mock = createMockNative({
328
- resetTypeKind: "reset_async_low",
329
- associatedClock: "clk",
330
- });
331
- const sim = Simulation.create(TopModule, {
332
- __nativeCreate: mock.create,
333
- });
334
-
335
- sim.addClock("clk", { period: 10 });
336
- sim.reset("rst");
337
- // active-low: releases to 1
338
- const view = new DataView(mock.buffer);
339
- expect(view.getUint8(0)).toBe(1);
340
- // activeCycles=2: 3 steps for 2 rising edges
341
- expect(mock.handle.step).toHaveBeenCalledTimes(3);
342
- });
343
-
344
- test("reset: throws when no associatedClock and no duration", () => {
345
- // No associatedClock in layout
346
- const mock = createMockNative();
347
- const sim = Simulation.create(TopModule, {
348
- __nativeCreate: mock.create,
349
- });
350
-
351
- expect(() => sim.reset("rst")).toThrow("has no associated clock");
352
- });
353
-
354
- test("reset: no associatedClock but duration specified works", () => {
355
- // No associatedClock in layout, but duration is given
356
- const mock = createMockNative();
357
- const sim = Simulation.create(TopModule, {
358
- __nativeCreate: mock.create,
359
- });
360
-
361
- sim.reset("rst", { duration: 100 });
362
- expect(mock.handle.runUntil).toHaveBeenCalledWith(100);
363
- });
364
-
365
- test("reset: throws when associatedClock not registered via addClock", () => {
366
- const mock = createMockNative({ associatedClock: "clk" });
367
- const sim = Simulation.create(TopModule, {
368
- __nativeCreate: mock.create,
369
- });
370
- // addClock not called
371
- expect(() => sim.reset("rst")).toThrow(
372
- "No clock registered for 'clk'",
373
- );
374
- });
375
-
376
- test("reset: throws on non-reset port", () => {
377
- const mock = createMockNative();
378
- const sim = Simulation.create(TopModule, {
379
- __nativeCreate: mock.create,
380
- });
381
-
382
- expect(() => sim.reset("d")).toThrow("not a reset signal");
383
- });
384
-
385
- test("reset: throws on unknown port", () => {
386
- const mock = createMockNative();
387
- const sim = Simulation.create(TopModule, {
388
- __nativeCreate: mock.create,
389
- });
390
-
391
- expect(() => sim.reset("nonexistent")).toThrow("Unknown port");
392
- });
123
+ test("create and addClock", () => {
124
+ const mock = createMockNative();
125
+ const sim = Simulation.create(TopModule, {
126
+ __nativeCreate: mock.create,
127
+ });
128
+
129
+ sim.addClock("clk", { period: 10 });
130
+ expect(mock.handle.addClock).toHaveBeenCalledWith(0, 10, 0);
131
+ });
132
+
133
+ test("addClock with initialDelay", () => {
134
+ const mock = createMockNative();
135
+ const sim = Simulation.create(TopModule, {
136
+ __nativeCreate: mock.create,
137
+ });
138
+
139
+ sim.addClock("clk", { period: 10, initialDelay: 5 });
140
+ expect(mock.handle.addClock).toHaveBeenCalledWith(0, 10, 5);
141
+ });
142
+
143
+ test("schedule", () => {
144
+ const mock = createMockNative();
145
+ const sim = Simulation.create(TopModule, {
146
+ __nativeCreate: mock.create,
147
+ });
148
+
149
+ sim.schedule("clk", { time: 50, value: 1 });
150
+ expect(mock.handle.schedule).toHaveBeenCalledWith(0, 50, 1);
151
+ });
152
+
153
+ test("runUntil", () => {
154
+ const mock = createMockNative();
155
+ const sim = Simulation.create(TopModule, {
156
+ __nativeCreate: mock.create,
157
+ });
158
+
159
+ sim.dut.d = 42n;
160
+ sim.runUntil(100);
161
+
162
+ expect(mock.handle.runUntil).toHaveBeenCalledWith(100);
163
+ expect(sim.dut.q).toBe(42n);
164
+ });
165
+
166
+ test("step", () => {
167
+ const mock = createMockNative();
168
+ const sim = Simulation.create(TopModule, {
169
+ __nativeCreate: mock.create,
170
+ });
171
+
172
+ sim.dut.d = 0xabn;
173
+ const t = sim.step();
174
+
175
+ expect(t).toBe(5);
176
+ expect(sim.dut.q).toBe(0xabn);
177
+ });
178
+
179
+ test("time", () => {
180
+ const mock = createMockNative();
181
+ const sim = Simulation.create(TopModule, {
182
+ __nativeCreate: mock.create,
183
+ });
184
+
185
+ expect(sim.time()).toBe(0);
186
+ sim.runUntil(100);
187
+ expect(sim.time()).toBe(100);
188
+ });
189
+
190
+ test("unknown event throws", () => {
191
+ const mock = createMockNative();
192
+ const sim = Simulation.create(TopModule, {
193
+ __nativeCreate: mock.create,
194
+ });
195
+
196
+ expect(() => sim.addClock("bad", { period: 10 })).toThrow("Unknown event");
197
+ });
198
+
199
+ test("dispose prevents further operations", () => {
200
+ const mock = createMockNative();
201
+ const sim = Simulation.create(TopModule, {
202
+ __nativeCreate: mock.create,
203
+ });
204
+
205
+ sim.dispose();
206
+ expect(() => sim.runUntil(100)).toThrow("disposed");
207
+ });
208
+
209
+ test("dump delegates to handle", () => {
210
+ const mock = createMockNative();
211
+ const sim = Simulation.create(TopModule, {
212
+ __nativeCreate: mock.create,
213
+ });
214
+
215
+ sim.dump(99);
216
+ expect(mock.handle.dump).toHaveBeenCalledWith(99);
217
+ });
218
+
219
+ test("create throws without native binding", () => {
220
+ expect(() => {
221
+ Simulation.create(TopModule);
222
+ }).toThrow("Native simulator binding not loaded");
223
+ });
224
+
225
+ test("runUntil with maxSteps: fast path when omitted", () => {
226
+ const mock = createMockNative();
227
+ const sim = Simulation.create(TopModule, {
228
+ __nativeCreate: mock.create,
229
+ });
230
+
231
+ sim.runUntil(100);
232
+ // Without maxSteps, the Rust fast-path should be used
233
+ expect(mock.handle.runUntil).toHaveBeenCalledWith(100);
234
+ });
235
+
236
+ test("runUntil with maxSteps: throws SimulationTimeoutError", () => {
237
+ const mock = createMockNative();
238
+ const sim = Simulation.create(TopModule, {
239
+ __nativeCreate: mock.create,
240
+ });
241
+
242
+ // step mock increments time by 5 each call, so reaching 10000 requires 2000 steps
243
+ // With maxSteps=10 we'll exhaust before getting there
244
+ expect(() => sim.runUntil(10000, { maxSteps: 10 })).toThrow(
245
+ SimulationTimeoutError,
246
+ );
247
+ });
248
+
249
+ test("runUntil with maxSteps: timeout error has correct properties", () => {
250
+ const mock = createMockNative();
251
+ const sim = Simulation.create(TopModule, {
252
+ __nativeCreate: mock.create,
253
+ });
254
+
255
+ try {
256
+ sim.runUntil(10000, { maxSteps: 5 });
257
+ expect.unreachable();
258
+ } catch (e) {
259
+ expect(e).toBeInstanceOf(SimulationTimeoutError);
260
+ const err = e as SimulationTimeoutError;
261
+ expect(err.steps).toBe(5);
262
+ expect(err.time).toBeGreaterThan(0);
263
+ }
264
+ });
265
+
266
+ test("waitUntil: returns time when condition is met", () => {
267
+ const mock = createMockNative();
268
+ const sim = Simulation.create(TopModule, {
269
+ __nativeCreate: mock.create,
270
+ });
271
+
272
+ sim.dut.d = 42n;
273
+ let callCount = 0;
274
+ const t = sim.waitUntil(() => {
275
+ callCount++;
276
+ return callCount >= 3;
277
+ });
278
+
279
+ expect(t).toBeGreaterThan(0);
280
+ expect(callCount).toBe(3);
281
+ });
282
+
283
+ test("waitUntil: throws on timeout", () => {
284
+ const mock = createMockNative();
285
+ const sim = Simulation.create(TopModule, {
286
+ __nativeCreate: mock.create,
287
+ });
288
+
289
+ expect(() => sim.waitUntil(() => false, { maxSteps: 5 })).toThrow(
290
+ SimulationTimeoutError,
291
+ );
292
+ });
293
+
294
+ test("waitForCycles: counts rising edges", () => {
295
+ const mock = createMockNative();
296
+ const sim = Simulation.create(TopModule, {
297
+ __nativeCreate: mock.create,
298
+ });
299
+
300
+ sim.addClock("clk", { period: 10 });
301
+ const t = sim.waitForCycles("clk", 3);
302
+ // clk toggles each step: 0→1→0→1→0→1 (5 steps for 3 rising edges)
303
+ expect(mock.handle.step).toHaveBeenCalledTimes(5);
304
+ expect(t).toBe(25);
305
+ });
306
+
307
+ test("waitForCycles: throws without addClock", () => {
308
+ const mock = createMockNative();
309
+ const sim = Simulation.create(TopModule, {
310
+ __nativeCreate: mock.create,
311
+ });
312
+
313
+ expect(() => sim.waitForCycles("clk", 3)).toThrow("No clock registered");
314
+ });
315
+
316
+ test("reset: active-high with associatedClock steps until target time", () => {
317
+ const mock = createMockNative({ associatedClock: "clk" });
318
+ const sim = Simulation.create(TopModule, {
319
+ __nativeCreate: mock.create,
320
+ });
321
+
322
+ sim.addClock("clk", { period: 10 });
323
+ sim.reset("rst");
324
+ // Default activeCycles=2: 3 steps for 2 rising edges (0→1→0→1)
325
+ expect(mock.handle.step).toHaveBeenCalledTimes(3);
326
+ // Released to inactive value (0 for active-high)
327
+ const view = new DataView(mock.buffer);
328
+ expect(view.getUint8(0)).toBe(0);
329
+ });
330
+
331
+ test("reset: custom activeCycles with associatedClock", () => {
332
+ const mock = createMockNative({ associatedClock: "clk" });
333
+ const sim = Simulation.create(TopModule, {
334
+ __nativeCreate: mock.create,
335
+ });
336
+
337
+ sim.addClock("clk", { period: 10 });
338
+ sim.reset("rst", { activeCycles: 3 });
339
+ // 3 cycles: 5 steps for 3 rising edges (0→1→0→1→0→1)
340
+ expect(mock.handle.step).toHaveBeenCalledTimes(5);
341
+ });
342
+
343
+ test("reset: explicit duration overrides cycle calculation", () => {
344
+ const mock = createMockNative({ associatedClock: "clk" });
345
+ const sim = Simulation.create(TopModule, {
346
+ __nativeCreate: mock.create,
347
+ });
348
+
349
+ sim.addClock("clk", { period: 10 });
350
+ sim.reset("rst", { duration: 50 });
351
+ // Explicit duration → runUntil(0 + 50 = 50)
352
+ expect(mock.handle.runUntil).toHaveBeenCalledWith(50);
353
+ });
354
+
355
+ test("reset: active-low with associatedClock asserts 0 then releases to 1", () => {
356
+ const mock = createMockNative({
357
+ resetTypeKind: "reset_async_low",
358
+ associatedClock: "clk",
359
+ });
360
+ const sim = Simulation.create(TopModule, {
361
+ __nativeCreate: mock.create,
362
+ });
363
+
364
+ sim.addClock("clk", { period: 10 });
365
+ sim.reset("rst");
366
+ // active-low: releases to 1
367
+ const view = new DataView(mock.buffer);
368
+ expect(view.getUint8(0)).toBe(1);
369
+ // activeCycles=2: 3 steps for 2 rising edges
370
+ expect(mock.handle.step).toHaveBeenCalledTimes(3);
371
+ });
372
+
373
+ test("reset: throws when no associatedClock and no duration", () => {
374
+ // No associatedClock in layout
375
+ const mock = createMockNative();
376
+ const sim = Simulation.create(TopModule, {
377
+ __nativeCreate: mock.create,
378
+ });
379
+
380
+ expect(() => sim.reset("rst")).toThrow("has no associated clock");
381
+ });
382
+
383
+ test("reset: no associatedClock but duration specified works", () => {
384
+ // No associatedClock in layout, but duration is given
385
+ const mock = createMockNative();
386
+ const sim = Simulation.create(TopModule, {
387
+ __nativeCreate: mock.create,
388
+ });
389
+
390
+ sim.reset("rst", { duration: 100 });
391
+ expect(mock.handle.runUntil).toHaveBeenCalledWith(100);
392
+ });
393
+
394
+ test("reset: throws when associatedClock not registered via addClock", () => {
395
+ const mock = createMockNative({ associatedClock: "clk" });
396
+ const sim = Simulation.create(TopModule, {
397
+ __nativeCreate: mock.create,
398
+ });
399
+ // addClock not called
400
+ expect(() => sim.reset("rst")).toThrow("No clock registered for 'clk'");
401
+ });
402
+
403
+ test("reset: throws on non-reset port", () => {
404
+ const mock = createMockNative();
405
+ const sim = Simulation.create(TopModule, {
406
+ __nativeCreate: mock.create,
407
+ });
408
+
409
+ expect(() => sim.reset("d")).toThrow("not a reset signal");
410
+ });
411
+
412
+ test("reset: throws on unknown port", () => {
413
+ const mock = createMockNative();
414
+ const sim = Simulation.create(TopModule, {
415
+ __nativeCreate: mock.create,
416
+ });
417
+
418
+ expect(() => sim.reset("nonexistent")).toThrow("Unknown port");
419
+ });
393
420
  });