@celox-sim/celox 0.1.11 → 0.1.13
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.map +1 -1
- package/dist/dut.js +106 -1
- package/dist/dut.js.map +1 -1
- package/dist/napi-helpers.d.ts +5 -0
- package/dist/napi-helpers.d.ts.map +1 -1
- package/dist/napi-helpers.js +34 -2
- package/dist/napi-helpers.js.map +1 -1
- package/dist/simulation.d.ts +1 -0
- package/dist/simulation.d.ts.map +1 -1
- package/dist/simulation.js +6 -3
- package/dist/simulation.js.map +1 -1
- package/package.json +2 -2
- package/src/dut.test.ts +271 -0
- package/src/dut.ts +131 -1
- package/src/e2e.bench.ts +314 -0
- package/src/e2e.test.ts +379 -3
- package/src/napi-helpers.ts +37 -2
- package/src/simulation.ts +6 -2
package/src/dut.test.ts
CHANGED
|
@@ -477,12 +477,283 @@ describe("createDut — array ports", () => {
|
|
|
477
477
|
expect(dut.data.at(3)).toBe(0xDDn);
|
|
478
478
|
expect(dut.data.length).toBe(4);
|
|
479
479
|
});
|
|
480
|
+
|
|
481
|
+
test("1-bit elements (logic [N]): bit-packed read/write", () => {
|
|
482
|
+
// logic[4]: 4 elements of 1 bit each = 1 byte total in native memory
|
|
483
|
+
const buffer = makeBuffer(64);
|
|
484
|
+
const layout: Record<string, SignalLayout> = {
|
|
485
|
+
push: { offset: 0, width: 1, byteSize: 1, is4state: false, direction: "input" },
|
|
486
|
+
};
|
|
487
|
+
const ports: Record<string, PortInfo> = {
|
|
488
|
+
push: { direction: "input", type: "logic", width: 1, arrayDims: [4] },
|
|
489
|
+
};
|
|
490
|
+
const handle = mockHandle();
|
|
491
|
+
const state: DirtyState = { dirty: false };
|
|
492
|
+
|
|
493
|
+
const dut = createDut<{ push: { at(i: number): bigint; set(i: number, v: bigint): void; length: number } }>(
|
|
494
|
+
buffer, layout, ports, handle, state,
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
dut.push.set(0, 1n);
|
|
498
|
+
dut.push.set(1, 1n);
|
|
499
|
+
dut.push.set(2, 0n);
|
|
500
|
+
dut.push.set(3, 1n);
|
|
501
|
+
|
|
502
|
+
expect(dut.push.at(0)).toBe(1n);
|
|
503
|
+
expect(dut.push.at(1)).toBe(1n);
|
|
504
|
+
expect(dut.push.at(2)).toBe(0n);
|
|
505
|
+
expect(dut.push.at(3)).toBe(1n);
|
|
506
|
+
expect(dut.push.length).toBe(4);
|
|
507
|
+
|
|
508
|
+
// Verify native layout: all 4 elements packed in byte 0
|
|
509
|
+
const view = new DataView(buffer);
|
|
510
|
+
expect(view.getUint8(0)).toBe(0b1011); // bits 0,1,3 set = 0x0B
|
|
511
|
+
expect(view.getUint8(1)).toBe(0); // no overflow into byte 1
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test("1-bit elements: writes don't corrupt adjacent elements", () => {
|
|
515
|
+
const buffer = makeBuffer(64);
|
|
516
|
+
const layout: Record<string, SignalLayout> = {
|
|
517
|
+
bits: { offset: 0, width: 1, byteSize: 1, is4state: false, direction: "input" },
|
|
518
|
+
};
|
|
519
|
+
const ports: Record<string, PortInfo> = {
|
|
520
|
+
bits: { direction: "input", type: "logic", width: 1, arrayDims: [8] },
|
|
521
|
+
};
|
|
522
|
+
const handle = mockHandle();
|
|
523
|
+
const state: DirtyState = { dirty: false };
|
|
524
|
+
|
|
525
|
+
const dut = createDut<{ bits: { at(i: number): bigint; set(i: number, v: bigint): void } }>(
|
|
526
|
+
buffer, layout, ports, handle, state,
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
// Set element 0; verify element 1 unaffected
|
|
530
|
+
dut.bits.set(0, 1n);
|
|
531
|
+
expect(dut.bits.at(0)).toBe(1n);
|
|
532
|
+
dut.bits.set(1, 1n);
|
|
533
|
+
expect(dut.bits.at(0)).toBe(1n); // element 0 must not be corrupted
|
|
534
|
+
expect(dut.bits.at(1)).toBe(1n);
|
|
535
|
+
|
|
536
|
+
// Clear element 0; element 1 must remain set
|
|
537
|
+
dut.bits.set(0, 0n);
|
|
538
|
+
expect(dut.bits.at(0)).toBe(0n);
|
|
539
|
+
expect(dut.bits.at(1)).toBe(1n);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test("1-bit elements: 8 elements span exactly one byte", () => {
|
|
543
|
+
const buffer = makeBuffer(64);
|
|
544
|
+
const layout: Record<string, SignalLayout> = {
|
|
545
|
+
bits: { offset: 2, width: 1, byteSize: 1, is4state: false, direction: "input" },
|
|
546
|
+
};
|
|
547
|
+
const ports: Record<string, PortInfo> = {
|
|
548
|
+
bits: { direction: "input", type: "logic", width: 1, arrayDims: [8] },
|
|
549
|
+
};
|
|
550
|
+
const handle = mockHandle();
|
|
551
|
+
const state: DirtyState = { dirty: false };
|
|
552
|
+
|
|
553
|
+
const dut = createDut<{ bits: { at(i: number): bigint; set(i: number, v: bigint): void } }>(
|
|
554
|
+
buffer, layout, ports, handle, state,
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
// Set all 8 bits
|
|
558
|
+
for (let i = 0; i < 8; i++) dut.bits.set(i, 1n);
|
|
559
|
+
const view = new DataView(buffer);
|
|
560
|
+
expect(view.getUint8(2)).toBe(0xff);
|
|
561
|
+
|
|
562
|
+
// Clear alternating bits
|
|
563
|
+
for (let i = 0; i < 8; i += 2) dut.bits.set(i, 0n);
|
|
564
|
+
expect(view.getUint8(2)).toBe(0b10101010);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test("elementWidth=3: elements spanning byte boundaries read/write correctly", () => {
|
|
568
|
+
// logic<3>[6]: 6 × 3 = 18 bits = 3 bytes
|
|
569
|
+
// Element 2: bitStart=6, spans bytes 0→1 ← cross-byte write/read path
|
|
570
|
+
// Element 5: bitStart=15, spans bytes 1→2 ← cross-byte write/read path
|
|
571
|
+
const buffer = makeBuffer(64);
|
|
572
|
+
const layout: Record<string, SignalLayout> = {
|
|
573
|
+
data: { offset: 0, width: 3, byteSize: 1, is4state: false, direction: "input" },
|
|
574
|
+
};
|
|
575
|
+
const ports: Record<string, PortInfo> = {
|
|
576
|
+
data: { direction: "input", type: "logic", width: 3, arrayDims: [6] },
|
|
577
|
+
};
|
|
578
|
+
const handle = mockHandle();
|
|
579
|
+
const state: DirtyState = { dirty: false };
|
|
580
|
+
|
|
581
|
+
const dut = createDut<{ data: { at(i: number): bigint; set(i: number, v: bigint): void; length: number } }>(
|
|
582
|
+
buffer, layout, ports, handle, state,
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
// Set all 6 elements to distinct values 1..6
|
|
586
|
+
for (let i = 0; i < 6; i++) dut.data.set(i, BigInt(i + 1));
|
|
587
|
+
for (let i = 0; i < 6; i++) expect(dut.data.at(i)).toBe(BigInt(i + 1));
|
|
588
|
+
|
|
589
|
+
// Overwrite the two cross-byte elements and verify neighbours are unaffected
|
|
590
|
+
dut.data.set(2, 0b101n); // bitStart=6, crosses byte 0→1
|
|
591
|
+
expect(dut.data.at(2)).toBe(5n);
|
|
592
|
+
expect(dut.data.at(1)).toBe(2n); // neighbour below
|
|
593
|
+
expect(dut.data.at(3)).toBe(4n); // neighbour above
|
|
594
|
+
|
|
595
|
+
dut.data.set(5, 0b110n); // bitStart=15, crosses byte 1→2
|
|
596
|
+
expect(dut.data.at(5)).toBe(6n);
|
|
597
|
+
expect(dut.data.at(4)).toBe(5n); // neighbour below
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
test("1-bit 4-state: X writes value=0 and mask=1 at correct byte offset", () => {
|
|
601
|
+
// logic[8] with 4-state: value in byte 0 (bits 0-7 = elements 0-7),
|
|
602
|
+
// mask in byte 1 (bits 0-7 = masks for elements 0-7).
|
|
603
|
+
// totalValueBytes = ceil(8*1/8) = 1, so maskBase = offset + 1.
|
|
604
|
+
const buffer = makeBuffer(64);
|
|
605
|
+
const layout: Record<string, SignalLayout> = {
|
|
606
|
+
bits: { offset: 0, width: 1, byteSize: 1, is4state: true, direction: "input" },
|
|
607
|
+
};
|
|
608
|
+
const ports: Record<string, PortInfo> = {
|
|
609
|
+
bits: { direction: "input", type: "logic", width: 1, arrayDims: [8], is4state: true },
|
|
610
|
+
};
|
|
611
|
+
const handle = mockHandle();
|
|
612
|
+
const state: DirtyState = { dirty: false };
|
|
613
|
+
|
|
614
|
+
const dut = createDut<{ bits: { at(i: number): bigint; set(i: number, v: unknown): void } }>(
|
|
615
|
+
buffer, layout, ports, handle, state,
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
const view = new DataView(buffer);
|
|
619
|
+
|
|
620
|
+
// Assign X to element 3: value bit 3 → 0, mask bit 3 → 1
|
|
621
|
+
dut.bits.set(3, X);
|
|
622
|
+
expect(view.getUint8(0) & 0b00001000).toBe(0); // value: bit 3 = 0
|
|
623
|
+
expect(view.getUint8(1) & 0b00001000).toBe(0b00001000); // mask: bit 3 = 1
|
|
624
|
+
|
|
625
|
+
// Write a defined 1 to element 3: value bit 3 → 1, mask bit 3 → 0
|
|
626
|
+
dut.bits.set(3, 1n);
|
|
627
|
+
expect(view.getUint8(0) & 0b00001000).toBe(0b00001000); // value: bit 3 = 1
|
|
628
|
+
expect(view.getUint8(1) & 0b00001000).toBe(0); // mask: bit 3 cleared
|
|
629
|
+
|
|
630
|
+
// Setting elements 0 and 7 to X should only affect those mask bits
|
|
631
|
+
dut.bits.set(0, X);
|
|
632
|
+
dut.bits.set(7, X);
|
|
633
|
+
expect(view.getUint8(1)).toBe(0b10000001); // mask: bits 0 and 7 set
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test("3-bit 4-state: mask region starts at ceil(totalBits/8) bytes after value", () => {
|
|
637
|
+
// logic<3>[5]: 5 × 3 = 15 bits → totalValueBytes = ceil(15/8) = 2
|
|
638
|
+
// value occupies bytes 0-1, mask occupies bytes 2-3; maskBase = offset + 2
|
|
639
|
+
const buffer = makeBuffer(64);
|
|
640
|
+
const layout: Record<string, SignalLayout> = {
|
|
641
|
+
data: { offset: 0, width: 3, byteSize: 1, is4state: true, direction: "input" },
|
|
642
|
+
};
|
|
643
|
+
const ports: Record<string, PortInfo> = {
|
|
644
|
+
data: { direction: "input", type: "logic", width: 3, arrayDims: [5], is4state: true },
|
|
645
|
+
};
|
|
646
|
+
const handle = mockHandle();
|
|
647
|
+
const state: DirtyState = { dirty: false };
|
|
648
|
+
|
|
649
|
+
const dut = createDut<{ data: { set(i: number, v: unknown): void } }>(
|
|
650
|
+
buffer, layout, ports, handle, state,
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
const view = new DataView(buffer);
|
|
654
|
+
|
|
655
|
+
// Assign X to element 0 (bitStart=0, no cross-byte): value bits 0-2 = 0, mask bits 0-2 = 0b111
|
|
656
|
+
dut.data.set(0, X);
|
|
657
|
+
expect(view.getUint8(0) & 0b111).toBe(0); // value byte 0: bits 0-2 cleared
|
|
658
|
+
expect(view.getUint8(2) & 0b111).toBe(0b111); // mask byte 2: bits 0-2 set
|
|
659
|
+
expect(view.getUint8(1)).toBe(0); // value byte 1: untouched
|
|
660
|
+
expect(view.getUint8(3)).toBe(0); // mask byte 3: untouched
|
|
661
|
+
|
|
662
|
+
// Write a defined value to element 0, verify mask cleared
|
|
663
|
+
dut.data.set(0, 0b110n);
|
|
664
|
+
expect(view.getUint8(0) & 0b111).toBe(0b110); // value bits 0-2 = 6
|
|
665
|
+
expect(view.getUint8(2) & 0b111).toBe(0); // mask bits 0-2 cleared
|
|
666
|
+
});
|
|
480
667
|
});
|
|
481
668
|
|
|
482
669
|
// ---------------------------------------------------------------------------
|
|
483
670
|
// Interface (nested) ports
|
|
484
671
|
// ---------------------------------------------------------------------------
|
|
485
672
|
|
|
673
|
+
describe("createDut — interface ports with array members", () => {
|
|
674
|
+
test("interface member with arrayDims uses array accessor", () => {
|
|
675
|
+
// Interface array pattern: bus: modport Bus::consumer [2]
|
|
676
|
+
// Veryl expands to bus.data with arrayDims:[2], bus.valid with arrayDims:[2]
|
|
677
|
+
const buffer = makeBuffer(64);
|
|
678
|
+
const layout: Record<string, SignalLayout> = {
|
|
679
|
+
"bus.data": { offset: 0, width: 8, byteSize: 2, is4state: false, direction: "input" },
|
|
680
|
+
"bus.valid": { offset: 2, width: 1, byteSize: 1, is4state: false, direction: "input" },
|
|
681
|
+
};
|
|
682
|
+
const ports: Record<string, PortInfo> = {
|
|
683
|
+
bus: {
|
|
684
|
+
direction: "inout",
|
|
685
|
+
type: "logic",
|
|
686
|
+
width: 0,
|
|
687
|
+
interface: {
|
|
688
|
+
data: { direction: "input", type: "logic", width: 8, arrayDims: [2] },
|
|
689
|
+
valid: { direction: "input", type: "logic", width: 1, arrayDims: [2] },
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
const handle = mockHandle();
|
|
694
|
+
const state: DirtyState = { dirty: false };
|
|
695
|
+
|
|
696
|
+
const dut = createDut<{
|
|
697
|
+
bus: {
|
|
698
|
+
data: { at(i: number): bigint; set(i: number, v: bigint): void; length: number };
|
|
699
|
+
valid: { at(i: number): bigint; set(i: number, v: bigint): void; length: number };
|
|
700
|
+
};
|
|
701
|
+
}>(buffer, layout, ports, handle, state);
|
|
702
|
+
|
|
703
|
+
// data: 2 elements of 8 bits each
|
|
704
|
+
dut.bus.data.set(0, 0xAAn);
|
|
705
|
+
dut.bus.data.set(1, 0xBBn);
|
|
706
|
+
expect(dut.bus.data.at(0)).toBe(0xAAn);
|
|
707
|
+
expect(dut.bus.data.at(1)).toBe(0xBBn);
|
|
708
|
+
expect(dut.bus.data.length).toBe(2);
|
|
709
|
+
|
|
710
|
+
// valid: 2 elements of 1 bit each (bit-packed)
|
|
711
|
+
dut.bus.valid.set(0, 1n);
|
|
712
|
+
dut.bus.valid.set(1, 0n);
|
|
713
|
+
expect(dut.bus.valid.at(0)).toBe(1n);
|
|
714
|
+
expect(dut.bus.valid.at(1)).toBe(0n);
|
|
715
|
+
expect(dut.bus.valid.length).toBe(2);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
test("interface with mixed scalar and array members", () => {
|
|
719
|
+
const buffer = makeBuffer(64);
|
|
720
|
+
const layout: Record<string, SignalLayout> = {
|
|
721
|
+
"bus.data": { offset: 0, width: 8, byteSize: 2, is4state: false, direction: "input" },
|
|
722
|
+
"bus.valid": { offset: 4, width: 1, byteSize: 1, is4state: false, direction: "input" },
|
|
723
|
+
};
|
|
724
|
+
const ports: Record<string, PortInfo> = {
|
|
725
|
+
bus: {
|
|
726
|
+
direction: "inout",
|
|
727
|
+
type: "logic",
|
|
728
|
+
width: 0,
|
|
729
|
+
interface: {
|
|
730
|
+
data: { direction: "input", type: "logic", width: 8, arrayDims: [2] },
|
|
731
|
+
valid: { direction: "input", type: "logic", width: 1 },
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
};
|
|
735
|
+
const handle = mockHandle();
|
|
736
|
+
const state: DirtyState = { dirty: false };
|
|
737
|
+
|
|
738
|
+
const dut = createDut<{
|
|
739
|
+
bus: {
|
|
740
|
+
data: { at(i: number): bigint; set(i: number, v: bigint): void; length: number };
|
|
741
|
+
valid: bigint;
|
|
742
|
+
};
|
|
743
|
+
}>(buffer, layout, ports, handle, state);
|
|
744
|
+
|
|
745
|
+
// Array member
|
|
746
|
+
dut.bus.data.set(0, 0x11n);
|
|
747
|
+
dut.bus.data.set(1, 0x22n);
|
|
748
|
+
expect(dut.bus.data.at(0)).toBe(0x11n);
|
|
749
|
+
expect(dut.bus.data.at(1)).toBe(0x22n);
|
|
750
|
+
|
|
751
|
+
// Scalar member
|
|
752
|
+
dut.bus.valid = 1n;
|
|
753
|
+
expect(dut.bus.valid).toBe(1n);
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
|
|
486
757
|
describe("createDut — interface ports", () => {
|
|
487
758
|
test("nested interface members", () => {
|
|
488
759
|
const buffer = makeBuffer(64);
|
package/src/dut.ts
CHANGED
|
@@ -288,6 +288,25 @@ export function createChildDut(
|
|
|
288
288
|
for (const [name, port] of Object.entries(hierarchy.ports)) {
|
|
289
289
|
if (port.type === "clock") continue;
|
|
290
290
|
|
|
291
|
+
// Handle nested interface port
|
|
292
|
+
if (port.interface) {
|
|
293
|
+
const nestedObj = createNestedDut(
|
|
294
|
+
view,
|
|
295
|
+
hierarchy.forDut,
|
|
296
|
+
port.interface,
|
|
297
|
+
name,
|
|
298
|
+
handle,
|
|
299
|
+
state,
|
|
300
|
+
);
|
|
301
|
+
Object.defineProperty(obj, name, {
|
|
302
|
+
value: nestedObj,
|
|
303
|
+
enumerable: true,
|
|
304
|
+
configurable: false,
|
|
305
|
+
writable: false,
|
|
306
|
+
});
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
291
310
|
const sig = hierarchy.forDut[name];
|
|
292
311
|
if (!sig) continue;
|
|
293
312
|
|
|
@@ -427,6 +446,14 @@ function createNestedDut(
|
|
|
427
446
|
configurable: false,
|
|
428
447
|
writable: false,
|
|
429
448
|
});
|
|
449
|
+
} else if (memberPort.arrayDims && memberPort.arrayDims.length > 0) {
|
|
450
|
+
const arrayObj = createArrayDut(view, sig, memberPort, handle, state);
|
|
451
|
+
Object.defineProperty(obj, memberName, {
|
|
452
|
+
value: arrayObj,
|
|
453
|
+
enumerable: true,
|
|
454
|
+
configurable: false,
|
|
455
|
+
writable: false,
|
|
456
|
+
});
|
|
430
457
|
} else {
|
|
431
458
|
defineSignalProperty(obj, memberName, view, sig, memberPort, handle, state);
|
|
432
459
|
}
|
|
@@ -439,6 +466,61 @@ function createNestedDut(
|
|
|
439
466
|
// Array port accessor
|
|
440
467
|
// ---------------------------------------------------------------------------
|
|
441
468
|
|
|
469
|
+
/**
|
|
470
|
+
* Read a single element from a bit-packed byte region.
|
|
471
|
+
*
|
|
472
|
+
* The JIT stores array elements contiguously at the bit level:
|
|
473
|
+
* element i occupies bits [i*W .. (i+1)*W - 1] starting at baseOffset.
|
|
474
|
+
* Used when elementWidth < 8 (sub-byte elements).
|
|
475
|
+
*/
|
|
476
|
+
function readBitPackedElement(
|
|
477
|
+
view: DataView,
|
|
478
|
+
baseOffset: number,
|
|
479
|
+
elementWidth: number,
|
|
480
|
+
i: number,
|
|
481
|
+
): number {
|
|
482
|
+
const bitStart = i * elementWidth;
|
|
483
|
+
const byteStart = baseOffset + (bitStart >> 3);
|
|
484
|
+
const bitShift = bitStart & 7;
|
|
485
|
+
const mask = (1 << elementWidth) - 1;
|
|
486
|
+
if (bitShift + elementWidth <= 8) {
|
|
487
|
+
return (view.getUint8(byteStart) >> bitShift) & mask;
|
|
488
|
+
}
|
|
489
|
+
// Spans two bytes
|
|
490
|
+
const lo = view.getUint8(byteStart);
|
|
491
|
+
const hi = view.getUint8(byteStart + 1);
|
|
492
|
+
return ((lo | (hi << 8)) >> bitShift) & mask;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Write a single element into a bit-packed byte region (read-modify-write).
|
|
497
|
+
*
|
|
498
|
+
* Used when elementWidth < 8 (sub-byte elements).
|
|
499
|
+
*/
|
|
500
|
+
function writeBitPackedElement(
|
|
501
|
+
view: DataView,
|
|
502
|
+
baseOffset: number,
|
|
503
|
+
elementWidth: number,
|
|
504
|
+
i: number,
|
|
505
|
+
value: number,
|
|
506
|
+
): void {
|
|
507
|
+
const bitStart = i * elementWidth;
|
|
508
|
+
const byteStart = baseOffset + (bitStart >> 3);
|
|
509
|
+
const bitShift = bitStart & 7;
|
|
510
|
+
const mask = (1 << elementWidth) - 1;
|
|
511
|
+
const maskedValue = value & mask;
|
|
512
|
+
|
|
513
|
+
const lo = view.getUint8(byteStart);
|
|
514
|
+
view.setUint8(byteStart, (lo & ~((mask << bitShift) & 0xff)) | ((maskedValue << bitShift) & 0xff));
|
|
515
|
+
|
|
516
|
+
if (bitShift + elementWidth > 8) {
|
|
517
|
+
const bitsInLo = 8 - bitShift;
|
|
518
|
+
const hi = view.getUint8(byteStart + 1);
|
|
519
|
+
const hiMask = mask >> bitsInLo;
|
|
520
|
+
view.setUint8(byteStart + 1, (hi & ~hiMask) | (maskedValue >> bitsInLo));
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
442
524
|
function createArrayDut(
|
|
443
525
|
view: DataView,
|
|
444
526
|
baseSig: SignalLayout,
|
|
@@ -448,13 +530,61 @@ function createArrayDut(
|
|
|
448
530
|
): object {
|
|
449
531
|
const dims = port.arrayDims!;
|
|
450
532
|
const elementWidth = port.width;
|
|
451
|
-
const elementByteSize = Math.ceil(elementWidth / 8);
|
|
452
533
|
const totalElements = dims.reduce((a, b) => a * b, 1);
|
|
453
534
|
const isOutput = port.direction === "output";
|
|
454
535
|
const isInput = port.direction === "input";
|
|
455
536
|
const baseOffset = baseSig.offset;
|
|
456
537
|
const is4state = baseSig.is4state;
|
|
457
538
|
|
|
539
|
+
if (elementWidth < 8) {
|
|
540
|
+
// Sub-byte elements: the JIT stores them bit-packed.
|
|
541
|
+
// Element i occupies bits [i*W .. (i+1)*W - 1] starting at baseOffset.
|
|
542
|
+
// For 4-state signals the mask region starts immediately after the value bytes.
|
|
543
|
+
const totalValueBytes = Math.ceil(totalElements * elementWidth / 8);
|
|
544
|
+
const maskBase = baseOffset + totalValueBytes;
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
length: totalElements,
|
|
548
|
+
|
|
549
|
+
at(i: number): bigint {
|
|
550
|
+
if (state.dirty && !isInput) {
|
|
551
|
+
handle.evalComb();
|
|
552
|
+
state.dirty = false;
|
|
553
|
+
}
|
|
554
|
+
return BigInt(readBitPackedElement(view, baseOffset, elementWidth, i));
|
|
555
|
+
},
|
|
556
|
+
|
|
557
|
+
set(i: number, value: bigint | number | symbol | FourStateValue): void {
|
|
558
|
+
if (isOutput) {
|
|
559
|
+
throw new Error("Cannot write to output array port");
|
|
560
|
+
}
|
|
561
|
+
if (value === Symbol.for("veryl:X")) {
|
|
562
|
+
if (!is4state) {
|
|
563
|
+
throw new Error("Array port is not 4-state; cannot assign X");
|
|
564
|
+
}
|
|
565
|
+
writeBitPackedElement(view, baseOffset, elementWidth, i, 0);
|
|
566
|
+
writeBitPackedElement(view, maskBase, elementWidth, i, (1 << elementWidth) - 1);
|
|
567
|
+
} else if (isFourStateValue(value)) {
|
|
568
|
+
if (!is4state) {
|
|
569
|
+
throw new Error("Array port is not 4-state; cannot assign FourState");
|
|
570
|
+
}
|
|
571
|
+
writeBitPackedElement(view, baseOffset, elementWidth, i, Number(value.value));
|
|
572
|
+
writeBitPackedElement(view, maskBase, elementWidth, i, Number(value.mask));
|
|
573
|
+
} else {
|
|
574
|
+
const bigVal = typeof value === "bigint" ? value : BigInt(value as number);
|
|
575
|
+
writeBitPackedElement(view, baseOffset, elementWidth, i, Number(bigVal));
|
|
576
|
+
if (is4state) {
|
|
577
|
+
writeBitPackedElement(view, maskBase, elementWidth, i, 0);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
state.dirty = true;
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// elementWidth >= 8: byte-aligned stride.
|
|
586
|
+
const elementByteSize = Math.ceil(elementWidth / 8);
|
|
587
|
+
|
|
458
588
|
return {
|
|
459
589
|
length: totalElements,
|
|
460
590
|
|