diff --git a/Changes b/Changes index 6f6cb2cd0..a13c9c8e1 100644 --- a/Changes +++ b/Changes @@ -13,6 +13,7 @@ Verilator 5.047 devel **Other:** +* Add VPI callback support to --main (#7145). * Fix wide conditional short circuiting (#7155). diff --git a/include/verilated.mk.in b/include/verilated.mk.in index 7a14bc558..8669bd330 100644 --- a/include/verilated.mk.in +++ b/include/verilated.mk.in @@ -114,7 +114,7 @@ CPPFLAGS += $(OPT) # On macOS, specify all weak symbols as dynamic_lookup. # Otherwise, you get undefined symbol errors. ifeq ($(UNAME_S),Darwin) - LDFLAGS += -Wl,-U,__Z15vl_time_stamp64v,-U,__Z13sc_time_stampv + LDFLAGS += -Wl,-U,__Z15vl_time_stamp64v,-U,__Z13sc_time_stampv,-U,vlog_startup_routines endif # Allow upper level user makefiles to specify flags they want. diff --git a/include/verilated_vpi.cpp b/include/verilated_vpi.cpp index 7bcee1bdc..004ebe33c 100644 --- a/include/verilated_vpi.cpp +++ b/include/verilated_vpi.cpp @@ -716,7 +716,7 @@ public: // cppcheck-suppress uninitVar // m_value VerilatedVpiCbHolder(uint64_t id, const s_cb_data* cbDatap, const VerilatedVpioVar* varop) : m_id{id} - , m_cbData{*cbDatap} + , m_cbData{*cbDatap} // vpi_register_cb checks cbDatap is non-null , m_varo{varop} { m_value.format = cbDatap->value ? cbDatap->value->format : vpiSuppressVal; m_cbData.value = &m_value; diff --git a/src/V3EmitCMain.cpp b/src/V3EmitCMain.cpp index 46734c540..624ae5b72 100644 --- a/src/V3EmitCMain.cpp +++ b/src/V3EmitCMain.cpp @@ -55,13 +55,21 @@ 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("#include \"" + EmitCUtil::topClassName() + ".h\"\n"); if (v3Global.opt.debugRuntimeTimeout()) { puts("\n"); puts("#include \n"); } + puts("\n"); - puts("\n//======================\n\n"); + if (v3Global.opt.vpi()) { + 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"); + } + + puts("//======================\n\n"); if (v3Global.opt.debugRuntimeTimeout()) { puts("void alarmHandler(int signum) {\n"); @@ -92,18 +100,43 @@ private: + EmitCUtil::topClassName() + "{contextp.get(), \"" + topName + "\"}};\n"); puts("\n"); + if (v3Global.opt.vpi()) { + 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++)" + " (*routinep)();\n"); + puts("}\n"); + puts("VerilatedVpi::callCbs(cbStartOfSimulation);\n"); + puts("\n"); + } + puts("// Simulate until $finish\n"); puts("while (VL_LIKELY(!contextp->gotFinish())) {\n"); + if (v3Global.opt.vpi()) { + puts(/**/ "// VPI callbacks\n"); + puts(/**/ "VerilatedVpi::callTimedCbs();\n"); + puts(/**/ "VerilatedVpi::callCbs(cbNextSimTime);\n"); // Before next event queue + puts(/**/ "VerilatedVpi::callCbs(cbAtStartOfSimTime);\n"); // Before time queue + } puts(/**/ "// Evaluate model\n"); puts(/**/ "topp->eval();\n"); + if (v3Global.opt.vpi()) { + puts(/**/ "// VPI callbacks\n"); + puts(/**/ "VerilatedVpi::callValueCbs();\n"); + puts(/**/ "VerilatedVpi::callCbs(cbAtEndOfSimTime);\n"); // After nonblocking events + puts(/**/ "VerilatedVpi::callCbs(cbReadWriteSynch);\n"); // After a specified time + puts(/**/ "VerilatedVpi::callCbs(cbReadOnlySynch);\n"); // After cbReadWriteSynch + } puts(/**/ "// Advance time\n"); if (v3Global.rootp()->delaySchedulerp() || v3Global.opt.timing()) { puts("if (!topp->eventsPending()) break;\n"); } - if (v3Global.rootp()->delaySchedulerp()) { - puts("contextp->time(topp->nextTimeSlot());\n"); + const std::string nextSlot = v3Global.rootp()->delaySchedulerp() ? "topp->nextTimeSlot()" + : "contextp->time() + 1"; + if (v3Global.opt.vpi()) { + puts("contextp->time(std::min("s + nextSlot + ", VerilatedVpi::cbNextDeadline()));\n"); } else { - puts("contextp->timeInc(1);\n"); + puts("contextp->time("s + nextSlot + ");\n"); } puts("}\n"); @@ -116,6 +149,7 @@ private: puts("// Execute 'final' processes\n"); puts("topp->final();\n"); + if (v3Global.opt.vpi()) puts("VerilatedVpi::callCbs(cbEndOfSimulation);\n"); puts("\n"); if (v3Global.opt.coverage()) { diff --git a/test_regress/t/t_vpi_dump.cpp b/test_regress/t/t_vpi_dump.cpp index 6e9e9d950..b8ed57511 100644 --- a/test_regress/t/t_vpi_dump.cpp +++ b/test_regress/t/t_vpi_dump.cpp @@ -159,7 +159,7 @@ void vpi_compat_bootstrap(void) { // We're able to call vpi_main() here on Verilator/Xcelium, // but Icarus complains (rightfully so) - s_cb_data cb_data; + s_cb_data cb_data{}; s_vpi_time vpi_time; vpi_time.high = 0; diff --git a/test_regress/t/t_vpi_empty.py b/test_regress/t/t_vpi_empty.py new file mode 100755 index 000000000..46dfce8c1 --- /dev/null +++ b/test_regress/t/t_vpi_empty.py @@ -0,0 +1,19 @@ +#!/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: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# Checks that --vpi without a C file doesn't break builds +test.compile(verilator_flags2=["--binary --vpi"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_vpi_empty.v b/test_regress/t/t_vpi_empty.v new file mode 100644 index 000000000..44d9f7881 --- /dev/null +++ b/test_regress/t/t_vpi_empty.v @@ -0,0 +1,9 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial $finish; +endmodule diff --git a/test_regress/t/t_vpi_onetime_cbs.cpp b/test_regress/t/t_vpi_onetime_cbs.cpp index 441ffae76..2c021d727 100644 --- a/test_regress/t/t_vpi_onetime_cbs.cpp +++ b/test_regress/t/t_vpi_onetime_cbs.cpp @@ -12,21 +12,10 @@ // Setup multiple one-time callbacks with different time delays. // Ensure they are not called before the delay has elapsed. -#ifdef IS_VPI - #include "vpi_user.h" #include -#else - -#include "verilated.h" -#include "verilated_vpi.h" - -#include VM_PREFIX_INCLUDE - -#endif - // These require the above. Comment prevents clang-format moving them #include "TestSimulator.h" #include "TestVpi.h" @@ -45,7 +34,7 @@ typedef struct { static cb_stats CallbackStats[cbAtEndOfSimTime + 1]; -bool got_error = false; +int errors = 0; static vpiHandle ValueHandle, ToggleHandle, ClockHandle; @@ -80,28 +69,40 @@ bool cb_time_is_delay(int cb_name) { return false; } +vpiHandle test_vpi_register_cb(p_cb_data cb_datap) { + if (verbose) + vpi_printf(const_cast("- test_vpi_register_cb(%s @%d)\n"), + strFromVpiCallbackReason(cb_datap->reason), + (cb_datap->time ? cb_datap->time->low : 0)); + return vpi_register_cb(cb_datap); +} + // forward declaration static PLI_INT32 TheCallback(s_cb_data* data); static PLI_INT32 AtEndOfSimTimeCallback(s_cb_data* data) { s_vpi_time t; + CHECK_RESULT(data->reason, cbAtEndOfSimTime); cb_stats* stats = &CallbackStats[data->reason]; t.type = vpiSimTime; vpi_get_time(0, &t); - if (verbose) vpi_printf(const_cast("- [@%d] AtEndOfSimTime Callback\n"), t.low); + if (verbose) + vpi_printf(const_cast("- [@%d] AtEndOfSimTime Callback (count=%d)\n"), t.low, + stats->count); + CHECK_RESULT(stats->count < stats->number_of_exp_times, 1); CHECK_RESULT(t.low, stats->exp_times[stats->count]); stats->count += 1; - s_cb_data cb_data; + s_cb_data cb_data{}; s_vpi_time time = {vpiSimTime, 0, 417, 0}; // non-zero time to check that it's ignored cb_data.time = &time; cb_data.reason = cbNextSimTime; cb_data.cb_rtn = TheCallback; - vpiHandle Handle = vpi_register_cb(&cb_data); + vpiHandle Handle = test_vpi_register_cb(&cb_data); CHECK_RESULT_NZ(Handle); return 0; @@ -116,16 +117,17 @@ static PLI_INT32 TheCallback(s_cb_data* data) { vpi_get_time(0, &t); if (verbose) { - vpi_printf(const_cast("- [@%d] %s Callback\n"), t.low, - cb_reason_to_string(data->reason)); + vpi_printf(const_cast("- [@%d] %s Callback (count=%d)\n"), t.low, + cb_reason_to_string(data->reason), stats->count); } + CHECK_RESULT(stats->count < stats->number_of_exp_times, 1); CHECK_RESULT(t.low, stats->exp_times[stats->count]); stats->count += 1; if (stats->count >= stats->number_of_exp_times) return 0; - s_cb_data cb_data; + s_cb_data cb_data{}; PLI_UINT32 next_time; if (data->reason == cbNextSimTime) { @@ -150,14 +152,14 @@ static PLI_INT32 TheCallback(s_cb_data* data) { s_vpi_time time = {vpiSimTime, 0, next_time, 0}; cb_data.time = &time; - vpiHandle Handle = vpi_register_cb(&cb_data); + vpiHandle Handle = test_vpi_register_cb(&cb_data); CHECK_RESULT_NZ(Handle); return 0; } static PLI_INT32 StartOfSimulationCallback(s_cb_data* data) { - s_cb_data cb_data; + s_cb_data cb_data{}; s_vpi_time timerec = {vpiSimTime, 0, 0, 0}; s_vpi_time t; @@ -179,32 +181,33 @@ static PLI_INT32 StartOfSimulationCallback(s_cb_data* data) { CallbackStats[cbAtStartOfSimTime].number_of_exp_times = 3; timerec.low = 5; cb_data.reason = cbAtStartOfSimTime; - vpiHandle ASOSHandle = vpi_register_cb(&cb_data); + vpiHandle ASOSHandle = test_vpi_register_cb(&cb_data); CHECK_RESULT_NZ(ASOSHandle); CallbackStats[cbReadWriteSynch].exp_times = new PLI_UINT32[3]{6, 16, 21}; CallbackStats[cbReadWriteSynch].number_of_exp_times = 3; timerec.low = 6; cb_data.reason = cbReadWriteSynch; - vpiHandle RWHandle = vpi_register_cb(&cb_data); + vpiHandle RWHandle = test_vpi_register_cb(&cb_data); CHECK_RESULT_NZ(RWHandle); CallbackStats[cbReadOnlySynch].exp_times = new PLI_UINT32[3]{7, 17, 22}; CallbackStats[cbReadOnlySynch].number_of_exp_times = 3; timerec.low = 7; cb_data.reason = cbReadOnlySynch; - vpiHandle ROHandle = vpi_register_cb(&cb_data); + vpiHandle ROHandle = test_vpi_register_cb(&cb_data); CHECK_RESULT_NZ(ROHandle); - CallbackStats[cbNextSimTime].exp_times = new PLI_UINT32[9]{5, 6, 7, 15, 16, 17, 20, 21, 22}; - CallbackStats[cbNextSimTime].number_of_exp_times = 9; + CallbackStats[cbNextSimTime].exp_times + = new PLI_UINT32[10]{5, 6, 7, 10, 15, 16, 17, 20, 21, 22}; + CallbackStats[cbNextSimTime].number_of_exp_times = 10; timerec.low = 8; cb_data.reason = cbNextSimTime; - vpiHandle NSTHandle = vpi_register_cb(&cb_data); + vpiHandle NSTHandle = test_vpi_register_cb(&cb_data); CHECK_RESULT_NZ(NSTHandle); - CallbackStats[cbAtEndOfSimTime].exp_times = new PLI_UINT32[8]{5, 6, 7, 15, 16, 17, 20, 21}; - CallbackStats[cbAtEndOfSimTime].number_of_exp_times = 8; + CallbackStats[cbAtEndOfSimTime].exp_times = new PLI_UINT32[9]{5, 6, 7, 10, 15, 16, 17, 20, 21}; + CallbackStats[cbAtEndOfSimTime].number_of_exp_times = 9; return (0); } @@ -223,23 +226,26 @@ static int EndOfSimulationCallback(p_cb_data cb_data) { CHECK_RESULT(CallbackStats[cbStartOfSimulation].count, 1); CHECK_RESULT(CallbackStats[cbAtStartOfSimTime].count, 3); - CHECK_RESULT(CallbackStats[cbNextSimTime].count, 9); + CHECK_RESULT(CallbackStats[cbNextSimTime].count, 10); CHECK_RESULT(CallbackStats[cbReadWriteSynch].count, 3); CHECK_RESULT(CallbackStats[cbReadOnlySynch].count, 3); - CHECK_RESULT(CallbackStats[cbAtEndOfSimTime].count, 8); + CHECK_RESULT(CallbackStats[cbAtEndOfSimTime].count, 9); CHECK_RESULT(CallbackStats[cbEndOfSimulation].count, 1); - if (!got_error) printf("*-* All Finished *-*\n"); + if (!errors) printf("*-* All Finished *-*\n"); + return 0; } // cver entry static void VPIRegister(void) { + if (verbose) vpi_printf(const_cast("- VPIRegister callback\n")); + // Clear stats for (int cb = 1; cb <= cbAtEndOfSimTime; cb++) CallbackStats[cb].count = 0; CallbackStats[cbStartOfSimulation].exp_times = new PLI_UINT32(0); - CallbackStats[cbEndOfSimulation].exp_times = new PLI_UINT32(22); - s_cb_data cb_data; + CallbackStats[cbEndOfSimulation].exp_times = new PLI_UINT32(100); + s_cb_data cb_data{}; s_vpi_time timerec = {vpiSuppressTime, 0, 0, 0}; cb_data.time = &timerec; @@ -248,80 +254,12 @@ static void VPIRegister(void) { cb_data.obj = 0; cb_data.reason = cbStartOfSimulation; cb_data.cb_rtn = StartOfSimulationCallback; - - vpi_register_cb(&cb_data); + test_vpi_register_cb(&cb_data); cb_data.reason = cbEndOfSimulation; cb_data.cb_rtn = EndOfSimulationCallback; - vpi_register_cb(&cb_data); + test_vpi_register_cb(&cb_data); } -#ifdef IS_VPI - -// icarus entry +// simulator entry void (*vlog_startup_routines[])(void) = {VPIRegister, 0}; - -#else - -int main(int argc, char** argv, char** env) { - double sim_time = 100; - const std::unique_ptr contextp{new VerilatedContext}; - - bool cbs_called; - contextp->commandArgs(argc, argv); - // contextp->debug(9); - - const std::unique_ptr topp{new VM_PREFIX{contextp.get(), - // Note null name - we're flattening it out - ""}}; - - topp->clk = 1; - - // StartOfSimulationCallback(nullptr); - VPIRegister(); - - VerilatedVpi::callCbs(cbStartOfSimulation); - - topp->clk = 0; - topp->eval(); - - while (contextp->time() < sim_time && !contextp->gotFinish()) { - VerilatedVpi::callTimedCbs(); - VerilatedVpi::callCbs(cbNextSimTime); - VerilatedVpi::callCbs(cbAtStartOfSimTime); - - topp->eval(); - - VerilatedVpi::callValueCbs(); - VerilatedVpi::callCbs(cbReadWriteSynch); - VerilatedVpi::callCbs(cbReadOnlySynch); - VerilatedVpi::callCbs(cbAtEndOfSimTime); - - const uint64_t next_time = VerilatedVpi::cbNextDeadline(); - if (next_time != -1) contextp->time(next_time); - if (verbose) - vpi_printf(const_cast("- [@%" PRId64 "] time change\n"), contextp->time()); - if (next_time == -1 && !contextp->gotFinish()) { - if (got_error) { - vl_stop(__FILE__, __LINE__, "TOP-cpp"); - } else { - VerilatedVpi::callCbs(cbEndOfSimulation); - contextp->gotFinish(true); - } - } - - // Count updates on rising edge, so cycle through falling edge as well - topp->clk = !topp->clk; - topp->eval(); - topp->clk = !topp->clk; - } - - if (!contextp->gotFinish()) { - vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish"); - } - topp->final(); - - exit(0L); -} - -#endif diff --git a/test_regress/t/t_vpi_onetime_cbs.py b/test_regress/t/t_vpi_onetime_cbs.py index 7c5bb911a..62f11ad2b 100755 --- a/test_regress/t/t_vpi_onetime_cbs.py +++ b/test_regress/t/t_vpi_onetime_cbs.py @@ -12,12 +12,11 @@ import vltest_bootstrap test.scenarios('simulator') test.compile(make_top_shell=False, - make_main=False, make_pli=True, - verilator_flags2=["--exe --vpi", test.pli_filename], + verilator_flags2=["--binary --vpi", test.pli_filename], iv_flags2=["-g2005-sv -D USE_VPI_NOT_DPI -DIVERILOG"], v_flags2=["+define+USE_VPI_NOT_DPI"]) -test.execute(use_libvpi=True) +test.execute(check_finished=True, use_libvpi=True) test.passes() diff --git a/test_regress/t/t_vpi_onetime_cbs.v b/test_regress/t/t_vpi_onetime_cbs.v index 33ca974e1..dce2d3a6f 100644 --- a/test_regress/t/t_vpi_onetime_cbs.v +++ b/test_regress/t/t_vpi_onetime_cbs.v @@ -5,20 +5,27 @@ // SPDX-License-Identifier: CC0-1.0 -module t (/*AUTOARG*/ - // Inputs - input clk - ); +module t; - reg [31:0] count /*verilator public_flat_rd */; + logic clk; + initial begin + clk = 0; + #10; + while ($time < 100) begin + clk = !clk; + #10; + end + end - // Test loop - initial begin - count = 0; - end + reg [31:0] count /*verilator public_flat_rd */; - always @(posedge clk) begin - count <= count + 2; - end + // Test loop + initial begin + count = 0; + end -endmodule : t + always @(posedge clk) begin + count <= count + 2; + end + +endmodule diff --git a/test_regress/t/t_vpi_unimpl.cpp b/test_regress/t/t_vpi_unimpl.cpp index 05e998241..359089492 100644 --- a/test_regress/t/t_vpi_unimpl.cpp +++ b/test_regress/t/t_vpi_unimpl.cpp @@ -81,7 +81,7 @@ int _mon_check_unimpl(p_cb_data cb_data) { handle = vpi_register_cb(NULL); CHECK_RESULT(handle, 0); - s_cb_data cb_data_s; + s_cb_data cb_data_s{}; cb_data_s.reason = 0; // Bad handle = vpi_register_cb(&cb_data_s); CHECK_RESULT(handle, 0); diff --git a/verilator-config.cmake.in b/verilator-config.cmake.in index 2da925698..1c739b433 100644 --- a/verilator-config.cmake.in +++ b/verilator-config.cmake.in @@ -120,7 +120,9 @@ if(NOT CMAKE_CXX_COMPILER_ID MATCHES MSVC) endif() if(APPLE) - add_link_options(-Wl,-U,__Z15vl_time_stamp64v,-U,__Z13sc_time_stampv) + add_link_options( + -Wl,-U,__Z15vl_time_stamp64v,-U,__Z13sc_time_stampv,-U,vlog_startup_routines + ) endif() define_property(