Coverage for tsfpga/yosys/common.py: 96%
55 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-09 00:40 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-09 00:40 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project, a project platform for modern FPGA development.
5# https://tsfpga.com
6# https://github.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9from __future__ import annotations
11import os
12import re
13import subprocess
14from pathlib import Path
15from shutil import which
17from vunit.ostools import Process
20def run_ghdl(ghdl_path: Path | None, arguments: list[str], cwd: Path) -> bool:
21 """
22 Run GHDL with the given arguments.
23 Used for analyzing VHDL source files into an on-disk library, which can later be picked up
24 by the ``ghdl-yosys-plugin`` when running :func:`run_yosys`.
26 Setting ``cwd`` ensures that any files produced (e.g. GHDL library files) end up in a
27 well-known location.
29 Arguments:
30 ghdl_path: Path to the GHDL executable. Can be set to ``None``
31 to use whatever version is in ``PATH``.
32 arguments: Arguments that shall be passed on to GHDL.
33 cwd: The GHDL process will be executed with this as the working directory.
35 Return:
36 True if everything went well.
37 """
38 cmd = [str(get_ghdl_path(ghdl_path)), *arguments]
40 try:
41 Process(args=cmd, cwd=cwd).consume_output()
42 except Process.NonZeroExitCode:
43 return False
44 return True
47def run_yosys(
48 script_file: Path,
49 cwd: Path,
50 yosys_path: Path | None = None,
51 ghdl_plugin_path: Path | None = None,
52 ghdl_path: Path | None = None,
53 ghdl_prefix: Path | None = None,
54) -> bool:
55 """
56 Run Yosys with the given script.
58 Setting ``cwd`` ensures that any files produced (e.g. reports, netlists) end up in a
59 well-known location.
61 Arguments:
62 script_file: Path to a file containing the Yosys commands that shall be executed.
63 cwd: The Yosys process will be executed with this as the working directory.
64 yosys_path: Path to the Yosys executable. Can be set to ``None``
65 to use whatever version is in ``PATH``.
66 ghdl_plugin_path: Path to the ``ghdl-yosys-plugin`` module (typically named ``ghdl.so``).
67 Can be set to ``None`` if the plugin is already available to Yosys without explicitly
68 loading it (e.g. if it has been installed in the Yosys plugin directory).
69 ghdl_path: Path to the GHDL executable, used only to auto-detect 'ghdl_prefix' below
70 when it is not given explicitly. Can be set to ``None`` to use whatever version is
71 in ``PATH``.
72 ghdl_prefix: Value to set the ``GHDL_PREFIX`` environment variable to for this process.
73 The ``ghdl-yosys-plugin`` module is loaded as part of the Yosys process, and hence
74 can not find the GHDL standard libraries (``std``, ``ieee``, ...) on its own the
75 way the standalone ``ghdl`` executable can.
76 Corresponds to the "library prefix" printed by ``ghdl --disp-config``.
77 If left as ``None``, this method will try to find a value automatically by calling
78 ``ghdl --disp-config`` (see 'ghdl_path' above) and parsing its output.
79 Set explicitly to override the auto-detected value, or if auto-detection fails.
81 Return:
82 True if everything went well.
83 """
84 cmd = [str(get_yosys_path(yosys_path))]
86 if ghdl_plugin_path is not None:
87 cmd += ["-m", str(ghdl_plugin_path.resolve())]
89 cmd += ["-s", str(script_file.resolve())]
91 resolved_ghdl_prefix = (
92 ghdl_prefix if ghdl_prefix is not None else get_ghdl_library_prefix(ghdl_path)
93 )
95 env = None
96 if resolved_ghdl_prefix is not None:
97 env = dict(os.environ)
98 env["GHDL_PREFIX"] = str(resolved_ghdl_prefix.resolve())
100 try:
101 Process(args=cmd, cwd=cwd, env=env).consume_output()
102 except Process.NonZeroExitCode:
103 return False
104 return True
107def get_ghdl_path(ghdl_path: Path | None = None) -> Path:
108 """
109 Wrapper to get a path to the GHDL executable.
111 Arguments:
112 ghdl_path: Path to the GHDL executable.
113 Leave as ``None`` to use whatever is available in the system ``PATH``.
114 """
115 if ghdl_path is not None:
116 return ghdl_path.resolve()
118 which_ghdl = which("ghdl")
119 if which_ghdl is None:
120 raise FileNotFoundError("Could not find ghdl on PATH")
122 return Path(which_ghdl).resolve()
125def get_ghdl_library_prefix(ghdl_path: Path | None = None) -> Path | None:
126 """
127 Try to automatically find the GHDL "library prefix", i.e. the location where the
128 ``std``/``ieee`` standard libraries are installed, by parsing the output of
129 ``ghdl --disp-config``.
131 GHDL can normally find this on its own, by looking at the path of its own executable.
132 This does however not work when GHDL is used as a library, e.g. via the
133 ``ghdl-yosys-plugin`` module loaded into Yosys (see :func:`run_yosys`), since there is no
134 ``ghdl`` executable path to derive the prefix from in that case.
135 Calling this function against the standalone ``ghdl`` executable lets us find the prefix
136 anyway, so it can be forwarded explicitly.
138 Arguments:
139 ghdl_path: Path to the GHDL executable. Can be set to ``None``
140 to use whatever version is in ``PATH``.
142 Return:
143 The library prefix, or ``None`` if it could not be determined.
144 """
145 try:
146 cmd = [str(get_ghdl_path(ghdl_path)), "--disp-config"]
147 result = subprocess.run(cmd, capture_output=True, text=True, check=True) # noqa: S603
148 except (OSError, subprocess.CalledProcessError):
149 return None
151 match = re.search(r"^library prefix:\s*(.+)$", result.stdout, flags=re.MULTILINE)
152 if match is None:
153 return None
155 return Path(match.group(1).strip())
158def to_yosys_path(path: Path) -> str:
159 """
160 Return a path string in a format suitable to embed in a Yosys command script.
161 """
162 return path.resolve().as_posix()
165def get_yosys_path(yosys_path: Path | None = None) -> Path:
166 """
167 Wrapper to get a path to the Yosys executable.
169 Arguments:
170 yosys_path: Path to the Yosys executable.
171 Leave as ``None`` to use whatever is available in the system ``PATH``.
172 """
173 if yosys_path is not None:
174 return yosys_path.resolve()
176 which_yosys = which("yosys")
177 if which_yosys is None:
178 raise FileNotFoundError("Could not find yosys on PATH")
180 return Path(which_yosys).resolve()