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"
)