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