Rename nMigen to Amaranth HDL.
This commit is contained in:
parent
0b28a97ca0
commit
909a3b8be7
200 changed files with 14493 additions and 14451 deletions
37
amaranth/_toolchain/__init__.py
Normal file
37
amaranth/_toolchain/__init__.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
__all__ = ["ToolNotFound", "tool_env_var", "has_tool", "require_tool"]
|
||||
|
||||
|
||||
class ToolNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def tool_env_var(name):
|
||||
return name.upper().replace("-", "_").replace("+", "X")
|
||||
|
||||
|
||||
def _get_tool(name):
|
||||
return os.environ.get(tool_env_var(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 env_var in os.environ:
|
||||
raise ToolNotFound("Could not find required tool {} in {} as "
|
||||
"specified via the {} environment variable".
|
||||
format(name, path, env_var))
|
||||
else:
|
||||
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))
|
||||
return path
|
||||
72
amaranth/_toolchain/cxx.py
Normal file
72
amaranth/_toolchain/cxx.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import tempfile
|
||||
import sysconfig
|
||||
import warnings
|
||||
import os.path
|
||||
|
||||
|
||||
__all__ = ["build_cxx"]
|
||||
|
||||
|
||||
def build_cxx(*, cxx_sources, output_name, include_dirs, macros):
|
||||
build_dir = tempfile.TemporaryDirectory(prefix="amaranth_cxx_")
|
||||
|
||||
cwd = os.getcwd()
|
||||
try:
|
||||
# Unforuntately, `ccompiler.compile` assumes the paths are relative, and interprets
|
||||
# the directory name of the source path specially. That makes it necessary to build in
|
||||
# the output directory directly.
|
||||
os.chdir(build_dir.name)
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
|
||||
# This emits a DeprecationWarning on Python 3.6 and 3.10.
|
||||
from setuptools import distutils
|
||||
cc_driver = distutils.ccompiler.new_compiler()
|
||||
|
||||
cc_driver.output_dir = "."
|
||||
|
||||
cc = sysconfig.get_config_var("CC")
|
||||
cxx = sysconfig.get_config_var("CXX")
|
||||
cflags = sysconfig.get_config_var("CCSHARED")
|
||||
ld_flags = sysconfig.get_config_var("LDSHARED")
|
||||
ld_cxxflags = sysconfig.get_config_var("LDCXXSHARED")
|
||||
if ld_cxxflags is None:
|
||||
# PyPy doesn't have LDCXXSHARED. Glue it together from CXX and LDSHARED and hope that
|
||||
# the result actually works; not many good options here.
|
||||
ld_cxxflags = " ".join([cxx.split()[0], *ld_flags.split()[1:]])
|
||||
cc_driver.set_executables(
|
||||
compiler=f"{cc} {cflags}",
|
||||
compiler_so=f"{cc} {cflags}",
|
||||
compiler_cxx=f"{cxx} {cflags}",
|
||||
linker_so=ld_cxxflags,
|
||||
)
|
||||
|
||||
# Sometimes CCompiler is modified to have additional executable entries for compiling and
|
||||
# linking CXX shared objects (e.g. on Gentoo). These executables have to be set then.
|
||||
try:
|
||||
cc_driver.set_executables(
|
||||
compiler_so_cxx=f"{cxx} {cflags}",
|
||||
linker_so_cxx=ld_cxxflags,
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
for include_dir in include_dirs:
|
||||
cc_driver.add_include_dir(include_dir)
|
||||
for macro in macros:
|
||||
cc_driver.define_macro(macro)
|
||||
for cxx_filename, cxx_source in cxx_sources.items():
|
||||
with open(cxx_filename, "w") as f:
|
||||
f.write(cxx_source)
|
||||
|
||||
cxx_filenames = list(cxx_sources.keys())
|
||||
obj_filenames = cc_driver.object_filenames(cxx_filenames)
|
||||
so_filename = cc_driver.shared_object_filename(output_name)
|
||||
|
||||
cc_driver.compile(cxx_filenames)
|
||||
cc_driver.link_shared_object(obj_filenames, output_filename=so_filename, target_lang="c++")
|
||||
|
||||
return build_dir, so_filename
|
||||
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
229
amaranth/_toolchain/yosys.py
Normal file
229
amaranth/_toolchain/yosys.py
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
import os
|
||||
import sys
|
||||
import re
|
||||
import subprocess
|
||||
import warnings
|
||||
import pathlib
|
||||
try:
|
||||
from importlib import metadata as importlib_metadata # py3.8+ stdlib
|
||||
except ImportError:
|
||||
try:
|
||||
import importlib_metadata # py3.7- shim
|
||||
except ImportError:
|
||||
importlib_metadata = None # not installed
|
||||
try:
|
||||
try:
|
||||
from importlib import resources as importlib_resources
|
||||
try:
|
||||
importlib_resources.files # py3.9+ stdlib
|
||||
except AttributeError:
|
||||
import importlib_resources # py3.8- shim
|
||||
except ImportError:
|
||||
import importlib_resources # py3.6- shim
|
||||
except ImportError:
|
||||
importlib_resources = None
|
||||
|
||||
from . import has_tool, require_tool
|
||||
|
||||
|
||||
__all__ = ["YosysError", "YosysBinary", "find_yosys"]
|
||||
|
||||
|
||||
class YosysError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class YosysWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
class YosysBinary:
|
||||
@classmethod
|
||||
def available(cls):
|
||||
"""Check for Yosys availability.
|
||||
|
||||
Returns
|
||||
-------
|
||||
available : bool
|
||||
``True`` if Yosys is installed, ``False`` otherwise. Installed binary may still not
|
||||
be runnable, or might be too old to be useful.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def version(cls):
|
||||
"""Get Yosys version.
|
||||
|
||||
Returns
|
||||
-------
|
||||
``None`` if version number could not be determined, or a 3-tuple ``(major, minor, distance)`` if it could.
|
||||
|
||||
major : int
|
||||
Major version.
|
||||
minor : int
|
||||
Minor version.
|
||||
distance : int
|
||||
Distance to last tag per ``git describe``. May not be exact for system Yosys.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def data_dir(cls):
|
||||
"""Get Yosys data directory.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data_dir : pathlib.Path
|
||||
Yosys data directory (also known as "datdir").
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def run(cls, args, stdin=""):
|
||||
"""Run Yosys process.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : list of str
|
||||
Arguments, not including the program name.
|
||||
stdin : str
|
||||
Standard input.
|
||||
|
||||
Returns
|
||||
-------
|
||||
stdout : str
|
||||
Standard output.
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
YosysError
|
||||
Raised if Yosys returns a non-zero code. The exception message is the standard error
|
||||
output.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def _process_result(cls, returncode, stdout, stderr, ignore_warnings, src_loc_at):
|
||||
if returncode:
|
||||
raise YosysError(stderr.strip())
|
||||
if not ignore_warnings:
|
||||
for match in re.finditer(r"(?ms:^Warning: (.+)\n$)", stderr):
|
||||
message = match.group(1).replace("\n", " ")
|
||||
warnings.warn(message, YosysWarning, stacklevel=3 + src_loc_at)
|
||||
return stdout
|
||||
|
||||
|
||||
class _BuiltinYosys(YosysBinary):
|
||||
YOSYS_PACKAGE = "amaranth_yosys"
|
||||
|
||||
@classmethod
|
||||
def available(cls):
|
||||
if importlib_metadata is None or importlib_resources is None:
|
||||
return False
|
||||
try:
|
||||
importlib_metadata.version(cls.YOSYS_PACKAGE)
|
||||
return True
|
||||
except importlib_metadata.PackageNotFoundError:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def version(cls):
|
||||
version = importlib_metadata.version(cls.YOSYS_PACKAGE)
|
||||
match = re.match(r"^(\d+)\.(\d+)(?:\.post(\d+))?", version)
|
||||
return (int(match[1]), int(match[2]), int(match[3] or 0))
|
||||
|
||||
@classmethod
|
||||
def data_dir(cls):
|
||||
return importlib_resources.files(cls.YOSYS_PACKAGE) / "share"
|
||||
|
||||
@classmethod
|
||||
def run(cls, args, stdin="", *, ignore_warnings=False, src_loc_at=0):
|
||||
popen = subprocess.Popen([sys.executable, "-m", cls.YOSYS_PACKAGE, *args],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
encoding="utf-8")
|
||||
stdout, stderr = popen.communicate(stdin)
|
||||
return cls._process_result(popen.returncode, stdout, stderr, ignore_warnings, src_loc_at)
|
||||
|
||||
|
||||
class _SystemYosys(YosysBinary):
|
||||
YOSYS_BINARY = "yosys"
|
||||
|
||||
@classmethod
|
||||
def available(cls):
|
||||
return has_tool(cls.YOSYS_BINARY)
|
||||
|
||||
@classmethod
|
||||
def version(cls):
|
||||
version = cls.run(["-V"])
|
||||
match = re.match(r"^Yosys (\d+)\.(\d+)(?:\+(\d+))?", version)
|
||||
if match:
|
||||
return (int(match[1]), int(match[2]), int(match[3] or 0))
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def data_dir(cls):
|
||||
popen = subprocess.Popen([require_tool(cls.YOSYS_BINARY) + "-config", "--datdir"],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
encoding="utf-8")
|
||||
stdout, stderr = popen.communicate()
|
||||
if popen.returncode:
|
||||
raise YosysError(stderr.strip())
|
||||
return pathlib.Path(stdout.strip())
|
||||
|
||||
@classmethod
|
||||
def run(cls, args, stdin="", *, ignore_warnings=False, src_loc_at=0):
|
||||
popen = subprocess.Popen([require_tool(cls.YOSYS_BINARY), *args],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
encoding="utf-8")
|
||||
stdout, stderr = popen.communicate(stdin)
|
||||
# If Yosys is built with an evaluation version of Verific, then Verific license
|
||||
# information is printed first. It consists of empty lines and lines starting with `--`,
|
||||
# which are not normally a part of Yosys output, and can be fairly safely removed.
|
||||
#
|
||||
# This is not ideal, but Verific license conditions rule out any other solution.
|
||||
stdout = re.sub(r"\A(-- .+\n|\n)*", "", stdout)
|
||||
return cls._process_result(popen.returncode, stdout, stderr, ignore_warnings, src_loc_at)
|
||||
|
||||
|
||||
def find_yosys(requirement):
|
||||
"""Find an available Yosys executable of required version.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
requirement : function
|
||||
Version check. Should return ``True`` if the version is acceptable, ``False`` otherwise.
|
||||
|
||||
Returns
|
||||
-------
|
||||
yosys_binary : subclass of YosysBinary
|
||||
Proxy for running the requested version of Yosys.
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
YosysError
|
||||
Raised if required Yosys version is not found.
|
||||
"""
|
||||
proxies = []
|
||||
clauses = os.environ.get("AMARANTH_USE_YOSYS", "system,builtin").split(",")
|
||||
for clause in clauses:
|
||||
if clause == "builtin":
|
||||
proxies.append(_BuiltinYosys)
|
||||
elif clause == "system":
|
||||
proxies.append(_SystemYosys)
|
||||
else:
|
||||
raise YosysError("The AMARANTH_USE_YOSYS environment variable contains "
|
||||
"an unrecognized clause {!r}"
|
||||
.format(clause))
|
||||
for proxy in proxies:
|
||||
if proxy.available():
|
||||
version = proxy.version()
|
||||
if version is not None and requirement(version):
|
||||
return proxy
|
||||
else:
|
||||
if "AMARANTH_USE_YOSYS" in os.environ:
|
||||
raise YosysError("Could not find an acceptable Yosys binary. Searched: {}"
|
||||
.format(", ".join(clauses)))
|
||||
else:
|
||||
raise YosysError("Could not find an acceptable Yosys binary. The `amaranth-yosys` PyPI "
|
||||
"package, if available for this platform, can be used as fallback")
|
||||
Loading…
Add table
Add a link
Reference in a new issue