|
|
@ -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): |
|
|
|