Add support for dynamically loading VPI extensions on POSIX platforms

Signed-off-by: Matthew Ballance <matt.ballance@gmail.com>
This commit is contained in:
Matthew Ballance 2026-06-06 19:55:23 +00:00
parent 0cd13f80c9
commit 47b3e6dd6c
19 changed files with 503 additions and 1 deletions

View File

@ -653,6 +653,7 @@ description of these arguments.
+verilator+solver+file+<filename> Set random solver log filename
+verilator+V Show verbose version and config
+verilator+version Show version and exit
+verilator+vpi+<library>[:<bootstrap>] Load VPI shared library (requires --vpi --main)
+verilator+wno+unsatconstr+<value> Disable constraint warnings

View File

@ -139,6 +139,22 @@ Options:
Displays program version and exits.
.. option:: +verilator+vpi+<library>[:<bootstrap>]
Load a VPI shared library before simulation starts. Only available when the
model was Verilated with :vlopt:`--vpi` and :vlopt:`--main` (or
:vlopt:`--binary`). ``<library>`` is the path to the shared library. If
``:<bootstrap>`` is given, that named no-argument function is called;
otherwise the library's ``vlog_startup_routines`` array (IEEE 1800 Section 37) is
invoked. May be repeated to load multiple libraries. For signals to be
accessible by name, also Verilate with :vlopt:`--public-flat-rw`. If passed
to a model not compiled with :vlopt:`--vpi`, a warning is emitted and the
argument is ignored.
Runtime loading is supported on POSIX platforms only (it relies on the
executable exporting its VPI symbols to the loaded library); on Windows the
argument is rejected and the VPI code must instead be linked into the model.
.. option:: +verilator+wno+unsatconstr+<value>
Disable unsatisfied constraint warnings at simulation runtime. When set to

View File

