Browse Source

logic_sim: better docs, deprecate LogicSim, move tests to newer classes

devel
stefan 1 week ago
parent
commit
8aa0f09254
  1. 37
      src/kyupy/logic_sim.py
  2. 83
      tests/test_logic_sim.py
  3. 8
      tests/test_wave_sim.py

37
src/kyupy/logic_sim.py

@ -1,12 +1,24 @@
"""A high-throughput combinational logic simulator. """High-throughput combinational logic simulators.
The class :py:class:`~kyupy.logic_sim.LogicSim` performs parallel simulations of the combinational part of a circuit. These simulators take batches of input assignments to a logic circuit and compute batches of output values.
The logic operations are performed bit-parallel on packed numpy arrays (see bit-parallel (bp) array description in :py:mod:`~kyupy.logic`). 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.
Simple sequential circuits can be simulated by repeated assignments and propagations. All simulations within a batch are mutually independent.
However, this simulator ignores the clock network and simply assumes that all state-elements are clocked all the time. 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 math
import warnings
import numpy as np import numpy as np
@ -17,6 +29,10 @@ from .circuit import Circuit, Line
class LogicSim(sim.SimOps): class LogicSim(sim.SimOps):
"""A bit-parallel naïve combinational simulator for 2-, 4-, or 8-valued logic. """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 circuit: The circuit to simulate.
:param sims: The number of parallel logic simulations to perform. :param sims: The number of parallel logic simulations to perform.
:param m: The arity of the logic, must be 2, 4, or 8. :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. :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): 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] assert m in [2, 4, 8]
super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks) super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks)
self.m = m self.m = m
@ -473,7 +492,7 @@ class LogicSim4V(sim.SimOps):
Storage locations are indirectly addressed. Storage locations are indirectly addressed.
Data for line `l` is in `self.c[self.c_locs[l]]`. 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. """Logic values assigned to the ports and flip-flops of the circuit.
The simulator reads (P)PI values from here. The simulator reads (P)PI values from here.
Values assigned to PO positions are ignored. 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 First index is the port position (defined by `self.circuit.s_nodes`), second
index is the pattern index (0 ... `self.sims-1`). 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. """Logic values at the ports and flip-flops of the circuit after simulation.
The simulator writes (P)PO values here. The simulator writes (P)PO values here.
Values assigned to PI positions are ignored. Values assigned to PI positions are ignored.
@ -739,7 +758,7 @@ class LogicSim6V(sim.SimOps):
nbytes = cdiv(sims, 8) nbytes = cdiv(sims, 8)
self.c = np.zeros((self.c_len, 3, nbytes), dtype=np.uint8) 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. """Logic values of the sequential elements (flip-flops) and ports.
It is a pair of arrays in mv storage format: It is a pair of arrays in mv storage format:

83
tests/test_logic_sim.py

@ -2,7 +2,7 @@ import numpy as np
from kyupy.logic_sim import LogicSim, LogicSim2V, LogicSim4V, LogicSim6V from kyupy.logic_sim import LogicSim, LogicSim2V, LogicSim4V, LogicSim6V
from kyupy import bench, logic, sim, verilog 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 from kyupy.techlib import SAED90, KYUPY
def test_dangling(): def test_dangling():
@ -150,14 +150,12 @@ def test_2v():
o31=OAI211(i0,i1,i2,i3) o31=OAI211(i0,i1,i2,i3)
o32=MUX21(i0,i1,i2) o32=MUX21(i0,i1,i2)
''') ''')
s = LogicSim(c, 16, m=2) s = LogicSim2V(c, 16)
bpa = logic.bparray([f'{i:04b}'+('-'*(s.s_len-4)) for i in range(16)]) s.s_assign[...] = logic.mvarray([f'{i:04b}'+('-'*(s.s_len-4)) for i in range(16)])
s.s[0] = bpa
s.s_to_c() s.s_to_c()
s.c_prop() s.c_prop()
s.c_to_s() s.c_to_s()
mva = logic.bp_to_mv(s.s[1]) for res, exp in zip(logic.packbits(s.s_result[4:], dtype=np.uint32), [
for res, exp in zip(logic.packbits(mva[4:], dtype=np.uint32), [
sim.BUF1, sim.INV1, sim.BUF1, sim.INV1,
sim.AND2, sim.AND3, sim.AND4, sim.AND2, sim.AND3, sim.AND4,
sim.NAND2, sim.NAND3, sim.NAND4, sim.NAND2, sim.NAND3, sim.NAND4,
@ -178,19 +176,17 @@ def test_2v():
def test_4v(): def test_4v():
c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)') 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 assert s.s_len == 5
bpa = bparray( s.s_assign[...] = mvarray(
'00---', '01---', '0----', '0X---', '00---', '01---', '0----', '0X---',
'10---', '11---', '1----', '1X---', '10---', '11---', '1----', '1X---',
'-0---', '-1---', '-----', '-X---', '-0---', '-1---', '-----', '-X---',
'X0---', 'X1---', 'X----', 'XX---') 'X0---', 'X1---', 'X----', 'XX---')
s.s[0] = bpa
s.s_to_c() s.s_to_c()
s.c_prop() s.c_prop()
s.c_to_s() s.c_to_s()
mva = bp_to_mv(s.s[1]) assert_equal_shape_and_contents(s.s_result, mvarray(
assert_equal_shape_and_contents(mva, mvarray(
'--001', '--011', '--0X1', '--0X1', '--001', '--011', '--0X1', '--0X1',
'--010', '--110', '--X10', '--X10', '--010', '--110', '--X10', '--X10',
'--0XX', '--X1X', '--XXX', '--XXX', '--0XX', '--X1X', '--XXX', '--XXX',
@ -198,38 +194,34 @@ def test_4v():
def test_4v_fault(): def test_4v_fault():
c = bench.parse('input(x, y) output(a) a=and(x,y)') 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 assert s.s_len == 3
bpa = bparray( s.s_assign[...] = mvarray(
'00-', '01-', '0--', '0X-', '00-', '01-', '0--', '0X-',
'10-', '11-', '1--', '1X-', '10-', '11-', '1--', '1X-',
'-0-', '-1-', '---', '-X-', '-0-', '-1-', '---', '-X-',
'X0-', 'X1-', 'X--', 'XX-') 'X0-', 'X1-', 'X--', 'XX-')
s.s[0] = bpa
s.s_to_c() s.s_to_c()
s.c_prop() s.c_prop()
s.c_to_s() s.c_to_s()
mva = bp_to_mv(s.s[1]) assert_equal_shape_and_contents(s.s_result, mvarray(
assert_equal_shape_and_contents(mva, mvarray(
'--0', '--0', '--0', '--0', '--0', '--0', '--0', '--0',
'--0', '--1', '--X', '--X', '--0', '--1', '--X', '--X',
'--0', '--X', '--X', '--X', '--0', '--X', '--X', '--X',
'--0', '--X', '--X', '--X')) '--0', '--X', '--X', '--X'))
fault_line = s.circuit.cells['a'].ins[0] fault_line = s.circuit.cells['a'].ins[0]
s.s_to_c() 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() s.c_to_s()
mva = bp_to_mv(s.s[1]) assert_equal_shape_and_contents(s.s_result, mvarray(
assert_equal_shape_and_contents(mva, mvarray(
'--0', '--1', '--X', '--X', '--0', '--1', '--X', '--X',
'--0', '--1', '--X', '--X', '--0', '--1', '--X', '--X',
'--0', '--1', '--X', '--X', '--0', '--1', '--X', '--X',
'--0', '--1', '--X', '--X')) '--0', '--1', '--X', '--X'))
s.s_to_c() 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() s.c_to_s()
mva = bp_to_mv(s.s[1]) assert_equal_shape_and_contents(s.s_result, mvarray(
assert_equal_shape_and_contents(mva, mvarray(
'--0', '--0', '--0', '--0', '--0', '--0', '--0', '--0',
'--0', '--0', '--0', '--0', '--0', '--0', '--0', '--0',
'--0', '--0', '--0', '--0', '--0', '--0', '--0', '--0',
@ -255,11 +247,11 @@ def test_6v():
resp = s.s[1].copy() resp = s.s[1].copy()
exp_resp = np.copy(mva) exp_resp = np.copy(mva)
exp_resp[:2] = logic.ZERO exp_resp[:2] = logic.UNASSIGNED
np.testing.assert_allclose(resp, exp_resp) 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)') 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) s = LogicSim(c, 64, m=8)
assert s.s_len == 6 assert s.s_len == 6
@ -286,7 +278,7 @@ def test_8v():
np.testing.assert_allclose(resp, exp_resp) np.testing.assert_allclose(resp, exp_resp)
def test_loop(): def xtest_loop():
c = bench.parse('q=DFF(d) d=NOT(q)') c = bench.parse('q=DFF(d) d=NOT(q)')
s = LogicSim(c, 4, m=8) s = LogicSim(c, 4, m=8)
assert s.s_len == 1 assert s.s_len == 1
@ -313,7 +305,7 @@ def test_loop():
# assert resp[3] == 'F' # assert resp[3] == 'F'
def test_latch(): def xtest_latch():
c = bench.parse('input(d, t) output(q) q=LATCH(d, t)') c = bench.parse('input(d, t) output(q) q=LATCH(d, t)')
s = LogicSim(c, 8, m=8) s = LogicSim(c, 8, m=8)
assert s.s_len == 4 assert s.s_len == 4
@ -327,19 +319,6 @@ def test_latch():
# assert resp[i] == exp[i] # 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): def sim_and_compare(c, test_resp, m=8):
tests, resp = test_resp tests, resp = test_resp
lsim = LogicSim(c, m=m, sims=tests.shape[1]) 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])}') 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 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 tests, resp = test_resp
lsim = LogicSim6V(c, sims=tests.shape[1]) lsim = SimCls(c, sims=tests.shape[1])
lsim.s[0] = tests lsim.s_assign[...] = tests
lsim.s_to_c() lsim.s_to_c()
lsim.c_prop() lsim.c_prop()
lsim.c_to_s() 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))) 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)): for i, (idx, pat) in enumerate(zip(idxs, pats)):
if i >= 10: 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): 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): 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): 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) 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): 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): 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) sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=8)

8
tests/test_wave_sim.py

@ -1,7 +1,7 @@
import numpy as np import numpy as np
from kyupy.wave_sim import WaveSim, WaveSimCuda, wave_eval_cpu, TMIN, TMAX 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 import logic, bench, sim
from kyupy.logic import mvarray from kyupy.logic import mvarray
@ -175,12 +175,12 @@ def compare_to_logic_sim(wsim: WaveSim):
resp |= ((resp ^ (resp >> 1)) & 1) << 2 # transitions resp |= ((resp ^ (resp >> 1)) & 1) << 2 # transitions
resp[wsim.pi_s_locs] = logic.UNASSIGNED resp[wsim.pi_s_locs] = logic.UNASSIGNED
lsim = LogicSim(wsim.circuit, tests.shape[-1]) lsim = LogicSim6V(wsim.circuit, tests.shape[-1])
lsim.s[0] = logic.mv_to_bp(tests) lsim.s_assign[...] = tests
lsim.s_to_c() lsim.s_to_c()
lsim.c_prop() lsim.c_prop()
lsim.c_to_s() 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.PPULSE] = logic.ZERO
resp[resp == logic.NPULSE] = logic.ONE resp[resp == logic.NPULSE] = logic.ONE

Loading…
Cancel
Save