Source code for FABulous.fabric_generator.parser.parse_configmem
"""Configuration memory parser for FABulous FPGA tiles.
This module parses configuration memory CSV files that define how configuration
bits are mapped to memory frames in frame-based configuration systems. It validates
the configuration data structure and creates ConfigMem objects for code generation.
The parser handles:
- Frame-based configuration bit mapping
- Bit mask validation for frame utilization
- Configuration bit range parsing (single bits, ranges, lists)
- Error checking for bit allocation conflicts
- Memory frame structure validation
"""
import csv
import re
from pathlib import Path
from FABulous.fabric_definition.ConfigMem import ConfigMem
[docs]
def parseConfigMem(
fileName: Path,
maxFramePerCol: int,
frameBitPerRow: int,
globalConfigBits: int,
) -> list[ConfigMem]:
"""Parse the config memory CSV file into a list of ConfigMem objects.
Parameters
----------
fileName : str
Directory of the config memory CSV file
maxFramePerCol : int
Maximum number of frames per colum
frameBitPerRow : int
Number of bits per row
globalConfigBits : int
Number of global config bits for the config memory
Raises
------
ValueError
- Invalid amount of frame entries in the config memory CSV file
- Too many values in bit mask
- Length of bit mask does not match the number of frame bits per row
- Bit mask does not have enough values matching the number of the
given config bits
- Repeated config bit entry in ':' separated format in config bit range
- Repeated config bit entry in list format in config bit range
- Invalid range entry in config bit range
Returns
-------
list[ConfigMem]
List of ConfigMem objects parsed from the config memory CSV file.
"""
with fileName.open() as f:
mappingFile = list(csv.DictReader(f))
# remove the pretty print from used_bits_mask
for i, _ in enumerate(mappingFile):
mappingFile[i]["used_bits_mask"] = mappingFile[i]["used_bits_mask"].replace(
"_", ""
)
# we should have as many lines as we have frames (=framePerCol)
if len(mappingFile) != maxFramePerCol:
raise ValueError(
f"The bitstream mapping file has only {len(mappingFile)} entries "
f"but MaxFramesPerCol is {maxFramePerCol}."
)
# we also check used_bits_mask (is a vector that is as long as a frame and
# contains a '1' for a bit used and a '0' if not used (padded)
usedBitsCounter = 0
for entry in mappingFile:
if entry["used_bits_mask"].count("1") > frameBitPerRow:
raise ValueError(
f"bitstream mapping file {fileName} has to many 1-elements in "
f"bitmask for frame : {entry['frame_name']}"
)
if len(entry["used_bits_mask"]) != frameBitPerRow:
raise ValueError(
f"bitstream mapping file {fileName} has has a too long or short "
f"bitmask for frame : {entry['frame_name']}"
)
usedBitsCounter += entry["used_bits_mask"].count("1")
if usedBitsCounter != globalConfigBits:
raise ValueError(
f"bitstream mapping file {fileName} has a bitmask mismatch; "
f"bitmask has in total {usedBitsCounter} 1-values for "
f"{globalConfigBits} bits."
)
allConfigBitsOrder = []
configMemEntry = []
for entry in mappingFile:
configBitsOrder = []
entry["ConfigBits_ranges"] = (
entry["ConfigBits_ranges"].replace(" ", "").replace("\t", "")
)
if ":" in entry["ConfigBits_ranges"]:
left, right = re.split(":", entry["ConfigBits_ranges"])
# check the order of the number, if right is smaller than left,
# then we swap them
left, right = int(left), int(right)
if right < left:
left, right = right, left
numList = list(reversed(range(left, right + 1)))
else:
numList = list(range(left, right + 1))
for i in numList:
if i in allConfigBitsOrder:
raise ValueError(
f"Configuration bit index {i} already allocated in "
f"{fileName}, {entry['frame_name']}."
)
configBitsOrder.append(i)
elif ";" in entry["ConfigBits_ranges"]:
for item in entry["ConfigBits_ranges"].split(";"):
if int(item) in allConfigBitsOrder:
raise ValueError(
f"Configuration bit index {item} already allocated in "
f"{fileName}, {entry['frame_name']}."
)
configBitsOrder.append(int(item))
elif entry["ConfigBits_ranges"].isdigit():
v = int(entry["ConfigBits_ranges"])
if v in allConfigBitsOrder:
raise ValueError(
f"Configuration bit index {v} already allocated in "
f"{fileName}, {entry['frame_name']}."
)
configBitsOrder.append(v)
elif "NULL" in entry["ConfigBits_ranges"]:
continue
else:
raise ValueError(
f"Range {entry['ConfigBits_ranges']} is not a valid format. "
"It should be in the form [int]:[int] or [int]. "
"If there are multiple ranges it should be separated by ';'."
)
if len(configBitsOrder) != entry["used_bits_mask"].count("1"):
raise ValueError(
f"bitstream mapping file {fileName} has a mismatch between the "
f"number of bits used in the frame "
f"({entry['used_bits_mask'].count('1')}) and the number of "
f"config bits in the range({len(configBitsOrder)}) "
f"for frame {entry['frame_name']}."
)
if any([i < 0 for i in configBitsOrder]):
raise ValueError(
f"Configuration bit index {configBitsOrder} in {fileName},"
f"{entry['frame_name']} is negative."
)
allConfigBitsOrder += configBitsOrder
if entry["used_bits_mask"].count("1") > 0:
configMemEntry.append(
ConfigMem(
frameName=entry["frame_name"],
frameIndex=int(entry["frame_index"]),
bitsUsedInFrame=entry["used_bits_mask"].count("1"),
usedBitMask=entry["used_bits_mask"],
configBitRanges=configBitsOrder,
)
)
return configMemEntry