diff --git a/src/kyupy/logic_sim.py b/src/kyupy/logic_sim.py index aaceb3c..4357401 100644 --- a/src/kyupy/logic_sim.py +++ b/src/kyupy/logic_sim.py @@ -1,12 +1,24 @@ -"""A high-throughput combinational logic simulator. - -The class :py:class:`~kyupy.logic_sim.LogicSim` performs parallel simulations of the combinational part of a circuit. -The logic operations are performed bit-parallel on packed numpy arrays (see bit-parallel (bp) array description in :py:mod:`~kyupy.logic`). -Simple sequential circuits can be simulated by repeated assignments and propagations. -However, this simulator ignores the clock network and simply assumes that all state-elements are clocked all the time. +"""High-throughput combinational logic simulators. + +These simulators take batches of input assignments to a logic circuit and compute batches of output values. +Each batch is simulated in data-parallel fashion using packed numpy arrays (see bit-parallel (bp) array description in :py:mod:`~kyupy.logic`) for each signal in the circuit. +All simulations within a batch are mutually independent. +Inner simulation loops (that iterate over the topologically sorted circuit model) are just-in-time (JIT) compiled using numba. +JIT compiling triggered by the first simulation may take several seconds. +If numba is not available, inner loop is executed in the python interpreter and is significantly slower. + +Circuits passed to these simulators must contain only cells defined in the :py:class:`~kyupy.techlib.KYUPY` cell library. +Other circuits must be mapped to that library first using :py:method:`~kyupy.circuit.resolve_tlib_cells()`. + +Sequential circuits are supported with some caveats. +The simulation model is built by stripping out all flip-flops, clock, and asynchronous set/reset logic. +Values stored in the flip-flops become part of the input assignments and output values (pseudo-primary I/O). +Only the combinational portion is actually simulated. +Simulation results remain correct under the assumption that all flip-clops are clocked all the time and set/reset logic is passive. """ import math +import warnings import numpy as np @@ -17,6 +29,10 @@ from .circuit import Circuit, Line class LogicSim(sim.SimOps): """A bit-parallel naïve combinational simulator for 2-, 4-, or 8-valued logic. + .. deprecated:: + Use the specialized :py:class:`LogicSim2V`, :py:class:`LogicSim4V`, or :py:class:`LogicSim6V` + simulators instead. + :param circuit: The circuit to simulate. :param sims: The number of parallel logic simulations to perform. :param m: The arity of the logic, must be 2, 4, or 8. @@ -24,6 +40,9 @@ class LogicSim(sim.SimOps): :param strip_forks: If True, forks are not included in the simulation model to save memory and simulation time. """ def __init__(self, circuit: Circuit, sims: int = 8, m: int = 8, c_reuse: bool = False, strip_forks: bool = False): + warnings.warn( + 'LogicSim is deprecated; use LogicSim2V, LogicSim4V, or LogicSim6V instead.', + DeprecationWarning, stacklevel=2) assert m in [2, 4, 8] super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks) self.m = m @@ -473,7 +492,7 @@ class LogicSim4V(sim.SimOps): 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) + self.s_assign = np.full((self.s_len, self.sims), logic.UNASSIGNED, 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. @@ -481,7 +500,7 @@ class LogicSim4V(sim.SimOps): 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) + self.s_result = np.full((self.s_len, self.sims), logic.UNASSIGNED, 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. @@ -739,7 +758,7 @@ class LogicSim6V(sim.SimOps): nbytes = cdiv(sims, 8) self.c = np.zeros((self.c_len, 3, nbytes), dtype=np.uint8) - self.s = np.zeros((2, self.s_len, self.sims), dtype=np.uint8) + self.s = np.full((2, self.s_len, self.sims), logic.UNASSIGNED, dtype=np.uint8) """Logic values of the sequential elements (flip-flops) and ports. It is a pair of arrays in mv storage format: diff --git a/tests/test_logic_sim.py b/tests/test_logic_sim.py index b200aeb..ed4506e 100644 --- a/tests/test_logic_sim.py +++ b/tests/test_logic_sim.py @@ -2,7 +2,7 @@ import numpy as np from kyupy.logic_sim import LogicSim, LogicSim2V, LogicSim4V, LogicSim6V from kyupy import bench, logic, sim, verilog -from kyupy.logic import mvarray, bparray, bp_to_mv, mv_to_bp +from kyupy.logic import mv_str, mvarray, bparray, bp_to_mv, mv_to_bp from kyupy.techlib import SAED90, KYUPY def test_dangling(): @@ -150,14 +150,12 @@ def test_2v(): o31=OAI211(i0,i1,i2,i3) o32=MUX21(i0,i1,i2) ''') - s = LogicSim(c, 16, m=2) - bpa = logic.bparray([f'{i:04b}'+('-'*(s.s_len-4)) for i in range(16)]) - s.s[0] = bpa + s = LogicSim2V(c, 16) + s.s_assign[...] = logic.mvarray([f'{i:04b}'+('-'*(s.s_len-4)) for i in range(16)]) s.s_to_c() s.c_prop() s.c_to_s() - mva = logic.bp_to_mv(s.s[1]) - for res, exp in zip(logic.packbits(mva[4:], dtype=np.uint32), [ + for res, exp in zip(logic.packbits(s.s_result[4:], dtype=np.uint32), [ sim.BUF1, sim.INV1, sim.AND2, sim.AND3, sim.AND4, sim.NAND2, sim.NAND3, sim.NAND4, @@ -178,19 +176,17 @@ def test_2v(): def test_4v(): c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)') - s = LogicSim(c, 16, m=4) + s = LogicSim4V(c, 16) assert s.s_len == 5 - bpa = bparray( + s.s_assign[...] = mvarray( '00---', '01---', '0----', '0X---', '10---', '11---', '1----', '1X---', '-0---', '-1---', '-----', '-X---', 'X0---', 'X1---', 'X----', 'XX---') - s.s[0] = bpa s.s_to_c() s.c_prop() s.c_to_s() - mva = bp_to_mv(s.s[1]) - assert_equal_shape_and_contents(mva, mvarray( + assert_equal_shape_and_contents(s.s_result, mvarray( '--001', '--011', '--0X1', '--0X1', '--010', '--110', '--X10', '--X10', '--0XX', '--X1X', '--XXX', '--XXX', @@ -198,38 +194,34 @@ def test_4v(): def test_4v_fault(): c = bench.parse('input(x, y) output(a) a=and(x,y)') - s = LogicSim(c, 16, m=4) + s = LogicSim4V(c, 16) assert s.s_len == 3 - bpa = bparray( + s.s_assign[...] = mvarray( '00-', '01-', '0--', '0X-', '10-', '11-', '1--', '1X-', '-0-', '-1-', '---', '-X-', 'X0-', 'X1-', 'X--', 'XX-') - s.s[0] = bpa s.s_to_c() s.c_prop() s.c_to_s() - mva = bp_to_mv(s.s[1]) - assert_equal_shape_and_contents(mva, mvarray( + assert_equal_shape_and_contents(s.s_result, mvarray( '--0', '--0', '--0', '--0', '--0', '--1', '--X', '--X', '--0', '--X', '--X', '--X', '--0', '--X', '--X', '--X')) fault_line = s.circuit.cells['a'].ins[0] s.s_to_c() - s.c_prop(fault_line=fault_line, fault_model=1) + s.c_prop(fault_line=fault_line.index, fault_model=1) s.c_to_s() - mva = bp_to_mv(s.s[1]) - assert_equal_shape_and_contents(mva, mvarray( + assert_equal_shape_and_contents(s.s_result, mvarray( '--0', '--1', '--X', '--X', '--0', '--1', '--X', '--X', '--0', '--1', '--X', '--X', '--0', '--1', '--X', '--X')) s.s_to_c() - s.c_prop(fault_line=fault_line, fault_model=0) + s.c_prop(fault_line=fault_line.index, fault_model=0) s.c_to_s() - mva = bp_to_mv(s.s[1]) - assert_equal_shape_and_contents(mva, mvarray( + assert_equal_shape_and_contents(s.s_result, mvarray( '--0', '--0', '--0', '--0', '--0', '--0', '--0', '--0', '--0', '--0', '--0', '--0', @@ -255,11 +247,11 @@ def test_6v(): resp = s.s[1].copy() exp_resp = np.copy(mva) - exp_resp[:2] = logic.ZERO + exp_resp[:2] = logic.UNASSIGNED np.testing.assert_allclose(resp, exp_resp) -def test_8v(): +def xtest_8v(): c = bench.parse('input(x, y) output(a, o, n, xo) a=and(x,y) o=or(x,y) n=not(x) xo=xor(x,y)') s = LogicSim(c, 64, m=8) assert s.s_len == 6 @@ -286,7 +278,7 @@ def test_8v(): np.testing.assert_allclose(resp, exp_resp) -def test_loop(): +def xtest_loop(): c = bench.parse('q=DFF(d) d=NOT(q)') s = LogicSim(c, 4, m=8) assert s.s_len == 1 @@ -313,7 +305,7 @@ def test_loop(): # assert resp[3] == 'F' -def test_latch(): +def xtest_latch(): c = bench.parse('input(d, t) output(q) q=LATCH(d, t)') s = LogicSim(c, 8, m=8) assert s.s_len == 4 @@ -327,19 +319,6 @@ def test_latch(): # assert resp[i] == exp[i] -def test_b01(mydir): - c = bench.load(mydir / 'b01.bench') - - # 8-valued - s = LogicSim(c, 8, m=8) - mva = np.zeros((s.s_len, 8), dtype=np.uint8) - s.s[0] = mv_to_bp(mva) - s.s_to_c() - s.c_prop() - s.c_to_s() - bp_to_mv(s.s[1]) - - def sim_and_compare(c, test_resp, m=8): tests, resp = test_resp lsim = LogicSim(c, m=m, sims=tests.shape[1]) @@ -356,14 +335,14 @@ def sim_and_compare(c, test_resp, m=8): print(f'mismatch pattern:{pat} ppio:{idx} exp:{logic.mv_str(resp[idx,pat])} act:{logic.mv_str(resp_sim[idx,pat])}') assert len(idxs) == 0 -def sim_and_compare_6v(c, test_resp): +def sim_and_compare_cls(c, test_resp, SimCls): tests, resp = test_resp - lsim = LogicSim6V(c, sims=tests.shape[1]) - lsim.s[0] = tests + lsim = SimCls(c, sims=tests.shape[1]) + lsim.s_assign[...] = tests lsim.s_to_c() lsim.c_prop() lsim.c_to_s() - resp_sim = lsim.s[1] + resp_sim = lsim.s_result idxs, pats = np.nonzero(((resp == logic.ONE) & (resp_sim != logic.ONE)) | ((resp == logic.ZERO) & (resp_sim != logic.ZERO))) for i, (idx, pat) in enumerate(zip(idxs, pats)): if i >= 10: @@ -374,28 +353,32 @@ def sim_and_compare_6v(c, test_resp): def test_b15_2ig_sa_2v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp): - sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=2) + sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim2V) def test_b15_2ig_sa_4v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp): - sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=4) + sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim4V) def test_b15_2ig_sa_6v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp): - sim_and_compare_6v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp) + sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim6V) -def test_b15_2ig_sa_8v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp): +def xtest_b15_2ig_sa_8v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp): sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=8) def test_b15_4ig_sa_2v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp): - sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=2) + sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim2V) def test_b15_4ig_sa_4v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp): - sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=4) + sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim4V) + + +def test_b15_4ig_sa_6v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp): + sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim6V) -def test_b15_4ig_sa_8v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp): +def xtest_b15_4ig_sa_8v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp): sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=8) diff --git a/tests/test_wave_sim.py b/tests/test_wave_sim.py index b7b6676..8c26c20 100644 --- a/tests/test_wave_sim.py +++ b/tests/test_wave_sim.py @@ -1,7 +1,7 @@ import numpy as np from kyupy.wave_sim import WaveSim, WaveSimCuda, wave_eval_cpu, TMIN, TMAX -from kyupy.logic_sim import LogicSim +from kyupy.logic_sim import LogicSim6V from kyupy import logic, bench, sim from kyupy.logic import mvarray @@ -175,12 +175,12 @@ def compare_to_logic_sim(wsim: WaveSim): resp |= ((resp ^ (resp >> 1)) & 1) << 2 # transitions resp[wsim.pi_s_locs] = logic.UNASSIGNED - lsim = LogicSim(wsim.circuit, tests.shape[-1]) - lsim.s[0] = logic.mv_to_bp(tests) + lsim = LogicSim6V(wsim.circuit, tests.shape[-1]) + lsim.s_assign[...] = tests lsim.s_to_c() lsim.c_prop() lsim.c_to_s() - exp = logic.bp_to_mv(lsim.s[1])[:,:tests.shape[-1]] + exp = lsim.s_result[:,:tests.shape[-1]] resp[resp == logic.PPULSE] = logic.ZERO resp[resp == logic.NPULSE] = logic.ONE