From 3edd3c47d8506d00fe6d21dabd66394ce15b9a82 Mon Sep 17 00:00:00 2001 From: gatecat Date: Wed, 14 Jul 2021 14:28:07 +0100 Subject: [PATCH 01/18] chip_info: Add timing structures Signed-off-by: gatecat --- fpga_interchange/chip_info.py | 113 ++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 13 deletions(-) diff --git a/fpga_interchange/chip_info.py b/fpga_interchange/chip_info.py index fba5fade..8821db15 100644 --- a/fpga_interchange/chip_info.py +++ b/fpga_interchange/chip_info.py @@ -231,6 +231,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 +257,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 +485,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 +498,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 +565,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) @@ -1013,6 +1073,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 +1471,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 +1488,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) From d5efc3c911e2cd9a3b5fd6ae5fb00a2da38b9aec Mon Sep 17 00:00:00 2001 From: gatecat Date: Wed, 14 Jul 2021 14:36:57 +0100 Subject: [PATCH 02/18] chip_info: Populate timing indices Signed-off-by: gatecat --- fpga_interchange/populate_chip_info.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/fpga_interchange/populate_chip_info.py b/fpga_interchange/populate_chip_info.py index 373750b8..0cf0cf8d 100644 --- a/fpga_interchange/populate_chip_info.py +++ b/fpga_interchange/populate_chip_info.py @@ -99,7 +99,7 @@ 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 @@ -238,7 +238,7 @@ 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 +247,7 @@ 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( @@ -437,7 +437,7 @@ 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 +447,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) @@ -638,7 +640,9 @@ 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) @@ -655,7 +659,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=-1, + is_buffered=True) return self.add_pip_common(flat_pip) @@ -2490,6 +2496,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] From 5486d2785208263cfaee6cdb87a1f72258418351 Mon Sep 17 00:00:00 2001 From: gatecat Date: Thu, 15 Jul 2021 11:02:11 +0100 Subject: [PATCH 03/18] chip_info: Import node and pip timings Signed-off-by: gatecat --- fpga_interchange/populate_chip_info.py | 81 ++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/fpga_interchange/populate_chip_info.py b/fpga_interchange/populate_chip_info.py index 0cf0cf8d..573cf4e5 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 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 is_buffered timing_idx') -): + '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 + 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 = {} @@ -238,7 +274,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.timing, pip.buffered20) + 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 +284,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, pip.timing, pip.buffered21) + 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( @@ -437,7 +475,12 @@ def add_pip_common(self, flat_pip): return pip_index - def add_tile_pip(self, tile_pip_index, src_wire, dst_wire, timing_idx=-1, is_buffered=True): + 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 @@ -755,6 +798,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 @@ -2233,6 +2279,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 @@ -2573,4 +2624,22 @@ 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) + + 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 From 3daf139ed98d7b87192d67992d723fce27c35ea1 Mon Sep 17 00:00:00 2001 From: gatecat Date: Thu, 15 Jul 2021 12:31:10 +0100 Subject: [PATCH 04/18] chip_info: Add timing index for single-tile wires Signed-off-by: gatecat --- fpga_interchange/chip_info.py | 3 +++ fpga_interchange/populate_chip_info.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/fpga_interchange/chip_info.py b/fpga_interchange/chip_info.py index 8821db15..abf7545a 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(): diff --git a/fpga_interchange/populate_chip_info.py b/fpga_interchange/populate_chip_info.py index 573cf4e5..010abeba 100644 --- a/fpga_interchange/populate_chip_info.py +++ b/fpga_interchange/populate_chip_info.py @@ -2539,6 +2539,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() From f0bb4cac0a2627212e6b3cb91c122d28373da7a3 Mon Sep 17 00:00:00 2001 From: gatecat Date: Thu, 15 Jul 2021 12:56:01 +0100 Subject: [PATCH 05/18] chip_info: Model site pin timing Signed-off-by: gatecat --- fpga_interchange/populate_chip_info.py | 47 ++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/fpga_interchange/populate_chip_info.py b/fpga_interchange/populate_chip_info.py index 010abeba..cf63e28b 100644 --- a/fpga_interchange/populate_chip_info.py +++ b/fpga_interchange/populate_chip_info.py @@ -233,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 @@ -250,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 = {} @@ -297,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, @@ -305,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. # @@ -504,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 @@ -671,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 @@ -689,7 +712,12 @@ def add_site_pip(self, src_wire, dst_wire, site_index, site_pip_index): 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: @@ -703,7 +731,7 @@ def add_site_pin(self, src_wire, dst_wire, site_index, site_pin_index): site_index=site_index, pip_index=site_pin_index, pseudo_cell_wires=[], - timing_idx=-1, + timing_idx=timing_idx, is_buffered=True) return self.add_pip_common(flat_pip) @@ -2393,11 +2421,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) @@ -2647,6 +2678,8 @@ def populate_chip_info(device, constids, device_config): 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) From a06964fcd6f7abd60769e0103b9768624e44f5fc Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Fri, 18 Feb 2022 12:19:39 +0100 Subject: [PATCH 06/18] Added reading cell timings from prjxray database Signed-off-by: Maciej Kurc --- fpga_interchange/prjxray_db_reader.py | 258 +++++++++++++++++++++++++- requirements.txt | 1 + 2 files changed, 256 insertions(+), 3 deletions(-) diff --git a/fpga_interchange/prjxray_db_reader.py b/fpga_interchange/prjxray_db_reader.py index 81eb1b50..2b8a2f41 100644 --- a/fpga_interchange/prjxray_db_reader.py +++ b/fpga_interchange/prjxray_db_reader.py @@ -23,19 +23,113 @@ 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. + """ + 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) + + 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)) + 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 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): self.timing_dir = timing_dir - def extract_data(self): + def extract_data(self, sdf_map=None): + + if sdf_map is None: + sdf_map = dict() + return_dict = {} for i, _file in enumerate(os.listdir(self.timing_dir)): if not os.path.isfile(os.path.join( @@ -107,8 +201,166 @@ 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.update(timings) + + # Collect timings + timings_dict = dict() + for site, site_data in sdf_map.items(): + + if site not in timings_dict: + timings_dict[site] = dict() + + for pattern, cell_data in site_data.items(): + + # Expand the BEL name pattern + bels = expand_pattern(pattern) + for bel in bels: + + 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(bel=bel) + ts = get_timings(site, spec, sdf_data) + if ts is not None: + 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 == "default": + continue + + timings = deepcopy(defaults) + for spec in data: + spec = spec.format(bel=bel) + ts = get_timings(site, spec, sdf_data) + if ts is not None: + 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() + + # Load SDF map + with open(args.sdf_map, "r") as fp: + sdf_map = json.load(fp) + + # Get data + reader = prjxray_db_reader(args.db_root) + routing_data, timings_dict = reader.extract_data(sdf_map) + + # 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 . From 9db2b0a0ed57888bf3d0a3571d36be5a4b0d8c2a Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Wed, 23 Feb 2022 13:10:54 +0100 Subject: [PATCH 07/18] Moved parsing SDF cell map to prjxray_db_reader Signed-off-by: Maciej Kurc --- fpga_interchange/prjxray_db_reader.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/fpga_interchange/prjxray_db_reader.py b/fpga_interchange/prjxray_db_reader.py index 2b8a2f41..36901a30 100644 --- a/fpga_interchange/prjxray_db_reader.py +++ b/fpga_interchange/prjxray_db_reader.py @@ -122,13 +122,16 @@ def walk(curr, new): class prjxray_db_reader: - def __init__(self, timing_dir): + def __init__(self, timing_dir, sdf_map_file = None): self.timing_dir = timing_dir - def extract_data(self, sdf_map=None): + 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) - if sdf_map is None: - sdf_map = dict() + + def extract_data(self): return_dict = {} for i, _file in enumerate(os.listdir(self.timing_dir)): @@ -231,7 +234,7 @@ def extract_data(self, sdf_map=None): # Collect timings timings_dict = dict() - for site, site_data in sdf_map.items(): + for site, site_data in self.sdf_map.items(): if site not in timings_dict: timings_dict[site] = dict() @@ -337,13 +340,9 @@ def apply_timescale(d): args = parser.parse_args() - # Load SDF map - with open(args.sdf_map, "r") as fp: - sdf_map = json.load(fp) - # Get data - reader = prjxray_db_reader(args.db_root) - routing_data, timings_dict = reader.extract_data(sdf_map) + 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:") From bb168e9e0e0ba18b0ab83db93ae52fe9fb9aa529 Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Wed, 23 Feb 2022 13:11:49 +0100 Subject: [PATCH 08/18] WIP mapping of timing data from prjxray SDF to FPGA interchange device resources Signed-off-by: Maciej Kurc --- fpga_interchange/device_timing_patching.py | 143 ++++++++++++++++++++- 1 file changed, 141 insertions(+), 2 deletions(-) diff --git a/fpga_interchange/device_timing_patching.py b/fpga_interchange/device_timing_patching.py index b0b19cf2..856a0bd7 100644 --- a/fpga_interchange/device_timing_patching.py +++ b/fpga_interchange/device_timing_patching.py @@ -97,6 +97,22 @@ def create_site_type_name_to_site_pin_map(device): siteType_name_sitePin[(i, sitePin.name)] = sitePin 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): @@ -123,12 +139,14 @@ def main(): 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 +161,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 +172,128 @@ 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] + + # Common pin maps + for pin_map in mapping.commonPins: + pins_delay = dict() + + # Collect pins delay + for entry in pin_map.siteTypes: + site = dev.strList[entry.siteType] + + if site not in cell_timing_data: + print("WARNING: no timing data for site '{}'".format(site)) + continue + + for bel_i in entry.bels: + bel = dev.strList[bel_i] + + if bel not in cell_timing_data[site]: + print("WARNING: no timing data for site/bel '{}/{}'".format(site, bel)) + continue + + # 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)) + continue + + # Translate the timing data + print("{} @ {}/{}".format(cell, site, bel)) + + if site not in pins_delay: + pins_delay[site] = dict() + + for path_name, path in timings.items(): + + 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 + + delays = path["delay_paths"] + corner = dict() + for key in ["slow", "fast"]: + if key in delays: + corner[key] = { + "min": delays[key]["min"], + "typ": delays[key]["avg"], + "max": delays[key]["max"], + } + + if not corner and "nominal" in delays: + for key in ["slow", "fast"]: + corner[key] = { + "min": delays["nominal"]["min"], + "typ": delays["nominal"]["avg"], + "max": delays["nominal"]["max"], + } + + if not corner: + print("WARNING: no timing data for path '{}'".format(path_name)) + continue + + if bel not in pins_delay[site]: + pins_delay[site][bel] = list() + + data = { + "from_pin": pin_1, + "to_pin": pin_2, + "type": path["type"], + "delays": corner + } + pins_delay[site][bel].append(data) + + # DEBUG + print("", path_name, data) + + # No valid data could be translated, delete the site entry + if not pins_delay[site]: + del pins_delay[site] + +# if pins_delay: +# exit(-1) + + # Serialize pins delay + # TODO + + # Parameter pin maps + # TODO + +# ## +# for i, mapping in enumerate(dev.cellBelMap): +# print("{}. '{}'".format(i, dev.strList[mapping.cell])) +# for j, pin_map in enumerate(mapping.commonPins): +# print(" mapping #{}".format(j)) +# print(" sites:") +# for k, site in enumerate(pin_map.siteTypes): +# bels = " ".join([dev.strList[bel] for bel in site.bels]) +# print(" {}. '{}', bels: {}".format(k, dev.strList[site.siteType], bels)) +# print(" pins:") +# for k, pins in enumerate(pin_map.pins): +# print(" {}. '{}' '{}'".format(k, +# dev.strList[pins.cellPin], +# dev.strList[pins.belPin] +# )) +# ## + for tile, _data in timing_data.items(): if tile not in string_map: continue @@ -244,6 +382,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) From 21d3d01d6571c77687db21387c647dd2206aab98 Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Mon, 28 Feb 2022 16:17:45 +0100 Subject: [PATCH 09/18] Add support for additional SDF cell to BEL pin mapping Signed-off-by: Maciej Kurc --- fpga_interchange/prjxray_db_reader.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/fpga_interchange/prjxray_db_reader.py b/fpga_interchange/prjxray_db_reader.py index 36901a30..b079e957 100644 --- a/fpga_interchange/prjxray_db_reader.py +++ b/fpga_interchange/prjxray_db_reader.py @@ -93,6 +93,23 @@ def get_timings(site, spec, sdf_data): 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" @@ -241,6 +258,9 @@ def extract_data(self): 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 = expand_pattern(pattern) for bel in bels: @@ -255,6 +275,7 @@ def extract_data(self): spec = spec.format(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 @@ -262,7 +283,7 @@ def extract_data(self): # Overlay subsequent cell-specific entries for cell, data in cell_data.items(): - if cell == "default": + if cell in ["pin_map", "default"]: continue timings = deepcopy(defaults) @@ -270,6 +291,7 @@ def extract_data(self): spec = spec.format(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 From c7bb74602dddb938de028ef24677ba0df123fa3f Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Tue, 1 Mar 2022 11:42:07 +0100 Subject: [PATCH 10/18] Working flow for populating cell timing data to device resources, works for "regular" cell-bel pin maps, not yet for parameter pins. Signed-off-by: Maciej Kurc --- fpga_interchange/device_timing_patching.py | 285 +++++++++++++-------- 1 file changed, 178 insertions(+), 107 deletions(-) diff --git a/fpga_interchange/device_timing_patching.py b/fpga_interchange/device_timing_patching.py index 856a0bd7..1bb745f4 100644 --- a/fpga_interchange/device_timing_patching.py +++ b/fpga_interchange/device_timing_patching.py @@ -132,6 +132,180 @@ def populate_corner_model(corner_model, fast_min, fast_typ, fast_max, slow_min, x = getattr(corner_model.slow.slow, field) setattr(x, field, slow[i]) +def populate_pin_delays(string_map, cell_bel_mapping, timings): + """ + 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" + + # 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 + assert timing["type"] in TYPE_MAP, timing["type"] + typ = TYPE_MAP[timing["type"]] + if typ == "comb" and timing["from_pin_edge"] is not None: + typ = "clk2q" + + entry.pinsDelayType = typ + + # From + pin = entry.init("firstPin") + populate_pin(pin, timing, "from") + + # To + pin = entry.init("secondPin") + populate_pin(pin, timing, "to") + + # Timing data + entry.init("cornerModel") + corner_model = entry.cornerModel + + delays_slow = timing["delays"]["slow"] + delays_fast = timing["delays"]["fast"] + populate_corner_model(corner_model, + delays_fast["min"], delays_fast["typ"], delays_fast["max"], + delays_slow["min"], delays_slow["typ"], delays_slow["max"] + ) + + +def collect_pins_delay(device, bel_pin_to_index_map, cell, pin_map, cell_timing_data): + """ + 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. + """ + + pins_delay = dict() + + for entry in pin_map.siteTypes: + site = device.strList[entry.siteType] + + if site not in cell_timing_data: + print("WARNING: no timing data for site '{}'".format(site)) + continue + + for bel_i in entry.bels: + bel = device.strList[bel_i] + + if bel not in cell_timing_data[site]: + print("WARNING: no timing data for site/bel '{}/{}'".format(site, bel)) + continue + + # 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)) + continue + + # Translate the timing data + print(" ", "@ {}/{}".format(site, bel)) + + if site not in pins_delay: + pins_delay[site] = dict() + + for path_name, path in timings.items(): + + 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 + + # Take the slow and fast corner + delays = path["delay_paths"] + corner = dict() + for key in ["slow", "fast"]: + if key in delays: + corner[key] = { + "min": delays[key]["min"], + "typ": delays[key]["avg"], + "max": delays[key]["max"], + } + + # 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 key in ["slow", "fast"]: + corner[key] = { + "min": delays["nominal"]["min"], + "typ": delays["nominal"]["avg"], + "max": delays["nominal"]["max"], + } + + if not corner: + print("WARNING: no timing data for path '{}'".format(path_name)) + continue + + if bel not in pins_delay[site]: + pins_delay[site][bel] = list() + + 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].append(data) + + # DEBUG + print(" ", path_name) + + # No valid data could be translated, delete the site entry + if not pins_delay[site]: + del pins_delay[site] + + # Flatten entries + flat_pins_delay = [] + for site, bels in pins_delay.items(): + for bel, timings in bels.items(): + for data in timings: + entry = { + "site": site, + "bel": bel + } + entry.update(data) + flat_pins_delay.append(entry) + + return flat_pins_delay + def main(): parser = argparse.ArgumentParser( @@ -181,119 +355,16 @@ def main(): for i, mapping in enumerate(dev.cellBelMap): cell = dev.strList[mapping.cell] + print("", cell) # Common pin maps for pin_map in mapping.commonPins: - pins_delay = dict() - - # Collect pins delay - for entry in pin_map.siteTypes: - site = dev.strList[entry.siteType] - - if site not in cell_timing_data: - print("WARNING: no timing data for site '{}'".format(site)) - continue - - for bel_i in entry.bels: - bel = dev.strList[bel_i] - - if bel not in cell_timing_data[site]: - print("WARNING: no timing data for site/bel '{}/{}'".format(site, bel)) - continue - - # 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)) - continue - - # Translate the timing data - print("{} @ {}/{}".format(cell, site, bel)) - - if site not in pins_delay: - pins_delay[site] = dict() - - for path_name, path in timings.items(): - - 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 - - delays = path["delay_paths"] - corner = dict() - for key in ["slow", "fast"]: - if key in delays: - corner[key] = { - "min": delays[key]["min"], - "typ": delays[key]["avg"], - "max": delays[key]["max"], - } - - if not corner and "nominal" in delays: - for key in ["slow", "fast"]: - corner[key] = { - "min": delays["nominal"]["min"], - "typ": delays["nominal"]["avg"], - "max": delays["nominal"]["max"], - } - - if not corner: - print("WARNING: no timing data for path '{}'".format(path_name)) - continue - - if bel not in pins_delay[site]: - pins_delay[site][bel] = list() - - data = { - "from_pin": pin_1, - "to_pin": pin_2, - "type": path["type"], - "delays": corner - } - pins_delay[site][bel].append(data) - - # DEBUG - print("", path_name, data) - - # No valid data could be translated, delete the site entry - if not pins_delay[site]: - del pins_delay[site] - -# if pins_delay: -# exit(-1) - - # Serialize pins delay - # TODO - + timings = collect_pins_delay(dev, bel_pin_to_index_map, cell, pin_map, cell_timing_data) + populate_pin_delays(string_map, mapping, timings) + # Parameter pin maps # TODO -# ## -# for i, mapping in enumerate(dev.cellBelMap): -# print("{}. '{}'".format(i, dev.strList[mapping.cell])) -# for j, pin_map in enumerate(mapping.commonPins): -# print(" mapping #{}".format(j)) -# print(" sites:") -# for k, site in enumerate(pin_map.siteTypes): -# bels = " ".join([dev.strList[bel] for bel in site.bels]) -# print(" {}. '{}', bels: {}".format(k, dev.strList[site.siteType], bels)) -# print(" pins:") -# for k, pins in enumerate(pin_map.pins): -# print(" {}. '{}' '{}'".format(k, -# dev.strList[pins.cellPin], -# dev.strList[pins.belPin] -# )) -# ## - for tile, _data in timing_data.items(): if tile not in string_map: continue From 11c3d02dcebe007000c3bf00a844ad9b52d02957 Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Tue, 1 Mar 2022 14:14:11 +0100 Subject: [PATCH 11/18] Added utilizing parameter cell-bel pin maps. Added writing timings only for BEL pins mentioned in a map. Signed-off-by: Maciej Kurc --- fpga_interchange/device_timing_patching.py | 215 ++++++++++++--------- 1 file changed, 121 insertions(+), 94 deletions(-) diff --git a/fpga_interchange/device_timing_patching.py b/fpga_interchange/device_timing_patching.py index 1bb745f4..4c1ed32c 100644 --- a/fpga_interchange/device_timing_patching.py +++ b/fpga_interchange/device_timing_patching.py @@ -132,7 +132,7 @@ def populate_corner_model(corner_model, fast_min, fast_typ, fast_max, slow_min, x = getattr(corner_model.slow.slow, field) setattr(x, field, slow[i]) -def populate_pin_delays(string_map, cell_bel_mapping, timings): +def populate_pin_delays(cell_bel_mapping, timings, string_map): """ Populates pin delays for the given cell bel pin map. """ @@ -197,114 +197,101 @@ def populate_pin(pin, data, key): ) -def collect_pins_delay(device, bel_pin_to_index_map, cell, pin_map, cell_timing_data): +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. """ - pins_delay = dict() + if site not in cell_timing_data: + print("WARNING: no timing data for site '{}'".format(site)) + return - for entry in pin_map.siteTypes: - site = device.strList[entry.siteType] + if bel not in cell_timing_data[site]: + print("WARNING: no timing data for site/bel '{}/{}'".format(site, bel)) + return - if site not in cell_timing_data: - print("WARNING: no timing data for site '{}'".format(site)) - continue + # 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) - for bel_i in entry.bels: - bel = device.strList[bel_i] + if timings is None: + print("WARNING: not timing data for cell '{}' at site/bel '{}/{}'".format(cell, site, bel)) + return - if bel not in cell_timing_data[site]: - print("WARNING: no timing data for site/bel '{}/{}'".format(site, bel)) - continue + # Translate the timing data + print(" ", "{}/{}".format(site, bel)) - # 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 site not in pins_delay: + pins_delay[site] = dict() - if timings is None: - print("WARNING: not timing data for cell '{}' at site/bel '{}/{}'".format(cell, site, bel)) - continue + for path_name, path in timings.items(): - # Translate the timing data - print(" ", "@ {}/{}".format(site, bel)) + if path["from_pin"] not in pins or path["to_pin"] not in pins: + continue - if site not in pins_delay: - pins_delay[site] = dict() + 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 path_name, path in timings.items(): + # Take the slow and fast corner + delays = path["delay_paths"] + corner = dict() + for key in ["slow", "fast"]: + if key in delays: + corner[key] = { + "min": delays[key]["min"], + "typ": delays[key]["avg"], + "max": delays[key]["max"], + } - 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 + # 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 key in ["slow", "fast"]: + corner[key] = { + "min": delays["nominal"]["min"], + "typ": delays["nominal"]["avg"], + "max": delays["nominal"]["max"], + } - # Take the slow and fast corner - delays = path["delay_paths"] - corner = dict() - for key in ["slow", "fast"]: - if key in delays: - corner[key] = { - "min": delays[key]["min"], - "typ": delays[key]["avg"], - "max": delays[key]["max"], - } - - # 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 key in ["slow", "fast"]: - corner[key] = { - "min": delays["nominal"]["min"], - "typ": delays["nominal"]["avg"], - "max": delays["nominal"]["max"], - } - - if not corner: - print("WARNING: no timing data for path '{}'".format(path_name)) - continue + # No data + if not corner: + print("WARNING: no timing data for path '{}'".format(path_name)) + continue - if bel not in pins_delay[site]: - pins_delay[site][bel] = list() + if bel not in pins_delay[site]: + pins_delay[site][bel] = dict() - 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].append(data) - - # DEBUG - print(" ", path_name) - - # No valid data could be translated, delete the site entry - if not pins_delay[site]: - del pins_delay[site] - - # Flatten entries - flat_pins_delay = [] - for site, bels in pins_delay.items(): - for bel, timings in bels.items(): - for data in timings: - entry = { - "site": site, - "bel": bel - } - entry.update(data) - flat_pins_delay.append(entry) + # 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 - return flat_pins_delay + # DEBUG + print(" ", path_name) + + # No valid data could be translated, delete the site entry + if not pins_delay[site]: + del pins_delay[site] def main(): @@ -357,13 +344,53 @@ def main(): cell = dev.strList[mapping.cell] print("", cell) - # Common pin maps + pins_delay = dict() + + # Common pins for pin_map in mapping.commonPins: - timings = collect_pins_delay(dev, bel_pin_to_index_map, cell, pin_map, cell_timing_data) - populate_pin_delays(string_map, mapping, timings) + + 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) - # Parameter pin maps - # TODO for tile, _data in timing_data.items(): if tile not in string_map: From 2b5a683820c23f7290ef78e509bd600398fc2c77 Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Tue, 1 Mar 2022 15:19:41 +0100 Subject: [PATCH 12/18] Fixed SDF data merging, added reporting of BEL pins that didn't get any timing annotations. Signed-off-by: Maciej Kurc --- fpga_interchange/device_timing_patching.py | 8 ++++++++ fpga_interchange/prjxray_db_reader.py | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/fpga_interchange/device_timing_patching.py b/fpga_interchange/device_timing_patching.py index 4c1ed32c..a413228f 100644 --- a/fpga_interchange/device_timing_patching.py +++ b/fpga_interchange/device_timing_patching.py @@ -228,6 +228,7 @@ def collect_pins_delay(pins_delay, cell_timing_data, cell, site, bel, pins, 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: @@ -242,6 +243,9 @@ def collect_pins_delay(pins_delay, cell_timing_data, cell, site, bel, pins, bel_ 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() @@ -293,6 +297,10 @@ def collect_pins_delay(pins_delay, cell_timing_data, cell, site, bel, pins, bel_ 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( diff --git a/fpga_interchange/prjxray_db_reader.py b/fpga_interchange/prjxray_db_reader.py index b079e957..d5647a8a 100644 --- a/fpga_interchange/prjxray_db_reader.py +++ b/fpga_interchange/prjxray_db_reader.py @@ -84,6 +84,7 @@ def get_timings(site, spec, sdf_data): 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]: @@ -247,7 +248,7 @@ def extract_data(self): timings = sdfparse.parse(sdf) timings = self.process_sdf_data(timings) - sdf_data.update(timings) + sdf_data = merge_timings(sdf_data, timings) # Collect timings timings_dict = dict() From a9f3001344548d72fbf1b5d6be546d7e35e4cdf3 Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Tue, 1 Mar 2022 17:02:51 +0100 Subject: [PATCH 13/18] Added access to site name, bel name and each substituted bel name part to the cell instance spec Signed-off-by: Maciej Kurc --- fpga_interchange/prjxray_db_reader.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fpga_interchange/prjxray_db_reader.py b/fpga_interchange/prjxray_db_reader.py index d5647a8a..af4f9568 100644 --- a/fpga_interchange/prjxray_db_reader.py +++ b/fpga_interchange/prjxray_db_reader.py @@ -45,7 +45,8 @@ def expand_pattern(pattern): """ Expands a simple reges pattern containing multiple "[]" closures - to all possible combinations. + to all possible combinations. Returns a list of tuples containing the + final string together with all substituted values """ expr = r"\[([^\[\]]+)\]" @@ -58,7 +59,7 @@ def expand_pattern(pattern): s = str(pattern) for i, sub in enumerate(subs): s = re.sub(expr, sub, s, 1) - strings.append(s) + strings.append((s, subs)) return strings @@ -263,8 +264,8 @@ def extract_data(self): pin_map = cell_data.get("pin_map", dict()) # Expand the BEL name pattern - bels = expand_pattern(pattern) - for bel in bels: + bels_subs = expand_pattern(pattern) + for bel, subs in bels_subs: if bel not in timings_dict[site]: timings_dict[site][bel] = dict() @@ -273,7 +274,7 @@ def extract_data(self): defaults = dict() if "default" in cell_data: for spec in cell_data["default"]: - spec = spec.format(bel=bel) + 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) @@ -289,7 +290,7 @@ def extract_data(self): timings = deepcopy(defaults) for spec in data: - spec = spec.format(bel=bel) + 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) From 6cfe7d9121847ed05614d8aa246e8d97360fccdf Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Wed, 2 Mar 2022 09:39:42 +0100 Subject: [PATCH 14/18] Initial SDF timing mapping for Xilinx 7-series device. Contains mostly everything for SLICEL and SLICEM Signed-off-by: Maciej Kurc --- test_data/series7_sdf_map.json | 344 +++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 test_data/series7_sdf_map.json 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" + ] + } + } +} From 1eefcbca6c081854cf26f5cbfb0889f113187460 Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Wed, 2 Mar 2022 13:25:43 +0100 Subject: [PATCH 15/18] Added support for missing timing values, added clamping negative delays to 0 (for now it seems that nextpnr won't handle eg. negative hold) Signed-off-by: Maciej Kurc --- fpga_interchange/device_timing_patching.py | 50 ++++++++++++++-------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/fpga_interchange/device_timing_patching.py b/fpga_interchange/device_timing_patching.py index a413228f..ce5d56b9 100644 --- a/fpga_interchange/device_timing_patching.py +++ b/fpga_interchange/device_timing_patching.py @@ -189,11 +189,19 @@ def populate_pin(pin, data, key): entry.init("cornerModel") corner_model = entry.cornerModel - delays_slow = timing["delays"]["slow"] - delays_fast = timing["delays"]["fast"] + 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["min"], delays_fast["typ"], delays_fast["max"], - delays_slow["min"], delays_slow["typ"], delays_slow["max"] + 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) ) @@ -249,29 +257,37 @@ def collect_pins_delay(pins_delay, cell_timing_data, cell, site, bel, pins, bel_ # Take the slow and fast corner delays = path["delay_paths"] corner = dict() - for key in ["slow", "fast"]: - if key in delays: - corner[key] = { - "min": delays[key]["min"], - "typ": delays[key]["avg"], - "max": delays[key]["max"], - } + 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 key in ["slow", "fast"]: - corner[key] = { - "min": delays["nominal"]["min"], - "typ": delays["nominal"]["avg"], - "max": delays["nominal"]["max"], - } + 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() From c2d7f4f152095d8c40f2652e30d4b4977f2d4b77 Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Wed, 2 Mar 2022 14:13:02 +0100 Subject: [PATCH 16/18] Added importing cell timings from FPGA interchange device resources to nextpnr BBA Signed-off-by: Maciej Kurc --- fpga_interchange/chip_info.py | 13 +++++- fpga_interchange/populate_chip_info.py | 58 +++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/fpga_interchange/chip_info.py b/fpga_interchange/chip_info.py index abf7545a..9f7b7e70 100644 --- a/fpga_interchange/chip_info.py +++ b/fpga_interchange/chip_info.py @@ -568,14 +568,20 @@ 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 = 0 + self.clock_edge = PinEdgeType.NONE def append_bba(self, bba, label_prefix): bba.str_id(self.pin_name) - bba.u32(self.clock_edge) + bba.u32(self.clock_edge.value) class TimingCorners(): @@ -607,6 +613,9 @@ def __init__(self): 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) diff --git a/fpga_interchange/populate_chip_info.py b/fpga_interchange/populate_chip_info.py index cf63e28b..b2784fcf 100644 --- a/fpga_interchange/populate_chip_info.py +++ b/fpga_interchange/populate_chip_info.py @@ -19,7 +19,7 @@ LutElement, LutBel, CellParameter, DefaultCellConnections, DefaultCellConnection, \ WireType, Macro, MacroNet, MacroPortInst, MacroCellInst, MacroExpansion, \ MacroParamMapRule, MacroParamRuleType, MacroParameter, GlobalCell, GlobalCellPin, Cluster, TimingCorners, PipTiming, \ - NodeTiming + NodeTiming, PinTiming, PinEdge, PinTimingType, PinEdgeType from fpga_interchange.constraints.model import Tag, Placement, \ ImpliesConstraint, RequiresConstraint, SiteTypeMatcher from fpga_interchange.constraint_generator import ConstraintPrototype @@ -143,7 +143,7 @@ def get_value_from_model(req_process, req_corner): for corner in ("min", "max"): val = get_value_from_model(process, corner) val = int(val * scale) - assert val >= 0 and val < 0x7FFFFFFF + assert val >= 0 and val < 0x7FFFFFFF, (process, corner, val) setattr(result, f"{process}_{corner}", val) return result @@ -895,6 +895,11 @@ 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] @@ -973,6 +978,52 @@ 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: @@ -2479,6 +2530,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[ From fde5f63ec55fd9f39ffec4f40797485a0ef7ac6d Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Fri, 4 Mar 2022 13:42:18 +0100 Subject: [PATCH 17/18] Fixed detection of clock-to-Q timings based on other SDF data Signed-off-by: Maciej Kurc --- fpga_interchange/device_timing_patching.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/fpga_interchange/device_timing_patching.py b/fpga_interchange/device_timing_patching.py index ce5d56b9..2a1c6b95 100644 --- a/fpga_interchange/device_timing_patching.py +++ b/fpga_interchange/device_timing_patching.py @@ -152,6 +152,14 @@ def populate_pin(pin, data, key): 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 = { @@ -170,9 +178,10 @@ def populate_pin(pin, data, key): 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_edge"] is not None: + if typ == "comb" and timing["from_pin"] in clock_pins: typ = "clk2q" entry.pinsDelayType = typ @@ -181,6 +190,11 @@ def populate_pin(pin, data, key): 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") From 5401db54c647937a0023dda9090b447e7cb105c1 Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Thu, 10 Mar 2022 12:58:53 +0100 Subject: [PATCH 18/18] Code formatting Signed-off-by: Maciej Kurc --- fpga_interchange/device_timing_patching.py | 62 ++++++++++++++-------- fpga_interchange/populate_chip_info.py | 15 ++++-- fpga_interchange/prjxray_db_reader.py | 25 +++++---- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/fpga_interchange/device_timing_patching.py b/fpga_interchange/device_timing_patching.py index 2a1c6b95..8608b6e6 100644 --- a/fpga_interchange/device_timing_patching.py +++ b/fpga_interchange/device_timing_patching.py @@ -97,6 +97,7 @@ def create_site_type_name_to_site_pin_map(device): siteType_name_sitePin[(i, sitePin.name)] = sitePin return siteType_name_sitePin + def create_bel_pin_to_index_map(device): bel_pin_to_index = dict() @@ -114,6 +115,7 @@ def create_bel_pin_to_index_map(device): 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'] @@ -132,6 +134,7 @@ def populate_corner_model(corner_model, fast_min, fast_typ, fast_max, slow_min, x = getattr(corner_model.slow.slow, field) 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. @@ -203,23 +206,28 @@ def populate_pin(pin, data, key): entry.init("cornerModel") corner_model = entry.cornerModel - delays_slow = timing["delays"].get("slow", - {"min": None, "typ": None, "max": None}) + delays_slow = timing["delays"].get("slow", { + "min": None, + "typ": None, + "max": None + }) - delays_fast = timing["delays"].get("fast", - {"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) - ) + 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): +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 @@ -241,7 +249,8 @@ def collect_pins_delay(pins_delay, cell_timing_data, cell, site, bel, pins, bel_ 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)) + print("WARNING: not timing data for cell '{}' at site/bel '{}/{}'". + format(cell, site, bel)) return # Translate the timing data @@ -257,12 +266,14 @@ def collect_pins_delay(pins_delay, cell_timing_data, cell, site, bel, pins, bel_ 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) + 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"])) + 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"])) + print("WARNING: unknown bel pin '{}.{}'".format( + bel, path["to_pin"])) continue for key in ["from_pin", "to_pin"]: @@ -299,7 +310,9 @@ def collect_pins_delay(pins_delay, cell_timing_data, cell, site, bel, pins, bel_ 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)) + 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]: @@ -307,7 +320,8 @@ def collect_pins_delay(pins_delay, cell_timing_data, cell, site, bel, pins, bel_ # Warn before overwrite if path_name in pins_delay[site][bel]: - print("WARNING: overwriting timing path '{}' for site/bel '{}/{}'".format(path_name, site, bel)) + print("WARNING: overwriting timing path '{}' for site/bel '{}/{}'". + format(path_name, site, bel)) # Format the entry data = { @@ -332,6 +346,7 @@ def collect_pins_delay(pins_delay, cell_timing_data, cell, site, bel, pins, bel_ 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") @@ -390,12 +405,13 @@ def main(): 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) + 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: @@ -407,7 +423,8 @@ def main(): 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) + collect_pins_delay(pins_delay, cell_timing_data, cell, site, + bel, pins, bel_pin_to_index_map) # No entries if not pins_delay: @@ -428,7 +445,6 @@ def main(): # Serialize populate_pin_delays(mapping, pins_delay_flat, string_map) - for tile, _data in timing_data.items(): if tile not in string_map: diff --git a/fpga_interchange/populate_chip_info.py b/fpga_interchange/populate_chip_info.py index b2784fcf..f9f3088a 100644 --- a/fpga_interchange/populate_chip_info.py +++ b/fpga_interchange/populate_chip_info.py @@ -898,7 +898,8 @@ def is_cell_bel_map_disabled(cell, bel): self.site_type_to_site_type_index = {} self.timings = {} - for i, site_type in enumerate(device.device_resource_capnp.siteTypeList): + 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: @@ -981,13 +982,16 @@ def is_cell_bel_map_disabled(cell, bel): 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] + 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] + 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 @@ -1015,7 +1019,8 @@ def is_cell_bel_map_disabled(cell, bel): typ = str(pins_delay.pinsDelayType).upper() pin_timing.type = PinTimingType[typ] - pin_timing.value = import_corner(pins_delay.cornerModel, DEL_SCALE) + pin_timing.value = import_corner(pins_delay.cornerModel, + DEL_SCALE) pin_timing.site_type_idx = site_type_index key = cell_type, site_type, bel diff --git a/fpga_interchange/prjxray_db_reader.py b/fpga_interchange/prjxray_db_reader.py index af4f9568..1c1d1d59 100644 --- a/fpga_interchange/prjxray_db_reader.py +++ b/fpga_interchange/prjxray_db_reader.py @@ -89,7 +89,8 @@ def get_timings(site, spec, sdf_data): return None if bel not in sdf_data[cell][site]: - print("ERROR: No SDF data for cell '{}', site '{}', bel '{}'".format(cell, site, bel)) + print("ERROR: No SDF data for cell '{}', site '{}', bel '{}'".format( + cell, site, bel)) return None return sdf_data[cell][site][bel] @@ -137,11 +138,12 @@ def walk(curr, new): return walk(timings, overlay) + # ============================================================================= class prjxray_db_reader: - def __init__(self, timing_dir, sdf_map_file = None): + def __init__(self, timing_dir, sdf_map_file=None): self.timing_dir = timing_dir self.sdf_map = dict() @@ -149,7 +151,6 @@ def __init__(self, timing_dir, sdf_map_file = None): with open(sdf_map_file, "r") as fp: self.sdf_map = json.load(fp) - def extract_data(self): return_dict = {} @@ -301,7 +302,6 @@ def extract_data(self): return return_dict, timings_dict - def process_sdf_data(self, data): # Timescale. Assume 1ns if not present in the header @@ -332,8 +332,15 @@ def apply_timescale(d): 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()} + 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(): @@ -353,14 +360,12 @@ def apply_timescale(d): "--db-root", type=str, required=True, - help="Project XRay database root path" - ) + 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" - ) + help="Map of SDF timing entries to cell @ site and bel") args = parser.parse_args()