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 cmds[0][0] == "verilator_bin"
54
55 def test_cmake_and_ninja_commands(self, tmp_path):
56 v = _make_verilator(tmp_path)
57 cmds = v.compile_commands()
58 # cmake+ninja present => 3 commands
59 if v._use_cmake:
60 assert len(cmds) == 3
61 assert cmds[1][0] == "cmake"
62 assert "-G" in cmds[1] and "Ninja" in cmds[1]
63 assert cmds[2][0] == "ninja"
64
66 """When using cmake, --exe and --build should not appear."""
67 v = _make_verilator(tmp_path)
68 if not v._use_cmake:
69 pytest.skip("cmake+ninja not available")
70 cmd = v.compile_commands()[0]
71 assert "--exe" not in cmd
72 assert "--build" not in cmd
73
75 """When using cmake, -CFLAGS and -LDFLAGS should not appear."""
76 v = _make_verilator(tmp_path)
77 if not v._use_cmake:
78 pytest.skip("cmake+ninja not available")
79 cmd = v.compile_commands()[0]
80 assert "-CFLAGS" not in cmd
81 assert "-LDFLAGS" not in cmd
82
84 """When using cmake, driver.cpp should not be in the verilator command."""
85 v = _make_verilator(tmp_path)
86 if not v._use_cmake:
87 pytest.skip("cmake+ninja not available")
88 cmd = v.compile_commands()[0]
89 assert not any("driver.cpp" in str(c) for c in cmd)
90
91 def test_trace_flags_in_debug(self, tmp_path):
92 v = _make_verilator(tmp_path, debug=True)
93 cmd = v.compile_commands()[0]
94 assert "--trace-fst" in cmd
95 assert "--trace-params" in cmd
96
98 with mock.patch.dict(os.environ,
99 {"VERILATOR_PATH": "/custom/verilator_bin"}):
100 v = _make_verilator(tmp_path)
101 assert v.verilator_bin == "/custom/verilator_bin"
102
104 with mock.patch.dict(os.environ, {"VERILATOR_PATH": "/usr/bin/verilator"}):
105 v = _make_verilator(tmp_path)
106 assert v.verilator_bin == str(Path("/usr/bin/verilator_bin"))
107
108 def test_macro_definitions(self, tmp_path):
109 v = _make_verilator(tmp_path, macros={"FOO": "BAR", "BAZ": None})
110 cmd = v.compile_commands()[0]
111 assert "+define+FOO=BAR" in cmd
112 assert "+define+BAZ" in cmd
113
114
116 """Tests for the make fallback when cmake/ninja are not available."""
117
118 def _make_no_cmake(self, tmp_path, **kwargs):
119 """Create a Verilator instance that thinks cmake/ninja are missing."""
120 v = _make_verilator(tmp_path, **kwargs)
121 return v
122
123 @pytest.fixture(autouse=True)
124 def _hide_cmake(self):
125 """Patch shutil.which so cmake and ninja appear absent."""
126 original_which = shutil.which
127
128 def _which_no_cmake(name, *args, **kwargs):
129 if name in ("cmake", "ninja"):
130 return None
131 return original_which(name, *args, **kwargs)
132
133 with mock.patch("shutil.which", side_effect=_which_no_cmake):
134 yield
135
136 def test_fallback_uses_make(self, tmp_path):
137 v = self._make_no_cmake(tmp_path)
138 cmds = v.compile_commands()
139 assert len(cmds) == 2
140 assert cmds[1][0] == "make"
141
142 def test_fallback_has_exe_flag(self, tmp_path):
143 v = self._make_no_cmake(tmp_path)
144 cmd = v.compile_commands()[0]
145 assert "--exe" in cmd
146
147 def test_fallback_has_cflags(self, tmp_path):
148 v = self._make_no_cmake(tmp_path)
149 cmd = v.compile_commands()[0]
150 assert "-CFLAGS" in cmd
151 idx = cmd.index("-CFLAGS")
152 assert "-DTOP_MODULE=TestTop" in cmd[idx + 1]
153
154 def test_fallback_has_driver(self, tmp_path):
155 v = self._make_no_cmake(tmp_path)
156 cmd = v.compile_commands()[0]
157 assert any("driver.cpp" in str(c) for c in cmd)
158
160 v = self._make_no_cmake(tmp_path, dpi_so=["EsiCosimDpiServer"])
161 cmd = v.compile_commands()[0]
162 assert "-LDFLAGS" in cmd
163 idx = cmd.index("-LDFLAGS")
164 assert "-lEsiCosimDpiServer" in cmd[idx + 1]
165
167 v = self._make_no_cmake(tmp_path, dpi_so=[])
168 cmd = v.compile_commands()[0]
169 assert "-LDFLAGS" not in cmd
170
172 v = self._make_no_cmake(tmp_path, debug=True)
173 cmd = v.compile_commands()[0]
174 idx = cmd.index("-CFLAGS")
175 assert "-DTRACE" in cmd[idx + 1]
176
177 def test_fallback_make_command(self, tmp_path):
178 v = self._make_no_cmake(tmp_path, top="MyTop")
179 cmds = v.compile_commands()
180 make_cmd = cmds[1]
181 assert make_cmd[0] == "make"
182 assert "-C" in make_cmd
183 assert "obj_dir" in make_cmd
184 assert "-f" in make_cmd
185 assert "VMyTop.mk" in make_cmd
186
187 def test_fallback_exe_path(self, tmp_path):
188 v = self._make_no_cmake(tmp_path, top="MyTop")
189 with mock.patch.object(Path, "cwd", return_value=tmp_path):
190 cmd = v.run_command(gui=False)
191 assert cmd == [str(tmp_path / "obj_dir" / "VMyTop")]
192
193
195
196 def test_from_env(self, tmp_path):
197 root = tmp_path / "verilator"
198 root.mkdir()
199 (root / "include").mkdir()
200 (root / "include" / "verilated.h").touch()
201 with mock.patch.dict(os.environ, {"VERILATOR_ROOT": str(root)}):
202 v = _make_verilator(tmp_path)
203 assert v._find_verilator_root() == root
204
205 def test_from_bin_in_path(self, tmp_path):
206 root = tmp_path / "verilator"
207 (root / "bin").mkdir(parents=True)
208 (root / "include").mkdir()
209 (root / "include" / "verilated.h").touch()
210 fake_bin = root / "bin" / "verilator_bin"
211 fake_bin.touch()
212 fake_bin.chmod(0o755)
213 with mock.patch.dict(os.environ, {}, clear=False):
214 os.environ.pop("VERILATOR_ROOT", None)
215 with mock.patch("shutil.which", return_value=str(fake_bin)):
216 v = _make_verilator(tmp_path)
217 found = v._find_verilator_root()
218 assert found == root
219
220 def test_raises_when_not_found(self, tmp_path):
221 with mock.patch.dict(os.environ, {}, clear=False):
222 os.environ.pop("VERILATOR_ROOT", None)
223 with mock.patch("shutil.which", return_value=None):
224 v = _make_verilator(tmp_path)
225 with pytest.raises(RuntimeError):
226 v._find_verilator_root()
227
228
230
231 def test_generates_cmake(self, tmp_path):
232 obj_dir = tmp_path / "obj_dir"
233 obj_dir.mkdir()
234 root = tmp_path / "verilator"
235 (root / "include").mkdir(parents=True)
236 (root / "include" / "verilated.h").touch()
237 with mock.patch.dict(os.environ, {"VERILATOR_ROOT": str(root)}):
238 v = _make_verilator(tmp_path, dpi_so=[])
239 build_dir = v._write_cmake(obj_dir)
240 assert (build_dir / "CMakeLists.txt").exists()
241 content = (build_dir / "CMakeLists.txt").read_text()
242 assert "VTestTop" in content
243 assert "verilated.cpp" in content
244 assert "verilated_threads.cpp" in content
245 assert "driver.cpp" in content
246
247 def test_trace_sources_in_debug(self, tmp_path):
248 obj_dir = tmp_path / "obj_dir"
249 obj_dir.mkdir()
250 root = tmp_path / "verilator"
251 (root / "include").mkdir(parents=True)
252 (root / "include" / "verilated.h").touch()
253 with mock.patch.dict(os.environ, {"VERILATOR_ROOT": str(root)}):
254 v = _make_verilator(tmp_path, debug=True, dpi_so=[])
255 build_dir = v._write_cmake(obj_dir)
256 content = (build_dir / "CMakeLists.txt").read_text()
257 assert "verilated_fst_c.cpp" in content
258 assert "TRACE" in content
259
260
262
263 def test_exe_path_cmake(self, tmp_path):
264 v = _make_verilator(tmp_path, top="MyTop")
265 if not v._use_cmake:
266 pytest.skip("cmake+ninja not available")
267 with mock.patch.object(Path, "cwd", return_value=tmp_path):
268 cmd = v.run_command(gui=False)
269 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)
_make_verilator(run_dir, top="TestTop", debug=False, dpi_so=None, macros=None)