diff --git a/bin/verilator b/bin/verilator index 57fd60507..cb866e243 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 +verilator+wno+unsatconstr+ Disable constraint warnings diff --git a/configure.ac b/configure.ac index 6917f0c4b..00d8ac713 100644 --- a/configure.ac +++ b/configure.ac @@ -589,6 +589,34 @@ m4_foreach([ldflag], [ AC_SUBST(CFG_LDLIBS_THREADS) AC_SUBST(CFG_LDFLAGS_THREADS_CMAKE) +# Find link flags for runtime VPI library loading (+verilator+vpi+). +# The model executable must export its VPI symbols so the dlopen'd library can +# resolve them: -rdynamic (GNU ld) or -Wl,-export_dynamic (Darwin); the first the +# linker accepts wins. -ldl provides dlopen/dlsym where it is a separate library. +_MY_LDLIBS_CHECK_SET(CFG_LDFLAGS_DYNAMIC, -rdynamic) +# -Wl,-export_dynamic contains a comma, so probe it directly rather than through +# the _MY_LDLIBS_CHECK_* macros (which re-expand their flag argument unquoted). +if test "$CFG_LDFLAGS_DYNAMIC" = ""; then + ACO_SAVE_LIBS="$LIBS" + LIBS="$LIBS -Wl,-export_dynamic" + AC_MSG_CHECKING([whether $CXX linker accepts -Wl,-export_dynamic]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[]])], + [_my_result=yes + if test -s conftest.err; then + if grep -e "-export_dynamic" conftest.err >/dev/null; then + _my_result=no + fi + fi], + [_my_result=no]) + AC_MSG_RESULT($_my_result) + LIBS="$ACO_SAVE_LIBS" + if test "$_my_result" = "yes"; then CFG_LDFLAGS_DYNAMIC="-Wl,-export_dynamic"; fi +fi +AC_SUBST(CFG_LDFLAGS_DYNAMIC) +_MY_LDLIBS_CHECK_OPT(CFG_LDLIBS_DYNAMIC, -ldl) +AC_SUBST(CFG_LDLIBS_DYNAMIC) + # If 'mold' is installed, use it to link for faster buildtimes _MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_SRC, -fuse-ld=mold) _MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_VERILATED, -fuse-ld=mold) diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index ac2b73f16..190367346 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -139,6 +139,20 @@ 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 38.37.2) is + invoked. May be repeated to load multiple libraries. + + 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 statically 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..a92c6ca5f 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -89,6 +89,12 @@ # include # define _VL_HAVE_GETRLIMIT #endif +#if VM_VPI +# include +# ifndef _WIN32 +# include // dlopen +# endif +#endif #include "verilated_threads.h" // clang-format on @@ -260,6 +266,58 @@ void VL_WARN_MT(const char* filename, int linenum, const char* hier, const char* }}); } +//=========================================================================== +// Runtime VPI shared library loading (--vpi) + +// Load one VPI shared library named by a +verilator+vpi+[:] argument. +// 'arg' is the payload after the prefix: either "" (invoke the library's +// vlog_startup_routines array) or ":" (invoke the named bootstrap). +void Verilated::loadVpiLib(const std::string& arg) VL_MT_UNSAFE { +#if VM_VPI + if (arg.empty()) return; +#ifdef _WIN32 + VL_FATAL_MT("", 0, "", + "+verilator+vpi+: runtime VPI library loading is not supported on" + " Windows; link the VPI code into the model instead"); +#else + using vlog_startup_t = void (*)(); + // Split : on the last ':' + const std::string::size_type colon_pos = arg.rfind(':'); + const bool has_entry = (colon_pos != std::string::npos); + const std::string libpath = has_entry ? arg.substr(0, colon_pos) : arg; + const std::string entry_name = has_entry ? arg.substr(colon_pos + 1) : std::string{}; + void* handle = dlopen(libpath.c_str(), RTLD_LAZY); + if (!handle) + // The library path is stable; the dlerror() text is platform-specific, so put it on + // a separate "- " line (test golden files strip "- " lines, keeping output portable). + VL_FATAL_MT( + "", 0, "", + (std::string{"Cannot load VPI library: "} + libpath + "\n- dlerror: " + dlerror()) + .c_str()); + if (has_entry) { + vlog_startup_t bsp = reinterpret_cast(dlsym(handle, entry_name.c_str())); + if (!bsp) + VL_FATAL_MT( + "", 0, "", + (std::string{"Cannot find VPI bootstrap '"} + entry_name + "' in: " + libpath) + .c_str()); + bsp(); + } else { + vlog_startup_t* routinesp + = reinterpret_cast(dlsym(handle, "vlog_startup_routines")); + if (!routinesp) + VL_FATAL_MT( + "", 0, "", + (std::string{"Cannot find 'vlog_startup_routines' in: "} + libpath).c_str()); + for (int j = 0; routinesp[j]; ++j) routinesp[j](); + } +#endif +#else + // Never reached: the command-line handler only calls this when compiled with --vpi. + (void)arg; +#endif +} + //=========================================================================== // Debug prints @@ -3544,6 +3602,17 @@ 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 (commandArgVlString(arg, "+verilator+vpi+", str)) { + // With --vpi, load the requested shared library now. Without --vpi there is + // no VPI runtime, so warn the argument is ignored. +#if VM_VPI + Verilated::loadVpiLib(str); +#else + VL_WARN_MT( + "COMMAND_LINE", 0, "", + ("+verilator+vpi+ ignored: simulation was not compiled with --vpi '" + arg + "'") + .c_str()); // LCOV_EXCL_LINE (gcov zeroes this wrapped continuation line) +#endif } else if (arg == "+verilator+V") { VerilatedImp::versionDump(); // Someday more info too VL_FATAL_MT("COMMAND_LINE", 0, "", diff --git a/include/verilated.h b/include/verilated.h index 59ae1ad8c..73c7e5b2a 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -1036,6 +1036,9 @@ public: static void scTraceBeforeElaborationError() VL_ATTR_NORETURN VL_MT_SAFE; static void stackCheck(QData needSize) VL_MT_UNSAFE; + // Internal: Load a VPI shared library (+verilator+vpi+[:]) + static void loadVpiLib(const std::string& arg) VL_MT_UNSAFE; + // Internal: Get and set DPI context static const VerilatedScope* dpiScope() VL_MT_SAFE { return t_s.t_dpiScopep; } static void dpiScope(const VerilatedScope* scopep) VL_MT_SAFE { t_s.t_dpiScopep = scopep; } diff --git a/include/verilated.mk.in b/include/verilated.mk.in index 9d3f3b2e7..a90907755 100644 --- a/include/verilated.mk.in +++ b/include/verilated.mk.in @@ -52,6 +52,9 @@ CFG_GCH_IF_CLANG = @CFG_GCH_IF_CLANG@ CFG_LDFLAGS_VERILATED = @CFG_LDFLAGS_VERILATED@ # Linker libraries for multithreading CFG_LDLIBS_THREADS = @CFG_LDLIBS_THREADS@ +# Linker flags/libraries for runtime VPI library loading (+verilator+vpi+) +CFG_LDFLAGS_DYNAMIC = @CFG_LDFLAGS_DYNAMIC@ +CFG_LDLIBS_DYNAMIC = @CFG_LDLIBS_DYNAMIC@ ###################################################################### # Programs @@ -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..0d3fd0b6d 100644 --- a/src/V3EmitCMain.cpp +++ b/src/V3EmitCMain.cpp @@ -104,6 +104,8 @@ private: puts("\n"); if (v3Global.opt.vpi()) { + // VPI shared libraries requested via +verilator+vpi+ are loaded by + // contextp->commandArgs() above, before the statically-linked startup routines. 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..a63d328f3 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 emitted by emitOverallMake() after + // verilated.mk is included (so $(CFG_LDFLAGS_DYNAMIC)/$(CFG_LDLIBS_DYNAMIC) are 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 the executable to + // export its VPI symbols so the dlopen'd library can resolve them, plus the + // dl library for dlopen/dlsym. The exact flags are probed at configure time + // (CFG_LDFLAGS_DYNAMIC / CFG_LDLIBS_DYNAMIC in verilated.mk). + of.puts("# Runtime VPI library loading (+verilator+vpi+) link requirements\n"); + of.puts("LDFLAGS += $(CFG_LDFLAGS_DYNAMIC)\n"); + of.puts("LDLIBS += $(CFG_LDLIBS_DYNAMIC)\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..1be31452a --- /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 section 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..6a9a7f37b --- /dev/null +++ b/test_regress/t/t_flag_main_vpi.out @@ -0,0 +1,2 @@ +*-* All Finished *-* +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 100755 index 000000000..695b70e76 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi.py @@ -0,0 +1,30 @@ +#!/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"]) + +# Run the generated binary; load the VPI library via the +verilator+vpi+ plusarg. +# The VPI library's output (observed 'count' reaching MAX_TICKS, then end-of-sim) is +# checked against the golden .out file. +# Also pass a non-VPI plusarg (skipped by the loader's prefix check) and a bare +# +verilator+vpi+ with an empty payload (skipped by the empty-arg check), so both +# loader-skip branches are exercised alongside the real library load. +test.execute(all_run_flags=[ + "+othertest", "+verilator+vpi+", "+verilator+vpi+" + test.obj_dir + "/libvpi.so" +], + check_finished=True, + expect_filename=test.golden_filename) + +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..c688405ca --- /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.out b/test_regress/t/t_flag_main_vpi_badentry.out new file mode 100644 index 000000000..6283fa03f --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_badentry.out @@ -0,0 +1,2 @@ +%Error: Cannot find VPI bootstrap 'no_such_fn' in: obj_vlt/t_flag_main_vpi_badentry/libvpi.so +Aborting... 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 100755 index 000000000..15398a3e5 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_badentry.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 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"], + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_flag_main_vpi_badlib.out b/test_regress/t/t_flag_main_vpi_badlib.out new file mode 100644 index 000000000..752708e9e --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_badlib.out @@ -0,0 +1,2 @@ +%Error: Cannot load VPI library: obj_vlt/t_flag_main_vpi_badlib/nonexistent.so +Aborting... 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 100755 index 000000000..2ac211d49 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_badlib.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') + +# +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"]) + +# The fatal names the (stable, relative) library path; the platform-specific dlerror() +# detail is emitted on a "- " line, which golden comparison strips, so the .out is portable. +test.execute(fails=True, + all_run_flags=["+verilator+vpi+" + test.obj_dir + "/nonexistent.so"], + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_flag_main_vpi_bootstrap.out b/test_regress/t/t_flag_main_vpi_bootstrap.out new file mode 100644 index 000000000..6a9a7f37b --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_bootstrap.out @@ -0,0 +1,2 @@ +*-* All Finished *-* +VPI end of simulation 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 100755 index 000000000..4c5c21e70 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_bootstrap.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') + +# 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, + expect_filename=test.golden_filename) + +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..8f5bba74b --- /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 section 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.out b/test_regress/t/t_flag_main_vpi_multi.out new file mode 100644 index 000000000..04f3f5e6d --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_multi.out @@ -0,0 +1,3 @@ +second VPI library loaded +*-* All Finished *-* +VPI end of simulation 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 100755 index 000000000..af3e301ee --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_multi.py @@ -0,0 +1,42 @@ +#!/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, + expect_filename=test.golden_filename) + +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.out b/test_regress/t/t_flag_main_vpi_noarray.out new file mode 100644 index 000000000..a7f54a6af --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_noarray.out @@ -0,0 +1,2 @@ +%Error: Cannot find 'vlog_startup_routines' in: obj_vlt/t_flag_main_vpi_noarray/libvpi.so +Aborting... 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 100755 index 000000000..9dd2ac853 --- /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"], + expect_filename=test.golden_filename) + +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 100755 index 000000000..1f347105b --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_noexe.py @@ -0,0 +1,30 @@ +#!/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 the runtime-VPI link flags since +# there is no executable to export VPI symbols from. Exercises the exe()==false +# branch of the VPI link-flag 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 (CFG_LDFLAGS_DYNAMIC/CFG_LDLIBS_DYNAMIC, probed at +# configure time) must not be referenced in the generated Makefile. +mk = test.obj_dir + "/V" + test.name + ".mk" +test.file_grep_not(mk, r'CFG_LDFLAGS_DYNAMIC') +test.file_grep_not(mk, r'CFG_LDLIBS_DYNAMIC') + +test.passes() diff --git a/test_regress/t/t_flag_main_vpi_nowarn.out b/test_regress/t/t_flag_main_vpi_nowarn.out new file mode 100644 index 000000000..e51042279 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_nowarn.out @@ -0,0 +1,3 @@ +%Warning: COMMAND_LINE:0: +verilator+vpi+ ignored: simulation was not compiled with --vpi '+verilator+vpi+/nonexistent.so' +[0] Hello +*-* All Finished *-* 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 100755 index 000000000..ab077f4f5 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_nowarn.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') + +# 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"]) + +# Without --vpi there is no loader, so the plusarg is ignored with a warning (checked via +# the golden .out). The plusarg value is fixed, so the warning text is portable. +test.execute(all_run_flags=["+verilator+vpi+/nonexistent.so"], + check_finished=True, + expect_filename=test.golden_filename) + +test.passes()