prjxray/minitests/timing/create_timing_worksheet_db.py

519 lines
17 KiB
Python
Raw Normal View History

import argparse
import json
from openpyxl import Workbook, utils
from prjxray.tile import OutPinTiming, InPinTiming
from prjxray.timing import Outpin, Inpin, Wire, Buffer, \
PassTransistor, IntristicDelay, RcElement, PvtCorner
from prjxray.math_models import ExcelMathModel
from prjxray.db import Database
FAST = PvtCorner.FAST
SLOW = PvtCorner.SLOW
class TimingLookup(object):
def __init__(self, db, nodes):
self.db = db
self.grid = db.grid()
self.nodes = nodes
def try_find_site_pin(self, site_pin_node, node_idx):
site_pin_wire = self.nodes[site_pin_node]['wires'][node_idx]['name']
tile, wire_in_tile = site_pin_wire.split('/')
gridinfo = self.grid.gridinfo_at_tilename(tile)
tile_type = self.db.get_tile_type(gridinfo.tile_type)
for site in tile_type.get_sites():
for site_pin in site.site_pins:
if site_pin.wire == wire_in_tile:
return site_pin
return None
def find_site_pin(self, site_pin_node, node_idx):
site_pin = self.try_find_site_pin(site_pin_node, node_idx)
assert site_pin is not None, site_pin_node
return site_pin
def find_pip(self, pip_name):
tile, pip = pip_name.split('/')
gridinfo = self.grid.gridinfo_at_tilename(tile)
tile_type = self.db.get_tile_type(gridinfo.tile_type)
return tile_type.get_pip_by_name(pip)
def find_wire(self, wire_name):
tile, wire_in_tile = wire_name.split('/')
gridinfo = self.grid.gridinfo_at_tilename(tile)
tile_type = self.db.get_tile_type(gridinfo.tile_type)
return tile_type.wires[wire_in_tile]
def delays_to_cells(ws, row, delays, cells):
cells['FAST_MAX'] = 'E{}'.format(row)
cells['FAST_MIN'] = 'F{}'.format(row)
cells['SLOW_MAX'] = 'G{}'.format(row)
cells['SLOW_MIN'] = 'H{}'.format(row)
if delays is not None:
ws[cells['FAST_MAX']] = delays[FAST].max
ws[cells['FAST_MIN']] = delays[FAST].min
ws[cells['SLOW_MAX']] = delays[SLOW].max
ws[cells['SLOW_MIN']] = delays[SLOW].min
else:
ws[cells['FAST_MAX']] = 0
ws[cells['FAST_MIN']] = 0
ws[cells['SLOW_MAX']] = 0
ws[cells['SLOW_MIN']] = 0
def cells_to_delays(cells):
return {
FAST: IntristicDelay(min=cells['FAST_MIN'], max=cells['FAST_MAX']),
SLOW: IntristicDelay(min=cells['SLOW_MIN'], max=cells['SLOW_MAX']),
}
class Net(object):
def __init__(self, net):
self.net = net
self.ipin_nodes = {}
self.row = None
self.math = ExcelMathModel()
self.models = {}
for ipin in net['ipins']:
self.ipin_nodes[ipin['node']] = ipin
# Map of wire name to parent node
self.wire_to_node = {}
# Map of node name to node
self.node_name_to_node = {}
for node in net['nodes']:
self.node_name_to_node[node['name']] = node
for wire in node['wires']:
self.wire_to_node[wire['name']] = node
# Map of (src node, dst wire).
self.pips = {}
for pip in net['pips']:
src_node = self.wire_to_node[pip['src_wire']]['name']
dst_wire = pip['dst_wire'].split('/')[1]
self.pips[(src_node, dst_wire)] = pip
if not pip['is_directional']:
dst_node = self.wire_to_node[pip['dst_wire']]['name']
src_wire = pip['src_wire'].split('/')[1]
self.pips[(dst_node, src_wire)] = pip
def extend_rc_tree(self, ws, current_rc_root, timing_lookup, node):
rc_elements = []
for wire in node['wires']:
wire_timing = timing_lookup.find_wire(wire['name'])
ws['A{}'.format(self.row)] = wire['name']
ws['B{}'.format(self.row)] = 'Part of wire'
if wire_timing is not None:
cells = {}
cells['R'] = 'C{}'.format(self.row)
cells['C'] = 'D{}'.format(self.row)
ws[cells['R']] = wire_timing.resistance
ws[cells['C']] = wire_timing.capacitance
rc_elements.append(
RcElement(
resistance=cells['R'],
capacitance=cells['C'],
))
self.row += 1
wire_rc_node = Wire(rc_elements=rc_elements, math=self.math)
self.models[self.row - 1] = wire_rc_node
current_rc_root.set_sink_wire(wire_rc_node)
return wire_rc_node
def descend_route(
self,
ws,
timing_lookup,
current_node,
route,
route_idx,
current_rc_root,
was_opin=False):
""" Traverse the next pip, or recurse deeper. """
# descend_route should've consumed this token
assert route[route_idx] != '}'
while route[route_idx] == '{':
# Go deeper
route_idx = self.descend_route(
ws,
timing_lookup,
current_node,
route,
route_idx=route_idx + 1,
current_rc_root=current_rc_root,
was_opin=was_opin)
next_edge = (current_node, route[route_idx])
route_idx += 1
assert next_edge in self.pips, (next_edge, self.pips.keys())
pip = self.pips[next_edge]
is_backward = self.wire_to_node[
pip['dst_wire']]['name'] == current_node
if not is_backward:
assert self.wire_to_node[
pip['src_wire']]['name'] == current_node, (current_node, pip)
pip_info = timing_lookup.find_pip(pip['name'])
if not is_backward:
pip_timing = pip_info.timing
current_node = self.wire_to_node[pip['dst_wire']]['name']
else:
pip_timing = pip_info.backward_timing
current_node = self.wire_to_node[pip['src_wire']]['name']
ws['A{}'.format(self.row)] = pip['name']
cells = {}
cells['R'] = 'C{}'.format(self.row)
cells['C'] = 'D{}'.format(self.row)
delays_to_cells(
ws, row=self.row, delays=pip_timing.delays, cells=cells)
delays = cells_to_delays(cells)
if pip_info.is_pass_transistor:
ws['B{}'.format(self.row)] = 'PassTransistor'
ws[cells['R']] = pip_timing.drive_resistance
pip_model = PassTransistor(
drive_resistance=cells['R'],
delays=delays,
)
else:
ws['B{}'.format(self.row)] = 'Buffer'
if pip_timing.drive_resistance is not None:
ws[cells['R']] = pip_timing.drive_resistance
if pip_timing.drive_resistance == 0 and was_opin:
new_site_pin = timing_lookup.try_find_site_pin(
current_node, node_idx=0)
if new_site_pin is not None:
ws[cells['R']] = new_site_pin.timing.drive_resistance
else:
ws[cells['R']] = 0
if pip_timing.internal_capacitance is not None:
ws[cells['C']] = pip_timing.internal_capacitance
else:
ws[cells['C']] = 0
pip_model = Buffer(
drive_resistance=cells['R'],
internal_capacitance=cells['C'],
delays=cells_to_delays(cells),
)
self.models[self.row] = pip_model
current_rc_root.add_child(pip_model)
self.row += 1
if current_node in self.ipin_nodes:
assert route[route_idx] == '}'
route_idx += 1
node = self.node_name_to_node[current_node]
current_rc_root = self.extend_rc_tree(
ws, pip_model, timing_lookup, node)
if current_node in self.ipin_nodes:
ipin = self.ipin_nodes[current_node]
cells = {}
name = '{} to {}'.format(self.net['opin']['name'], ipin['name'])
ws['A{}'.format(self.row)] = ipin['name']
ws['B{}'.format(self.row)] = 'Inpin'
site_pin = timing_lookup.find_site_pin(ipin['node'], node_idx=-1)
assert isinstance(site_pin.timing, InPinTiming)
cells = {}
cells['C'] = 'D{}'.format(self.row)
delays_to_cells(
ws, row=self.row, delays=site_pin.timing.delays, cells=cells)
delays = cells_to_delays(cells)
ws[cells['C']] = site_pin.timing.capacitance
ipin_model = Inpin(
capacitance=cells['C'], delays=delays, name=name)
self.models[self.row] = ipin_model
current_rc_root.add_child(ipin_model)
self.row += 1
#Sum delays only (sum*1000)
#Sum delays + capacitive delay
#
#Total delay (from Vivado)
ws['A{}'.format(self.row)] = '{}: {} sum delays'.format(
self.net['net'], name)
self.row += 1
ws['A{}'.format(self.row)] = '{}: {}sum delays + cap delay'.format(
self.net['net'], name)
self.row += 2
ws['A{}'.format(
self.row)] = '{}: {}Total delay (from Vivado)'.format(
self.net['net'], name)
ws['E{}'.format(self.row)] = ipin['ic_delays']['FAST_MAX']
ws['F{}'.format(self.row)] = ipin['ic_delays']['FAST_MIN']
ws['G{}'.format(self.row)] = ipin['ic_delays']['SLOW_MAX']
ws['H{}'.format(self.row)] = ipin['ic_delays']['SLOW_MIN']
self.row += 2
return route_idx
else:
return self.descend_route(
ws,
timing_lookup,
current_node,
route,
route_idx=route_idx,
current_rc_root=current_rc_root)
def walk_route(self, ws, timing_lookup):
""" Walk route, creating rows in table.
First row will always be the OPIN, followed by the node/wire connected
to the OPIN. After a node/wire is always 1 or more pips. After a pip
is always a node/wire. A terminal node/wire will then reach an IPIN.
"""
self.row = 2
ws['A{}'.format(self.row)] = self.net['opin']['wire']
site_pin = timing_lookup.find_site_pin(
self.net['opin']['node'], node_idx=0)
assert isinstance(site_pin.timing, OutPinTiming)
ws['B{}'.format(self.row)] = 'Outpin'
ws['C{}'.format(self.row)] = site_pin.timing.drive_resistance
cells = {}
cells['R'] = 'C{}'.format(self.row)
delays_to_cells(
ws, row=self.row, delays=site_pin.timing.delays, cells=cells)
model_root = Outpin(
resistance=cells['R'], delays=cells_to_delays(cells))
self.models[self.row] = model_root
self.row += 1
node = self.net['opin']['node']
tile, first_wire = self.net['opin']['node'].split('/')
route = [r for r in self.net['route'].strip().split(' ') if r != '']
assert route[0] == '{'
assert route[1] == first_wire
node = self.node_name_to_node[node]
current_rc_root = self.extend_rc_tree(
ws, model_root, timing_lookup, node)
self.descend_route(
ws,
timing_lookup,
node['name'],
route,
route_idx=2,
current_rc_root=current_rc_root,
was_opin=True)
model_root.propigate_delays(self.math)
model_rows = {}
for row, model in self.models.items():
model_rows[id(model)] = row
for row, model in self.models.items():
rc_delay = model.get_rc_delay()
if rc_delay is not None:
ws['J{}'.format(row)] = self.math.eval(rc_delay)
downstream_cap = model.get_downstream_cap()
if downstream_cap is not None:
ws['I{}'.format(row)] = self.math.eval(downstream_cap)
if isinstance(model, Inpin):
ipin_results = {
'Name': model.name,
'truth': {},
'computed': {},
}
ipin_delays = {}
DELAY_COLS = (
('E', 'FAST_MAX'),
('F', 'FAST_MIN'),
('G', 'SLOW_MAX'),
('H', 'SLOW_MIN'),
)
for col, value in DELAY_COLS:
ipin_delays[value] = []
ipin_results['computed'][
value] = '{title}!{col}{row}'.format(
title=utils.quote_sheetname(ws.title),
col=col,
row=row + 2)
ipin_results['truth'][value] = '{title}!{col}{row}'.format(
title=utils.quote_sheetname(ws.title),
col=col,
row=row + 4)
rc_delays = []
for model in model.get_delays():
delays = model.get_intrinsic_delays()
if delays is not None:
ipin_delays['FAST_MAX'].append(delays[FAST].max)
ipin_delays['FAST_MIN'].append(delays[FAST].min)
ipin_delays['SLOW_MAX'].append(delays[SLOW].max)
ipin_delays['SLOW_MIN'].append(delays[SLOW].min)
if id(model) in model_rows:
rc_delays.append('J{}'.format(model_rows[id(model)]))
ws['J{}'.format(row + 1)] = self.math.eval(
self.math.sum(rc_delays))
for col, value in DELAY_COLS:
ws['{}{}'.format(col, row + 1)] = self.math.eval(
self.math.sum(ipin_delays[value]))
ws['{}{}'.format(
col, row + 2)] = '=1000*({col}{row} + J{row})'.format(
col=col, row=row + 1)
yield ipin_results
def add_net(wb, net, timing_lookup):
ws = wb.create_sheet(
title="Net {}".format(net['net'].replace('[', '_').replace(']', '_')))
# Header
ws['A1'] = 'Name'
ws['B1'] = 'Type'
ws['C1'] = 'RES'
ws['D1'] = 'CAP'
ws['E1'] = 'FAST_MAX'
ws['F1'] = 'FAST_MIN'
ws['G1'] = 'SLOW_MAX'
ws['H1'] = 'SLOW_MIN'
ws['I1'] = 'Downstream C'
ws['J1'] = 'Delay from cap'
net_obj = Net(net)
yield from net_obj.walk_route(ws, timing_lookup)
def main():
parser = argparse.ArgumentParser(
description="Create timing worksheet for 7-series timing analysis.")
parser.add_argument('--timing_json', required=True)
parser.add_argument('--db_root', required=True)
parser.add_argument('--output_xlsx', required=True)
args = parser.parse_args()
with open(args.timing_json) as f:
timing = json.load(f)
db = Database(args.db_root)
nodes = {}
for net in timing:
for node in net['nodes']:
nodes[node['name']] = node
timing_lookup = TimingLookup(db, nodes)
wb = Workbook()
summary_ws = wb.get_sheet_by_name(wb.sheetnames[0])
summary_ws.title = 'Summary'
summary_ws['A1'] = 'Name'
cols = ['FAST_MAX', 'FAST_MIN', 'SLOW_MAX', 'SLOW_MIN']
cur_col = 'B'
for col in cols:
summary_ws['{}1'.format(cur_col)] = col
cur_col = chr(ord(cur_col) + 1)
summary_ws['{}1'.format(cur_col)] = 'Computed ' + col
cur_col = chr(ord(cur_col) + 3)
summary_row = 2
for net in timing:
if '<' in net['route']:
print(
"WARNING: Skipping net {} because it has complicated route description."
.format(net['net']))
continue
for summary_cells in add_net(wb, net, timing_lookup):
summary_ws['A{}'.format(summary_row)] = summary_cells['Name']
cur_col = 'B'
for col in cols:
truth_col = chr(ord(cur_col) + 0)
computed_col = chr(ord(cur_col) + 1)
error_col = chr(ord(cur_col) + 2)
error_per_col = chr(ord(cur_col) + 3)
summary_ws['{}{}'.format(
truth_col,
summary_row)] = '=' + summary_cells['truth'][col]
summary_ws['{}{}'.format(
computed_col,
summary_row)] = '=' + summary_cells['computed'][col]
summary_ws['{}{}'.format(
error_col,
summary_row)] = '={truth}{row}-{comp}{row}'.format(
truth=truth_col, comp=computed_col, row=summary_row)
summary_ws['{}{}'.format(
error_per_col,
summary_row)] = '={error}{row}/{truth}{row}'.format(
error=error_col, truth=truth_col, row=summary_row)
cur_col = chr(ord(cur_col) + 4)
summary_row += 1
wb.save(filename=args.output_xlsx)
if __name__ == "__main__":
main()