Skip to content

Commit

Permalink
fasm: add LUT init features generation
Browse files Browse the repository at this point in the history
This also adds some basic support for routing bels

Signed-off-by: Alessandro Comodi <acomodi@antmicro.com>
  • Loading branch information
acomodi committed Apr 8, 2021
1 parent ccb29b1 commit 8e1b4e9
Show file tree
Hide file tree
Showing 2 changed files with 296 additions and 12 deletions.
177 changes: 168 additions & 9 deletions fpga_interchange/fasm_generators/generic.py
Expand Up @@ -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():
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand All @@ -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.
Expand Down
131 changes: 128 additions & 3 deletions fpga_interchange/fasm_generators/xc7.py
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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
Expand All @@ -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]):
Expand Down

0 comments on commit 8e1b4e9

Please sign in to comment.