From 8e1b4e963ee23e3856f333072808595187e45445 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Thu, 8 Apr 2021 16:50:07 +0200 Subject: [PATCH] fasm: add LUT init features generation This also adds some basic support for routing bels Signed-off-by: Alessandro Comodi --- fpga_interchange/fasm_generators/generic.py | 177 +++++++++++++++++++- fpga_interchange/fasm_generators/xc7.py | 131 ++++++++++++++- 2 files changed, 296 insertions(+), 12 deletions(-) diff --git a/fpga_interchange/fasm_generators/generic.py b/fpga_interchange/fasm_generators/generic.py index 86adb2dd..f1091941 100644 --- a/fpga_interchange/fasm_generators/generic.py +++ b/fpga_interchange/fasm_generators/generic.py @@ -9,12 +9,16 @@ # # SPDX-License-Identifier: ISC from collections import namedtuple +from math import log2 from fpga_interchange.route_stitching import flatten_segments -from fpga_interchange.physical_netlist import PhysicalPip +from fpga_interchange.physical_netlist import PhysicalPip, PhysicalSitePip +from fpga_interchange.chip_info_utils import LutCell, LutBel, LutElement PhysCellInstance = namedtuple( - 'CellInstance', 'cell_type site_name tile_name sites_in_tile attributes') + 'CellInstance', + 'cell_type site_name site_type tile_name tile_type sites_in_tile bel bel_pins attributes' +) class FasmGenerator(): @@ -34,6 +38,138 @@ def __init__(self, interchange, device_resources, log_netlist_file, self.routing_pips_features = list() self.cells_features = list() + self.build_luts_definitions() + self.build_log_cells_instances() + self.build_phys_cells_instances() + self.flatten_nets() + + def flatten_nets(self): + self.flattened_nets = dict() + + for net in self.physical_netlist.nets: + self.flattened_nets[net.name] = flatten_segments(net.sources + + net.stubs) + + def get_tile_info_at_site(self, site_name): + tile_name = self.device_resources.get_tile_name_at_site_name(site_name) + tile = self.device_resources.tile_name_to_tile[tile_name] + tile_type = tile.tile_type + sites_in_tile = tile.site_names + + return tile_name, tile_type, sites_in_tile + + def build_luts_definitions(self): + """ + Fills luts definition from the device resources database + """ + + self.site_lut_elements = dict() + self.lut_cells = dict() + + for site_lut_element in self.device_resources.device_resource_capnp.lutDefinitions.lutElements: + site = site_lut_element.site + self.site_lut_elements[site] = list() + for lut in site_lut_element.luts: + lut_element = LutElement() + self.site_lut_elements[site].append(lut_element) + + lut_element.width = lut.width + + for bel in lut.bels: + lut_bel = LutBel() + lut_element.lut_bels.append(lut_bel) + + lut_bel.name = bel.name + for pin in bel.inputPins: + lut_bel.pins.append(pin) + + lut_bel.out_pin = bel.outputPin + + assert bel.lowBit < lut.width + assert bel.highBit < lut.width + + lut_bel.low_bit = bel.lowBit + lut_bel.high_bit = bel.highBit + + for lut_cell in self.device_resources.device_resource_capnp.lutDefinitions.lutCells: + lut = LutCell() + self.lut_cells[lut_cell.cell] = lut + + lut.name = lut_cell.cell + for pin in lut_cell.inputPins: + lut.pins.append(pin) + + def get_phys_lut_init(self, logical_init_value, cell_data): + """ + Returns the LUTs physical INIT parameter mapping given the initial logical INIT + value and the cells' data containing the physical mapping of the input pins. + + It is left to the caller to handle cases of fructured LUTs. + """ + + def find_lut_bel(lut_elements, bel): + """ Returns the LUT Bel definition and the corresponding LUT element. """ + for lut_element in lut_elements: + for lut_bel in lut_element.lut_bels: + if lut_bel.name == bel: + return lut_element, lut_bel + + def physical_to_logical_map(lut_bel, bel_pins): + """ + Returns the physical pin to logical pin LUTs mapping. + Unused physical pins are set to None. + """ + phys_to_log = dict() + + for pin in lut_bel.pins: + phys_to_log[pin] = None + + for bel_pin in bel_pins: + if bel_pin.bel_pin == pin: + phys_to_log[pin] = bel_pin.cell_pin + break + + return phys_to_log + + cell_type = cell_data.cell_type + bel = cell_data.bel + bel_pins = cell_data.bel_pins + site_type = cell_data.site_type + + assert site_type in self.site_lut_elements, site_type + lut_elements = self.site_lut_elements[site_type] + + lut_element, lut_bel = find_lut_bel(lut_elements, bel) + lut_cell = self.lut_cells[cell_type] + + bitstring_init = "{value:0{digits}b}".format( + value=logical_init_value, digits=lut_bel.high_bit + 1) + + # Invert the string to have the LSB in the beginning + logical_lut_init = bitstring_init[::-1] + phys_to_log = physical_to_logical_map(lut_bel, bel_pins) + + physical_lut_init = list() + for phys_init_index in range(0, lut_element.width): + log_init_index = 0 + + for phys_port_idx in range(0, int(log2(lut_element.width))): + if not phys_init_index & (1 << phys_port_idx): + continue + + if phys_port_idx < len(lut_bel.pins): + log_port = phys_to_log.get(lut_bel.pins[phys_port_idx]) + + if log_port is None: + continue + + log_port_idx = lut_cell.pins.index(log_port) + log_init_index |= (1 << log_port_idx) + + physical_lut_init.append(logical_lut_init[log_init_index]) + + # Generate a string and invert the list, to have MSB in first position + return "".join(physical_lut_init[::-1]) def build_log_cells_instances(self): """ @@ -62,22 +198,29 @@ def build_phys_cells_instances(self): for placement in self.physical_netlist.placements: cell_name = placement.cell_name cell_type = placement.cell_type + site_name = placement.site - tile_name = self.device_resources.get_tile_name_at_site_name( + site_type = self.physical_netlist.site_instances[site_name] + + tile_name, tile_type, sites_in_tile = self.get_tile_info_at_site( site_name) - tile = self.device_resources.tile_name_to_tile[tile_name] - sites_in_tile = tile.site_names + bel = placement.bel + bel_pins = placement.pins cell_attr = self.logical_cells_instances.get(cell_name, None) self.physical_cells_instances[cell_name] = PhysCellInstance( cell_type=cell_type, site_name=site_name, + site_type=site_type, tile_name=tile_name, + tile_type=tile_type, sites_in_tile=sites_in_tile, + bel=bel, + bel_pins=bel_pins, attributes=cell_attr) - def fill_pip_features(self, site_thru_features=dict()): + def fill_pip_features(self): """ This function generates all features corresponding to the physical routing PIPs present in the physical netlist. @@ -97,9 +240,7 @@ def fill_pip_features(self, site_thru_features=dict()): site_thru_pips = list() for net in self.physical_netlist.nets: - net_segments = flatten_segments(net.sources + net.stubs) - - for segment in net_segments: + for segment in self.flattened_nets[net.name]: if isinstance(segment, PhysicalPip): tile = segment.tile @@ -124,6 +265,24 @@ def fill_pip_features(self, site_thru_features=dict()): return site_thru_pips + def get_routing_bels(self, allowed_routing_bels): + + routing_bels = list() + + for net in self.physical_netlist.nets: + for segment in self.flattened_nets[net.name]: + if isinstance(segment, PhysicalSitePip): + bel = segment.bel + if bel not in allowed_routing_bels: + continue + + site = segment.site + pin = segment.pin + + routing_bels.append((site, bel, pin)) + + return routing_bels + def output_fasm(self): """ Function to generate and print out the FASM features. diff --git a/fpga_interchange/fasm_generators/xc7.py b/fpga_interchange/fasm_generators/xc7.py index 1e0c7587..e11c447d 100644 --- a/fpga_interchange/fasm_generators/xc7.py +++ b/fpga_interchange/fasm_generators/xc7.py @@ -8,11 +8,28 @@ # https://opensource.org/licenses/ISC # # SPDX-License-Identifier: ISC +import re +from enum import Enum + from fpga_interchange.fasm_generators.generic import FasmGenerator from fpga_interchange.route_stitching import flatten_segments from fpga_interchange.physical_netlist import PhysicalPip +class LutsEnum(Enum): + LUT5 = 0 + LUT6 = 1 + + @classmethod + def from_str(cls, label): + if label == "LUT5": + return cls.LUT5 + elif label == "LUT6": + return cls.LUT6 + else: + raise NotImplementedError + + class XC7FasmGenerator(FasmGenerator): def handle_ios(self): """ @@ -50,6 +67,95 @@ def handle_ios(self): self.cells_features.append("{}.{}.{}".format( cell_data.tile_name, iob_site, feature)) + @staticmethod + def get_slice_prefix(site_name, tile_type, sites_in_tile): + """ + Returns the slice prefix corresponding to the input site name. + """ + + slice_sites = { + "CLBLL_L": ["SLICEL_X1", "SLICEL_X0"], + "CLBLL_R": ["SLICEL_X1", "SLICEL_X0"], + "CLBLM_L": ["SLICEL_X1", "SLICEM_X0"], + "CLBLM_R": ["SLICEL_X1", "SLICEM_X0"], + } + + slice_site_idx = sites_in_tile.index(site_name) + return slice_sites[tile_type][slice_site_idx] + + def handle_luts(self): + """ + This function handles LUTs FASM features generation + """ + + bel_re = re.compile("([ABCD])([56])LUT") + + luts = dict() + + for cell_instance, cell_data in self.physical_cells_instances.items(): + if not cell_data.cell_type.startswith("LUT"): + continue + + site_name = cell_data.site_name + site_type = cell_data.site_type + + tile_name = cell_data.tile_name + tile_type = cell_data.tile_type + sites_in_tile = cell_data.sites_in_tile + slice_site = self.get_slice_prefix(site_name, tile_type, + sites_in_tile) + + bel = cell_data.bel + m = bel_re.match(bel) + assert m, bel + + # A, B, C or D + lut_loc = m.group(1) + lut_name = "{}LUT".format(lut_loc) + + # LUT5 or LUT6 + lut_type = "LUT{}".format(m.group(2)) + + init_param = self.device_resources.get_parameter_definition( + cell_data.cell_type, "INIT") + init_value = init_param.decode_integer( + cell_data.attributes["INIT"]) + + phys_lut_init = self.get_phys_lut_init(init_value, cell_data) + + key = (site_name, lut_loc) + if key not in luts: + luts[key] = { + "data": (tile_name, slice_site, lut_name), + LutsEnum.LUT5: None, + LutsEnum.LUT6: None, + } + + luts[key][LutsEnum.from_str(lut_type)] = phys_lut_init + + for lut in luts.values(): + tile_name, slice_site, lut_name = lut["data"] + + lut5 = lut[LutsEnum.LUT5] + lut6 = lut[LutsEnum.LUT6] + + if lut5 is not None and lut6 is not None: + lut_init = "{}{}".format(lut6[0:32], lut5[32:64]) + elif lut5 is not None: + lut_init = lut5 + elif lut6 is not None: + lut_init = lut6 + else: + assert False + + init_feature = "{}.INIT[{}:0]={}'b{}".format( + lut_name, + len(lut_init) - 1, len(lut_init), lut_init) + lut_feature = "{}.{}.{}".format(tile_name, slice_site, + init_feature) + + self.cells_features.append(lut_feature) + def handle_site_thru(self, site_thru_pips): """ This function is currently specialized to add very specific features @@ -75,13 +181,32 @@ def handle_site_thru(self, site_thru_pips): for feature in features: self.cells_features.append("{}.{}".format(tile, feature)) - def output_fasm(self): - self.build_log_cells_instances() - self.build_phys_cells_instances() + def handle_slice_routing_bels(self): + allowed_routing_bels = list() + + for loc in "ABCD": + ff_mux = "{}FFMUX".format(loc) + out_mux = "{}OUTMUX".format(loc) + allowed_routing_bels.extend([ff_mux, out_mux]) + routing_bels = self.get_routing_bels(allowed_routing_bels) + + for site, bel, pin in routing_bels: + tile_name, tile_type, sites_in_tile = self.get_tile_info_at_site( + site) + slice_prefix = self.get_slice_prefix(site, tile_type, + sites_in_tile) + + routing_bel_feature = "{}.{}.{}.{}".format(tile_name, slice_prefix, + bel, pin) + self.cells_features.append(routing_bel_feature) + + def output_fasm(self): site_thru_pips = self.fill_pip_features() self.handle_site_thru(site_thru_pips) + self.handle_slice_routing_bels() self.handle_ios() + self.handle_luts() for cell_feature in sorted( self.cells_features, key=lambda f: f.split(".")[0]):