diff --git a/fpga_interchange/chip_info.py b/fpga_interchange/chip_info.py index fba5fade..9f7b7e70 100644 --- a/fpga_interchange/chip_info.py +++ b/fpga_interchange/chip_info.py @@ -176,6 +176,8 @@ def __init__(self): # -1 if site is a primary type, otherwise index into altSiteTypes. self.site_variant = 0 + self.timing_idx = -1 + def field_label(self, label_prefix, field): if self.site != -1: prefix = '{}.site{}.{}.{}'.format(label_prefix, self.site, @@ -210,6 +212,7 @@ def append_bba(self, bba, label_prefix): bba.u16(self.site) bba.u16(self.site_variant) + bba.u32(self.timing_idx) class PipInfo(): @@ -231,6 +234,10 @@ def __init__(self): self.extra_data = 0 + # Timing info + self.timing_idx = -1 + self.is_buffered = 0 + self.pseudo_cell_wires = [] def field_label(self, label_prefix, field): @@ -253,6 +260,9 @@ def append_bba(self, bba, label_prefix): bba.u16(self.site_variant) bba.u16(self.bel) bba.u16(self.extra_data) + bba.u32(self.timing_idx) + bba.u16(self.is_buffered) + bba.u16(0) # padding bba.ref(self.field_label(label_prefix, 'pseudo_cell_wires')) bba.u32(len(self.pseudo_cell_wires)) @@ -478,6 +488,7 @@ def append_bba(self, bba, label_prefix): class NodeInfo(): def __init__(self): self.name = '' + self.timing_idx = -1 self.tile_wires = [] def tile_wires_label(self, label_prefix): @@ -490,6 +501,7 @@ def append_children_bba(self, bba, label_prefix): tile_wire.append_bba(bba, label) def append_bba(self, bba, label_prefix): + bba.u32(self.timing_idx) bba.ref(self.tile_wires_label(label_prefix)) bba.u32(len(self.tile_wires)) @@ -556,15 +568,75 @@ def append_bba(self, bba, label_prefix): bba.u32(len(self.states)) +class PinEdgeType(Enum): + NONE = 0 + RISE = 1 + FALL = 2 + + +class PinEdge(): + def __init__(self): + self.pin_name = "" + self.clock_edge = PinEdgeType.NONE + + def append_bba(self, bba, label_prefix): + bba.str_id(self.pin_name) + bba.u32(self.clock_edge.value) + + +class TimingCorners(): + def __init__(self): + self.fast_min = 0 + self.fast_max = 0 + self.slow_min = 0 + self.slow_max = 0 + + def append_bba(self, bba, label_prefix): + bba.u32(self.fast_min) + bba.u32(self.fast_max) + bba.u32(self.slow_min) + bba.u32(self.slow_max) + + +class PinTimingType(Enum): + COMB = 0 + SETUP = 1 + HOLD = 2 + CLK2Q = 3 + + +class PinTiming(): + def __init__(self): + self.from_pin = PinEdge() + self.to_pin = PinEdge() + self.type = PinTimingType.COMB + self.value = TimingCorners() + self.site_type_idx = 0 + + def append_children_bba(self, bba, label_prefix): + pass + + def append_bba(self, bba, label_prefix): + self.from_pin.append_bba(bba, label_prefix) + self.to_pin.append_bba(bba, label_prefix) + bba.u32(self.type.value) + self.value.append_bba(bba, label_prefix) + bba.u32(self.site_type_idx) + + class CellBelMap(): - fields = ['common_pins', 'parameter_pins', 'constraints'] - field_types = ['CellBelPinPOD', 'ParameterPinsPOD', 'CellConstraintPOD'] + fields = ['common_pins', 'parameter_pins', 'constraints', 'timing'] + field_types = [ + 'CellBelPinPOD', 'ParameterPinsPOD', 'CellConstraintPOD', + 'PinTimingPOD' + ] def __init__(self, cell, tile_type, site_index, bel): self.key = '_'.join((cell, tile_type, str(site_index), bel)) self.common_pins = [] self.parameter_pins = [] self.constraints = [] + self.timing = [] def field_label(self, label_prefix, field): prefix = '{}.{}.{}'.format(label_prefix, self.key, field) @@ -1013,6 +1085,36 @@ def append_bba(self, bba, label_prefix): bba.u32(len(self.package_pins)) +class PipTiming(): + def __init__(self): + self.int_cap = TimingCorners() + self.int_delay = TimingCorners() + self.out_res = TimingCorners() + self.out_cap = TimingCorners() + + def append_children_bba(self, bba, label_prefix): + pass + + def append_bba(self, bba, label_prefix): + self.int_cap.append_bba(bba, label_prefix) + self.int_delay.append_bba(bba, label_prefix) + self.out_res.append_bba(bba, label_prefix) + self.out_cap.append_bba(bba, label_prefix) + + +class NodeTiming(): + def __init__(self): + self.res = TimingCorners() + self.cap = TimingCorners() + + def append_children_bba(self, bba, label_prefix): + pass + + def append_bba(self, bba, label_prefix): + self.res.append_bba(bba, label_prefix) + self.cap.append_bba(bba, label_prefix) + + class DefaultCellConnection(): def __init__(self): self.name = '' @@ -1381,6 +1483,8 @@ def __init__(self): self.wire_types = [] self.global_cells = [] self.clusters = [] + self.node_timings = [] + self.pip_timings = [] # str, constids self.bel_buckets = [] @@ -1396,19 +1500,14 @@ def append_bba(self, bba, label_prefix): children_fields = [ 'tile_types', 'sites', 'tiles', 'nodes', 'packages', 'wire_types', - 'global_cells', 'macros', 'macro_rules', 'clusters' + 'global_cells', 'macros', 'macro_rules', 'clusters', + 'node_timings', 'pip_timings' ] children_types = [ - 'TileTypeInfoPOD', - 'SiteInstInfoPOD', - 'TileInstInfoPOD', - 'NodeInfoPOD', - 'PackagePOD', - 'WireTypePOD', - 'GlobalCellPOD', - 'MacroPOD', - 'MacroExpansionPOD', - 'ClusterPOD', + 'TileTypeInfoPOD', 'SiteInstInfoPOD', 'TileInstInfoPOD', + 'NodeInfoPOD', 'PackagePOD', 'WireTypePOD', 'GlobalCellPOD', + 'MacroPOD', 'MacroExpansionPOD', 'ClusterPOD', 'NodeTimingPOD', + 'PipTimingPOD' ] for field, field_type in zip(children_fields, children_types): prefix = '{}.{}'.format(label, field) diff --git a/fpga_interchange/device_timing_patching.py b/fpga_interchange/device_timing_patching.py index b0b19cf2..8608b6e6 100644 --- a/fpga_interchange/device_timing_patching.py +++ b/fpga_interchange/device_timing_patching.py @@ -98,6 +98,24 @@ def create_site_type_name_to_site_pin_map(device): return siteType_name_sitePin +def create_bel_pin_to_index_map(device): + bel_pin_to_index = dict() + + for i, site in enumerate(device.siteTypeList): + site_type = device.strList[site.name] + bel_pin_to_index[site_type] = dict() + + for j, bel_pin in enumerate(site.belPins): + pin = device.strList[bel_pin.name] + bel = device.strList[bel_pin.bel] + + if bel not in bel_pin_to_index[site_type]: + bel_pin_to_index[site_type][bel] = dict() + bel_pin_to_index[site_type][bel][pin] = j + + return bel_pin_to_index + + def populate_corner_model(corner_model, fast_min, fast_typ, fast_max, slow_min, slow_typ, slow_max): fields = ['min', 'typ', 'max'] @@ -117,18 +135,232 @@ def populate_corner_model(corner_model, fast_min, fast_typ, fast_max, slow_min, setattr(x, field, slow[i]) +def populate_pin_delays(cell_bel_mapping, timings, string_map): + """ + Populates pin delays for the given cell bel pin map. + """ + + def populate_pin(pin, data, key): + """ + Fills in a PinDelay struct + """ + bel_pin = data[key + "_pin"] + edge = data[key + "_pin_edge"] + + pin.pin = bel_pin + if edge is not None: + assert edge in ["posedge", "negedge"], edge + if edge == "posedge": + pin.clockEdge = "rise" + if edge == "negedge": + pin.clockEdge = "fall" + + # FIXME: There is no explicit information whether a pin is a clock in/out + # Identify clock pins by looking for clock edges in setup/hold annotations + clock_pins = set() + for timing in timings: + if timing["type"] in ["setup", "hold"]: + if timing["from_pin_edge"] is not None: + clock_pins.add(timing["from_pin"]) + + # Maps path type from SDF to the FPGA interchange schema + # "clk2q" is detected separately as it is not a separate type in SDF + TYPE_MAP = { + "iopath": "comb", + "setup": "setup", + "hold": "hold", + "recovery": "setup", + "removal": "hold", + } + + pins_delay = cell_bel_mapping.init("pinsDelay", len(timings)) + for j, timing in enumerate(timings): + entry = pins_delay[j] + + # Site + entry.site = string_map[timing["site"]] + + # Type + # FIXME: Detect clock-to-Q here + assert timing["type"] in TYPE_MAP, timing["type"] + typ = TYPE_MAP[timing["type"]] + if typ == "comb" and timing["from_pin"] in clock_pins: + typ = "clk2q" + + entry.pinsDelayType = typ + + # From + pin = entry.init("firstPin") + populate_pin(pin, timing, "from") + + # FIXME: there is no explicit clock-to-Q in prjxray SDF. Assume + # posedge of a clock + if typ == "clk2q": + pin.clockEdge = "rise" + + # To + pin = entry.init("secondPin") + populate_pin(pin, timing, "to") + + # Timing data + entry.init("cornerModel") + corner_model = entry.cornerModel + + delays_slow = timing["delays"].get("slow", { + "min": None, + "typ": None, + "max": None + }) + + delays_fast = timing["delays"].get("fast", { + "min": None, + "typ": None, + "max": None + }) + + populate_corner_model(corner_model, delays_fast.get("min", None), + delays_fast.get("typ", None), + delays_fast.get("max", None), + delays_slow.get("min", None), + delays_slow.get("typ", None), + delays_slow.get("max", None)) + + +def collect_pins_delay(pins_delay, cell_timing_data, cell, site, bel, pins, + bel_pin_to_index_map): + """ + Collects timing data for the given cell type and cell bel pin mapping. + Matches the SDF data against device resources data. Converts BEL pin names + to their indices. + """ + + if site not in cell_timing_data: + print("WARNING: no timing data for site '{}'".format(site)) + return + + if bel not in cell_timing_data[site]: + print("WARNING: no timing data for site/bel '{}/{}'".format(site, bel)) + return + + # Get timings for the specific cell. If not found then + # look for data for "any" cell. + timings = cell_timing_data[site][bel].get(cell, None) + if timings is None: + timings = cell_timing_data[site][bel].get(None, None) + + if timings is None: + print("WARNING: not timing data for cell '{}' at site/bel '{}/{}'". + format(cell, site, bel)) + return + + # Translate the timing data + print(" ", "{}/{}".format(site, bel)) + + if site not in pins_delay: + pins_delay[site] = dict() + + used_pins = set() + for path_name, path in timings.items(): + + if path["from_pin"] not in pins or path["to_pin"] not in pins: + continue + + pin_1 = bel_pin_to_index_map[site][bel].get(path["from_pin"], None) + pin_2 = bel_pin_to_index_map[site][bel].get(path["to_pin"], None) + if pin_1 is None or pin_2 is None: + if pin_1 is None: + print("WARNING: unknown bel pin '{}.{}'".format( + bel, path["from_pin"])) + if pin_2 is None: + print("WARNING: unknown bel pin '{}.{}'".format( + bel, path["to_pin"])) + continue + + for key in ["from_pin", "to_pin"]: + used_pins.add(path[key]) + + # Take the slow and fast corner + delays = path["delay_paths"] + corner = dict() + for k1 in ["slow", "fast"]: + if k1 in delays: + corner[k1] = dict() + for k2 in ["min", "typ", "max"]: + val = delays[k1].get(k2, None) + if val is not None: + corner[k1][k2] = val + + # If no slow or fast data was found but there is "nominal" then + # use it for both. + if not corner and "nominal" in delays: + for k1 in ["slow", "fast"]: + corner[k1] = dict() + for k2 in ["min", "typ", "max"]: + val = delays["nominal"].get(k2, None) + if val is not None: + corner[k1][k2] = val + + # No data + if not corner: + print("WARNING: no timing data for path '{}'".format(path_name)) + continue + + # Handle negative delays + for k1 in corner.keys(): + for k2 in corner[k1].keys(): + val = corner[k1][k2] + if val < 0.0: + print( + "WARNING: delay for path '{}' {} {} is negative ({:.3f}ps), clamping to 0" + .format(path_name, k1, k2, val * 1e12)) + corner[k1][k2] = 0.0 + + if bel not in pins_delay[site]: + pins_delay[site][bel] = dict() + + # Warn before overwrite + if path_name in pins_delay[site][bel]: + print("WARNING: overwriting timing path '{}' for site/bel '{}/{}'". + format(path_name, site, bel)) + + # Format the entry + data = { + "from_pin": pin_1, + "from_pin_edge": path.get("from_pin_edge", None), + "to_pin": pin_2, + "to_pin_edge": path.get("to_pin_edge", None), + "type": path["type"], + "delays": corner + } + pins_delay[site][bel][path_name] = data + + # DEBUG + print(" ", path_name) + + # No valid data could be translated, delete the site entry + if not pins_delay[site]: + del pins_delay[site] + + # Report missing pin timings + if pins != used_pins: + missing_pins = sorted(list(pins - used_pins)) + print("WARNING: no timings for BEL pins:", ",".join(missing_pins)) + + def main(): parser = argparse.ArgumentParser( description="Add timing information to Device") parser.add_argument("--schema_dir", required=True) parser.add_argument("--timing_dir", required=True) + parser.add_argument("--timing_map", required=True) parser.add_argument("--family", required=True) parser.add_argument("device") parser.add_argument("patched_device") args = parser.parse_args() + print("Loading device resources...") device_schema = get_schema(args.schema_dir, "device") with open(args.device, 'rb') as f: dev = read_capnp_file(device_schema, f) @@ -143,6 +375,7 @@ def main(): tileType_wires_pip_map = create_tile_type_wire0_wire1_pip_map(dev) siteName_siteType_map = create_site_name_to_site_type_map(dev) siteType_name_sitePin_map = create_site_type_name_to_site_pin_map(dev) + bel_pin_to_index_map = create_bel_pin_to_index_map(dev) tile_type_name_to_number = {} for i, tileType in enumerate(dev.tileTypeList): @@ -153,9 +386,66 @@ def main(): family_map = {"xc7": prjxray_db_reader} + print("Loading timing data...") timing_dir = args.timing_dir - timing_reader = family_map[args.family](timing_dir) - timing_data = timing_reader.extract_data() + timing_reader = family_map[args.family](timing_dir, args.timing_map) + timing_data, cell_timing_data = timing_reader.extract_data() + + print("Patching device resources with timing data...") + + for i, mapping in enumerate(dev.cellBelMap): + cell = dev.strList[mapping.cell] + print("", cell) + + pins_delay = dict() + + # Common pins + for pin_map in mapping.commonPins: + + pins = set() + for entry in pin_map.pins: + pins.add(dev.strList[entry.belPin]) + + for entry in pin_map.siteTypes: + site = dev.strList[entry.siteType] + for bel_i in entry.bels: + bel = dev.strList[bel_i] + collect_pins_delay(pins_delay, cell_timing_data, cell, + site, bel, pins, bel_pin_to_index_map) + + # Parameter pins + for pin_map in mapping.parameterPins: + + pins = set() + for entry in pin_map.pins: + pins.add(dev.strList[entry.belPin]) + + for entry in pin_map.parametersSiteTypes: + site = dev.strList[entry.siteType] + bel = dev.strList[entry.bel] + collect_pins_delay(pins_delay, cell_timing_data, cell, site, + bel, pins, bel_pin_to_index_map) + + # No entries + if not pins_delay: + print("WARNING: No timing data for cell '{}'".format(cell)) + continue + + # Flatten + pins_delay_flat = [] + for site, bels in pins_delay.items(): + for bel, paths in bels.items(): + for path in paths.values(): + entry = { + "site": site, + "bel": bel, + } + entry.update(path) + pins_delay_flat.append(entry) + + # Serialize + populate_pin_delays(mapping, pins_delay_flat, string_map) + for tile, _data in timing_data.items(): if tile not in string_map: continue @@ -244,6 +534,7 @@ def main(): for pip, timing in pip_models.items(): pip.timing = timing_dict[timing] + print("Writing device resources...") with open(args.patched_device, "wb") as fp: write_capnp_file(dev, fp) diff --git a/fpga_interchange/populate_chip_info.py b/fpga_interchange/populate_chip_info.py index 373750b8..f9f3088a 100644 --- a/fpga_interchange/populate_chip_info.py +++ b/fpga_interchange/populate_chip_info.py @@ -18,7 +18,8 @@ CellConstraint, ConstraintType, Package, PackagePin, LutCell, \ LutElement, LutBel, CellParameter, DefaultCellConnections, DefaultCellConnection, \ WireType, Macro, MacroNet, MacroPortInst, MacroCellInst, MacroExpansion, \ - MacroParamMapRule, MacroParamRuleType, MacroParameter, GlobalCell, GlobalCellPin, Cluster + MacroParamMapRule, MacroParamRuleType, MacroParameter, GlobalCell, GlobalCellPin, Cluster, TimingCorners, PipTiming, \ + NodeTiming, PinTiming, PinEdge, PinTimingType, PinEdgeType from fpga_interchange.constraints.model import Tag, Placement, \ ImpliesConstraint, RequiresConstraint, SiteTypeMatcher from fpga_interchange.constraint_generator import ConstraintPrototype @@ -27,6 +28,7 @@ from fpga_interchange.nextpnr import PortType from fpga_interchange.logical_netlist import PortInstance, Net from fpga_interchange.device_resources import Direction +from fpga_interchange.static_timing_analysis import SECOND_CHOICE, ALL_POSSIBLE_VALUES class FlattenedWireType(Enum): @@ -99,8 +101,8 @@ def __init__(self, type, name, wire_index, site_index): class FlattenedPip( namedtuple( 'FlattenedPip', - 'type src_index dst_index site_index pip_index pseudo_cell_wires') -): + 'type src_index dst_index site_index pip_index pseudo_cell_wires is_buffered timing_idx' + )): pass @@ -112,6 +114,40 @@ class FlattenedSite( pass +def import_corner(corner_model, scale=1.0): + result = TimingCorners() + + def get_value_from_model(req_process, req_corner): + process = getattr(corner_model, req_process) + if process.which() == req_process: + process = getattr(process, req_process) + corner = getattr(process, req_corner) + if corner.which() == req_corner: + return getattr(corner, req_corner) + for corner in ALL_POSSIBLE_VALUES: + if getattr(process, corner).which() == corner: + return getattr(getattr(process, corner), corner) + process = getattr(corner_model, SECOND_CHOICE[req_process]) + if process.which() == SECOND_CHOICE[req_process]: + process = getattr(process, SECOND_CHOICE[req_process]) + corner = getattr(process, req_corner) + if corner.which() == req_corner: + return getattr(corner, req_corner) + for corner in ALL_POSSIBLE_VALUES: + if getattr(process, corner).which() == corner: + return getattr(getattr(process, corner), corner) + else: + return 0 + + for process in ("fast", "slow"): + for corner in ("min", "max"): + val = get_value_from_model(process, corner) + val = int(val * scale) + assert val >= 0 and val < 0x7FFFFFFF, (process, corner, val) + setattr(result, f"{process}_{corner}", val) + return result + + def emit_constraints(tile_type, tile_constraints, cell_bel_mapper): flat_tag_indicies = {} flat_tag_state_indicies = {} @@ -197,7 +233,7 @@ def emit(self, lut_elements): class FlattenedTileType(): def __init__(self, device, tile_type_index, tile_type, cell_bel_mapper, constraints, lut_elements, disabled_routethrus, - disabled_site_pips): + disabled_site_pips, extra_pip_timings): self.tile_type_name = device.strs[tile_type.name] self.tile_type = tile_type @@ -214,6 +250,7 @@ def __init__(self, device, tile_type_index, tile_type, cell_bel_mapper, self.lut_elements_map = {} self.disabled_site_pips = disabled_site_pips + self.extra_pip_timings = extra_pip_timings # Add tile wires self.tile_wire_to_wire_in_tile_index = {} @@ -238,7 +275,8 @@ def __init__(self, device, tile_type_index, tile_type, cell_bel_mapper, # Skip pseudo pips through disabled cells continue - pip_index = self.add_tile_pip(idx, pip.wire0, pip.wire1) + pip_index = self.add_tile_pip(idx, pip.wire0, pip.wire1, + pip.timing, pip.buffered20) if is_pseudo_cell: pseudo_pips.append((pip_index, pip)) @@ -247,7 +285,8 @@ def __init__(self, device, tile_type_index, tile_type, cell_bel_mapper, # Pseudo pips should not be bidirectional! assert not is_pseudo_cell - self.add_tile_pip(idx, pip.wire1, pip.wire0) + self.add_tile_pip(idx, pip.wire1, pip.wire0, pip.timing, + pip.buffered21) # Add all site variants for site_in_type_index, site_type_in_tile_type in enumerate( @@ -259,7 +298,8 @@ def __init__(self, device, tile_type_index, tile_type, cell_bel_mapper, self.add_site_type(device, site_type_in_tile_type, site_in_type_index, site_type_index, - site_variant, cell_bel_mapper, lut_elements) + site_variant, cell_bel_mapper, lut_elements, + extra_pip_timings) for site_variant, (alt_site_type_index, _) in enumerate( zip(primary_site_type.altSiteTypes, @@ -267,7 +307,7 @@ def __init__(self, device, tile_type_index, tile_type, cell_bel_mapper, self.add_site_type(device, site_type_in_tile_type, site_in_type_index, alt_site_type_index, site_variant, cell_bel_mapper, lut_elements, - primary_site_type) + extra_pip_timings, primary_site_type) # Now that sites have been emitted, populate pseudo_pips data. # @@ -437,7 +477,12 @@ def add_pip_common(self, flat_pip): return pip_index - def add_tile_pip(self, tile_pip_index, src_wire, dst_wire): + def add_tile_pip(self, + tile_pip_index, + src_wire, + dst_wire, + timing_idx=-1, + is_buffered=True): assert self.wires[src_wire].type == FlattenedWireType.TILE_WIRE assert self.wires[dst_wire].type == FlattenedWireType.TILE_WIRE @@ -447,7 +492,9 @@ def add_tile_pip(self, tile_pip_index, src_wire, dst_wire): dst_index=dst_wire, site_index=None, pip_index=tile_pip_index, - pseudo_cell_wires=[]) + pseudo_cell_wires=[], + timing_idx=timing_idx, + is_buffered=is_buffered) return self.add_pip_common(flat_pip) @@ -459,6 +506,7 @@ def add_site_type(self, site_variant, cell_bel_mapper, lut_elements, + extra_pip_timings, primary_site_type=None): if site_variant == -1: assert primary_site_type is None @@ -626,7 +674,27 @@ def add_site_type(self, src_wire = site_wire dst_wire = tile_wire - self.add_site_pin(src_wire, dst_wire, site_index, idx) + timing_idx = -1 + if site_pin.model.which( + ) != 'noModel' or site_pin.delay.slow.which( + ) != 'noSlow' or site_pin.delay.fast.which() != 'noFast': + # Site pin has timing data associated with it + pin_timing = PipTiming() + if site_pin.model.which() == 'resistance': + pin_timing.out_res = import_corner( + site_pin.model.resistance, RES_SCALE) + elif site_pin.model.which() == 'capacitance': + pin_timing.int_cap = import_corner( + site_pin.model.capacitance, CAP_SCALE) + else: + assert site_pin.model.which( + ) == 'noModel', site_pin.model.which() + pin_timing.int_delay = import_corner(site_pin.delay, DEL_SCALE) + timing_idx = len(device.device_resource_capnp. + pipTimings) + len(extra_pip_timings) + extra_pip_timings.append(pin_timing) + + self.add_site_pin(src_wire, dst_wire, site_index, idx, timing_idx) def add_site_pip(self, src_wire, dst_wire, site_index, site_pip_index): assert self.wires[src_wire].type == FlattenedWireType.SITE_WIRE @@ -638,11 +706,18 @@ def add_site_pip(self, src_wire, dst_wire, site_index, site_pip_index): dst_index=dst_wire, site_index=site_index, pip_index=site_pip_index, - pseudo_cell_wires=[]) + pseudo_cell_wires=[], + timing_idx=-1, + is_buffered=True) return self.add_pip_common(flat_pip) - def add_site_pin(self, src_wire, dst_wire, site_index, site_pin_index): + def add_site_pin(self, + src_wire, + dst_wire, + site_index, + site_pin_index, + timing_idx=-1): if self.wires[src_wire].type == FlattenedWireType.SITE_WIRE: assert self.wires[dst_wire].type == FlattenedWireType.TILE_WIRE else: @@ -655,7 +730,9 @@ def add_site_pin(self, src_wire, dst_wire, site_index, site_pin_index): dst_index=dst_wire, site_index=site_index, pip_index=site_pin_index, - pseudo_cell_wires=[]) + pseudo_cell_wires=[], + timing_idx=timing_idx, + is_buffered=True) return self.add_pip_common(flat_pip) @@ -749,6 +826,9 @@ def create_tile_type_info(self, cell_bel_mapper): pip_info.dst_index = pip.dst_index pip_info.pseudo_cell_wires = pip.pseudo_cell_wires + pip_info.timing_idx = pip.timing_idx + pip_info.is_buffered = pip.is_buffered + if pip.site_index is not None: site = self.sites[pip.site_index] site_type = site.site_type @@ -815,6 +895,12 @@ def is_cell_bel_map_disabled(cell, bel): self.cell_site_bel_index = {} self.cell_to_bel_constraints = {} self.bel_to_bel_buckets = {} + self.site_type_to_site_type_index = {} + self.timings = {} + + for i, site_type in enumerate( + device.device_resource_capnp.siteTypeList): + self.site_type_to_site_type_index[site_type.name] = i for cell_bel_map in device.device_resource_capnp.cellBelMap: cell_name = device.strs[cell_bel_map.cell] @@ -893,6 +979,56 @@ def is_cell_bel_map_disabled(cell, bel): self.cell_to_bel_map[cell_type] = bels + site_type_list = device.device_resource_capnp.siteTypeList + for pins_delay in cell_bel_map.pinsDelay: + site_type = device.strs[pins_delay.site] + site_type_index = self.site_type_to_site_type_index[pins_delay. + site] + + pin_delay_1 = pins_delay.firstPin + pin_delay_2 = pins_delay.secondPin + + pin_1 = site_type_list[site_type_index].belPins[pin_delay_1. + pin] + pin_2 = site_type_list[site_type_index].belPins[pin_delay_2. + pin] + + bel_1 = pin_1.bel + bel_2 = pin_2.bel + assert bel_1 == bel_2, (bel_1, bel_2) + bel = device.strs[bel_1] + + pin_timing = PinTiming() + + pin_timing.from_pin = PinEdge() + pin_timing.from_pin.pin_name = device.strs[pin_1.name] + if pin_delay_1.which() == "clockEdge": + edge = str(pin_delay_1.clockEdge).upper() + pin_timing.from_pin.clock_edge = PinEdgeType[edge] + else: + pin_timing.from_pin.clock_edge = PinEdgeType.NONE + + pin_timing.to_pin = PinEdge() + pin_timing.to_pin.pin_name = device.strs[pin_2.name] + if pin_delay_2.which() == "clockEdge": + edge = str(pin_delay_2.clockEdge).upper() + pin_timing.to_pin.clock_edge = PinEdgeType[edge] + else: + pin_timing.to_pin.clock_edge = PinEdgeType.NONE + + typ = str(pins_delay.pinsDelayType).upper() + pin_timing.type = PinTimingType[typ] + + pin_timing.value = import_corner(pins_delay.cornerModel, + DEL_SCALE) + pin_timing.site_type_idx = site_type_index + + key = cell_type, site_type, bel + if key not in self.timings: + self.timings[key] = list() + + self.timings[key].append(pin_timing) + self.bels = set() for site_type in device.device_resource_capnp.siteTypeList: for bel in site_type.bels: @@ -2227,6 +2363,11 @@ def populate_macro_rules(device, chip_info): chip_info.macro_rules.append(exp_data) +CAP_SCALE = 1e15 # fF +RES_SCALE = 1e6 # uOhm +DEL_SCALE = 1e12 # ps + + def populate_chip_info(device, constids, device_config): assert len(constids.values) == 1 @@ -2336,11 +2477,14 @@ def populate_chip_info(device, constids, device_config): chip_info.clusters.append(cluster_obj) + extra_pip_timings = [] + for tile_type_index, tile_type in enumerate( device.device_resource_capnp.tileTypeList): flattened_tile_type = FlattenedTileType( device, tile_type_index, tile_type, cell_bel_mapper, constraints, - lut_elements, disabled_routethrus, disabled_site_pips) + lut_elements, disabled_routethrus, disabled_site_pips, + extra_pip_timings) tile_type_info = flattened_tile_type.create_tile_type_info( cell_bel_mapper) @@ -2391,6 +2535,9 @@ def populate_chip_info(device, constids, device_config): cell_bel_map.parameter_pins.append(parameter) + if pin_key in cell_bel_mapper.timings: + cell_bel_map.timing = cell_bel_mapper.timings[pin_key] + cell_bel_map = chip_info.cell_map.cell_bel_map[idx] if idx in cell_bel_mapper.cell_to_bel_constraints: cell_bel_map.constraints = cell_bel_mapper.cell_to_bel_constraints[ @@ -2482,6 +2629,17 @@ def populate_chip_info(device, constids, device_config): for idx, node in enumerate(device.device_resource_capnp.nodes): # Skip nodes with only 1 wire! if len(node.wires) == 1: + # Move timing index to tile type + wire = device.device_resource_capnp.wires[node.wires[0]] + tile_name = device.strs[wire.tile] + wire_name = device.strs[wire.wire] + + tile_index = tile_name_to_tile_index[tile_name] + tile_info = chip_info.tiles[tile_index] + wire_in_tile_id = tile_wire_to_wire_in_tile_index[tile_info. + type][wire_name] + chip_info.tile_types[tile_info.type].wire_data[ + wire_in_tile_id].timing_idx = node.nodeTiming continue node_info = NodeInfo() @@ -2490,6 +2648,7 @@ def populate_chip_info(device, constids, device_config): # FIXME: Replace with actual node name? node_info.name = 'node_{}'.format(node_index) + node_info.timing_idx = node.nodeTiming for wire_index in node.wires: wire = device.device_resource_capnp.wires[wire_index] @@ -2566,4 +2725,24 @@ def populate_chip_info(device, constids, device_config): global_cell_data.pins.append(pin_data) chip_info.global_cells.append(global_cell_data) + for pip_timing in device.device_resource_capnp.pipTimings: + pip_tmg_data = PipTiming() + pip_tmg_data.int_cap = import_corner(pip_timing.internalCapacitance, + CAP_SCALE) + pip_tmg_data.int_delay = import_corner(pip_timing.internalDelay, + DEL_SCALE) + pip_tmg_data.out_res = import_corner(pip_timing.outputResistance, + RES_SCALE) + pip_tmg_data.out_cap = import_corner(pip_timing.outputCapacitance, + CAP_SCALE) + chip_info.pip_timings.append(pip_tmg_data) + + chip_info.pip_timings += extra_pip_timings + + for node_timing in device.device_resource_capnp.nodeTimings: + node_tmg_data = NodeTiming() + node_tmg_data.res = import_corner(node_timing.resistance, RES_SCALE) + node_tmg_data.cap = import_corner(node_timing.capacitance, CAP_SCALE) + chip_info.node_timings.append(node_tmg_data) + return chip_info diff --git a/fpga_interchange/prjxray_db_reader.py b/fpga_interchange/prjxray_db_reader.py index 81eb1b50..1c1d1d59 100644 --- a/fpga_interchange/prjxray_db_reader.py +++ b/fpga_interchange/prjxray_db_reader.py @@ -23,19 +23,136 @@ delays inside sites. """ +import argparse import os import json +import re +import itertools +from copy import deepcopy + +from collections import defaultdict + +from sdf_timing import sdfparse +from sdf_timing.utils import get_scale_seconds CAPACITANCE = 1e-9 # convert from prjxray internal values to farads RESISTANCE = 1e-6 # convert from prjxray internal values to ohms DELAY = 1e-9 # convert from ns to s +# ============================================================================= + + +def expand_pattern(pattern): + """ + Expands a simple reges pattern containing multiple "[]" closures + to all possible combinations. Returns a list of tuples containing the + final string together with all substituted values + """ + expr = r"\[([^\[\]]+)\]" + + groups = [] + for match in re.finditer(expr, pattern): + groups.append(match.group(1)) + + strings = [] + for subs in itertools.product(*groups): + s = str(pattern) + for i, sub in enumerate(subs): + s = re.sub(expr, sub, s, 1) + strings.append((s, subs)) + + return strings + + +def get_timings(site, spec, sdf_data): + """ + Given a cell and instance specification as "@" finds and + returns the timings. + """ + + assert spec.count("@") == 1, spec + cell, instance = spec.split("@", maxsplit=1) + + if cell not in sdf_data: + print("ERROR: No SDF data for cell '{}'".format(cell)) + return None + + assert instance.count("/") <= 1, (cell, instance) + if "/" in instance: + site, bel = instance.split("/") + else: + site, bel = instance, None + + if site not in sdf_data[cell]: + print("ERROR: No SDF data for cell '{}', site '{}'".format(cell, site)) + print(sdf_data[cell].keys()) + return None + + if bel not in sdf_data[cell][site]: + print("ERROR: No SDF data for cell '{}', site '{}', bel '{}'".format( + cell, site, bel)) + return None + + return sdf_data[cell][site][bel] + + +def map_timings(timings, pin_map): + """ + Applies pin map to SDF timings dict + """ + mapped_timings = dict() + for key, data in timings.items(): + + data = deepcopy(data) + for k in ["from_pin", "to_pin"]: + data[k] = pin_map.get(data[k], data[k]) + + key = "{}_{}_{}".format(data["type"], data["from_pin"], data["to_pin"]) + mapped_timings[key] = data + + return mapped_timings + + +def merge_timings(timings, overlay): + """ + Overlays timing data from the "new" dict tree onto the existing "timing" + one. + """ + + def walk(curr, new): + + assert type(curr) == type(new), (type(curr), type(new)) + + # Non-dict, just replace + if not isinstance(new, dict): + return new + + # Merge dicts recursively + for k, v in new.items(): + if k not in curr: + curr[k] = v + else: + curr[k] = walk(curr[k], v) + + return curr + + return walk(timings, overlay) + + +# ============================================================================= + class prjxray_db_reader: - def __init__(self, timing_dir): + def __init__(self, timing_dir, sdf_map_file=None): self.timing_dir = timing_dir + self.sdf_map = dict() + if sdf_map_file is not None: + with open(sdf_map_file, "r") as fp: + self.sdf_map = json.load(fp) + def extract_data(self): + return_dict = {} for i, _file in enumerate(os.listdir(self.timing_dir)): if not os.path.isfile(os.path.join( @@ -107,8 +224,171 @@ def extract_data(self): float(dic['delay'][3]) * DELAY) tile_dict['sites'][siteType][sitePin] = (values, delays) return_dict[tile_name] = tile_dict - return return_dict + + # Scan and parse SDFs + sdf_data = dict() + + sdf_dir = os.path.join(self.timing_dir, "timings") + for fname in os.listdir(sdf_dir): + + if not fname.lower().endswith(".sdf"): + continue + + sdf_file = os.path.join(sdf_dir, fname) + if not os.path.isfile(sdf_file): + continue + + tile_type, _ = os.path.splitext(fname) + + if tile_type not in return_dict: + print("WARNING: No tile '{}'".format(tile_type)) + continue + + with open(sdf_file) as f: + sdf = f.read() + + timings = sdfparse.parse(sdf) + timings = self.process_sdf_data(timings) + + sdf_data = merge_timings(sdf_data, timings) + + # Collect timings + timings_dict = dict() + for site, site_data in self.sdf_map.items(): + + if site not in timings_dict: + timings_dict[site] = dict() + + for pattern, cell_data in site_data.items(): + + # Get BEL pin map + pin_map = cell_data.get("pin_map", dict()) + + # Expand the BEL name pattern + bels_subs = expand_pattern(pattern) + for bel, subs in bels_subs: + + if bel not in timings_dict[site]: + timings_dict[site][bel] = dict() + + # Get default timing data if any + defaults = dict() + if "default" in cell_data: + for spec in cell_data["default"]: + spec = spec.format(*subs, site=site, bel=bel) + ts = get_timings(site, spec, sdf_data) + if ts is not None: + ts = map_timings(ts, pin_map) + defaults = merge_timings(defaults, ts) + + # Store timing data for any cell + timings_dict[site][bel][None] = defaults + + # Overlay subsequent cell-specific entries + for cell, data in cell_data.items(): + if cell in ["pin_map", "default"]: + continue + + timings = deepcopy(defaults) + for spec in data: + spec = spec.format(*subs, site=site, bel=bel) + ts = get_timings(site, spec, sdf_data) + if ts is not None: + ts = map_timings(ts, pin_map) + timings = merge_timings(timings, ts) + + # Store timing data for the specific cell + timings_dict[site][bel][cell] = timings + + return return_dict, timings_dict + + def process_sdf_data(self, data): + + # Timescale. Assume 1ns if not present in the header + scale = get_scale_seconds(data["header"].get("timescale", "1ns")) + + def apply_timescale(d): + if isinstance(d, dict): + new_d = {} + for k, v in d.items(): + new_d[k] = apply_timescale(v) + return new_d + elif isinstance(d, float): + return d * scale + else: + return d + + # Group timing data by cell, site and bel. Scale values to seconds + timings = {} + for cell, instances in data["cells"].items(): + timings[cell] = defaultdict(dict) + for instance, paths in instances.items(): + + # Get site and bell name if possible + assert instance.count("/") <= 1, (cell, instance) + if "/" in instance: + site, bel = instance.split("/") + else: + site, bel = instance, None + + # Filter timing data + keys = { + "type", "from_pin", "to_pin", "from_pin_edge", + "to_pin_edge", "delay_paths" + } + paths = { + t: {k: v + for k, v in d.items() if k in keys} + for t, d in paths.items() + } + + # Apply timescale + for key, path in paths.items(): + paths[key] = apply_timescale(path) + + timings[cell][site][bel] = paths + + timings[cell] = dict(timings[cell]) + + return timings if __name__ == "__main__": - print("This file conatins reader class for prjxray-db-timings") + + parser = argparse.ArgumentParser() + parser.add_argument( + "--db-root", + type=str, + required=True, + help="Project XRay database root path") + parser.add_argument( + "--sdf-map", + type=str, + required=True, + help="Map of SDF timing entries to cell @ site and bel") + + args = parser.parse_args() + + # Get data + reader = prjxray_db_reader(args.db_root, args.sdf_map) + routing_data, timings_dict = reader.extract_data() + + # Dump data (not all of it of course) + print("Routing timing:") + for tile, tile_data in routing_data.items(): + print("", tile) + print(" ", "{} wires".format(len(tile_data['wires']))) + print(" ", "{} pips".format(len(tile_data['pips']))) + + for site, site_data in tile_data['sites'].items(): + print(" ", "site '{}' {} pins".format(site, len(site_data))) + + print("Cell timings (site/bel/cell):") + for site, bels in timings_dict.items(): + print("", site) + for bel, cells in bels.items(): + print(" ", bel) + for cell, timings in cells.items(): + print(" ", "any" if cell is None else cell) + for key in timings: + print(" ", key) diff --git a/requirements.txt b/requirements.txt index 47078251..777a5d34 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ pyyaml yapf==0.24.0 # TODO: https://github.com/SymbiFlow/python-fpga-interchange/issues/11 git+https://github.com/litghost/rapidyaml.git@fixup_python_packaging#egg=rapidyaml +git+https://github.com/SymbiFlow/python-sdf-timing@99410bd -e . diff --git a/test_data/series7_sdf_map.json b/test_data/series7_sdf_map.json new file mode 100644 index 00000000..1cce2a6a --- /dev/null +++ b/test_data/series7_sdf_map.json @@ -0,0 +1,344 @@ +{ + "SLICEL": { + "[ABCD][5]LUT": { + "default": [ + "LUT5@SLICEL/{bel}" + ] + }, + "[ABCD][6]LUT": { + "default": [ + "LUT6@SLICEL/{bel}" + ] + }, + "[ABCD][5]FF": { + "pin_map": { + "CLK": "CK", + "DIN": "D" + }, + "default": [ + "REG_INIT_FF@{site}", + "REG_INIT_FF_QL@{site}" + ] + }, + "[ABCD]FF": { + "pin_map": { + "CLK": "CK", + "DIN": "D" + }, + "default": [ + "REG_INIT_FF@{site}", + "REG_INIT_FF_QL@{site}" + ] + }, + + "CARRY4": { + "default": [ + "CARRY4@{site}", + "CARRY4_AX@{site}", + "CARRY4_BX@{site}", + "CARRY4_CX@{site}", + "CARRY4_DX@{site}" + ] + }, + + "CARRY4_AMUX": { + "pin_map": { + "S0": "S0", + "DI0": "0", + "CYINIT": "1", + "CO0": "OUT" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_AX@{site}" + ] + }, + "CARRY4_BMUX": { + "pin_map": { + "S1": "S0", + "DI1": "0", + "CO1": "OUT" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_BX@{site}" + ] + }, + "CARRY4_CMUX": { + "pin_map": { + "S2": "S0", + "DI2": "0", + "CO2": "OUT" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_CX@{site}" + ] + }, + "CARRY4_DMUX": { + "pin_map": { + "S3": "S0", + "DI3": "0", + "CO3": "OUT" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_DX@{site}" + ] + }, + + "CARRY4_AXOR": { + "pin_map": { + "S0": "0", + "CYINIT": "1", + "O0": "O" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_AX@{site}" + ] + }, + "CARRY4_BXOR": { + "pin_map": { + "S1": "0", + "O1": "O" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_BX@{site}" + ] + }, + "CARRY4_CXOR": { + "pin_map": { + "S2": "0", + "O2": "O" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_CX@{site}" + ] + }, + "CARRY4_DXOR": { + "pin_map": { + "S3": "0", + "O3": "O" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_DX@{site}" + ] + }, + + "F7[AB]MUX": { + "default": [ + "SELMUX2_1@{site}/{bel}" + ] + }, + "F8MUX": { + "default": [ + "SELMUX2_1@{site}/{bel}" + ] + } + }, + + "SLICEM": { + "[ABCD]5LUT": { + "default": [ + "LUT5@SLICEL/{bel}" + ], + "SRL16E": [ + "LUT_OR_MEM5LRAM@{site}", + "LUT_OR_MEM5LRAM@{site}/{bel}", + "LUT_OR_MEM5SHFREG@{site}", + "LUT_OR_MEM5SHFREG@{site}/{bel}" + ], + "RAMS32": [ + "LUT_OR_MEM5LRAM@{site}", + "LUT_OR_MEM5LRAM@{site}/{bel}" + ], + "RAMD32": [ + "LUT_OR_MEM5LRAM@{site}", + "LUT_OR_MEM5LRAM@{site}/{bel}" + ] + }, + "[ABCD]6LUT": { + "default": [ + "LUT6@SLICEL/{bel}" + ], + "SRLC32E": [ + "LUT_OR_MEM6LRAM@{site}", + "LUT_OR_MEM6LRAM@{site}/{bel}", + "LUT_OR_MEM6SHFREG@{site}", + "LUT_OR_MEM6SHFREG@{site}/{bel}" + ], + "SRLC16E": [ + "LUT_OR_MEM6LRAM@{site}", + "LUT_OR_MEM6LRAM@{site}/{bel}", + "LUT_OR_MEM6SHFREG@{site}", + "LUT_OR_MEM6SHFREG@{site}/{bel}" + ], + "SRL16E": [ + "LUT_OR_MEM6LRAM@{site}", + "LUT_OR_MEM6LRAM@{site}/{bel}", + "LUT_OR_MEM6SHFREG@{site}", + "LUT_OR_MEM6SHFREG@{site}/{bel}" + ], + "RAMS32": [ + "LUT_OR_MEM6LRAM@{site}", + "LUT_OR_MEM6LRAM@{site}/{bel}" + ], + "RAMD32": [ + "LUT_OR_MEM6LRAM@{site}", + "LUT_OR_MEM6LRAM@{site}/{bel}" + ], + "RAMS64E": [ + "LUT_OR_MEM6LRAM@{site}", + "LUT_OR_MEM6LRAM@{site}/{bel}" + ], + "RAMD64E": [ + "LUT_OR_MEM6LRAM@{site}", + "LUT_OR_MEM6LRAM@{site}/{bel}" + ] + }, + "[ABCD][5]FF": { + "pin_map": { + "CLK": "CK", + "DIN": "D" + }, + "default": [ + "REG_INIT_FF@{site}", + "REG_INIT_FF_QL@{site}" + ] + }, + "[ABCD]FF": { + "pin_map": { + "CLK": "CK", + "DIN": "D" + }, + "default": [ + "REG_INIT_FF@{site}", + "REG_INIT_FF_QL@{site}" + ] + }, + + "CARRY4": { + "default": [ + "CARRY4@{site}", + "CARRY4_AX@{site}", + "CARRY4_BX@{site}", + "CARRY4_CX@{site}", + "CARRY4_DX@{site}" + ] + }, + + "CARRY4_AMUX": { + "pin_map": { + "S0": "S0", + "DI0": "0", + "CYINIT": "1", + "CO0": "OUT" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_AX@{site}" + ] + }, + "CARRY4_BMUX": { + "pin_map": { + "S1": "S0", + "DI1": "0", + "CO1": "OUT" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_BX@{site}" + ] + }, + "CARRY4_CMUX": { + "pin_map": { + "S2": "S0", + "DI2": "0", + "CO2": "OUT" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_CX@{site}" + ] + }, + "CARRY4_DMUX": { + "pin_map": { + "S3": "S0", + "DI3": "0", + "CO3": "OUT" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_DX@{site}" + ] + }, + + "CARRY4_AXOR": { + "pin_map": { + "S0": "0", + "CYINIT": "1", + "O0": "O" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_AX@{site}" + ] + }, + "CARRY4_BXOR": { + "pin_map": { + "S1": "0", + "O1": "O" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_BX@{site}" + ] + }, + "CARRY4_CXOR": { + "pin_map": { + "S2": "0", + "O2": "O" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_CX@{site}" + ] + }, + "CARRY4_DXOR": { + "pin_map": { + "S3": "0", + "O3": "O" + }, + "default": [ + "CARRY4@{site}", + "CARRY4_DX@{site}" + ] + }, + + "F7[AB]MUX": { + "default": [ + "SELMUX2_1@{site}/{bel}" + ] + }, + "F8MUX": { + "default": [ + "SELMUX2_1@{site}/{bel}" + ] + } + }, + + "BUFGCTRL": { + "BUFGCTRL": { + "pin_map": { + "I": "I0" + }, + "default": [ + "BUFGCTRL@BUFGCTRL" + ] + } + } +}