# Copyright edalize contributors
# Licensed under the 2-Clause BSD License, see LICENSE for details.
# SPDX-License-Identifier: BSD-2-Clause
import os
import logging
from edalize.edatool import Edatool
logger = logging.getLogger(__name__)
"""
OpenFPGA Flow Backend.
A core (usually the system core) can add the following files:
* Benchmark RTL sources (Yosys supports only Verilog file type) and module name
* The target FPGA architecture name, made with OpenFPGA fabric flow (SOFA,...)
* Source the required environment variables: OPENFPGA_PATH, SOFA_PATH
* Optional parameters: task options ('--debug', ...)
"""
SOFA_TASK_DIRS = {
"sofa-chd": "FPGA1212_SOFA_CHD_PNR/FPGA1212_SOFA_CHD_task",
"sofa-hd": "FPGA1212_SOFA_HD_PNR/FPGA1212_SOFA_HD_task",
"sofa-plus-hd": "FPGA1212_SOFA_PLUS_HD_PNR/FPGA1212_SOFA_PLUS_HD_task",
"sofa-qlhd": "FPGA1212_QLSOFA_HD_PNR/FPGA1212_QLSOFA_HD_task",
}
[docs]
class Openfpga(Edatool):
argtypes = ["plusarg", "vlogdefine", "vlogparam"]
[docs]
@classmethod
def get_doc(cls, api_ver):
if api_ver == 0:
return {
"description": "The OpenFPGA backend executes Yosys synthesis tool and VPR place and route. It can target multiple different open-source FPGAs (supported: sofa-chd, sofa-hd, sofa-qlhd, sofa-plus-hd)",
"members": [
{
"name": "arch",
"type": "String",
"desc": "Target architecture. Legal values are *sofa* and *sofa-plus*.",
},
],
"lists": [
{
"name": "task_options",
"type": "String",
"desc": "Additional options for OpenFPGA task flow execution.",
},
],
}
def __init__(self, edam=None, work_root=None, eda_api=None, verbose=False):
"""
This calls the parent constructor, but also identifies whether the
current system has correctly set the following environment variables:
- ``OPENFPGA_PATH``: directory of the OpenFPGA framework, available here: https://github.com/lnis-uofu/OpenFPGA
- ``SOFA_PATH``: directory of the SOFA eFPGA IPs, available here: https://github.com/lnis-uofu/SOFA
"""
super(Openfpga, self).__init__(edam, work_root, verbose)
# Check environment variable setup
if os.environ.get("OPENFPGA_PATH") is None:
raise RuntimeError(
"""\
Environment variable 'OPENFPGA_PATH' was not found!
Download, build and source the framework: https://github.com/lnis-uofu/OpenFPGA"""
)
if os.environ.get("SOFA_PATH") is None:
raise RuntimeError(
"""\
Environment variable 'SOFA_PATH' was not found!
Download and source the project: https://github.com/lnis-uofu/SOFA"""
)
self.openfpga_path = os.environ["OPENFPGA_PATH"]
self.openfpga_flow = f"{self.openfpga_path}/openfpga_flow"
self.sofa_path = os.environ["SOFA_PATH"]
def _write_testbench(self):
"""
As required by the OpenFPGA configuration format specifications, the
benchmark variable need to be a Verilog file type, a made up of
multiple files.
"""
(src_files, inc_dirs) = self._get_fileset_files(force_slash=True)
# Create the benchmark variable, as a list of files using absolute path
tb_files = []
for f in src_files:
# check the correct file type
if not f.file_type.startswith("verilogSource"):
logger.warning(f"File type not supported for '{f.name}'")
continue
# find the absolute path
if os.path.isfile(f.name):
fname = f.name
elif os.path.isfile(f"{self.work_root}/{f.name}"):
fname = f.name
else:
logger.error(f"Can't found file '{f.name}'")
continue
tb_files.append(fname)
if len(tb_files) == 0:
logger.error("Missing testbench source file(s)!")
self.testbench_file = ",".join(tb_files)
[docs]
def configure_main(self):
"""
Configuration is the first phase of the build.
This writes an OpenFPGA task file for SOFA/SOFA+ architectures, which
will generate the according OpenFPGA flow. It first collects all
verilog sources, top_module and then writes them into the task file.
Note: OpenFPGA flow may uses Yosys/VPR backend for Synthesis and P&R,
respectively.
"""
# Create a single testbench file
self._write_testbench()
# Set the target architecture and its dependencies
arch = self.tool_options.get("arch")
if arch is None:
raise RuntimeError("Missing required option 'arch'")
if not arch in SOFA_TASK_DIRS:
raise RuntimeError(f"Unsupported FPGA architecture: 'arch={arch}'")
# Workspace variables
sofa_dir = f"{self.sofa_path}/{SOFA_TASK_DIRS[arch]}"
flow_dir = self.openfpga_flow
template_vars = {
"power_tech_file": f"{flow_dir}/tech/PTM_45nm/45nm.xml",
"openfpga_sim_setting_file": f"{flow_dir}/openfpga_simulation_settings/auto_sim_openfpga.xml",
"arch_variable_file": f"{sofa_dir}/design_variables.yml",
"openfpga_shell_template": f"{sofa_dir}/generate_testbench.openfpga",
"openfpga_arch_file": f"{sofa_dir}/arch/openfpga_arch.xml",
"external_fabric_key_file": f"{sofa_dir}/arch/fabric_key.xml",
"vpr_arch_file": f"{sofa_dir}/arch/vpr_arch.xml",
"tb_verilog_file": self.testbench_file,
"tb_top_entity": self.toplevel,
}
# Specific features by architectures
if arch == "sofa-chd":
template_vars.update(
{
"vpr_device_layout": "12x12",
"vpr_route_chan_width": "60",
}
)
if arch == "sofa-hd":
template_vars.update(
{
"vpr_device_layout": "12x12",
"vpr_route_chan_width": "40",
}
)
if arch == "sofa-plus-hd":
template_vars.update(
{
"openfpga_sim_setting_file": f"{flow_dir}/openfpga_simulation_settings/fixed_sim_openfpga.xml",
"vpr_device_layout": "12x12",
"vpr_route_chan_width": "60",
}
)
if arch == "sofa-qlhd":
template_vars.update(
{
"vpr_device_layout": "12x12",
"vpr_route_chan_width": "60",
}
)
# Render the OpenFPGA task configuration file
try:
os.makedirs(os.path.join(self.work_root, "config"))
except:
pass
self.render_template(
"task_simulation.conf.j2", "config/task.conf", template_vars
)
[docs]
def build_main(self):
pass
[docs]
def run_main(self):
"""
Run the FPGA simulation.
"""
# NOTE: edalize subprocess runs the current tool from the work_root
# directory, where the 'config/' dir is located.
task_script = f"{self.openfpga_flow}/scripts/run_fpga_task.py"
task_options = self.tool_options.get("task_options", [])
args = [task_script, "."] + task_options
logger.info("run OpenFPGA simulation task")
self._run_tool("python3", args)