@ -3544,6 +3544,15 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) {
// and the run can be reproduced by passing +verilator+seed+<that_value>.
if (u64 == 0) u64 = pickRandomSeed();
randSeed(static_cast<int>(u64));
} else if (0 == std::strncmp(arg.c_str(), "+verilator+vpi+", std::strlen("+verilator+vpi+"))) {
// With --vpi the generated --main (vl_load_vpi_libs) consumes this, so accept
// it silently here. Without --vpi there is no loader, so warn it is ignored.
#if !VM_VPI
VL_WARN_MT("COMMAND_LINE", 0, "",
("+verilator+vpi+ ignored: simulation was not compiled with --vpi (" + arg
+ ")")
.c_str());
#endif
} else if (arg == "+verilator+V") {
VerilatedImp::versionDump(); // Someday more info too
VL_FATAL_MT("COMMAND_LINE", 0, "",

View File

@ -80,6 +80,9 @@ UNAME_S := $(shell uname -s)
######################################################################
# C Preprocessor flags
# Default for makefiles generated before --vpi tracking was added
VM_VPI ?= 0
# Add -MMD -MP if you're using a recent version of GCC.
VK_CPPFLAGS_ALWAYS += \
-MMD \
@ -93,6 +96,7 @@ VK_CPPFLAGS_ALWAYS += \
-DVM_TRACE_FST=$(VM_TRACE_FST) \
-DVM_TRACE_VCD=$(VM_TRACE_VCD) \
-DVM_TRACE_SAIF=$(VM_TRACE_SAIF) \
-DVM_VPI=$(VM_VPI) \
$(CFG_CXXFLAGS_NO_UNUSED) \
ifeq ($(CFG_WITH_CCWARN),yes) # Local... Else don't burden users

View File

@ -55,7 +55,13 @@ private:
// Heavily commented output, as users are likely to look at or copy this code
puts("#include \"verilated.h\"\n");
if (v3Global.opt.vpi()) puts("#include \"verilated_vpi.h\"\n");
if (v3Global.opt.vpi()) {
puts("#include \"verilated_vpi.h\"\n");
puts("#ifndef _WIN32\n");
puts("# include <dlfcn.h> // For runtime +verilator+vpi+ library loading\n");
puts("#endif\n");
puts("#include <cstring>\n");
}
puts("#include \"" + EmitCUtil::topClassName() + ".h\"\n");
if (v3Global.opt.debugRuntimeTimeout()) {
puts("\n");
@ -67,6 +73,53 @@ private:
puts("// User VPI code adds to this array to get startup main() callbacks\n");
puts("extern \"C\" void (*vlog_startup_routines[])() VL_ATTR_WEAK;\n");
puts("\n");
// Runtime loader for VPI shared libraries requested via +verilator+vpi+<lib>.
// POSIX only: relies on the executable exporting its VPI symbols.
puts("// Load VPI shared libraries requested via +verilator+vpi+<lib>[:<bootstrap>]\n");
puts("static void vl_load_vpi_libs(int argc, char** argv) {\n");
puts(/**/ "const char* prefix = \"+verilator+vpi+\";\n");
puts(/**/ "const size_t pfx_len = std::strlen(prefix);\n");
puts(/**/ "for (int i = 1; i < argc; ++i) {\n");
puts(/****/ "if (std::strncmp(argv[i], prefix, pfx_len) != 0) continue;\n");
puts(/****/ "const std::string arg(argv[i] + pfx_len);\n");
puts(/****/ "if (arg.empty()) continue;\n");
puts("#ifdef _WIN32\n");
puts(/****/ "VL_FATAL_MT(\"\", 0, \"\","
" \"+verilator+vpi+: runtime VPI library loading is not supported on\"\n");
puts(/******/ "\" Windows; link the VPI code into the model instead\");\n");
puts("#else\n");
puts(/****/ "using vlog_startup_t = void (*)();\n");
puts(/****/ "// Split <lib>:<bootstrap> on the last ':'\n");
puts(/****/ "const std::string::size_type colon_pos = arg.rfind(':');\n");
puts(/****/ "const bool has_entry = (colon_pos != std::string::npos);\n");
puts(/****/ "const std::string libpath = has_entry ? arg.substr(0, colon_pos) : arg;\n");
puts(/****/ "const std::string entry_name\n");
puts(/******/ "= has_entry ? arg.substr(colon_pos + 1) : std::string{};\n");
puts(/****/ "void* handle = dlopen(libpath.c_str(), RTLD_LAZY);\n");
puts(/****/ "if (!handle)\n");
puts(/******/ "VL_FATAL_MT(\"\", 0, \"\","
" (std::string{\"Cannot load VPI library: \"} + dlerror()).c_str());\n");
puts(/****/ "if (has_entry) {\n");
puts(/******/ "vlog_startup_t bsp = reinterpret_cast<vlog_startup_t>("
"dlsym(handle, entry_name.c_str()));\n");
puts(/******/ "if (!bsp)\n");
puts(/********/ "VL_FATAL_MT(\"\", 0, \"\", (std::string{\"Cannot find VPI bootstrap '\"}\n");
puts(/**********/ "+ entry_name + \"' in: \" + libpath).c_str());\n");
puts(/******/ "bsp();\n");
puts(/****/ "} else {\n");
puts(/******/ "vlog_startup_t* routinesp = reinterpret_cast<vlog_startup_t*>(\n");
puts(/********/ "dlsym(handle, \"vlog_startup_routines\"));\n");
puts(/******/ "if (!routinesp)\n");
puts(/********/ "VL_FATAL_MT(\"\", 0, \"\","
" (std::string{\"Cannot find 'vlog_startup_routines' in: \"}\n");
puts(/**********/ "+ libpath).c_str());\n");
puts(/******/ "for (int j = 0; routinesp[j]; ++j) routinesp[j]();\n");
puts(/****/ "}\n");
puts("#endif\n");
puts(/**/ "}\n");
puts("}\n");
puts("\n");
}
puts("//======================\n\n");
@ -104,6 +157,8 @@ private:
puts("\n");
if (v3Global.opt.vpi()) {
puts("// Load VPI shared libraries requested via +verilator+vpi+<lib>\n");
puts("vl_load_vpi_libs(argc, argv);\n");
puts("// Hook VPI startup routines and invoke callback\n");
puts("if (vlog_startup_routines) {\n");
puts(/**/ "for (auto routinep = &vlog_startup_routines[0]; *routinep; routinep++)"

View File

@ -567,6 +567,12 @@ public:
of.puts("VM_TRACE_VCD = ");
of.puts(v3Global.opt.traceEnabledVcd() ? "1" : "0");
of.puts("\n");
of.puts("# VPI enabled? 0/1 (from --vpi)\n");
of.puts("VM_VPI = ");
of.puts(v3Global.opt.vpi() ? "1" : "0");
of.puts("\n");
// Link flags for runtime VPI library loading are platform-specific and emitted by
// emitOverallMake() after verilated.mk is included (so $(UNAME_S) is defined).
of.puts("\n### Object file lists...\n");
for (int support = 0; support < 3; ++support) {
@ -729,6 +735,17 @@ public:
of.puts("\n### Executable rules... (from --exe)\n");
of.puts("VPATH += $(VM_USER_DIR)\n");
of.puts("\n");
if (v3Global.opt.vpi()) {
// Runtime VPI library loading (+verilator+vpi+<lib>) needs platform-specific
// link flags. Resolve them with $(UNAME_S)
of.puts("# Runtime VPI library loading (+verilator+vpi+) link requirements\n");
of.puts("ifeq ($(UNAME_S),Linux)\n");
of.puts(/**/ "LDFLAGS += -rdynamic\n");
of.puts(/**/ "LDLIBS += -ldl\n");
of.puts("endif\n");
of.puts("\n");
}
}
const string compilerIncludePch

View File

@ -0,0 +1,94 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: VPI test library for t_flag_main_vpi
//
// Loaded at runtime via +verilator+vpi+<path> to verify that --binary --vpi
// correctly loads shared libraries and invokes vlog_startup_routines[] (or a
// named bootstrap). The design drives its own clock; this library only
// observes 'count' via a cbValueChange callback and calls $finish after
// MAX_TICKS edges -- so a successful $finish proves the library was loaded
// and is able to register callbacks and reach signals by name.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2025 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "vpi_user.h"
#include <cstdio>
#include <cstdlib>
// Number of count increments to observe before calling $finish
static const int MAX_TICKS = 10;
static vpiHandle s_count_handle = nullptr;
static PLI_INT32 count_change_cb(p_cb_data /*cb_data*/) {
if (!s_count_handle) return 0;
s_vpi_value val;
val.format = vpiIntVal;
vpi_get_value(s_count_handle, &val);
if (val.value.integer >= MAX_TICKS) {
vpi_printf(const_cast<char*>("*-* All Finished *-*\n"));
vpi_control(vpiFinish, 0);
}
return 0;
}
static PLI_INT32 start_of_sim_cb(p_cb_data /*cb_data*/) {
s_count_handle = vpi_handle_by_name(const_cast<char*>("t.count"), nullptr);
if (!s_count_handle) {
vpi_printf(const_cast<char*>("ERROR: cannot find t.count\n"));
vpi_control(vpiFinish, 1);
return 0;
}
// Observe count: fire a callback whenever it changes
t_cb_data cb_data;
s_vpi_time t;
s_vpi_value val;
t.type = vpiSuppressTime;
val.format = vpiSuppressVal;
cb_data.reason = cbValueChange;
cb_data.cb_rtn = count_change_cb;
cb_data.obj = s_count_handle;
cb_data.time = &t;
cb_data.value = &val;
cb_data.user_data = nullptr;
vpi_register_cb(&cb_data);
return 0;
}
static PLI_INT32 end_of_sim_cb(p_cb_data /*cb_data*/) {
vpi_printf(const_cast<char*>("- VPI end of simulation\n"));
return 0;
}
static void register_callbacks() {
// cbStartOfSimulation
t_cb_data cb_data;
s_vpi_time t;
t.type = vpiSuppressTime;
cb_data.reason = cbStartOfSimulation;
cb_data.cb_rtn = start_of_sim_cb;
cb_data.obj = nullptr;
cb_data.time = &t;
cb_data.value = nullptr;
cb_data.user_data = nullptr;
vpi_register_cb(&cb_data);
// cbEndOfSimulation
cb_data.reason = cbEndOfSimulation;
cb_data.cb_rtn = end_of_sim_cb;
vpi_register_cb(&cb_data);
}
// IEEE 1800 §37: vlog_startup_routines[] — null-terminated array of startup functions
extern "C" {
void (*vlog_startup_routines[])() = {register_callbacks, nullptr};
// Named bootstrap entrypoint — used when library is loaded as <path>:my_vpi_bootstrap
void my_vpi_bootstrap() { register_callbacks(); }
}

View File

@ -0,0 +1 @@
- VPI end of simulation

View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
# Compile with --binary --vpi to exercise the VPI-aware generated main.
# Also compile a VPI shared library to be loaded at runtime via +verilator+vpi+.
test.compile(
make_pli=True,
verilator_flags2=["--binary --vpi --public-flat-rw"])
# With --vpi and an executable (--binary implies --exe), the Makefile must, on Linux,
# export the executable's symbols (-rdynamic) and link libdl for the generated loader's
# dlopen/dlsym calls. These are gated on $(UNAME_S) so macOS/Windows are unaffected.
mk = test.obj_dir + "/V" + test.name + ".mk"
test.file_grep(mk, r'ifeq \(\$\(UNAME_S\),Linux\)')
test.file_grep(mk, r'LDFLAGS \+= -rdynamic')
test.file_grep(mk, r'LDLIBS \+= -ldl')
# Run the generated binary; load the VPI library via the +verilator+vpi+ plusarg.
test.execute(all_run_flags=["+verilator+vpi+" + test.obj_dir + "/libvpi.so"],
check_finished=True)
test.passes()

View File

@ -0,0 +1,28 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2025 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// Test for --binary --vpi runtime library loading. The design provides its
// own clock (so the simulation has Verilog event activity); the VPI library
// (t_flag_main_vpi.cpp), loaded at runtime via +verilator+vpi+, observes
// 'count' via a cbValueChange callback and calls $finish after MAX_TICKS
// edges. Signals are public so the library can reach them by name
// (requires --public-flat-rw).
module t;
reg clk /*verilator public_flat_rw*/;
reg [31:0] count /*verilator public_flat_rw*/;
initial begin
clk = 0;
count = 0;
end
// Self-driving clock: the design itself keeps the simulation alive
always #5 clk = ~clk;
always @(posedge clk) count <= count + 1;
endmodule

View File

@ -0,0 +1,26 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
# A valid library loaded with a :<bootstrap> entry that does not exist must
# fail with a clear error (the missing-named-bootstrap branch of the loader).
test.top_filename = 't/t_flag_main_vpi.v'
test.pli_filename = 't/t_flag_main_vpi.cpp'
test.compile(make_pli=True, verilator_flags2=["--binary --vpi --public-flat-rw"])
test.execute(fails=True,
all_run_flags=["+verilator+vpi+" + test.obj_dir + "/libvpi.so:no_such_fn"])
test.file_grep(test.run_log_filename, r"Cannot find VPI bootstrap 'no_such_fn'")
test.passes()

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
# +verilator+vpi+ pointing at a non-existent library must fail with a clear
# error (the dlopen-failure branch of the runtime loader).
test.top_filename = 't/t_flag_main_vpi.v'
test.compile(verilator_flags2=["--binary --vpi --public-flat-rw"])
test.execute(fails=True, all_run_flags=["+verilator+vpi+" + test.obj_dir + "/nonexistent.so"])
test.file_grep(test.run_log_filename, r'Cannot load VPI library')
test.passes()

View File

@ -0,0 +1,27 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
# Same design and VPI library as t_flag_main_vpi, but loaded via the
# +verilator+vpi+<path>:<name> named-bootstrap syntax instead of vlog_startup_routines[].
test.top_filename = 't/t_flag_main_vpi.v'
test.pli_filename = 't/t_flag_main_vpi.cpp'
test.compile(
make_pli=True,
verilator_flags2=["--binary --vpi --public-flat-rw"])
test.execute(
all_run_flags=["+verilator+vpi+" + test.obj_dir + "/libvpi.so:my_vpi_bootstrap"],
check_finished=True)
test.passes()

View File

@ -0,0 +1,25 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Second VPI test library for t_flag_main_vpi_multi
//
// A second, independent VPI library loaded alongside t_flag_main_vpi.cpp via a
// repeated +verilator+vpi+ argument, to verify multiple libraries are loaded.
// Its startup routine prints a marker proving it was loaded; it does not drive
// or finish the simulation (the first library does that).
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2025 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "vpi_user.h"
static void lib2_startup() { vpi_printf(const_cast<char*>("- second VPI library loaded\n")); }
// IEEE 1800 §37: vlog_startup_routines[] — null-terminated array of startup functions
extern "C" {
void (*vlog_startup_routines[])() = {lib2_startup, nullptr};
}

View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import os
import platform
import vltest_bootstrap
test.scenarios('vlt')
# Two +verilator+vpi+ arguments load two independent libraries: the first
# (t_flag_main_vpi.cpp) observes the design and calls $finish; the second
# (t_flag_main_vpi_lib2.cpp) prints a marker proving it too was loaded.
test.top_filename = 't/t_flag_main_vpi.v'
test.pli_filename = 't/t_flag_main_vpi.cpp'
test.compile(make_pli=True, verilator_flags2=["--binary --vpi --public-flat-rw"])
# Build the second VPI library (make_pli only builds libvpi.so), mirroring the
# driver's own pli flags.
root = os.environ['VERILATOR_ROOT']
pli2_cmd = [os.environ['CXX'], "-I" + root + "/include/vltstd", "-I" + root + "/include", "-fPIC",
"-shared"]
pli2_cmd += (["-Wl,-undefined,dynamic_lookup"] if platform.system() == 'Darwin' else ["-rdynamic"])
pli2_cmd += ["-o", test.obj_dir + "/libvpi2.so", "t/t_flag_main_vpi_lib2.cpp"]
test.run(logfile=test.obj_dir + "/pli2_compile.log", cmd=pli2_cmd)
test.execute(all_run_flags=[
"+verilator+vpi+" + test.obj_dir + "/libvpi.so",
"+verilator+vpi+" + test.obj_dir + "/libvpi2.so"
],
check_finished=True)
test.file_grep(test.run_log_filename, r'- second VPI library loaded')
test.passes()

View File

@ -0,0 +1,21 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: VPI test library lacking vlog_startup_routines
//
// Loaded via +verilator+vpi+<path> with no :<bootstrap> entry, to exercise
// the loader's error path when a library defines neither a named bootstrap
// nor the vlog_startup_routines[] array.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2025 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "vpi_user.h"
// Intentionally no vlog_startup_routines and no bootstrap; just some symbol so
// the shared object is non-empty and loads successfully.
extern "C" void t_flag_main_vpi_noarray_present() {}

View File

@ -0,0 +1,25 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
# A library loaded with no :<bootstrap> entry that lacks vlog_startup_routines
# must fail with a clear error (the missing-array branch of the loader).
test.top_filename = 't/t_flag_main_vpi.v'
test.pli_filename = 't/t_flag_main_vpi_noarray.cpp'
test.compile(make_pli=True, verilator_flags2=["--binary --vpi --public-flat-rw"])
test.execute(fails=True, all_run_flags=["+verilator+vpi+" + test.obj_dir + "/libvpi.so"])
test.file_grep(test.run_log_filename, r"Cannot find 'vlog_startup_routines'")
test.passes()

View File

@ -0,0 +1,29 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
# --vpi without --exe (no generated main, library-only output): the Makefile
# must still report VM_VPI = 1, but must NOT add '-rdynamic' since there is no
# executable to export VPI symbols from. Exercises the exe()==false branch of
# the VPI -rdynamic gate in V3EmitMk.
test.top_filename = 't/t_flag_main_vpi.v'
test.compile(make_main=False, verilator_make_gmake=False, verilator_flags2=["--vpi --timing"])
test.file_grep(test.obj_dir + "/V" + test.name + "_classes.mk", r'VM_VPI = 1')
# Without --exe there is no executable to export symbols from or to dlopen into,
# so the runtime-VPI link flags must not be emitted into the Makefile at all.
mk = test.obj_dir + "/V" + test.name + ".mk"
test.file_grep_not(mk, r'-rdynamic')
test.file_grep_not(mk, r'-ldl')
test.passes()

View File

@ -0,0 +1,27 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
# Compile with --binary but WITHOUT --vpi.
# Passing +verilator+vpi+ at runtime should emit a warning, not load anything.
test.compile(
top_filename='t/t_flag_main.v',
verilator_flags2=["--binary"])
test.execute(
all_run_flags=["+verilator+vpi+/nonexistent.so"],
check_finished=True)
test.file_grep(test.run_log_filename,
r'%Warning: COMMAND_LINE:0: \+verilator\+vpi\+ ignored: simulation was not compiled with --vpi')
test.passes()