_toolchain,build.plat,vendor.*: add required_tools list and checks.

This commit is contained in:
Emily 2019-08-31 00:27:22 +01:00 committed by whitequark
parent 4e91710933
commit c4e8ac734f
8 changed files with 75 additions and 22 deletions

View file

@ -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

View file

@ -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,

View file

@ -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.

View file

@ -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)

View file

@ -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":

View file

@ -39,6 +39,12 @@ class LatticeICE40Platform(TemplatedPlatform):
device = abstractproperty()
package = abstractproperty()
required_tools = [
"yosys",
"nextpnr-ice40",
"icepack",
]
_nextpnr_device_options = {
"iCE40LP384": "--lp384",
"iCE40LP1K": "--lp1k",

View file

@ -50,6 +50,8 @@ class Xilinx7SeriesPlatform(TemplatedPlatform):
package = abstractproperty()
speed = abstractproperty()
required_tools = ["vivado"]
file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.v": r"""

View file

@ -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()