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
from collections import deque, defaultdict from collections import deque, defaultdict
import re import re
from typing import Union, Any
import numpy as np 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. Use the :class:`Node` constructor and :py:attr:`Node.remove()` to add and remove nodes.
""" """
@property def s_nodes(self, tlib: 'TechLib') -> list: # type: ignore
def s_nodes(self):
"""A list of all primary I/Os as well as all flip-flops and latches in the circuit (in that order). """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. 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. 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): def io_locs(self, prefix):
"""Returns a list of indices of primary I/Os that start with given name 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)) 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. """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. 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. 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[...]] def _locs(self, prefix, nodes:list[Node]) -> NestedNumericList: # can return list[list[...]]
d_top: NestedStrIntDict = dict() 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] while isinstance(l, list) and len(l) == 1 and isinstance(l[0], list): l = l[0]
return l return l
@property def stats(self, tlib: 'TechLib'): # type: ignore
def stats(self):
"""A dictionary with the counts of all different elements in the circuit. """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 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
from . import log from . import log
from .circuit import Circuit from .circuit import Circuit
from .techlib import KYUPY
BUF1 = np.uint16(0b1010_1010_1010_1010) BUF1 = np.uint16(0b1010_1010_1010_1010)
INV1 = ~BUF1 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): 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.circuit = circuit
self.s_len = len(circuit.s_nodes) self.s_len = len(circuit.s_nodes(KYUPY))
if isinstance(c_caps, int): if isinstance(c_caps, int):
c_caps = [c_caps] * (len(circuit.lines)+3) c_caps = [c_caps] * (len(circuit.lines)+3)
@ -187,8 +188,8 @@ class SimOps:
# ALAP-toposort the circuit into self.ops # ALAP-toposort the circuit into self.ops
levels = [] levels = []
ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes)) ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes(KYUPY)))
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 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 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 ] 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 ref_count[self.tmp2_idx] += 1
# allocate and keep memory for PI/PPI, keep memory for PO/PPO (allocated later) # 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 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 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 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] self.c_locs[lidx], self.c_caps[lidx] = self.c_locs[stem], self.c_caps[stem]
# copy memory location to PO/PPO area # 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: 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]] 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:
tests[pi_map, i] = logic.mvarray(p.capture['_pi'][0]) tests[pi_map, i] = logic.mvarray(p.capture['_pi'][0])
return tests 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. """Assembles and returns a LoC scan test pattern set for given circuit.
This function assumes a launch-on-capture (LoC) delay test. This function assumes a launch-on-capture (LoC) delay test.
@ -139,7 +139,9 @@ class StilFile:
init[scan_maps[si_port], i] = pattern 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]) 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) 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[0] = logic.mv_to_bp(init)
sim8v.s_to_c() sim8v.s_to_c()
sim8v.c_prop() 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.""" """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. """Parses the contents of ``file`` and returns a :class:`StilFile` object.
Files with `.gz`-suffix are decompressed on-the-fly. Files with `.gz`-suffix are decompressed on-the-fly.

25
src/kyupy/techlib.py

@ -38,9 +38,11 @@ class TechLib:
else: else:
pin_dict[n.name] = (o_idx, True) pin_dict[n.name] = (o_idx, True)
o_idx += 1 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] 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)]: 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): def pin_index(self, kind, pin):
"""Returns a pin list position for a given node kind and pin name.""" """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 kind in self.cells, f'Unknown cell: {kind}'
assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}' assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}'
return self.cells[kind][1][pin][1] 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""" 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) ; 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) ; MUX21 input(i0,i1,i2) output(o) o=MUX21(i0,i1,i2) ;
DFF input(D,CLK) output(Q) Q=DFF(D,CLK) ; 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. """A synthetic library of all KyuPy simulation primitives.
""" """

6
tests/test_circuit.py

@ -2,7 +2,7 @@ import pickle
from kyupy.circuit import GrowingList, Circuit, Node, Line from kyupy.circuit import GrowingList, Circuit, Node, Line
from kyupy import verilog, bench from kyupy import verilog, bench
from kyupy.techlib import SAED32 from kyupy.techlib import KYUPY, SAED32
def test_growing_list(): def test_growing_list():
gl = GrowingList() gl = GrowingList()
@ -155,7 +155,7 @@ def test_substitute():
def test_resolve(mydir): def test_resolve(mydir):
c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32) 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) 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' 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
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 mvarray, bparray, bp_to_mv, mv_to_bp
from kyupy.techlib import SAED90 from kyupy.techlib import SAED90, KYUPY
def test_dangling(): def test_dangling():
c = verilog.parse(''' c = verilog.parse('''
@ -105,7 +105,7 @@ def test_LogicSim6V_simprims(mydir):
sim1.simulate(tests1) sim1.simulate(tests1)
sim2.simulate(tests2) sim2.simulate(tests2)
for loc1, loc2 in zip(c1.io_locs('o'), c2.io_locs('o')): 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(): if (tests1[loc1] != tests2[loc2]).any():
print(f'Mismatch at output {n}') print(f'Mismatch at output {n}')
np.testing.assert_array_equal( np.testing.assert_array_equal(
@ -287,7 +287,7 @@ def test_8v():
def test_loop(): 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) s = LogicSim(c, 4, m=8)
assert s.s_len == 1 assert s.s_len == 1
mva = mvarray([['0'], ['1'], ['R'], ['F']]) mva = mvarray([['0'], ['1'], ['R'], ['F']])
@ -314,7 +314,7 @@ def test_loop():
def test_latch(): 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) s = LogicSim(c, 8, m=8)
assert s.s_len == 4 assert s.s_len == 4
mva = mvarray('00-0', '00-1', '01-0', '01-1', '10-0', '10-1', '11-0', '11-1') 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):
assert len(resp) > 0 assert len(resp) > 0
s2 = stil.load(mydir / 'b15_2ig.tf_nf.stil.gz') 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) resp = s2.responses(b15)
assert len(tests) > 0 assert len(tests) > 0
assert len(resp) > 0 assert len(resp) > 0

Loading…
Cancel
Save