| #!/usr/bin/env python3 |
| import lxml.etree as ET |
| import argparse |
| from sdf_timing import sdfparse |
| from sdf_timing.utils import get_scale_seconds |
| from lib.pb_type import get_pb_type_chain |
| import re |
| import os |
| import sys |
| |
| # Adds output to stderr to track if timing data for a particular BEL was found |
| # in bels.json |
| DEBUG = False |
| |
| |
| def eprint(*args, **kwargs): |
| print(*args, file=sys.stderr, **kwargs) |
| |
| |
| def mergedicts(source, destination): |
| """This function recursively merges two dictionaries: |
| `source` into `destination""" |
| for key, value in source.items(): |
| if isinstance(value, dict): |
| # get node or create one |
| node = destination.setdefault(key, {}) |
| mergedicts(value, node) |
| else: |
| destination[key] = value |
| |
| return destination |
| |
| |
| def remove_site_number(site): |
| """Some sites are numbered in the VPR arch definitions. |
| This happens for e.g. SLICE0. This function removes |
| trailing numbers from the name""" |
| number = re.search(r'\d+$', site) |
| if number is not None: |
| site = site[:-len(str(number.group()))] |
| return site |
| |
| |
| def gen_all_possibilities(pattern): |
| """ |
| Generates all possible combinations of a pattern if it contains a |
| wildcard string in braces eg. "LUT[ABCD]" will yield in "LUTA", "LUTB" |
| and so on. |
| |
| >>> list(gen_all_possibilities("LUT")) |
| ['LUT'] |
| |
| >>> list(gen_all_possibilities("LUT[ABCD]")) |
| ['LUTA', 'LUTB', 'LUTC', 'LUTD'] |
| """ |
| |
| # Match the regex |
| match = re.match(r"(.*)\[([A-Za-z0-9]+)\](.*)", pattern) |
| |
| # Generate combinations |
| if match is not None: |
| for c in match.group(2): |
| yield match.group(1) + c + match.group(3) |
| |
| # Not a regex |
| else: |
| yield pattern |
| |
| |
| def get_cell_types_and_instances(bel, location, site, bels): |
| """This function searches for a bel type and instance |
| translation between VPR and Vivado. The translation |
| is defined in the `bels` dictionary. If translation |
| is found a list of celltypes and bel instances is returned, |
| None otherwise""" |
| if site not in bels: |
| if DEBUG: |
| eprint( |
| "Site '{}' not found among '{}'".format( |
| site, ", ".join(bels.keys()) |
| ) |
| ) |
| return None |
| if bel not in bels[site]: |
| if DEBUG: |
| eprint( |
| "Bel '{}' not found among '{}'".format( |
| bel, ", ".join(bels[site].keys()) |
| ) |
| ) |
| return None |
| if location not in bels[site][bel]: |
| if DEBUG: |
| eprint( |
| "Location '{}' not found among '{}'".format( |
| location, ", ".join(bels[site][bel].keys()) |
| ) |
| ) |
| return None |
| |
| # Generate a list of tuples (celltype, instance) |
| cells = [] |
| for pattern in bels[site][bel][location]: |
| for names in gen_all_possibilities(pattern): |
| cells.append(tuple(names.split("."))) |
| |
| return cells |
| |
| |
| def find_timings(timings, bel, location, site, bels, corner, speed_type): |
| """This function returns all the timings associated with |
| the selected `bel` in `location` and `site`. If timings |
| are not found, empty dict is returned""" |
| |
| def get_timing(cell, delay, corner, speed_type): |
| """ |
| Gets timing for a particular cornet case. If not fount then chooses |
| the next best one. |
| """ |
| entries = cell[delay]['delay_paths'][corner.lower()] |
| entry = entries.get(speed_type, None) |
| |
| if speed_type == 'min': |
| if entry is None: |
| entry = entries.get('avg', None) |
| if entry is None: |
| entry = entries.get('max', None) |
| |
| elif speed_type == 'avg': |
| if entry is None: |
| entry = entries.get('max', None) |
| if entry is None: |
| entry = entries.get('min', None) |
| |
| elif speed_type == 'max': |
| if entry is None: |
| entry = entries.get('avg', None) |
| if entry is None: |
| entry = entries.get('min', None) |
| |
| if entry is None: |
| # if we failed with desired corner, try the opposite |
| newcorner = 'FAST' if corner == 'SLOW' else 'SLOW' |
| entry = get_timing(cell, delay, newcorner, speed_type) |
| assert entry is not None, (delay, corner, speed_type) |
| return entry |
| |
| # Get cells, reverse the list so former timings will be overwritten by |
| # latter ones. |
| cells = get_cell_types_and_instances(bel, location, site, bels) |
| if cells is None: |
| return None |
| |
| cells.reverse() |
| |
| # Gather CELLs |
| cell = dict() |
| for ct, inst in cells: |
| cell = mergedicts(timings['cells'][ct][inst], cell) |
| |
| # Gather timings |
| bel_timings = dict() |
| for delay in cell: |
| if cell[delay]['is_absolute']: |
| entry = get_timing(cell, delay, corner.lower(), speed_type) |
| elif cell[delay]['is_timing_check']: |
| if cell[delay]['type'] == "setuphold": |
| # 'setup' and 'hold' are identical |
| entry = get_timing(cell, delay, 'setup', speed_type) |
| else: |
| entry = get_timing(cell, delay, 'nominal', speed_type) |
| bel_timings[delay] = float(entry) * get_scale_seconds('1 ns') |
| |
| return bel_timings |
| |
| |
| def get_bel_timings(element, timings, bels, corner, speed_type): |
| """This function returns all the timings for an arch.xml |
| `element`. It determines the bel location by traversing |
| the pb_type chain""" |
| pb_chain = get_pb_type_chain(element) |
| if len(pb_chain) == 1: |
| return None |
| |
| if 'max' in element.attrib and element.attrib['max'].startswith( |
| '{interconnect'): |
| bel = 'ROUTING_BEL' |
| else: |
| bel = pb_chain[-1] |
| location = pb_chain[-2] |
| site = remove_site_number(pb_chain[1]) |
| |
| result = find_timings( |
| timings, bel, location, site, bels, corner, speed_type |
| ) |
| |
| if DEBUG: |
| print(site, bel, location, result is not None, file=sys.stderr) |
| |
| return result |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| |
| parser.add_argument( |
| '--input_arch', required=True, help="Input arch.xml file" |
| ) |
| parser.add_argument('--sdf_dir', required=True, help="SDF files directory") |
| parser.add_argument( |
| '--out_arch', required=True, help="Output arch.xml file" |
| ) |
| parser.add_argument( |
| '--bels_map', |
| required=True, |
| help="VPR <-> timing info bels mapping json file" |
| ) |
| |
| args = parser.parse_args() |
| |
| arch_xml = ET.ElementTree() |
| root_element = arch_xml.parse(args.input_arch) |
| |
| # read bels json |
| import json |
| with open(args.bels_map, 'r') as fp: |
| bels = json.load(fp) |
| |
| timings = dict() |
| files = os.listdir(args.sdf_dir) |
| for f in files: |
| if not f.endswith('.sdf'): |
| continue |
| with open(args.sdf_dir + '/' + f, 'r') as fp: |
| try: |
| tmp = sdfparse.parse(fp.read()) |
| except Exception as ex: |
| print("{}:".format(args.sdf_dir + '/' + f), file=sys.stderr) |
| print(repr(ex), file=sys.stderr) |
| raise |
| mergedicts(tmp, timings) |
| |
| if DEBUG: |
| with open("/tmp/dump.json", 'w') as fp: |
| json.dump(timings, fp, indent=4) |
| |
| for dm in root_element.iter('delay_matrix'): |
| if dm.attrib['type'] == 'max': |
| bel_timings = get_bel_timings(dm, timings, bels, 'SLOW', 'max') |
| elif dm.attrib['type'] == 'min': |
| bel_timings = get_bel_timings(dm, timings, bels, 'FAST', 'min') |
| else: |
| assert dm.attrib['type'] |
| |
| if bel_timings is None: |
| continue |
| |
| dm.text = dm.text.format(**bel_timings) |
| |
| for dc in root_element.iter('delay_constant'): |
| format_s = dc.attrib['max'] |
| max_tim = get_bel_timings(dc, timings, bels, 'SLOW', 'max') |
| if max_tim is not None: |
| dc.attrib['max'] = format_s.format(**max_tim) |
| |
| min_tim = get_bel_timings(dc, timings, bels, 'FAST', 'min') |
| if min_tim is not None: |
| dc.attrib['min'] = format_s.format(**min_tim) |
| |
| for tq in root_element.iter('T_clock_to_Q'): |
| format_s = tq.attrib['max'] |
| max_tim = get_bel_timings(tq, timings, bels, 'SLOW', 'max') |
| if max_tim is not None: |
| tq.attrib['max'] = format_s.format(**max_tim) |
| |
| min_tim = get_bel_timings(tq, timings, bels, 'FAST', 'min') |
| if min_tim is not None: |
| tq.attrib['min'] = format_s.format(**min_tim) |
| |
| for ts in root_element.iter('T_setup'): |
| bel_timings = get_bel_timings(ts, timings, bels, 'SLOW', 'max') |
| if bel_timings is None: |
| continue |
| ts.attrib['value'] = ts.attrib['value'].format(**bel_timings) |
| |
| for th in root_element.iter('T_hold'): |
| bel_timings = get_bel_timings(th, timings, bels, 'FAST', 'min') |
| if bel_timings is None: |
| continue |
| th.attrib['value'] = th.attrib['value'].format(**bel_timings) |
| |
| with open(args.out_arch, 'wb') as fp: |
| fp.write(ET.tostring(arch_xml)) |
| |
| |
| if __name__ == "__main__": |
| main() |