diff --git a/litex/build/openfpga/__init__.py b/litex/build/openfpga/__init__.py new file mode 100644 index 000000000..b9fc5c1eb --- /dev/null +++ b/litex/build/openfpga/__init__.py @@ -0,0 +1 @@ +from litex.build.openfpga.platform import OpenFPGAPlatform diff --git a/litex/build/openfpga/blinky.py b/litex/build/openfpga/blinky.py new file mode 100755 index 000000000..4dd2d547a --- /dev/null +++ b/litex/build/openfpga/blinky.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +# +# This file is part of LiteX. +# +# Copyright (c) 2022 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os + +from migen import * + +from litex.build.generic_platform import Pins +from litex.build.openfpga import OpenFPGAPlatform + +# export LITEX_ENV_OPENFPGA=/home/florent/dev/openfpga/OpenFPGA +# export LITEX_ENV_OPENFPGA_SOFA=/home/florent/dev/openfpga/SOFA + +# Minimal Platform --------------------------------------------------------------------------------- + +_io = [ + ("clk", 0, Pins(1)), + ("led", 0, Pins(1)) +] + +class Platform(OpenFPGAPlatform): + def __init__(self): + OpenFPGAPlatform.__init__(self, "FPGA1212_QLSOFA_HD", _io) + +# Minimal Design ----------------------------------------------------------------------------------- + +platform = Platform() +clk = platform.request("clk") +led = platform.request("led") +module = Module() +module.clock_domains.cd_sys = ClockDomain("sys") +module.cd_sys.clk.eq(clk) +counter = Signal(26) +module.comb += led.eq(counter[25]) +module.sync += counter.eq(counter + 1) + +# Build -------------------------------------------------------------------------------------------- + +platform.build(module, run=True) diff --git a/litex/build/openfpga/common.py b/litex/build/openfpga/common.py new file mode 100644 index 000000000..dd5422536 --- /dev/null +++ b/litex/build/openfpga/common.py @@ -0,0 +1,9 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2022 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +# OpenFPGA Special Overrides --------------------------------------------------------------------- + +openfpga_special_overrides = {} diff --git a/litex/build/openfpga/openfpga.py b/litex/build/openfpga/openfpga.py new file mode 100644 index 000000000..76a9f39b9 --- /dev/null +++ b/litex/build/openfpga/openfpga.py @@ -0,0 +1,152 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2022 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os +import sys +import subprocess +from shutil import which + +from migen.fhdl.structure import _Fragment + +from litex.build.generic_platform import * +from litex.build import tools +from litex.build.openfpga import common + +# Check Setup -------------------------------------------------------------------------------------- + +def _check_setup(): + if os.getenv("LITEX_ENV_OPENFPGA", False) == False: + msg = "Unable to find OpenFPGA toolchain, please:\n" + msg += "- Set LITEX_ENV_OPENFPGA environment variant to OpenFPGA's settings path.\n" + raise OSError(msg) + + if os.getenv("LITEX_ENV_OPENFPGA_SOFA", False) == False: + msg = "Unable to find OpenFPGA's SOFA project, please:\n" + msg += "- Set LITEX_ENV_OPENFPGA_SOFA environment variant to OpenFPGA's SOFA settings path.\n" + raise OSError(msg) + +# Task Config ------------------------------------------------------------------------------------- + +def _build_task_conf(platform, sources, build_dir, build_name): + # Get Environnment variables. + openfpga_path = os.getenv("LITEX_ENV_OPENFPGA") + openfpga_sofa_path = os.getenv("LITEX_ENV_OPENFPGA_SOFA") + + # Get PnR/Task directories from OPENFPGA/SOFA paths. + pnr_path = os.path.join(openfpga_sofa_path, platform.device + "_PNR") + task_path = os.path.join(pnr_path, platform.device + "_task") + + # Get Config file. + task_conf = os.path.join(task_path, "config", "task_simulation.conf") + + # Helpers. + def replace_openfpga_task_section(filename, section, contents): + lines = [] + # Read file and replace section with contents. + copy = True + for line in open(filename, "r"): + if not copy and line.startswith("["): + copy = True + if line.startswith(section): + copy = False + lines.append(section + "\n") + for l in contents: + lines.append(l + "\n") + lines.append("\n") + if copy: + lines.append(line) + + # Save file to .orig. + os.system(f"mv {filename} {filename}.orig") + + # Write file with replaced section. + with open(filename, "w") as f: + f.write("".join(lines)) + + # Add sources. + bench_sources = [] + for filename, language, library in sources: + if language is None: + continue + if language not in ["verilog"]: + raise ValueError("OpenFPGA flow only supports verilog") + bench_sources.append(filename) + replace_openfpga_task_section(task_conf, "[BENCHMARKS]", [f"bench0={' '.join(bench_sources)}"]) + + # Set Top-Level. + replace_openfpga_task_section(task_conf, "[SYNTHESIS_PARAM]", [f"bench0_top={build_name}"]) + +def _run_task(device): + # Get Environnment variables. + openfpga_path = os.getenv("LITEX_ENV_OPENFPGA") + openfpga_sofa_path = os.getenv("LITEX_ENV_OPENFPGA_SOFA") + + # Get PnR/Task directories from OPENFPGA/SOFA paths. + pnr_path = os.path.join(openfpga_sofa_path, device + "_PNR") + task_path = os.path.join(pnr_path, device + "_task") + + # Set OPENFPGA_PATH. + os.environ["OPENFPGA_PATH"] = os.getenv("LITEX_ENV_OPENFPGA") + + # Run OpenFPGA flow. + build_cmd = ["make", "-C", pnr_path, "clean", "runOpenFPGA"] + if subprocess.call(build_cmd) != 0: + raise OSError("Error occured during OpenFPGA's flow execution.") + + # Copy artifacts. + os.system("rm -rf run001") + os.system(f"cp -r {task_path}/run001 run001") + + # Display log. FIXME: Do it during build? + os.system("cat run001/vpr_arch/top/MIN_ROUTE_CHAN_WIDTH/openfpgashell.log") + +# OpenFPGAToolchain -------------------------------------------------------------------------------- + +class OpenFPGAToolchain: + attr_translate = {} + + special_overrides = common.openfpga_special_overrides + + def __init__(self): + self.clocks = dict() + self.false_paths = set() + + def build(self, platform, fragment, + build_dir = "build", + build_name = "top", + run = False, + **kwargs): + + # Create Build Directory. + os.makedirs(build_dir, exist_ok=True) + cwd = os.getcwd() + os.chdir(build_dir) + + # Finalize Design. + if not isinstance(fragment, _Fragment): + fragment = fragment.get_fragment() + platform.finalize(fragment) + + # Generate Verilog. + v_output = platform.get_verilog(fragment, name=build_name, **kwargs) + named_sc, named_pc = platform.resolve_signals(v_output.ns) + top_file = build_name + ".v" + v_output.write(top_file) + platform.add_source(top_file) + + # Check Setup. + _check_setup() + + # Generate Task Config. + _build_task_conf(platform, platform.sources, build_dir, build_name) + + # Run Task. + if run: + _run_task(platform.device) + + os.chdir(cwd) + + return v_output.ns diff --git a/litex/build/openfpga/platform.py b/litex/build/openfpga/platform.py new file mode 100644 index 000000000..e339b68f2 --- /dev/null +++ b/litex/build/openfpga/platform.py @@ -0,0 +1,28 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2022 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os + +from litex.build.generic_platform import GenericPlatform +from litex.build.openfpga import common, openfpga + +# OpenFPGAPlatform ------------------------------------------------------------------------------- + +class OpenFPGAPlatform(GenericPlatform): + def __init__(self, device, *args, **kwargs): + GenericPlatform.__init__(self, device, *args, **kwargs) + self.toolchain = openfpga.OpenFPGAToolchain() + + def get_verilog(self, *args, special_overrides=dict(), **kwargs): + so = dict(common.openfpga_special_overrides) + so.update(special_overrides) + return GenericPlatform.get_verilog(self, *args, + special_overrides = so, + attr_translate = self.toolchain.attr_translate, + **kwargs) + + def build(self, *args, **kwargs): + return self.toolchain.build(self, *args, **kwargs)