From b62f1a530957f6c1ec58cbf203772386a033873d Mon Sep 17 00:00:00 2001 From: _Trizion_ Date: Wed, 28 Feb 2024 16:30:33 +0100 Subject: [PATCH] Improve and fix problems in geometry generation --- README.md | 22 +++ geometry_generator/fabric_geometry.py | 119 +++++++++++--- geometry_generator/geometry_obj.py | 12 ++ geometry_generator/sm_geometry.py | 84 +++++----- geometry_generator/tile_geometry.py | 222 ++++++++++++++++---------- geometry_generator/wire_geometry.py | 167 ++++++++++++------- 6 files changed, 427 insertions(+), 199 deletions(-) diff --git a/README.md b/README.md index 6875cf52..a13333b5 100644 --- a/README.md +++ b/README.md @@ -87,3 +87,25 @@ The tool also supports using TCL script to drive the build process. Assuming you More details on bitstream generation can be found [here](https://fabulous.readthedocs.io/en/latest/FPGA-to-bitstream/Bitstream%20generation.html). Detailed documentation for the project can be found [here](https://fabulous.readthedocs.io/en/latest/index.html) + + +## GUI Setup + +[FABulator](https://github.com/FPGA-Research-Manchester/FABulator) +supports exploration of fabrics generated +by FABulous, as well as displaying user +designs. + +to import a fabric into FABulator, a geometry file +for the fabric is needed. This can be generated by +running + +``` +load_fabric +gen_fabric # only needed once to generate matrix.csv files +gen_geometry +``` + +in the FABulous shell. This file can then be imported +into FABulator. More information can be found +[here](https://github.com/FPGA-Research-Manchester/FABulator). \ No newline at end of file diff --git a/geometry_generator/fabric_geometry.py b/geometry_generator/fabric_geometry.py index 8377553e..323d4a10 100644 --- a/geometry_generator/fabric_geometry.py +++ b/geometry_generator/fabric_geometry.py @@ -7,6 +7,8 @@ logger = logging.getLogger(__name__) +GENERATOR_VERSION = "1.0.0" + class FabricGeometry: """ @@ -21,7 +23,7 @@ class FabricGeometry: 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 - + """ fabric: Fabric tileNames: Set[str] @@ -31,7 +33,6 @@ class FabricGeometry: width: int height: int - def __init__(self, fabric: Fabric, padding: int = 8): self.fabric = fabric self.tileNames = set() @@ -43,18 +44,44 @@ def __init__(self, fabric: Fabric, padding: int = 8): self.generateGeometry() + def genNeighbourConstraints(self, queried: TileGeometry) -> None: + for i in range(self.fabric.numberOfRows): + for j in range(self.fabric.numberOfColumns): + tile = self.fabric.tile[i][j] + if tile is None: + continue + tileName = tile.name + tileGeom = self.tileGeomMap[tileName] + + if tileGeom == queried: + searchedTile = None + if queried.border == Border.NORTHSOUTH: + if i == 0: + searchedTile = self.fabric.tile[i + 1][j] + else: + searchedTile = self.fabric.tile[i - 1][j] + + elif queried.border == Border.EASTWEST: + if j == 0: + searchedTile = self.fabric.tile[i][j + 1] + else: + searchedTile = self.fabric.tile[i][j - 1] + + if searchedTile is not None: + searchedTileName = searchedTile.name + queried.neighbourConstraints = self.tileGeomMap[searchedTileName].wireConstraints def generateGeometry(self) -> None: """ Generates the geometric information from the given fabric object - + """ # here, the border attribute is set for tiles that are - # located at a border of the tile. This is done to + # 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 + # 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): @@ -67,12 +94,32 @@ def generateGeometry(self) -> None: 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) + 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 + + # generate geometry for central tiles first + # to avoid conflicts at outer tiles: + # with the geometry of inner tiles already + # generated, outer tiles can simply generate + # their wires such that everything lines up. + innerTileNames = [] + outerTileNames = [] - 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: + tileGeom = self.tileGeomMap[tileName] + self.genNeighbourConstraints(tileGeom) + + if tileGeom.border == Border.NONE: + innerTileNames.append(tileName) + else: + outerTileNames.append(tileName) for tileName in self.tileNames: tile = self.fabric.getTileByName(tileName) @@ -101,7 +148,7 @@ def generateGeometry(self) -> None: maxWidth = 0 maxSmRelX = 0 maxSmWidth = 0 - for i in range(self.fabric.numberOfColumns): + 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) @@ -178,7 +225,7 @@ def generateGeometry(self) -> None: self.width = rightMostX self.height = bottomMostY - # this step is for rearranging the switch matrices by setting + # 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() @@ -198,20 +245,49 @@ def generateGeometry(self) -> None: # By now, the geometry of the whole fabric is fixed, # hence we can start generating the inter-tile wires. - for tileName in self.tileNames: + for tileName in innerTileNames: tileGeom = self.tileGeomMap[tileName] tileGeom.generateWires(self.padding) + for tileName in outerTileNames: + tileGeom = self.tileGeomMap[tileName] + tileGeom.generateWires(self.padding) + + def totalWireLines(self) -> int: + """ + Returns the total amount of lines (segments) + of wires of the fabrics routing. + Can, for instance, be used to initialize + the size of datastructures in the frontend. + + """ + lineGeomMap = {} + totalWireLines = 0 + + for i in range(self.fabric.numberOfRows): + for j in range(self.fabric.numberOfColumns): + tile = self.fabric.tile[i][j] + if tile is None: continue + + if tile.name not in lineGeomMap: + tileGeom = self.tileGeomMap[tile.name] + tileLines = tileGeom.totalWireLines() + lineGeomMap[tile.name] = tileLines + totalWireLines += tileLines + else: + totalWireLines += lineGeomMap[tile.name] + + return totalWireLines def saveToCSV(self, fileName: str) -> None: """ - Saves the generated geometric information of the + Saves the generated geometric information of the given fabric to a .csv file that can be imported into the graphical frontend. Args: fileName (str): the name of the csv file - + """ logger.info(f"Generating geometry csv file for {self.fabric.name} # file name: {fileName}") @@ -220,11 +296,13 @@ def saveToCSV(self, fileName: str) -> None: writer.writerows([ ["PARAMS"], - ["Name"] + [self.fabric.name], - ["Rows"] + [str(self.fabric.numberOfRows)], + ["GeneratorVersion"] + [GENERATOR_VERSION], + ["Name"] + [self.fabric.name], + ["Rows"] + [str(self.fabric.numberOfRows)], ["Columns"] + [str(self.fabric.numberOfColumns)], - ["Width"] + [str(self.width)], - ["Height"] + [str(self.height)], + ["Width"] + [str(self.width)], + ["Height"] + [str(self.height)], + ["Lines"] + [str(self.totalWireLines())], [] ]) @@ -242,7 +320,6 @@ def saveToCSV(self, fileName: str) -> None: tileGeometry = self.tileGeomMap[tileName] tileGeometry.saveToCSV(writer) - def __repr__(self) -> str: geometry = "Respective dimensions of tiles: \n" for i in range(self.fabric.numberOfRows): diff --git a/geometry_generator/geometry_obj.py b/geometry_generator/geometry_obj.py index 1985817e..6f9d04ca 100644 --- a/geometry_generator/geometry_obj.py +++ b/geometry_generator/geometry_obj.py @@ -1,4 +1,5 @@ from enum import Enum +from fabric_generator.fabric import IO class Location: @@ -29,3 +30,14 @@ class Border(Enum): EASTWEST = "EASTWEST" CORNER = "CORNER" NONE = "NONE" + + + +def oppositeIO(io: IO): + if io == IO.INPUT: + return IO.OUTPUT + if io == IO.OUTPUT: + return IO.INPUT + if io == IO.INOUT: + return IO.INOUT + return IO.NULL diff --git a/geometry_generator/sm_geometry.py b/geometry_generator/sm_geometry.py index 5fdb6266..7ab673d9 100644 --- a/geometry_generator/sm_geometry.py +++ b/geometry_generator/sm_geometry.py @@ -1,6 +1,6 @@ from typing import List from fabric_generator.fabric import Port, Tile, Direction, Side, IO -from geometry_generator.geometry_obj import Border +from geometry_generator.geometry_obj import Border, oppositeIO from geometry_generator.bel_geometry import BelGeometry from geometry_generator.port_geometry import PortGeometry, PortType from csv import writer as csvWriter @@ -52,7 +52,7 @@ class SmGeometry: southWiresReservedWidth: int eastWiresReservedHeight: int westWiresReservedHeight: int - southPortsTopY: int + southPortsTopY: int westPortsRightX: int def __init__(self): @@ -76,7 +76,6 @@ def __init__(self): self.southPortsTopY = 0 self.westPortsRightX = 0 - def preprocessPorts(self, tileBorder: Border) -> None: """ Ensures that ports are ordered correctly, @@ -84,18 +83,26 @@ def preprocessPorts(self, tileBorder: Border) -> None: ports for term tiles. """ + # 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 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.sourceName, + 0, + 1, + southPort.destinationName, southPort.wireCount * abs(southPort.yOffset), southPort.name, southPort.inOut, @@ -111,7 +118,10 @@ def preprocessPorts(self, tileBorder: Border) -> None: if abs(northPort.yOffset) > 1: augmentedPort = Port( northPort.wireDirection, - northPort.sourceName, 0, 1, northPort.destinationName, + northPort.sourceName, + 0, + 1, + northPort.destinationName, northPort.wireCount * abs(northPort.yOffset), northPort.name, northPort.inOut, @@ -128,7 +138,10 @@ def preprocessPorts(self, tileBorder: Border) -> None: if abs(eastPort.xOffset) > 1: augmentedPort = Port( eastPort.wireDirection, - eastPort.sourceName, 1, 0, eastPort.destinationName, + eastPort.sourceName, + 1, + 0, + eastPort.destinationName, eastPort.wireCount * abs(eastPort.xOffset), eastPort.name, eastPort.inOut, @@ -144,7 +157,10 @@ def preprocessPorts(self, tileBorder: Border) -> None: if abs(westPort.xOffset) > 1: augmentedPort = Port( westPort.wireDirection, - westPort.sourceName, 1, 0, westPort.destinationName, + westPort.sourceName, + 1, + 0, + westPort.destinationName, westPort.wireCount * abs(westPort.xOffset), westPort.name, westPort.inOut, @@ -155,13 +171,6 @@ def preprocessPorts(self, tileBorder: Border) -> None: 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 = [] @@ -202,7 +211,6 @@ def preprocessPorts(self, tileBorder: Border) -> None: self.jumpPorts = mergedJumpPorts - def generateGeometry(self, tile: Tile, tileBorder: Border, belGeoms: List[BelGeometry], padding: int) -> None: self.name = tile.name + "_switch_matrix" @@ -226,9 +234,9 @@ def generateGeometry(self, tile: Tile, tileBorder: Border, 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.westWiresReservedHeight = sum([abs(port.xOffset) * port.wireCount for port in self.westPorts]) - self.relX = max(self.northWiresReservedWidth, self.southWiresReservedWidth) + 2 * padding + self.relX = max(self.northWiresReservedWidth, self.southWiresReservedWidth) + 2 * padding self.relY = padding # These gaps are for the stair-like wires, @@ -237,7 +245,9 @@ def generateGeometry(self, tile: Tile, tileBorder: Border, 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 = 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: @@ -252,13 +262,14 @@ def generateGeometry(self, tile: Tile, tileBorder: Border, belsReservedSpace = belsHeightTotal + belsPaddingTotal self.width = max(eastWires + westWires + portsGapSouth, jumpWires) + 2 * padding - self.height = max(southWires + northWires + portsGapWest + 2 * padding, belsReservedSpace) + 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.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]) - def generatePortsGeometry(self, padding: int) -> None: jumpPortX = padding jumpPortY = 0 @@ -297,7 +308,7 @@ def generatePortsGeometry(self, padding: int) -> None: self.portGeoms.append(portGeom) northPortY += 1 - PortGeometry.nextId += 1 + PortGeometry.nextId += 1 southPortX = 0 southPortY = self.height - padding @@ -320,7 +331,7 @@ def generatePortsGeometry(self, padding: int) -> None: self.portGeoms.append(portGeom) southPortY -= 1 - PortGeometry.nextId += 1 + PortGeometry.nextId += 1 eastPortX = self.width - padding eastPortY = self.height @@ -334,7 +345,7 @@ def generatePortsGeometry(self, padding: int) -> None: PortType.SWITCH_MATRIX, port.inOut, eastPortX, eastPortY - ) + ) portGeom.sideOfTile = port.sideOfTile portGeom.offset = port.xOffset portGeom.wireDirection = port.wireDirection @@ -343,7 +354,7 @@ def generatePortsGeometry(self, padding: int) -> None: self.portGeoms.append(portGeom) eastPortX -= 1 - PortGeometry.nextId += 1 + PortGeometry.nextId += 1 westPortX = padding westPortY = self.height @@ -368,7 +379,6 @@ def generatePortsGeometry(self, padding: int) -> None: westPortX += 1 PortGeometry.nextId += 1 - def generateBelPorts(self, belGeomList: List[BelGeometry]) -> None: for belGeom in belGeomList: for belPortGeom in belGeom.internalPortGeoms: @@ -381,25 +391,23 @@ def generateBelPorts(self, belGeomList: List[BelGeometry]) -> None: belPortGeom.sourceName, belPortGeom.destName, PortType.SWITCH_MATRIX, - belPortGeom.ioDirection, + oppositeIO(belPortGeom.ioDirection), portX, portY ) self.portGeoms.append(portGeom) - def saveToCSV(self, writer: csvWriter) -> None: 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)], + ["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) - \ No newline at end of file diff --git a/geometry_generator/tile_geometry.py b/geometry_generator/tile_geometry.py index c0a47efd..6abba4c5 100644 --- a/geometry_generator/tile_geometry.py +++ b/geometry_generator/tile_geometry.py @@ -3,7 +3,7 @@ from geometry_generator.geometry_obj import Border, Location from geometry_generator.sm_geometry import SmGeometry from geometry_generator.bel_geometry import BelGeometry -from geometry_generator.wire_geometry import WireGeometry, StairWires +from geometry_generator.wire_geometry import WireGeometry, StairWires, WireConstraints from geometry_generator.port_geometry import PortGeometry from csv import writer as csvWriter @@ -13,99 +13,97 @@ class TileGeometry: A datastruct 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 - + 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 + wireConstraints (WireConstraints) : Positions of wires arriving at the tile border + neighbourConstraints (WireConstraints) : WireConstraints of neighbouring tile + 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 + """ name: str width: int height: int border: Border + wireConstraints: WireConstraints + neighbourConstraints: WireConstraints smGeometry: SmGeometry belGeomList: List[BelGeometry] wireGeomList: List[WireGeometry] stairWiresList: List[StairWires] - def __init__(self): self.name = None self.width = 0 self.height = 0 self.border = Border.NONE + self.wireConstraints = WireConstraints() + self.neighbourConstraints = None self.smGeometry = SmGeometry() self.belGeomList = [] self.wireGeomList = [] self.stairWiresList = [] - def generateGeometry(self, tile: Tile, padding: int) -> None: self.name = tile.name - + for bel in tile.bels: belGeom = BelGeometry() belGeom.generateGeometry(bel, padding) - self.belGeomList.append(belGeom) - + 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) + 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) def adjustDimensions(self, maxWidthInColumn: int, - maxHeightInRow: int, - maxSmWidthInColumn: int, - maxSmRelXInColumn: int) -> None: - + maxHeightInRow: int, + maxSmWidthInColumn: int, + maxSmRelXInColumn: int) -> None: + self.width = maxWidthInColumn self.height = maxHeightInRow - self.smGeometry.width = maxSmWidthInColumn # TODO: needed? + self.smGeometry.width = maxSmWidthInColumn self.smGeometry.relX = maxSmRelXInColumn - # TODO: - #dim.smWidth = dim.smWidth*2 if dim.smWidth*2 < maxSmWidths[j] else dim.smWidth - - def adjustSmPos(self, lowestSmYInRow: int, padding: int) -> None: """ - ajusts the position of the switch matrix, using + adjusts the position of the switch matrix, 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 + 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 + # Bel positions are set by now, so the bel ports # of the switch matrix can be generated now. self.smGeometry.generateBelPorts(self.belGeomList) - def setBelPositions(self, padding: int) -> None: """ The position of the switch matrix is final when this is called, thus bel positions can be set. - + """ belPadding = padding // 2 belX = self.smGeometry.relX + self.smGeometry.width + padding @@ -115,28 +113,26 @@ def setBelPositions(self, padding: int) -> None: belY += belGeom.height belY += belPadding - def generateWires(self, padding: int) -> None: 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) + # 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.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) - def generateBelWires(self) -> None: """ Generates the wires between the switch matrix and its bels. - + """ for belGeom in self.belGeomList: belToSmDistanceX = belGeom.relX - (self.smGeometry.relX + self.smGeometry.width) @@ -156,7 +152,6 @@ def generateBelWires(self) -> None: wireGeom.addPathLoc(end) self.wireGeomList.append(wireGeom) - northMiddleX = None southMiddleX = None eastMiddleY = None @@ -164,15 +159,31 @@ def generateBelWires(self) -> None: def generateDirectWires(self, padding: int) -> None: """ - Generates wires to neigbouring tiles, which are + Generates wires to neighbouring tiles, which are straightforward to generate. - """ + """ 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 + if self.border == Border.NORTHSOUTH: + wireNorthPositions = sorted(self.neighbourConstraints.southPositions, reverse=True) + wireSouthPositions = sorted(self.neighbourConstraints.northPositions, reverse=True) + northIter = iter(wireNorthPositions) + southIter = iter(wireSouthPositions) + self.northMiddleX = next(northIter, None) + self.southMiddleX = next(southIter, None) + + if self.border == Border.EASTWEST: + wireEastPositions = sorted(self.neighbourConstraints.westPositions) + wireWestPositions = sorted(self.neighbourConstraints.eastPositions) + eastIter = iter(wireEastPositions) + westIter = iter(wireWestPositions) + self.eastMiddleY = next(eastIter, None) + self.westMiddleY = next(westIter, None) + for portGeom in self.smGeometry.portGeoms: if abs(portGeom.offset) != 1: continue wireName = f"{portGeom.sourceName} ⟶ {portGeom.destName}" @@ -189,7 +200,12 @@ def generateDirectWires(self, padding: int) -> None: endX = self.northMiddleX endY = 0 wireGeom.addPathLoc(Location(endX, endY)) - self.northMiddleX -= 1 + self.wireConstraints.northPositions.append(self.northMiddleX) + + if self.border == Border.NORTHSOUTH: + self.northMiddleX = next(northIter, 0) + else: + self.northMiddleX -= 1 elif portGeom.sideOfTile == Side.SOUTH: startX = self.smGeometry.relX @@ -202,7 +218,12 @@ def generateDirectWires(self, padding: int) -> None: endX = self.southMiddleX endY = self.height wireGeom.addPathLoc(Location(endX, endY)) - self.southMiddleX -= 1 + self.wireConstraints.southPositions.append(self.southMiddleX) + + if self.border == Border.NORTHSOUTH: + self.southMiddleX = next(southIter, 0) + else: + self.southMiddleX -= 1 elif portGeom.sideOfTile == Side.EAST: startX = self.smGeometry.relX + portGeom.relX @@ -215,7 +236,12 @@ def generateDirectWires(self, padding: int) -> None: endX = self.width endY = self.eastMiddleY wireGeom.addPathLoc(Location(endX, endY)) - self.eastMiddleY += 1 + self.wireConstraints.eastPositions.append(self.eastMiddleY) + + if self.border == Border.EASTWEST: + self.eastMiddleY = next(eastIter, 0) + else: + self.eastMiddleY += 1 elif portGeom.sideOfTile == Side.WEST: startX = self.smGeometry.relX + portGeom.relX @@ -228,11 +254,16 @@ def generateDirectWires(self, padding: int) -> None: endX = 0 endY = self.westMiddleY wireGeom.addPathLoc(Location(endX, endY)) - self.westMiddleY += 1 + self.wireConstraints.westPositions.append(self.westMiddleY) + + if self.border == Border.EASTWEST: + self.westMiddleY = next(westIter, 0) + else: + self.westMiddleY += 1 else: raise Exception("port with offset 1 and no tile side!") - + self.wireGeomList.append(wireGeom) currPortGroupId = 0 @@ -246,10 +277,11 @@ def generateIndirectWires(self, padding: int): Generates wires to non-neighbouring tiles. These are not straightforward to generate, as they require a staircase-like shape. - + """ for portGeom in self.smGeometry.portGeoms: - if abs(portGeom.offset) < 2: continue + if abs(portGeom.offset) < 2: + continue if portGeom.sideOfTile == Side.NORTH: self.indirectNorthSideWire(portGeom, padding) @@ -262,7 +294,6 @@ def generateIndirectWires(self, padding: int): else: raise Exception("port with abs(offset) > 1 and no tile side!") - def indirectNorthSideWire(self, portGeom: PortGeometry, padding: int) -> None: """ generates indirect wires on the north side of the tile, @@ -271,7 +302,7 @@ def indirectNorthSideWire(self, portGeom: PortGeometry, padding: int) -> None: """ generateNorthSouthStairWire = (self.border != Border.NORTHSOUTH and self.border != Border.CORNER) - # with a new group of ports, there will be the + # 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 @@ -290,12 +321,13 @@ def indirectNorthSideWire(self, portGeom: PortGeometry, padding: int) -> None: stairWiresName = f"({portGeom.sourceName} ⟶ {portGeom.destName})" stairWires = StairWires(stairWiresName) stairWires.generateGeometry( - self.northMiddleX - xOffset, + self.northMiddleX - xOffset, self.smGeometry.southPortsTopY + self.smGeometry.relY - padding, portGeom.offset, portGeom.wireDirection, portGeom.groupWires, self.width, self.height ) self.stairWiresList.append(stairWires) + self.wireConstraints.addConstraintsOf(stairWires) if portGeom.wireDirection == Direction.NORTH: stairReservedWidth = portGeom.groupWires * (abs(portGeom.offset) - 1) @@ -304,7 +336,7 @@ def indirectNorthSideWire(self, portGeom: PortGeometry, padding: int) -> None: wireName = f"{portGeom.sourceName} ⟶ {portGeom.destName}" wireGeom = WireGeometry(wireName) start = Location( - self.northMiddleX, + self.northMiddleX, 0 ) middle = Location( @@ -319,19 +351,19 @@ def indirectNorthSideWire(self, portGeom: PortGeometry, padding: int) -> None: wireGeom.addPathLoc(middle) wireGeom.addPathLoc(end) self.wireGeomList.append(wireGeom) - self.northMiddleX -= 1 - - + self.wireConstraints.northPositions.append(self.northMiddleX) + self.northMiddleX -= 1 + def indirectSouthSideWire(self, portGeom: PortGeometry) -> None: """ - In contrast to indirectNorthSideWire(), this method - generates only indirect wires on the south side of + In contrast to indirectNorthSideWire(), this method + generates only indirect wires on the south side of the tile, but no stair-like wires. """ generateNorthSouthStairWire = (self.border != Border.NORTHSOUTH and self.border != Border.CORNER) - # with a new group of ports, there will be the + # 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 @@ -344,7 +376,7 @@ def indirectSouthSideWire(self, portGeom: PortGeometry) -> None: # 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 @@ -352,7 +384,7 @@ def indirectSouthSideWire(self, portGeom: PortGeometry) -> None: wireName = f"{portGeom.sourceName} ⟶ {portGeom.destName}" wireGeom = WireGeometry(wireName) start = Location( - self.southMiddleX, + self.southMiddleX, self.height ) middle = Location( @@ -367,9 +399,9 @@ def indirectSouthSideWire(self, portGeom: PortGeometry) -> None: wireGeom.addPathLoc(middle) wireGeom.addPathLoc(end) self.wireGeomList.append(wireGeom) + self.wireConstraints.southPositions.append(self.southMiddleX) self.southMiddleX -= 1 - def indirectEastSideWire(self, portGeom: PortGeometry, padding: int) -> None: """ generates indirect wires on the east side of the tile, @@ -378,7 +410,7 @@ def indirectEastSideWire(self, portGeom: PortGeometry, padding: int) -> None: """ generateEastWestStairWire = (self.border != Border.EASTWEST and self.border != Border.CORNER) - # with a new group of ports, there will be the + # 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 @@ -397,12 +429,13 @@ def indirectEastSideWire(self, portGeom: PortGeometry, padding: int) -> None: stairWiresName = f"({portGeom.sourceName} ⟶ {portGeom.destName})" stairWires = StairWires(stairWiresName) stairWires.generateGeometry( - self.smGeometry.westPortsRightX + self.smGeometry.relX + padding, + self.smGeometry.westPortsRightX + self.smGeometry.relX + padding, self.eastMiddleY + yOffset, portGeom.offset, portGeom.wireDirection, portGeom.groupWires, self.width, self.height ) self.stairWiresList.append(stairWires) + self.wireConstraints.addConstraintsOf(stairWires) if portGeom.wireDirection == Direction.EAST: stairReservedWidth = portGeom.groupWires * (abs(portGeom.offset) - 1) @@ -411,7 +444,7 @@ def indirectEastSideWire(self, portGeom: PortGeometry, padding: int) -> None: wireName = f"{portGeom.sourceName} ⟶ {portGeom.destName}" wireGeom = WireGeometry(wireName) start = Location( - self.smGeometry.relX + portGeom.relX, + self.smGeometry.relX + portGeom.relX, self.smGeometry.relY + portGeom.relY ) middle = Location( @@ -426,19 +459,19 @@ def indirectEastSideWire(self, portGeom: PortGeometry, padding: int) -> None: wireGeom.addPathLoc(middle) wireGeom.addPathLoc(end) self.wireGeomList.append(wireGeom) - self.eastMiddleY += 1 - + self.wireConstraints.eastPositions.append(self.eastMiddleY) + self.eastMiddleY += 1 def indirectWestSideWire(self, portGeom: PortGeometry) -> None: """ - In contrast to indirectEastSideWire(), this method - generates only indirect wires on the south side of + In contrast to indirectEastSideWire(), this method + generates only indirect wires on the south side of the tile, but no stair-like wires. """ generateEastWestStairWire = (self.border != Border.EASTWEST and self.border != Border.CORNER) - # with a new group of ports, there will be the + # 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 @@ -451,7 +484,7 @@ def indirectWestSideWire(self, portGeom: PortGeometry) -> None: # 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 @@ -459,7 +492,7 @@ def indirectWestSideWire(self, portGeom: PortGeometry) -> None: wireName = f"{portGeom.sourceName} ⟶ {portGeom.destName}" wireGeom = WireGeometry(wireName) start = Location( - 0, + 0, self.westMiddleY ) middle = Location( @@ -474,15 +507,34 @@ def indirectWestSideWire(self, portGeom: PortGeometry) -> None: wireGeom.addPathLoc(middle) wireGeom.addPathLoc(end) self.wireGeomList.append(wireGeom) + self.wireConstraints.westPositions.append(self.westMiddleY) self.westMiddleY += 1 + def totalWireLines(self) -> int: + """ + Returns the total amount of lines (segments) + of wires of the tiles routing. + + """ + totalWireLines = 0 + + for wireGeom in self.wireGeomList: + lines = len(wireGeom.path) - 1 + totalWireLines += lines + + for stairWires in self.stairWiresList: + for wireGeom in stairWires.wireGeoms: + lines = len(wireGeom.path) - 1 + totalWireLines += lines + + return totalWireLines def saveToCSV(self, writer: csvWriter) -> None: writer.writerows([ ["TILE"], - ["Name"] + [self.name], - ["Width"] + [str(self.width)], - ["Height"] + [str(self.height)], + ["Name"] + [self.name], + ["Width"] + [str(self.width)], + ["Height"] + [str(self.height)], [] ]) self.smGeometry.saveToCSV(writer) @@ -491,7 +543,7 @@ def saveToCSV(self, writer: csvWriter) -> None: belGeometry.saveToCSV(writer) for wireGeometry in self.wireGeomList: - wireGeometry.saveToCSV(writer) + wireGeometry.saveToCSV(writer) for stairWires in self.stairWiresList: stairWires.saveToCSV(writer) diff --git a/geometry_generator/wire_geometry.py b/geometry_generator/wire_geometry.py index 10ce7d14..0a2d6dc8 100644 --- a/geometry_generator/wire_geometry.py +++ b/geometry_generator/wire_geometry.py @@ -16,16 +16,13 @@ class WireGeometry: name: str path: List[Location] - def __init__(self, name: str): self.name = name self.path = [] - def addPathLoc(self, pathLoc: Location) -> None: self.path.append(pathLoc) - def saveToCSV(self, writer: csvWriter) -> None: writer.writerows([ ["WIRE"], @@ -39,7 +36,6 @@ def saveToCSV(self, writer: csvWriter) -> None: writer.writerow([]) - class StairWires: """ A datastruct representing a stair-like collection of wires @@ -48,24 +44,24 @@ class StairWires: name (str) : Name of the structure refX (int) : Reference point x coord of the stair structure refY (int) : Reference point y coord of the stair structure - offset (int) : Offset of the wires - direction (Direction) : Direction of the wires + offset (int) : Offset of the wires + direction (Direction) : Direction of the wires groupWires (int) : Amount of wires of a single "strand" tileWidth (int) : Width of the tile containing the wires tileHeight (int) : Height of the tile containing the wires - wireGeoms List[WireGeometry] : List of the wires geometries + wireGeoms List[WireGeometry] : List of the wires geometries The (refX, refY) point refers to the following location(s) of the stair-like structure: - - @ @ @ @@ @@ @@ - @ @ @ @@ @@ @@ - @ @ @ @@ @@ @@ - @ @ @ @@ @@ @@ - @ @ @ @@ @@ @@ - @ @ @@@@@@@@ @@@@@@@@ @@ @@ - @ @ @@ @ @@ @@ - @ @@@@@@@% @@ @ @@@@@@@@ @@ - @ @% @@ @ @@ @@ + + @ @ @ @@ @@ @@ + @ @ @ @@ @@ @@ + @ @ @ @@ @@ @@ + @ @ @ @@ @@ @@ + @ @ @ @@ @@ @@ + @ @ @@@@@@@@ @@@@@@@@ @@ @@ + @ @ @@ @ @@ @@ + @ @@@@@@@% @@ @ @@@@@@@@ @@ + @ @% @@ @ @@ @@ --> @@@@@@%@ @% @@ @ @# #@@@@@@@@ <-- (refX, refY) %@ @% @@ @ @# #@ %@ @% @@ @ @# #@ @@ -73,7 +69,7 @@ class StairWires: %@ @% @@ @ @# #@ %@ @@ @@ @ @# #@ - Depending on the orientation of the structure. Rotate right by 90° to get + Depending on the orientation of the structure. Rotate right by 90° to get the image for the corresponding left-right stair-ike wire structure. The right stair-like structure represents a north stair, the left one represents a south stair (These being the directions of the wires). @@ -85,11 +81,10 @@ class StairWires: offset: int direction: Direction groupWires: int - tileWidth: int + tileWidth: int tileHeight: int wireGeoms: List[WireGeometry] - def __init__(self, name: str): self.name = name self.refX = 0 @@ -101,8 +96,7 @@ def __init__(self, name: str): self.tileHeight = 0 self.wireGeoms = [] - - def generateGeometry(self, refX: int, refY: int, + def generateGeometry(self, refX: int, refY: int, offset: int, direction: Direction, groupWires: int, tileWidth: int, tileHeight: int) -> None: self.refX = refX @@ -124,56 +118,59 @@ def generateGeometry(self, refX: int, refY: int, else: raise Exception("Invalid direction!") - def generateNorthStairWires(self) -> None: totalWires = self.groupWires * (abs(self.offset) - 1) + refX = self.refX + refY = self.refY for i in range(totalWires): - wireGeom = WireGeometry(f"{self.name} #{i}") - start = Location(self.refX, 0) - nextToStart = Location(self.refX, self.refY) - nextToEnd = Location(self.refX - self.groupWires, self.refY) - end = Location(self.refX - self.groupWires, self.tileHeight) + wireGeom = WireGeometry(f"{self.name} #{i}") + start = Location(refX, 0) + nextToStart = Location(refX, refY) + nextToEnd = Location(refX - self.groupWires, refY) + end = Location(refX - self.groupWires, self.tileHeight) wireGeom.addPathLoc(start) wireGeom.addPathLoc(nextToStart) wireGeom.addPathLoc(nextToEnd) wireGeom.addPathLoc(end) self.wireGeoms.append(wireGeom) - - self.refX -= 1 - self.refY -= 1 + refX -= 1 + refY -= 1 def generateSouthStairWires(self) -> None: totalWires = self.groupWires * (abs(self.offset) - 1) + refX = self.refX + refY = self.refY for i in range(totalWires): - wireGeom = WireGeometry(f"{self.name} #{i}") - start = Location(self.refX, 0) - nextToStart = Location(self.refX, self.refY) - nextToEnd = Location(self.refX + self.groupWires, self.refY) - end = Location(self.refX + self.groupWires, self.tileHeight) + wireGeom = WireGeometry(f"{self.name} #{i}") + start = Location(refX, 0) + nextToStart = Location(refX, refY) + nextToEnd = Location(refX + self.groupWires, refY) + end = Location(refX + self.groupWires, self.tileHeight) wireGeom.addPathLoc(start) wireGeom.addPathLoc(nextToStart) wireGeom.addPathLoc(nextToEnd) wireGeom.addPathLoc(end) self.wireGeoms.append(wireGeom) - - self.refX += 1 - self.refY -= 1 + refX += 1 + refY -= 1 def generateEastStairWires(self) -> None: totalWires = self.groupWires * (abs(self.offset) - 1) + refX = self.refX + refY = self.refY for i in range(totalWires): - wireGeom = WireGeometry(f"{self.name} #{i}") - start = Location(self.tileWidth, self.refY) - nextToStart = Location(self.refX, self.refY) - nextToEnd = Location(self.refX, self.refY + self.groupWires) - end = Location(0, self.refY + self.groupWires) + wireGeom = WireGeometry(f"{self.name} #{i}") + start = Location(self.tileWidth, refY) + nextToStart = Location(refX, refY) + nextToEnd = Location(refX, refY + self.groupWires) + end = Location(0, refY + self.groupWires) wireGeom.addPathLoc(start) wireGeom.addPathLoc(nextToStart) @@ -181,19 +178,20 @@ def generateEastStairWires(self) -> None: wireGeom.addPathLoc(end) self.wireGeoms.append(wireGeom) - self.refX += 1 - self.refY += 1 - + refX += 1 + refY += 1 def generateWestStairWires(self) -> None: totalWires = self.groupWires * (abs(self.offset) - 1) + refX = self.refX + refY = self.refY for i in range(totalWires): - wireGeom = WireGeometry(f"{self.name} #{i}") - start = Location(self.tileWidth, self.refY) - nextToStart = Location(self.refX, self.refY) - nextToEnd = Location(self.refX, self.refY - self.groupWires) - end = Location(0, self.refY - self.groupWires) + wireGeom = WireGeometry(f"{self.name} #{i}") + start = Location(self.tileWidth, refY) + nextToStart = Location(refX, refY) + nextToEnd = Location(refX, refY - self.groupWires) + end = Location(0, refY - self.groupWires) wireGeom.addPathLoc(start) wireGeom.addPathLoc(nextToStart) @@ -201,10 +199,69 @@ def generateWestStairWires(self) -> None: wireGeom.addPathLoc(end) self.wireGeoms.append(wireGeom) - self.refX += 1 - self.refY -= 1 - + refX += 1 + refY -= 1 def saveToCSV(self, writer: csvWriter) -> None: for wireGeom in self.wireGeoms: wireGeom.saveToCSV(writer) + + +class WireConstraints: + """ + A simple datastruct for storing + information on where wires arrive + at the border of a tile. + + Attributes: + northPositions: List[int] + southPositions: List[int] + eastPositions: List[int] + westPositions: List[int] + + """ + northPositions: List[int] + southPositions: List[int] + eastPositions: List[int] + westPositions: List[int] + + def __init__(self): + self.northPositions = [] + self.southPositions = [] + self.eastPositions = [] + self.westPositions = [] + + def addConstraintsOf(self, stairWires: StairWires) -> None: + totalWires = stairWires.groupWires * (abs(stairWires.offset) - 1) + + if stairWires.direction == Direction.NORTH: + refX = stairWires.refX + + for i in range(totalWires): + self.northPositions.append(refX) + self.southPositions.append(refX - stairWires.groupWires) + refX -= 1 + + if stairWires.direction == Direction.SOUTH: + refX = stairWires.refX + + for i in range(totalWires): + self.northPositions.append(refX) + self.southPositions.append(refX + stairWires.groupWires) + refX += 1 + + if stairWires.direction == Direction.EAST: + refY = stairWires.refY + + for i in range(totalWires): + self.eastPositions.append(refY) + self.westPositions.append(refY + stairWires.groupWires) + refY += 1 + + if stairWires.direction == Direction.WEST: + refY = stairWires.refY + + for i in range(totalWires): + self.eastPositions.append(refY) + self.westPositions.append(refY - stairWires.groupWires) + refY -= 1