diff --git a/fpga_interchange/chip_info.py b/fpga_interchange/chip_info.py index 18979961..519c7054 100644 --- a/fpga_interchange/chip_info.py +++ b/fpga_interchange/chip_info.py @@ -143,6 +143,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, @@ -177,6 +179,7 @@ def append_bba(self, bba, label_prefix): bba.u16(self.site) bba.u16(self.site_variant) + bba.u32(self.timing_idx) class PipInfo(): @@ -198,6 +201,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): @@ -220,6 +227,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)) @@ -445,6 +455,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): @@ -457,6 +468,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)) @@ -523,15 +535,66 @@ def append_bba(self, bba, label_prefix): bba.u32(len(self.states)) +class PinEdge(): + def __init__(self): + self.pin_name = "" + self.clock_edge = 0 + + def append_bba(self, bba, label_prefix): + bba.str_id(self.pin_name) + bba.u32(self.clock_edge) + + +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_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) @@ -785,6 +848,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 = '' @@ -1153,6 +1246,8 @@ def __init__(self): self.wire_types = [] self.global_cells = [] self.clusters = [] + self.node_timings = [] + self.pip_timings = [] # str, constids self.bel_buckets = [] @@ -1168,19 +1263,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/populate_chip_info.py b/fpga_interchange/populate_chip_info.py index 6ad67649..50244dde 100644 --- a/fpga_interchange/populate_chip_info.py +++ b/fpga_interchange/populate_chip_info.py @@ -17,12 +17,14 @@ 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 from fpga_interchange.constraints.model import Tag, Placement, \ ImpliesConstraint, RequiresConstraint from fpga_interchange.constraint_generator import ConstraintPrototype from fpga_interchange.device_resources import convert_wire_category from fpga_interchange.nextpnr import PortType +from fpga_interchange.static_timing_analysis import SECOND_CHOICE, ALL_POSSIBLE_VALUES class FlattenedWireType(Enum): @@ -94,8 +96,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 @@ -107,6 +109,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 + 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 = {} @@ -192,7 +228,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 @@ -209,6 +245,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 = {} @@ -233,7 +270,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)) @@ -242,7 +280,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( @@ -254,7 +293,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, @@ -262,7 +302,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. # @@ -432,7 +472,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 @@ -442,7 +487,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) @@ -454,6 +501,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 @@ -608,7 +656,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 @@ -620,11 +688,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: @@ -637,7 +712,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) @@ -730,6 +807,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 @@ -1773,6 +1853,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 @@ -1863,11 +1948,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) @@ -2009,6 +2097,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() @@ -2017,6 +2116,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] @@ -2093,4 +2193,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