1"""Unit tests for the Verilator cosim backend.
3These tests exercise the command-generation and CMake-template logic of the
4Verilator class *without* requiring the compiled ``esiCppAccel`` C++
5extension. We achieve this by inserting a ``MagicMock`` for the extension
6module before the real package is imported.
12from pathlib
import Path
13from unittest
import mock
14from unittest.mock
import MagicMock
22_accel_mock = MagicMock()
23sys.modules[
"esiaccel.esiCppAccel"] = _accel_mock
26from esiaccel.cosim.verilator
import Verilator
27from esiaccel.cosim.simulator
import SourceFiles
35 """Create a Verilator instance with minimal setup."""
36 sources = SourceFiles(top)
37 if dpi_so
is not None:
38 sources.dpi_so = dpi_so
43 make_default_logs=
False,
44 macro_definitions=macros,
52 cmds = v.compile_commands()
53 assert Path(cmds[0][0]).name ==
"verilator_bin"
54 assert cmds[0][0] == v.verilator_bin
58 cmds = v.compile_commands()
62 assert callable(cmds[1])
63 assert cmds[2][0] ==
"cmake"
64 assert "-G" in cmds[2]
and "Ninja" in cmds[2]
65 assert cmds[3][0] ==
"ninja"
68 """When using cmake, --exe and --build should not appear."""
71 pytest.skip(
"cmake+ninja not available")
72 cmd = v.compile_commands()[0]
73 assert "--exe" not in cmd
74 assert "--build" not in cmd
77 """When using cmake, -CFLAGS and -LDFLAGS should not appear."""
80 pytest.skip(
"cmake+ninja not available")
81 cmd = v.compile_commands()[0]
82 assert "-CFLAGS" not in cmd
83 assert "-LDFLAGS" not in cmd
86 """When using cmake, driver.cpp should not be in the verilator command."""
89 pytest.skip(
"cmake+ninja not available")
90 cmd = v.compile_commands()[0]
91 assert not any(
"driver.cpp" in str(c)
for c
in cmd)
95 cmd = v.compile_commands()[0]
96 assert "--trace-fst" in cmd
97 assert "--trace-structs" in cmd
98 assert "--trace-underscore" in cmd
101 with mock.patch.dict(os.environ,
102 {
"VERILATOR_PATH":
"/custom/verilator_bin"}):
104 assert v.verilator_bin ==
"/custom/verilator_bin"
107 with mock.patch.dict(os.environ, {
"VERILATOR_PATH":
"/usr/bin/verilator"}):
109 assert v.verilator_bin == str(Path(
"/usr/bin/verilator_bin"))
113 cmd = v.compile_commands()[0]
114 assert "+define+FOO=BAR" in cmd
115 assert "+define+BAZ" in cmd
119 """Tests for the make fallback when cmake/ninja are not available."""
122 """Create a Verilator instance that thinks cmake/ninja are missing."""
126 @pytest.fixture(autouse=True)
128 """Patch shutil.which so cmake and ninja appear absent."""
129 original_which = shutil.which
131 def _which_no_cmake(name, *args, **kwargs):
132 if name
in (
"cmake",
"ninja"):
134 return original_which(name, *args, **kwargs)
136 with mock.patch(
"shutil.which", side_effect=_which_no_cmake):
141 cmds = v.compile_commands()
142 assert len(cmds) == 2
143 assert cmds[1][0] ==
"make"
147 cmd = v.compile_commands()[0]
148 assert "--exe" in cmd
152 cmd = v.compile_commands()[0]
153 assert "-CFLAGS" in cmd
154 idx = cmd.index(
"-CFLAGS")
155 assert "-DTOP_MODULE=TestTop" in cmd[idx + 1]
159 cmd = v.compile_commands()[0]
160 assert any(
"driver.cpp" in str(c)
for c
in cmd)
164 cmd = v.compile_commands()[0]
165 assert "-LDFLAGS" in cmd
166 idx = cmd.index(
"-LDFLAGS")
167 assert "-lEsiCosimDpiServer" in cmd[idx + 1]
171 cmd = v.compile_commands()[0]
172 assert "-LDFLAGS" not in cmd
176 cmd = v.compile_commands()[0]
177 idx = cmd.index(
"-CFLAGS")
178 assert "-DTRACE" in cmd[idx + 1]
182 cmds = v.compile_commands()
184 assert make_cmd[0] ==
"make"
185 assert "-C" in make_cmd
186 assert "obj_dir" in make_cmd
187 assert "-f" in make_cmd
188 assert "VMyTop.mk" in make_cmd
192 with mock.patch.object(Path,
"cwd", return_value=tmp_path):
193 cmd = v.run_command(gui=
False)
194 assert cmd == [str(tmp_path /
"obj_dir" /
"VMyTop")]
200 root = tmp_path /
"verilator"
202 (root /
"include").mkdir()
203 (root /
"include" /
"verilated.h").touch()
204 with mock.patch.dict(os.environ, {
"VERILATOR_ROOT": str(root)}):
206 assert v._find_verilator_root() == root
209 root = tmp_path /
"verilator"
210 (root /
"bin").mkdir(parents=
True)
211 (root /
"include").mkdir()
212 (root /
"include" /
"verilated.h").touch()
213 fake_bin = root /
"bin" /
"verilator_bin"
215 fake_bin.chmod(0o755)
216 with mock.patch.dict(os.environ, {}, clear=
False):
217 os.environ.pop(
"VERILATOR_ROOT",
None)
218 with mock.patch(
"shutil.which", return_value=str(fake_bin)):
220 found = v._find_verilator_root()
224 with mock.patch.dict(os.environ, {}, clear=
False):
225 os.environ.pop(
"VERILATOR_ROOT",
None)
226 with mock.patch(
"shutil.which", return_value=
None):
228 with pytest.raises(RuntimeError):
229 v._find_verilator_root()
235 obj_dir = tmp_path /
"obj_dir"
237 generated_sources = [obj_dir /
"VTestTop.cpp"]
238 root = tmp_path /
"verilator"
239 (root /
"include").mkdir(parents=
True)
240 (root /
"include" /
"verilated.h").touch()
241 with mock.patch.dict(os.environ, {
"VERILATOR_ROOT": str(root)}):
243 build_dir = v._write_cmake(obj_dir, generated_sources)
244 assert (build_dir /
"CMakeLists.txt").exists()
245 content = (build_dir /
"CMakeLists.txt").read_text()
246 assert "VTestTop" in content
247 assert str(generated_sources[0])
in content
248 assert "verilated.cpp" in content
249 assert "verilated_threads.cpp" in content
250 assert "driver.cpp" in content
253 obj_dir = tmp_path /
"obj_dir"
255 generated_sources = [obj_dir /
"VTestTop.cpp"]
256 root = tmp_path /
"verilator"
257 (root /
"include").mkdir(parents=
True)
258 (root /
"include" /
"verilated.h").touch()
259 with mock.patch.dict(os.environ, {
"VERILATOR_ROOT": str(root)}):
261 build_dir = v._write_cmake(obj_dir, generated_sources)
262 content = (build_dir /
"CMakeLists.txt").read_text()
263 assert "verilated_fst_c.cpp" in content
264 assert "TRACE" in content
267 obj_dir = tmp_path /
"obj_dir"
269 generated_sources = [obj_dir /
"VTestTop.cpp"]
270 pch_header = obj_dir /
"VTestTop__pch.h"
271 root = tmp_path /
"verilator"
272 (root /
"include").mkdir(parents=
True)
273 (root /
"include" /
"verilated.h").touch()
274 with mock.patch.dict(os.environ, {
"VERILATOR_ROOT": str(root)}):
276 build_dir = v._write_cmake(obj_dir, generated_sources, pch_header)
277 content = (build_dir /
"CMakeLists.txt").read_text()
278 assert "target_precompile_headers(VTestTop PRIVATE" in content
279 assert "VTestTop__pch.h" in content
280 assert "SKIP_PRECOMPILE_HEADERS ON" in content
281 assert "verilated.cpp" in content
282 assert "driver.cpp" in content
290 pytest.skip(
"cmake+ninja not available")
291 with mock.patch.object(Path,
"cwd", return_value=tmp_path):
292 cmd = v.run_command(gui=
False)
293 assert cmd == [str(tmp_path /
"obj_dir" /
"cmake_build" /
"VMyTop")]
test_macro_definitions(self, tmp_path)
test_respects_verilator_path_env(self, tmp_path)
test_driver_not_in_verilator_cmd_cmake(self, tmp_path)
test_trace_flags_in_debug(self, tmp_path)
test_uses_verilator_bin(self, tmp_path)
test_no_cflags_or_ldflags_cmake(self, tmp_path)
test_no_exe_or_build_flags_cmake(self, tmp_path)
test_cmake_and_ninja_commands(self, tmp_path)
test_verilator_path_redirects_perl_wrapper(self, tmp_path)
test_from_env(self, tmp_path)
test_from_bin_in_path(self, tmp_path)
test_raises_when_not_found(self, tmp_path)
test_fallback_has_cflags(self, tmp_path)
test_fallback_exe_path(self, tmp_path)
test_fallback_trace_cflags_in_debug(self, tmp_path)
test_fallback_has_driver(self, tmp_path)
_make_no_cmake(self, tmp_path, **kwargs)
test_fallback_no_ldflags_without_dpi(self, tmp_path)
test_fallback_uses_make(self, tmp_path)
test_fallback_has_exe_flag(self, tmp_path)
test_fallback_make_command(self, tmp_path)
test_fallback_has_ldflags_with_dpi(self, tmp_path)
test_exe_path_cmake(self, tmp_path)
test_generates_cmake(self, tmp_path)
test_trace_sources_in_debug(self, tmp_path)
test_enables_pch_when_generated_header_exists(self, tmp_path)
_make_verilator(run_dir, top="TestTop", debug=False, dpi_so=None, macros=None)