From cf9a98b5cec3b50295ec3aa2a44bf812778aee9e Mon Sep 17 00:00:00 2001 From: Stefan Holst Date: Mon, 10 Jul 2023 20:35:19 +0900 Subject: [PATCH] del deprecated sdf code, explicit tlib use --- src/kyupy/sdf.py | 170 +++++++++++++--------------------------------- tests/conftest.py | 6 +- tests/test_sdf.py | 8 ++- 3 files changed, 55 insertions(+), 129 deletions(-) diff --git a/src/kyupy/sdf.py b/src/kyupy/sdf.py index 0346adf..77a7274 100644 --- a/src/kyupy/sdf.py +++ b/src/kyupy/sdf.py @@ -16,7 +16,6 @@ from lark import Lark, Transformer from . import log, readtext from .circuit import Circuit -from .techlib import TechLib Interconnect = namedtuple('Interconnect', ['orig', 'dest', 'r', 'f']) @@ -28,14 +27,14 @@ class DelayFile: """ def __init__(self, name, cells): 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) def __repr__(self): 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. 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. 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: if i_pin_spec.startswith('(posedge '): i_pol_idxs = [0] elif i_pin_spec.startswith('(negedge '): i_pol_idxs = [1] @@ -75,135 +75,57 @@ class DelayFile: return np.moveaxis(delays, -1, 0) - def annotation(self, circuit:Circuit, tlib=TechLib(), dataset=1, interconnect=True, ffdelays=True): - """Constructs an 3-dimensional ndarray with timing data for each line in ``circuit``. - - 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 interconnects(self, circuit, tlib): + """Constructs an ndarray containing all INTERCONNECT delays. - def find_cell(name:str): - if name not in circuit.cells: name = name.replace('\\', '') - if name not in circuit.cells: name = name.replace('[', '_').replace(']', '_') - if name not in circuit.cells: - return None - return circuit.cells[name] - - timing = np.zeros((len(circuit.lines), 2, 2)) - for cn, iopaths in self.cells.items(): - for ipn, opn, *delvals in iopaths: - delvals = [d if len(d) > 0 else [0, 0, 0] for d in delvals] - if max(max(delvals)) == 0: - continue - 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}') + 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` or :py:func:`kyupy.verilog.load`. + + Limited support of SDF spec: + * ABSOLUTE delay values only + * two delvals per delval_list. First is rising/posedge, second is falling/negedge transition. + * PATHPULSE declarations are ignored. + + * Axis 0: dataset (usually 3 datasets per SDF-file) + * Axis 1: line index. usually input line of a __fork__ + * Axis 2: (axis of size 2 for compatability to IOPATH results. Values are broadcast along this axis.) + * Axis 3: polarity of the transition, 0='rising/posedge', 1='falling/negedge' + """ - if not interconnect or self.interconnects is None: - return timing + delays = np.zeros((len(circuit.lines), 2, 2, 3)) # dataset last during construction. - 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] - if max(max(delvals)) == 0: - continue - if '/' in n1: - i = n1.rfind('/') - cn1 = n1[0:i] - pn1 = n1[i+1:] - else: - cn1, pn1 = (n1, 'Z') - if '/' in n2: - i = n2.rfind('/') - 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}') + if max(max(delvals)) == 0: continue + cn1, pn1 = n1.split('/') if '/' in n1 else (n1, None) + cn2, pn2 = n2.split('/') if '/' in n2 else (n2, None) + cn1 = cn1.replace('\\','') + cn2 = cn2.replace('\\','') + c1, c2 = circuit.cells[cn1], circuit.cells[cn2] + p1 = tlib.pin_index(c1.kind, pn1) if pn1 is not None else 0 + p2 = tlib.pin_index(c2.kind, pn2) if pn2 is not None else 0 + if len(c1.outs) <= p1 or c1.outs[p1] is None: + log.warn(f'No line to annotate pin {pn1} of {c1}') continue - p1, p2 = tlib.pin_index(c1.kind, pn1), tlib.pin_index(c2.kind, pn2) - line = None - if len(c2.ins) <= p2: + if len(c2.ins) <= p2 or c2.ins[p2] is None: log.warn(f'No line to annotate pin {pn2} of {c2}') continue - f1, f2 = c1.outs[p1].reader, c2.ins[p2].driver - if f1 != f2: # possible branchfork - assert len(f2.ins) == 1 + f1, f2 = c1.outs[p1].reader, c2.ins[p2].driver # find the forks between cells. + assert f1.kind == '__fork__' + 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] - assert f1.outs[f2.ins[0].driver_pin] == line - elif len(f2.outs) == 1: # no fanout? + elif len(f2.outs) == 1: # f1==f2, only OK when there is no fanout. line = f2.ins[0] - if line is not None: - timing[line, :, 0] += select_del(delvals, 0) - timing[line, :, 1] += select_del(delvals, 1) else: - log.warn(f'No branchfork for annotating interconnect delay {c1.name}/{p1}->{c2.name}/{p2}') - return timing + log.warn(f'No branchfork to annotate interconnect delay {c1.name}/{p1}->{c2.name}/{p2}') + continue + delays[line, :] = delvals + + return np.moveaxis(delays, -1, 0) def sanitize(args): diff --git a/tests/conftest.py b/tests/conftest.py index e2b538e..0f6fce7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,9 +10,11 @@ def mydir(): @pytest.fixture(scope='session') def b14_circuit(mydir): 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') def b14_delays(mydir, b14_circuit): 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] diff --git a/tests/test_sdf.py b/tests/test_sdf.py index 6a47b86..f57a5a5 100644 --- a/tests/test_sdf.py +++ b/tests/test_sdf.py @@ -2,6 +2,7 @@ import numpy as np from kyupy import sdf, verilog, bench from kyupy.wave_sim import WaveSim, TMAX, TMIN +from kyupy.techlib import SAED32, SAED90 def test_parse(): test = ''' @@ -80,9 +81,9 @@ def test_b14(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') - lt = df.iopaths(c)[1] + lt = df.iopaths(c, tlib=SAED90)[1] nand_a = c.cells['nandgate'].ins[0] nand_b = c.cells['nandgate'].ins[1] 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) # input A1