8 from importlib
import metadata
as importlib_metadata
# py3.8+ stdlib
11 import importlib_metadata
# py3.7- shim
13 importlib_metadata
= None # not installed
15 from importlib
import resources
as importlib_resources
17 importlib_resources
.files
# py3.9+ stdlib
18 except AttributeError:
19 import importlib_resources
# py3.8- shim
21 import importlib_resources
# py3.6- shim
23 from ._toolchain
import has_tool
, require_tool
26 __all__
= ["YosysError", "YosysBinary", "find_yosys"]
29 class YosysError(Exception):
33 class YosysWarning(Warning):
40 """Check for Yosys availability.
45 ``True`` if Yosys is installed, ``False`` otherwise. Installed binary may still not
46 be runnable, or might be too old to be useful.
48 raise NotImplementedError
56 ``None`` if version number could not be determined, or a 3-tuple ``(major, minor, distance)`` if it could.
63 Distance to last tag per ``git describe``. May not be exact for system Yosys.
65 raise NotImplementedError
69 """Get Yosys data directory.
73 data_dir : pathlib.Path
74 Yosys data directory (also known as "datdir").
76 raise NotImplementedError
79 def run(cls
, args
, stdin
=""):
85 Arguments, not including the program name.
97 Raised if Yosys returns a non-zero code. The exception message is the standard error
100 raise NotImplementedError
103 def _process_result(cls
, returncode
, stdout
, stderr
, ignore_warnings
, src_loc_at
):
105 raise YosysError(stderr
.strip())
106 if not ignore_warnings
:
107 for match
in re
.finditer(r
"(?ms:^Warning: (.+)\n$)", stderr
):
108 message
= match
.group(1).replace("\n", " ")
109 warnings
.warn(message
, YosysWarning
, stacklevel
=3 + src_loc_at
)
113 class _BuiltinYosys(YosysBinary
):
114 YOSYS_PACKAGE
= "nmigen_yosys"
118 if importlib_metadata
is None:
121 importlib_metadata
.version(cls
.YOSYS_PACKAGE
)
123 except importlib_metadata
.PackageNotFoundError
:
128 version
= importlib_metadata
.version(cls
.YOSYS_PACKAGE
)
129 match
= re
.match(r
"^(\d+)\.(\d+)(?:\.post(\d+))?", version
)
130 return (int(match
[1]), int(match
[2]), int(match
[3] or 0))
134 return importlib_resources
.files(cls
.YOSYS_PACKAGE
) / "share"
137 def run(cls
, args
, stdin
="", *, ignore_warnings
=False, src_loc_at
=0):
138 popen
= subprocess
.Popen([sys
.executable
, "-m", cls
.YOSYS_PACKAGE
, *args
],
139 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
141 stdout
, stderr
= popen
.communicate(stdin
)
142 return cls
._process
_result
(popen
.returncode
, stdout
, stderr
, ignore_warnings
, src_loc_at
)
145 class _SystemYosys(YosysBinary
):
146 YOSYS_BINARY
= "yosys"
150 return has_tool(cls
.YOSYS_BINARY
)
154 version
= cls
.run(["-V"])
155 match
= re
.match(r
"^Yosys (\d+)\.(\d+)(?:\+(\d+))?", version
)
157 return (int(match
[1]), int(match
[2]), int(match
[3] or 0))
163 popen
= subprocess
.Popen([require_tool(cls
.YOSYS_BINARY
) + "-config", "--datdir"],
164 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
166 stdout
, stderr
= popen
.communicate()
168 raise YosysError(stderr
.strip())
169 return pathlib
.Path(stdout
.strip())
172 def run(cls
, args
, stdin
="", *, ignore_warnings
=False, src_loc_at
=0):
173 popen
= subprocess
.Popen([require_tool(cls
.YOSYS_BINARY
), *args
],
174 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
176 stdout
, stderr
= popen
.communicate(stdin
)
177 # If Yosys is built with an evaluation version of Verific, then Verific license
178 # information is printed first. It consists of empty lines and lines starting with `--`,
179 # which are not normally a part of Yosys output, and can be fairly safely removed.
181 # This is not ideal, but Verific license conditions rule out any other solution.
182 stdout
= re
.sub(r
"\A(-- .+\n|\n)*", "", stdout
)
183 return cls
._process
_result
(popen
.returncode
, stdout
, stderr
, ignore_warnings
, src_loc_at
)
186 def find_yosys(requirement
):
187 """Find an available Yosys executable of required version.
191 requirement : function
192 Version check. Should return ``True`` if the version is acceptable, ``False`` otherwise.
196 yosys_binary : subclass of YosysBinary
197 Proxy for running the requested version of Yosys.
202 Raised if required Yosys version is not found.
205 clauses
= os
.environ
.get("NMIGEN_USE_YOSYS", "system,builtin").split(",")
206 for clause
in clauses
:
207 if clause
== "builtin":
208 proxies
.append(_BuiltinYosys
)
209 elif clause
== "system":
210 proxies
.append(_SystemYosys
)
212 raise YosysError("The NMIGEN_USE_YOSYS environment variable contains "
213 "an unrecognized clause {!r}"
215 for proxy
in proxies
:
216 if proxy
.available():
217 version
= proxy
.version()
218 if version
is not None and requirement(version
):
221 if "NMIGEN_USE_YOSYS" in os
.environ
:
222 raise YosysError("Could not find an acceptable Yosys binary. Searched: {}"
223 .format(", ".join(clauses
)))
225 raise YosysError("Could not find an acceptable Yosys binary. The `nmigen-yosys` PyPI "
226 "package, if available for this platform, can be used as fallback")