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
24 self,
25 sources: SourceFiles,
26 run_dir: Path,
27 debug: bool,
28 save_waveform: bool = False,
29 run_stdout_callback: Optional[Callable[[str], None]] = None,
30 run_stderr_callback: Optional[Callable[[str], None]] = None,
31 compile_stdout_callback: Optional[Callable[[str], None]] = None,
32 compile_stderr_callback: Optional[Callable[[str], None]] = None,
33 make_default_logs: bool = True,
34 macro_definitions: Optional[Dict[str, str]] = None,
35 ):
36 super().__init__(
37 sources=sources,
38 run_dir=run_dir,
39 debug=debug,
40 save_waveform=save_waveform,
41 run_stdout_callback=run_stdout_callback,
42 run_stderr_callback=run_stderr_callback,
43 compile_stdout_callback=compile_stdout_callback,
44 compile_stderr_callback=compile_stderr_callback,
45 make_default_logs=make_default_logs,
46 macro_definitions=macro_definitions,
47 )
48 self.verilator_bin = "verilator_bin"
49 if "VERILATOR_PATH" in os.environ:
50 vpath = os.environ["VERILATOR_PATH"]
51 # Backwards compatibility: if the env var points to the Perl wrapper,
52 # redirect to verilator_bin.
53 basename = Path(vpath).stem
54 if basename == "verilator":
55 self.verilator_bin = str(Path(vpath).parent / "verilator_bin")
56 else:
57 self.verilator_bin = vpath
58
59 def _find_verilator_bin(self, required: bool = True) -> str:
60 verilator_bin_path = shutil.which(self.verilator_bin)
61 if verilator_bin_path:
62 return str(Path(verilator_bin_path).resolve())
63
64 verilator_bin_path = Path(self.verilator_bin)
65 if verilator_bin_path.is_file():
66 return str(verilator_bin_path.resolve())
67
68 if not required:
69 return self.verilator_bin
70
71 raise RuntimeError(
72 "Cannot find verilator_bin. Set VERILATOR_PATH to an absolute path "
73 "or ensure verilator_bin is in PATH.")
74
75 def _find_verilator_root(self, verilator_bin: Optional[str] = None) -> Path:
76 """Locate VERILATOR_ROOT for runtime includes and sources.
77
78 Checks the ``VERILATOR_ROOT`` environment variable first, then attempts
79 to derive the root from the location of ``verilator_bin``. Supports both
80 source-tree layouts (``$ROOT/include/verilated.h``) and system package
81 layouts (``$PREFIX/share/verilator/include/verilated.h``)."""
82 if "VERILATOR_ROOT" in os.environ:
83 root = Path(os.environ["VERILATOR_ROOT"])
84 if root.is_dir():
85 return root
86 if verilator_bin is None:
87 verilator_bin = self.verilator_bin
88
89 verilator_bin_path = Path(verilator_bin)
90 if not verilator_bin_path.is_file():
91 resolved = shutil.which(verilator_bin)
92 if resolved is None:
93 raise RuntimeError(
94 "Cannot find VERILATOR_ROOT. Set the VERILATOR_ROOT environment "
95 "variable or ensure verilator_bin is in PATH.")
96 verilator_bin_path = Path(resolved)
97
98 # verilator_bin is typically in $PREFIX/bin/
99 prefix = verilator_bin_path.resolve().parent.parent
100 # Source-tree layout: $VERILATOR_ROOT/bin/verilator_bin
101 if (prefix / "include" / "verilated.h").exists():
102 return prefix
103 # System package layout: $PREFIX/share/verilator/include/verilated.h
104 pkg_root = prefix / "share" / "verilator"
105 if (pkg_root / "include" / "verilated.h").exists():
106 return pkg_root
107 raise RuntimeError(
108 "Cannot find VERILATOR_ROOT. Set the VERILATOR_ROOT environment "
109 "variable or ensure verilator_bin is in PATH.")
110
111 @property
112 def _use_cmake(self) -> bool:
113 """True when both cmake and ninja are available on PATH."""
114 return shutil.which("cmake") is not None and \
115 shutil.which("ninja") is not None
116
117 def compile_commands(self) -> List[Simulator.CompileStep]:
118 """Return the compile steps for the full compile flow.
119
120 When cmake and ninja are available the returned list contains four
121 sequential steps:
122 1. ``verilator_bin`` – generates C++ from RTL.
123 2. Python callback – generates the CMakeLists.txt from the depfile.
124 3. ``cmake`` – configures the C++ build (Ninja generator).
125 4. ``ninja`` – builds the simulation executable.
126
127 Otherwise falls back to two commands:
128 1. ``verilator_bin --exe`` – generates C++ and a Makefile.
129 2. ``make`` – builds via the generated Makefile.
130 """
131 self.verilator_bin = self._find_verilator_bin(required=True)
132 os.environ["VERILATOR_ROOT"] = str(
134
135 verilator_cmd: List[str] = [
136 self.verilator_bin,
137 "--cc",
138 ]
139
140 if self.macro_definitions:
141 verilator_cmd += [
142 f"+define+{k}={v}" if v is not None else f"+define+{k}"
143 for k, v in self.macro_definitions.items()
144 ]
145
146 verilator_cmd += [
147 "--top-module",
148 self.sources.top,
149 "-DSIMULATION",
150 "-Wno-TIMESCALEMOD",
151 "-Wno-fatal",
152 "-sv",
153 "--verilate-jobs",
154 "0",
155 "--output-split",
156 "2500",
157 ]
158 if self.debug:
159 verilator_cmd += [
160 "--assert",
161 "--trace-fst",
162 "--trace-structs",
163 "--trace-underscore",
164 ]
165
166 if self._use_cmake:
167 verilator_cmd += [str(p) for p in self.sources.rtl_sources]
168 build_dir = str(Path.cwd() / "obj_dir" / "cmake_build")
169 cmake_cmd = ["cmake", "-G", "Ninja", "-S", build_dir, "-B", build_dir]
170 ninja_cmd = ["ninja", "-C", build_dir]
171 return [
172 verilator_cmd, self._write_cmake_from_depfile_write_cmake_from_depfile, cmake_cmd, ninja_cmd
173 ]
174
175 # -- make fallback --
176 # Let verilator generate a Makefile with --exe so it includes the
177 # driver, CFLAGS, and LDFLAGS directly.
178 verilator_cmd += ["--exe", str(Verilator.DefaultDriver)]
179 cflags = ["-DTOP_MODULE=" + self.sources.top]
180 if self.debug:
181 cflags.append("-DTRACE")
182 verilator_cmd += ["-CFLAGS", " ".join(cflags)]
183 if self.sources.dpi_so:
184 dpi_so_paths = self.sources.dpi_so_paths()
185 verilator_cmd += [
186 "-LDFLAGS",
187 " ".join(["-l" + so for so in self.sources.dpi_so]) + " " +
188 " ".join(["-L" + so.parent.as_posix() for so in dpi_so_paths]),
189 ]
190 verilator_cmd += [str(p) for p in self.sources.rtl_sources]
191 top = self.sources.top
192 make_cmd = ["make", "-C", "obj_dir", "-f", f"V{top}.mk", "-j"]
193 return [verilator_cmd, make_cmd]
194
195 def _depfile_path(self, obj_dir: Path) -> Path:
196 return obj_dir / f"V{self.sources.top}__ver.d"
197
198 def _generated_targets(self, depfile: Path) -> List[Path]:
199 depfile_contents = depfile.read_text().replace("\\\n", " ")
200 separator = re.search(r":\s", depfile_contents)
201 if separator is None:
202 raise RuntimeError(f"Malformed Verilator depfile: {depfile}")
203 return [(Path.cwd() / path).resolve()
204 for path in depfile_contents[:separator.start()].split()]
205
207 obj_dir = Path.cwd() / "obj_dir"
208 depfile = self._depfile_path(obj_dir)
209 generated_targets = self._generated_targets(depfile)
210 generated_sources = [
211 path for path in generated_targets if path.suffix == ".cpp"
212 ]
213 pch_header = next(
214 (path for path in generated_targets if path.name.endswith("__pch.h")),
215 None)
216 self._write_cmake(obj_dir, generated_sources, pch_header)
217 return 0
218
219 def _generated_cpp_sources(self, depfile: Path) -> List[Path]:
220 generated_sources = [
221 path for path in self._generated_targets(depfile)
222 if path.suffix == ".cpp"
223 ]
224 if not generated_sources:
225 raise RuntimeError(
226 f"No generated C++ sources found in depfile: {depfile}")
227 return generated_sources
228
229 def _write_cmake(self,
230 obj_dir: Path,
231 generated_sources: List[Path],
232 pch_header: Optional[Path] = None) -> Path:
233 """Write a CMakeLists.txt for building the verilated simulation.
234
235 Returns the path to the CMake build directory."""
236
237 verilator_root = self._find_verilator_root()
238 include_dir = verilator_root / "include"
239 exe_name = "V" + self.sources.top
240
241 runtime_sources = [
242 include_dir / "verilated.cpp",
243 include_dir / "verilated_threads.cpp",
244 ]
245 if self.sources.dpi_so:
246 runtime_sources.append(include_dir / "verilated_dpi.cpp")
247 if self.debug:
248 runtime_sources.append(include_dir / "verilated_fst_c.cpp")
249 # Include constrained-randomization runtime when available (Verilator 5.x+).
250 random_cpp = include_dir / "verilated_random.cpp"
251 if random_cpp.exists():
252 runtime_sources.append(random_cpp)
253
254 generated_src = "\n ".join(str(source) for source in generated_sources)
255 rt_src = "\n ".join(str(s) for s in runtime_sources)
256 driver = str(Verilator.DefaultDriver)
257 inc = str(include_dir)
258 vltstd = str(include_dir / "vltstd")
259
260 defs = [f"TOP_MODULE={self.sources.top}"]
261 if self.debug:
262 defs.append("TRACE")
263 defs_str = "\n ".join(defs)
264
265 # Link DPI shared objects by full path.
266 dpi_link = ""
267 if self.sources.dpi_so:
268 dpi_paths = self.sources.dpi_so_paths()
269 dpi_link = "\n ".join(str(p) for p in dpi_paths)
270
271 pch_setup = ""
272 if pch_header is not None:
273 runtime_and_driver = "\n ".join(
274 [str(source) for source in runtime_sources] + [driver])
275 pch_setup = f"""
276target_precompile_headers({exe_name} PRIVATE
277 {pch_header}
278)
279
280set_source_files_properties(
281 {runtime_and_driver}
282 PROPERTIES SKIP_PRECOMPILE_HEADERS ON
283)
284"""
285
286 content = f"""\
287cmake_minimum_required(VERSION 3.20)
288project({exe_name} CXX)
289
290add_executable({exe_name}
291 {generated_src}
292 {rt_src}
293 {driver}
294)
295
296target_include_directories({exe_name} PRIVATE
297 {inc}
298 {vltstd}
299 ${{CMAKE_CURRENT_SOURCE_DIR}}/..
300)
301
302target_compile_definitions({exe_name} PRIVATE
303 {defs_str}
304)
305{pch_setup}
306
307find_package(Threads REQUIRED)
308find_package(ZLIB REQUIRED)
309target_link_libraries({exe_name} PRIVATE
310 Threads::Threads
311 ZLIB::ZLIB
312 {dpi_link}
313)
314"""
315 build_dir = obj_dir / "cmake_build"
316 build_dir.mkdir(parents=True, exist_ok=True)
317 (build_dir / "CMakeLists.txt").write_text(content)
318 return build_dir
319
320 @property
321 def waveform_extension(self) -> str:
322 """Verilator's C++ driver uses ``VerilatedFstC`` — FST format."""
323 return ".fst"
324
325 def run_command(self, gui: bool):
326 if gui:
327 raise RuntimeError("Verilator does not support GUI mode.")
328 exe_name = "V" + self.sources.top
329 if self._use_cmake:
330 exe = Path.cwd() / "obj_dir" / "cmake_build" / exe_name
331 else:
332 exe = Path.cwd() / "obj_dir" / exe_name
333 return [str(exe)]
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
str waveform_extension(self)
Definition verilator.py:321
List[Path] _generated_targets(self, Path depfile)
Definition verilator.py:198
int _write_cmake_from_depfile(self)
Definition verilator.py:206
Path _write_cmake(self, Path obj_dir, List[Path] generated_sources, Optional[Path] pch_header=None)
Definition verilator.py:232
Path _depfile_path(self, Path obj_dir)
Definition verilator.py:195
List[Path] _generated_cpp_sources(self, Path depfile)
Definition verilator.py:219
bool _use_cmake(self)
Definition verilator.py:112
str _find_verilator_bin(self, bool required=True)
Definition verilator.py:59
run_command(self, bool gui)
Definition verilator.py:325
List[Simulator.CompileStep] compile_commands(self)
Definition verilator.py:117
Path _find_verilator_root(self, Optional[str] verilator_bin=None)
Definition verilator.py:75
__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:35