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
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
""".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,
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)
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
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.
from ..hdl.ast import *
from ..hdl.ir import *
from ..back import rtlil
-from .._toolchain import get_tool
+from .._toolchain import require_tool
__all__ = ["FHDLTestCase"]
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)
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":
device = abstractproperty()
package = abstractproperty()
+ required_tools = [
+ "yosys",
+ "nextpnr-ice40",
+ "icepack",
+ ]
+
_nextpnr_device_options = {
"iCE40LP384": "--lp384",
"iCE40LP1K": "--lp1k",
package = abstractproperty()
speed = abstractproperty()
+ required_tools = ["vivado"]
+
file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.v": r"""
package = abstractproperty()
speed = abstractproperty()
+ required_tools = [
+ "xst",
+ "ngdbuild",
+ "map",
+ "par",
+ "bitgen",
+ ]
+
@property
def family(self):
device = self.device.upper()