def load_sdf_timings(sdf_dir): """ Loads and merges SDF timing data from all *.sdf files in the given directory. """ def apply_scale(cells, scale=1.0): """ Scales all timings represented by the given SDF structure. """ for cell_type, cell_data in cells.items(): for instance, instance_data in cell_data.items(): for timing, timing_data in instance_data.items(): paths = timing_data["delay_paths"] for path_name, path_data in paths.items(): for k in path_data.keys(): if path_data[k] is not None: path_data[k] *= scale # List SDF files files = [f for f in os.listdir(sdf_dir) if f.lower().endswith(".sdf")] # Read and parse cell_timings = {} for f in files: print("Loading SDF: '{}'".format(f)) # Read fname = os.path.join(sdf_dir, f) with open(fname, "r") as fp: sdf = sdfparse.parse(fp.read()) # Get the timing scale header = sdf["header"] if "timescale" in header: timescale = get_scale_seconds(header["timescale"]) else: print("WARNING: the SDF has no timescale, assuming 1.0") timescale = 1.0 # Apply the scale and update cells cells = sdf["cells"] apply_scale(cells, timescale) cell_timings.update(cells) return cell_timings
def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--read_sdf', type=argparse.FileType('r'), help='sdf file to read timing from') parser.add_argument('--read_arch_xml', type=argparse.FileType('r'), help='arch xml file to read and update/add timing') parser.add_argument( '--write_arch_xml', type=argparse.FileType('w'), help='arch xml file to write with updaed timing calues') parser.add_argument('-v', type=bool, help='verbose output') logging.basicConfig(level=logging.WARNING) args = parser.parse_args() timing = sdf_parse(args.read_sdf.read()) tree = ET.parse(args.read_arch_xml, ET.XMLParser(remove_blank_text=True)) scale = get_scale_seconds(timing['header']['timescale']) # logging.info('sdf scale set to', scale) # flatten cells to list of max flat_timing = dict() for cell_name, cell in timing['cells'].items(): flat_timing[cell_name] = [] for instance_key, instance in cell.items(): assert ( instance_key == '*' ), "For iCE40 expect only wildcard instance {} in cell {}".format( instance_key, cell_name) for _, path in instance.items(): flat_timing[cell_name].append(path) for key, time_list in flat_timing.items(): logging.debug(key) for delay in time_list: logging.debug(delay) # look up parsed sdf on in from_pin, to_pin, and type def lookup_timing(timing_list, type, to_pin, from_pin): ret = [] for xx in timing_list: if type == xx['type'] and xx['to_pin'].startswith( to_pin) and xx['from_pin'].startswith(from_pin): ret.append(xx) else: pass return ret def get_pessimistic(timing): vals = [] for del_type in timing['delay_paths'].values(): for dels in del_type.values(): vals.append(dels) max_del = max(vals) return str(max_del * scale) # TODO: need to take max across negedge and posedge # remove all existing tags and warn on them # iterate over existing arch and update/add delay tags # root = tree.getroot() for el in tree.iter('pb_type'): pb_name = el.attrib['name'] # insert timing tags from SDF file if pb_name in _arch_to_sdf.keys(): cell_name, pin_table = _arch_to_sdf.get(pb_name, None) for timing in flat_timing[cell_name]: def try_translate_pin(table, timing, name): res = [ entry for entry in table if entry.sdf == timing[name] ] assert len(res) <= 1 if len(res) == 1: return res[0] else: return None if timing['type'] == 'hold' or timing['type'] == 'setup': topin = try_translate_pin(pin_table, timing, 'to_pin') frompin = try_translate_pin(pin_table, timing, 'from_pin') if topin is None or frompin is None: continue attribs = { 'clock': frompin.arch, 'port': '{}.{}'.format(pb_name, topin.arch), 'value': get_pessimistic(timing) } hold_setup_el = ET.SubElement( el, 'T_{}'.format(timing['type']), attribs) # hold_setup_el.tail = '\n' logging.info(ET.tostring(hold_setup_el)) elif timing['type'] == 'iopath': topin = try_translate_pin(pin_table, timing, 'to_pin') frompin = try_translate_pin(pin_table, timing, 'from_pin') if topin is None or frompin is None: continue if frompin.is_clk: attribs = { 'clock': '{}'.format(frompin.arch), 'port': '{}.{}'.format(pb_name, topin.arch), 'max': get_pessimistic(timing) } iopath_el = ET.SubElement(el, 'T_clock_to_Q', attribs) # iopath_el.tail = '\n' else: attribs = { 'in_port': '{}.{}'.format(pb_name, frompin.arch), 'out_port': '{}.{}'.format(pb_name, topin.arch), 'max': get_pessimistic(timing) } iopath_el = ET.SubElement(el, 'delay_constant', attribs) # iopath_el.tail = '\n' logging.info(ET.tostring(iopath_el)) elif timing['type'] == 'recovery': pass elif timing['type'] == 'removal': pass xml_str = ET.tostring(tree, pretty_print=True).decode('utf-8') args.write_arch_xml.write(xml_str)