@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/dut.ts
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* accessors are fully visible to V8 inline caches.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { HierarchyNode } from "./napi-helpers.js";
|
|
10
11
|
import type {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
FourStateValue,
|
|
13
|
+
NativeHandle,
|
|
14
|
+
PortInfo,
|
|
15
|
+
SignalLayout,
|
|
15
16
|
} from "./types.js";
|
|
16
|
-
import type { HierarchyNode } from "./napi-helpers.js";
|
|
17
17
|
import { isFourStateValue } from "./types.js";
|
|
18
18
|
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
@@ -28,7 +28,7 @@ import { isFourStateValue } from "./types.js";
|
|
|
28
28
|
* @internal
|
|
29
29
|
*/
|
|
30
30
|
export interface DirtyState {
|
|
31
|
-
|
|
31
|
+
dirty: boolean;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
@@ -37,141 +37,139 @@ export interface DirtyState {
|
|
|
37
37
|
|
|
38
38
|
/** Read an unsigned integer of the given byte-size (little-endian), masked to width. */
|
|
39
39
|
function readNumber(view: DataView, offset: number, width: number): number {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
40
|
+
if (width <= 8) {
|
|
41
|
+
const raw = view.getUint8(offset);
|
|
42
|
+
return width === 8 ? raw : raw & ((1 << width) - 1);
|
|
43
|
+
}
|
|
44
|
+
if (width <= 16) {
|
|
45
|
+
const raw = view.getUint16(offset, true);
|
|
46
|
+
return width === 16 ? raw : raw & ((1 << width) - 1);
|
|
47
|
+
}
|
|
48
|
+
if (width <= 32) {
|
|
49
|
+
const raw = view.getUint32(offset, true);
|
|
50
|
+
return width === 32 ? raw : (raw & ((1 << width) - 1)) >>> 0;
|
|
51
|
+
}
|
|
52
|
+
// 33..53 bits — fits safely in a JS number
|
|
53
|
+
const lo = view.getUint32(offset, true);
|
|
54
|
+
const hi = view.getUint32(offset + 4, true) & ((1 << (width - 32)) - 1);
|
|
55
|
+
return lo + hi * 0x1_0000_0000;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/** Write an unsigned integer of the given byte-size (little-endian). */
|
|
59
59
|
function writeNumber(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
view: DataView,
|
|
61
|
+
offset: number,
|
|
62
|
+
width: number,
|
|
63
|
+
value: number,
|
|
64
64
|
): void {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
if (width <= 8) {
|
|
66
|
+
view.setUint8(offset, value & ((1 << width) - 1));
|
|
67
|
+
} else if (width <= 16) {
|
|
68
|
+
view.setUint16(offset, value & ((1 << width) - 1), true);
|
|
69
|
+
} else if (width <= 32) {
|
|
70
|
+
view.setUint32(offset, value >>> 0, true);
|
|
71
|
+
} else {
|
|
72
|
+
// 33..53 bits
|
|
73
|
+
view.setUint32(offset, value >>> 0, true);
|
|
74
|
+
view.setUint32(offset + 4, Math.floor(value / 0x1_0000_0000) >>> 0, true);
|
|
75
|
+
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/** Read a wide value (≥ 54 bits) as BigInt, little-endian. */
|
|
79
79
|
function readBigInt(view: DataView, offset: number, byteSize: number): bigint {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
let result = 0n;
|
|
81
|
+
// Read 8 bytes at a time, then remaining bytes
|
|
82
|
+
const fullWords = Math.floor(byteSize / 8);
|
|
83
|
+
for (let i = 0; i < fullWords; i++) {
|
|
84
|
+
const word = view.getBigUint64(offset + i * 8, true);
|
|
85
|
+
result |= word << BigInt(i * 64);
|
|
86
|
+
}
|
|
87
|
+
const remaining = byteSize % 8;
|
|
88
|
+
if (remaining > 0) {
|
|
89
|
+
const base = offset + fullWords * 8;
|
|
90
|
+
for (let i = 0; i < remaining; i++) {
|
|
91
|
+
result |=
|
|
92
|
+
BigInt(view.getUint8(base + i)) << BigInt(fullWords * 64 + i * 8);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
/** Write a wide value (≥ 54 bits) as BigInt, little-endian. */
|
|
98
99
|
function writeBigInt(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
view: DataView,
|
|
101
|
+
offset: number,
|
|
102
|
+
byteSize: number,
|
|
103
|
+
value: bigint,
|
|
103
104
|
): void {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
105
|
+
const fullWords = Math.floor(byteSize / 8);
|
|
106
|
+
for (let i = 0; i < fullWords; i++) {
|
|
107
|
+
view.setBigUint64(offset + i * 8, value & 0xffff_ffff_ffff_ffffn, true);
|
|
108
|
+
value >>= 64n;
|
|
109
|
+
}
|
|
110
|
+
const remaining = byteSize % 8;
|
|
111
|
+
if (remaining > 0) {
|
|
112
|
+
const base = offset + fullWords * 8;
|
|
113
|
+
for (let i = 0; i < remaining; i++) {
|
|
114
|
+
view.setUint8(base + i, Number(value & 0xffn));
|
|
115
|
+
value >>= 8n;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
/** Read a signal value from the DataView. Always returns bigint. */
|
|
120
|
-
function readSignal(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return BigInt(readNumber(view, sig.offset, sig.width));
|
|
126
|
-
}
|
|
127
|
-
return readBigInt(view, sig.offset, sig.byteSize);
|
|
121
|
+
function readSignal(view: DataView, sig: SignalLayout): bigint {
|
|
122
|
+
if (sig.width <= 53) {
|
|
123
|
+
return BigInt(readNumber(view, sig.offset, sig.width));
|
|
124
|
+
}
|
|
125
|
+
return readBigInt(view, sig.offset, sig.byteSize);
|
|
128
126
|
}
|
|
129
127
|
|
|
130
128
|
/** Write a signal value to the DataView. Accepts bigint (number accepted for compat). */
|
|
131
129
|
function writeSignal(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
view: DataView,
|
|
131
|
+
sig: SignalLayout,
|
|
132
|
+
value: bigint | number,
|
|
135
133
|
): void {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
134
|
+
const bigVal = typeof value === "bigint" ? value : BigInt(value);
|
|
135
|
+
if (sig.width <= 53) {
|
|
136
|
+
writeNumber(view, sig.offset, sig.width, Number(bigVal));
|
|
137
|
+
} else {
|
|
138
|
+
writeBigInt(view, sig.offset, sig.byteSize, bigVal);
|
|
139
|
+
}
|
|
142
140
|
}
|
|
143
141
|
|
|
144
142
|
/** Write a 4-state value (value + mask) to the DataView. */
|
|
145
143
|
function writeFourState(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
view: DataView,
|
|
145
|
+
sig: SignalLayout,
|
|
146
|
+
fsv: FourStateValue,
|
|
149
147
|
): void {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
148
|
+
writeSignal(view, sig, fsv.value);
|
|
149
|
+
// Mask is stored immediately after the value bytes
|
|
150
|
+
const maskLayout: SignalLayout = {
|
|
151
|
+
offset: sig.offset + sig.byteSize,
|
|
152
|
+
width: sig.width,
|
|
153
|
+
byteSize: sig.byteSize,
|
|
154
|
+
is4state: false,
|
|
155
|
+
direction: sig.direction,
|
|
156
|
+
};
|
|
157
|
+
writeSignal(view, maskLayout, fsv.mask);
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
/** Write all-X mask for a signal. */
|
|
163
161
|
function writeAllX(view: DataView, sig: SignalLayout): void {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
162
|
+
// Value = 0, mask = all 1s
|
|
163
|
+
writeSignal(view, sig, 0n);
|
|
164
|
+
const allOnes = (1n << BigInt(sig.width)) - 1n;
|
|
165
|
+
const maskLayout: SignalLayout = {
|
|
166
|
+
offset: sig.offset + sig.byteSize,
|
|
167
|
+
width: sig.width,
|
|
168
|
+
byteSize: sig.byteSize,
|
|
169
|
+
is4state: false,
|
|
170
|
+
direction: sig.direction,
|
|
171
|
+
};
|
|
172
|
+
writeSignal(view, maskLayout, allOnes);
|
|
175
173
|
}
|
|
176
174
|
|
|
177
175
|
// ---------------------------------------------------------------------------
|
|
@@ -189,86 +187,86 @@ function writeAllX(view: DataView, sig: SignalLayout): void {
|
|
|
189
187
|
* @param hierarchy Optional hierarchy node for child instance access
|
|
190
188
|
*/
|
|
191
189
|
export function createDut<P>(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
buffer: ArrayBuffer | SharedArrayBuffer,
|
|
191
|
+
layout: Record<string, SignalLayout>,
|
|
192
|
+
portDefs: Record<string, PortInfo>,
|
|
193
|
+
handle: NativeHandle,
|
|
194
|
+
state: DirtyState,
|
|
195
|
+
hierarchy?: HierarchyNode,
|
|
198
196
|
): P {
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
197
|
+
const view = new DataView(buffer);
|
|
198
|
+
const obj = Object.create(null) as P;
|
|
199
|
+
|
|
200
|
+
// Iterate portDefs (not layout) so that interface ports are discovered
|
|
201
|
+
// even though their individual members are the ones that appear in layout.
|
|
202
|
+
for (const [name, port] of Object.entries(portDefs)) {
|
|
203
|
+
// Skip clock ports — they are controlled via tick()/addClock()
|
|
204
|
+
if (port.type === "clock") continue;
|
|
205
|
+
|
|
206
|
+
// Check for nested interface
|
|
207
|
+
if (port.interface) {
|
|
208
|
+
const nestedObj = createNestedDut(
|
|
209
|
+
view,
|
|
210
|
+
layout,
|
|
211
|
+
port.interface,
|
|
212
|
+
name,
|
|
213
|
+
handle,
|
|
214
|
+
state,
|
|
215
|
+
);
|
|
216
|
+
Object.defineProperty(obj, name, {
|
|
217
|
+
value: nestedObj,
|
|
218
|
+
enumerable: true,
|
|
219
|
+
configurable: false,
|
|
220
|
+
writable: false,
|
|
221
|
+
});
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const sig = layout[name];
|
|
226
|
+
if (!sig) continue;
|
|
227
|
+
|
|
228
|
+
// Check for array port
|
|
229
|
+
if (port.arrayDims && port.arrayDims.length > 0) {
|
|
230
|
+
const arrayObj = createArrayDut(view, sig, port, handle, state);
|
|
231
|
+
Object.defineProperty(obj, name, {
|
|
232
|
+
value: arrayObj,
|
|
233
|
+
enumerable: true,
|
|
234
|
+
configurable: false,
|
|
235
|
+
writable: false,
|
|
236
|
+
});
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Scalar port — define getter/setter
|
|
241
|
+
defineSignalProperty(obj as object, name, view, sig, port, handle, state);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Attach child instance accessors from hierarchy
|
|
245
|
+
if (hierarchy) {
|
|
246
|
+
for (const [childName, instances] of Object.entries(hierarchy.children)) {
|
|
247
|
+
if (instances.length === 1) {
|
|
248
|
+
const childDut = createChildDut(buffer, instances[0]!, handle, state);
|
|
249
|
+
Object.defineProperty(obj, childName, {
|
|
250
|
+
value: childDut,
|
|
251
|
+
enumerable: true,
|
|
252
|
+
configurable: false,
|
|
253
|
+
writable: false,
|
|
254
|
+
});
|
|
255
|
+
} else if (instances.length > 1) {
|
|
256
|
+
const childDuts = instances.map((inst) =>
|
|
257
|
+
createChildDut(buffer, inst, handle, state),
|
|
258
|
+
);
|
|
259
|
+
Object.defineProperty(obj, childName, {
|
|
260
|
+
value: childDuts,
|
|
261
|
+
enumerable: true,
|
|
262
|
+
configurable: false,
|
|
263
|
+
writable: false,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return obj;
|
|
272
270
|
}
|
|
273
271
|
|
|
274
272
|
/**
|
|
@@ -276,140 +274,143 @@ export function createDut<P>(
|
|
|
276
274
|
* Recursively creates accessors for the child's signals and its own children.
|
|
277
275
|
*/
|
|
278
276
|
export function createChildDut(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
277
|
+
buffer: ArrayBuffer | SharedArrayBuffer,
|
|
278
|
+
hierarchy: HierarchyNode,
|
|
279
|
+
handle: NativeHandle,
|
|
280
|
+
state: DirtyState,
|
|
283
281
|
): object {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
282
|
+
const view = new DataView(buffer);
|
|
283
|
+
const obj = Object.create(null);
|
|
284
|
+
|
|
285
|
+
// Define signal properties for this child instance
|
|
286
|
+
for (const [name, port] of Object.entries(hierarchy.ports)) {
|
|
287
|
+
if (port.type === "clock") continue;
|
|
288
|
+
|
|
289
|
+
// Handle nested interface port
|
|
290
|
+
if (port.interface) {
|
|
291
|
+
const nestedObj = createNestedDut(
|
|
292
|
+
view,
|
|
293
|
+
hierarchy.forDut,
|
|
294
|
+
port.interface,
|
|
295
|
+
name,
|
|
296
|
+
handle,
|
|
297
|
+
state,
|
|
298
|
+
);
|
|
299
|
+
Object.defineProperty(obj, name, {
|
|
300
|
+
value: nestedObj,
|
|
301
|
+
enumerable: true,
|
|
302
|
+
configurable: false,
|
|
303
|
+
writable: false,
|
|
304
|
+
});
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const sig = hierarchy.forDut[name];
|
|
309
|
+
if (!sig) continue;
|
|
310
|
+
|
|
311
|
+
if (port.arrayDims && port.arrayDims.length > 0) {
|
|
312
|
+
const arrayObj = createArrayDut(view, sig, port, handle, state);
|
|
313
|
+
Object.defineProperty(obj, name, {
|
|
314
|
+
value: arrayObj,
|
|
315
|
+
enumerable: true,
|
|
316
|
+
configurable: false,
|
|
317
|
+
writable: false,
|
|
318
|
+
});
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
defineSignalProperty(obj, name, view, sig, port, handle, state);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Recursively attach children
|
|
326
|
+
for (const [childName, instances] of Object.entries(hierarchy.children)) {
|
|
327
|
+
if (instances.length === 1) {
|
|
328
|
+
const childDut = createChildDut(buffer, instances[0]!, handle, state);
|
|
329
|
+
Object.defineProperty(obj, childName, {
|
|
330
|
+
value: childDut,
|
|
331
|
+
enumerable: true,
|
|
332
|
+
configurable: false,
|
|
333
|
+
writable: false,
|
|
334
|
+
});
|
|
335
|
+
} else if (instances.length > 1) {
|
|
336
|
+
const childDuts = instances.map((inst) =>
|
|
337
|
+
createChildDut(buffer, inst, handle, state),
|
|
338
|
+
);
|
|
339
|
+
Object.defineProperty(obj, childName, {
|
|
340
|
+
value: childDuts,
|
|
341
|
+
enumerable: true,
|
|
342
|
+
configurable: false,
|
|
343
|
+
writable: false,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return obj;
|
|
351
349
|
}
|
|
352
350
|
|
|
353
351
|
/** Define a single scalar signal property on the target object. */
|
|
354
352
|
function defineSignalProperty(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
353
|
+
target: object,
|
|
354
|
+
name: string,
|
|
355
|
+
view: DataView,
|
|
356
|
+
sig: SignalLayout,
|
|
357
|
+
port: PortInfo | undefined,
|
|
358
|
+
handle: NativeHandle,
|
|
359
|
+
state: DirtyState,
|
|
362
360
|
): void {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
361
|
+
const isOutput = port?.direction === "output";
|
|
362
|
+
const isInput = port?.direction === "input";
|
|
363
|
+
|
|
364
|
+
Object.defineProperty(target, name, {
|
|
365
|
+
get(): bigint {
|
|
366
|
+
// Output reads: lazy evalComb if dirty
|
|
367
|
+
if (state.dirty && !isInput) {
|
|
368
|
+
handle.evalComb();
|
|
369
|
+
state.dirty = false;
|
|
370
|
+
}
|
|
371
|
+
return readSignal(view, sig);
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
set(value: bigint | number | symbol | FourStateValue) {
|
|
375
|
+
if (isOutput) {
|
|
376
|
+
throw new Error(`Cannot write to output port '${name}'`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (value === Symbol.for("veryl:X")) {
|
|
380
|
+
if (!sig.is4state) {
|
|
381
|
+
throw new Error(`Port '${name}' is not 4-state; cannot assign X`);
|
|
382
|
+
}
|
|
383
|
+
writeAllX(view, sig);
|
|
384
|
+
} else if (isFourStateValue(value)) {
|
|
385
|
+
if (!sig.is4state) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Port '${name}' is not 4-state; cannot assign FourState`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
writeFourState(view, sig, value);
|
|
391
|
+
} else {
|
|
392
|
+
const bigVal =
|
|
393
|
+
typeof value === "bigint" ? value : BigInt(value as number);
|
|
394
|
+
writeSignal(view, sig, bigVal);
|
|
395
|
+
// Clear mask when writing a defined value to a 4-state signal
|
|
396
|
+
if (sig.is4state) {
|
|
397
|
+
const maskLayout: SignalLayout = {
|
|
398
|
+
offset: sig.offset + sig.byteSize,
|
|
399
|
+
width: sig.width,
|
|
400
|
+
byteSize: sig.byteSize,
|
|
401
|
+
is4state: false,
|
|
402
|
+
direction: sig.direction,
|
|
403
|
+
};
|
|
404
|
+
writeSignal(view, maskLayout, 0n);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
state.dirty = true;
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
enumerable: true,
|
|
412
|
+
configurable: false,
|
|
413
|
+
});
|
|
413
414
|
}
|
|
414
415
|
|
|
415
416
|
// ---------------------------------------------------------------------------
|
|
@@ -417,49 +418,57 @@ function defineSignalProperty(
|
|
|
417
418
|
// ---------------------------------------------------------------------------
|
|
418
419
|
|
|
419
420
|
function createNestedDut(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
421
|
+
view: DataView,
|
|
422
|
+
layout: Record<string, SignalLayout>,
|
|
423
|
+
members: Record<string, PortInfo>,
|
|
424
|
+
prefix: string,
|
|
425
|
+
handle: NativeHandle,
|
|
426
|
+
state: DirtyState,
|
|
426
427
|
): object {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
428
|
+
const obj = Object.create(null);
|
|
429
|
+
|
|
430
|
+
for (const [memberName, memberPort] of Object.entries(members)) {
|
|
431
|
+
const qualifiedName = `${prefix}.${memberName}`;
|
|
432
|
+
const sig = layout[qualifiedName];
|
|
433
|
+
if (!sig) continue;
|
|
434
|
+
|
|
435
|
+
if (memberPort.interface) {
|
|
436
|
+
const nested = createNestedDut(
|
|
437
|
+
view,
|
|
438
|
+
layout,
|
|
439
|
+
memberPort.interface,
|
|
440
|
+
qualifiedName,
|
|
441
|
+
handle,
|
|
442
|
+
state,
|
|
443
|
+
);
|
|
444
|
+
Object.defineProperty(obj, memberName, {
|
|
445
|
+
value: nested,
|
|
446
|
+
enumerable: true,
|
|
447
|
+
configurable: false,
|
|
448
|
+
writable: false,
|
|
449
|
+
});
|
|
450
|
+
} else if (memberPort.arrayDims && memberPort.arrayDims.length > 0) {
|
|
451
|
+
const arrayObj = createArrayDut(view, sig, memberPort, handle, state);
|
|
452
|
+
Object.defineProperty(obj, memberName, {
|
|
453
|
+
value: arrayObj,
|
|
454
|
+
enumerable: true,
|
|
455
|
+
configurable: false,
|
|
456
|
+
writable: false,
|
|
457
|
+
});
|
|
458
|
+
} else {
|
|
459
|
+
defineSignalProperty(
|
|
460
|
+
obj,
|
|
461
|
+
memberName,
|
|
462
|
+
view,
|
|
463
|
+
sig,
|
|
464
|
+
memberPort,
|
|
465
|
+
handle,
|
|
466
|
+
state,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return obj;
|
|
463
472
|
}
|
|
464
473
|
|
|
465
474
|
// ---------------------------------------------------------------------------
|
|
@@ -474,22 +483,22 @@ function createNestedDut(
|
|
|
474
483
|
* Used when elementWidth < 8 (sub-byte elements).
|
|
475
484
|
*/
|
|
476
485
|
function readBitPackedElement(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
486
|
+
view: DataView,
|
|
487
|
+
baseOffset: number,
|
|
488
|
+
elementWidth: number,
|
|
489
|
+
i: number,
|
|
481
490
|
): number {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
491
|
+
const bitStart = i * elementWidth;
|
|
492
|
+
const byteStart = baseOffset + (bitStart >> 3);
|
|
493
|
+
const bitShift = bitStart & 7;
|
|
494
|
+
const mask = (1 << elementWidth) - 1;
|
|
495
|
+
if (bitShift + elementWidth <= 8) {
|
|
496
|
+
return (view.getUint8(byteStart) >> bitShift) & mask;
|
|
497
|
+
}
|
|
498
|
+
// Spans two bytes
|
|
499
|
+
const lo = view.getUint8(byteStart);
|
|
500
|
+
const hi = view.getUint8(byteStart + 1);
|
|
501
|
+
return ((lo | (hi << 8)) >> bitShift) & mask;
|
|
493
502
|
}
|
|
494
503
|
|
|
495
504
|
/**
|
|
@@ -498,149 +507,192 @@ function readBitPackedElement(
|
|
|
498
507
|
* Used when elementWidth < 8 (sub-byte elements).
|
|
499
508
|
*/
|
|
500
509
|
function writeBitPackedElement(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
510
|
+
view: DataView,
|
|
511
|
+
baseOffset: number,
|
|
512
|
+
elementWidth: number,
|
|
513
|
+
i: number,
|
|
514
|
+
value: number,
|
|
506
515
|
): void {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
516
|
+
const bitStart = i * elementWidth;
|
|
517
|
+
const byteStart = baseOffset + (bitStart >> 3);
|
|
518
|
+
const bitShift = bitStart & 7;
|
|
519
|
+
const mask = (1 << elementWidth) - 1;
|
|
520
|
+
const maskedValue = value & mask;
|
|
521
|
+
|
|
522
|
+
const lo = view.getUint8(byteStart);
|
|
523
|
+
view.setUint8(
|
|
524
|
+
byteStart,
|
|
525
|
+
(lo & ~((mask << bitShift) & 0xff)) | ((maskedValue << bitShift) & 0xff),
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
if (bitShift + elementWidth > 8) {
|
|
529
|
+
const bitsInLo = 8 - bitShift;
|
|
530
|
+
const hi = view.getUint8(byteStart + 1);
|
|
531
|
+
const hiMask = mask >> bitsInLo;
|
|
532
|
+
view.setUint8(byteStart + 1, (hi & ~hiMask) | (maskedValue >> bitsInLo));
|
|
533
|
+
}
|
|
522
534
|
}
|
|
523
535
|
|
|
524
536
|
function createArrayDut(
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
537
|
+
view: DataView,
|
|
538
|
+
baseSig: SignalLayout,
|
|
539
|
+
port: PortInfo,
|
|
540
|
+
handle: NativeHandle,
|
|
541
|
+
state: DirtyState,
|
|
530
542
|
): object {
|
|
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
|
-
|
|
543
|
+
const dims = port.arrayDims!;
|
|
544
|
+
const elementWidth = port.width;
|
|
545
|
+
const totalElements = dims.reduce((a, b) => a * b, 1);
|
|
546
|
+
const isOutput = port.direction === "output";
|
|
547
|
+
const isInput = port.direction === "input";
|
|
548
|
+
const baseOffset = baseSig.offset;
|
|
549
|
+
const is4state = baseSig.is4state;
|
|
550
|
+
|
|
551
|
+
if (elementWidth < 8) {
|
|
552
|
+
// Sub-byte elements: the JIT stores them bit-packed.
|
|
553
|
+
// Element i occupies bits [i*W .. (i+1)*W - 1] starting at baseOffset.
|
|
554
|
+
// For 4-state signals the mask region starts immediately after the value bytes.
|
|
555
|
+
const totalValueBytes = Math.ceil((totalElements * elementWidth) / 8);
|
|
556
|
+
const maskBase = baseOffset + totalValueBytes;
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
length: totalElements,
|
|
560
|
+
|
|
561
|
+
at(i: number): bigint {
|
|
562
|
+
if (state.dirty && !isInput) {
|
|
563
|
+
handle.evalComb();
|
|
564
|
+
state.dirty = false;
|
|
565
|
+
}
|
|
566
|
+
return BigInt(readBitPackedElement(view, baseOffset, elementWidth, i));
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
set(i: number, value: bigint | number | symbol | FourStateValue): void {
|
|
570
|
+
if (isOutput) {
|
|
571
|
+
throw new Error("Cannot write to output array port");
|
|
572
|
+
}
|
|
573
|
+
if (value === Symbol.for("veryl:X")) {
|
|
574
|
+
if (!is4state) {
|
|
575
|
+
throw new Error("Array port is not 4-state; cannot assign X");
|
|
576
|
+
}
|
|
577
|
+
writeBitPackedElement(view, baseOffset, elementWidth, i, 0);
|
|
578
|
+
writeBitPackedElement(
|
|
579
|
+
view,
|
|
580
|
+
maskBase,
|
|
581
|
+
elementWidth,
|
|
582
|
+
i,
|
|
583
|
+
(1 << elementWidth) - 1,
|
|
584
|
+
);
|
|
585
|
+
} else if (isFourStateValue(value)) {
|
|
586
|
+
if (!is4state) {
|
|
587
|
+
throw new Error(
|
|
588
|
+
"Array port is not 4-state; cannot assign FourState",
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
writeBitPackedElement(
|
|
592
|
+
view,
|
|
593
|
+
baseOffset,
|
|
594
|
+
elementWidth,
|
|
595
|
+
i,
|
|
596
|
+
Number(value.value),
|
|
597
|
+
);
|
|
598
|
+
writeBitPackedElement(
|
|
599
|
+
view,
|
|
600
|
+
maskBase,
|
|
601
|
+
elementWidth,
|
|
602
|
+
i,
|
|
603
|
+
Number(value.mask),
|
|
604
|
+
);
|
|
605
|
+
} else {
|
|
606
|
+
const bigVal =
|
|
607
|
+
typeof value === "bigint" ? value : BigInt(value as number);
|
|
608
|
+
writeBitPackedElement(
|
|
609
|
+
view,
|
|
610
|
+
baseOffset,
|
|
611
|
+
elementWidth,
|
|
612
|
+
i,
|
|
613
|
+
Number(bigVal),
|
|
614
|
+
);
|
|
615
|
+
if (is4state) {
|
|
616
|
+
writeBitPackedElement(view, maskBase, elementWidth, i, 0);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
state.dirty = true;
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// elementWidth >= 8: byte-aligned stride.
|
|
625
|
+
const elementByteSize = Math.ceil(elementWidth / 8);
|
|
626
|
+
|
|
627
|
+
return {
|
|
628
|
+
length: totalElements,
|
|
629
|
+
|
|
630
|
+
at(i: number): bigint {
|
|
631
|
+
if (state.dirty && !isInput) {
|
|
632
|
+
handle.evalComb();
|
|
633
|
+
state.dirty = false;
|
|
634
|
+
}
|
|
635
|
+
const offset = baseOffset + i * elementByteSize;
|
|
636
|
+
if (elementWidth <= 53) {
|
|
637
|
+
return BigInt(readNumber(view, offset, elementWidth));
|
|
638
|
+
}
|
|
639
|
+
return readBigInt(view, offset, elementByteSize);
|
|
640
|
+
},
|
|
641
|
+
|
|
642
|
+
set(i: number, value: bigint | number | symbol | FourStateValue): void {
|
|
643
|
+
if (isOutput) {
|
|
644
|
+
throw new Error("Cannot write to output array port");
|
|
645
|
+
}
|
|
646
|
+
const offset = baseOffset + i * elementByteSize;
|
|
647
|
+
if (value === Symbol.for("veryl:X")) {
|
|
648
|
+
if (!is4state) {
|
|
649
|
+
throw new Error("Array port is not 4-state; cannot assign X");
|
|
650
|
+
}
|
|
651
|
+
const elemSig: SignalLayout = {
|
|
652
|
+
offset,
|
|
653
|
+
width: elementWidth,
|
|
654
|
+
byteSize: elementByteSize,
|
|
655
|
+
is4state,
|
|
656
|
+
direction: baseSig.direction,
|
|
657
|
+
};
|
|
658
|
+
writeAllX(view, elemSig);
|
|
659
|
+
} else if (isFourStateValue(value)) {
|
|
660
|
+
if (!is4state) {
|
|
661
|
+
throw new Error("Array port is not 4-state; cannot assign FourState");
|
|
662
|
+
}
|
|
663
|
+
const elemSig: SignalLayout = {
|
|
664
|
+
offset,
|
|
665
|
+
width: elementWidth,
|
|
666
|
+
byteSize: elementByteSize,
|
|
667
|
+
is4state,
|
|
668
|
+
direction: baseSig.direction,
|
|
669
|
+
};
|
|
670
|
+
writeFourState(view, elemSig, value);
|
|
671
|
+
} else {
|
|
672
|
+
const bigVal =
|
|
673
|
+
typeof value === "bigint" ? value : BigInt(value as number);
|
|
674
|
+
const elemSig: SignalLayout = {
|
|
675
|
+
offset,
|
|
676
|
+
width: elementWidth,
|
|
677
|
+
byteSize: elementByteSize,
|
|
678
|
+
is4state,
|
|
679
|
+
direction: baseSig.direction,
|
|
680
|
+
};
|
|
681
|
+
writeSignal(view, elemSig, bigVal);
|
|
682
|
+
if (is4state) {
|
|
683
|
+
const maskSig: SignalLayout = {
|
|
684
|
+
offset: offset + elementByteSize,
|
|
685
|
+
width: elementWidth,
|
|
686
|
+
byteSize: elementByteSize,
|
|
687
|
+
is4state: false,
|
|
688
|
+
direction: baseSig.direction,
|
|
689
|
+
};
|
|
690
|
+
writeSignal(view, maskSig, 0n);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
state.dirty = true;
|
|
694
|
+
},
|
|
695
|
+
};
|
|
644
696
|
}
|
|
645
697
|
|
|
646
698
|
// ---------------------------------------------------------------------------
|
|
@@ -652,21 +704,21 @@ function createArrayDut(
|
|
|
652
704
|
* Mask bits set to 1 indicate X.
|
|
653
705
|
*/
|
|
654
706
|
export function readFourState(
|
|
655
|
-
|
|
656
|
-
|
|
707
|
+
buffer: ArrayBuffer | SharedArrayBuffer,
|
|
708
|
+
sig: SignalLayout,
|
|
657
709
|
): [value: bigint, mask: bigint] {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
710
|
+
if (!sig.is4state) {
|
|
711
|
+
throw new Error("Signal is not 4-state");
|
|
712
|
+
}
|
|
713
|
+
const view = new DataView(buffer);
|
|
714
|
+
const value = readSignal(view, sig);
|
|
715
|
+
const maskSig: SignalLayout = {
|
|
716
|
+
offset: sig.offset + sig.byteSize,
|
|
717
|
+
width: sig.width,
|
|
718
|
+
byteSize: sig.byteSize,
|
|
719
|
+
is4state: false,
|
|
720
|
+
direction: sig.direction,
|
|
721
|
+
};
|
|
722
|
+
const mask = readSignal(view, maskSig);
|
|
723
|
+
return [value, mask];
|
|
672
724
|
}
|