Browse Source

del deprecated sdf code, explicit tlib use

devel
Stefan Holst 1 year ago
parent
commit
cf9a98b5ce
  1. 170
      src/kyupy/sdf.py
  2. 6
      tests/conftest.py
  3. 8
      tests/test_sdf.py

170
src/kyupy/sdf.py

@ -16,7 +16,6 @@ from lark import Lark, Transformer
from . import log, readtext from . import log, readtext
from .circuit import Circuit from .circuit import Circuit
from .techlib import TechLib
Interconnect = namedtuple('Interconnect', ['orig', 'dest', 'r', 'f']) Interconnect = namedtuple('Interconnect', ['orig', 'dest', 'r', 'f'])
@ -28,14 +27,14 @@ class DelayFile:
""" """
def __init__(self, name, cells): def __init__(self, name, cells):
self.name = name self.name = name
self.interconnects = cells.get(None, None) self._interconnects = cells.get(None, None)
self.cells = dict((n, l) for n, l in cells.items() if n) self.cells = dict((n, l) for n, l in cells.items() if n)
def __repr__(self): def __repr__(self):
return '\n'.join(f'{n}: {l}' for n, l in self.cells.items()) + '\n' + \ return '\n'.join(f'{n}: {l}' for n, l in self.cells.items()) + '\n' + \
'\n'.join(str(i) for i in self.interconnects) '\n'.join(str(i) for i in self._interconnects)
def iopaths(self, circuit:Circuit, tlib=TechLib()): def iopaths(self, circuit:Circuit, tlib):
"""Constructs an ndarray containing all IOPATH delays. """Constructs an ndarray containing all IOPATH delays.
All IOPATH delays for a node ``n`` are annotated to the line connected to the input pin specified in the IOPATH. All IOPATH delays for a node ``n`` are annotated to the line connected to the input pin specified in the IOPATH.
@ -60,7 +59,8 @@ class DelayFile:
delays = np.zeros((len(circuit.lines), 2, 2, 3)) # dataset last during construction. delays = np.zeros((len(circuit.lines), 2, 2, 3)) # dataset last during construction.
for name, iopaths in self.cells.items(): for name, iopaths in self.cells.items():
if cell := find_cell(name): name = name.replace('\\', '')
if cell := circuit.cells.get(name, None):
for i_pin_spec, o_pin_spec, *dels in iopaths: for i_pin_spec, o_pin_spec, *dels in iopaths:
if i_pin_spec.startswith('(posedge '): i_pol_idxs = [0] if i_pin_spec.startswith('(posedge '): i_pol_idxs = [0]
elif i_pin_spec.startswith('(negedge '): i_pol_idxs = [1] elif i_pin_spec.startswith('(negedge '): i_pol_idxs = [1]
@ -75,135 +75,57 @@ class DelayFile:
return np.moveaxis(delays, -1, 0) return np.moveaxis(delays, -1, 0)
def annotation(self, circuit:Circuit, tlib=TechLib(), dataset=1, interconnect=True, ffdelays=True): def interconnects(self, circuit, tlib):
"""Constructs an 3-dimensional ndarray with timing data for each line in ``circuit``. """Constructs an ndarray containing all INTERCONNECT delays.
DEPRECATED
An IOPATH delay for a node is annotated to the line connected to the input pin specified in the IOPATH.
Currently, only ABSOLUTE IOPATH and INTERCONNECT delays are supported.
Pulse rejection limits are derived from absolute delays, explicit declarations (PATHPULSE etc.) are ignored.
:param circuit: The circuit to annotate. Names from the STIL file are matched to the node names.
:type circuit: :class:`~kyupy.circuit.Circuit`
:param tlib: A technology library object that provides pin name mappings.
:type tlib: :py:class:`~kyupy.techlib.TechLib`
:param dataset: SDFs store multiple values for each delay (e.g. minimum, typical, maximum).
An integer selects the dataset to use (default is 1 for 'typical').
If a tuple is given, the annotator will calculate the average of multiple datasets.
:type dataset: ``int`` or ``tuple``
:param interconnect: Whether or not to include the delays of interconnects in the annotation.
To properly annotate interconnect delays, the circuit model has to include a '__fork__' node on
every signal and every fanout-branch. The Verilog parser aids in this by setting the parameter
`branchforks=True` in :py:func:`kyupy.verilog.parse`.
:type interconnect: ``bool``
:param ffdelays: Whether or not to include the delays of flip-flops in the annotation.
:type ffdelays: ``bool``
:return: A 3-dimensional ndarray with timing data.
* Axis 0: line index.
* Axis 1: type of timing data: 0='delay', 1='pulse rejection limit'.
* Axis 2: The polarity of the output transition of the reading node: 0='rising', 1='falling'.
The polarity for pulse rejection is determined by the latter transition of the pulse.
E.g., ``timing[42, 1, 0]`` is the rejection limit of a negative pulse at the output
of the reader of line 42.
"""
def select_del(_delvals, idx):
if isinstance(dataset, tuple):
return sum(_delvals[idx][d] for d in dataset) / len(dataset)
return _delvals[idx][dataset]
def find_cell(name:str): To properly annotate interconnect delays, the circuit model has to include a '__fork__' node on
if name not in circuit.cells: name = name.replace('\\', '') every signal and every fanout-branch. The Verilog parser aids in this by setting the parameter
if name not in circuit.cells: name = name.replace('[', '_').replace(']', '_') `branchforks=True` in :py:func:`kyupy.verilog.parse` or :py:func:`kyupy.verilog.load`.
if name not in circuit.cells:
return None Limited support of SDF spec:
return circuit.cells[name] * ABSOLUTE delay values only
* two delvals per delval_list. First is rising/posedge, second is falling/negedge transition.
timing = np.zeros((len(circuit.lines), 2, 2)) * PATHPULSE declarations are ignored.
for cn, iopaths in self.cells.items():
for ipn, opn, *delvals in iopaths: * Axis 0: dataset (usually 3 datasets per SDF-file)
delvals = [d if len(d) > 0 else [0, 0, 0] for d in delvals] * Axis 1: line index. usually input line of a __fork__
if max(max(delvals)) == 0: * Axis 2: (axis of size 2 for compatability to IOPATH results. Values are broadcast along this axis.)
continue * Axis 3: polarity of the transition, 0='rising/posedge', 1='falling/negedge'
cell = find_cell(cn) """
if cell is None:
#log.warn(f'Cell from SDF not found in circuit: {cn}')
continue
ipn = re.sub(r'\((neg|pos)edge ([^)]+)\)', r'\2', ipn)
ipin = tlib.pin_index(cell.kind, ipn)
opin = tlib.pin_index(cell.kind, opn)
kind = cell.kind.lower()
def add_delays(_line):
if _line is not None:
timing[_line, :, 0] += select_del(delvals, 0)
timing[_line, :, 1] += select_del(delvals, 1)
take_avg = False
if kind.startswith('sdff'):
if not ipn.startswith('CLK'):
continue
if ffdelays and (len(cell.outs) > opin):
add_delays(cell.outs[opin])
else:
if ipin < len(cell.ins):
if kind.startswith(('xor', 'xnor')):
# print(ipn, ipin, times[cell.i_lines[ipin], 0, 0])
take_avg = timing[cell.ins[ipin]].sum() > 0
add_delays(cell.ins[ipin])
if take_avg:
timing[cell.ins[ipin]] /= 2
else:
log.warn(f'No line to annotate pin {ipn} of {cell}')
if not interconnect or self.interconnects is None: delays = np.zeros((len(circuit.lines), 2, 2, 3)) # dataset last during construction.
return timing
for n1, n2, *delvals in self.interconnects: for n1, n2, *delvals in self._interconnects:
delvals = [d if len(d) > 0 else [0, 0, 0] for d in delvals] delvals = [d if len(d) > 0 else [0, 0, 0] for d in delvals]
if max(max(delvals)) == 0: if max(max(delvals)) == 0: continue
continue cn1, pn1 = n1.split('/') if '/' in n1 else (n1, None)
if '/' in n1: cn2, pn2 = n2.split('/') if '/' in n2 else (n2, None)
i = n1.rfind('/') cn1 = cn1.replace('\\','')
cn1 = n1[0:i] cn2 = cn2.replace('\\','')
pn1 = n1[i+1:] c1, c2 = circuit.cells[cn1], circuit.cells[cn2]
else: p1 = tlib.pin_index(c1.kind, pn1) if pn1 is not None else 0
cn1, pn1 = (n1, 'Z') p2 = tlib.pin_index(c2.kind, pn2) if pn2 is not None else 0
if '/' in n2: if len(c1.outs) <= p1 or c1.outs[p1] is None:
i = n2.rfind('/') log.warn(f'No line to annotate pin {pn1} of {c1}')
cn2 = n2[0:i]
pn2 = n2[i+1:]
else:
cn2, pn2 = (n2, 'IN')
c1 = find_cell(cn1)
if c1 is None:
#log.warn(f'Cell from SDF not found in circuit: {cn1}')
continue
c2 = find_cell(cn2)
if c2 is None:
#log.warn(f'Cell from SDF not found in circuit: {cn2}')
continue continue
p1, p2 = tlib.pin_index(c1.kind, pn1), tlib.pin_index(c2.kind, pn2) if len(c2.ins) <= p2 or c2.ins[p2] is None:
line = None
if len(c2.ins) <= p2:
log.warn(f'No line to annotate pin {pn2} of {c2}') log.warn(f'No line to annotate pin {pn2} of {c2}')
continue continue
f1, f2 = c1.outs[p1].reader, c2.ins[p2].driver f1, f2 = c1.outs[p1].reader, c2.ins[p2].driver # find the forks between cells.
if f1 != f2: # possible branchfork assert f1.kind == '__fork__'
assert len(f2.ins) == 1 assert f2.kind == '__fork__'
if f1 != f2: # at least two forks, make sure f2 is a branchfork connected to f1
assert len(f2.outs) == 1
assert f1.outs[f2.ins[0].driver_pin] == f2.ins[0]
line = f2.ins[0] line = f2.ins[0]
assert f1.outs[f2.ins[0].driver_pin] == line elif len(f2.outs) == 1: # f1==f2, only OK when there is no fanout.
elif len(f2.outs) == 1: # no fanout?
line = f2.ins[0] line = f2.ins[0]
if line is not None:
timing[line, :, 0] += select_del(delvals, 0)
timing[line, :, 1] += select_del(delvals, 1)
else: else:
log.warn(f'No branchfork for annotating interconnect delay {c1.name}/{p1}->{c2.name}/{p2}') log.warn(f'No branchfork to annotate interconnect delay {c1.name}/{p1}->{c2.name}/{p2}')
return timing continue
delays[line, :] = delvals
return np.moveaxis(delays, -1, 0)
def sanitize(args): def sanitize(args):

