diff --git a/litex/build/altera/platform.py b/litex/build/altera/platform.py index 0e7f24d09..87471b58c 100644 --- a/litex/build/altera/platform.py +++ b/litex/build/altera/platform.py @@ -16,6 +16,8 @@ class AlteraPlatform(GenericPlatform): bitstream_ext = ".sof" create_rbf = True + _supported_toolchains = ["quartus"] + def __init__(self, *args, toolchain="quartus", **kwargs): GenericPlatform.__init__(self, *args, **kwargs) self.ips = set() diff --git a/litex/build/anlogic/platform.py b/litex/build/anlogic/platform.py index b5bfab63c..e857ce0a6 100644 --- a/litex/build/anlogic/platform.py +++ b/litex/build/anlogic/platform.py @@ -15,6 +15,8 @@ from litex.build.anlogic import common, anlogic class AnlogicPlatform(GenericPlatform): bitstream_ext = ".fs" + _supported_toolchains = ["td"] + def __init__(self, device, *args, toolchain="td", **kwargs): GenericPlatform.__init__(self, device, *args, **kwargs) if toolchain == "td": diff --git a/litex/build/argument_parser.py b/litex/build/argument_parser.py new file mode 100644 index 000000000..944095759 --- /dev/null +++ b/litex/build/argument_parser.py @@ -0,0 +1,194 @@ +# +# This file is part of LiteX. +# +# This file is Copyright (c) 2022 Gwenhael Goavec-Merou +# SPDX-License-Identifier: BSD-2-Clause + +import argparse +import importlib +import sys + +from litex.soc.integration.soc_core import * +from litex.soc.integration.builder import * + +# LitexArgumentParser ------------------------------------------------------------------------------ +class LiteXArgumentParser(argparse.ArgumentParser): + """ + ArgumentParser subclass used to intercept parse_args call and to simplify + common arguments addition + Attributes + ========== + _platform: GenericPlatform subclass + target platform + _device: str + target device family + _args: argparse.Namespace + list of args after parse_args call + _toolchain: str + toolchain used at build time + _default_toolchain: str + toolchain to use by default or when no selection is done by the user + """ + def __init__(self, platform=None, **kwargs): + """ + CTOR: create a Target options group, adds toolchain, build and load + arguments. Call builder_args and soc_core_args for fill parser with + corresponding options + + Parameters + ========== + platform: GenericPlatform subclass + targeted platform + kwargs: dict + all arguments passed to argparse.ArgumentParser CTOR + """ + argparse.ArgumentParser.__init__(self, kwargs) + self._platform = platform + if platform is not None: + self._device = platform.device_family + toolchains = platform.toolchains(self._device) + self._default_toolchain = toolchains[0] + else: + self._device = None + toolchains = None + self._default_toolchain = None + self._args = None + self._toolchain = None + + self._target_group = self.add_argument_group(title="Target options") + if toolchains is not None: + self.add_target_argument("--toolchain", + default=self._default_toolchain, + choices=toolchains, + help="FPGA toolchain ({}).".format(" or ".join(toolchains))) + else: + self.add_target_argument("-toolchain", help="FPGA toolchain") + self.add_target_argument("--build", action="store_true", help="Build design.") + self.add_target_argument("--load", action="store_true", help="Load bitstream.") + builder_args(self) + soc_core_args(self) + + def set_platform(self, platform): + """ set platform. Check first if not already set + + Parameter + ========= + platform: GenericPlatform subclass + the platform + """ + assert self._platform is None + self._platform = platform + self._device = platform.device_family + toolchains = platform.toolchains(self._device) + self._default_toolchain = toolchains[0] + # add a setter (LitexArgumentParserInstance.platform = myPlatform) + platform = property(None, set_platform) + + @property + def target_group(self): + """ return target_group + """ + return self._target_group + + def add_target_argument(self, *args, **kwargs): + """ wrapper to add argument to "Target options group" from outer of this + class + """ + self._target_group.add_argument(*args, **kwargs) + + @property + def builder_argdict(self): + """ + access to builder_argdict + + Return + ====== + builder arguments dict + """ + return builder_argdict(self._args) + + @property + def soc_core_argdict(self): + """ + access to soc_core_argdict + + Return + ====== + soc_core arguments dict + """ + return soc_core_argdict(self._args) + + @property + def toolchain_argdict(self): + """ + access to target toolchain argdict + + Return + ====== + toolchain arguments dict + """ + if self._platform is None: + return dict() + else: + return self._platform.get_argdict(self._toolchain, self._args) + + def parse_args(self, args=None, namespace=None): + """ + override argparse.ArgumentParser.parse_args to inject toolchain + and soc_core args. + Checks first is platform is set: when platform is none: try to + search for a platform argument + """ + # When platform is None try to search for a user input + if self._platform is None: + platform = self.get_value_from_key("--platform", None) + if platform is not None: + self.set_platform(importlib.import_module(platform).Platform) + + # Intercept selected toolchain to fill arguments. + if self._platform is not None: + self._toolchain = self.get_value_from_key("--toolchain", + self._default_toolchain) + if self._toolchain is not None: + self._platform.fill_args(self._toolchain, self) + + # Intercept selected CPU to fill arguments. + cpu_cls = None + cpu_name = self.get_value_from_key("--cpu-type") + if cpu_name is not None: + cpu_cls = cpu.CPUS[cpu_name] + if cpu_cls is not None and hasattr(cpu_cls, "args_fill"): + cpu_cls.args_fill(self) + self._args = argparse.ArgumentParser.parse_args(self, args, namespace) + + # Re-inject CPU read arguments. + if cpu_cls is not None and hasattr(cpu_cls, "args_read"): + cpu_cls.args_read(args) + + return self._args + + def get_value_from_key(self, key, default=None): + """ + search key into sys.argv + + Parameters + ========== + key: str + key to search + default: str + default value when key is not in sys.argv + + Return + ====== + sys.argv corresponding value or default + """ + value = None + try: + index = [i for i, item in enumerate(sys.argv) if key in item][0] + if '=' in sys.argv[index]: + value = sys.argv[index].split('=')[1] + else: + value = sys.argv[index+1] + except IndexError: + value = default + return value diff --git a/litex/build/efinix/platform.py b/litex/build/efinix/platform.py index 948b0033c..a186cae3d 100644 --- a/litex/build/efinix/platform.py +++ b/litex/build/efinix/platform.py @@ -18,6 +18,8 @@ from litex.build.efinix import EfinixDbParser class EfinixPlatform(GenericPlatform): bitstream_ext = ".bit" + _supported_toolchains = ["efinity"] + def __init__(self, *args, iobank_info=None, toolchain="efinity", **kwargs): GenericPlatform.__init__(self, *args, **kwargs) diff --git a/litex/build/generic_platform.py b/litex/build/generic_platform.py index 0826e4a28..c86287bee 100644 --- a/litex/build/generic_platform.py +++ b/litex/build/generic_platform.py @@ -322,6 +322,8 @@ class ConstraintManager: # Generic Platform --------------------------------------------------------------------------------- class GenericPlatform: + device_family = None + def __init__(self, device, io, connectors=[], name=None): self.toolchain = None self.device = device @@ -467,3 +469,54 @@ class GenericPlatform: @property def support_mixed_language(self): return self.toolchain.support_mixed_language + + @classmethod + def fill_args(cls, toolchain, parser): + """ + pass parser to the specific toolchain to + fill this with toolchain args + + Parameters + ========== + toolchain: str + toolchain name + parser: argparse.ArgumentParser + parser to be filled + """ + pass # pass must be overloaded (if required) + + @classmethod + def get_argdict(cls, toolchain, args): + """ + return a dict of args + + Parameters + ========== + toolchain: str + toolchain name + + Return + ====== + a dict of key/value for each args or an empty dict + """ + return {} # Empty must be overloaded (if required) + + @classmethod + def toolchains(cls, device): + """ + Returns list of toolchains compatible with device + + Parameters + ========== + device: str + device name (ice40, ecp5, nexus) + + Return + ====== + A list of compatible toolchains (str) or an empty list + """ + if type(cls._supported_toolchains) == dict: + assert device is not None + return cls._supported_toolchains[device] + else: + return cls._supported_toolchains diff --git a/litex/build/gowin/platform.py b/litex/build/gowin/platform.py index d130d7b8f..62525fe65 100644 --- a/litex/build/gowin/platform.py +++ b/litex/build/gowin/platform.py @@ -15,6 +15,8 @@ from litex.build.gowin import common, gowin class GowinPlatform(GenericPlatform): bitstream_ext = ".fs" + _supported_toolchains = ["gowin", "apicula"] + def __init__(self, device, *args, toolchain="gowin", devicename=None, **kwargs): GenericPlatform.__init__(self, device, *args, **kwargs) if not devicename: diff --git a/litex/build/lattice/__init__.py b/litex/build/lattice/__init__.py index 6b356c722..95b9e92f6 100644 --- a/litex/build/lattice/__init__.py +++ b/litex/build/lattice/__init__.py @@ -1,2 +1,2 @@ -from litex.build.lattice.platform import LatticePlatform +from litex.build.lattice.platform import LatticePlatform, LatticeiCE40Platform, LatticeECP5Platform, LatticeNexusPlatform from litex.build.lattice.programmer import LatticeProgrammer diff --git a/litex/build/lattice/icestorm.py b/litex/build/lattice/icestorm.py index 597e09003..db88daf12 100644 --- a/litex/build/lattice/icestorm.py +++ b/litex/build/lattice/icestorm.py @@ -106,7 +106,7 @@ class LatticeIceStormToolchain(YosysNextPNRToolchain): def icestorm_args(parser): - toolchain_group = parser.add_argument_group(title="Toolchain options") + toolchain_group = parser.add_argument_group(title="Icestorm toolchain options") yosys_nextpnr_args(toolchain_group) def icestorm_argdict(args): diff --git a/litex/build/lattice/oxide.py b/litex/build/lattice/oxide.py index 7bc2f094f..9fa2f2a1c 100644 --- a/litex/build/lattice/oxide.py +++ b/litex/build/lattice/oxide.py @@ -62,7 +62,7 @@ class LatticeOxideToolchain(YosysNextPNRToolchain): return (self._build_name + ".pdc", "PDC") def oxide_args(parser): - toolchain_group = parser.add_argument_group(title="Toolchain options") + toolchain_group = parser.add_argument_group(title="Oxide toolchain options") yosys_nextpnr_args(toolchain_group) toolchain_group.add_argument("--nexus-es-device", action="store_true", help="Use Nexus-ES1 part.") diff --git a/litex/build/lattice/platform.py b/litex/build/lattice/platform.py index 3e95f8322..b6bf94788 100644 --- a/litex/build/lattice/platform.py +++ b/litex/build/lattice/platform.py @@ -13,6 +13,12 @@ from litex.build.lattice import common, diamond, icestorm, trellis, radiant, oxi class LatticePlatform(GenericPlatform): bitstream_ext = ".bit" + _supported_toolchains = { + "ice40": ["icestorm"], + "ecp5": ["trellis", "diamond"], + "nexus": ["radiant", "oxide"], + } + def __init__(self, *args, toolchain="diamond", **kwargs): GenericPlatform.__init__(self, *args, **kwargs) if toolchain == "diamond": @@ -38,7 +44,6 @@ class LatticePlatform(GenericPlatform): attr_translate = self.toolchain.attr_translate, **kwargs) - def build(self, *args, **kwargs): return self.toolchain.build(self, *args, **kwargs) @@ -54,3 +59,67 @@ class LatticePlatform(GenericPlatform): if hasattr(to, "p"): to = to.p self.toolchain.add_false_path_constraint(self, from_, to) + + @classmethod + def fill_args(cls, toolchain, parser): + """ + pass parser to the specific toolchain to + fill this with toolchain args + + Parameters + ========== + toolchain: str + toolchain name + parser: argparse.ArgumentParser + parser to be filled + """ + if toolchain == "radiant": + radiant.radiant_build_args(parser) + elif toolchain == "oxide": + oxide.oxide_args(parser) + elif toolchain == "trellis": + trellis.trellis_args(parser) + elif toolchain == "icestorm": + icestorm.icestorm_args(parser) + # nothing for diamond + + @classmethod + def get_argdict(cls, toolchain, args): + """ + return a dict of args + + Parameters + ========== + toolchain: str + toolchain name + + Return + ====== + a dict of key/value for each args or an empty dict + """ + if toolchain == "radiant": + return radiant.radiant_build_argdict(args) + elif toolchain == "oxide": + return oxide.oxide_argdict(args) + elif toolchain == "trellis": + return trellis.trellis_argdict(args) + elif toolchain == "icestorm": + return icestorm.icestorm_argdict(args) + else: + return {} + # nothing for diamond + +# LatticeiCE40Platform ----------------------------------------------------------------------------- + +class LatticeiCE40Platform(LatticePlatform): + device_family = "ice40" + +# LatticeECP5Platform ------------------------------------------------------------------------------ + +class LatticeECP5Platform(LatticePlatform): + device_family = "ecp5" + +# LatticeNexusPlatform ----------------------------------------------------------------------------- + +class LatticeNexusPlatform(LatticePlatform): + device_family = "nexus" diff --git a/litex/build/lattice/radiant.py b/litex/build/lattice/radiant.py index 328a2dde6..9cb54b730 100644 --- a/litex/build/lattice/radiant.py +++ b/litex/build/lattice/radiant.py @@ -273,7 +273,7 @@ class LatticeRadiantToolchain(GenericToolchain): def radiant_build_args(parser): - toolchain_group = parser.add_argument_group(title="Toolchain options") + toolchain_group = parser.add_argument_group(title="Radiant toolchain options") toolchain_group.add_argument("--synth-mode", default="synplify", help="Synthesis mode (synplify or yosys).") def radiant_build_argdict(args): diff --git a/litex/build/lattice/trellis.py b/litex/build/lattice/trellis.py index 0a833613d..0f365c831 100644 --- a/litex/build/lattice/trellis.py +++ b/litex/build/lattice/trellis.py @@ -150,7 +150,7 @@ class LatticeTrellisToolchain(YosysNextPNRToolchain): freq=str(float(1/period)*1000), clk="{clk}"), clk=clk) def trellis_args(parser): - toolchain_group = parser.add_argument_group(title="Toolchain options") + toolchain_group = parser.add_argument_group(title="Trellis toolchain options") yosys_nextpnr_args(toolchain_group) toolchain_group.add_argument("--ecppack-bootaddr", default=0, help="Set boot address for next image.") toolchain_group.add_argument("--ecppack-spimode", default=None, help="Set slave SPI programming mode.") diff --git a/litex/build/microsemi/platform.py b/litex/build/microsemi/platform.py index ac4aa6938..a36ee7656 100644 --- a/litex/build/microsemi/platform.py +++ b/litex/build/microsemi/platform.py @@ -12,6 +12,8 @@ from litex.build.microsemi import common, libero_soc class MicrosemiPlatform(GenericPlatform): bitstream_ext = ".bit" + _supported_toolchains = ["libero_soc_polarfire"] + def __init__(self, *args, toolchain="libero_soc_polarfire", **kwargs): GenericPlatform.__init__(self, *args, **kwargs) if toolchain == "libero_soc_polarfire": diff --git a/litex/build/osfpga/platform.py b/litex/build/osfpga/platform.py index d04fc06f0..bdd576651 100644 --- a/litex/build/osfpga/platform.py +++ b/litex/build/osfpga/platform.py @@ -14,6 +14,8 @@ from litex.build.osfpga import common, osfpga class OSFPGAPlatform(GenericPlatform): bitstream_ext = ".bin" + _supported_toolchains = ["osfpga"] + def __init__(self, device, *args, toolchain="foedag", devicename=None, **kwargs): GenericPlatform.__init__(self, device, *args, **kwargs) self.devicename = devicename diff --git a/litex/build/quicklogic/platform.py b/litex/build/quicklogic/platform.py index 8142f22d4..506ac0bed 100644 --- a/litex/build/quicklogic/platform.py +++ b/litex/build/quicklogic/platform.py @@ -14,6 +14,8 @@ from litex.build.quicklogic import common, f4pga class QuickLogicPlatform(GenericPlatform): bitstream_ext = ".bit" + _supported_toolchains = ["f4pga"] + def __init__(self, *args, toolchain="f4pga", **kwargs): GenericPlatform.__init__(self, *args, **kwargs) if toolchain == "symbiflow" or toolchain == "f4pga": diff --git a/litex/build/sim/platform.py b/litex/build/sim/platform.py index 633a16892..bae83f907 100644 --- a/litex/build/sim/platform.py +++ b/litex/build/sim/platform.py @@ -16,6 +16,9 @@ from litex.soc.interconnect.csr import AutoCSR, CSR, CSRStorage class SimPlatform(GenericPlatform): + + _supported_toolchains = ["verilator"] + def __init__(self, device, io, name="sim", toolchain="verilator", **kwargs): if "sim_trace" not in (iface[0] for iface in io): io.append(("sim_trace", 0, Pins(1))) diff --git a/litex/build/xilinx/__init__.py b/litex/build/xilinx/__init__.py index 53e6e840a..9d9bc1801 100644 --- a/litex/build/xilinx/__init__.py +++ b/litex/build/xilinx/__init__.py @@ -1,2 +1,2 @@ -from litex.build.xilinx.platform import XilinxPlatform +from litex.build.xilinx.platform import XilinxPlatform, XilinxSpartan6Platform, Xilinx7SeriesPlatform from litex.build.xilinx.programmer import UrJTAG, XC3SProg, FpgaProg, VivadoProgrammer, iMPACT, Adept diff --git a/litex/build/xilinx/platform.py b/litex/build/xilinx/platform.py index 14f6092bb..ad392c05e 100644 --- a/litex/build/xilinx/platform.py +++ b/litex/build/xilinx/platform.py @@ -9,13 +9,18 @@ import os from litex.build.generic_platform import GenericPlatform -from litex.build.xilinx import common +from litex.build.xilinx import common, vivado, ise, yosys_nextpnr # XilinxPlatform ----------------------------------------------------------------------------------- class XilinxPlatform(GenericPlatform): bitstream_ext = ".bit" + _supported_toolchains = { + "serie7": ["vivado", "f4pga", "yosys+nextpnr"], + "spartan6": ["ise"], + } + def __init__(self, *args, toolchain="ise", **kwargs): GenericPlatform.__init__(self, *args, **kwargs) self.edifs = set() @@ -84,3 +89,49 @@ class XilinxPlatform(GenericPlatform): if hasattr(to, "p"): to = to.p self.toolchain.add_false_path_constraint(self, from_, to) + + @classmethod + def fill_args(cls, toolchain, parser): + """ + pass parser to the specific toolchain to + fill this with toolchain args + + Parameters + ========== + toolchain: str + toolchain name + parser: argparse.ArgumentParser + parser to be filled + """ + if toolchain == "vivado": + vivado.vivado_build_args(parser) + + @classmethod + def get_argdict(cls, toolchain, args): + """ + return a dict of args + + Parameters + ========== + toolchain: str + toolchain name + + Return + ====== + a dict of key/value for each args or an empty dict + """ + if toolchain == "vivado": + return vivado.vivado_build_argdict(args) + else: + return dict() + +# Xilinx7SeriesPlatform ----------------------------------------------------------------------------- + +class Xilinx7SeriesPlatform(XilinxPlatform): + device_family = "serie7" + +# XilinxSpartan6Platform --------------------------------------------------------------------------- + +class XilinxSpartan6Platform(XilinxPlatform): + device_family = "spartan6" +