9from pathlib
import Path
10from typing
import List, Optional, Callable, Dict
12from .simulator
import CosimCollateralDir, Simulator, SourceFiles
16 """Run and compile funcs for Verilator.
18 Calls ``verilator_bin`` directly (bypassing the Perl wrapper) to generate
19 C++ from RTL, then builds the simulation executable with CMake + Ninja.
20 Falls back to ``make`` when cmake/ninja are not available."""
22 DefaultDriver = CosimCollateralDir /
"driver.cpp"
23 _CMakeSignatureFilename =
".esi-cosim-cmake-config.json"
24 _CMakeSignatureEnv = (
26 "CMAKE_TOOLCHAIN_FILE",
32 VerilatorBinNotFound = (
33 "Cannot find verilator_bin. Set VERILATOR_PATH to an absolute path "
34 "or ensure verilator_bin is in PATH.")
35 VerilatorRootNotFound = (
36 "Cannot find VERILATOR_ROOT. Set the VERILATOR_ROOT environment "
37 "variable or ensure verilator_bin is in PATH.")
38 VerilatorPathInvalid = (
39 "VERILATOR_PATH does not point to a valid verilator_bin executable.")
40 VerilatorRootInvalid = (
41 "VERILATOR_ROOT does not point to a Verilator root containing "
42 "include/verilated.h.")
49 save_waveform: bool =
False,
50 run_stdout_callback: Optional[Callable[[str],
None]] =
None,
51 run_stderr_callback: Optional[Callable[[str],
None]] =
None,
52 compile_stdout_callback: Optional[Callable[[str],
None]] =
None,
53 compile_stderr_callback: Optional[Callable[[str],
None]] =
None,
54 make_default_logs: bool =
True,
55 macro_definitions: Optional[Dict[str, str]] =
None,
61 save_waveform=save_waveform,
62 run_stdout_callback=run_stdout_callback,
63 run_stderr_callback=run_stderr_callback,
64 compile_stdout_callback=compile_stdout_callback,
65 compile_stderr_callback=compile_stderr_callback,
66 make_default_logs=make_default_logs,
67 macro_definitions=macro_definitions,
74 vpath = Verilator._find_verilator_bin()
81 """Locate the ``verilator_bin`` executable.
83 When ``VERILATOR_PATH`` is set it must point to a valid executable;
84 otherwise a ``RuntimeError`` is raised. Without it, ``verilator_bin`` is
85 looked up on ``PATH``. Returns ``None`` when nothing is found."""
87 def check_path(path: Path | str |
None) -> Optional[Path]:
88 if isinstance(path, str):
90 if path
is not None and path.exists()
and path.is_file():
94 if "VERILATOR_PATH" in os.environ:
95 vpath = Path(os.environ[
"VERILATOR_PATH"])
96 if vpath.stem ==
"verilator":
97 vpath = vpath.parent /
"verilator_bin"
98 checked = check_path(vpath)
102 return check_path(shutil.which(
"verilator_bin"))
106 """Locate the Verilator root containing ``include/verilated.h``.
108 When ``VERILATOR_ROOT`` is set it must contain ``include/verilated.h``;
109 otherwise a ``RuntimeError`` is raised. Without it, the packaged root
110 (``$PREFIX/share/verilator``) is derived from the ``verilator_bin``
111 location. Returns ``None`` when nothing is found."""
112 if "VERILATOR_ROOT" in os.environ:
113 root = Path(os.environ[
"VERILATOR_ROOT"])
114 if (root /
"include" /
"verilated.h").exists():
118 verilator_bin = Verilator._find_verilator_bin()
119 if verilator_bin
is None:
124 pkg_root = verilator_bin.parent.parent /
"share" /
"verilator"
125 if (pkg_root /
"include" /
"verilated.h").exists():
132 """True when both cmake and ninja are available on PATH."""
133 return shutil.which(
"cmake")
is not None and \
134 shutil.which(
"ninja")
is not None
138 """Lift the stack soft limit to the hard limit for the verilator process.
140 Verilator recurses over the design AST and segfaults on large designs with
141 the usual 8MB stack. Its ``verilator`` wrapper script normally runs
142 ``ulimit -s unlimited`` first; we invoke ``verilator_bin`` directly and so
143 have to do it ourselves. Subprocesses inherit the raised limit.
148 soft, hard = resource.getrlimit(resource.RLIMIT_STACK)
152 resource.setrlimit(resource.RLIMIT_STACK, (hard, hard))
153 except (ValueError, OSError):
158 """Prefer clang and lld when they are available.
160 Verilated code compiles about twice as fast with clang as with gcc, and
161 the model is a throwaway simulation binary, so the toolchain only affects
162 build time. Setting ``CXX`` or ``LDFLAGS`` opts back out.
167 if not os.environ.get(
"CXX"):
168 clangxx = shutil.which(
"clang++")
169 if clangxx
is not None:
170 args.append(f
"-DCMAKE_CXX_COMPILER={clangxx}")
171 if not os.environ.get(
"LDFLAGS")
and shutil.which(
"ld.lld")
is not None:
172 args.append(
"-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld")
177 """Serialize inputs that can affect CMake configuration."""
179 "command": cmake_cmd,
181 name: os.environ.get(name)
for name
in Verilator._CMakeSignatureEnv
184 return json.dumps(signature, indent=2, sort_keys=
True) +
"\n"
187 """Return the compile steps for the full compile flow.
189 When cmake and ninja are available the returned list contains four
191 1. ``verilator_bin`` – generates C++ from RTL.
192 2. Python callback – generates the CMakeLists.txt from the depfile.
193 3. Python callback – configures the C++ build when inputs changed.
194 4. ``ninja`` – builds the simulation executable.
196 Otherwise falls back to two commands:
197 1. ``verilator_bin --exe`` – generates C++ and a Makefile.
198 2. ``make`` – builds via the generated Makefile.
201 if verilator_bin
is None:
204 if verilator_root
is None:
206 os.environ[
"VERILATOR_ROOT"] = str(verilator_root)
209 verilator_cmd: List[str] = [
216 f
"+define+{k}={v}" if v
is not None else f
"+define+{k}"
239 "--trace-underscore",
243 verilator_cmd += [str(p)
for p
in self.
sources.rtl_sources]
244 build_dir = Path.cwd() /
"obj_dir" /
"cmake_build"
250 "cmake",
"-G",
"Ninja",
"-DCMAKE_BUILD_TYPE=Release",
"-S",
251 str(build_dir),
"-B",
258 vcpkg_root = os.environ.get(
"VCPKG_ROOT")
or os.environ.get(
259 "VCPKG_INSTALLATION_ROOT")
262 vcpkg_root) /
"scripts" /
"buildsystems" /
"vcpkg.cmake"
263 if toolchain.exists():
264 cmake_cmd.append(f
"-DCMAKE_TOOLCHAIN_FILE={toolchain}")
265 ninja_cmd = [
"ninja",
"-C", str(build_dir)]
269 def configure(cmake_cmd=cmake_cmd,
270 cmake_signature=cmake_signature,
271 signature_file=signature_file) -> int:
274 signature_matches = signature_file.exists()
and \
275 signature_file.read_text() == cmake_signature
276 if (build_dir /
"build.ninja").exists()
and signature_matches:
280 signature_file.write_text(cmake_signature)
290 verilator_cmd += [
"--exe", str(Verilator.DefaultDriver)]
291 cflags = [
"-DTOP_MODULE=" + self.
sources.top]
293 cflags.append(
"-DTRACE")
294 verilator_cmd += [
"-CFLAGS",
" ".join(cflags)]
296 dpi_so_paths = self.
sources.dpi_so_paths()
299 " ".join([
"-l" + so
for so
in self.
sources.dpi_so]) +
" " +
300 " ".join([
"-L" + so.parent.as_posix()
for so
in dpi_so_paths]),
302 verilator_cmd += [str(p)
for p
in self.
sources.rtl_sources]
304 make_cmd = [
"make",
"-C",
"obj_dir",
"-f", f
"V{top}.mk",
"-j"]
305 return [verilator_cmd, make_cmd]
308 return obj_dir / f
"V{self.sources.top}__ver.d"
311 depfile_contents = depfile.read_text().replace(
"\\\n",
" ")
312 separator = re.search(
r":\s", depfile_contents)
313 if separator
is None:
314 raise RuntimeError(f
"Malformed Verilator depfile: {depfile}")
315 return [(Path.cwd() / path).
resolve()
316 for path
in depfile_contents[:separator.start()].split()]
319 obj_dir = Path.cwd() /
"obj_dir"
322 generated_sources = [
323 path
for path
in generated_targets
if path.suffix ==
".cpp"
326 (path
for path
in generated_targets
if path.name.endswith(
"__pch.h")),
328 self.
_write_cmake(obj_dir, generated_sources, pch_header)
332 generated_sources = [
334 if path.suffix ==
".cpp"
336 if not generated_sources:
338 f
"No generated C++ sources found in depfile: {depfile}")
339 return generated_sources
343 """Verilator suffixes cold-path TUs, including Syms/ConstPool, with
345 return source.stem.endswith(
"__Slow")
349 generated_sources: List[Path],
350 pch_header: Optional[Path] =
None) -> Path:
351 """Write a CMakeLists.txt for building the verilated simulation.
353 Returns the path to the CMake build directory."""
356 if verilator_root
is None:
358 include_dir = verilator_root /
"include"
359 exe_name =
"V" + self.
sources.top
361 slow_sources = [s
for s
in generated_sources
if self.
_is_slow(s)]
362 fast_sources = [s
for s
in generated_sources
if not self.
_is_slow(s)]
364 if os.name ==
"nt" and all(source.exists()
for source
in generated_sources):
370 short_source_dir = obj_dir /
"cmake_src"
371 if short_source_dir.exists():
372 shutil.rmtree(short_source_dir)
373 short_source_dir.mkdir(parents=
True)
375 def shorten(sources: List[Path], prefix: str) -> List[Path]:
377 for index, source
in enumerate(sources):
378 destination = short_source_dir / f
"{prefix}{index}.cpp"
379 shutil.copy2(source, destination)
380 shortened.append(destination)
383 fast_sources = shorten(fast_sources,
"vfast_")
384 slow_sources = shorten(slow_sources,
"vslow_")
387 include_dir /
"verilated.cpp",
388 include_dir /
"verilated_threads.cpp",
392 runtime_sources.append(include_dir /
"verilated_dpi.cpp")
394 runtime_sources.append(include_dir /
"verilated_fst_c.cpp")
396 random_cpp = include_dir /
"verilated_random.cpp"
397 if random_cpp.exists():
398 runtime_sources.append(random_cpp)
400 rt_src =
"\n ".join(s.as_posix()
for s
in runtime_sources)
401 driver = Path(Verilator.DefaultDriver).as_posix()
402 rt_and_driver =
"\n ".join([s.as_posix()
for s
in runtime_sources] +
404 inc = include_dir.as_posix()
405 vltstd = (include_dir /
"vltstd").as_posix()
407 defs = [f
"TOP_MODULE={self.sources.top}"]
410 defs_str =
"\n ".join(defs)
417 dpi_paths = self.
sources.dpi_link_paths()
418 dpi_link =
"\n ".join(p.as_posix()
for p
in dpi_paths)
424 for name, group_sources
in ((
"vl_fast", fast_sources), (
"vl_slow",
426 if not group_sources:
428 listing =
"\n ".join(s.as_posix()
for s
in group_sources)
429 opts = (
"\ntarget_compile_options(vl_slow PRIVATE ${VL_OPT_SLOW})"
430 if name ==
"vl_slow" else "")
431 pch = (
"" if pch_header
is None else
432 f
"\ntarget_precompile_headers({name} PRIVATE "
433 f
"{pch_header.as_posix()})")
435add_library({name} OBJECT
438target_link_libraries({name} PRIVATE vl_common){opts}{pch}""")
439 obj_refs.append(f
"$<TARGET_OBJECTS:{name}>")
440 groups_str =
"\n".join(groups)
441 obj_str =
"\n ".join(obj_refs)
445 zlib_find = (
"find_package(ZLIB REQUIRED)\n"
446 "find_library(LZ4_LIBRARY NAMES lz4 REQUIRED)")
447 zlib_link =
"ZLIB::ZLIB\n ${LZ4_LIBRARY}"
453cmake_minimum_required(VERSION 3.20)
454project({exe_name} CXX)
456set(CMAKE_CXX_STANDARD 17)
457set(CMAKE_CXX_STANDARD_REQUIRED ON)
460 add_compile_options(/EHsc /bigobj)
462 set(VL_OPT_GLOBAL /O1)
465 set(VL_OPT_GLOBAL -Os)
468find_package(Threads REQUIRED)
470add_library(vl_common INTERFACE)
472target_include_directories(vl_common INTERFACE
475 ${{CMAKE_CURRENT_SOURCE_DIR}}/..
478target_compile_definitions(vl_common INTERFACE
483add_executable({exe_name}
489set_source_files_properties(
491 PROPERTIES COMPILE_OPTIONS "${{VL_OPT_GLOBAL}}"
494target_link_libraries({exe_name} PRIVATE
501 build_dir = obj_dir /
"cmake_build"
502 build_dir.mkdir(parents=
True, exist_ok=
True)
503 cmake_file = build_dir /
"CMakeLists.txt"
504 existing = cmake_file.read_text()
if cmake_file.exists()
else None
507 cmake_file.write_text(content)
512 """Verilator's C++ driver uses ``VerilatedFstC`` — FST format."""
517 raise RuntimeError(
"Verilator does not support GUI mode.")
518 exe_name =
"V" + self.
sources.top
522 exe = Path.cwd() /
"obj_dir" /
"cmake_build" / exe_name
524 exe = Path.cwd() /
"obj_dir" / exe_name
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
int _run_compile_command(self, CompileCommand cmd)
str _CMakeSignatureFilename
str waveform_extension(self)
Optional[Path] _find_verilator_root()
List[Path] _generated_targets(self, Path depfile)
int _write_cmake_from_depfile(self)
bool _is_slow(Path source)
str _cmake_signature(List[str] cmake_cmd)
None _raise_stack_limit()
Path _write_cmake(self, Path obj_dir, List[Path] generated_sources, Optional[Path] pch_header=None)
List[str] _toolchain_args()
Path _depfile_path(self, Path obj_dir)
List[Path] _generated_cpp_sources(self, Path depfile)
_write_cmake_from_depfile
run_command(self, bool gui)
List[Simulator.CompileStep] compile_commands(self)
Optional[Path] _find_verilator_bin()
__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)