"""Tile class definition for FPGA fabric representation."""
from dataclasses import dataclass, field
from decimal import Decimal
from pathlib import Path
from typing import TYPE_CHECKING
from fabulous.fabric_definition.bel import Bel
from fabulous.fabric_definition.define import IO, Direction, PinSortMode, Side
from fabulous.fabric_definition.gen_io import Gen_IO
from fabulous.fabric_definition.port import Port
from fabulous.fabric_definition.wire import Wire
if TYPE_CHECKING:
from fabulous.fabric_generator.gds_generator.gen_io_pin_config_yaml import (
PinOrderConfig,
)
@dataclass
[docs]
class Tile:
"""Store information about a tile.
Parameters
----------
name : str
The name of the tile
ports : list[Port]
List of ports for the tile
bels : list[Bel]
List of Basic Elements of Logic (BELs) in the tile
tileDir : Path
Directory path for the tile
matrixDir : Path
Directory path for the tile matrix
gen_ios : list[Gen_IO]
List of general I/O components
userCLK : bool
True if the tile uses a clk signal
configBit : int, optional
Number of configuration bits for the switch matrix. Default is 0.
pinOrderConfig : dict[Side, PinOrderConfig] | None, optional
Configuration for pin ordering on each side of the tile. If None, defaults to
BUS_MAJOR sorting on all sides.
Attributes
----------
name : str
The name of the tile
portsInfo : list[Port]
The list of ports of the tile
bels: list[Bel]
The list of BELs of the tile
matrixDir : Path
The directory of the tile matrix
matrixConfigBits : int
The number of config bits the tile switch matrix has
gen_ios : list[Gen_IO]
The list of GEN_IOs of the tile
withUserCLK : bool
Whether the tile has a userCLK port. Default is False.
wireList : list[Wire]
The list of wires of the tile
tileDir : Path
The path to the tile folder
partOfSuperTile : bool, optional
Whether the tile is part of a super tile. Default is False.
pinOrderConfig : dict, optional
Configuration for pin ordering on each side of the tile.
"""
name: str
portsInfo: list[Port]
bels: list[Bel]
matrixDir: Path
matrixConfigBits: int
gen_ios: list[Gen_IO]
withUserCLK: bool = False
wireList: list[Wire] = field(default_factory=list)
tileDir: Path = Path()
partOfSuperTile: bool = False
pinOrderConfig: dict = field(default_factory=dict)
def __init__(
self,
name: str,
ports: list[Port],
bels: list[Bel],
tileDir: Path,
matrixDir: Path,
gen_ios: list[Gen_IO],
userCLK: bool,
configBit: int = 0,
pinOrderConfig: dict[Side, "PinOrderConfig"] | None = None,
) -> None:
self.name = name
self.portsInfo = ports
self.bels = bels
self.gen_ios = gen_ios
self.matrixDir = matrixDir
self.withUserCLK = userCLK
self.matrixConfigBits = configBit
self.wireList = []
self.tileDir = tileDir
if pinOrderConfig is None:
from fabulous.fabric_generator.gds_generator.gen_io_pin_config_yaml import (
PinOrderConfig,
)
self.pinOrderConfig = {
Side.NORTH: PinOrderConfig(sort_mode=PinSortMode.BUS_MAJOR),
Side.EAST: PinOrderConfig(sort_mode=PinSortMode.BUS_MAJOR),
Side.SOUTH: PinOrderConfig(sort_mode=PinSortMode.BUS_MAJOR),
Side.WEST: PinOrderConfig(sort_mode=PinSortMode.BUS_MAJOR),
}
else:
self.pinOrderConfig = pinOrderConfig
def __eq__(self, __o: object, /) -> bool:
"""Check equality between tiles based on their name.
Parameters
----------
__o : object
The object to compare with.
Returns
-------
bool
True if both tiles have the same name, False otherwise.
"""
if __o is None or not isinstance(__o, Tile):
return False
return self.name == __o.name
[docs]
def getWestSidePorts(self) -> list[Port]:
"""Get all ports physically located on the west side of the tile.
Returns
-------
list[Port]
List of ports on the west side, excluding NULL ports.
"""
return [
p for p in self.portsInfo if p.sideOfTile == Side.WEST and p.name != "NULL"
]
[docs]
def getEastSidePorts(self) -> list[Port]:
"""Get all ports physically located on the east side of the tile.
Returns
-------
list[Port]
List of ports on the east side, excluding NULL ports.
"""
return [
p for p in self.portsInfo if p.sideOfTile == Side.EAST and p.name != "NULL"
]
[docs]
def getNorthSidePorts(self) -> list[Port]:
"""Get all ports physically located on the north side of the tile.
Returns
-------
list[Port]
List of ports on the north side, excluding NULL ports.
"""
return [
p for p in self.portsInfo if p.sideOfTile == Side.NORTH and p.name != "NULL"
]
[docs]
def getSouthSidePorts(self) -> list[Port]:
"""Get all ports physically located on the south side of the tile.
Returns
-------
list[Port]
List of ports on the south side, excluding NULL ports.
"""
return [
p for p in self.portsInfo if p.sideOfTile == Side.SOUTH and p.name != "NULL"
]
[docs]
def getNorthPorts(self, io: IO) -> list[Port]:
"""Get all ports with north wire direction filtered by I/O type.
Parameters
----------
io : IO
The I/O direction to filter by (INPUT or OUTPUT).
Returns
-------
list[Port]
List of north-direction ports with specified I/O type, excluding NULL ports.
"""
return [
p
for p in self.portsInfo
if p.wireDirection == Direction.NORTH and p.name != "NULL" and p.inOut == io
]
[docs]
def getSouthPorts(self, io: IO) -> list[Port]:
"""Get all ports with south wire direction filtered by I/O type.
Parameters
----------
io : IO
The I/O direction to filter by (INPUT or OUTPUT).
Returns
-------
list[Port]
List of south-direction ports with specified I/O type, excluding NULL ports.
"""
return [
p
for p in self.portsInfo
if p.wireDirection == Direction.SOUTH and p.name != "NULL" and p.inOut == io
]
[docs]
def getEastPorts(self, io: IO) -> list[Port]:
"""Get all ports with east wire direction filtered by I/O type.
Parameters
----------
io : IO
The I/O direction to filter by (INPUT or OUTPUT).
Returns
-------
list[Port]
List of east-direction ports with specified I/O type, excluding NULL ports.
"""
return [
p
for p in self.portsInfo
if p.wireDirection == Direction.EAST and p.name != "NULL" and p.inOut == io
]
[docs]
def getWestPorts(self, io: IO) -> list[Port]:
"""Get all ports with west wire direction filtered by I/O type.
Parameters
----------
io : IO
The I/O direction to filter by (INPUT or OUTPUT).
Returns
-------
list[Port]
List of west-direction ports with specified I/O type, excluding NULL ports.
"""
return [
p
for p in self.portsInfo
if p.wireDirection == Direction.WEST and p.name != "NULL" and p.inOut == io
]
[docs]
def getTileOutputNames(self) -> list[str]:
"""Get all output port source names for the tile.
Returns
-------
list[str]
List of source names for output ports, excluding NULL and
JUMP direction ports.
"""
return [
p.sourceName
for p in self.portsInfo
if p.sourceName != "NULL"
and p.wireDirection != Direction.JUMP
and p.inOut == IO.OUTPUT
]
@property
[docs]
def globalConfigBits(self) -> int:
"""Get the total number of global configuration bits.
Calculates the sum of switch matrix configuration bits
and all BEL configuration bits.
Returns
-------
int
Total number of global configuration bits for the tile.
"""
ret = self.matrixConfigBits
for b in self.bels:
ret += b.configBit
return ret
[docs]
def get_port_count(self, side: Side) -> int:
"""Count total number of expanded physical pins on a given side of the tile.
Parameters
----------
side : Side
The side of the tile to count ports for.
Returns
-------
int
Total number of expanded ports on the given side.
"""
total = 0
for p in self.portsInfo:
if p.sideOfTile != side or p.name == "NULL":
continue
inputs, outputs = p.expandPortInfo("all")
if p.name == p.sourceName:
total += len(inputs)
elif p.name == p.destinationName:
total += len(outputs)
return total
[docs]
def get_min_die_area(
self,
x_pitch: Decimal,
y_pitch: Decimal,
x_pin_thickness_mult: Decimal = Decimal(1),
y_pin_thickness_mult: Decimal = Decimal(1),
frame_data_width: int = 32,
frame_strobe_width: int = 20,
edge_offset: int = 2,
) -> tuple[Decimal, Decimal]:
"""Calculate minimum tile dimensions based on IO pin track requirements.
The IO pin placer distributes pins across available tracks on each
tile edge. Each pin occupies ``thickness_mult`` consecutive tracks,
and ``edge_offset`` tracks are reserved at the start of the tile
(see ``tile_io_place.allocate_tracks``).
The minimum number of tracks on a side is therefore::
required_tracks = pin_count * thickness_mult + edge_offset
And the minimum physical dimension is::
min_dim = required_tracks * pitch
Parameters
----------
x_pitch : Decimal
Vertical-layer track pitch (for north/south pins).
y_pitch : Decimal
Horizontal-layer track pitch (for east/west pins).
x_pin_thickness_mult : Decimal
Number of tracks each north/south pin spans, by default 1.
y_pin_thickness_mult : Decimal
Number of tracks each east/west pin spans, by default 1.
frame_data_width : int, optional
Frame data width, by default 32.
frame_strobe_width : int, optional
Frame strobe width, by default 20.
edge_offset : int, optional
Reserved tracks at tile edge, by default 2.
Returns
-------
tuple[Decimal, Decimal]
(min_width, min_height)
"""
north_ports = self.get_port_count(Side.NORTH)
south_ports = self.get_port_count(Side.SOUTH)
west_ports = self.get_port_count(Side.WEST)
east_ports = self.get_port_count(Side.EAST)
x_io_count = Decimal(max(north_ports, south_ports) + frame_strobe_width)
min_width_io = (x_io_count * x_pin_thickness_mult + edge_offset) * x_pitch
y_io_count = Decimal(max(west_ports, east_ports) + frame_data_width)
min_height_io = (y_io_count * y_pin_thickness_mult + edge_offset) * y_pitch
return min_width_io, min_height_io