@celox-sim/celox 0.0.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dut.d.ts +35 -0
- package/dist/dut.d.ts.map +1 -0
- package/dist/dut.js +342 -0
- package/dist/dut.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/napi-bridge.d.ts +7 -0
- package/dist/napi-bridge.d.ts.map +1 -0
- package/dist/napi-bridge.js +7 -0
- package/dist/napi-bridge.js.map +1 -0
- package/dist/napi-helpers.d.ts +104 -0
- package/dist/napi-helpers.d.ts.map +1 -0
- package/dist/napi-helpers.js +207 -0
- package/dist/napi-helpers.js.map +1 -0
- package/dist/simulation.d.ts +111 -0
- package/dist/simulation.d.ts.map +1 -0
- package/dist/simulation.js +187 -0
- package/dist/simulation.js.map +1 -0
- package/dist/simulator.d.ts +92 -0
- package/dist/simulator.d.ts.map +1 -0
- package/dist/simulator.js +171 -0
- package/dist/simulator.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -7
- package/src/dut.test.ts +534 -0
- package/src/dut.ts +449 -0
- package/src/e2e.bench.ts +240 -0
- package/src/e2e.test.ts +965 -0
- package/src/index.ts +57 -0
- package/src/matchers.ts +154 -0
- package/src/napi-bridge.ts +13 -0
- package/src/napi-helpers.ts +326 -0
- package/src/simulation.test.ts +185 -0
- package/src/simulation.ts +273 -0
- package/src/simulator.test.ts +191 -0
- package/src/simulator.ts +266 -0
- package/src/types.ts +164 -0
- package/README.md +0 -45
package/src/dut.test.ts
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from "vitest";
|
|
2
|
+
import { createDut, readFourState, type DirtyState } from "./dut.js";
|
|
3
|
+
import type {
|
|
4
|
+
NativeSimulatorHandle,
|
|
5
|
+
PortInfo,
|
|
6
|
+
SignalLayout,
|
|
7
|
+
} from "./types.js";
|
|
8
|
+
import { FourState, X } from "./types.js";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Helpers
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
function mockHandle(): NativeSimulatorHandle {
|
|
15
|
+
return {
|
|
16
|
+
tick: vi.fn(),
|
|
17
|
+
tickN: vi.fn(),
|
|
18
|
+
evalComb: vi.fn(),
|
|
19
|
+
dump: vi.fn(),
|
|
20
|
+
dispose: vi.fn(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeBuffer(size: number): SharedArrayBuffer {
|
|
25
|
+
return new SharedArrayBuffer(size);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Basic scalar read/write
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
describe("createDut — scalar ports", () => {
|
|
33
|
+
test("write and read 8-bit input", () => {
|
|
34
|
+
const buffer = makeBuffer(64);
|
|
35
|
+
const layout: Record<string, SignalLayout> = {
|
|
36
|
+
a: { offset: 0, width: 8, byteSize: 1, is4state: false, direction: "input" },
|
|
37
|
+
};
|
|
38
|
+
const ports: Record<string, PortInfo> = {
|
|
39
|
+
a: { direction: "input", type: "logic", width: 8 },
|
|
40
|
+
};
|
|
41
|
+
const handle = mockHandle();
|
|
42
|
+
const state: DirtyState = { dirty: false };
|
|
43
|
+
|
|
44
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
45
|
+
|
|
46
|
+
dut.a = 42;
|
|
47
|
+
expect(state.dirty).toBe(true);
|
|
48
|
+
expect(dut.a).toBe(42);
|
|
49
|
+
// Reading an input doesn't trigger evalComb
|
|
50
|
+
expect(handle.evalComb).not.toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("write and read 16-bit input", () => {
|
|
54
|
+
const buffer = makeBuffer(64);
|
|
55
|
+
const layout: Record<string, SignalLayout> = {
|
|
56
|
+
a: { offset: 0, width: 16, byteSize: 2, is4state: false, direction: "input" },
|
|
57
|
+
};
|
|
58
|
+
const ports: Record<string, PortInfo> = {
|
|
59
|
+
a: { direction: "input", type: "logic", width: 16 },
|
|
60
|
+
};
|
|
61
|
+
const handle = mockHandle();
|
|
62
|
+
const state: DirtyState = { dirty: false };
|
|
63
|
+
|
|
64
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
65
|
+
|
|
66
|
+
dut.a = 0xABCD;
|
|
67
|
+
expect(dut.a).toBe(0xABCD);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("write and read 32-bit input", () => {
|
|
71
|
+
const buffer = makeBuffer(64);
|
|
72
|
+
const layout: Record<string, SignalLayout> = {
|
|
73
|
+
a: { offset: 0, width: 32, byteSize: 4, is4state: false, direction: "input" },
|
|
74
|
+
};
|
|
75
|
+
const ports: Record<string, PortInfo> = {
|
|
76
|
+
a: { direction: "input", type: "logic", width: 32 },
|
|
77
|
+
};
|
|
78
|
+
const handle = mockHandle();
|
|
79
|
+
const state: DirtyState = { dirty: false };
|
|
80
|
+
|
|
81
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
82
|
+
|
|
83
|
+
dut.a = 0xDEAD_BEEF;
|
|
84
|
+
expect(dut.a).toBe(0xDEAD_BEEF);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("write and read 48-bit value (fits in number)", () => {
|
|
88
|
+
const buffer = makeBuffer(64);
|
|
89
|
+
const layout: Record<string, SignalLayout> = {
|
|
90
|
+
a: { offset: 0, width: 48, byteSize: 8, is4state: false, direction: "input" },
|
|
91
|
+
};
|
|
92
|
+
const ports: Record<string, PortInfo> = {
|
|
93
|
+
a: { direction: "input", type: "logic", width: 48 },
|
|
94
|
+
};
|
|
95
|
+
const handle = mockHandle();
|
|
96
|
+
const state: DirtyState = { dirty: false };
|
|
97
|
+
|
|
98
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
99
|
+
|
|
100
|
+
const val = 0x1234_5678_9ABC;
|
|
101
|
+
dut.a = val;
|
|
102
|
+
expect(dut.a).toBe(val);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("write and read 64-bit BigInt value", () => {
|
|
106
|
+
const buffer = makeBuffer(64);
|
|
107
|
+
const layout: Record<string, SignalLayout> = {
|
|
108
|
+
a: { offset: 0, width: 64, byteSize: 8, is4state: false, direction: "input" },
|
|
109
|
+
};
|
|
110
|
+
const ports: Record<string, PortInfo> = {
|
|
111
|
+
a: { direction: "input", type: "logic", width: 64 },
|
|
112
|
+
};
|
|
113
|
+
const handle = mockHandle();
|
|
114
|
+
const state: DirtyState = { dirty: false };
|
|
115
|
+
|
|
116
|
+
const dut = createDut<{ a: bigint }>(buffer, layout, ports, handle, state);
|
|
117
|
+
|
|
118
|
+
const val = 0xDEAD_BEEF_CAFE_BABEn;
|
|
119
|
+
(dut as any).a = val;
|
|
120
|
+
expect(dut.a).toBe(val);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("8-bit write masks to width", () => {
|
|
124
|
+
const buffer = makeBuffer(64);
|
|
125
|
+
const layout: Record<string, SignalLayout> = {
|
|
126
|
+
a: { offset: 0, width: 4, byteSize: 1, is4state: false, direction: "input" },
|
|
127
|
+
};
|
|
128
|
+
const ports: Record<string, PortInfo> = {
|
|
129
|
+
a: { direction: "input", type: "logic", width: 4 },
|
|
130
|
+
};
|
|
131
|
+
const handle = mockHandle();
|
|
132
|
+
const state: DirtyState = { dirty: false };
|
|
133
|
+
|
|
134
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
135
|
+
|
|
136
|
+
dut.a = 0xFF; // Only lower 4 bits should be stored
|
|
137
|
+
expect(dut.a).toBe(0x0F);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Dirty tracking and evalComb
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
describe("createDut — dirty tracking", () => {
|
|
146
|
+
test("reading output when dirty triggers evalComb", () => {
|
|
147
|
+
const buffer = makeBuffer(64);
|
|
148
|
+
const layout: Record<string, SignalLayout> = {
|
|
149
|
+
a: { offset: 0, width: 16, byteSize: 2, is4state: false, direction: "input" },
|
|
150
|
+
sum: { offset: 4, width: 17, byteSize: 4, is4state: false, direction: "output" },
|
|
151
|
+
};
|
|
152
|
+
const ports: Record<string, PortInfo> = {
|
|
153
|
+
a: { direction: "input", type: "logic", width: 16 },
|
|
154
|
+
sum: { direction: "output", type: "logic", width: 17 },
|
|
155
|
+
};
|
|
156
|
+
const handle = mockHandle();
|
|
157
|
+
const state: DirtyState = { dirty: false };
|
|
158
|
+
|
|
159
|
+
const dut = createDut<{ a: number; readonly sum: number }>(
|
|
160
|
+
buffer, layout, ports, handle, state,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Write input → dirty
|
|
164
|
+
dut.a = 100;
|
|
165
|
+
expect(state.dirty).toBe(true);
|
|
166
|
+
|
|
167
|
+
// Read output → evalComb should be called
|
|
168
|
+
void dut.sum;
|
|
169
|
+
expect(handle.evalComb).toHaveBeenCalledTimes(1);
|
|
170
|
+
expect(state.dirty).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("reading output when clean does NOT trigger evalComb", () => {
|
|
174
|
+
const buffer = makeBuffer(64);
|
|
175
|
+
const layout: Record<string, SignalLayout> = {
|
|
176
|
+
sum: { offset: 0, width: 17, byteSize: 4, is4state: false, direction: "output" },
|
|
177
|
+
};
|
|
178
|
+
const ports: Record<string, PortInfo> = {
|
|
179
|
+
sum: { direction: "output", type: "logic", width: 17 },
|
|
180
|
+
};
|
|
181
|
+
const handle = mockHandle();
|
|
182
|
+
const state: DirtyState = { dirty: false };
|
|
183
|
+
|
|
184
|
+
const dut = createDut<{ readonly sum: number }>(
|
|
185
|
+
buffer, layout, ports, handle, state,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
void dut.sum;
|
|
189
|
+
expect(handle.evalComb).not.toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("reading input does NOT trigger evalComb even when dirty", () => {
|
|
193
|
+
const buffer = makeBuffer(64);
|
|
194
|
+
const layout: Record<string, SignalLayout> = {
|
|
195
|
+
a: { offset: 0, width: 8, byteSize: 1, is4state: false, direction: "input" },
|
|
196
|
+
};
|
|
197
|
+
const ports: Record<string, PortInfo> = {
|
|
198
|
+
a: { direction: "input", type: "logic", width: 8 },
|
|
199
|
+
};
|
|
200
|
+
const handle = mockHandle();
|
|
201
|
+
const state: DirtyState = { dirty: true };
|
|
202
|
+
|
|
203
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
204
|
+
|
|
205
|
+
void dut.a;
|
|
206
|
+
expect(handle.evalComb).not.toHaveBeenCalled();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("writing to output throws", () => {
|
|
210
|
+
const buffer = makeBuffer(64);
|
|
211
|
+
const layout: Record<string, SignalLayout> = {
|
|
212
|
+
sum: { offset: 0, width: 17, byteSize: 4, is4state: false, direction: "output" },
|
|
213
|
+
};
|
|
214
|
+
const ports: Record<string, PortInfo> = {
|
|
215
|
+
sum: { direction: "output", type: "logic", width: 17 },
|
|
216
|
+
};
|
|
217
|
+
const handle = mockHandle();
|
|
218
|
+
const state: DirtyState = { dirty: false };
|
|
219
|
+
|
|
220
|
+
const dut = createDut<{ sum: number }>(buffer, layout, ports, handle, state);
|
|
221
|
+
|
|
222
|
+
expect(() => {
|
|
223
|
+
dut.sum = 42;
|
|
224
|
+
}).toThrow("Cannot write to output port 'sum'");
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Clock port is hidden
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
describe("createDut — clock ports", () => {
|
|
233
|
+
test("clock ports are not exposed on the DUT", () => {
|
|
234
|
+
const buffer = makeBuffer(64);
|
|
235
|
+
const layout: Record<string, SignalLayout> = {
|
|
236
|
+
clk: { offset: 0, width: 1, byteSize: 1, is4state: false, direction: "input" },
|
|
237
|
+
a: { offset: 1, width: 8, byteSize: 1, is4state: false, direction: "input" },
|
|
238
|
+
};
|
|
239
|
+
const ports: Record<string, PortInfo> = {
|
|
240
|
+
clk: { direction: "input", type: "clock", width: 1 },
|
|
241
|
+
a: { direction: "input", type: "logic", width: 8 },
|
|
242
|
+
};
|
|
243
|
+
const handle = mockHandle();
|
|
244
|
+
const state: DirtyState = { dirty: false };
|
|
245
|
+
|
|
246
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
247
|
+
|
|
248
|
+
expect(Object.keys(dut as object)).toEqual(["a"]);
|
|
249
|
+
expect((dut as any).clk).toBeUndefined();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Multiple signals at different offsets
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
describe("createDut — multiple signals", () => {
|
|
258
|
+
test("Adder-like module with a, b, sum", () => {
|
|
259
|
+
const buffer = makeBuffer(64);
|
|
260
|
+
const layout: Record<string, SignalLayout> = {
|
|
261
|
+
rst: { offset: 0, width: 1, byteSize: 1, is4state: false, direction: "input" },
|
|
262
|
+
a: { offset: 2, width: 16, byteSize: 2, is4state: false, direction: "input" },
|
|
263
|
+
b: { offset: 4, width: 16, byteSize: 2, is4state: false, direction: "input" },
|
|
264
|
+
sum: { offset: 8, width: 17, byteSize: 4, is4state: false, direction: "output" },
|
|
265
|
+
};
|
|
266
|
+
const ports: Record<string, PortInfo> = {
|
|
267
|
+
clk: { direction: "input", type: "clock", width: 1 },
|
|
268
|
+
rst: { direction: "input", type: "reset", width: 1 },
|
|
269
|
+
a: { direction: "input", type: "logic", width: 16 },
|
|
270
|
+
b: { direction: "input", type: "logic", width: 16 },
|
|
271
|
+
sum: { direction: "output", type: "logic", width: 17 },
|
|
272
|
+
};
|
|
273
|
+
const handle = mockHandle();
|
|
274
|
+
// Simulate evalComb by writing result into buffer
|
|
275
|
+
(handle.evalComb as ReturnType<typeof vi.fn>).mockImplementation(() => {
|
|
276
|
+
const view = new DataView(buffer);
|
|
277
|
+
const a = view.getUint16(2, true);
|
|
278
|
+
const b = view.getUint16(4, true);
|
|
279
|
+
view.setUint32(8, a + b, true);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const state: DirtyState = { dirty: false };
|
|
283
|
+
const dut = createDut<{
|
|
284
|
+
rst: number;
|
|
285
|
+
a: number;
|
|
286
|
+
b: number;
|
|
287
|
+
readonly sum: number;
|
|
288
|
+
}>(buffer, layout, ports, handle, state);
|
|
289
|
+
|
|
290
|
+
dut.a = 100;
|
|
291
|
+
dut.b = 200;
|
|
292
|
+
// sum read triggers evalComb
|
|
293
|
+
expect(dut.sum).toBe(300);
|
|
294
|
+
expect(handle.evalComb).toHaveBeenCalledTimes(1);
|
|
295
|
+
|
|
296
|
+
// second read without changes → no evalComb
|
|
297
|
+
expect(dut.sum).toBe(300);
|
|
298
|
+
expect(handle.evalComb).toHaveBeenCalledTimes(1);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// 4-state support
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
describe("createDut — 4-state", () => {
|
|
307
|
+
test("write X to a 4-state signal", () => {
|
|
308
|
+
// 8-bit signal: 1 byte value + 1 byte mask = 2 bytes
|
|
309
|
+
const buffer = makeBuffer(64);
|
|
310
|
+
const layout: Record<string, SignalLayout> = {
|
|
311
|
+
a: { offset: 0, width: 8, byteSize: 1, is4state: true, direction: "input" },
|
|
312
|
+
};
|
|
313
|
+
const ports: Record<string, PortInfo> = {
|
|
314
|
+
a: { direction: "input", type: "logic", width: 8, is4state: true },
|
|
315
|
+
};
|
|
316
|
+
const handle = mockHandle();
|
|
317
|
+
const state: DirtyState = { dirty: false };
|
|
318
|
+
|
|
319
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
320
|
+
|
|
321
|
+
(dut as any).a = X;
|
|
322
|
+
// Value should be 0, mask should be 0xFF
|
|
323
|
+
const [value, mask] = readFourState(buffer, layout.a);
|
|
324
|
+
expect(value).toBe(0);
|
|
325
|
+
expect(mask).toBe(0xFF);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("write FourState to a 4-state signal", () => {
|
|
329
|
+
const buffer = makeBuffer(64);
|
|
330
|
+
const layout: Record<string, SignalLayout> = {
|
|
331
|
+
a: { offset: 0, width: 8, byteSize: 1, is4state: true, direction: "input" },
|
|
332
|
+
};
|
|
333
|
+
const ports: Record<string, PortInfo> = {
|
|
334
|
+
a: { direction: "input", type: "logic", width: 8, is4state: true },
|
|
335
|
+
};
|
|
336
|
+
const handle = mockHandle();
|
|
337
|
+
const state: DirtyState = { dirty: false };
|
|
338
|
+
|
|
339
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
340
|
+
|
|
341
|
+
(dut as any).a = FourState(0b1010, 0b0100);
|
|
342
|
+
const [value, mask] = readFourState(buffer, layout.a);
|
|
343
|
+
expect(value).toBe(0b1010);
|
|
344
|
+
expect(mask).toBe(0b0100);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("writing X to non-4-state signal throws", () => {
|
|
348
|
+
const buffer = makeBuffer(64);
|
|
349
|
+
const layout: Record<string, SignalLayout> = {
|
|
350
|
+
a: { offset: 0, width: 8, byteSize: 1, is4state: false, direction: "input" },
|
|
351
|
+
};
|
|
352
|
+
const ports: Record<string, PortInfo> = {
|
|
353
|
+
a: { direction: "input", type: "logic", width: 8 },
|
|
354
|
+
};
|
|
355
|
+
const handle = mockHandle();
|
|
356
|
+
const state: DirtyState = { dirty: false };
|
|
357
|
+
|
|
358
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
359
|
+
|
|
360
|
+
expect(() => {
|
|
361
|
+
(dut as any).a = X;
|
|
362
|
+
}).toThrow("not 4-state");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("writing FourState to non-4-state signal throws", () => {
|
|
366
|
+
const buffer = makeBuffer(64);
|
|
367
|
+
const layout: Record<string, SignalLayout> = {
|
|
368
|
+
a: { offset: 0, width: 8, byteSize: 1, is4state: false, direction: "input" },
|
|
369
|
+
};
|
|
370
|
+
const ports: Record<string, PortInfo> = {
|
|
371
|
+
a: { direction: "input", type: "logic", width: 8 },
|
|
372
|
+
};
|
|
373
|
+
const handle = mockHandle();
|
|
374
|
+
const state: DirtyState = { dirty: false };
|
|
375
|
+
|
|
376
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
377
|
+
|
|
378
|
+
expect(() => {
|
|
379
|
+
(dut as any).a = FourState(0xA5, 0x0F);
|
|
380
|
+
}).toThrow("not 4-state");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("writing defined value to 4-state signal clears mask", () => {
|
|
384
|
+
const buffer = makeBuffer(64);
|
|
385
|
+
const layout: Record<string, SignalLayout> = {
|
|
386
|
+
a: { offset: 0, width: 8, byteSize: 1, is4state: true, direction: "input" },
|
|
387
|
+
};
|
|
388
|
+
const ports: Record<string, PortInfo> = {
|
|
389
|
+
a: { direction: "input", type: "logic", width: 8, is4state: true },
|
|
390
|
+
};
|
|
391
|
+
const handle = mockHandle();
|
|
392
|
+
const state: DirtyState = { dirty: false };
|
|
393
|
+
|
|
394
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
395
|
+
|
|
396
|
+
// First write X
|
|
397
|
+
(dut as any).a = X;
|
|
398
|
+
const [, maskBefore] = readFourState(buffer, layout.a);
|
|
399
|
+
expect(maskBefore).toBe(0xFF);
|
|
400
|
+
|
|
401
|
+
// Then write a defined value — mask should clear
|
|
402
|
+
dut.a = 42;
|
|
403
|
+
const [value, maskAfter] = readFourState(buffer, layout.a);
|
|
404
|
+
expect(value).toBe(42);
|
|
405
|
+
expect(maskAfter).toBe(0);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("reading 4-state output returns value part only", () => {
|
|
409
|
+
const buffer = makeBuffer(64);
|
|
410
|
+
const view = new DataView(buffer);
|
|
411
|
+
const layout: Record<string, SignalLayout> = {
|
|
412
|
+
y: { offset: 0, width: 8, byteSize: 1, is4state: true, direction: "output" },
|
|
413
|
+
};
|
|
414
|
+
const ports: Record<string, PortInfo> = {
|
|
415
|
+
y: { direction: "output", type: "logic", width: 8, is4state: true },
|
|
416
|
+
};
|
|
417
|
+
const handle = mockHandle();
|
|
418
|
+
const state: DirtyState = { dirty: false };
|
|
419
|
+
|
|
420
|
+
const dut = createDut<{ readonly y: number }>(buffer, layout, ports, handle, state);
|
|
421
|
+
|
|
422
|
+
// Set value=0xAB, mask=0x0F (lower 4 bits are X)
|
|
423
|
+
view.setUint8(0, 0xAB);
|
|
424
|
+
view.setUint8(1, 0x0F);
|
|
425
|
+
|
|
426
|
+
// DUT getter returns the value part
|
|
427
|
+
expect(dut.y).toBe(0xAB);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("write X sets dirty flag", () => {
|
|
431
|
+
const buffer = makeBuffer(64);
|
|
432
|
+
const layout: Record<string, SignalLayout> = {
|
|
433
|
+
a: { offset: 0, width: 8, byteSize: 1, is4state: true, direction: "input" },
|
|
434
|
+
};
|
|
435
|
+
const ports: Record<string, PortInfo> = {
|
|
436
|
+
a: { direction: "input", type: "logic", width: 8, is4state: true },
|
|
437
|
+
};
|
|
438
|
+
const handle = mockHandle();
|
|
439
|
+
const state: DirtyState = { dirty: false };
|
|
440
|
+
|
|
441
|
+
const dut = createDut<{ a: number }>(buffer, layout, ports, handle, state);
|
|
442
|
+
|
|
443
|
+
(dut as any).a = X;
|
|
444
|
+
expect(state.dirty).toBe(true);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
449
|
+
// Array ports
|
|
450
|
+
// ---------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
describe("createDut — array ports", () => {
|
|
453
|
+
test("read/write array elements", () => {
|
|
454
|
+
// 4 elements of 8 bits each = 4 bytes
|
|
455
|
+
const buffer = makeBuffer(64);
|
|
456
|
+
const layout: Record<string, SignalLayout> = {
|
|
457
|
+
data: { offset: 0, width: 8, byteSize: 4, is4state: false, direction: "input" },
|
|
458
|
+
};
|
|
459
|
+
const ports: Record<string, PortInfo> = {
|
|
460
|
+
data: { direction: "input", type: "logic", width: 8, arrayDims: [4] },
|
|
461
|
+
};
|
|
462
|
+
const handle = mockHandle();
|
|
463
|
+
const state: DirtyState = { dirty: false };
|
|
464
|
+
|
|
465
|
+
const dut = createDut<{ data: number[] }>(
|
|
466
|
+
buffer, layout, ports, handle, state,
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
(dut.data as any)[0] = 0xAA;
|
|
470
|
+
(dut.data as any)[1] = 0xBB;
|
|
471
|
+
(dut.data as any)[2] = 0xCC;
|
|
472
|
+
(dut.data as any)[3] = 0xDD;
|
|
473
|
+
|
|
474
|
+
expect((dut.data as any)[0]).toBe(0xAA);
|
|
475
|
+
expect((dut.data as any)[1]).toBe(0xBB);
|
|
476
|
+
expect((dut.data as any)[2]).toBe(0xCC);
|
|
477
|
+
expect((dut.data as any)[3]).toBe(0xDD);
|
|
478
|
+
expect((dut.data as any).length).toBe(4);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// ---------------------------------------------------------------------------
|
|
483
|
+
// Interface (nested) ports
|
|
484
|
+
// ---------------------------------------------------------------------------
|
|
485
|
+
|
|
486
|
+
describe("createDut — interface ports", () => {
|
|
487
|
+
test("nested interface members", () => {
|
|
488
|
+
const buffer = makeBuffer(64);
|
|
489
|
+
const layout: Record<string, SignalLayout> = {
|
|
490
|
+
"bus.addr": { offset: 0, width: 32, byteSize: 4, is4state: false, direction: "input" },
|
|
491
|
+
"bus.data": { offset: 4, width: 32, byteSize: 4, is4state: false, direction: "input" },
|
|
492
|
+
"bus.valid": { offset: 8, width: 1, byteSize: 1, is4state: false, direction: "input" },
|
|
493
|
+
"bus.ready": { offset: 9, width: 1, byteSize: 1, is4state: false, direction: "output" },
|
|
494
|
+
};
|
|
495
|
+
const ports: Record<string, PortInfo> = {
|
|
496
|
+
bus: {
|
|
497
|
+
direction: "input",
|
|
498
|
+
type: "logic",
|
|
499
|
+
width: 0,
|
|
500
|
+
interface: {
|
|
501
|
+
addr: { direction: "input", type: "logic", width: 32 },
|
|
502
|
+
data: { direction: "input", type: "logic", width: 32 },
|
|
503
|
+
valid: { direction: "input", type: "logic", width: 1 },
|
|
504
|
+
ready: { direction: "output", type: "logic", width: 1 },
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
const handle = mockHandle();
|
|
509
|
+
(handle.evalComb as ReturnType<typeof vi.fn>).mockImplementation(() => {
|
|
510
|
+
const view = new DataView(buffer);
|
|
511
|
+
// mock: ready = valid
|
|
512
|
+
view.setUint8(9, view.getUint8(8));
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
const state: DirtyState = { dirty: false };
|
|
516
|
+
const dut = createDut<{
|
|
517
|
+
bus: {
|
|
518
|
+
addr: number;
|
|
519
|
+
data: number;
|
|
520
|
+
valid: number;
|
|
521
|
+
readonly ready: number;
|
|
522
|
+
};
|
|
523
|
+
}>(buffer, layout, ports, handle, state);
|
|
524
|
+
|
|
525
|
+
dut.bus.addr = 0x1000;
|
|
526
|
+
dut.bus.data = 0xFF;
|
|
527
|
+
dut.bus.valid = 1;
|
|
528
|
+
|
|
529
|
+
expect(dut.bus.addr).toBe(0x1000);
|
|
530
|
+
expect(dut.bus.data).toBe(0xFF);
|
|
531
|
+
expect(dut.bus.ready).toBe(1);
|
|
532
|
+
expect(handle.evalComb).toHaveBeenCalledTimes(1);
|
|
533
|
+
});
|
|
534
|
+
});
|