Source code for FABulous.geometry_generator.tile_geometry

"""Tile geometry generation and management for FABulous FPGA tiles.

This module provides the `TileGeometry` class for representing and generating the
geometric layout of FPGA tiles, including switch matrices, BELs, and interconnect wires.
It handles both direct connections to neighboring tiles and complex stair-like routing
for longer-distance connections.
"""

from FABulous.custom_exception import InvalidPortType
from FABulous.fabric_definition.define import Direction, Side
from FABulous.fabric_definition.Tile import Tile
from FABulous.geometry_generator.bel_geometry import BelGeometry
from FABulous.geometry_generator.geometry_obj import Border, Location
from FABulous.geometry_generator.port_geometry import PortGeometry
from FABulous.geometry_generator.sm_geometry import SmGeometry
from FABulous.geometry_generator.wire_geometry import StairWires, WireGeometry


[docs] class TileGeometry: """A data structure representing the geometry of a tile. Attributes ---------- name : str Name of the tile width : int Width of the tile height : int Height of the tile border : Border Border of the fabric the tile is on smGeometry : SmGeometry Geometry of the tiles switch matrix belGeomList : List[BelGeometry] List of the geometries of the tiles bels wireGeomList : List[WireGeometry] List of the geometries of the tiles wires stairWiresList : List[StairWires] List of the stair-like wires of the tile """
[docs] name: str
[docs] width: int
[docs] height: int
[docs] border: Border
[docs] smGeometry: SmGeometry
[docs] belGeomList: list[BelGeometry]
[docs] wireGeomList: list[WireGeometry]
[docs] stairWiresList: list[StairWires]
def __init__(self) -> None: """Initialize a `TileGeometry` instance. Initializes all attributes to default values: empty name, zero dimensions, no border, and empty lists for geometric components. """ self.name = None self.width = 0 self.height = 0 self.border = Border.NONE self.smGeometry = SmGeometry() self.belGeomList = [] self.wireGeomList = [] self.stairWiresList = []
[docs] def generateGeometry(self, tile: Tile, padding: int) -> None: """Generate the geometry for a tile. Creates geometric representations for all BELs and the switch matrix, then calculates the overall tile dimensions based on the generated components and padding requirements. Parameters ---------- tile : Tile The `Tile` object to generate geometry for padding : int The padding space to add around components """ self.name = tile.name for bel in tile.bels: belGeom = BelGeometry() belGeom.generateGeometry(bel, padding) self.belGeomList.append(belGeom) self.smGeometry.generateGeometry(tile, self.border, self.belGeomList, padding) maxBelWidth = max([belGeom.width for belGeom in self.belGeomList] + [0]) self.width = ( self.smGeometry.relX + self.smGeometry.width + 2 * padding + maxBelWidth ) self.height = ( self.smGeometry.relY + self.smGeometry.height + max( self.smGeometry.eastWiresReservedHeight, self.smGeometry.westWiresReservedHeight, ) + 2 * padding )
[docs] def adjustDimensions( self, maxWidthInColumn: int, maxHeightInRow: int, maxSmWidthInColumn: int, maxSmRelXInColumn: int, ) -> None: """Adjust tile dimensions to match maximum values in fabric grid. Normalizes the tile dimensions and switch matrix positioning to align with the maximum dimensions found in the same fabric column/row, ensuring uniform tile sizing across the fabric. Parameters ---------- maxWidthInColumn : int Maximum width among tiles in the same column maxHeightInRow : int Maximum height among tiles in the same row maxSmWidthInColumn : int Maximum switch matrix width in the same column maxSmRelXInColumn : int Maximum switch matrix relative X position in the same column """ self.width = maxWidthInColumn self.height = maxHeightInRow self.smGeometry.width = maxSmWidthInColumn # TODO: needed? self.smGeometry.relX = maxSmRelXInColumn
# TODO: dim.smWidth = dim.smWidth*2 if dim.smWidth*2 < maxSmWidths[j] # else dim.smWidth
[docs] def adjustSmPos(self, lowestSmYInRow: int, padding: int) -> None: """Ajusts the position of the switch matrix. This is done by using the lowest Y coordinate of any switch matrix in the same row for reference. After this step is completed for all switch matrices, their southern edge will be on the same Y coordinate, allowing for easier inter-tile routing. """ currentSmY = self.smGeometry.relY + self.smGeometry.height additionalOffset = lowestSmYInRow - currentSmY self.smGeometry.relY += additionalOffset self.setBelPositions(padding) # Bel positions are set by now, so the bel ports # of the switch matrix can be generated now. self.smGeometry.generateBelPorts(self.belGeomList)
[docs] def setBelPositions(self, padding: int) -> None: """Set BEL positions.""" belPadding = padding // 2 belX = self.smGeometry.relX + self.smGeometry.width + padding belY = self.smGeometry.relY + belPadding for belGeom in self.belGeomList: belGeom.adjustPos(belX, belY) belY += belGeom.height belY += belPadding
[docs] def generateWires(self, padding: int) -> None: """Generate all wire geometries for the tile. Creates wire geometries for BEL connections, direct connections to neighboring tiles, and indirect connections requiring stair-like routing. Ensures proper alignment of wire positions across different tile types. Parameters ---------- padding : int The padding space to add around wire routing """ self.generateBelWires() self.generateDirectWires(padding) # This adjustment is done to ensure that wires # in tiles with less/more direct north than # south wires (and the same with east/west) # align, such as in some super-tiles. self.northMiddleX = min(self.northMiddleX, self.southMiddleX) self.southMiddleX = min(self.northMiddleX, self.southMiddleX) self.eastMiddleY = max(self.eastMiddleY, self.westMiddleY) self.westMiddleY = max(self.eastMiddleY, self.westMiddleY) self.generateIndirectWires(padding)
[docs] def generateBelWires(self) -> None: """Generate the wires between the switch matrix and its bels.""" for belGeom in self.belGeomList: belToSmDistanceX = belGeom.relX - ( self.smGeometry.relX + self.smGeometry.width ) for portGeom in belGeom.internalPortGeoms: wireName = f"{portGeom.sourceName}{portGeom.destName}" wireGeom = WireGeometry(wireName) start = Location( portGeom.relX + belGeom.relX, portGeom.relY + belGeom.relY ) end = Location( portGeom.relX + belGeom.relX - belToSmDistanceX, portGeom.relY + belGeom.relY, ) wireGeom.addPathLoc(start) wireGeom.addPathLoc(end) self.wireGeomList.append(wireGeom)
northMiddleX = None southMiddleX = None eastMiddleY = None westMiddleY = None
[docs] def generateDirectWires(self, padding: int) -> None: """Generate wires to neigbouring tiles.""" self.northMiddleX = self.smGeometry.relX - padding self.southMiddleX = self.smGeometry.relX - padding self.eastMiddleY = self.smGeometry.relY + self.smGeometry.height + padding self.westMiddleY = self.smGeometry.relY + self.smGeometry.height + padding for portGeom in self.smGeometry.portGeoms: if abs(portGeom.offset) != 1: continue wireName = f"{portGeom.sourceName}{portGeom.destName}" wireGeom = WireGeometry(wireName) if portGeom.sideOfTile == Side.NORTH: startX = self.smGeometry.relX startY = self.smGeometry.relY + portGeom.relY wireGeom.addPathLoc(Location(startX, startY)) middleY = self.smGeometry.relY + portGeom.relY wireGeom.addPathLoc(Location(self.northMiddleX, middleY)) endX = self.northMiddleX endY = 0 wireGeom.addPathLoc(Location(endX, endY)) self.northMiddleX -= 1 elif portGeom.sideOfTile == Side.SOUTH: startX = self.smGeometry.relX startY = self.smGeometry.relY + portGeom.relY wireGeom.addPathLoc(Location(startX, startY)) middleY = self.smGeometry.relY + portGeom.relY wireGeom.addPathLoc(Location(self.southMiddleX, middleY)) endX = self.southMiddleX endY = self.height wireGeom.addPathLoc(Location(endX, endY)) self.southMiddleX -= 1 elif portGeom.sideOfTile == Side.EAST: startX = self.smGeometry.relX + portGeom.relX startY = self.smGeometry.relY + self.smGeometry.height wireGeom.addPathLoc(Location(startX, startY)) middleX = self.smGeometry.relX + portGeom.relX wireGeom.addPathLoc(Location(middleX, self.eastMiddleY)) endX = self.width endY = self.eastMiddleY wireGeom.addPathLoc(Location(endX, endY)) self.eastMiddleY += 1 elif portGeom.sideOfTile == Side.WEST: startX = self.smGeometry.relX + portGeom.relX startY = self.smGeometry.relY + self.smGeometry.height wireGeom.addPathLoc(Location(startX, startY)) middleX = self.smGeometry.relX + portGeom.relX wireGeom.addPathLoc(Location(middleX, self.westMiddleY)) endX = 0 endY = self.westMiddleY wireGeom.addPathLoc(Location(endX, endY)) self.westMiddleY += 1 else: raise InvalidPortType( f"Port with offset 1 and no tile side! {portGeom}" ) self.wireGeomList.append(wireGeom)
currPortGroupId = 0 reserveStairSpaceLeft = False reserveStairSpaceBottom = False queuedAdjustmentLeft = 0 queuedAdjustmentBottom = 0
[docs] def generateIndirectWires(self, padding: int) -> None: """Generate wires to non-neighbouring tiles. These wires require staircase-like routing patterns to reach tiles that are not direct neighbors (offset >= 2). The routing varies by tile side and wire direction. Parameters ---------- padding : int The padding space to add around wire routing """ for portGeom in self.smGeometry.portGeoms: if abs(portGeom.offset) < 2: continue if portGeom.sideOfTile == Side.NORTH: self.indirectNorthSideWire(portGeom, padding) elif portGeom.sideOfTile == Side.SOUTH: self.indirectSouthSideWire(portGeom) elif portGeom.sideOfTile == Side.EAST: self.indirectEastSideWire(portGeom, padding) elif portGeom.sideOfTile == Side.WEST: self.indirectWestSideWire(portGeom) else: raise InvalidPortType( f"Port with abs(offset) > 1 and no tile side! {portGeom}" )
[docs] def indirectNorthSideWire(self, portGeom: PortGeometry, padding: int) -> None: """Generate indirect wires with stair-like routing. Creates staircase-shaped wire routing for connections that span multiple tiles northward. Manages stair wire generation and space reservation based on wire direction and grouping. Parameters ---------- portGeom : PortGeometry The port geometry defining the wire characteristics padding : int The padding space around the wire routing """ generateNorthSouthStairWire = ( self.border != Border.NORTHSOUTH and self.border != Border.CORNER ) # with a new group of ports, there will be the # need for a new stair-like wire for that group if generateNorthSouthStairWire and self.currPortGroupId != portGeom.groupId: self.currPortGroupId = portGeom.groupId if self.reserveStairSpaceLeft: self.reserveStairSpaceLeft = False lastStair = self.stairWiresList[-1] lastStairWidth = lastStair.groupWires * (abs(lastStair.offset) - 1) self.northMiddleX -= lastStairWidth xOffset = 0 if portGeom.wireDirection == Direction.SOUTH: self.reserveStairSpaceLeft = True xOffset = portGeom.groupWires * abs(portGeom.offset) - 1 stairWiresName = f"({portGeom.sourceName}{portGeom.destName})" stairWires = StairWires(stairWiresName) stairWires.generateGeometry( self.northMiddleX - xOffset, self.smGeometry.southPortsTopY + self.smGeometry.relY - padding, portGeom.offset, portGeom.wireDirection, portGeom.groupWires, self.width, self.height, ) self.stairWiresList.append(stairWires) if portGeom.wireDirection == Direction.NORTH: stairReservedWidth = portGeom.groupWires * (abs(portGeom.offset) - 1) self.northMiddleX -= stairReservedWidth wireName = f"{portGeom.sourceName}{portGeom.destName}" wireGeom = WireGeometry(wireName) start = Location(self.northMiddleX, 0) middle = Location(self.northMiddleX, self.smGeometry.relY + portGeom.relY) end = Location(self.smGeometry.relX, self.smGeometry.relY + portGeom.relY) wireGeom.addPathLoc(start) wireGeom.addPathLoc(middle) wireGeom.addPathLoc(end) self.wireGeomList.append(wireGeom) self.northMiddleX -= 1
[docs] def indirectSouthSideWire(self, portGeom: PortGeometry) -> None: """Generate indirect wires on the south side without creating stair-like wires. Creates L-shaped wire routing for southward connections. Unlike north side wires, this method only generates the connection wires and reserves space for stair wires created by the north side method. Parameters ---------- portGeom : PortGeometry The port geometry defining the wire characteristics """ generateNorthSouthStairWire = ( self.border != Border.NORTHSOUTH and self.border != Border.CORNER ) # with a new group of ports, there will be the # need for space for the generated stair-like wire if generateNorthSouthStairWire and self.currPortGroupId != portGeom.groupId: self.currPortGroupId = portGeom.groupId self.southMiddleX -= self.queuedAdjustmentLeft stairReservedWidth = portGeom.groupWires * (abs(portGeom.offset) - 1) # depending on the direction, do the adjustment # now, or queue it - taking the different "bending" # of the stair-like wire into account. if portGeom.wireDirection == Direction.NORTH: self.queuedAdjustmentLeft = stairReservedWidth if portGeom.wireDirection == Direction.SOUTH: self.southMiddleX -= stairReservedWidth self.queuedAdjustmentLeft = 0 wireName = f"{portGeom.sourceName}{portGeom.destName}" wireGeom = WireGeometry(wireName) start = Location(self.southMiddleX, self.height) middle = Location(self.southMiddleX, self.smGeometry.relY + portGeom.relY) end = Location(self.smGeometry.relX, self.smGeometry.relY + portGeom.relY) wireGeom.addPathLoc(start) wireGeom.addPathLoc(middle) wireGeom.addPathLoc(end) self.wireGeomList.append(wireGeom) self.southMiddleX -= 1
[docs] def indirectEastSideWire(self, portGeom: PortGeometry, padding: int) -> None: """Generate indirect wires on the east side of the tile with stair-like routing. Creates staircase-shaped wire routing for connections that span multiple tiles eastward. Manages stair wire generation and space reservation based on wire direction and grouping. Parameters ---------- portGeom : PortGeometry The port geometry defining the wire characteristics padding : int The padding space around the wire routing """ generateEastWestStairWire = ( self.border != Border.EASTWEST and self.border != Border.CORNER ) # with a new group of ports, there will be the # need for a new stair-like wire for that group if generateEastWestStairWire and self.currPortGroupId != portGeom.groupId: self.currPortGroupId = portGeom.groupId if self.reserveStairSpaceBottom: self.reserveStairSpaceBottom = False lastStair = self.stairWiresList[-1] lastStairWidth = lastStair.groupWires * (abs(lastStair.offset) - 1) self.eastMiddleY += lastStairWidth yOffset = 0 if portGeom.wireDirection == Direction.WEST: self.reserveStairSpaceBottom = True yOffset = portGeom.groupWires * abs(portGeom.offset) - 1 stairWiresName = f"({portGeom.sourceName}{portGeom.destName})" stairWires = StairWires(stairWiresName) stairWires.generateGeometry( self.smGeometry.westPortsRightX + self.smGeometry.relX + padding, self.eastMiddleY + yOffset, portGeom.offset, portGeom.wireDirection, portGeom.groupWires, self.width, self.height, ) self.stairWiresList.append(stairWires) if portGeom.wireDirection == Direction.EAST: stairReservedWidth = portGeom.groupWires * (abs(portGeom.offset) - 1) self.eastMiddleY += stairReservedWidth wireName = f"{portGeom.sourceName}{portGeom.destName}" wireGeom = WireGeometry(wireName) start = Location( self.smGeometry.relX + portGeom.relX, self.smGeometry.relY + portGeom.relY ) middle = Location(self.smGeometry.relX + portGeom.relX, self.eastMiddleY) end = Location(self.width, self.eastMiddleY) wireGeom.addPathLoc(start) wireGeom.addPathLoc(middle) wireGeom.addPathLoc(end) self.wireGeomList.append(wireGeom) self.eastMiddleY += 1
[docs] def indirectWestSideWire(self, portGeom: PortGeometry) -> None: """Generate indirect wires on the west side without creating stair-like wires. Creates L-shaped wire routing for westward connections. Unlike east side wires, this method only generates the connection wires and reserves space for stair wires created by the east side method. Parameters ---------- portGeom : PortGeometry The port geometry defining the wire characteristics """ generateEastWestStairWire = ( self.border != Border.EASTWEST and self.border != Border.CORNER ) # with a new group of ports, there will be the # need for space for the generated stair-like wire if generateEastWestStairWire and self.currPortGroupId != portGeom.groupId: self.currPortGroupId = portGeom.groupId self.westMiddleY += self.queuedAdjustmentBottom stairReservedHeight = portGeom.groupWires * (abs(portGeom.offset) - 1) # depending on the direction, do the adjustment # now, or queue it - taking the different "bending" # of the stair-like wire into account. if portGeom.wireDirection == Direction.EAST: self.queuedAdjustmentBottom = stairReservedHeight if portGeom.wireDirection == Direction.WEST: self.westMiddleY += stairReservedHeight self.queuedAdjustmentBottom = 0 wireName = f"{portGeom.sourceName}{portGeom.destName}" wireGeom = WireGeometry(wireName) start = Location(0, self.westMiddleY) middle = Location(self.smGeometry.relX + portGeom.relX, self.westMiddleY) end = Location( self.smGeometry.relX + portGeom.relX, self.smGeometry.relY + portGeom.relY ) wireGeom.addPathLoc(start) wireGeom.addPathLoc(middle) wireGeom.addPathLoc(end) self.wireGeomList.append(wireGeom) self.westMiddleY += 1
[docs] def saveToCSV(self, writer: object) -> None: """Save tile geometry data to CSV format. Writes the tile geometry information including dimensions and all geometric components (switch matrix, BELs, wires, stair wires) to a CSV file using the provided writer. Parameters ---------- writer The CSV `writer` object to use for output """ writer.writerows( [ ["TILE"], ["Name"] + [self.name], ["Width"] + [str(self.width)], ["Height"] + [str(self.height)], [], ] ) self.smGeometry.saveToCSV(writer) for belGeometry in self.belGeomList: belGeometry.saveToCSV(writer) for wireGeometry in self.wireGeomList: wireGeometry.saveToCSV(writer) for stairWires in self.stairWiresList: stairWires.saveToCSV(writer)
def __repr__(self) -> str: """Return string representation of the tile geometry. Returns ------- str String containing the width and height of the tile """ return f"{self.width, self.height}"