| #!/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() |