CIRCT 23.0.0git
Loading...
Searching...
No Matches
test_verilator.py
Go to the documentation of this file.
1"""Unit tests for the Verilator cosim backend.
2
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.
7"""
8
9import os
10import shutil
11import sys
12from pathlib import Path
13from unittest import mock
14from unittest.mock import MagicMock
15
16import pytest
17
18# ---------------------------------------------------------------------------
19# Provide a comprehensive mock for the native extension so we can import the
20# pure-Python cosim modules without a full C++ build.
21# ---------------------------------------------------------------------------
22_accel_mock = MagicMock()
23sys.modules["esiaccel.esiCppAccel"] = _accel_mock
24
25# Now we can safely import the cosim modules.
26from esiaccel.cosim.verilator import Verilator # noqa: E402
27from esiaccel.cosim.simulator import SourceFiles # noqa: E402
28
29
30def _make_verilator(run_dir,
31 top="TestTop",
32 debug=False,
33 dpi_so=None,
34 macros=None):
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
39 return Verilator(
40 sources=sources,
41 run_dir=run_dir,
42 debug=debug,
43 make_default_logs=False,
44 macro_definitions=macros,
45 )
46
47
49
50 def test_uses_verilator_bin(self, tmp_path):
51 v = _make_verilator(tmp_path)
52 cmds = v.compile_commands()
53 assert Path(cmds[0][0]).name == "verilator_bin"
54 assert cmds[0][0] == v.verilator_bin
55
56 def test_cmake_and_ninja_commands(self, tmp_path):
57 v = _make_verilator(tmp_path)
58 cmds = v.compile_commands()
59 # cmake+ninja present => 4 steps, including a Python callback.
60 if v._use_cmake:
61 assert len(cmds) == 4
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"
66
68 """When using cmake, --exe and --build should not appear."""
69 v = _make_verilator(tmp_path)
70 if not v._use_cmake:
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
75
77 """When using cmake, -CFLAGS and -LDFLAGS should not appear."""
78 v = _make_verilator(tmp_path)
79 if not v._use_cmake:
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
84
86 """When using cmake, driver.cpp should not be in the verilator command."""
87 v = _make_verilator(tmp_path)
88 if not v._use_cmake:
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)
92
93 def test_trace_flags_in_debug(self, tmp_path):
94 v = _make_verilator(tmp_path, debug=True)
95 cmd = v.compile_commands()[0]
96 assert "--trace-fst" in cmd
97 assert "--trace-structs" in cmd
98 assert "--trace-underscore" in cmd
99
101 with mock.patch.dict(os.environ,
102 {"VERILATOR_PATH": "/custom/verilator_bin"}):
103 v = _make_verilator(tmp_path)
104 assert v.verilator_bin == "/custom/verilator_bin"
105
107 with mock.patch.dict(os.environ, {"VERILATOR_PATH": "/usr/bin/verilator"}):
108 v = _make_verilator(tmp_path)
109 assert v.verilator_bin == str(Path("/usr/bin/verilator_bin"))
110
111 def test_macro_definitions(self, tmp_path):
112 v = _make_verilator(tmp_path, macros={"FOO": "BAR", "BAZ": None})
113 cmd = v.compile_commands()[0]
114 assert "+define+FOO=BAR" in cmd
115 assert "+define+BAZ" in cmd
116
117
119 """Tests for the make fallback when cmake/ninja are not available."""
120
121 def _make_no_cmake(self, tmp_path, **kwargs):
122 """Create a Verilator instance that thinks cmake/ninja are missing."""
123 v = _make_verilator(tmp_path, **kwargs)
124 return v
125
126 @pytest.fixture(autouse=True)
127 def _hide_cmake(self):
128 """Patch shutil.which so cmake and ninja appear absent."""
129 original_which = shutil.which
130
131 def _which_no_cmake(name, *args, **kwargs):
132 if name in ("cmake", "ninja"):
133 return None
134 return original_which(name, *args, **kwargs)
135
136 with mock.patch("shutil.which", side_effect=_which_no_cmake):
137 yield
138
139 def test_fallback_uses_make(self, tmp_path):
140 v = self._make_no_cmake(tmp_path)
141 cmds = v.compile_commands()
142 assert len(cmds) == 2
143 assert cmds[1][0] == "make"
144
145 def test_fallback_has_exe_flag(self, tmp_path):
146 v = self._make_no_cmake(tmp_path)
147 cmd = v.compile_commands()[0]
148 assert "--exe" in cmd
149
150 def test_fallback_has_cflags(self, tmp_path):
151 v = self._make_no_cmake(tmp_path)
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]
156
157 def test_fallback_has_driver(self, tmp_path):
158 v = self._make_no_cmake(tmp_path)
159 cmd = v.compile_commands()[0]
160 assert any("driver.cpp" in str(c) for c in cmd)
161
163 v = self._make_no_cmake(tmp_path, dpi_so=["EsiCosimDpiServer"])
164 cmd = v.compile_commands()[0]
165 assert "-LDFLAGS" in cmd
166 idx = cmd.index("-LDFLAGS")
167 assert "-lEsiCosimDpiServer" in cmd[idx + 1]
168
170 v = self._make_no_cmake(tmp_path, dpi_so=[])
171 cmd = v.compile_commands()[0]
172 assert "-LDFLAGS" not in cmd
173
175 v = self._make_no_cmake(tmp_path, debug=True)
176 cmd = v.compile_commands()[0]
177 idx = cmd.index("-CFLAGS")
178 assert "-DTRACE" in cmd[idx + 1]
179
180 def test_fallback_make_command(self, tmp_path):
181 v = self._make_no_cmake(tmp_path, top="MyTop")
182 cmds = v.compile_commands()
183 make_cmd = cmds[1]
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
189
190 def test_fallback_exe_path(self, tmp_path):
191 v = self._make_no_cmake(tmp_path, top="MyTop")
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")]
195
196
198
199 def test_from_env(self, tmp_path):
200 root = tmp_path / "verilator"
201 root.mkdir()
202 (root / "include").mkdir()
203 (root / "include" / "verilated.h").touch()
204 with mock.patch.dict(os.environ, {"VERILATOR_ROOT": str(root)}):
205 v = _make_verilator(tmp_path)
206 assert v._find_verilator_root() == root
207
208 def test_from_bin_in_path(self, tmp_path):
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"
214 fake_bin.touch()
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)):
219 v = _make_verilator(tmp_path)
220 found = v._find_verilator_root()
221 assert found == root
222
223 def test_raises_when_not_found(self, tmp_path):
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):
227 v = _make_verilator(tmp_path)
228 with pytest.raises(RuntimeError):
229 v._find_verilator_root()
230
231
233
234 def test_generates_cmake(self, tmp_path):
235 obj_dir = tmp_path / "obj_dir"
236 obj_dir.mkdir()
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)}):
242 v = _make_verilator(tmp_path, dpi_so=[])
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
251
252 def test_trace_sources_in_debug(self, tmp_path):
253 obj_dir = tmp_path / "obj_dir"
254 obj_dir.mkdir()
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)}):
260 v = _make_verilator(tmp_path, debug=True, dpi_so=[])
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
265
267 obj_dir = tmp_path / "obj_dir"
268 obj_dir.mkdir()
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)}):
275 v = _make_verilator(tmp_path, dpi_so=[])
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
283
284
286
287 def test_exe_path_cmake(self, tmp_path):
288 v = _make_verilator(tmp_path, top="MyTop")
289 if not v._use_cmake:
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_respects_verilator_path_env(self, tmp_path)
test_driver_not_in_verilator_cmd_cmake(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_fallback_trace_cflags_in_debug(self, tmp_path)
_make_no_cmake(self, tmp_path, **kwargs)
test_fallback_no_ldflags_without_dpi(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_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)