Source code for FABulous.fabric_cad.gen_design_top_wrapper

"""User design top wrapper generation module.

This module provides functionality to generate top-level wrappers for user designs that
interface with the FPGA fabric. The wrapper handles signal mapping between user logic
and fabric I/O ports.
"""

from pathlib import Path

from loguru import logger

from FABulous.custom_exception import InvalidFileType
from FABulous.fabric_definition.Fabric import Fabric
from FABulous.fabric_generator.parser.parse_hdl import parseBelFile


[docs] def generateUserDesignTopWrapper( fabric: Fabric, user_design_path: Path, output: Path ) -> None: """Generate a top wrapper for the user design. This function creates a Verilog top-level wrapper that instantiates the user's design and connects its ports to the fabric's external I/O BELs. It automatically discovers external ports from the fabric definition and generates the necessary wiring and BEL instantiations. Params ------ fabric: Fabric Fabric object user_design_path: Path Path to the user design file output: Path Path to output the user design top wrapper Raises ------ ValueError Output file is not a Verilog file or user design path is not a file """ top_wrapper: list[str] = [""] if output.suffix != ".v": raise InvalidFileType(f"{output} is not a Verilog file") if not user_design_path.is_file(): raise FileNotFoundError(f"{user_design_path} is not a file or does not exist") if output.exists(): logger.warning(f"{output} already exists, overwriting") output.unlink() else: logger.info(f"Creating {output}") output.touch() user_design = parseBelFile(user_design_path, "") top_wrapper.append("// Generated by FABulous") top_wrapper.append(f"// Top wrapper for user design {user_design_path}\n") top_wrapper.append("module top_wrapper;\n") top_wrapper.append("wire clk;") top_wrapper.append("(* keep *) Global_Clock clk_i (.CLK(clk));\n") bel_count: dict[str, int] = {} # count how often a bel is instantiated bel_inputs: dict[str, list[str]] = {} bel_outputs: dict[str, list[str]] = {} # generate component instantioations for x in range(fabric.numberOfColumns): # we walk backwards through the Y list, since there is something mixed up with # the coordinate system for y in range(fabric.numberOfRows - 1, -1, -1): bels = fabric.getBelsByTileXY(x, y) if not bels: continue for i, bel in enumerate( reversed(bels) ): # we walk backwards trough the bel list belstr = "" # we only add bels with external ports to the top wrapper. if not bel.externalInput and not bel.externalOutput: logger.info( f"Skipping bel {bel.name} in tile X{x}Y{y} since it has no " f"external ports" ) continue if len(bel.inputs + bel.outputs) == 0: logger.info( f"{bel.name} in tile X{x}Y{y} has no internal ports, " "only external ports, we just add a dummy to the user design " "top wrapper!" ) belstr += "//" if bel.name not in bel_count: bel_count[bel.name] = 0 bel_inputs[bel.name] = [ port.removeprefix(bel.prefix) for port in bel.inputs ] bel_outputs[bel.name] = [ port.removeprefix(bel.prefix) for port in bel.outputs ] else: # count number of times a BEL type is used bel_count[bel.name] += 1 # This is done similar in the npnr model gen, to get the bel prefix # So we assume to get the same Bel prefix here. # convert number of bel i to character A,B,C ... # But we need to do this backwards, # starting with the highest letter for a tile prefix = chr(ord("A") + len(bels) - 1 - i) if bel.name in [ "InPass4_frame_config", "OutPass4_frame_config", "InPass4_frame_config_mux", "OutPass4_frame_config_mux", ]: # This is a special case for the RAM_IO bels, since # for some unknown reasons, the prefix used in the nexpnr backend # is not based on the number of bels, # it is based on the actual bel prefix # which is defined in the tile csv. # https://github.com/YosysHQ/nextpnr/blob/master/generic/viaduct/fabulous/fabulous.cc#L355 prefix = bel.prefix.removesuffix("_") belstr += ( f'(* keep, BEL="X{x}Y{y}.{prefix}" *) {bel.name} ' f"bel_X{x}Y{y}_{prefix} (" ) first = True for port in bel.inputs + bel.outputs: port_name = port.removeprefix(bel.prefix) if first: first = False else: belstr += ", " belstr += ( f".{port_name}({bel.name}_{port_name}[{bel_count[bel.name]}])" ) belstr += ");" top_wrapper.append(belstr) top_wrapper.append("\n") for belname in bel_count: count = bel_count[belname] if bel_inputs[belname]: top_wrapper.append(f"// bel {belname} input wires:") for port in bel_inputs[belname]: top_wrapper.append(f"wire [{count}:0]{belname}_{port};") if bel_outputs[belname]: top_wrapper.append(f"// bel {belname} output wires:") for port in bel_outputs[belname]: top_wrapper.append(f"wire [{count}:0]{belname}_{port};") top_wrapper.append("\n") top_wrapper.append("// instantiate user_design") if user_design.language == "vhdl": user_design_inst = f"{user_design.name} user_design_i (" else: user_design_inst = f"{user_design.module_name} user_design_i (" if ( user_design.name == "sequential_16bit_en" and "IO_1_bidirectional_frame_config_pass" in bel_count ): # hardcoded for now logger.info( "Using default design, " "with sequential_16bit_en counter and IO_1_bidirectional_frame_config_pass" ) user_design_inst += ".clk(clk), " user_design_inst += ".io_in(IO_1_bidirectional_frame_config_pass_O), " user_design_inst += ".io_out(IO_1_bidirectional_frame_config_pass_I), " user_design_inst += ".io_oeb(IO_1_bidirectional_frame_config_pass_T));" else: # if its not our default design, we are just instantiate it, # and the user needs to take connect the ports logger.warning( f"Custom design detected, " f"please connect the ports manually in user design top wrapper " f"{user_design_path}!" ) if user_design.language == "vhdl": logger.warning( f"VHDL design detected, " f"please check the generated top wrapper {top_wrapper} and check the " "user design module name, as well as the ports!" ) first = True for port in user_design.inputs + user_design.outputs: if first: first = False else: user_design_inst += ", " if port in ["clk", "CLK"]: user_design_inst += f".{port}(clk) " else: user_design_inst += f".{port}() " else: # verilog first = True for port in user_design.ports_vectors["internal"]: if first: first = False else: user_design_inst += ", " if port in ["clk", "CLK"]: user_design_inst += f".{port}(clk)" else: user_design_inst += f".{port}()" user_design_inst += ");" top_wrapper.append(user_design_inst) top_wrapper.append("\n") top_wrapper.append("endmodule //top_wrapper\n") # write file _ = output.write_text("\n".join(top_wrapper)) logger.info( f"Generated user design top wrapper {output} with {len(top_wrapper)} lines" )