From b332c736dfea861dcbe107c7a704f7a74da90bcb Mon Sep 17 00:00:00 2001 From: Matthew Ballance Date: Sun, 7 Jun 2026 14:27:36 +0000 Subject: [PATCH] Updates from feedback ; move location of loading function ; Use gold file for all tests Signed-off-by: Matthew Ballance --- bin/verilator | 2 +- docs/guide/exe_sim.rst | 8 +-- include/verilated.cpp | 60 +++++++++++++++++++- include/verilated.mk.in | 3 - include/verilated_funcs.h | 5 ++ src/V3EmitCMain.cpp | 58 +------------------ test_regress/t/t_flag_main_vpi.cpp | 2 +- test_regress/t/t_flag_main_vpi.out | 3 +- test_regress/t/t_flag_main_vpi.py | 6 +- test_regress/t/t_flag_main_vpi_badentry.out | 2 + test_regress/t/t_flag_main_vpi_badentry.py | 5 +- test_regress/t/t_flag_main_vpi_badlib.out | 2 + test_regress/t/t_flag_main_vpi_badlib.py | 8 ++- test_regress/t/t_flag_main_vpi_bootstrap.out | 2 + test_regress/t/t_flag_main_vpi_bootstrap.py | 3 +- test_regress/t/t_flag_main_vpi_lib2.cpp | 2 +- test_regress/t/t_flag_main_vpi_multi.out | 3 + test_regress/t/t_flag_main_vpi_multi.py | 5 +- test_regress/t/t_flag_main_vpi_noarray.out | 2 + test_regress/t/t_flag_main_vpi_noarray.py | 6 +- test_regress/t/t_flag_main_vpi_nowarn.out | 3 + test_regress/t/t_flag_main_vpi_nowarn.py | 11 ++-- 22 files changed, 111 insertions(+), 90 deletions(-) create mode 100644 test_regress/t/t_flag_main_vpi_badentry.out create mode 100644 test_regress/t/t_flag_main_vpi_badlib.out create mode 100644 test_regress/t/t_flag_main_vpi_bootstrap.out create mode 100644 test_regress/t/t_flag_main_vpi_multi.out create mode 100644 test_regress/t/t_flag_main_vpi_noarray.out create mode 100644 test_regress/t/t_flag_main_vpi_nowarn.out diff --git a/bin/verilator b/bin/verilator index a2d3866f5..cb866e243 100755 --- a/bin/verilator +++ b/bin/verilator @@ -653,7 +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+vpi+[:] Load VPI shared library +verilator+wno+unsatconstr+ Disable constraint warnings diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index 968f5a632..de5f990fe 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -146,14 +146,12 @@ Options: :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. + 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 linked into the model. + argument is rejected and the VPI code must instead be statically linked + into the model. .. option:: +verilator+wno+unsatconstr+ diff --git a/include/verilated.cpp b/include/verilated.cpp index 1e3b1a256..f7eb3039e 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 // Used by vl_load_vpi_libs +# 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) + +#if VM_VPI +void vl_load_vpi_libs(int argc, char** argv) VL_MT_UNSAFE { + const char* prefix = "+verilator+vpi+"; + const size_t pfx_len = std::strlen(prefix); + for (int i = 1; i < argc; ++i) { + if (std::strncmp(argv[i], prefix, pfx_len) != 0) continue; + const std::string arg(argv[i] + pfx_len); + if (arg.empty()) continue; +#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 + } +} +#endif + //=========================================================================== // Debug prints @@ -3552,7 +3610,7 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) { #if !VM_VPI VL_WARN_MT( "COMMAND_LINE", 0, "", - ("+verilator+vpi+ ignored: simulation was not compiled with --vpi (" + arg + ")") + ("+verilator+vpi+ ignored: simulation was not compiled with --vpi '" + arg + "'") .c_str()); #endif } else if (arg == "+verilator+V") { diff --git a/include/verilated.mk.in b/include/verilated.mk.in index 17ef6118a..21c465258 100644 --- a/include/verilated.mk.in +++ b/include/verilated.mk.in @@ -80,9 +80,6 @@ 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 \ diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index d69bca256..71c0bf729 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -74,6 +74,11 @@ extern void VL_FATAL_MT(const char* filename, int linenum, const char* hier, extern void VL_WARN_MT(const char* filename, int linenum, const char* hier, const char* msg) VL_MT_SAFE; +#if VM_VPI +/// Load VPI shared libraries requested via +verilator+vpi+[:]. +extern void vl_load_vpi_libs(int argc, char** argv) VL_MT_UNSAFE; +#endif + /// Print a string, multithread safe. Eventually VL_PRINTF will get called. extern void VL_PRINTF_MT(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; diff --git a/src/V3EmitCMain.cpp b/src/V3EmitCMain.cpp index 2ec909b3c..21d701c05 100644 --- a/src/V3EmitCMain.cpp +++ b/src/V3EmitCMain.cpp @@ -55,13 +55,7 @@ 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"); - puts("#ifndef _WIN32\n"); - puts("# include // For runtime +verilator+vpi+ library loading\n"); - puts("#endif\n"); - puts("#include \n"); - } + if (v3Global.opt.vpi()) puts("#include \"verilated_vpi.h\"\n"); puts("#include \"" + EmitCUtil::topClassName() + ".h\"\n"); if (v3Global.opt.debugRuntimeTimeout()) { puts("\n"); @@ -73,56 +67,6 @@ 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"); diff --git a/test_regress/t/t_flag_main_vpi.cpp b/test_regress/t/t_flag_main_vpi.cpp index 28c4d3f9e..1be31452a 100644 --- a/test_regress/t/t_flag_main_vpi.cpp +++ b/test_regress/t/t_flag_main_vpi.cpp @@ -62,7 +62,7 @@ static PLI_INT32 start_of_sim_cb(p_cb_data /*cb_data*/) { } static PLI_INT32 end_of_sim_cb(p_cb_data /*cb_data*/) { - vpi_printf(const_cast("- VPI end of simulation\n")); + vpi_printf(const_cast("VPI end of simulation\n")); return 0; } diff --git a/test_regress/t/t_flag_main_vpi.out b/test_regress/t/t_flag_main_vpi.out index c2a232589..6a9a7f37b 100644 --- a/test_regress/t/t_flag_main_vpi.out +++ b/test_regress/t/t_flag_main_vpi.out @@ -1 +1,2 @@ -- VPI end of simulation +*-* 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 index 43282cb6a..dbb186f02 100755 --- a/test_regress/t/t_flag_main_vpi.py +++ b/test_regress/t/t_flag_main_vpi.py @@ -24,6 +24,10 @@ 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) +# The VPI library's output (observed 'count' reaching MAX_TICKS, then end-of-sim) is +# checked against the golden .out file. +test.execute(all_run_flags=["+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_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 index 15e7f31e2..15398a3e5 100755 --- a/test_regress/t/t_flag_main_vpi_badentry.py +++ b/test_regress/t/t_flag_main_vpi_badentry.py @@ -19,8 +19,7 @@ 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'") + 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 index 1ed3105e8..2ac211d49 100755 --- a/test_regress/t/t_flag_main_vpi_badlib.py +++ b/test_regress/t/t_flag_main_vpi_badlib.py @@ -17,8 +17,10 @@ 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') +# 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 index ecf4e5f65..4c5c21e70 100755 --- a/test_regress/t/t_flag_main_vpi_bootstrap.py +++ b/test_regress/t/t_flag_main_vpi_bootstrap.py @@ -19,6 +19,7 @@ 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) + 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 index b8c8f617f..8f5bba74b 100644 --- a/test_regress/t/t_flag_main_vpi_lib2.cpp +++ b/test_regress/t/t_flag_main_vpi_lib2.cpp @@ -17,7 +17,7 @@ #include "vpi_user.h" -static void lib2_startup() { vpi_printf(const_cast("- second VPI library loaded\n")); } +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" { 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 index ee8244242..af3e301ee 100755 --- a/test_regress/t/t_flag_main_vpi_multi.py +++ b/test_regress/t/t_flag_main_vpi_multi.py @@ -36,8 +36,7 @@ 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') + check_finished=True, + expect_filename=test.golden_filename) test.passes() 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 index 857365a1a..9dd2ac853 100755 --- a/test_regress/t/t_flag_main_vpi_noarray.py +++ b/test_regress/t/t_flag_main_vpi_noarray.py @@ -18,8 +18,8 @@ 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.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_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 index 740b72e77..ab077f4f5 100755 --- a/test_regress/t/t_flag_main_vpi_nowarn.py +++ b/test_regress/t/t_flag_main_vpi_nowarn.py @@ -15,11 +15,10 @@ test.scenarios('vlt') # 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' -) +# 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()