From 71a5ef238087de5fb973f5fe7ad31b29d67bd07e Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 16 May 2022 16:25:47 +0200 Subject: [PATCH] build: Add initial OSFPGA/FOEDAG build backend and blinky example. --- litex/build/openfpga/blinky.py | 2 +- litex/build/osfpga/__init__.py | 1 + litex/build/osfpga/blinky.py | 41 ++++++++++ litex/build/osfpga/common.py | 14 ++++ litex/build/osfpga/foedag.py | 137 +++++++++++++++++++++++++++++++++ litex/build/osfpga/platform.py | 38 +++++++++ 6 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 litex/build/osfpga/__init__.py create mode 100755 litex/build/osfpga/blinky.py create mode 100644 litex/build/osfpga/common.py create mode 100644 litex/build/osfpga/foedag.py create mode 100644 litex/build/osfpga/platform.py diff --git a/litex/build/openfpga/blinky.py b/litex/build/openfpga/blinky.py index 4dd2d547a..cfa504680 100755 --- a/litex/build/openfpga/blinky.py +++ b/litex/build/openfpga/blinky.py @@ -34,7 +34,7 @@ clk = platform.request("clk") led = platform.request("led") module = Module() module.clock_domains.cd_sys = ClockDomain("sys") -module.cd_sys.clk.eq(clk) +module.comb += module.cd_sys.clk.eq(clk) counter = Signal(26) module.comb += led.eq(counter[25]) module.sync += counter.eq(counter + 1) diff --git a/litex/build/osfpga/__init__.py b/litex/build/osfpga/__init__.py new file mode 100644 index 000000000..9b874d960 --- /dev/null +++ b/litex/build/osfpga/__init__.py @@ -0,0 +1 @@ +from litex.build.osfpga.platform import OSFPGAPlatform diff --git a/litex/build/osfpga/blinky.py b/litex/build/osfpga/blinky.py new file mode 100755 index 000000000..7c1054e0b --- /dev/null +++ b/litex/build/osfpga/blinky.py @@ -0,0 +1,41 @@ +#!/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.osfpga import OSFPGAPlatform + +# Minimal Platform --------------------------------------------------------------------------------- + +_io = [ + ("clk", 0, Pins(1)), + ("led", 0, Pins(1)) +] + +class Platform(OSFPGAPlatform): + def __init__(self): + OSFPGAPlatform.__init__(self, device=None, io=_io) # FIXME: Add device support. + +# Minimal Design ----------------------------------------------------------------------------------- + +platform = Platform() +clk = platform.request("clk") +led = platform.request("led") +module = Module() +module.clock_domains.cd_sys = ClockDomain("sys") +module.comb += 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, build_name="blinky", run=True) diff --git a/litex/build/osfpga/common.py b/litex/build/osfpga/common.py new file mode 100644 index 000000000..45ff7e469 --- /dev/null +++ b/litex/build/osfpga/common.py @@ -0,0 +1,14 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2022 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +from migen.fhdl.module import Module +from migen.genlib.resetsync import AsyncResetSynchronizer + +from litex.build.io import * + +# OS-FPGA Special Overrides -------------------------------------------------------------------------- + +osfpga_special_overrides = {} diff --git a/litex/build/osfpga/foedag.py b/litex/build/osfpga/foedag.py new file mode 100644 index 000000000..a84a718d2 --- /dev/null +++ b/litex/build/osfpga/foedag.py @@ -0,0 +1,137 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2022 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os +import sys +import math +import subprocess +from shutil import which, copyfile + +from migen.fhdl.structure import _Fragment + +from litex.build.generic_platform import * +from litex.build import tools + +# Timing Constraints (.sdc) ------------------------------------------------------------------------ + +def _build_sdc(clocks, vns, build_name): + sdc = [] + for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid): + sdc.append(f"create_clock -name {vns.get_name(clk)} -period {str(period)} [get_ports {{{vns.get_name(clk)}}}]") + with open(f"{build_name}.sdc", "w") as f: + f.write("\n".join(sdc)) + +# Script ------------------------------------------------------------------------------------------- + +def _build_tcl(name, device, files, build_name): + tcl = [] + + # Create Design. + tcl.append(f"create_design {build_name}") + + # Set Device. + # TODO (Use Macro for now). + tcl.append("set_macro P1=10 P2=20") + + # Add Include Path. + # TODO. + + # Add Sources. + for f, typ, lib in files: + tcl.append(f"add_design_file {f}") + + # Set Top Module. + tcl.append(f"set_top_module {build_name}") + + # Add Timings Constraints. + tcl.append(f"add_constraint_file {build_name}.sdc") + + # Run. + tcl.append("synth") + tcl.append("packing") + tcl.append("place") + tcl.append("route") + tcl.append("sta") + tcl.append("power") + tcl.append("bitstream") + + # Generate .tcl. + with open("build.tcl", "w") as f: + f.write("\n".join(tcl)) + +# FOEDAGToolchain ----------------------------------------------------------------------------------- + +class FOEDAGToolchain: + attr_translate = {} + + def __init__(self): + self.clocks = dict() + + def build(self, platform, fragment, + build_dir = "build", + build_name = "top", + run = True, + **kwargs): + + # Create build directory. + cwd = os.getcwd() + os.makedirs(build_dir, exist_ok=True) + 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) + v_file = build_name + ".v" + v_output.write(v_file) + platform.add_source(v_file) + + # Generate constraints file. + # IOs. + # TODO. + + # Timings (.sdc) + _build_sdc( + clocks = self.clocks, + vns = v_output.ns, + build_name = build_name, + ) + + # Generate build script (.tcl) + script = _build_tcl( + name = platform.devicename, + device = platform.device, + files = platform.sources, + build_name = build_name, + ) + + # Run + if run: + foedag_sh = "foedag" + if which(foedag_sh) is None: + msg = "Unable to find FOEDAG toolchain, please:\n" + msg += "- Add FOEDAG toolchain to your $PATH." + raise OSError(msg) + + if subprocess.call([foedag_sh, "--batch", "--script", "build.tcl"]) != 0: + raise OSError("Error occured during FOEDAG's script execution.") + + os.chdir(cwd) + + return v_output.ns + + def add_period_constraint(self, platform, clk, period): + clk.attr.add("keep") + period = math.floor(period*1e3)/1e3 # round to lowest picosecond + if clk in self.clocks: + if period != self.clocks[clk]: + raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns" + .format(self.clocks[clk], period)) + self.clocks[clk] = period diff --git a/litex/build/osfpga/platform.py b/litex/build/osfpga/platform.py new file mode 100644 index 000000000..6412bd1b8 --- /dev/null +++ b/litex/build/osfpga/platform.py @@ -0,0 +1,38 @@ +# +# 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.osfpga import common, foedag + +# OSFPGAPlatform ----------------------------------------------------------------------------------- + +class OSFPGAPlatform(GenericPlatform): + bitstream_ext = ".bin" + + def __init__(self, device, *args, toolchain="foedag", devicename=None, **kwargs): + GenericPlatform.__init__(self, device, *args, **kwargs) + self.devicename = devicename + if toolchain == "foedag": + self.toolchain = foedag.FOEDAGToolchain() + else: + raise ValueError(f"Unknown toolchain {toolchain}") + + def get_verilog(self, *args, special_overrides=dict(), **kwargs): + so = dict(common.osfpga_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) + + def add_period_constraint(self, clk, period): + if clk is None: return + self.toolchain.add_period_constraint(self, clk, period)