From 09420fd72cf750478bcc2f54b59e8a17dfb521e4 Mon Sep 17 00:00:00 2001 From: Stefan Holst Date: Sun, 2 Nov 2025 20:56:14 +0900 Subject: [PATCH] new logic sim interface prototype, simprim lib and test --- src/kyupy/logic_sim.py | 86 +++++++++++++++++++++++++++- tests/all_kyupy_simprims.minimal.v | 91 ++++++++++++++++++++++++++++++ tests/all_kyupy_simprims.v | 49 ++++++++++++++++ tests/kyupy_simprims.genlib | 34 +++++++++++ tests/map_to_minimal.abc | 6 ++ tests/minimal.genlib | 5 ++ tests/test_logic_sim.py | 19 ++++++- 7 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 tests/all_kyupy_simprims.minimal.v create mode 100644 tests/all_kyupy_simprims.v create mode 100644 tests/kyupy_simprims.genlib create mode 100644 tests/map_to_minimal.abc create mode 100644 tests/minimal.genlib diff --git a/src/kyupy/logic_sim.py b/src/kyupy/logic_sim.py index d33d7b4..e235a2f 100644 --- a/src/kyupy/logic_sim.py +++ b/src/kyupy/logic_sim.py @@ -10,7 +10,7 @@ import math import numpy as np -from . import numba, logic, hr_bytes, sim, eng, cdiv +from . import numba, logic, hr_bytes, sim, eng, cdiv, batchrange from .circuit import Circuit @@ -363,6 +363,90 @@ def _prop_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model): c[o0] = c[o0] ^ fault_mask +class LogicSim2V(sim.SimOps): + """A bit-parallel naïve combinational simulator for 2-valued logic. + + :param circuit: The circuit to simulate. + :param sims: The number of parallel logic simulations to perform. + :param c_reuse: If True, intermediate signal values may get overwritten when not needed anymore to save memory. + :param strip_forks: If True, forks are not included in the simulation model to save memory and simulation time. + Caveat: faults on fanout branches will not be injected if forks are stripped. + """ + def __init__(self, circuit: Circuit, sims: int = 8, c_reuse: bool = False, strip_forks: bool = False): + super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks) + self.sims = sims + nbytes = cdiv(sims, 8) + + self.c = np.zeros((self.c_len, 1, nbytes), dtype=np.uint8) + """Logic values within the combinational portion of the circuit. + + In bit-parallel (bp) storage format. + Storage locations are indirectly addressed. + Data for line `l` is in `self.c[self.c_locs[l]]`. + """ + self.s_assign = np.zeros((self.s_len, self.sims), dtype=np.uint8) + """Logic values assigned to the ports and flip-flops of the circuit. + + The simulator reads (P)PI values from here. + Values assigned to PO positions are ignored. + This field is a 2-dimensional array and expects values in the mv storage format. + First index is the port position (defined by `self.circuit.s_nodes`), second index is the pattern index (0 ... `self.sims-1`). + """ + self.s_result = np.zeros((self.s_len, self.sims), dtype=np.uint8) + """Logic values at the ports and flip-flops of the circuit after simulation. + + The simulator writes (P)PO values here. + Values assigned to PI positions are ignored. + This field is a 2-dimensional array and expects values in the mv storage format. + First index is the port position (defined by `self.circuit.s_nodes`), second index is the pattern index (0 ... `self.sims-1`). + """ + self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8) + + def __repr__(self): + return f'{{name: "{self.circuit.name}", sims: {self.sims}, c_bytes: {eng(self.c.nbytes)}}}' + + def s_to_c(self): + """Assigns the values from ``self.s_assign`` to the inputs of the combinational portion. + """ + self.c[self.pippi_c_locs] = logic.mv_to_bp(self.s_assign[self.pippi_s_locs])[:,:1,:] + + def c_prop(self, fault_line=-1, fault_model=2, fault_mask=None): + if fault_mask is None: + fault_mask = self._full_mask # default: full mask + else: + if len(fault_mask) < self.c.shape[-1]: # pad mask with 0's if necessary + fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8) + fault_mask2[:len(fault_mask)] = fault_mask + fault_mask = fault_mask2 + _prop_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model)) + + def c_to_s(self): + """Captures the results of the combinational portion into ``self.s_result``. + """ + self.s_result[self.poppo_s_locs] = logic.bp_to_mv(self.c[self.poppo_c_locs])[:,:self.sims] * logic.ONE + + def c_ppo_to_ppi(self): + """Copies the result data for all PPOs (flip-flops) to PPIs for a next simulation cycle. + """ + self.c[self.ppi_c_locs] = self.c[self.ppo_c_locs].copy() # copy prevents undefined behavior if (P)PIs are connected directly to (P)POs + + def allocate(self): + """Allocates a new pattern array with appropriate dimensions ``(self.s_len, self.sims)`` for one-pass simulation. + """ + return np.full((self.s_len, self.sims), logic.ZERO, dtype=np.uint8) + + def simulate(self, patterns, cycles:int = 1, fault_line=-1, fault_model=2, fault_mask=None): + assert cycles >= 1 + for bo, bs in batchrange(patterns.shape[-1], self.sims): + self.s_assign[self.pippi_s_locs, :bs] = patterns[self.pippi_s_locs, bo:bo+bs] + self.s_to_c() + for cycle in range(cycles): + self.c_prop(fault_line=fault_line, fault_model=fault_model, fault_mask=fault_mask) + if cycle < (cycles-1): self.c_ppo_to_ppi() + self.c_to_s() + patterns[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs] + + class LogicSim6V(sim.SimOps): """A bit-parallel naïve combinational simulator for 6-valued logic. diff --git a/tests/all_kyupy_simprims.minimal.v b/tests/all_kyupy_simprims.minimal.v new file mode 100644 index 0000000..1f0f986 --- /dev/null +++ b/tests/all_kyupy_simprims.minimal.v @@ -0,0 +1,91 @@ +// Benchmark "all_kyupy_primitives" written by ABC on Sat Nov 1 23:49:37 2025 + +module all_kyupy_primitives ( + i0, i1, i2, i3, + \o[0] , \o[1] , \o[2] , \o[3] , \o[4] , \o[5] , \o[6] , \o[7] , \o[8] , + \o[9] , \o[10] , \o[11] , \o[12] , \o[13] , \o[14] , \o[15] , \o[16] , + \o[17] , \o[18] , \o[19] , \o[20] , \o[21] , \o[22] , \o[23] , \o[24] , + \o[25] , \o[26] , \o[27] , \o[28] , \o[29] ); + input i0, i1, i2, i3; + output \o[0] , \o[1] , \o[2] , \o[3] , \o[4] , \o[5] , \o[6] , \o[7] , + \o[8] , \o[9] , \o[10] , \o[11] , \o[12] , \o[13] , \o[14] , \o[15] , + \o[16] , \o[17] , \o[18] , \o[19] , \o[20] , \o[21] , \o[22] , \o[23] , + \o[24] , \o[25] , \o[26] , \o[27] , \o[28] , \o[29] ; + wire new_n42, new_n45, new_n48, new_n51, new_n52, new_n53, new_n54, + new_n55, new_n57, new_n58, new_n59, new_n60, new_n61, new_n62, new_n63, + new_n64, new_n65, new_n66, new_n67, new_n69, new_n70, new_n71, new_n72, + new_n73, new_n74, new_n75, new_n76, new_n77, new_n78, new_n79, new_n83, + new_n84, new_n88, new_n89, new_n93, new_n96, new_n99, new_n100, + new_n101; + INV1 g00(.i0(i1), .o(\o[1] )); + AND2 g01(.i0(i1), .i1(i0), .o(\o[2] )); + AND2 g02(.i0(\o[2] ), .i1(i2), .o(\o[3] )); + AND2 g03(.i0(\o[3] ), .i1(i3), .o(\o[4] )); + INV1 g04(.i0(\o[2] ), .o(\o[5] )); + INV1 g05(.i0(\o[3] ), .o(\o[6] )); + INV1 g06(.i0(\o[4] ), .o(\o[7] )); + INV1 g07(.i0(i0), .o(new_n42)); + AND2 g08(.i0(\o[1] ), .i1(new_n42), .o(\o[11] )); + INV1 g09(.i0(\o[11] ), .o(\o[8] )); + INV1 g10(.i0(i2), .o(new_n45)); + AND2 g11(.i0(\o[11] ), .i1(new_n45), .o(\o[12] )); + INV1 g12(.i0(\o[12] ), .o(\o[9] )); + INV1 g13(.i0(i3), .o(new_n48)); + AND2 g14(.i0(\o[12] ), .i1(new_n48), .o(\o[13] )); + INV1 g15(.i0(\o[13] ), .o(\o[10] )); + AND2 g16(.i0(\o[1] ), .i1(i0), .o(new_n51)); + INV1 g17(.i0(new_n51), .o(new_n52)); + AND2 g18(.i0(i1), .i1(new_n42), .o(new_n53)); + INV1 g19(.i0(new_n53), .o(new_n54)); + AND2 g20(.i0(new_n54), .i1(new_n52), .o(new_n55)); + INV1 g21(.i0(new_n55), .o(\o[14] )); + AND2 g22(.i0(i2), .i1(i1), .o(new_n57)); + INV1 g23(.i0(new_n57), .o(new_n58)); + AND2 g24(.i0(new_n45), .i1(\o[1] ), .o(new_n59)); + INV1 g25(.i0(new_n59), .o(new_n60)); + AND2 g26(.i0(new_n60), .i1(new_n58), .o(new_n61)); + INV1 g27(.i0(new_n61), .o(new_n62)); + AND2 g28(.i0(new_n62), .i1(i0), .o(new_n63)); + INV1 g29(.i0(new_n63), .o(new_n64)); + AND2 g30(.i0(new_n61), .i1(new_n42), .o(new_n65)); + INV1 g31(.i0(new_n65), .o(new_n66)); + AND2 g32(.i0(new_n66), .i1(new_n64), .o(new_n67)); + INV1 g33(.i0(new_n67), .o(\o[15] )); + AND2 g34(.i0(i3), .i1(new_n45), .o(new_n69)); + INV1 g35(.i0(new_n69), .o(new_n70)); + AND2 g36(.i0(new_n48), .i1(i2), .o(new_n71)); + INV1 g37(.i0(new_n71), .o(new_n72)); + AND2 g38(.i0(new_n72), .i1(new_n70), .o(new_n73)); + INV1 g39(.i0(new_n73), .o(new_n74)); + AND2 g40(.i0(new_n74), .i1(new_n55), .o(new_n75)); + INV1 g41(.i0(new_n75), .o(new_n76)); + AND2 g42(.i0(new_n73), .i1(\o[14] ), .o(new_n77)); + INV1 g43(.i0(new_n77), .o(new_n78)); + AND2 g44(.i0(new_n78), .i1(new_n76), .o(new_n79)); + INV1 g45(.i0(new_n79), .o(\o[16] )); + AND2 g46(.i0(\o[5] ), .i1(new_n45), .o(\o[21] )); + INV1 g47(.i0(\o[21] ), .o(\o[17] )); + AND2 g48(.i0(i3), .i1(i2), .o(new_n83)); + INV1 g49(.i0(new_n83), .o(new_n84)); + AND2 g50(.i0(new_n84), .i1(\o[5] ), .o(\o[22] )); + INV1 g51(.i0(\o[22] ), .o(\o[18] )); + AND2 g52(.i0(\o[8] ), .i1(i2), .o(\o[19] )); + AND2 g53(.i0(new_n48), .i1(new_n45), .o(new_n88)); + INV1 g54(.i0(new_n88), .o(new_n89)); + AND2 g55(.i0(new_n89), .i1(\o[8] ), .o(\o[20] )); + INV1 g56(.i0(\o[19] ), .o(\o[23] )); + INV1 g57(.i0(\o[20] ), .o(\o[24] )); + AND2 g58(.i0(\o[5] ), .i1(new_n48), .o(new_n93)); + AND2 g59(.i0(new_n93), .i1(new_n45), .o(\o[27] )); + INV1 g60(.i0(\o[27] ), .o(\o[25] )); + AND2 g61(.i0(\o[8] ), .i1(i3), .o(new_n96)); + AND2 g62(.i0(new_n96), .i1(i2), .o(\o[26] )); + INV1 g63(.i0(\o[26] ), .o(\o[28] )); + AND2 g64(.i0(new_n45), .i1(i0), .o(new_n99)); + INV1 g65(.i0(new_n99), .o(new_n100)); + AND2 g66(.i0(new_n100), .i1(new_n58), .o(new_n101)); + INV1 g67(.i0(new_n101), .o(\o[29] )); + BUF1 g68(.i0(i0), .o(\o[0] )); +endmodule + + diff --git a/tests/all_kyupy_simprims.v b/tests/all_kyupy_simprims.v new file mode 100644 index 0000000..d3ce2f4 --- /dev/null +++ b/tests/all_kyupy_simprims.v @@ -0,0 +1,49 @@ +module all_kyupy_primitives (i0, i1, i2, i3, o); + input i0; + input i1; + input i2; + input i3; + output [29:0] o; + + BUF1 buf1_0 (.i0(i0), .o(o[0])); + INV1 inv1_0 (.i0(i1), .o(o[1])); + + AND2 and2_0 (.i0(i0), .i1(i1), .o(o[2])); + AND3 and3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[3])); + AND4 and4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[4])); + + NAND2 nand2_0 (.i0(i0), .i1(i1), .o(o[5])); + NAND3 nand3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[6])); + NAND4 nand4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[7])); + + OR2 or2_0 (.i0(i0), .i1(i1), .o(o[8])); + OR3 or3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[9])); + OR4 or4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[10])); + + NOR2 nor2_0 (.i0(i0), .i1(i1), .o(o[11])); + NOR3 nor3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[12])); + NOR4 nor4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[13])); + + XOR2 xor2_0 (.i0(i0), .i1(i1), .o(o[14])); + XOR3 xor3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[15])); + XOR4 xor4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[16])); + + AO21 ao21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[17])); + AO22 ao22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[18])); + OA21 oa21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[19])); + OA22 oa22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[20])); + + AOI21 aoi21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[21])); + AOI22 aoi22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[22])); + OAI21 oai21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[23])); + OAI22 oai22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[24])); + + AO211 ao211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[25])); + OA211 oa211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[26])); + + AOI211 aoi211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[27])); + OAI211 oai211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[28])); + + MUX21 mux21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[29])); + +endmodule \ No newline at end of file diff --git a/tests/kyupy_simprims.genlib b/tests/kyupy_simprims.genlib new file mode 100644 index 0000000..c7b19b1 --- /dev/null +++ b/tests/kyupy_simprims.genlib @@ -0,0 +1,34 @@ +# library of all KyuPy simulation primitives defined in kyupy.sim +GATE BUF1 1 o=i0; PIN * NONINV 1 999 1 0 1 0 +GATE INV1 1 o=!i0; PIN * INV 1 999 1 0 1 0 +GATE AND2 1 o=i0*i1; PIN * NONINV 1 999 1 0 1 0 +GATE AND3 1 o=i0*i1*i2; PIN * NONINV 1 999 1 0 1 0 +GATE AND4 1 o=i0*i1*i2*i3; PIN * NONINV 1 999 1 0 1 0 +GATE NAND2 1 o=!(i0*i1); PIN * INV 1 999 1 0 1 0 +GATE NAND3 1 o=!(i0*i1*i2); PIN * INV 1 999 1 0 1 0 +GATE NAND4 1 o=!(i0*i1*i2*i3); PIN * INV 1 999 1 0 1 0 +GATE OR2 1 o=i0+i1; PIN * NONINV 1 999 1 0 1 0 +GATE OR3 1 o=i0+i1+i2; PIN * NONINV 1 999 1 0 1 0 +GATE OR4 1 o=i0+i1+i2+i3; PIN * NONINV 1 999 1 0 1 0 +GATE NOR2 1 o=!(i0+i1); PIN * INV 1 999 1 0 1 0 +GATE NOR3 1 o=!(i0+i1+i2); PIN * INV 1 999 1 0 1 0 +GATE NOR4 1 o=!(i0+i1+i2+i3); PIN * INV 1 999 1 0 1 0 +GATE XOR2 1 o=i0^i1; PIN * UNKNOWN 1 999 1 0 1 0 +GATE XOR3 1 o=i0^i1^i2; PIN * UNKNOWN 1 999 1 0 1 0 +GATE XOR4 1 o=i0^i1^i2^i3; PIN * UNKNOWN 1 999 1 0 1 0 +GATE XNOR2 1 o=!(i0^i1); PIN * UNKNOWN 1 999 1 0 1 0 +GATE XNOR3 1 o=!(i0^i1^i2); PIN * UNKNOWN 1 999 1 0 1 0 +GATE XNOR4 1 o=!(i0^i1^i2^i3); PIN * UNKNOWN 1 999 1 0 1 0 +GATE AO21 1 o=(i0*i1)+i2; PIN * NONINV 1 999 1 0 1 0 +GATE AO22 1 o=(i0*i1)+(i2*i3); PIN * NONINV 1 999 1 0 1 0 +GATE OA21 1 o=(i0+i1)*i2; PIN * NONINV 1 999 1 0 1 0 +GATE OA22 1 o=(i0+i1)*(i2+i3); PIN * NONINV 1 999 1 0 1 0 +GATE AOI21 1 o=!( (i0*i1)+i2 ); PIN * INV 1 999 1 0 1 0 +GATE AOI22 1 o=!( (i0*i1)+(i2*i3) ); PIN * INV 1 999 1 0 1 0 +GATE OAI21 1 o=!( (i0+i1)*i2 ); PIN * INV 1 999 1 0 1 0 +GATE OAI22 1 o=!( (i0+i1)*(i2+i3) ); PIN * INV 1 999 1 0 1 0 +GATE AO211 1 o=(i0*i1)+i2+i3; PIN * NONINV 1 999 1 0 1 0 +GATE OA211 1 o=(i0+i1)*i2*i3; PIN * NONINV 1 999 1 0 1 0 +GATE AOI211 1 o=!( (i0*i1)+i2+i3 ); PIN * INV 1 999 1 0 1 0 +GATE OAI211 1 o=!( (i0+i1)*i2*i3 ); PIN * INV 1 999 1 0 1 0 +GATE MUX21 1 o=(i0*!i2)+(i1*i2); PIN * UNKNOWN 1 999 1 0 1 0 \ No newline at end of file diff --git a/tests/map_to_minimal.abc b/tests/map_to_minimal.abc new file mode 100644 index 0000000..6f48d7d --- /dev/null +++ b/tests/map_to_minimal.abc @@ -0,0 +1,6 @@ +read kyupy_simprims.genlib +read -m all_kyupy_simprims.v +fraig +read minimal.genlib +map +write all_kyupy_simprims.minimal.v diff --git a/tests/minimal.genlib b/tests/minimal.genlib new file mode 100644 index 0000000..7cc487a --- /dev/null +++ b/tests/minimal.genlib @@ -0,0 +1,5 @@ +# A minimal library for generating logically equivalent circuits with +# minimum number of gate types. +GATE BUF1 1 o=i0; PIN * NONINV 1 999 1 0 1 0 +GATE INV1 1 o=!i0; PIN * INV 1 999 1 0 1 0 +GATE AND2 1 o=i0*i1; PIN * NONINV 1 999 1 0 1 0 \ No newline at end of file diff --git a/tests/test_logic_sim.py b/tests/test_logic_sim.py index f173a41..2345de5 100644 --- a/tests/test_logic_sim.py +++ b/tests/test_logic_sim.py @@ -1,7 +1,7 @@ import numpy as np -from kyupy.logic_sim import LogicSim, LogicSim6V -from kyupy import bench, logic, sim +from kyupy.logic_sim import LogicSim, LogicSim2V, LogicSim6V +from kyupy import bench, logic, sim, verilog from kyupy.logic import mvarray, bparray, bp_to_mv, mv_to_bp def assert_equal_shape_and_contents(actual, desired): @@ -9,6 +9,21 @@ def assert_equal_shape_and_contents(actual, desired): assert actual.shape == desired.shape np.testing.assert_allclose(actual, desired) +def test_simprims(mydir): + c1 = verilog.load(mydir / 'all_kyupy_simprims.v') + c2 = verilog.load(mydir / 'all_kyupy_simprims.minimal.v') + pi_count = sum([len(n.ins)==0 for n in c1.io_nodes]) + sim1 = LogicSim2V(c1, sims=2**pi_count) + sim2 = LogicSim2V(c2, sims=2**pi_count) + tests1 = sim1.allocate() + tests1[sim1.pippi_s_locs] = np.fromfunction(lambda x, y: logic.ONE * ((2**x & y) != 0), (pi_count, sim1.sims), dtype=int) # exhaustive test + tests2 = tests1.copy() + sim1.simulate(tests1) + sim2.simulate(tests2) + np.testing.assert_array_equal( + tests1[c1.io_locs('o')], + tests2[c2.io_locs('o')]) + def test_2v(): c = bench.parse(f''' input(i3, i2, i1, i0)