Source code for FABulous.geometry_generator.sm_geometry

"""Switch matrix geometry definitions."""

import pathlib

from loguru import logger

from FABulous.fabric_definition.define import IO, Direction, Side
from FABulous.fabric_definition.Port import Port
from FABulous.fabric_definition.Tile import Tile
from FABulous.geometry_generator.bel_geometry import BelGeometry
from FABulous.geometry_generator.geometry_obj import Border
from FABulous.geometry_generator.port_geometry import PortGeometry, PortType


[docs] class SmGeometry: """A data structure representing the geometry of a Switch Matrix. Attributes ---------- name : str Name of the switch matrix src : str File path of the switch matrix HDL source file csv : str File path of the switch matrix CSV file width : int Width of the switch matrix height : int Height of the switch matrix relX : int X coordinate of the switch matrix, relative within the tile relY : int Y coordinate of the switch matrix, relative within the tile northPorts : List[Port] List of the ports of the switch matrix in north direction southPorts : List[Port] List of the ports of the switch matrix in south direction eastPorts : List[Port] List of the ports of the switch matrix in east direction westPorts : List[Port] List of the ports of the switch matrix in west direction jumpPorts : List[Port] List of the jump ports of the switch matrix portGeoms : List[PortGeometry] List of geometries of the ports of the switch matrix northWiresReservedWidth : int Reserved width for wires going north southWiresReservedWidth : int Reserved width for wires going south eastWiresReservedHeight : int Reserved height for wires going east westWiresReservedHeight : int Reserved height for wires going west southPortsTopY : int Top most y coord of any south port, reference for stair-wires westPortsRightX : int Right most x coord of any west port, reference for stair-wires """
[docs] name: str
[docs] src: pathlib.Path
[docs] csv: pathlib.Path
[docs] width: int
[docs] height: int
[docs] relX: int
[docs] relY: int
[docs] northPorts: list[Port]
[docs] southPorts: list[Port]
[docs] eastPorts: list[Port]
[docs] westPorts: list[Port]
[docs] jumpPorts: list[Port]
[docs] portGeoms: list[PortGeometry]
[docs] northWiresReservedWidth: int
[docs] southWiresReservedWidth: int
[docs] eastWiresReservedHeight: int
[docs] westWiresReservedHeight: int
[docs] southPortsTopY: int
[docs] westPortsRightX: int
def __init__(self) -> None: """Initialize a SmGeometry instance. Sets all attributes to default values: None for names and paths, zero for dimensions and coordinates, and empty lists for ports and port geometries. """ self.name = None self.src = None self.csv = None self.width = 0 self.height = 0 self.relX = 0 self.relY = 0 self.northPorts = [] self.southPorts = [] self.eastPorts = [] self.westPorts = [] self.jumpPorts = [] self.portGeoms = [] self.northWiresReservedWidth = 0 self.southWiresReservedWidth = 0 self.eastWiresReservedHeight = 0 self.westWiresReservedHeight = 0 self.southPortsTopY = 0 self.westPortsRightX = 0
[docs] def preprocessPorts(self, tileBorder: Border) -> None: """Order the ports for downstream drawing. Ensure that ports are ordered correctly, merge connected jump ports and augment ports for term tiles. This step augments ports in border tiles. This is needed, as these are not contained in the (north...west)SidePorts in FABulous. """ # TODO: check if numbering is generated correctly # for augmented ports if tileBorder == Border.NORTHSOUTH or tileBorder == Border.CORNER: augmentedSouthPorts = [] for southPort in self.southPorts: if abs(southPort.yOffset) > 1: augmentedPort = Port( southPort.wireDirection, southPort.sourceName, 0, 1, southPort.destinationName, southPort.wireCount * abs(southPort.yOffset), southPort.name, southPort.inOut, southPort.sideOfTile, ) augmentedSouthPorts.append(augmentedPort) else: augmentedSouthPorts.append(southPort) self.southPorts = augmentedSouthPorts augmentedNorthPorts = [] for northPort in self.northPorts: if abs(northPort.yOffset) > 1: augmentedPort = Port( northPort.wireDirection, northPort.sourceName, 0, 1, northPort.destinationName, northPort.wireCount * abs(northPort.yOffset), northPort.name, northPort.inOut, northPort.sideOfTile, ) augmentedNorthPorts.append(augmentedPort) else: augmentedNorthPorts.append(northPort) self.northPorts = augmentedNorthPorts if tileBorder == Border.EASTWEST or tileBorder == Border.CORNER: augmentedEastPorts = [] for eastPort in self.eastPorts: if abs(eastPort.xOffset) > 1: augmentedPort = Port( eastPort.wireDirection, eastPort.sourceName, 1, 0, eastPort.destinationName, eastPort.wireCount * abs(eastPort.xOffset), eastPort.name, eastPort.inOut, eastPort.sideOfTile, ) augmentedEastPorts.append(augmentedPort) else: augmentedEastPorts.append(eastPort) self.eastPorts = augmentedEastPorts augmentedWestPorts = [] for westPort in self.westPorts: if abs(westPort.xOffset) > 1: augmentedPort = Port( westPort.wireDirection, westPort.sourceName, 1, 0, westPort.destinationName, westPort.wireCount * abs(westPort.xOffset), westPort.name, westPort.inOut, westPort.sideOfTile, ) augmentedWestPorts.append(augmentedPort) else: augmentedWestPorts.append(westPort) self.westPorts = augmentedWestPorts # This step ensures correct ordering, this is important # for the wire generation step. self.northPorts = sorted(self.northPorts, key=lambda port: abs(port.yOffset)) self.southPorts = sorted(self.southPorts, key=lambda port: abs(port.yOffset)) self.eastPorts = sorted(self.eastPorts, key=lambda port: abs(port.xOffset)) self.westPorts = sorted(self.westPorts, key=lambda port: abs(port.xOffset)) # This step merges connected jump ports into # a single port. mergedJumpPorts = [] portNameMap = {} for jumpPort in self.jumpPorts: portNameMap[jumpPort.name] = jumpPort while len(portNameMap) != 0: firstPortName = next(iter(portNameMap)) firstPort = portNameMap[firstPortName] if firstPortName != firstPort.sourceName: partnerName = firstPort.sourceName else: partnerName = firstPort.destinationName if partnerName in portNameMap: mergedPort = Port( Direction.JUMP, firstPort.sourceName, 0, 0, firstPort.destinationName, firstPort.wireCount, firstPortName, IO.INOUT, firstPort.sideOfTile, ) mergedJumpPorts.append(mergedPort) del portNameMap[firstPortName] del portNameMap[partnerName] else: logger.info(f"No partner found for {firstPortName}") logger.info(f"Partner would have been {partnerName}") logger.info(f"Adding jump port {firstPortName} without partner") mergedJumpPorts.append(firstPort) del portNameMap[firstPortName] self.jumpPorts = mergedJumpPorts
[docs] def generateGeometry( self, tile: Tile, tileBorder: Border, belGeoms: list[BelGeometry], padding: int ) -> None: """Generate the geometry for a switch matrix. Creates the geometric representation of a switch matrix including its dimensions, port arrangements, and spatial relationships. Calculates the required space for routing wires and positions the switch matrix within the tile. the required space for routing wires and positions for the switch matrix Parameters ---------- tile : Tile The tile object containing the switch matrix definition tileBorder : Border The border type of the tile within the fabric belGeoms : list[BelGeometry] List of BEL geometries within the same tile padding : int The padding space to add around the switch matrix """ self.name = f"{tile.name}_switch_matrix" self.src = tile.tileDir.parent.joinpath(f"{self.name}.v") self.csv = tile.tileDir.parent.joinpath(f"{self.name}.csv") self.jumpPorts = [ port for port in tile.portsInfo if port.wireDirection == Direction.JUMP ] self.northPorts = tile.getNorthSidePorts() self.southPorts = tile.getSouthSidePorts() self.eastPorts = tile.getEastSidePorts() self.westPorts = tile.getWestSidePorts() self.preprocessPorts(tileBorder) # Counting the total number of wires for each direction northWires = sum([port.wireCount for port in self.northPorts]) southWires = sum([port.wireCount for port in self.southPorts]) eastWires = sum([port.wireCount for port in self.eastPorts]) westWires = sum([port.wireCount for port in self.westPorts]) jumpWires = sum([port.wireCount for port in self.jumpPorts]) self.northWiresReservedWidth = sum( [abs(port.yOffset) * port.wireCount for port in self.northPorts] ) self.southWiresReservedWidth = sum( [abs(port.yOffset) * port.wireCount for port in self.southPorts] ) self.eastWiresReservedHeight = sum( [abs(port.xOffset) * port.wireCount for port in self.eastPorts] ) self.westWiresReservedHeight = sum( [abs(port.xOffset) * port.wireCount for port in self.westPorts] ) self.relX = ( max(self.northWiresReservedWidth, self.southWiresReservedWidth) + 2 * padding ) self.relY = padding # These gaps are for the stair-like wires, # hence they're not needed for border tiles, # where no stair-like wires are generated. if tileBorder == Border.NORTHSOUTH or tileBorder == Border.CORNER: portsGapWest = 0 else: portsGapWest = sum( [ port.wireCount for port in (self.northPorts + self.southPorts) if abs(port.yOffset) > 1 ] ) portsGapWest += padding if tileBorder == Border.EASTWEST or tileBorder == Border.CORNER: portsGapSouth = 0 else: portsGapSouth = sum( [ port.wireCount for port in (self.eastPorts + self.westPorts) if abs(port.xOffset) > 1 ] ) portsGapSouth += padding belsHeightTotal = sum([belGeom.height for belGeom in belGeoms]) belPadding = padding // 2 belsPaddingTotal = (len(belGeoms) + 1) * belPadding belsReservedSpace = belsHeightTotal + belsPaddingTotal self.width = max(eastWires + westWires + portsGapSouth, jumpWires) + 2 * padding self.height = max( southWires + northWires + portsGapWest + 2 * padding, belsReservedSpace ) self.generatePortsGeometry(padding) self.southPortsTopY = min( [geom.relY for geom in self.portGeoms if geom.sideOfTile == Side.SOUTH] + [self.height] ) self.westPortsRightX = max( [geom.relX for geom in self.portGeoms if geom.sideOfTile == Side.WEST] + [0] )
[docs] def generatePortsGeometry(self, padding: int) -> None: """Generate the geometry for all ports of the switch matrix. Creates `PortGeometry` objects for all jump, north, south, east, and west ports of the switch matrix. Positions each port according to its type and assigns appropriate coordinates and grouping information. Parameters ---------- padding : int The padding space to add around ports """ jumpPortX = padding jumpPortY = 0 for port in self.jumpPorts: for i in range(port.wireCount): portGeom = PortGeometry() portGeom.generateGeometry( f"{port.name}{i}", f"{port.sourceName}{i}", f"{port.destinationName}{i}", PortType.JUMP, port.inOut, jumpPortX, jumpPortY, ) self.portGeoms.append(portGeom) jumpPortX += 1 northPortX = 0 northPortY = padding for port in self.northPorts: for i in range(port.wireCount): portGeom = PortGeometry() portGeom.generateGeometry( f"{port.name}{i}", f"{port.sourceName}{i}", f"{port.destinationName}{i}", PortType.SWITCH_MATRIX, port.inOut, northPortX, northPortY, ) portGeom.sideOfTile = port.sideOfTile portGeom.offset = port.yOffset portGeom.wireDirection = port.wireDirection portGeom.groupId = PortGeometry.nextId portGeom.groupWires = port.wireCount self.portGeoms.append(portGeom) northPortY += 1 PortGeometry.nextId += 1 southPortX = 0 southPortY = self.height - padding for port in self.southPorts: for i in range(port.wireCount): portGeom = PortGeometry() portGeom.generateGeometry( f"{port.name}{i}", f"{port.sourceName}{i}", f"{port.destinationName}{i}", PortType.SWITCH_MATRIX, port.inOut, southPortX, southPortY, ) portGeom.sideOfTile = port.sideOfTile portGeom.offset = port.yOffset portGeom.wireDirection = port.wireDirection portGeom.groupId = PortGeometry.nextId portGeom.groupWires = port.wireCount self.portGeoms.append(portGeom) southPortY -= 1 PortGeometry.nextId += 1 eastPortX = self.width - padding eastPortY = self.height for port in self.eastPorts: for i in range(port.wireCount): portGeom = PortGeometry() portGeom.generateGeometry( f"{port.name}{i}", f"{port.sourceName}{i}", f"{port.destinationName}{i}", PortType.SWITCH_MATRIX, port.inOut, eastPortX, eastPortY, ) portGeom.sideOfTile = port.sideOfTile portGeom.offset = port.xOffset portGeom.wireDirection = port.wireDirection portGeom.groupId = PortGeometry.nextId portGeom.groupWires = port.wireCount self.portGeoms.append(portGeom) eastPortX -= 1 PortGeometry.nextId += 1 westPortX = padding westPortY = self.height for port in self.westPorts: for i in range(port.wireCount): portGeom = PortGeometry() portGeom.generateGeometry( f"{port.name}{i}", f"{port.sourceName}{i}", f"{port.destinationName}{i}", PortType.SWITCH_MATRIX, port.inOut, westPortX, westPortY, ) portGeom.sideOfTile = port.sideOfTile portGeom.offset = port.xOffset portGeom.wireDirection = port.wireDirection portGeom.groupId = PortGeometry.nextId portGeom.groupWires = port.wireCount self.portGeoms.append(portGeom) westPortX += 1 PortGeometry.nextId += 1
[docs] def generateBelPorts(self, belGeomList: list[BelGeometry]) -> None: """Generate port geometries for BEL connections to the switch matrix. Creates `PortGeometry` objects for connecting BEL internal ports to the switch matrix. These ports facilitate routing between BELs and the switch matrix interconnect network. Parameters ---------- belGeomList : list[BelGeometry] List of BEL geometries to connect to the switch matrix """ for belGeom in belGeomList: for belPortGeom in belGeom.internalPortGeoms: portX = self.width portY = belGeom.relY - self.relY + belPortGeom.relY portGeom = PortGeometry() portGeom.generateGeometry( belPortGeom.name, belPortGeom.sourceName, belPortGeom.destName, PortType.SWITCH_MATRIX, belPortGeom.ioDirection, portX, portY, ) self.portGeoms.append(portGeom)
[docs] def saveToCSV(self, writer: object) -> None: """Save switch matrix geometry data to CSV format. Writes the switch matrix geometry information including name, source and CSV file paths, position, dimensions, and all port geometries to a CSV file using the provided writer. Parameters ---------- writer : The CSV `writer` object to use for output """ writer.writerows( [ ["SWITCH_MATRIX"], ["Name"] + [self.name], ["Src"] + [self.src], ["Csv"] + [self.csv], ["RelX"] + [str(self.relX)], ["RelY"] + [str(self.relY)], ["Width"] + [str(self.width)], ["Height"] + [str(self.height)], [], ] ) for portGeom in self.portGeoms: portGeom.saveToCSV(writer)