Browse Source

techlib decides on cell type comb/dff/latch

devel
stefan 2 weeks ago
parent
commit
3a2cfb7788
  1. 13
      src/kyupy/circuit.py
  2. 11
      src/kyupy/sim.py
  3. 12
      src/kyupy/stil.py
  4. 25
      src/kyupy/techlib.py
  5. 6
      tests/test_circuit.py
  6. 8
      tests/test_logic_sim.py
  7. 2
      tests/test_stil.py

13
src/kyupy/circuit.py

@ -14,7 +14,6 @@ from __future__ import annotations @@ -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: @@ -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: @@ -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: @@ -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

11
src/kyupy/sim.py

@ -6,6 +6,7 @@ import numpy as np @@ -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: @@ -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: @@ -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: @@ -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: @@ -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]]

12
src/kyupy/stil.py

@ -106,7 +106,7 @@ class StilFile: @@ -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: @@ -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""" @@ -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.

25
src/kyupy/techlib.py

@ -38,9 +38,11 @@ class TechLib: @@ -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: @@ -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) ; @@ -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.
"""

6
tests/test_circuit.py

@ -2,7 +2,7 @@ import pickle @@ -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(): @@ -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'

8
tests/test_logic_sim.py

@ -3,7 +3,7 @@ import numpy as np @@ -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): @@ -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(): @@ -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(): @@ -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')

2
tests/test_stil.py

@ -14,7 +14,7 @@ def test_b15(mydir): @@ -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

Loading…
Cancel
Save