@celox-sim/celox 0.1.10 → 0.1.12
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 +98 -2
- 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/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/dut.test.ts +187 -0
- package/src/dut.ts +124 -2
- package/src/e2e.test.ts +371 -2
- package/src/napi-helpers.ts +37 -2
- package/src/simulation.ts +6 -2
- package/src/types.ts +1 -1
package/src/e2e.test.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
const FIXTURES_DIR = path.resolve(import.meta.dirname ?? __dirname, "../fixtures");
|
|
28
28
|
const ADDER_PROJECT = path.join(FIXTURES_DIR, "adder");
|
|
29
29
|
const COUNTER_PROJECT = path.join(FIXTURES_DIR, "counter_project");
|
|
30
|
+
const CELOX_TOML_PROJECT = path.join(FIXTURES_DIR, "celox_toml");
|
|
30
31
|
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
33
|
// Test Veryl sources
|
|
@@ -496,9 +497,9 @@ module InitTest (
|
|
|
496
497
|
const layout = parseNapiLayout(raw.layoutJson);
|
|
497
498
|
const buf = raw.sharedMemory().buffer;
|
|
498
499
|
|
|
499
|
-
// logic port should have mask=0xFF (all X)
|
|
500
|
+
// logic port should have mask=0xFF (all X), X encoding: v=1, m=1
|
|
500
501
|
const [valA, maskA] = readFourState(buf, layout.forDut.a);
|
|
501
|
-
expect(valA).toBe(
|
|
502
|
+
expect(valA).toBe(0xFFn);
|
|
502
503
|
expect(maskA).toBe(0xFFn);
|
|
503
504
|
|
|
504
505
|
// bit port should have mask=0 (defined)
|
|
@@ -811,6 +812,11 @@ describe("E2E: 4-state high-level DUT API", () => {
|
|
|
811
812
|
// In 4-state mode, count starts as X. Reset should clear it.
|
|
812
813
|
// (default async_low: rst=0 is active)
|
|
813
814
|
sim.dut.rst = 0n;
|
|
815
|
+
// Clear X on en before deactivating reset. In 4-state mode, en starts
|
|
816
|
+
// as X (v=1, m=1). The FF branch condition currently uses only the value
|
|
817
|
+
// bit, so X with v=1 is treated as "true" — a known limitation in FF
|
|
818
|
+
// conditional handling (combinational mux handles X correctly).
|
|
819
|
+
sim.dut.en = 0n;
|
|
814
820
|
sim.tick();
|
|
815
821
|
sim.dut.rst = 1n;
|
|
816
822
|
sim.tick();
|
|
@@ -1654,3 +1660,366 @@ describe("E2E: parameter override — DUT correctness", () => {
|
|
|
1654
1660
|
sim.dispose();
|
|
1655
1661
|
});
|
|
1656
1662
|
});
|
|
1663
|
+
|
|
1664
|
+
// ---------------------------------------------------------------------------
|
|
1665
|
+
// celox.toml: test-only source directories
|
|
1666
|
+
// ---------------------------------------------------------------------------
|
|
1667
|
+
|
|
1668
|
+
describe("E2E: celox.toml test sources", () => {
|
|
1669
|
+
test("fromProject loads test-only module declared in celox.toml", () => {
|
|
1670
|
+
interface RegPorts {
|
|
1671
|
+
rst: bigint;
|
|
1672
|
+
d: bigint;
|
|
1673
|
+
readonly q: bigint;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
// `Reg` lives in test_veryl/ — not in Veryl.toml sources — but celox.toml
|
|
1677
|
+
// declares test_veryl/ as an additional test source directory.
|
|
1678
|
+
const sim = Simulator.fromProject<RegPorts>(CELOX_TOML_PROJECT, "Reg");
|
|
1679
|
+
|
|
1680
|
+
// Deassert async_low reset (rst=0 is active; rst=1 is normal operation)
|
|
1681
|
+
sim.dut.rst = 1n;
|
|
1682
|
+
sim.dut.d = 0xABn;
|
|
1683
|
+
sim.tick();
|
|
1684
|
+
expect(sim.dut.q).toBe(0xABn);
|
|
1685
|
+
|
|
1686
|
+
sim.dut.d = 0xCDn;
|
|
1687
|
+
expect(sim.dut.q).toBe(0xABn); // not yet clocked
|
|
1688
|
+
sim.tick();
|
|
1689
|
+
expect(sim.dut.q).toBe(0xCDn);
|
|
1690
|
+
|
|
1691
|
+
sim.dispose();
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
test("genTs includes test-only module declared in celox.toml", () => {
|
|
1695
|
+
const addon = loadNativeAddon();
|
|
1696
|
+
const json = addon.genTs(CELOX_TOML_PROJECT);
|
|
1697
|
+
const output = JSON.parse(json) as { modules: Array<{ moduleName: string }> };
|
|
1698
|
+
const names = output.modules.map((m) => m.moduleName);
|
|
1699
|
+
// Both the regular source (Adder) and the test-only source (Reg) must appear
|
|
1700
|
+
expect(names).toContain("Adder");
|
|
1701
|
+
expect(names).toContain("Reg");
|
|
1702
|
+
});
|
|
1703
|
+
|
|
1704
|
+
test("[simulation] max_steps from celox.toml is used as waitUntil default", () => {
|
|
1705
|
+
// celox.toml sets [simulation] max_steps = 20; waitUntil should honour it
|
|
1706
|
+
// when no per-call maxSteps is provided.
|
|
1707
|
+
const sim = Simulation.fromProject(CELOX_TOML_PROJECT, "Reg");
|
|
1708
|
+
sim.addClock("clk", { period: 10 });
|
|
1709
|
+
expect(() => sim.waitUntil(() => false)).toThrow(SimulationTimeoutError);
|
|
1710
|
+
const err = (() => {
|
|
1711
|
+
try { sim.waitUntil(() => false); } catch (e) { return e; }
|
|
1712
|
+
})() as SimulationTimeoutError;
|
|
1713
|
+
expect(err.steps).toBe(20);
|
|
1714
|
+
sim.dispose();
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
test("[simulation] max_steps per-call override takes precedence over celox.toml", () => {
|
|
1718
|
+
const sim = Simulation.fromProject(CELOX_TOML_PROJECT, "Reg");
|
|
1719
|
+
sim.addClock("clk", { period: 10 });
|
|
1720
|
+
// Per-call maxSteps=5 is lower than the toml default of 20
|
|
1721
|
+
expect(() => sim.waitUntil(() => false, { maxSteps: 5 })).toThrow(SimulationTimeoutError);
|
|
1722
|
+
const err = (() => {
|
|
1723
|
+
try { sim.waitUntil(() => false, { maxSteps: 5 }); } catch (e) { return e; }
|
|
1724
|
+
})() as SimulationTimeoutError;
|
|
1725
|
+
expect(err.steps).toBe(5);
|
|
1726
|
+
sim.dispose();
|
|
1727
|
+
});
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
// ---------------------------------------------------------------------------
|
|
1731
|
+
// Interface port DUT accessor tests
|
|
1732
|
+
// ---------------------------------------------------------------------------
|
|
1733
|
+
|
|
1734
|
+
// A module whose top-level port is a modport interface.
|
|
1735
|
+
// After Veryl expansion "sig.data" and "sig.valid" become individual ports.
|
|
1736
|
+
// buildPortsFromLayout must group them into a nested PortInfo so that the
|
|
1737
|
+
// DUT exposes them as dut.sig.data / dut.sig.valid.
|
|
1738
|
+
const INTERFACE_PORT_SOURCE = `
|
|
1739
|
+
interface Signal {
|
|
1740
|
+
var data: logic<8>;
|
|
1741
|
+
var valid: logic;
|
|
1742
|
+
modport src {
|
|
1743
|
+
data: output,
|
|
1744
|
+
valid: output,
|
|
1745
|
+
}
|
|
1746
|
+
modport dst {
|
|
1747
|
+
data: input,
|
|
1748
|
+
valid: input,
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
module Top (
|
|
1753
|
+
sig: modport Signal::dst,
|
|
1754
|
+
out: output logic<8>,
|
|
1755
|
+
) {
|
|
1756
|
+
assign out = sig.data;
|
|
1757
|
+
}
|
|
1758
|
+
`;
|
|
1759
|
+
|
|
1760
|
+
// A module that uses two instances connected via an interface so we can verify
|
|
1761
|
+
// that the hierarchy DUT also exposes nested accessors correctly.
|
|
1762
|
+
const INTERFACE_HIERARCHY_SOURCE = `
|
|
1763
|
+
interface Bus {
|
|
1764
|
+
var data: logic<8>;
|
|
1765
|
+
var valid: logic;
|
|
1766
|
+
modport producer {
|
|
1767
|
+
data: output,
|
|
1768
|
+
valid: output,
|
|
1769
|
+
}
|
|
1770
|
+
modport consumer {
|
|
1771
|
+
data: input,
|
|
1772
|
+
valid: input,
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
module Producer (
|
|
1777
|
+
bus: modport Bus::producer,
|
|
1778
|
+
val: input logic<8>,
|
|
1779
|
+
) {
|
|
1780
|
+
assign bus.data = val;
|
|
1781
|
+
assign bus.valid = 1'b1;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
module Consumer (
|
|
1785
|
+
bus: modport Bus::consumer,
|
|
1786
|
+
out: output logic<8>,
|
|
1787
|
+
got: output logic,
|
|
1788
|
+
) {
|
|
1789
|
+
assign out = bus.data;
|
|
1790
|
+
assign got = bus.valid;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
module Top (
|
|
1794
|
+
val: input logic<8>,
|
|
1795
|
+
out: output logic<8>,
|
|
1796
|
+
got: output logic,
|
|
1797
|
+
) {
|
|
1798
|
+
inst b: Bus;
|
|
1799
|
+
inst p: Producer (bus: b, val: val);
|
|
1800
|
+
inst c: Consumer (bus: b, out: out, got: got);
|
|
1801
|
+
}
|
|
1802
|
+
`;
|
|
1803
|
+
|
|
1804
|
+
describe("E2E: interface port DUT accessor", () => {
|
|
1805
|
+
test("top-level interface port members accessible as nested dut properties", () => {
|
|
1806
|
+
interface TopPorts {
|
|
1807
|
+
sig: { data: bigint; valid: bigint };
|
|
1808
|
+
readonly out: bigint;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
const sim = Simulator.fromSource<TopPorts>(INTERFACE_PORT_SOURCE, "Top");
|
|
1812
|
+
|
|
1813
|
+
sim.dut.sig.data = 0x42n;
|
|
1814
|
+
expect(sim.dut.out).toBe(0x42n);
|
|
1815
|
+
|
|
1816
|
+
sim.dut.sig.data = 0xFFn;
|
|
1817
|
+
expect(sim.dut.out).toBe(0xFFn);
|
|
1818
|
+
|
|
1819
|
+
sim.dut.sig.data = 0x00n;
|
|
1820
|
+
expect(sim.dut.out).toBe(0x00n);
|
|
1821
|
+
|
|
1822
|
+
sim.dispose();
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
test("writing to interface output member throws", () => {
|
|
1826
|
+
// bus.data / bus.valid are outputs of Top — writing should throw.
|
|
1827
|
+
const PRODUCER_ONLY = `
|
|
1828
|
+
interface Bus {
|
|
1829
|
+
var data: logic<8>;
|
|
1830
|
+
var valid: logic;
|
|
1831
|
+
modport src {
|
|
1832
|
+
data: output,
|
|
1833
|
+
valid: output,
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
module Top (
|
|
1837
|
+
bus: modport Bus::src,
|
|
1838
|
+
val: input logic<8>,
|
|
1839
|
+
) {
|
|
1840
|
+
assign bus.data = val;
|
|
1841
|
+
assign bus.valid = 1'b1;
|
|
1842
|
+
}
|
|
1843
|
+
`;
|
|
1844
|
+
const sim = Simulator.fromSource(PRODUCER_ONLY, "Top");
|
|
1845
|
+
|
|
1846
|
+
expect(() => {
|
|
1847
|
+
(sim.dut as any).bus.data = 0n;
|
|
1848
|
+
}).toThrow("Cannot write to output port 'data'");
|
|
1849
|
+
|
|
1850
|
+
sim.dispose();
|
|
1851
|
+
});
|
|
1852
|
+
|
|
1853
|
+
test("interface port propagates through sub-module instances", () => {
|
|
1854
|
+
interface TopPorts {
|
|
1855
|
+
val: bigint;
|
|
1856
|
+
readonly out: bigint;
|
|
1857
|
+
readonly got: bigint;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
const sim = Simulator.fromSource<TopPorts>(INTERFACE_HIERARCHY_SOURCE, "Top");
|
|
1861
|
+
|
|
1862
|
+
sim.dut.val = 0xABn;
|
|
1863
|
+
expect(sim.dut.out).toBe(0xABn);
|
|
1864
|
+
expect(sim.dut.got).toBe(1n);
|
|
1865
|
+
|
|
1866
|
+
sim.dut.val = 0x00n;
|
|
1867
|
+
expect(sim.dut.out).toBe(0x00n);
|
|
1868
|
+
|
|
1869
|
+
sim.dispose();
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
test("multiple interface port members are independently accessible", () => {
|
|
1873
|
+
interface TopPorts {
|
|
1874
|
+
sig: { data: bigint; valid: bigint };
|
|
1875
|
+
readonly out: bigint;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
const sim = Simulator.fromSource<TopPorts>(INTERFACE_PORT_SOURCE, "Top");
|
|
1879
|
+
|
|
1880
|
+
sim.dut.sig.data = 0x77n;
|
|
1881
|
+
sim.dut.sig.valid = 1n;
|
|
1882
|
+
expect(sim.dut.out).toBe(0x77n);
|
|
1883
|
+
|
|
1884
|
+
sim.dut.sig.data = 0x33n;
|
|
1885
|
+
sim.dut.sig.valid = 0n;
|
|
1886
|
+
expect(sim.dut.out).toBe(0x33n);
|
|
1887
|
+
|
|
1888
|
+
sim.dispose();
|
|
1889
|
+
});
|
|
1890
|
+
});
|
|
1891
|
+
|
|
1892
|
+
// ---------------------------------------------------------------------------
|
|
1893
|
+
// Bug fix: 1-bit unpacked array port (logic [N])
|
|
1894
|
+
// ---------------------------------------------------------------------------
|
|
1895
|
+
|
|
1896
|
+
const ARRAY_1BIT_SOURCE = `
|
|
1897
|
+
module ArrayPassThrough (
|
|
1898
|
+
clk: input clock,
|
|
1899
|
+
rst: input reset,
|
|
1900
|
+
en: input logic [4],
|
|
1901
|
+
y0: output logic,
|
|
1902
|
+
y1: output logic,
|
|
1903
|
+
y2: output logic,
|
|
1904
|
+
y3: output logic,
|
|
1905
|
+
) {
|
|
1906
|
+
always_comb {
|
|
1907
|
+
y0 = en[0];
|
|
1908
|
+
y1 = en[1];
|
|
1909
|
+
y2 = en[2];
|
|
1910
|
+
y3 = en[3];
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
`;
|
|
1914
|
+
|
|
1915
|
+
describe("E2E: 1-bit unpacked array port (logic [N])", () => {
|
|
1916
|
+
test("each element is independently visible in hardware (issue #6)", () => {
|
|
1917
|
+
interface Ports {
|
|
1918
|
+
rst: bigint;
|
|
1919
|
+
en: { at(i: number): bigint; set(i: number, v: bigint): void; length: number };
|
|
1920
|
+
readonly y0: bigint;
|
|
1921
|
+
readonly y1: bigint;
|
|
1922
|
+
readonly y2: bigint;
|
|
1923
|
+
readonly y3: bigint;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
const sim = Simulator.fromSource<Ports>(ARRAY_1BIT_SOURCE, "ArrayPassThrough");
|
|
1927
|
+
|
|
1928
|
+
// Default: all inputs 0
|
|
1929
|
+
sim.tick();
|
|
1930
|
+
expect(sim.dut.y0).toBe(0n);
|
|
1931
|
+
expect(sim.dut.y1).toBe(0n);
|
|
1932
|
+
expect(sim.dut.y2).toBe(0n);
|
|
1933
|
+
expect(sim.dut.y3).toBe(0n);
|
|
1934
|
+
|
|
1935
|
+
// Set element 0 only
|
|
1936
|
+
sim.dut.en.set(0, 1n);
|
|
1937
|
+
sim.tick();
|
|
1938
|
+
expect(sim.dut.y0).toBe(1n);
|
|
1939
|
+
expect(sim.dut.y1).toBe(0n);
|
|
1940
|
+
expect(sim.dut.y2).toBe(0n);
|
|
1941
|
+
expect(sim.dut.y3).toBe(0n);
|
|
1942
|
+
|
|
1943
|
+
// Set element 1 (was silently ignored before the fix)
|
|
1944
|
+
sim.dut.en.set(0, 0n);
|
|
1945
|
+
sim.dut.en.set(1, 1n);
|
|
1946
|
+
sim.tick();
|
|
1947
|
+
expect(sim.dut.y0).toBe(0n);
|
|
1948
|
+
expect(sim.dut.y1).toBe(1n);
|
|
1949
|
+
expect(sim.dut.y2).toBe(0n);
|
|
1950
|
+
expect(sim.dut.y3).toBe(0n);
|
|
1951
|
+
|
|
1952
|
+
// Set elements 2 and 3
|
|
1953
|
+
sim.dut.en.set(1, 0n);
|
|
1954
|
+
sim.dut.en.set(2, 1n);
|
|
1955
|
+
sim.dut.en.set(3, 1n);
|
|
1956
|
+
sim.tick();
|
|
1957
|
+
expect(sim.dut.y0).toBe(0n);
|
|
1958
|
+
expect(sim.dut.y1).toBe(0n);
|
|
1959
|
+
expect(sim.dut.y2).toBe(1n);
|
|
1960
|
+
expect(sim.dut.y3).toBe(1n);
|
|
1961
|
+
|
|
1962
|
+
sim.dispose();
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
test("length property is correct", () => {
|
|
1966
|
+
interface Ports {
|
|
1967
|
+
rst: bigint;
|
|
1968
|
+
en: { at(i: number): bigint; set(i: number, v: bigint): void; length: number };
|
|
1969
|
+
readonly y0: bigint;
|
|
1970
|
+
readonly y1: bigint;
|
|
1971
|
+
readonly y2: bigint;
|
|
1972
|
+
readonly y3: bigint;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
const sim = Simulator.fromSource<Ports>(ARRAY_1BIT_SOURCE, "ArrayPassThrough");
|
|
1976
|
+
expect(sim.dut.en.length).toBe(4);
|
|
1977
|
+
sim.dispose();
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1980
|
+
test("4-state mode: X written to element i propagates only to output yi (verifies maskBase layout)", () => {
|
|
1981
|
+
// This test validates that the maskBase = baseOffset + totalValueBytes assumption
|
|
1982
|
+
// matches the actual JIT memory layout when fourState: true is used.
|
|
1983
|
+
interface Ports {
|
|
1984
|
+
rst: bigint;
|
|
1985
|
+
en: { at(i: number): bigint; set(i: number, v: unknown): void; length: number };
|
|
1986
|
+
readonly y0: bigint;
|
|
1987
|
+
readonly y1: bigint;
|
|
1988
|
+
readonly y2: bigint;
|
|
1989
|
+
readonly y3: bigint;
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
const sim = Simulator.fromSource<Ports>(ARRAY_1BIT_SOURCE, "ArrayPassThrough", { fourState: true });
|
|
1993
|
+
|
|
1994
|
+
// Start with all defined zeros
|
|
1995
|
+
for (let i = 0; i < 4; i++) sim.dut.en.set(i, 0n);
|
|
1996
|
+
sim.tick();
|
|
1997
|
+
|
|
1998
|
+
// Set element 1 to X; only y1 should become X
|
|
1999
|
+
sim.dut.en.set(1, X);
|
|
2000
|
+
sim.tick();
|
|
2001
|
+
|
|
2002
|
+
expect(sim.fourState("y1").mask).not.toBe(0n); // y1 = X
|
|
2003
|
+
expect(sim.fourState("y0").mask).toBe(0n); // y0 defined
|
|
2004
|
+
expect(sim.fourState("y2").mask).toBe(0n); // y2 defined
|
|
2005
|
+
expect(sim.fourState("y3").mask).toBe(0n); // y3 defined
|
|
2006
|
+
|
|
2007
|
+
// Set element 2 to X; y2 becomes X, y1 remains X
|
|
2008
|
+
sim.dut.en.set(2, X);
|
|
2009
|
+
sim.tick();
|
|
2010
|
+
|
|
2011
|
+
expect(sim.fourState("y1").mask).not.toBe(0n);
|
|
2012
|
+
expect(sim.fourState("y2").mask).not.toBe(0n);
|
|
2013
|
+
expect(sim.fourState("y0").mask).toBe(0n);
|
|
2014
|
+
expect(sim.fourState("y3").mask).toBe(0n);
|
|
2015
|
+
|
|
2016
|
+
// Clear element 1 to defined 0; y1 becomes defined again
|
|
2017
|
+
sim.dut.en.set(1, 0n);
|
|
2018
|
+
sim.tick();
|
|
2019
|
+
|
|
2020
|
+
expect(sim.fourState("y1").mask).toBe(0n);
|
|
2021
|
+
expect(sim.fourState("y2").mask).not.toBe(0n);
|
|
2022
|
+
|
|
2023
|
+
sim.dispose();
|
|
2024
|
+
});
|
|
2025
|
+
});
|
package/src/napi-helpers.ts
CHANGED
|
@@ -46,6 +46,7 @@ export interface RawNapiSimulationHandle {
|
|
|
46
46
|
readonly hierarchyJson: string;
|
|
47
47
|
readonly stableSize: number;
|
|
48
48
|
readonly totalSize: number;
|
|
49
|
+
readonly defaultMaxSteps: number | null;
|
|
49
50
|
addClock(eventId: number, period: number, initialDelay: number): void;
|
|
50
51
|
schedule(eventId: number, time: number, value: number): void;
|
|
51
52
|
runUntil(endTime: number): void;
|
|
@@ -300,12 +301,17 @@ export function parseNapiLayout(json: string): {
|
|
|
300
301
|
/**
|
|
301
302
|
* Build PortInfo records from the NAPI layout signals.
|
|
302
303
|
* This auto-detects port metadata so users don't need to hand-write ModuleDefinition.
|
|
304
|
+
*
|
|
305
|
+
* Hierarchical signal names (e.g. "bus.data", "bus.valid" from an interface port)
|
|
306
|
+
* are grouped into a nested PortInfo with an `interface` map so that createDut can
|
|
307
|
+
* expose them as `dut.bus.data` and `dut.bus.valid`.
|
|
303
308
|
*/
|
|
304
309
|
export function buildPortsFromLayout(
|
|
305
310
|
signals: Record<string, SignalLayout & { typeKind: string; arrayDims?: number[] }>,
|
|
306
311
|
_events: Record<string, number>,
|
|
307
312
|
): Record<string, PortInfo> {
|
|
308
|
-
|
|
313
|
+
// Step 1: Build flat PortInfo for every signal name
|
|
314
|
+
const flat: Record<string, PortInfo> = {};
|
|
309
315
|
|
|
310
316
|
for (const [name, sig] of Object.entries(signals)) {
|
|
311
317
|
const typeKind = sig.typeKind;
|
|
@@ -329,7 +335,36 @@ export function buildPortsFromLayout(
|
|
|
329
335
|
if (sig.arrayDims && sig.arrayDims.length > 0) {
|
|
330
336
|
(port as { arrayDims: readonly number[] }).arrayDims = sig.arrayDims;
|
|
331
337
|
}
|
|
332
|
-
|
|
338
|
+
flat[name] = port;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Step 2: Group hierarchical signals (e.g. "bus.data") into nested PortInfo.
|
|
342
|
+
// Veryl interface ports are exactly one level deep, so "bus.data" → parent "bus",
|
|
343
|
+
// member "data". The parent entry receives an `interface` map of its members;
|
|
344
|
+
// the flat layout keys are kept as-is so createNestedDut can look them up.
|
|
345
|
+
const ports: Record<string, PortInfo> = {};
|
|
346
|
+
const ifMaps = new Map<string, Record<string, PortInfo>>();
|
|
347
|
+
|
|
348
|
+
for (const [name, port] of Object.entries(flat)) {
|
|
349
|
+
const dotIdx = name.indexOf(".");
|
|
350
|
+
if (dotIdx < 0) {
|
|
351
|
+
ports[name] = port;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
// Hierarchical signal: first segment is the interface port name
|
|
355
|
+
const parentName = name.slice(0, dotIdx);
|
|
356
|
+
const memberName = name.slice(dotIdx + 1);
|
|
357
|
+
if (!ifMaps.has(parentName)) {
|
|
358
|
+
const ifMap: Record<string, PortInfo> = {};
|
|
359
|
+
ifMaps.set(parentName, ifMap);
|
|
360
|
+
ports[parentName] = {
|
|
361
|
+
direction: "inout",
|
|
362
|
+
type: "logic",
|
|
363
|
+
width: 0,
|
|
364
|
+
interface: ifMap,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
ifMaps.get(parentName)![memberName] = port;
|
|
333
368
|
}
|
|
334
369
|
|
|
335
370
|
return ports;
|
package/src/simulation.ts
CHANGED
|
@@ -57,6 +57,7 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
57
57
|
private readonly _buffer: ArrayBuffer | SharedArrayBuffer;
|
|
58
58
|
private readonly _layout: Record<string, SignalLayout & { typeKind?: string; associatedClock?: string }>;
|
|
59
59
|
private readonly _clocks = new Map<string, { period: number; eventId: number }>();
|
|
60
|
+
private readonly _defaultMaxSteps: number | undefined;
|
|
60
61
|
private _disposed = false;
|
|
61
62
|
|
|
62
63
|
private constructor(
|
|
@@ -66,6 +67,7 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
66
67
|
state: DirtyState,
|
|
67
68
|
buffer: ArrayBuffer | SharedArrayBuffer,
|
|
68
69
|
layout: Record<string, SignalLayout & { typeKind?: string; associatedClock?: string }>,
|
|
70
|
+
defaultMaxSteps?: number,
|
|
69
71
|
) {
|
|
70
72
|
this._handle = handle;
|
|
71
73
|
this._dut = dut;
|
|
@@ -73,6 +75,7 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
73
75
|
this._state = state;
|
|
74
76
|
this._buffer = buffer;
|
|
75
77
|
this._layout = layout;
|
|
78
|
+
this._defaultMaxSteps = defaultMaxSteps;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
/**
|
|
@@ -185,12 +188,13 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
185
188
|
const ports = buildPortsFromLayout(layout.signals, events);
|
|
186
189
|
|
|
187
190
|
const buf = raw.sharedMemory().buffer;
|
|
191
|
+
const defaultMaxSteps = raw.defaultMaxSteps ?? undefined;
|
|
188
192
|
|
|
189
193
|
const state: DirtyState = { dirty: false };
|
|
190
194
|
const handle = wrapDirectSimulationHandle(raw);
|
|
191
195
|
const dut = createDut<P>(buf, layout.forDut, ports, handle, state, hierarchy);
|
|
192
196
|
|
|
193
|
-
return new Simulation<P>(handle, dut, events, state, buf, layout.signals);
|
|
197
|
+
return new Simulation<P>(handle, dut, events, state, buf, layout.signals, defaultMaxSteps);
|
|
194
198
|
}
|
|
195
199
|
|
|
196
200
|
/** The DUT accessor object — read/write ports as plain properties. */
|
|
@@ -298,7 +302,7 @@ export class Simulation<P = Record<string, unknown>> {
|
|
|
298
302
|
opts?: { maxSteps?: number },
|
|
299
303
|
): number {
|
|
300
304
|
this.ensureAlive();
|
|
301
|
-
const max = opts?.maxSteps ?? 100_000;
|
|
305
|
+
const max = opts?.maxSteps ?? this._defaultMaxSteps ?? 100_000;
|
|
302
306
|
let steps = 0;
|
|
303
307
|
while (!condition()) {
|
|
304
308
|
const t = this._handle.step();
|
package/src/types.ts
CHANGED
|
@@ -124,7 +124,7 @@ export interface CreateResult<H extends NativeHandle = NativeHandle> {
|
|
|
124
124
|
// ---------------------------------------------------------------------------
|
|
125
125
|
|
|
126
126
|
export interface SimulatorOptions {
|
|
127
|
-
/** Enable 4-state (X
|
|
127
|
+
/** Enable 4-state (X) simulation. Default: false. */
|
|
128
128
|
fourState?: boolean;
|
|
129
129
|
/** Path to write VCD waveform output. */
|
|
130
130
|
vcd?: string;
|