CIRCT 23.0.0git
Loading...
Searching...
No Matches
verilator.py
Go to the documentation of this file.
1# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2# See https://llvm.org/LICENSE.txt for license information.
3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5import os
6import re
7import shutil
8from pathlib import Path
9from typing import List, Optional, Callable, Dict
10
11from .simulator import CosimCollateralDir, Simulator, SourceFiles
12
13
15 """Run and compile funcs for Verilator.
16
17 Calls ``verilator_bin`` directly (bypassing the Perl wrapper) to generate
18 C++ from RTL, then builds the simulation executable with CMake + Ninja.
19 Falls back to ``make`` when cmake/ninja are not available."""
20
21 DefaultDriver = CosimCollateralDir / "driver.cpp"
22 VerilatorBinNotFound = (
23 "Cannot find verilator_bin. Set VERILATOR_PATH to an absolute path "
24 "or ensure verilator_bin is in PATH.")
25 VerilatorRootNotFound = (
26 "Cannot find VERILATOR_ROOT. Set the VERILATOR_ROOT environment "
27 "variable or ensure verilator_bin is in PATH.")
28 VerilatorPathInvalid = (
29 "VERILATOR_PATH does not point to a valid verilator_bin executable.")
30 VerilatorRootInvalid = (
31 "VERILATOR_ROOT does not point to a Verilator root containing "
32 "include/verilated.h.")
33
35 self,
36 sources: SourceFiles,
37 run_dir: Path,
38 debug: bool,
39 save_waveform: bool = False,
40 run_stdout_callback: Optional[Callable[[str], None]] = None,
41 run_stderr_callback: Optional[Callable[[str], None]] = None,
42 compile_stdout_callback: Optional[Callable[[str], None]] = None,
43 compile_stderr_callback: Optional[Callable[[str], None]] = None,
44 make_default_logs: bool = True,
45 macro_definitions: Optional[Dict[str, str]] = None,
46 ):
47 super().__init__(
48 sources=sources,
49 run_dir=run_dir,
50 debug=debug,
51 save_waveform=save_waveform,
52 run_stdout_callback=run_stdout_callback,
53 run_stderr_callback=run_stderr_callback,
54 compile_stdout_callback=compile_stdout_callback,
55 compile_stderr_callback=compile_stderr_callback,
56 make_default_logs=make_default_logs,
57 macro_definitions=macro_definitions,
58 )
59
60 @property
61 def verilator_bin(self) -> Path:
62 vpath = Verilator._find_verilator_bin()
63 if vpath is None:
64 raise RuntimeError(Verilator.VerilatorBinNotFound)
65 return vpath
66
67 @staticmethod
68 def _find_verilator_bin() -> Optional[Path]:
69 """Locate the ``verilator_bin`` executable.
70
71 When ``VERILATOR_PATH`` is set it must point to a valid executable;
72 otherwise a ``RuntimeError`` is raised. Without it, ``verilator_bin`` is
73 looked up on ``PATH``. Returns ``None`` when nothing is found."""
74
75 def check_path(path: Path | str | None) -> Optional[Path]:
76 if isinstance(path, str):
77 path = Path(path)
78 if path is not None and path.exists() and path.is_file():
79 return path.resolve()
80 return None
81
82 if "VERILATOR_PATH" in os.environ:
83 vpath = Path(os.environ["VERILATOR_PATH"])
84 if vpath.stem == "verilator":
85 vpath = vpath.parent / "verilator_bin"
86 checked = check_path(vpath)
87 if checked is None:
88 raise RuntimeError(Verilator.VerilatorPathInvalid)
89 return checked
90 return check_path(shutil.which("verilator_bin"))
91
92 @staticmethod
93 def _find_verilator_root() -> Optional[Path]:
94 """Locate the Verilator root containing ``include/verilated.h``.
95
96 When ``VERILATOR_ROOT`` is set it must contain ``include/verilated.h``;
97 otherwise a ``RuntimeError`` is raised. Without it, the packaged root
98 (``$PREFIX/share/verilator``) is derived from the ``verilator_bin``
99 location. Returns ``None`` when nothing is found."""
100 if "VERILATOR_ROOT" in os.environ:
101 root = Path(os.environ["VERILATOR_ROOT"])
102 if (root / "include" / "verilated.h").exists():
103 return root
104 raise RuntimeError(Verilator.VerilatorRootInvalid)
105
106 verilator_bin = Verilator._find_verilator_bin()
107 if verilator_bin is None:
108 return None
109
110 # Packaged installations put Verilator's support files under
111 # $PREFIX/share/verilator, where $PREFIX is the bin directory's parent.
112 pkg_root = verilator_bin.parent.parent / "share" / "verilator"
113 if (pkg_root / "include" / "verilated.h").exists():
114 return pkg_root
115
116 return None
117
118 @property
119 def _use_cmake(self) -> bool:
120 """True when both cmake and ninja are available on PATH."""
121 return shutil.which("cmake") is not None and \
122 shutil.which("ninja") is not None
123
124 def compile_commands(self) -> List[Simulator.CompileStep]:
125 """Return the compile steps for the full compile flow.
126
127 When cmake and ninja are available the returned list contains four
128 sequential steps:
129 1. ``verilator_bin`` – generates C++ from RTL.
130 2. Python callback – generates the CMakeLists.txt from the depfile.
131 3. ``cmake`` – configures the C++ build (Ninja generator).
132 4. ``ninja`` – builds the simulation executable.
133
134 Otherwise falls back to two commands:
135 1. ``verilator_bin --exe`` – generates C++ and a Makefile.
136 2. ``make`` – builds via the generated Makefile.
137 """
138 verilator_bin = self._find_verilator_bin()
139 if verilator_bin is None:
140 raise RuntimeError(Verilator.VerilatorBinNotFound)
141 verilator_root = self._find_verilator_root()
142 if verilator_root is None:
143 raise RuntimeError(Verilator.VerilatorRootNotFound)
144 os.environ["VERILATOR_ROOT"] = str(verilator_root)
145
146 verilator_cmd: List[str] = [
147 str(verilator_bin),
148 "--cc",
149 ]
150
151 if self.macro_definitions:
152 verilator_cmd += [
153 f"+define+{k}={v}" if v is not None else f"+define+{k}"
154 for k, v in self.macro_definitions.items()
155 ]
156
157 verilator_cmd += [
158 "--top-module",
159 self.sources.top,
160 "-DSIMULATION",
161 "-Wno-TIMESCALEMOD",
162 "-Wno-fatal",
163 "-sv",
164 "--verilate-jobs",
165 "0",
166 "--output-split",
167 "2500",
168 ]
169 if self.debug:
170 verilator_cmd += [
171 "--assert",
172 "--trace-fst",
173 "--trace-structs",
174 "--trace-underscore",
175 ]
176
177 if self._use_cmake:
178 verilator_cmd += [str(p) for p in self.sources.rtl_sources]
179 build_dir = str(Path.cwd() / "obj_dir" / "cmake_build")
180 # ``CMAKE_BUILD_TYPE=Release`` is important on Windows: the prebuilt
181 # ``EsiCosimDpiServer.dll`` ships with the Release MSVC runtime, and
182 # mixing it with a Debug-runtime executable causes silent failures
183 # (e.g. transport/control connections come up but requests stall).
184 cmake_cmd = [
185 "cmake", "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release", "-S", build_dir,
186 "-B", build_dir
187 ]
188 # If vcpkg is available, use its toolchain file so that
189 # ``find_package(ZLIB)`` (and other transitive deps) can pick up vcpkg
190 # installations. This is the standard story on Windows.
191 vcpkg_root = os.environ.get("VCPKG_ROOT") or os.environ.get(
192 "VCPKG_INSTALLATION_ROOT")
193 if vcpkg_root:
194 toolchain = Path(
195 vcpkg_root) / "scripts" / "buildsystems" / "vcpkg.cmake"
196 if toolchain.exists():
197 cmake_cmd.append(f"-DCMAKE_TOOLCHAIN_FILE={toolchain}")
198 ninja_cmd = ["ninja", "-C", build_dir]
199 return [
200 verilator_cmd, self._write_cmake_from_depfile_write_cmake_from_depfile, cmake_cmd, ninja_cmd
201 ]
202
203 # -- make fallback --
204 # Let verilator generate a Makefile with --exe so it includes the
205 # driver, CFLAGS, and LDFLAGS directly.
206 verilator_cmd += ["--exe", str(Verilator.DefaultDriver)]
207 cflags = ["-DTOP_MODULE=" + self.sources.top]
208 if self.debug:
209 cflags.append("-DTRACE")
210 verilator_cmd += ["-CFLAGS", " ".join(cflags)]
211 if self.sources.dpi_so:
212 dpi_so_paths = self.sources.dpi_so_paths()
213 verilator_cmd += [
214 "-LDFLAGS",
215 " ".join(["-l" + so for so in self.sources.dpi_so]) + " " +
216 " ".join(["-L" + so.parent.as_posix() for so in dpi_so_paths]),
217 ]
218 verilator_cmd += [str(p) for p in self.sources.rtl_sources]
219 top = self.sources.top
220 make_cmd = ["make", "-C", "obj_dir", "-f", f"V{top}.mk", "-j"]
221 return [verilator_cmd, make_cmd]
222
223 def _depfile_path(self, obj_dir: Path) -> Path:
224 return obj_dir / f"V{self.sources.top}__ver.d"
225
226 def _generated_targets(self, depfile: Path) -> List[Path]:
227 depfile_contents = depfile.read_text().replace("\\\n", " ")
228 separator = re.search(r":\s", depfile_contents)
229 if separator is None:
230 raise RuntimeError(f"Malformed Verilator depfile: {depfile}")
231 return [(Path.cwd() / path).resolve()
232 for path in depfile_contents[:separator.start()].split()]
233
235 obj_dir = Path.cwd() / "obj_dir"
236 depfile = self._depfile_path(obj_dir)
237 generated_targets = self._generated_targets(depfile)
238 generated_sources = [
239 path for path in generated_targets if path.suffix == ".cpp"
240 ]
241 pch_header = next(
242 (path for path in generated_targets if path.name.endswith("__pch.h")),
243 None)
244 self._write_cmake(obj_dir, generated_sources, pch_header)
245 return 0
246
247 def _generated_cpp_sources(self, depfile: Path) -> List[Path]:
248 generated_sources = [
249 path for path in self._generated_targets(depfile)
250 if path.suffix == ".cpp"
251 ]
252 if not generated_sources:
253 raise RuntimeError(
254 f"No generated C++ sources found in depfile: {depfile}")
255 return generated_sources
256
257 def _write_cmake(self,
258 obj_dir: Path,
259 generated_sources: List[Path],
260 pch_header: Optional[Path] = None) -> Path:
261 """Write a CMakeLists.txt for building the verilated simulation.
262
263 Returns the path to the CMake build directory."""
264
265 verilator_root = self._find_verilator_root()
266 if verilator_root is None:
267 raise RuntimeError(Verilator.VerilatorRootNotFound)
268 include_dir = verilator_root / "include"
269 exe_name = "V" + self.sources.top
270
271 if os.name == "nt" and all(source.exists() for source in generated_sources):
272 # Verilator can emit deeply descriptive source filenames. CMake uses the
273 # source basename in MSVC's /Fo object path, which can overflow Windows'
274 # practical object path limits even after CMake hashes directories.
275 # Short local copies keep the build graph stable without changing the
276 # generated code or its includes.
277 short_source_dir = obj_dir / "cmake_src"
278 if short_source_dir.exists():
279 shutil.rmtree(short_source_dir)
280 short_source_dir.mkdir(parents=True)
281 shortened_sources = []
282 for index, source in enumerate(generated_sources):
283 shortened_source = short_source_dir / f"vsrc_{index}.cpp"
284 shutil.copy2(source, shortened_source)
285 shortened_sources.append(shortened_source)
286 generated_sources = shortened_sources
287
288 runtime_sources = [
289 include_dir / "verilated.cpp",
290 include_dir / "verilated_threads.cpp",
291 ]
292 # Include Verilator's DPI helpers when DPI shared objects are enabled.
293 if self.sources.dpi_so:
294 runtime_sources.append(include_dir / "verilated_dpi.cpp")
295 if self.debug:
296 runtime_sources.append(include_dir / "verilated_fst_c.cpp")
297 # Include constrained-randomization runtime when available (Verilator 5.x+).
298 random_cpp = include_dir / "verilated_random.cpp"
299 if random_cpp.exists():
300 runtime_sources.append(random_cpp)
301
302 generated_src = "\n ".join(
303 source.as_posix() for source in generated_sources)
304 rt_src = "\n ".join(s.as_posix() for s in runtime_sources)
305 driver = Path(Verilator.DefaultDriver).as_posix()
306 inc = include_dir.as_posix()
307 vltstd = (include_dir / "vltstd").as_posix()
308
309 defs = [f"TOP_MODULE={self.sources.top}"]
310 if self.debug:
311 defs.append("TRACE")
312 defs_str = "\n ".join(defs)
313
314 # Link DPI shared objects by full path. On Windows, link against the
315 # ``.lib`` import library; the matching ``.dll`` is found at runtime via
316 # ``PATH`` (see ``Simulator.get_env``).
317 dpi_link = ""
318 if self.sources.dpi_so:
319 dpi_paths = self.sources.dpi_link_paths()
320 dpi_link = "\n ".join(p.as_posix() for p in dpi_paths)
321
322 pch_setup = ""
323 if pch_header is not None:
324 runtime_and_driver = "\n ".join(
325 [source.as_posix() for source in runtime_sources] + [driver])
326 pch_setup = f"""
327target_precompile_headers({exe_name} PRIVATE
328 {pch_header.as_posix()}
329)
330
331set_source_files_properties(
332 {runtime_and_driver}
333 PROPERTIES SKIP_PRECOMPILE_HEADERS ON
334)
335"""
336
337 # zlib is only needed when FST tracing (debug builds) is enabled.
338 if self.debug:
339 zlib_find = "find_package(ZLIB REQUIRED)"
340 zlib_link = "ZLIB::ZLIB"
341 else:
342 zlib_find = ""
343 zlib_link = ""
344
345 content = f"""\
346cmake_minimum_required(VERSION 3.20)
347project({exe_name} CXX)
348
349set(CMAKE_CXX_STANDARD 17)
350set(CMAKE_CXX_STANDARD_REQUIRED ON)
351
352if(MSVC)
353 add_compile_options(/EHsc /bigobj)
354endif()
355
356find_package(Threads REQUIRED)
357{zlib_find}
358add_executable({exe_name}
359 {generated_src}
360 {rt_src}
361 {driver}
362)
363
364target_include_directories({exe_name} PRIVATE
365 {inc}
366 {vltstd}
367 ${{CMAKE_CURRENT_SOURCE_DIR}}/..
368)
369
370target_compile_definitions({exe_name} PRIVATE
371 {defs_str}
372)
373{pch_setup}
374
375target_link_libraries({exe_name} PRIVATE
376 Threads::Threads
377 {zlib_link}
378 {dpi_link}
379)
380"""
381 build_dir = obj_dir / "cmake_build"
382 build_dir.mkdir(parents=True, exist_ok=True)
383 (build_dir / "CMakeLists.txt").write_text(content)
384 return build_dir
385
386 @property
387 def waveform_extension(self) -> str:
388 """Verilator's C++ driver uses ``VerilatedFstC`` — FST format."""
389 return ".fst"
390
391 def run_command(self, gui: bool):
392 if gui:
393 raise RuntimeError("Verilator does not support GUI mode.")
394 exe_name = "V" + self.sources.top
395 if os.name == "nt":
396 exe_name += ".exe"
397 if self._use_cmake:
398 exe = Path.cwd() / "obj_dir" / "cmake_build" / exe_name
399 else:
400 exe = Path.cwd() / "obj_dir" / exe_name
401 return [str(exe)]
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
str waveform_extension(self)
Definition verilator.py:387
Optional[Path] _find_verilator_root()
Definition verilator.py:93
List[Path] _generated_targets(self, Path depfile)
Definition verilator.py:226
int _write_cmake_from_depfile(self)
Definition verilator.py:234
Path _write_cmake(self, Path obj_dir, List[Path] generated_sources, Optional[Path] pch_header=None)
Definition verilator.py:260
Path _depfile_path(self, Path obj_dir)
Definition verilator.py:223
List[Path] _generated_cpp_sources(self, Path depfile)
Definition verilator.py:247
Path verilator_bin(self)
Definition verilator.py:61
bool _use_cmake(self)
Definition verilator.py:119
run_command(self, bool gui)
Definition verilator.py:391
List[Simulator.CompileStep] compile_commands(self)
Definition verilator.py:124
Optional[Path] _find_verilator_bin()
Definition verilator.py:68
__init__(self, SourceFiles sources, Path run_dir, bool debug, bool save_waveform=False, Optional[Callable[[str], None]] run_stdout_callback=None, Optional[Callable[[str], None]] run_stderr_callback=None, Optional[Callable[[str], None]] compile_stdout_callback=None, Optional[Callable[[str], None]] compile_stderr_callback=None, bool make_default_logs=True, Optional[Dict[str, str]] macro_definitions=None)
Definition verilator.py:46