Source code for FABulous.geometry_generator.fabric_geometry
"""Classes for generating and managing the geometry of FPGA fabrics."""
from csv import writer as csvWriter
from pathlib import Path
from loguru import logger
from FABulous.fabric_definition.Fabric import Fabric
from FABulous.geometry_generator.geometry_obj import Border, Location
from FABulous.geometry_generator.tile_geometry import TileGeometry
[docs]
class FabricGeometry:
"""Fetch and hold geometric information about a fabric.
Objects of this class can be constructed by passing a `Fabric` object
and optionally, padding.
Attributes
----------
fabric : Fabric
The fabric object passed from the CSV definition files
tileNames : Set[str]
Set of unique tileNames in the fabric
tileGeomMap : Dict[str, TileGeometry]
Map of the geometry of each tile by name
tileLocs : List[List[Location]]
Locations of all tiles in the fabric
padding : int
Padding used throughout the geometry, in multiples of the width between wires
width : int
Width of the fabric
height : int
Height of the fabric
"""
[docs]
tileGeomMap: dict[str, TileGeometry]
[docs]
tileLocs: list[list[Location]]
def __init__(self, fabric: Fabric, padding: int = 8) -> None:
"""Initialize a FabricGeometry instance.
Creates the fabric geometry by processing the given fabric definition
and automatically generating the complete geometric layout.
Parameters
----------
fabric : Fabric
The fabric object from CSV definition files
padding : int, optional
Padding used throughout the geometry, by default 8
"""
self.fabric = fabric
self.tileNames = set()
self.tileGeomMap = {}
self.tileLocs = []
self.padding = padding
self.width = 0
self.height = 0
self.generateGeometry()
[docs]
def generateGeometry(self) -> None:
"""Generate the geometric information from the given fabric object.
The border attribute is set for tiles that are located at a border of the tile.
This is done to ensure no stair-like wires being generated for these tiles. The
distinction left/right and top/bottom is made, to prevent generation of
horizontal and vertical stair-like wires respectively.
"""
for i in range(self.fabric.numberOfRows):
for j in range(self.fabric.numberOfColumns):
tile = self.fabric.tile[i][j]
if tile is not None:
self.tileNames.add(tile.name)
if tile.name not in self.tileGeomMap:
self.tileGeomMap[tile.name] = TileGeometry()
tileGeom = self.tileGeomMap[tile.name]
northSouth = i == 0 or i + 1 == self.fabric.numberOfRows
eastWest = j == 0 or j + 1 == self.fabric.numberOfColumns
if northSouth and eastWest:
tileGeom.border = Border.CORNER
elif northSouth:
tileGeom.border = Border.NORTHSOUTH
elif eastWest:
tileGeom.border = Border.EASTWEST
for tileName in self.tileNames:
tile = self.fabric.getTileByName(tileName)
tileGeom = self.tileGeomMap[tileName]
tileGeom.generateGeometry(tile, self.padding)
# This step is for figuring out, which tile
# is the widest/tallest in each column/row
# All tiles are resized to those dimensions
# in order to form a regular grid.
tileGeometries = []
for i in range(self.fabric.numberOfRows):
tileGeometries.append([])
for j in range(self.fabric.numberOfColumns):
tile = self.fabric.tile[i][j]
if tile is None:
tileGeometries[i].append(TileGeometry())
else:
tileGeometries[i].append(self.tileGeomMap[tile.name])
maxWidths = []
maxSmRelXValues = []
maxSmWidths = []
for j in range(self.fabric.numberOfColumns):
maxWidth = 0
maxSmRelX = 0
maxSmWidth = 0
for i in range(self.fabric.numberOfRows):
maxWidth = max(maxWidth, tileGeometries[i][j].width)
maxSmRelX = max(maxSmRelX, tileGeometries[i][j].smGeometry.relX)
maxSmWidth = max(maxSmWidth, tileGeometries[i][j].smGeometry.width)
maxWidths.append(maxWidth)
maxSmRelXValues.append(maxSmRelX)
maxSmWidths.append(maxSmWidth)
lowestSmYCoords = []
maxHeights = []
for i in range(self.fabric.numberOfRows):
lowestSmYCoord = 0
maxHeight = 0
for j in range(self.fabric.numberOfColumns):
smRelY = tileGeometries[i][j].smGeometry.relY
smHeight = tileGeometries[i][j].smGeometry.height
lowestSmYCoord = max(lowestSmYCoord, smRelY + smHeight)
maxHeight = max(maxHeight, tileGeometries[i][j].height)
lowestSmYCoords.append(lowestSmYCoord)
maxHeights.append(maxHeight)
for i in range(self.fabric.numberOfRows):
for j in range(self.fabric.numberOfColumns):
tile = self.fabric.tile[i][j]
if tile is not None:
maxWidthInColumn = maxWidths[j]
maxSmWidthInColumn = maxSmWidths[j]
maxSmRelXInColumn = maxSmRelXValues[j]
maxHeightInRow = maxHeights[i]
tileGeom = self.tileGeomMap[tile.name]
tileGeom.adjustDimensions(
maxWidthInColumn,
maxHeightInRow,
maxSmWidthInColumn,
maxSmRelXInColumn,
)
for i in range(self.fabric.numberOfRows):
self.tileLocs.append([])
for j in range(self.fabric.numberOfColumns):
tile = self.fabric.tile[i][j]
if tile is None:
self.tileLocs[i].append(None)
else:
tileX = sum([maxWidths[k] for k in range(j)])
tileY = sum([maxHeights[k] for k in range(i)])
self.tileLocs[i].append(Location(tileX, tileY))
# this step is for figuring out the fabric dimensions
# as tile dimensions are fixed by now.
# Because of the top left point of the fabric being
# the origin (0, 0), the fabrics dimensions can be
# figured out by determining the rightmost and
# bottommost points of the fabric.
rightMostX = 0
bottomMostY = 0
for i in range(self.fabric.numberOfRows):
tile = self.fabric.tile[i][-1]
if tile is not None:
tileGeom = self.tileGeomMap[tile.name]
tileLoc = self.tileLocs[i][-1]
tileRightmostX = tileLoc.x + tileGeom.width
rightMostX = max(rightMostX, tileRightmostX)
for j in range(self.fabric.numberOfColumns):
tile = self.fabric.tile[-1][j]
if tile is not None:
tileGeom = self.tileGeomMap[tile.name]
tileLoc = self.tileLocs[-1][j]
tileBottommostY = tileLoc.y + tileGeom.height
bottomMostY = max(bottomMostY, tileBottommostY)
self.width = rightMostX
self.height = bottomMostY
# this step is for rearranging the switch matrices by setting
# the relX/relY appropriately. This is done to ensure that
# all inter-tile wires line up correctly.
adjustedTileNames = set()
for i in range(self.fabric.numberOfRows):
for j in range(self.fabric.numberOfColumns):
tile = self.fabric.tile[i][j]
if tile is not None and tile.name not in adjustedTileNames:
lowestSmYInRow = lowestSmYCoords[i]
tileGeom = self.tileGeomMap[tile.name]
tileGeom.adjustSmPos(lowestSmYInRow, self.padding)
adjustedTileNames.add(tile.name)
# all tiles should now be adjusted
assert adjustedTileNames == self.tileNames
# By now, the geometry of the whole fabric is fixed,
# hence we can start generating the inter-tile wires.
for tileName in self.tileNames:
tileGeom = self.tileGeomMap[tileName]
tileGeom.generateWires(self.padding)
[docs]
def saveToCSV(self, fileName: str) -> None:
"""Save geometric information of the given fabric for the graphical frontend.
Parameters
----------
fileName : str
The name of the csv file
"""
logger.info(
f"Generating geometry csv file for {self.fabric.name} # file name: "
f"{fileName}"
)
with Path(f"{fileName}").open("w", newline="", encoding="utf-8") as file:
writer = csvWriter(file)
writer.writerows(
[
["PARAMS"],
["Name"] + [self.fabric.name],
["Rows"] + [str(self.fabric.numberOfRows)],
["Columns"] + [str(self.fabric.numberOfColumns)],
["Width"] + [str(self.width)],
["Height"] + [str(self.height)],
[],
]
)
writer.writerow(["FABRIC_DEF"])
for i in range(self.fabric.numberOfRows):
writer.writerow(
[
tile.name if tile is not None else "Null"
for tile in self.fabric.tile[i]
]
)
writer.writerow([])
writer.writerow(["FABRIC_LOCS"])
for i in range(self.fabric.numberOfRows):
writer.writerow(
[loc if loc is not None else "Null" for loc in self.tileLocs[i]]
)
writer.writerows([[], []])
for tileName in sorted(self.tileNames):
tileGeometry = self.tileGeomMap[tileName]
tileGeometry.saveToCSV(writer)
def __repr__(self) -> str:
"""Return the string representation of the fabric geometry.
Provides a formatted view of the tile dimensions and locations in a
grid layout showing the fabric structure.
Returns
-------
str
Multi-line string showing tile dimensions and locations
"""
geometry = "Respective dimensions of tiles: \n"
for i in range(self.fabric.numberOfRows):
for j in range(self.fabric.numberOfColumns):
tile = self.fabric.tile[i][j]
if tile is None:
geometry += "Null".ljust(8) + "\t"
else:
geometry += f"{str(self.tileGeomMap[tile.name]).ljust(8)}\t "
geometry += "\n"
geometry += "Respective locations of tiles: \n"
for i in range(self.fabric.numberOfRows):
for j in range(self.fabric.numberOfColumns):
loc = self.tileLocs[i][j]
if loc is None:
geometry += "Null".ljust(8) + "\t"
else:
geometry += f"{str(loc).ljust(8)}\t "
geometry += "\n"
return geometry