diff --git a/src/kyupy/circuit.py b/src/kyupy/circuit.py index fd8dc05..638d311 100644 --- a/src/kyupy/circuit.py +++ b/src/kyupy/circuit.py @@ -14,7 +14,6 @@ from __future__ import annotations from collections import deque, defaultdict import re -from typing import Union, Any import numpy as np @@ -282,14 +281,13 @@ class Circuit: Use the :class:`Node` constructor and :py:attr:`Node.remove()` to add and remove nodes. """ - @property - def s_nodes(self): + def s_nodes(self, tlib: 'TechLib') -> list: # type: ignore """A list of all primary I/Os as well as all flip-flops and latches in the circuit (in that order). The s_nodes list defines the order of all ports and all sequential elements in the circuit. This list is constructed on-the-fly. If used in some inner toop, consider caching the list for better performance. """ - return list(self.io_nodes) + [n for n in self.nodes if 'dff' in n.kind.lower()] + [n for n in self.nodes if 'latch' in n.kind.lower()] + return list(self.io_nodes) + [n for n in self.nodes if tlib.is_dff(n.kind)] + [n for n in self.nodes if tlib.is_latch(n.kind)] def io_locs(self, prefix): """Returns a list of indices of primary I/Os that start with given name prefix. @@ -305,13 +303,13 @@ class Circuit: """ return self._locs(prefix, list(self.io_nodes)) - def s_locs(self, prefix): + def s_locs(self, prefix, tlib: 'TechLib'): # type: ignore """Returns the indices of I/Os and sequential elements that start with given name prefix. The returned values are used to index into the :py:attr:`s_nodes` list. It works the same as :py:attr:`io_locs`. See there for more details. """ - return self._locs(prefix, self.s_nodes) + return self._locs(prefix, self.s_nodes(tlib)) def _locs(self, prefix, nodes:list[Node]) -> NestedNumericList: # can return list[list[...]] d_top: NestedStrIntDict = dict() @@ -331,8 +329,7 @@ class Circuit: while isinstance(l, list) and len(l) == 1 and isinstance(l[0], list): l = l[0] return l - @property - def stats(self): + def stats(self, tlib: 'TechLib'): # type: ignore """A dictionary with the counts of all different elements in the circuit. The dictionary contains the number of all different kinds of nodes, the number diff --git a/src/kyupy/sim.py b/src/kyupy/sim.py index 0871a42..9c90305 100644 --- a/src/kyupy/sim.py +++ b/src/kyupy/sim.py @@ -6,6 +6,7 @@ import numpy as np from . import log from .circuit import Circuit +from .techlib import KYUPY BUF1 = np.uint16(0b1010_1010_1010_1010) INV1 = ~BUF1 @@ -167,7 +168,7 @@ class SimOps: """ def __init__(self, circuit: Circuit, c_caps=1, c_caps_min=1, a_ctrl=None, c_reuse=False, strip_forks=False): self.circuit = circuit - self.s_len = len(circuit.s_nodes) + self.s_len = len(circuit.s_nodes(KYUPY)) if isinstance(c_caps, int): c_caps = [c_caps] * (len(circuit.lines)+3) @@ -187,8 +188,8 @@ class SimOps: # ALAP-toposort the circuit into self.ops levels = [] - ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes)) - root_nodes = set([n for n in circuit.s_nodes if len(n.ins) > 0] + [n for n in circuit.nodes if len(n.outs) == 0]) # start from POs, PPOs, and any dangling nodes + ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes(KYUPY))) + root_nodes = set([n for n in circuit.s_nodes(KYUPY) if len(n.ins) > 0] + [n for n in circuit.nodes if len(n.outs) == 0]) # start from POs, PPOs, and any dangling nodes readers = np.array([1 if l.reader in root_nodes else len(l.reader.outs) for l in circuit.lines], dtype=np.int32) # for ref-counting forks level_lines = [n.ins[0] for n in root_nodes if len(n.ins) > 0 ] @@ -271,7 +272,7 @@ class SimOps: ref_count[self.tmp2_idx] += 1 # allocate and keep memory for PI/PPI, keep memory for PO/PPO (allocated later) - for i, n in enumerate(circuit.s_nodes): + for i, n in enumerate(circuit.s_nodes(KYUPY)): if 'dff' in n.kind.lower() or len(n.ins) == 0: # PPI or PI self.c_locs[self.ppi_offset + i], self.c_caps[self.ppi_offset + i] = h.alloc(c_caps_min), c_caps_min ref_count[self.ppi_offset + i] += 1 @@ -310,7 +311,7 @@ class SimOps: self.c_locs[lidx], self.c_caps[lidx] = self.c_locs[stem], self.c_caps[stem] # copy memory location to PO/PPO area - for i, n in enumerate(circuit.s_nodes): + for i, n in enumerate(circuit.s_nodes(KYUPY)): if len(n.ins) > 0: self.c_locs[self.ppo_offset + i], self.c_caps[self.ppo_offset + i] = self.c_locs[n.ins[0]], self.c_caps[n.ins[0]] diff --git a/src/kyupy/stil.py b/src/kyupy/stil.py index 0b217ee..6abb0b3 100644 --- a/src/kyupy/stil.py +++ b/src/kyupy/stil.py @@ -106,7 +106,7 @@ class StilFile: tests[pi_map, i] = logic.mvarray(p.capture['_pi'][0]) return tests - def tests_loc(self, circuit, init_filter=None, launch_filter=None): + def tests_loc(self, circuit, tlib, init_filter=None, launch_filter=None): """Assembles and returns a LoC scan test pattern set for given circuit. This function assumes a launch-on-capture (LoC) delay test. @@ -139,7 +139,9 @@ class StilFile: init[scan_maps[si_port], i] = pattern init[pi_map, i] = logic.mvarray(p.launch['_pi'][0] if '_pi' in p.launch else p.capture['_pi'][0]) if init_filter: init = init_filter(init) - sim8v = LogicSim(circuit, init.shape[-1], m=8) + circuit_resolved = circuit.copy() + circuit_resolved.resolve_tlib_cells(tlib) + sim8v = LogicSim(circuit_resolved, init.shape[-1], m=8) sim8v.s[0] = logic.mv_to_bp(init) sim8v.s_to_c() sim8v.c_prop() @@ -263,12 +265,12 @@ GRAMMAR = r""" """ -def parse(text): +def parse(text) -> StilFile: """Parses the given ``text`` and returns a :class:`StilFile` object.""" - return Lark(GRAMMAR, parser="lalr", transformer=StilTransformer()).parse(text) + return Lark(GRAMMAR, parser="lalr", transformer=StilTransformer()).parse(text) # type: ignore -def load(file): +def load(file) -> StilFile: """Parses the contents of ``file`` and returns a :class:`StilFile` object. Files with `.gz`-suffix are decompressed on-the-fly. diff --git a/src/kyupy/techlib.py b/src/kyupy/techlib.py index d740b8f..1fb0e39 100644 --- a/src/kyupy/techlib.py +++ b/src/kyupy/techlib.py @@ -38,9 +38,11 @@ class TechLib: else: pin_dict[n.name] = (o_idx, True) o_idx += 1 + has_dff = 'DFF' in set(n.kind for n in c.cells.values()) + has_latch = 'LATCH' in set(n.kind for n in c.cells.values()) parts = [s[1:-1].split(',') if s[0] == '{' else [s] for s in re.split(r'({[^}]+})', c.name) if len(s) > 0] for name in [''.join(item) for item in product(*parts)]: - self.cells[name] = (c, pin_dict) + self.cells[name] = (c, pin_dict, has_dff, has_latch) def pin_index(self, kind, pin): """Returns a pin list position for a given node kind and pin name.""" @@ -61,6 +63,26 @@ class TechLib: assert kind in self.cells, f'Unknown cell: {kind}' assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}' return self.cells[kind][1][pin][1] + + def is_dff(self, kind): + """Returns True, if given node kind is a d-flip-flop.""" + if kind == '__fork__': return False + if kind == '__const0__': return False + if kind == '__const1__': return False + if kind == 'input': return False + if kind == 'output': return False + assert kind in self.cells, f'Unknown cell: {kind}' + return self.cells[kind][2] + + def is_latch(self, kind): + """Returns True, if given node kind is a latch.""" + if kind == '__fork__': return False + if kind == '__const0__': return False + if kind == '__const1__': return False + if kind == 'input': return False + if kind == 'output': return False + assert kind in self.cells, f'Unknown cell: {kind}' + return self.cells[kind][3] KYUPY = TechLib(r""" @@ -98,6 +120,7 @@ AOI211 input(i0,i1,i2,i3) output(o) o=AOI211(i0,i1,i2,i3) ; OAI211 input(i0,i1,i2,i3) output(o) o=OAI211(i0,i1,i2,i3) ; MUX21 input(i0,i1,i2) output(o) o=MUX21(i0,i1,i2) ; DFF input(D,CLK) output(Q) Q=DFF(D,CLK) ; +LATCH input(D,CLK) output(Q) Q=LATCH(D,CLK) ; """) """A synthetic library of all KyuPy simulation primitives. """ diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 834c53f..0ea5144 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -2,7 +2,7 @@ import pickle from kyupy.circuit import GrowingList, Circuit, Node, Line from kyupy import verilog, bench -from kyupy.techlib import SAED32 +from kyupy.techlib import KYUPY, SAED32 def test_growing_list(): gl = GrowingList() @@ -155,7 +155,7 @@ def test_substitute(): def test_resolve(mydir): c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32) - s_names = [n.name for n in c.s_nodes] + s_names = [n.name for n in c.s_nodes(SAED32)] c.resolve_tlib_cells(SAED32) - s_names_prim = [n.name for n in c.s_nodes] + s_names_prim = [n.name for n in c.s_nodes(KYUPY)] assert s_names == s_names_prim, 'resolve_tlib_cells does not preserve names or order of s_nodes' diff --git a/tests/test_logic_sim.py b/tests/test_logic_sim.py index 72095a8..b200aeb 100644 --- a/tests/test_logic_sim.py +++ b/tests/test_logic_sim.py @@ -3,7 +3,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.techlib import SAED90 +from kyupy.techlib import SAED90, KYUPY def test_dangling(): c = verilog.parse(''' @@ -105,7 +105,7 @@ def test_LogicSim6V_simprims(mydir): sim1.simulate(tests1) sim2.simulate(tests2) for loc1, loc2 in zip(c1.io_locs('o'), c2.io_locs('o')): - n = c1.s_nodes[loc1] + n = c1.s_nodes(KYUPY)[loc1] if (tests1[loc1] != tests2[loc2]).any(): print(f'Mismatch at output {n}') np.testing.assert_array_equal( @@ -287,7 +287,7 @@ def test_8v(): def test_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) assert s.s_len == 1 mva = mvarray([['0'], ['1'], ['R'], ['F']]) @@ -314,7 +314,7 @@ def test_loop(): def test_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) assert s.s_len == 4 mva = mvarray('00-0', '00-1', '01-0', '01-1', '10-0', '10-1', '11-0', '11-1') diff --git a/tests/test_stil.py b/tests/test_stil.py index f179493..4485051 100644 --- a/tests/test_stil.py +++ b/tests/test_stil.py @@ -14,7 +14,7 @@ def test_b15(mydir): assert len(resp) > 0 s2 = stil.load(mydir / 'b15_2ig.tf_nf.stil.gz') - tests = s2.tests_loc(b15) + tests = s2.tests_loc(b15, SAED32) resp = s2.responses(b15) assert len(tests) > 0 assert len(resp) > 0