From 47b3e6dd6cdc7d2d574d1b8cca0e86d94656ee97 Mon Sep 17 00:00:00 2001 From: Matthew Ballance Date: Sat, 6 Jun 2026 19:55:23 +0000 Subject: [PATCH] Add support for dynamically loading VPI extensions on POSIX platforms Signed-off-by: Matthew Ballance --- bin/verilator | 1 + docs/guide/exe_sim.rst | 16 ++++ include/verilated.cpp | 9 ++ include/verilated.mk.in | 4 + src/V3EmitCMain.cpp | 57 ++++++++++++- src/V3EmitMk.cpp | 17 ++++ test_regress/t/t_flag_main_vpi.cpp | 94 +++++++++++++++++++++ test_regress/t/t_flag_main_vpi.out | 1 + test_regress/t/t_flag_main_vpi.py | 32 +++++++ test_regress/t/t_flag_main_vpi.v | 28 ++++++ test_regress/t/t_flag_main_vpi_badentry.py | 26 ++++++ test_regress/t/t_flag_main_vpi_badlib.py | 24 ++++++ test_regress/t/t_flag_main_vpi_bootstrap.py | 27 ++++++ test_regress/t/t_flag_main_vpi_lib2.cpp | 25 ++++++ test_regress/t/t_flag_main_vpi_multi.py | 41 +++++++++ test_regress/t/t_flag_main_vpi_noarray.cpp | 21 +++++ test_regress/t/t_flag_main_vpi_noarray.py | 25 ++++++ test_regress/t/t_flag_main_vpi_noexe.py | 29 +++++++ test_regress/t/t_flag_main_vpi_nowarn.py | 27 ++++++ 19 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 test_regress/t/t_flag_main_vpi.cpp create mode 100644 test_regress/t/t_flag_main_vpi.out create mode 100644 test_regress/t/t_flag_main_vpi.py create mode 100644 test_regress/t/t_flag_main_vpi.v create mode 100644 test_regress/t/t_flag_main_vpi_badentry.py create mode 100644 test_regress/t/t_flag_main_vpi_badlib.py create mode 100644 test_regress/t/t_flag_main_vpi_bootstrap.py create mode 100644 test_regress/t/t_flag_main_vpi_lib2.cpp create mode 100644 test_regress/t/t_flag_main_vpi_multi.py create mode 100644 test_regress/t/t_flag_main_vpi_noarray.cpp create mode 100644 test_regress/t/t_flag_main_vpi_noarray.py create mode 100644 test_regress/t/t_flag_main_vpi_noexe.py create mode 100644 test_regress/t/t_flag_main_vpi_nowarn.py diff --git a/bin/verilator b/bin/verilator index 57fd60507..a2d3866f5 100755 --- a/bin/verilator +++ b/bin/verilator @@ -653,6 +653,7 @@ description of these arguments. +verilator+solver+file+ Set random solver log filename +verilator+V Show verbose version and config +verilator+version Show version and exit + +verilator+vpi+[:] Load VPI shared library (requires --vpi --main) +verilator+wno+unsatconstr+ Disable constraint warnings diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index ac2b73f16..968f5a632 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -139,6 +139,22 @@ Options: Displays program version and exits. +.. option:: +verilator+vpi+[:] + + Load a VPI shared library before simulation starts. Only available when the + model was Verilated with :vlopt:`--vpi` and :vlopt:`--main` (or + :vlopt:`--binary`). ```` is the path to the shared library. If + ``:`` 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+ Disable unsatisfied constraint warnings at simulation runtime. When set to diff --git a/include/verilated.cpp b/include/verilated.cpp index 9cbb58ffc..698ecaedd 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -3544,6 +3544,15 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) { // and the run can be reproduced by passing +verilator+seed+. if (u64 == 0) u64 = pickRandomSeed(); randSeed(static_cast(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, "", diff --git a/include/verilated.mk.in b/include/verilated.mk.in index 9d3f3b2e7..17ef6118a 100644 --- a/include/verilated.mk.in +++ b/include/verilated.mk.in @@ -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 diff --git a/src/V3EmitCMain.cpp b/src/V3EmitCMain.cpp index 000567eaf..bef1524c4 100644 --- a/src/V3EmitCMain.cpp +++ b/src/V3EmitCMain.cpp @@ -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 // For runtime +verilator+vpi+ library loading\n"); + puts("#endif\n"); + puts("#include \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+. + // POSIX only: relies on the executable exporting its VPI symbols. + puts("// Load VPI shared libraries requested via +verilator+vpi+[:]\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 : 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(" + "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(\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+\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++)" diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp index f76945279..2b1eca99b 100644 --- a/src/V3EmitMk.cpp +++ b/src/V3EmitMk.cpp @@ -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+) 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 diff --git a/test_regress/t/t_flag_main_vpi.cpp b/test_regress/t/t_flag_main_vpi.cpp new file mode 100644 index 000000000..2ea90b86f --- /dev/null +++ b/test_regress/t/t_flag_main_vpi.cpp @@ -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+ 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 +#include + +// 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("*-* 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("t.count"), nullptr); + if (!s_count_handle) { + vpi_printf(const_cast("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("- 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 :my_vpi_bootstrap +void my_vpi_bootstrap() { register_callbacks(); } +} diff --git a/test_regress/t/t_flag_main_vpi.out b/test_regress/t/t_flag_main_vpi.out new file mode 100644 index 000000000..c2a232589 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi.out @@ -0,0 +1 @@ +- VPI end of simulation diff --git a/test_regress/t/t_flag_main_vpi.py b/test_regress/t/t_flag_main_vpi.py new file mode 100644 index 000000000..70e765911 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi.py @@ -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() diff --git a/test_regress/t/t_flag_main_vpi.v b/test_regress/t/t_flag_main_vpi.v new file mode 100644 index 000000000..083200958 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi.v @@ -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 diff --git a/test_regress/t/t_flag_main_vpi_badentry.py b/test_regress/t/t_flag_main_vpi_badentry.py new file mode 100644 index 000000000..15e7f31e2 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_badentry.py @@ -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 : 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() diff --git a/test_regress/t/t_flag_main_vpi_badlib.py b/test_regress/t/t_flag_main_vpi_badlib.py new file mode 100644 index 000000000..1ed3105e8 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_badlib.py @@ -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() diff --git a/test_regress/t/t_flag_main_vpi_bootstrap.py b/test_regress/t/t_flag_main_vpi_bootstrap.py new file mode 100644 index 000000000..6a7283eeb --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_bootstrap.py @@ -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+: 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() diff --git a/test_regress/t/t_flag_main_vpi_lib2.cpp b/test_regress/t/t_flag_main_vpi_lib2.cpp new file mode 100644 index 000000000..b918d0683 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_lib2.cpp @@ -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("- 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}; +} diff --git a/test_regress/t/t_flag_main_vpi_multi.py b/test_regress/t/t_flag_main_vpi_multi.py new file mode 100644 index 000000000..cbd691016 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_multi.py @@ -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() diff --git a/test_regress/t/t_flag_main_vpi_noarray.cpp b/test_regress/t/t_flag_main_vpi_noarray.cpp new file mode 100644 index 000000000..02568d282 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_noarray.cpp @@ -0,0 +1,21 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: VPI test library lacking vlog_startup_routines +// +// Loaded via +verilator+vpi+ with no : 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() {} diff --git a/test_regress/t/t_flag_main_vpi_noarray.py b/test_regress/t/t_flag_main_vpi_noarray.py new file mode 100644 index 000000000..857365a1a --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_noarray.py @@ -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 : 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() diff --git a/test_regress/t/t_flag_main_vpi_noexe.py b/test_regress/t/t_flag_main_vpi_noexe.py new file mode 100644 index 000000000..8daa43531 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_noexe.py @@ -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() diff --git a/test_regress/t/t_flag_main_vpi_nowarn.py b/test_regress/t/t_flag_main_vpi_nowarn.py new file mode 100644 index 000000000..07ab25bcd --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_nowarn.py @@ -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()