6
tests/conftest.py

@ -10,9 +10,11 @@ def mydir():
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def b14_circuit(mydir): def b14_circuit(mydir):
from kyupy import verilog from kyupy import verilog
return verilog.load(mydir / 'b14.v.gz', branchforks=True) from kyupy.techlib import SAED32
return verilog.load(mydir / 'b14.v.gz', branchforks=True, tlib=SAED32)
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def b14_delays(mydir, b14_circuit): def b14_delays(mydir, b14_circuit):
from kyupy import sdf from kyupy import sdf
return sdf.load(mydir / 'b14.sdf.gz').iopaths(b14_circuit)[1:2] from kyupy.techlib import SAED32
return sdf.load(mydir / 'b14.sdf.gz').iopaths(b14_circuit, tlib=SAED32)[1:2]

8
tests/test_sdf.py

@ -2,6 +2,7 @@ import numpy as np
from kyupy import sdf, verilog, bench from kyupy import sdf, verilog, bench
from kyupy.wave_sim import WaveSim, TMAX, TMIN from kyupy.wave_sim import WaveSim, TMAX, TMIN
from kyupy.techlib import SAED32, SAED90
def test_parse(): def test_parse():
test = ''' test = '''
@ -80,9 +81,9 @@ def test_b14(mydir):
def test_gates(mydir): def test_gates(mydir):
c = verilog.load(mydir / 'gates.v') c = verilog.load(mydir / 'gates.v', tlib=SAED90)
df = sdf.load(mydir / 'gates.sdf') df = sdf.load(mydir / 'gates.sdf')
lt = df.iopaths(c)[1] lt = df.iopaths(c, tlib=SAED90)[1]
nand_a = c.cells['nandgate'].ins[0] nand_a = c.cells['nandgate'].ins[0]
nand_b = c.cells['nandgate'].ins[1] nand_b = c.cells['nandgate'].ins[1]
and_a = c.cells['andgate'].ins[0] and_a = c.cells['andgate'].ins[0]
@ -134,7 +135,8 @@ def test_nand_xor():
) )
) )
""") """)
d = df.iopaths(c)[1] d = df.iopaths(c, tlib=SAED32)[1]
c.resolve_tlib_cells(SAED32)
sim = WaveSim(c, delays=d, sims=16) sim = WaveSim(c, delays=d, sims=16)
# input A1 # input A1

Loading…
Cancel
Save