Source code for FABulous.fabric_definition.Fabric

"""FPGA fabric definition module.

This module contains the Fabric class which represents the complete FPGA fabric
including tile layout, configuration parameters, and connectivity information. The
fabric is the top-level container for all tiles, BELs, and routing resources.
"""

from dataclasses import dataclass, field

from FABulous.fabric_definition.Bel import Bel
from FABulous.fabric_definition.define import (
    ConfigBitMode,
    Direction,
    MultiplexerStyle,
)
from FABulous.fabric_definition.SuperTile import SuperTile
from FABulous.fabric_definition.Tile import Tile
from FABulous.fabric_definition.Wire import Wire


@dataclass
[docs] class Fabric: """Store the configuration of a fabric. All the information is parsed from the CSV file. Attributes ---------- tile : list[list[Tile]] The tile map of the fabric name : str The name of the fabric numberOfRows : int The number of rows of the fabric numberOfColumns : int The number of columns of the fabric configMitMode : ConfigBitMode The configuration bit mode of the fabric. Currently supports frame based or ff chain frameBitsPerRow : int The number of frame bits per row of the fabric maxFramesPerCol : int The maximum number of frames per column of the fabric package : str The extra package used by the fabric. Only useful for VHDL output. generateDelayInSwitchMatrix : int The amount of delay in a switch matrix. multiplexerStyle : MultiplexerStyle The style of the multiplexer used in the fabric. Currently supports custom or generic frameSelectWidth : int The width of the frame select signal. rowSelectWidth : int The width of the row select signal. desync_flag : int The flag indicating desynchronization status, used to manage timing issues within the fabric. numberOfBRAMs : int The number of BRAMs in the fabric. superTileEnable : bool Whether the fabric has super tile. tileDic : dict[str, Tile] A dictionary of tiles used in the fabric. The key is the name of the tile and the value is the tile. superTileDic : dict[str, SuperTile] A dictionary of super tiles used in the fabric. The key is the name of the supertile and the value is the supertile. unusedTileDic: dict[str, Tile] A dictionary of tiles that are not used in the fabric, but defined in the fabric.csv. The key is the name of the tile and the value is the tile. unusedSuperTileDic: dict[str, Tile] A dictionary of super tiles that are not used in the fabric, but defined in the fabric.csv. The key is the name of the tile and the value is the tile. """
[docs] tile: list[list[Tile]] = field(default_factory=list)
[docs] name: str = "eFPGA"
[docs] numberOfRows: int = 15
[docs] numberOfColumns: int = 15
configBitMode: ConfigBitMode = ConfigBitMode.FRAME_BASED
[docs] frameBitsPerRow: int = 32
[docs] maxFramesPerCol: int = 20
[docs] package: str = "use work.my_package.all"
[docs] generateDelayInSwitchMatrix: int = 80
[docs] multiplexerStyle: MultiplexerStyle = MultiplexerStyle.CUSTOM
[docs] frameSelectWidth: int = 5
[docs] rowSelectWidth: int = 5
[docs] desync_flag: int = 20
[docs] numberOfBRAMs: int = 10
[docs] superTileEnable: bool = True
[docs] tileDic: dict[str, Tile] = field(default_factory=dict)
[docs] superTileDic: dict[str, SuperTile] = field(default_factory=dict)
[docs] unusedTileDic: dict[str, Tile] = field(default_factory=dict)
[docs] unusedSuperTileDic: dict[str, SuperTile] = field(default_factory=dict)
commonWirePair: list[tuple[str, str]] = field(default_factory=list) def __post_init__(self) -> None: """Generate and get all the wire pairs in the fabric. The wire pair are used during model generation when some of the signals have source or destination of "NULL". The wires are used during model generation to work with wire that going cross tile. """ for row in self.tile: for tile in row: if tile is None: continue for port in tile.portsInfo: self.commonWirePair.append((port.sourceName, port.destinationName)) self.commonWirePair = list(dict.fromkeys(self.commonWirePair)) self.commonWirePair = [ (i, j) for i, j in self.commonWirePair if i != "NULL" and j != "NULL" ] for y, row in enumerate(self.tile): for x, tile in enumerate(row): if tile is None: continue for port in tile.portsInfo: if ( abs(port.xOffset) <= 1 and abs(port.yOffset) <= 1 and port.sourceName != "NULL" and port.destinationName != "NULL" ): for i in range(port.wireCount): tile.wireList.append( Wire( direction=port.wireDirection, source=f"{port.sourceName}{i}", xOffset=port.xOffset, yOffset=port.yOffset, destination=f"{port.destinationName}{i}", sourceTile="", destinationTile="", ) ) elif port.sourceName != "NULL" and port.destinationName != "NULL": # clamp the xOffset to 1 or -1 value = min(max(port.xOffset, -1), 1) cascadedI = 0 for i in range(port.wireCount * abs(port.xOffset)): if i < port.wireCount: cascadedI = i + port.wireCount * (abs(port.xOffset) - 1) else: cascadedI = i - port.wireCount tile.wireList.append( Wire( direction=Direction.JUMP, source=f"{port.destinationName}{i}", xOffset=0, yOffset=0, destination=f"{port.sourceName}{i}", sourceTile=f"X{x}Y{y}", destinationTile=f"X{x}Y{y}", ) ) tile.wireList.append( Wire( direction=port.wireDirection, source=f"{port.sourceName}{i}", xOffset=value, yOffset=port.yOffset, destination=f"{port.destinationName}{cascadedI}", sourceTile=f"X{x}Y{y}", destinationTile=f"X{x + value}Y{y + port.yOffset}", ) ) # clamp the yOffset to 1 or -1 value = min(max(port.yOffset, -1), 1) cascadedI = 0 for i in range(port.wireCount * abs(port.yOffset)): if i < port.wireCount: cascadedI = i + port.wireCount * (abs(port.yOffset) - 1) else: cascadedI = i - port.wireCount tile.wireList.append( Wire( direction=Direction.JUMP, source=f"{port.destinationName}{i}", xOffset=0, yOffset=0, destination=f"{port.sourceName}{i}", sourceTile=f"X{x}Y{y}", destinationTile=f"X{x}Y{y}", ) ) tile.wireList.append( Wire( direction=port.wireDirection, source=f"{port.sourceName}{i}", xOffset=port.xOffset, yOffset=value, destination=f"{port.destinationName}{cascadedI}", sourceTile=f"X{x}Y{y}", destinationTile=f"X{x + port.xOffset}Y{y + value}", ) ) elif port.sourceName != "NULL" and port.destinationName == "NULL": sourceName = port.sourceName destName = port.sourceName # if sourcename is not in a common pair wire we assume # the source name is the same as destination name wire_pair = dict(self.commonWirePair) if sourceName in wire_pair: destName = wire_pair[sourceName] value = min(max(port.xOffset, -1), 1) for i in range(port.wireCount * abs(port.xOffset)): tile.wireList.append( Wire( direction=port.wireDirection, source=f"{sourceName}{i}", xOffset=value, yOffset=port.yOffset, destination=f"{destName}{i}", sourceTile=f"X{x}Y{y}", destinationTile=f"X{x + value}Y{y + port.yOffset}", ) ) value = min(max(port.yOffset, -1), 1) for i in range(port.wireCount * abs(port.yOffset)): tile.wireList.append( Wire( direction=port.wireDirection, source=f"{sourceName}{i}", xOffset=port.xOffset, yOffset=value, destination=f"{destName}{i}", sourceTile=f"X{x}Y{y}", destinationTile=f"X{x + port.xOffset}Y{y + value}", ) ) tile.wireList = list(dict.fromkeys(tile.wireList)) def __repr__(self) -> str: """Return the string representation of the fabric. Returns ------- str A formatted string showing the fabric layout and key parameters. """ fabric = "" for i in range(self.numberOfRows): for j in range(self.numberOfColumns): if self.tile[i][j] is None: fabric += "Null".ljust(15) + "\t" else: fabric += f"{str(self.tile[i][j].name).ljust(15)}\t" fabric += "\n" fabric += "\n" fabric += f"numberOfColumns: {self.numberOfColumns}\n" fabric += f"numberOfRows: {self.numberOfRows}\n" fabric += f"configBitMode: {self.configBitMode}\n" fabric += f"frameBitsPerRow: {self.frameBitsPerRow}\n" fabric += f"maxFramesPerCol: {self.maxFramesPerCol}\n" fabric += f"package: {self.package}\n" fabric += f"generateDelayInSwitchMatrix: {self.generateDelayInSwitchMatrix}\n" fabric += f"multiplexerStyle: {self.multiplexerStyle}\n" fabric += f"superTileEnable: {self.superTileEnable}\n" fabric += f"tileDic: {list(self.tileDic.keys())}\n" return fabric
[docs] def getTileByName(self, name: str) -> Tile | None: """Get a tile by its name from the fabric. Search for the tile first in the used tiles dictionary, then in the unused tiles dictionary if not found. Parameters ---------- name : str The name of the tile to retrieve. Returns ------- Tile | None The tile object if found. Raises ------ KeyError If the tile name is not found in either used or unused tiles. """ ret = self.tileDic.get(name) if ret is None: ret = self.unusedTileDic.get(name) if ret is None: raise KeyError(f"Tile {name} not found in fabric.") return ret
[docs] def getSuperTileByName(self, name: str) -> SuperTile | None: """Get a supertile by its name from the fabric. Searches for the supertile first in the used supertiles dictionary, then in the unused supertiles dictionary if not found. Parameters ---------- name : str The name of the supertile to retrieve. Returns ------- SuperTile | None The super tile object if found. Raises ------ KeyError If the super tile name is not found in either used or unused super tiles. """ ret = self.superTileDic.get(name) if ret is None: ret = self.unusedSuperTileDic.get(name) if ret is None: raise KeyError(f"SuperTile {name} not found in fabric.") return ret
[docs] def getAllUniqueBels(self) -> list[Bel]: """Get all unique BELs from all tiles in the fabric. Returns ------- list[Bel] A list of all unique BELs across all tiles. """ bels = list() for tile in self.tileDic.values(): bels.extend(tile.bels) return bels
[docs] def getBelsByTileXY(self, x: int, y: int) -> list[Bel]: """Get all the Bels of a tile. Parameters ---------- x : int The x coordinate of / column the tile. y : int The y coordinate / row of the tile. Returns ------- list[Bel] A list of Bels in the tile. Raises ------ ValueError Tile coordinates are out of range. """ if x < 0 or x >= self.numberOfColumns or y < 0 or y >= self.numberOfRows: raise ValueError( f"Invalid tile coordinates: ({x},{y}) max (0,0) - ({self.numberOfRows}," f"{self.numberOfColumns})" ) if self.tile[y][x] is None: return [] return self.tile[y][x].bels