Source code for FABulous.fabric_cad.gen_bitstream_spec
"""Bitstream specification generation module.
This module provides functionality to generate bitstream specifications from FPGA fabric
definitions. The specification defines how configuration bits map to physical frame
locations and is used during bitstream generation.
"""
import string
from typing import TYPE_CHECKING
from loguru import logger
from FABulous.fabric_definition.Fabric import Fabric
from FABulous.fabric_generator.parser.parse_configmem import parseConfigMem
from FABulous.fabric_generator.parser.parse_switchmatrix import parseMatrix
from FABulous.FABulous_settings import get_context
if TYPE_CHECKING:
from FABulous.fabric_definition.ConfigMem import ConfigMem
[docs]
def generateBitstreamSpec(fabric: Fabric) -> dict[str, dict]:
"""Generate the fabric's bitstream specification.
This is needed to tell where each FASM configuration is mapped to the physical
bitstream
The result file will be further parsed by `bit_gen.py`.
Returns
-------
dict [str, dict]
The bits stream specification of the fabric.
"""
specData = {
"TileMap": {},
"TileSpecs": {},
"TileSpecs_No_Mask": {},
"FrameMap": {},
"FrameMapEncode": {},
"ArchSpecs": {
"MaxFramesPerCol": fabric.maxFramesPerCol,
"FrameBitsPerRow": fabric.frameBitsPerRow,
},
}
tileMap = {}
for y, row in enumerate(fabric.tile):
for x, tile in enumerate(row):
if tile is not None:
tileMap[f"X{x}Y{y}"] = tile.name
else:
tileMap[f"X{x}Y{y}"] = "NULL"
specData["TileMap"] = tileMap
configMemList: list[ConfigMem] = []
for y, row in enumerate(fabric.tile):
for x, tile in enumerate(row):
if tile is None:
continue
if "fabric.csv" in str(tile.tileDir):
# backward compatibility for old project structure
# We need to take the matrixDir from the tile, since there
# is the actual path to the tile defined in the fabric.csv
if tile.matrixDir.is_file():
configMemPath = tile.matrixDir.parent / f"{tile.name}_ConfigMem.csv"
elif tile.matrixDir.is_dir():
configMemPath = tile.matrixDir / f"{tile.name}_ConfigMem.csv"
else:
configMemPath = (
get_context().proj_dir
/ "Tile"
/ tile.name
/ f"{tile.name}_ConfigMem.csv"
)
logger.warning(
f"MatrixDir for {tile.name} is not a valid file or directory. "
f"Assuming default path: {configMemPath}"
)
else:
configMemPath = tile.tileDir.parent.joinpath(
f"{tile.name}_ConfigMem.csv"
)
logger.info(f"ConfigMemPath: {configMemPath}")
if configMemPath.exists() and configMemPath.is_file():
configMemList = parseConfigMem(
configMemPath,
fabric.maxFramesPerCol,
fabric.frameBitsPerRow,
tile.globalConfigBits,
)
elif tile.globalConfigBits > 0:
logger.critical(
f"No ConfigMem csv file found for {tile.name} which "
"have config bits"
)
configMemList = []
else:
logger.info(f"No config memory for {tile.name}.")
configMemList = []
encodeDict = [-1] * (fabric.maxFramesPerCol * fabric.frameBitsPerRow)
maskDic = {}
for cfm in configMemList:
maskDic[cfm.frameIndex] = cfm.usedBitMask
# matching the value in the configBitRanges with the reversedBitMask
# bit 0 in bit mask is the first value in the configBitRanges
for i, char in enumerate(cfm.usedBitMask):
if char == "1":
encodeDict[cfm.configBitRanges.pop(0)] = (
fabric.frameBitsPerRow - 1 - i
) + fabric.frameBitsPerRow * cfm.frameIndex
# filling the maskDic with the unused frames
for i in range(fabric.maxFramesPerCol - len(configMemList)):
maskDic[len(configMemList) + i] = "0" * fabric.frameBitsPerRow
specData["FrameMap"][tile.name] = maskDic
if tile.globalConfigBits == 0:
logger.info(f"No config memory for X{x}Y{y}_{tile.name}.")
specData["FrameMap"][tile.name] = {}
specData["FrameMapEncode"][tile.name] = {}
curBitOffset = 0
curTileMap = {}
curTileMapNoMask = {}
for i, bel in enumerate(tile.bels):
for featureKey, keyDict in bel.belFeatureMap.items():
for entry in keyDict:
if isinstance(entry, int):
for v in keyDict[entry]:
curTileMap[
f"{string.ascii_uppercase[i]}.{featureKey}"
] = {encodeDict[curBitOffset + v]: keyDict[entry][v]}
curTileMapNoMask[
f"{string.ascii_uppercase[i]}.{featureKey}"
] = {encodeDict[curBitOffset + v]: keyDict[entry][v]}
curBitOffset += len(keyDict[entry])
# All the generation will be working on the tile level with the tileDic
# This is added to propagate the updated switch matrix to
# each of the tile in the fabric
if tile.matrixDir.suffix == ".list":
tile.matrixDir = tile.matrixDir.with_suffix(".csv")
result = parseMatrix(tile.matrixDir, tile.name)
for source, sinkList in result.items():
controlWidth = 0
for i, sink in enumerate(reversed(sinkList)):
controlWidth = (len(sinkList) - 1).bit_length()
controlValue = f"{len(sinkList) - 1 - i:0{controlWidth}b}"
pip = f"{sink}.{source}"
if len(sinkList) < 2:
curTileMap[pip] = {}
curTileMapNoMask[pip] = {}
continue
for c, curChar in enumerate(controlValue[::-1]):
if pip not in curTileMap:
curTileMap[pip] = {}
curTileMapNoMask[pip] = {}
curTileMap[pip][encodeDict[curBitOffset + c]] = curChar
curTileMapNoMask[pip][encodeDict[curBitOffset + c]] = curChar
curBitOffset += controlWidth
# And now we add empty config bit mappings for immutable connections
# (i.e. wires), as nextpnr sees these the same as normal pips
for wire in tile.wireList:
curTileMap[f"{wire.source}.{wire.destination}"] = {}
curTileMapNoMask[f"{wire.source}.{wire.destination}"] = {}
specData["TileSpecs"][f"X{x}Y{y}"] = curTileMap
specData["TileSpecs_No_Mask"][f"X{x}Y{y}"] = curTileMapNoMask
return specData