Source code for FABulous.fabric_cad.bit_gen

#!/usr/bin/env python

"""Bitstream generation utilities for FABulous FPGA fabrics.

This module provides functionality for generating bitstreams from FASM (FPGA Assembly)
files for FABulous FPGA fabrics. It handles the conversion of place-and-route results
into configuration bitstreams that can be loaded onto the FPGA fabric.

The module includes functions for parsing FASM files, processing configuration bits, and
generating the final bitstream output in various formats.
"""

import pickle
import re
import sys
from pathlib import Path

from loguru import logger

from FABulous.custom_exception import SpecMissMatch

try:
    from fasm import (
        fasm_tuple_to_string,
        parse_fasm_filename,
        parse_fasm_string,
        set_feature_to_str,
    )
except ImportError:
    logger.critical("Could not import fasm. Bitstream generation not supported.")


[docs] def bitstring_to_bytes(s: str) -> bytes: """Convert binary string to bytes. Parameters ---------- s : str Binary string (e.g., '10110101') Returns ------- bytes Byte representation of the binary string """ return int(s, 2).to_bytes((len(s) + 7) // 8, byteorder="big")
[docs] def genBitstream(fasmFile: str, specFile: str, bitstreamFile: str) -> None: """ Generate the bitstream from the FASM file using the bitstream specification. ---------- fasmFile : str Path to FASM file containing configuration features specFile : str Path to pickle file containing bitstream specification bitstreamFile : str Output path for generated bitstream file """ lGen = parse_fasm_filename(fasmFile) canonStr = fasm_tuple_to_string(lGen, True) canonList = list(parse_fasm_string(canonStr)) with Path(specFile).open("rb") as f: specDict = pickle.load(f) tileDict = {} tileDict_No_Mask = {} FrameBitsPerRow = specDict["ArchSpecs"]["FrameBitsPerRow"] MaxFramesPerCol = specDict["ArchSpecs"]["MaxFramesPerCol"] # Change this so it has the actual right dimensions, # initialised as an empty bitstream for tile in specDict["TileMap"]: tileDict[tile] = [0] * (MaxFramesPerCol * FrameBitsPerRow) tileDict_No_Mask[tile] = [0] * (MaxFramesPerCol * FrameBitsPerRow) # NOTE: SOME OF THE FOLLOWING METHODS HAVE BEEN CHANGED DUE TO A MODIFIED BITSTREAM # SPEC FORMAT # Please bear in mind that the tilespecs are now mapped by # tile loc and not by cell type for line in canonList: if "CLK" in set_feature_to_str(line.set_feature): continue if line.set_feature: tileVals = set_feature_to_str(line.set_feature).split(".") tileLoc = tileVals[0] featureName = ".".join((tileVals[1], tileVals[2])) if tileLoc not in specDict["TileMap"]: raise SpecMissMatch( f"Tile location {tileLoc} not found in the bitstream spec" ) # Set the necessary bits high tileType = specDict["TileMap"][tileLoc] if featureName in specDict["TileSpecs"][tileLoc]: if specDict["TileSpecs"][tileLoc][featureName]: for bitIndex in specDict["TileSpecs"][tileLoc][featureName]: tileDict[tileLoc][bitIndex] = int( specDict["TileSpecs"][tileLoc][featureName][bitIndex] ) for bitIndex_No_Mask in specDict["TileSpecs_No_Mask"][tileLoc][ featureName ]: tileDict_No_Mask[tileLoc][bitIndex_No_Mask] = int( specDict["TileSpecs_No_Mask"][tileLoc][featureName][ bitIndex_No_Mask ] ) else: raise SpecMissMatch( f"Tile type: {tileType}\n" "with location {tileLoc} and \n" f"Feature: {featureName}\n" "found in fasm file was not found in the bitstream spec" ) # Write output string and introduce mask coordsRE = re.compile(r"X(\d*)Y(\d*)") num_columns = 0 num_rows = 0 for tileKey in tileDict: coordsMatch = coordsRE.match(tileKey) num_columns = max(int(coordsMatch.group(1)) + 1, num_columns) num_rows = max(int(coordsMatch.group(2)) + 1, num_rows) outStr = "" bitStr = bytes.fromhex("00AAFF01000000010000000000000000FAB0FAB1") bit_array = [[b"" for x in range(20)] for y in range(num_columns)] verilog_str = "" vhdl_str = ( "library IEEE;\nuse IEEE.STD_LOGIC_1164.ALL;\n\npackage emulate_bitstream is\n" ) for tileKey in tileDict_No_Mask: if ( specDict["TileMap"][tileKey] == "NULL" or len(specDict["FrameMap"][specDict["TileMap"][tileKey]]) == 0 ): continue verilog_str += f"// {tileKey}, {specDict['TileMap'][tileKey]}\n" verilog_str += ( f"`define Tile_{tileKey}_Emulate_Bitstream " f"{MaxFramesPerCol * FrameBitsPerRow}'b" ) vhdl_str += f"--{tileKey}, {specDict['TileMap'][tileKey]}\n" vhdl_str += ( f"constant Tile_{tileKey}_Emulate_Bitstream : std_logic_vector(" f'{MaxFramesPerCol * FrameBitsPerRow}-1 downto 0) := "' ) for i in range((MaxFramesPerCol * FrameBitsPerRow) - 1, -1, -1): verilog_str += str(tileDict_No_Mask[tileKey][i]) vhdl_str += str(tileDict_No_Mask[tileKey][i]) verilog_str += "\n" vhdl_str += '";\n' vhdl_str += "end package emulate_bitstream;" # Top/bottom rows have no bitstream content (hardcoded throughout fabulous) # reversed row order for y in range(num_rows - 2, 0, -1): for x in range(num_columns): tileKey = f"X{x}Y{y}" curStr = ",".join((tileKey, specDict["TileMap"][tileKey], str(x), str(y))) curStr += "\n" for frameIndex in range(MaxFramesPerCol): if specDict["TileMap"][tileKey] == "NULL": frame_bit_row = "0" * FrameBitsPerRow else: frame_bit_row = ( "".join( map( str, ( tileDict[tileKey][ FrameBitsPerRow * frameIndex : ( FrameBitsPerRow * frameIndex ) + FrameBitsPerRow ] ), ) ) )[::-1] curStr += ",".join( ( f"frame{frameIndex}", str(frameIndex), str(FrameBitsPerRow), frame_bit_row, ) ) curStr += "\n" bit_hex = bitstring_to_bytes(frame_bit_row) bit_array[x][frameIndex] += bit_hex outStr += curStr + "\n" for i in range(num_columns): for j in range(20): bin_temp = f"{i:05b}"[::-1] frame_select = ["0" for k in range(32)] for k in range(-5, 0, 1): frame_select[k] = bin_temp[k] frame_select[j] = "1" frame_select_temp = ("".join(frame_select))[::-1] bitStr += bitstring_to_bytes(frame_select_temp) bitStr += bit_array[i][j] # Add desync frame # 20th bit is desync flag bitStr += bytes.fromhex("00100000") # Note - format in output file is line by line: # Tile Loc, Tile Type, X, Y, bits...... \n # Each line is one tile # Write out bitstream CSV representation with Path(bitstreamFile.replace("bin", "csv")).open("w+") as f: f.write(outStr) # Write out HDL representations with Path(bitstreamFile.replace("bin", "vh")).open("w+") as f: f.write(verilog_str) with Path(bitstreamFile.replace("bin", "vhd")).open("w+") as f: f.write(vhdl_str) # Write out binary representation with Path(bitstreamFile).open("bw+") as f: f.write(bitStr)
##################################################################################### # Main #####################################################################################
[docs] def bit_gen() -> None: """Command-line entry point for bitstream generation. Parses command-line arguments and calls genBitstream to create bitstream files from FASM and specification inputs. """ # Strip arguments caseProcessedArguments = list(map(lambda x: x.strip(), sys.argv)) processedArguments = list(map(lambda x: x.lower(), caseProcessedArguments)) flagRE = re.compile(r"-\S*") if "-genBitstream".lower() in str(sys.argv).lower(): argIndex = processedArguments.index("-genBitstream".lower()) if len(processedArguments) <= argIndex + 3: logger.error( "genBitstream expects three file names - the fasm file, the spec file " "and the output file" ) raise ValueError if ( flagRE.match(caseProcessedArguments[argIndex + 1]) or flagRE.match(caseProcessedArguments[argIndex + 2]) or flagRE.match(caseProcessedArguments[argIndex + 3]) ): logger.error( "genBitstream expects three file names, but" " found a flag in the arguments: " f"{caseProcessedArguments[argIndex + 1]}, " f"{caseProcessedArguments[argIndex + 2]}, " f"{caseProcessedArguments[argIndex + 3]}" ) raise ValueError FasmFileName = caseProcessedArguments[argIndex + 1] SpecFileName = caseProcessedArguments[argIndex + 2] OutFileName = caseProcessedArguments[argIndex + 3] genBitstream(FasmFileName, SpecFileName, OutFileName) if ("-help".lower() in str(sys.argv).lower()) or ("-h" in str(sys.argv).lower()): logger.info("Help:") logger.info("Options/Switches") logger.info( " -genBitstream foo.fasm spec.txt bitstream.txt - generates a bitstream - " "the first file is the fasm file, the second is the bitstream spec and " "the third is the fasm file to write to" )
if __name__ == "__main__": bit_gen()