From 5d174e4c3ce0a0602c432044d99e29f589616b35 Mon Sep 17 00:00:00 2001 From: Emily Date: Sat, 31 Aug 2019 00:27:22 +0100 Subject: [PATCH] _toolchain,build.plat,vendor.*: add required_tools list and checks. --- nmigen/_toolchain.py | 37 +++++++++++++++++++++++++++-- nmigen/back/verilog.py | 17 +++---------- nmigen/build/plat.py | 15 ++++++++---- nmigen/test/tools.py | 4 ++-- nmigen/vendor/lattice_ecp5.py | 8 +++++++ nmigen/vendor/lattice_ice40.py | 6 +++++ nmigen/vendor/xilinx_7series.py | 2 ++ nmigen/vendor/xilinx_spartan_3_6.py | 8 +++++++ 8 files changed, 75 insertions(+), 22 deletions(-) diff --git a/nmigen/_toolchain.py b/nmigen/_toolchain.py index 43fd53d..4878a32 100644 --- a/nmigen/_toolchain.py +++ b/nmigen/_toolchain.py @@ -1,11 +1,44 @@ import os +import shutil -__all__ = ["get_tool"] +__all__ = ["ToolNotFound", "get_tool", "has_tool", "require_tool"] + + +class ToolNotFound(Exception): + pass + + +def _tool_env_var(name): + return name.upper().replace("-", "_") def get_tool(name): - return os.environ.get(name.upper().replace("-", "_"), overrides.get(name, name)) + return os.environ.get(_tool_env_var(name), overrides.get(name, name)) + + +def has_tool(name): + return shutil.which(get_tool(name)) is not None + + +def require_tool(name): + env_var = _tool_env_var(name) + path = get_tool(name) + if shutil.which(path) is None: + if path == name: + raise ToolNotFound("Could not find required tool {} in PATH. Place " + "it directly in PATH or specify path explicitly " + "via the {} environment variable". + format(name, env_var)) + else: + if os.getenv(env_var): + via = "the {} environment variable".format(env_var) + else: + via = "your packager's toolchain overrides. This is either an " \ + "nMigen bug or a packaging error" + raise ToolNotFound("Could not find required tool {} in {} as " + "specified via {}".format(name, path, via)) + return path # Packages for systems like Nix can inject full paths to certain tools by adding them in diff --git a/nmigen/back/verilog.py b/nmigen/back/verilog.py index 190f31a..d1a4032 100644 --- a/nmigen/back/verilog.py +++ b/nmigen/back/verilog.py @@ -14,19 +14,8 @@ class YosysError(Exception): def _yosys_version(): - yosys_path = get_tool("yosys") - try: - version = subprocess.check_output([yosys_path, "-V"], encoding="utf-8") - except FileNotFoundError as e: - if os.getenv("YOSYS"): - raise YosysError("Could not find Yosys in {} as specified via the YOSYS environment " - "variable".format(os.getenv("YOSYS"))) from e - elif yosys_path == "yosys": - raise YosysError("Could not find Yosys in PATH. Place `yosys` in PATH or specify " - "path explicitly via the YOSYS environment variable") from e - else: - raise - + yosys_path = require_tool("yosys") + version = subprocess.check_output([yosys_path, "-V"], encoding="utf-8") m = re.match(r"^Yosys ([\d.]+)(?:\+(\d+))?", version) tag, offset = m[1], m[2] or 0 return tuple(map(int, tag.split("."))), offset @@ -57,7 +46,7 @@ write_verilog -norename """.format(il_text, " ".join(attr_map), prune="# " if version == (0, 9) and offset == 0 else "") - popen = subprocess.Popen([os.getenv("YOSYS", "yosys"), "-q", "-"], + popen = subprocess.Popen([require_tool("yosys"), "-q", "-"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, diff --git a/nmigen/build/plat.py b/nmigen/build/plat.py index 477e17a..1c35cf6 100644 --- a/nmigen/build/plat.py +++ b/nmigen/build/plat.py @@ -20,10 +20,11 @@ __all__ = ["Platform", "TemplatedPlatform"] class Platform(ResourceManager, metaclass=ABCMeta): - resources = abstractproperty() - connectors = abstractproperty() - default_clk = None - default_rst = None + resources = abstractproperty() + connectors = abstractproperty() + default_clk = None + default_rst = None + required_tools = abstractproperty() def __init__(self): super().__init__(self.resources, self.connectors) @@ -63,6 +64,9 @@ class Platform(ResourceManager, metaclass=ABCMeta): build_dir="build", do_build=True, program_opts=None, do_program=False, **kwargs): + for tool in self.required_tools: + require_tool(tool) + plan = self.prepare(elaboratable, name, **kwargs) if not do_build: return plan @@ -73,6 +77,9 @@ class Platform(ResourceManager, metaclass=ABCMeta): self.toolchain_program(products, name, **(program_opts or {})) + def has_required_tools(self): + return all(has_tool(name) for name in self.required_tools) + @abstractmethod def create_missing_domain(self, name): # Simple instantiation of a clock domain driven directly by the board clock and reset. diff --git a/nmigen/test/tools.py b/nmigen/test/tools.py index ba9846f..0329e43 100644 --- a/nmigen/test/tools.py +++ b/nmigen/test/tools.py @@ -11,7 +11,7 @@ from contextlib import contextmanager from ..hdl.ast import * from ..hdl.ir import * from ..back import rtlil -from .._toolchain import get_tool +from .._toolchain import require_tool __all__ = ["FHDLTestCase"] @@ -95,7 +95,7 @@ class FHDLTestCase(unittest.TestCase): script=script, rtlil=rtlil.convert(Fragment.get(spec, platform="formal")) ) - with subprocess.Popen([get_tool("sby"), "-f", "-d", spec_name], cwd=spec_dir, + with subprocess.Popen([require_tool("sby"), "-f", "-d", spec_name], cwd=spec_dir, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc: stdout, stderr = proc.communicate(config) diff --git a/nmigen/vendor/lattice_ecp5.py b/nmigen/vendor/lattice_ecp5.py index 25dc24c..2497f99 100644 --- a/nmigen/vendor/lattice_ecp5.py +++ b/nmigen/vendor/lattice_ecp5.py @@ -239,6 +239,14 @@ class LatticeECP5Platform(TemplatedPlatform): assert toolchain in ("Trellis", "Diamond") self.toolchain = toolchain + @property + def required_tools(self): + if self.toolchain == "Trellis": + return ["yosys", "nextpnr-ecp5", "ecppack"] + if self.toolchain == "Diamond": + return ["pnmainc", "ddtcmd"] + assert False + @property def file_templates(self): if self.toolchain == "Trellis": diff --git a/nmigen/vendor/lattice_ice40.py b/nmigen/vendor/lattice_ice40.py index 21e9297..e403341 100644 --- a/nmigen/vendor/lattice_ice40.py +++ b/nmigen/vendor/lattice_ice40.py @@ -39,6 +39,12 @@ class LatticeICE40Platform(TemplatedPlatform): device = abstractproperty() package = abstractproperty() + required_tools = [ + "yosys", + "nextpnr-ice40", + "icepack", + ] + _nextpnr_device_options = { "iCE40LP384": "--lp384", "iCE40LP1K": "--lp1k", diff --git a/nmigen/vendor/xilinx_7series.py b/nmigen/vendor/xilinx_7series.py index be59017..9529597 100644 --- a/nmigen/vendor/xilinx_7series.py +++ b/nmigen/vendor/xilinx_7series.py @@ -50,6 +50,8 @@ class Xilinx7SeriesPlatform(TemplatedPlatform): package = abstractproperty() speed = abstractproperty() + required_tools = ["vivado"] + file_templates = { **TemplatedPlatform.build_script_templates, "{{name}}.v": r""" diff --git a/nmigen/vendor/xilinx_spartan_3_6.py b/nmigen/vendor/xilinx_spartan_3_6.py index 050a3e3..9f152dc 100644 --- a/nmigen/vendor/xilinx_spartan_3_6.py +++ b/nmigen/vendor/xilinx_spartan_3_6.py @@ -57,6 +57,14 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform): package = abstractproperty() speed = abstractproperty() + required_tools = [ + "xst", + "ngdbuild", + "map", + "par", + "bitgen", + ] + @property def family(self): device = self.device.upper() -- 2.30.2