From c2bdd06fccd4ef551948ed6dfe0eff76004fa117 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 21 Jan 2023 13:43:27 -0500 Subject: [PATCH] Fix VPI one-time timed callbacks (#2778). --- Changes | 1 + include/verilated_vpi.cpp | 279 ++++++++------ include/verilated_vpi.h | 2 + test_regress/t/t_vpi_onetime_cbs.cpp | 342 ++++++++++++++++++ ...vpi_cbs_called.pl => t_vpi_onetime_cbs.pl} | 6 +- ...t_vpi_cbs_called.v => t_vpi_onetime_cbs.v} | 0 ...bs_called.cpp => t_vpi_repetitive_cbs.cpp} | 130 ++++++- test_regress/t/t_vpi_repetitive_cbs.pl | 28 ++ test_regress/t/t_vpi_repetitive_cbs.v | 24 ++ test_regress/t/t_vpi_unimpl.cpp | 2 +- 10 files changed, 689 insertions(+), 125 deletions(-) create mode 100644 test_regress/t/t_vpi_onetime_cbs.cpp rename test_regress/t/{t_vpi_cbs_called.pl => t_vpi_onetime_cbs.pl} (79%) rename test_regress/t/{t_vpi_cbs_called.v => t_vpi_onetime_cbs.v} (100%) rename test_regress/t/{t_vpi_cbs_called.cpp => t_vpi_repetitive_cbs.cpp} (76%) create mode 100755 test_regress/t/t_vpi_repetitive_cbs.pl create mode 100644 test_regress/t/t_vpi_repetitive_cbs.v diff --git a/Changes b/Changes index b593efde9..9d31e866c 100644 --- a/Changes +++ b/Changes @@ -19,6 +19,7 @@ Verilator 5.005 devel * Add IMPLICITSTATIC warning when a ftask/function is implicitly static (#3839). [Ryszard Rozak, Antmicro Ltd] * Optimize expansion of extend operators. * Internal multithreading tests. [Mariusz Glebocki, et al, Antmicro Ltd] +* Fix VPI one-time timed callbacks (#2778). [Marlon James, et al] * Fix initiation of function variables (#3815). [Dan Gisselquist] * Fix to zero possibly uninitialized bits in replications (#3815). * Fix crash in DFT due to width use after free (#3817) (#3820). [Jevin Sweval] diff --git a/include/verilated_vpi.cpp b/include/verilated_vpi.cpp index efbe70d0a..4c255ed79 100644 --- a/include/verilated_vpi.cpp +++ b/include/verilated_vpi.cpp @@ -126,34 +126,18 @@ public: virtual PLI_INT32 dovpi_remove_cb() { return 0; } }; -class VerilatedVpioTimedCb final : public VerilatedVpio { - // A handle to a timed callback created with vpi_register_cb - // User can call vpi_remove_cb or vpi_release_handle on it - const uint64_t m_id; // Unique id/sequence number to find schedule's event - const QData m_time; - -public: - VerilatedVpioTimedCb(uint64_t id, QData time) - : m_id{id} - , m_time{time} {} - ~VerilatedVpioTimedCb() override = default; - static VerilatedVpioTimedCb* castp(vpiHandle h) { - return dynamic_cast(reinterpret_cast(h)); - } - uint32_t type() const override { return vpiCallback; } - PLI_INT32 dovpi_remove_cb() override; -}; - class VerilatedVpioReasonCb final : public VerilatedVpio { - // A handle to a non-timed callback created with vpi_register_cb + // A handle to a timed or non-timed callback created with vpi_register_cb // User can call vpi_remove_cb or vpi_release_handle on it const uint64_t m_id; // Unique id/sequence number to find schedule's event + const QData m_time; // Scheduled time, or 0 = not timed const PLI_INT32 m_reason; // VPI callback reason code public: // cppcheck-suppress uninitVar // m_value - VerilatedVpioReasonCb(uint64_t id, PLI_INT32 reason) + VerilatedVpioReasonCb(uint64_t id, QData time, PLI_INT32 reason) : m_id{id} + , m_time{time} , m_reason{reason} {} ~VerilatedVpioReasonCb() override = default; static VerilatedVpioReasonCb* castp(vpiHandle h) { @@ -474,7 +458,7 @@ using VerilatedPliCb = PLI_INT32 (*)(struct t_cb_data*); class VerilatedVpiCbHolder final { // Holds information needed to call a callback - uint64_t m_id; + uint64_t m_id; // Unique id/sequence number to find schedule's event, 0 = invalid s_cb_data m_cbData; s_vpi_value m_value; VerilatedVpioVar m_varo; // If a cbValueChange callback, the object we will return @@ -517,11 +501,13 @@ class VerilatedVpiError; class VerilatedVpiImp final { enum { CB_ENUM_MAX_VALUE = cbAtEndOfSimTime + 1 }; // Maximum callback reason using VpioCbList = std::list; - using VpioTimedCbs = std::map, VerilatedVpiCbHolder>; + using VpioFutureCbs = std::map, VerilatedVpiCbHolder>; // All only medium-speed, so use singleton function - VpioCbList m_cbObjLists[CB_ENUM_MAX_VALUE]; // Callbacks for each supported reason - VpioTimedCbs m_timedCbs; // Time based callbacks + // Callbacks that are past or at current timestamp + std::array m_cbCurrentLists; + VpioFutureCbs m_futureCbs; // Time based callbacks for future timestamps + VpioFutureCbs m_nextCbs; // cbNextSimTime callbacks VerilatedVpiError* m_errorInfop = nullptr; // Container for vpi error info VerilatedAssertOneThread m_assertOne; // Assert only called from single thread uint64_t m_nextCallbackId = 1; // Id to identify callback @@ -535,7 +521,7 @@ public: static void assertOneCheck() { s().m_assertOne.check(); } static uint64_t nextCallbackId() { return ++s().m_nextCallbackId; } - static void cbReasonAdd(uint64_t id, const s_cb_data* cb_data_p) { + static void cbCurrentAdd(uint64_t id, const s_cb_data* cb_data_p) { // The passed cb_data_p was property of the user, so need to recreate if (VL_UNCOVERABLE(cb_data_p->reason >= CB_ENUM_MAX_VALUE)) { VL_FATAL_MT(__FILE__, __LINE__, "", "vpi bb reason too large"); @@ -544,82 +530,109 @@ public: cb_data_p->reason, id, cb_data_p->obj);); VerilatedVpioVar* varop = nullptr; if (cb_data_p->reason == cbValueChange) varop = VerilatedVpioVar::castp(cb_data_p->obj); - s().m_cbObjLists[cb_data_p->reason].emplace_back(id, cb_data_p, varop); + s().m_cbCurrentLists[cb_data_p->reason].emplace_back(id, cb_data_p, varop); } - static void cbTimedAdd(uint64_t id, const s_cb_data* cb_data_p, QData time) { + static void cbFutureAdd(uint64_t id, const s_cb_data* cb_data_p, QData time) { // The passed cb_data_p was property of the user, so need to recreate - VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_register_cb reason=%d id=%" PRId64 - " delay=%" PRIu64 "\n", - cb_data_p->reason, id, time);); - s().m_timedCbs.emplace(std::piecewise_construct, - std::forward_as_tuple(std::make_pair(time, id)), - std::forward_as_tuple(id, cb_data_p, nullptr)); + VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_register_cb reason=%d id=%" PRId64 " time=%" PRIu64 + " obj=%p\n", + cb_data_p->reason, id, time, cb_data_p->obj);); + s().m_futureCbs.emplace(std::piecewise_construct, + std::forward_as_tuple(std::make_pair(time, id)), + std::forward_as_tuple(id, cb_data_p, nullptr)); } - static void cbReasonRemove(uint64_t id, uint32_t reason) { + static void cbNextAdd(uint64_t id, const s_cb_data* cb_data_p, QData time) { + // The passed cb_data_p was property of the user, so need to recreate + VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_register_cb reason=%d(NEXT) id=%" PRId64 + " time=%" PRIu64 " obj=%p\n", + cb_data_p->reason, id, time, cb_data_p->obj);); + s().m_nextCbs.emplace(std::piecewise_construct, + std::forward_as_tuple(std::make_pair(time, id)), + std::forward_as_tuple(id, cb_data_p, nullptr)); + } + static void cbReasonRemove(uint64_t id, uint32_t reason, QData time) { // Id might no longer exist, if already removed due to call after event, or teardown - VpioCbList& cbObjList = s().m_cbObjLists[reason]; // We do not remove it now as we may be iterating the list, // instead set to nullptr and will cleanup later - for (auto& ir : cbObjList) { - if (ir.id() == id) ir.invalidate(); + // Remove from cbCurrent queue + for (auto& ir : s().m_cbCurrentLists[reason]) { + if (ir.id() == id) { + ir.invalidate(); + return; // Once found, it won't also be in m_futureCbs + } + } + { // Remove from cbFuture queue + const auto it = s().m_futureCbs.find(std::make_pair(time, id)); + if (it != s().m_futureCbs.end()) { + it->second.invalidate(); + return; + } + } + { // Remove from cbNext + const auto it = s().m_nextCbs.find(std::make_pair(time, id)); + if (it != s().m_nextCbs.end()) { + it->second.invalidate(); + return; + } } } - static void cbTimedRemove(uint64_t id, QData time) { - // Id might no longer exist, if already removed due to call after event, or teardown - const auto it = s().m_timedCbs.find(std::make_pair(time, id)); - if (VL_LIKELY(it != s().m_timedCbs.end())) it->second.invalidate(); - } - static void callTimedCbs() VL_MT_UNSAFE_ONE { - assertOneCheck(); + static void moveFutureCbs() VL_MT_UNSAFE_ONE { + // For any events past current time, move from cbFuture queue to cbCurrent queue + if (s().m_futureCbs.empty() && s().m_nextCbs.empty()) return; + // VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: moveFutureCbs\n"); dumpCbs(); ); const QData time = VL_TIME_Q(); - for (auto it = s().m_timedCbs.begin(); it != s().m_timedCbs.end();) { - if (VL_UNLIKELY(it->first.first <= time)) { - VerilatedVpiCbHolder& ho = it->second; - const auto last_it = it; - ++it; - if (VL_UNLIKELY(!ho.invalid())) { - VL_DEBUG_IF_PLI( - VL_DBG_MSGF("- vpi: timed_callback id=%" PRId64 "\n", ho.id());); - ho.invalidate(); // Timed callbacks are one-shot - (ho.cb_rtnp())(ho.cb_datap()); - } - s().m_timedCbs.erase(last_it); - } else { - ++it; + for (auto it = s().m_futureCbs.begin(); // + VL_UNLIKELY(it != s().m_futureCbs.end() && it->first.first <= time);) { + VerilatedVpiCbHolder& hor = it->second; + const auto last_it = it; + ++it; + if (VL_UNLIKELY(!hor.invalid())) { + VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: moveFutureCbs id=%" PRId64 "\n", hor.id());); + s().m_cbCurrentLists[hor.cb_datap()->reason].emplace_back(hor); } + s().m_futureCbs.erase(last_it); + } + for (auto it = s().m_nextCbs.begin(); // + VL_UNLIKELY(it != s().m_nextCbs.end() && it->first.first < time);) { + VerilatedVpiCbHolder& hor = it->second; + const auto last_it = it; + ++it; + if (VL_UNLIKELY(!hor.invalid())) { + VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: moveFutureCbs id=%" PRId64 "\n", hor.id());); + s().m_cbCurrentLists[hor.cb_datap()->reason].emplace_back(hor); + } + s().m_nextCbs.erase(last_it); } } static QData cbNextDeadline() { - const auto it = s().m_timedCbs.cbegin(); - if (VL_LIKELY(it != s().m_timedCbs.cend())) return it->first.first; + const auto it = s().m_futureCbs.cbegin(); + if (VL_LIKELY(it != s().m_futureCbs.cend())) return it->first.first; return ~0ULL; // maxquad } static bool callCbs(const uint32_t reason) VL_MT_UNSAFE_ONE { - VpioCbList& cbObjList = s().m_cbObjLists[reason]; + VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: callCbs reason=%u\n", reason);); + assertOneCheck(); + moveFutureCbs(); + if (s().m_cbCurrentLists[reason].empty()) return false; + // Iterate on old list, making new list empty, to prevent looping over newly added elements + VpioCbList cbObjList; + std::swap(s().m_cbCurrentLists[reason], cbObjList); bool called = false; - if (cbObjList.empty()) return called; - const auto last = std::prev(cbObjList.end()); // prevent looping over newly added elements - for (auto it = cbObjList.begin(); true;) { + for (VerilatedVpiCbHolder& ihor : cbObjList) { // cbReasonRemove sets to nullptr, so we know on removal the old end() will still exist - const bool was_last = it == last; - if (VL_UNLIKELY(it->invalid())) { // Deleted earlier, cleanup - it = cbObjList.erase(it); - if (was_last) break; - continue; + if (VL_LIKELY(!ihor.invalid())) { // Not deleted earlier + VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: reason_callback reason=%d id=%" PRId64 "\n", + reason, ihor.id());); + ihor.invalidate(); // Timed callbacks are one-shot + (ihor.cb_rtnp())(ihor.cb_datap()); + called = true; } - VerilatedVpiCbHolder& ho = *it; - VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: reason_callback reason=%d id=%" PRId64 "\n", - reason, ho.id());); - (ho.cb_rtnp())(ho.cb_datap()); - called = true; - if (was_last) break; - ++it; } return called; } static bool callValueCbs() VL_MT_UNSAFE_ONE { assertOneCheck(); - VpioCbList& cbObjList = s().m_cbObjLists[cbValueChange]; + VpioCbList& cbObjList = s().m_cbCurrentLists[cbValueChange]; bool called = false; std::unordered_set update; // set of objects to update after callbacks if (cbObjList.empty()) return called; @@ -658,7 +671,7 @@ public: } return called; } - + static void dumpCbs() VL_MT_UNSAFE_ONE; static VerilatedVpiError* error_info() VL_MT_UNSAFE_ONE; // getter for vpi error info }; @@ -742,23 +755,21 @@ public: //====================================================================== // VerilatedVpi implementation -void VerilatedVpi::callTimedCbs() VL_MT_UNSAFE_ONE { VerilatedVpiImp::callTimedCbs(); } - -bool VerilatedVpi::callValueCbs() VL_MT_UNSAFE_ONE { return VerilatedVpiImp::callValueCbs(); } - bool VerilatedVpi::callCbs(uint32_t reason) VL_MT_UNSAFE_ONE { return VerilatedVpiImp::callCbs(reason); } +// Historical, before we had multiple kinds of timed callbacks +void VerilatedVpi::callTimedCbs() VL_MT_UNSAFE_ONE { VerilatedVpiImp::callCbs(cbAfterDelay); } + +bool VerilatedVpi::callValueCbs() VL_MT_UNSAFE_ONE { return VerilatedVpiImp::callValueCbs(); } + QData VerilatedVpi::cbNextDeadline() VL_MT_UNSAFE_ONE { return VerilatedVpiImp::cbNextDeadline(); } -PLI_INT32 VerilatedVpioTimedCb::dovpi_remove_cb() { - VerilatedVpiImp::cbTimedRemove(m_id, m_time); - delete this; // IEEE 37.2.2 a vpi_remove_cb does a vpi_release_handle - return 1; -} +void VerilatedVpi::dumpCbs() VL_MT_UNSAFE_ONE { VerilatedVpiImp::dumpCbs(); } + PLI_INT32 VerilatedVpioReasonCb::dovpi_remove_cb() { - VerilatedVpiImp::cbReasonRemove(m_id, m_reason); + VerilatedVpiImp::cbReasonRemove(m_id, m_reason, m_time); delete this; // IEEE 37.2.2 a vpi_remove_cb does a vpi_release_handle return 1; } @@ -766,6 +777,42 @@ PLI_INT32 VerilatedVpioReasonCb::dovpi_remove_cb() { //====================================================================== // VerilatedVpiImp implementation +void VerilatedVpiImp::dumpCbs() VL_MT_UNSAFE_ONE { + assertOneCheck(); + VL_DBG_MSGF("- vpi: dumpCbs\n"); + for (uint32_t reason = 0; reason < CB_ENUM_MAX_VALUE; ++reason) { + VpioCbList& cbObjList = s().m_cbCurrentLists[reason]; + for (auto& ho : cbObjList) { + if (VL_UNLIKELY(!ho.invalid())) { + VL_DBG_MSGF("- vpi: reason=%d=%s id=%" PRId64 "\n", reason, + VerilatedVpiError::strFromVpiCallbackReason(reason), ho.id()); + } + } + } + for (auto& ifuture : s().m_nextCbs) { + const QData time = ifuture.first.first; + const uint64_t id = ifuture.first.second; + VerilatedVpiCbHolder& ho = ifuture.second; + if (VL_UNLIKELY(!ho.invalid())) { + VL_DBG_MSGF("- vpi: time=%" PRId64 "(NEXT) reason=%d=%s id=%" PRId64 "\n", time, + ho.cb_datap()->reason, + VerilatedVpiError::strFromVpiCallbackReason(ho.cb_datap()->reason), + ho.id()); + } + } + for (auto& ifuture : s().m_futureCbs) { + const QData time = ifuture.first.first; + const uint64_t id = ifuture.first.second; + VerilatedVpiCbHolder& ho = ifuture.second; + if (VL_UNLIKELY(!ho.invalid())) { + VL_DBG_MSGF("- vpi: time=%" PRId64 " reason=%d=%s id=%" PRId64 "\n", time, + ho.cb_datap()->reason, + VerilatedVpiError::strFromVpiCallbackReason(ho.cb_datap()->reason), + ho.id()); + } + } +} + VerilatedVpiError* VerilatedVpiImp::error_info() VL_MT_UNSAFE_ONE { VerilatedVpiImp::assertOneCheck(); if (VL_UNLIKELY(!s().m_errorInfop)) s().m_errorInfop = new VerilatedVpiError; @@ -1306,34 +1353,54 @@ vpiHandle vpi_register_cb(p_cb_data cb_data_p) { VL_VPI_WARNING_(__FILE__, __LINE__, "%s : callback data pointer is null", __func__); return nullptr; } - switch (cb_data_p->reason) { - case cbAfterDelay: { - QData time = 0; - if (cb_data_p->time) time = VL_SET_QII(cb_data_p->time->high, cb_data_p->time->low); - const QData abstime = VL_TIME_Q() + time; + const PLI_INT32 reason = cb_data_p->reason; + switch (reason) { + case cbAfterDelay: // FALLTHRU // One-shot; time relative + case cbAtEndOfSimTime: // FALLTHRU // One-shot; time absolute; supported via vlt_main.cpp + case cbAtStartOfSimTime: // FALLTHRU // One-shot; time absolute; supported via vlt_main.cpp + case cbReadOnlySynch: // FALLTHRU // One-shot; time relative; supported via vlt_main.cpp + case cbReadWriteSynch: { // One-shot; time relative; supported via vlt_main.cpp + const bool abs = reason == cbAtStartOfSimTime || reason == cbAtEndOfSimTime; + const QData time = VL_TIME_Q(); + QData abstime = 0; + if (cb_data_p->time) { + if (abs) { + abstime = VL_SET_QII(cb_data_p->time->high, cb_data_p->time->low); + } else { + abstime = time + VL_SET_QII(cb_data_p->time->high, cb_data_p->time->low); + } + } const uint64_t id = VerilatedVpiImp::nextCallbackId(); - VerilatedVpioTimedCb* const vop = new VerilatedVpioTimedCb{id, abstime}; - VerilatedVpiImp::cbTimedAdd(id, cb_data_p, abstime); + VerilatedVpioReasonCb* const vop = new VerilatedVpioReasonCb{id, abstime, reason}; + if (abstime <= time) { + VerilatedVpiImp::cbCurrentAdd(id, cb_data_p); + } else { + VerilatedVpiImp::cbFutureAdd(id, cb_data_p, abstime); + } return vop->castVpiHandle(); } - case cbReadWriteSynch: // FALLTHRU // Supported via vlt_main.cpp - case cbReadOnlySynch: // FALLTHRU // Supported via vlt_main.cpp - case cbNextSimTime: // FALLTHRU // Supported via vlt_main.cpp - case cbStartOfSimulation: // FALLTHRU // Supported via vlt_main.cpp - case cbEndOfSimulation: // FALLTHRU // Supported via vlt_main.cpp - case cbValueChange: // FALLTHRU // Supported via vlt_main.cpp - case cbPLIError: // FALLTHRU // NOP, but need to return handle, so make object + case cbNextSimTime: { // One-shot; time always next; supported via vlt_main.cpp + const QData time = VL_TIME_Q(); + const uint64_t id = VerilatedVpiImp::nextCallbackId(); + VerilatedVpioReasonCb* const vop = new VerilatedVpioReasonCb{id, 0, reason}; + VerilatedVpiImp::cbNextAdd(id, cb_data_p, time); + return vop->castVpiHandle(); + } + case cbEndOfSimulation: // FALLTHRU // One-shot; time ignored; supported via vlt_main.cpp case cbEnterInteractive: // FALLTHRU // NOP, but need to return handle, so make object case cbExitInteractive: // FALLTHRU // NOP, but need to return handle, so make object - case cbInteractiveScopeChange: { // FALLTHRU // NOP, but need to return handle, so make object + case cbInteractiveScopeChange: // FALLTHRU // NOP, but need to return handle, so make object + case cbPLIError: // FALLTHRU // NOP, but need to return handle, so make object + case cbStartOfSimulation: // FALLTHRU // One-shot; time ignored; supported via vlt_main.cpp + case cbValueChange: { // Multi-shot; supported via vlt_main.cpp const uint64_t id = VerilatedVpiImp::nextCallbackId(); - VerilatedVpioReasonCb* const vop = new VerilatedVpioReasonCb{id, cb_data_p->reason}; - VerilatedVpiImp::cbReasonAdd(id, cb_data_p); + VerilatedVpioReasonCb* const vop = new VerilatedVpioReasonCb{id, 0, reason}; + VerilatedVpiImp::cbCurrentAdd(id, cb_data_p); return vop->castVpiHandle(); } default: VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Unsupported callback type %s", __func__, - VerilatedVpiError::strFromVpiCallbackReason(cb_data_p->reason)); + VerilatedVpiError::strFromVpiCallbackReason(reason)); return nullptr; } } diff --git a/include/verilated_vpi.h b/include/verilated_vpi.h index edbc0bb32..0d9679eb7 100644 --- a/include/verilated_vpi.h +++ b/include/verilated_vpi.h @@ -52,6 +52,8 @@ public: /// Returns time of the next registered VPI callback, or /// ~(0ULL) if none are registered static QData cbNextDeadline() VL_MT_UNSAFE_ONE; + /// Debug dump of callbacks + static void dumpCbs() VL_MT_UNSAFE_ONE; // Self test, for internal use only static void selfTest() VL_MT_UNSAFE_ONE; diff --git a/test_regress/t/t_vpi_onetime_cbs.cpp b/test_regress/t/t_vpi_onetime_cbs.cpp new file mode 100644 index 000000000..c7632f8a9 --- /dev/null +++ b/test_regress/t/t_vpi_onetime_cbs.cpp @@ -0,0 +1,342 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Copyright 2021 by Wilson Snyder and Marlon James. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +// 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 "Vt_vpi_onetime_cbs.h" + +#endif + +#include "TestSimulator.h" +#include "TestVpi.h" + +#include +#include +#include +#include +#include + +typedef struct { + PLI_UINT32 count; + PLI_UINT32* exp_times; + PLI_UINT32 number_of_exp_times; +} cb_stats; + +static cb_stats CallbackStats[cbAtEndOfSimTime + 1]; + +bool got_error = false; + +static vpiHandle ValueHandle, ToggleHandle, ClockHandle; + +#ifdef TEST_VERBOSE +bool verbose = true; +#else +bool verbose = false; +#endif + +#define CHECK_RESULT_NZ(got) \ + if (!(got)) { \ + printf("%%Error: %s:%d: GOT = NULL EXP = !NULL\n", __FILE__, __LINE__); \ + got_error = true; \ + return __LINE__; \ + } + +// Use cout to avoid issues with %d/%lx etc +#define CHECK_RESULT(got, exp) \ + if ((got) != (exp)) { \ + std::cout << std::dec << "%Error: " << __FILE__ << ":" << __LINE__ << ": GOT = " << (got) \ + << " EXP = " << (exp) << std::endl; \ + got_error = true; \ + return __LINE__; \ + } + +#define STRINGIFY_CB_CASE(_cb) \ + case _cb: return #_cb + +static const char* cb_reason_to_string(int cb_name) { + switch (cb_name) { + STRINGIFY_CB_CASE(cbAtStartOfSimTime); + STRINGIFY_CB_CASE(cbReadWriteSynch); + STRINGIFY_CB_CASE(cbReadOnlySynch); + STRINGIFY_CB_CASE(cbNextSimTime); + STRINGIFY_CB_CASE(cbStartOfSimulation); + STRINGIFY_CB_CASE(cbEndOfSimulation); + STRINGIFY_CB_CASE(cbAtEndOfSimTime); + default: return "Unsupported callback"; + } +} + +#undef STRINGIFY_CB_CASE + +bool cb_time_is_delay(int cb_name) { + // For some callbacks, time is interpreted as a delay from current time + // instead of an absolute time + if (cb_name == cbReadOnlySynch || cb_name == cbReadWriteSynch) { return true; } + return false; +} + +// forward declaration +static PLI_INT32 TheCallback(s_cb_data* data); + +static PLI_INT32 AtEndOfSimTimeCallback(s_cb_data* data) { + s_vpi_time t; + + 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); + + CHECK_RESULT(t.low, stats->exp_times[stats->count]); + stats->count += 1; + + 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); + CHECK_RESULT_NZ(Handle); + + return 0; +} + +static PLI_INT32 TheCallback(s_cb_data* data) { + s_vpi_time t; + + cb_stats* stats = &CallbackStats[data->reason]; + + t.type = vpiSimTime; + vpi_get_time(0, &t); + + if (verbose) { + vpi_printf(const_cast("- [@%d] %s Callback\n"), t.low, + cb_reason_to_string(data->reason)); + } + + 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; + PLI_UINT32 next_time; + + if (data->reason == cbNextSimTime) { + // if a cbNextSimTime calback is scheduled from + // another cbNextSimTime callback, it will + // be called in the same timestep, so we need + // to delay registering + next_time = t.low; + cb_data.reason = cbAtEndOfSimTime; + cb_data.cb_rtn = AtEndOfSimTimeCallback; + } else { + next_time = stats->exp_times[stats->count]; + if (cb_time_is_delay(data->reason)) { next_time -= t.low; } + cb_data.reason = data->reason; + cb_data.cb_rtn = TheCallback; + } + + if (verbose) { + vpi_printf(const_cast("- [@%d] Registering %s Callback with time = %d\n"), t.low, + cb_reason_to_string(cb_data.reason), next_time); + } + + s_vpi_time time = {vpiSimTime, 0, next_time, 0}; + cb_data.time = &time; + vpiHandle Handle = 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_vpi_time timerec = {vpiSimTime, 0, 0, 0}; + + s_vpi_time t; + t.type = vpiSimTime; + vpi_get_time(0, &t); + + if (verbose) vpi_printf(const_cast("- [@%d] cbStartOfSimulation Callback\n"), t.low); + + CHECK_RESULT(t.low, CallbackStats[data->reason].exp_times[0]); + CallbackStats[data->reason].count += 1; + + cb_data.time = &timerec; + cb_data.value = 0; + cb_data.user_data = 0; + cb_data.obj = 0; + cb_data.cb_rtn = TheCallback; + + CallbackStats[cbAtStartOfSimTime].exp_times = new PLI_UINT32[3]{5, 15, 20}; + CallbackStats[cbAtStartOfSimTime].number_of_exp_times = 3; + timerec.low = 5; + cb_data.reason = cbAtStartOfSimTime; + vpiHandle ASOSHandle = 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); + 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); + 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; + timerec.low = 8; + cb_data.reason = cbNextSimTime; + vpiHandle NSTHandle = 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; + + return (0); +} + +static int EndOfSimulationCallback(p_cb_data cb_data) { + s_vpi_time t; + t.type = vpiSimTime; + vpi_get_time(0, &t); + + (void)cb_data; // Unused + + if (verbose) vpi_printf(const_cast("- [@%d] cbEndOfSimulation Callback\n"), t.low); + + CHECK_RESULT(t.low, CallbackStats[cbEndOfSimulation].exp_times[0]); + CallbackStats[cbEndOfSimulation].count += 1; + + CHECK_RESULT(CallbackStats[cbStartOfSimulation].count, 1); + CHECK_RESULT(CallbackStats[cbAtStartOfSimTime].count, 3); + CHECK_RESULT(CallbackStats[cbNextSimTime].count, 9); + CHECK_RESULT(CallbackStats[cbReadWriteSynch].count, 3); + CHECK_RESULT(CallbackStats[cbReadOnlySynch].count, 3); + CHECK_RESULT(CallbackStats[cbAtEndOfSimTime].count, 8); + CHECK_RESULT(CallbackStats[cbEndOfSimulation].count, 1); + + if (!got_error) { printf("*-* All Finished *-*\n"); } + return 0; +} + +// cver entry +static void VPIRegister(void) { + // 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; + s_vpi_time timerec = {vpiSuppressTime, 0, 0, 0}; + + cb_data.time = &timerec; + cb_data.value = 0; + cb_data.user_data = 0; + cb_data.obj = 0; + cb_data.reason = cbStartOfSimulation; + cb_data.cb_rtn = StartOfSimulationCallback; + + vpi_register_cb(&cb_data); + + cb_data.reason = cbEndOfSimulation; + cb_data.cb_rtn = EndOfSimulationCallback; + vpi_register_cb(&cb_data); +} + +#ifdef IS_VPI + +// icarus 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_cbs_called.pl b/test_regress/t/t_vpi_onetime_cbs.pl similarity index 79% rename from test_regress/t/t_vpi_cbs_called.pl rename to test_regress/t/t_vpi_onetime_cbs.pl index 0d8f23641..8dab7fd6d 100755 --- a/test_regress/t/t_vpi_cbs_called.pl +++ b/test_regress/t/t_vpi_onetime_cbs.pl @@ -8,16 +8,20 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -scenarios(vlt => 1); +scenarios(simulator => 1); compile( make_top_shell => 0, make_main => 0, + make_pli => 1, verilator_flags2 => ["--exe --vpi $Self->{t_dir}/$Self->{name}.cpp"], + iv_flags2 => ["-g2005-sv -D USE_VPI_NOT_DPI -DIVERILOG"], + v_flags2 => ["+define+USE_VPI_NOT_DPI"], ); execute( check_finished => 1, + use_libvpi => 1, ); ok(1); diff --git a/test_regress/t/t_vpi_cbs_called.v b/test_regress/t/t_vpi_onetime_cbs.v similarity index 100% rename from test_regress/t/t_vpi_cbs_called.v rename to test_regress/t/t_vpi_onetime_cbs.v diff --git a/test_regress/t/t_vpi_cbs_called.cpp b/test_regress/t/t_vpi_repetitive_cbs.cpp similarity index 76% rename from test_regress/t/t_vpi_cbs_called.cpp rename to test_regress/t/t_vpi_repetitive_cbs.cpp index 16dac6df9..84a82fa2c 100644 --- a/test_regress/t/t_vpi_cbs_called.cpp +++ b/test_regress/t/t_vpi_repetitive_cbs.cpp @@ -9,11 +9,19 @@ // //************************************************************************* +#ifdef IS_VPI + +#include "vpi_user.h" + +#include + +#else + #include "verilated.h" #include "verilated_vpi.h" -#include "Vt_vpi_cbs_called.h" -#include "vpi_user.h" +#include "Vt_vpi_repetitive_cbs.h" +#endif #include #include @@ -25,8 +33,7 @@ #include "TestSimulator.h" #include "TestVpi.h" -const std::vector cbs_to_test{cbReadWriteSynch, cbReadOnlySynch, cbNextSimTime, - cbStartOfSimulation, cbEndOfSimulation, cbValueChange}; +const std::vector cbs_to_test{cbValueChange}; enum CallbackState { PRE_REGISTER, ACTIVE, ACTIVE_AGAIN, REM_REREG_ACTIVE, POST_REMOVE }; const std::vector cb_states{PRE_REGISTER, ACTIVE, ACTIVE_AGAIN, REM_REREG_ACTIVE, @@ -46,17 +53,29 @@ std::vector::const_iterator state_iter; bool got_error = false; +#ifdef IS_VPI +vpiHandle clk_h; +#endif + #ifdef TEST_VERBOSE bool verbose = true; #else bool verbose = false; #endif +#ifdef IS_VPI +#define END_TEST \ + vpi_control(vpiStop); \ + return 0; +#else +#define END_TEST return __LINE__; +#endif + #define CHECK_RESULT_NZ(got) \ if (!(got)) { \ printf("%%Error: %s:%d: GOT = NULL EXP = !NULL\n", __FILE__, __LINE__); \ got_error = true; \ - return __LINE__; \ + END_TEST \ } // Use cout to avoid issues with %d/%lx etc @@ -65,7 +84,7 @@ bool verbose = false; std::cout << std::dec << "%Error: " << __FILE__ << ":" << __LINE__ << ": GOT = " << (got) \ << " EXP = " << (exp) << std::endl; \ got_error = true; \ - return __LINE__; \ + END_TEST \ } #define STRINGIFY_CB_CASE(_cb) \ @@ -73,11 +92,6 @@ bool verbose = false; static const char* cb_reason_to_string(int cb_name) { switch (cb_name) { - STRINGIFY_CB_CASE(cbReadWriteSynch); - STRINGIFY_CB_CASE(cbReadOnlySynch); - STRINGIFY_CB_CASE(cbNextSimTime); - STRINGIFY_CB_CASE(cbStartOfSimulation); - STRINGIFY_CB_CASE(cbEndOfSimulation); STRINGIFY_CB_CASE(cbValueChange); default: return "Unsupported callback"; } @@ -86,6 +100,7 @@ static const char* cb_reason_to_string(int cb_name) { #undef STRINGIFY_CB_CASE static int the_callback(p_cb_data cb_data) { + vpi_printf(const_cast(" The callback\n")); callback_counts[cb_data->reason] = callback_counts[cb_data->reason] + 1; return 0; } @@ -98,7 +113,12 @@ static int register_cb(const int next_state) { cb_data_testcase.cb_rtn = the_callback; cb_data_testcase.reason = cb; +#ifdef IS_VPI + TestVpiHandle count_h = vpi_handle_by_name(const_cast("t.count"), + 0); // Needed in this scope as is in cb_data +#else TestVpiHandle count_h = VPI_HANDLE("count"); // Needed in this scope as is in cb_data +#endif CHECK_RESULT_NZ(count_h); if (cb == cbValueChange) { v.format = vpiSuppressVal; @@ -170,9 +190,11 @@ static int test_callbacks(p_cb_data cb_data) { auto exp_count = callback_expected_counts[cb]; CHECK_RESULT(count, exp_count); +#if !defined(IS_VPI) bool called = callbacks_called[cb]; bool exp_called = callbacks_expected_called[cb]; CHECK_RESULT(called, exp_called); +#endif // Update expected values based on state of callback in next time through main loop reset_expected(); @@ -213,7 +235,7 @@ static int test_callbacks(p_cb_data cb_data) { cb_data_n.reason = cbAfterDelay; t1.type = vpiSimTime; t1.high = 0; - t1.low = 1; + t1.low = 10; cb_data_n.time = &t1; cb_data_n.cb_rtn = test_callbacks; TestVpiHandle vh_test_cb = vpi_register_cb(&cb_data_n); @@ -223,7 +245,34 @@ static int test_callbacks(p_cb_data cb_data) { return ret; } -static int register_test_callback() { +#ifdef IS_VPI +static int toggle_clock(p_cb_data data) { + s_vpi_value val; + s_vpi_time time = {vpiSimTime, 0, 0, 0}; + + val.format = vpiIntVal; + vpi_get_value(clk_h, &val); + val.value.integer = !val.value.integer; + vpi_put_value(clk_h, &val, &time, vpiInertialDelay); + + s_vpi_time cur_time = {vpiSimTime, 0, 0, 0}; + vpi_get_time(0, &cur_time); + + if (cur_time.low < 100 && !got_error) { + t_cb_data cb_data; + bzero(&cb_data, sizeof(cb_data)); + time.low = 5; + cb_data.reason = cbAfterDelay; + cb_data.time = &time; + cb_data.cb_rtn = toggle_clock; + vpi_register_cb(&cb_data); + } + + return 0; +} +#endif + +static int register_test_callback(p_cb_data data) { t_cb_data cb_data; bzero(&cb_data, sizeof(cb_data)); s_vpi_time t1; @@ -233,7 +282,7 @@ static int register_test_callback() { cb_data.reason = cbAfterDelay; t1.type = vpiSimTime; t1.high = 0; - t1.low = 1; + t1.low = 10; cb_data.time = &t1; cb_data.cb_rtn = test_callbacks; TestVpiHandle vh_test_cb = vpi_register_cb(&cb_data); @@ -242,9 +291,49 @@ static int register_test_callback() { cb_iter = cbs_to_test.cbegin(); state_iter = cb_states.cbegin(); +#ifdef IS_VPI + t1.low = 1; + cb_data.cb_rtn = toggle_clock; + TestVpiHandle vh_toggle_cb = vpi_register_cb(&cb_data); + CHECK_RESULT_NZ(vh_toggle_cb); + + clk_h = vpi_handle_by_name(const_cast("t.clk"), 0); + CHECK_RESULT_NZ(clk_h); +#endif + return 0; } +#ifdef IS_VPI + +static int end_of_sim_cb(p_cb_data cb_data) { + if (!got_error) { fprintf(stdout, "*-* All Finished *-*\n"); } + return 0; +} + +// cver entry +void vpi_compat_bootstrap(void) { + t_cb_data cb_data; + bzero(&cb_data, sizeof(cb_data)); + { + vpi_printf(const_cast("register start-of-sim callback\n")); + cb_data.reason = cbStartOfSimulation; + cb_data.time = 0; + cb_data.cb_rtn = register_test_callback; + vpi_register_cb(&cb_data); + } + { + cb_data.reason = cbEndOfSimulation; + cb_data.time = 0; + cb_data.cb_rtn = end_of_sim_cb; + vpi_register_cb(&cb_data); + } +} +// icarus entry +void (*vlog_startup_routines[])() = {vpi_compat_bootstrap, 0}; + +#else + int main(int argc, char** argv) { const std::unique_ptr contextp{new VerilatedContext}; @@ -258,7 +347,7 @@ int main(int argc, char** argv) { if (verbose) VL_PRINTF("-- { Sim Time %" PRId64 " } --\n", contextp->time()); - register_test_callback(); + register_test_callback(nullptr); topp->eval(); topp->clk = 0; @@ -274,17 +363,22 @@ int main(int argc, char** argv) { for (const auto& i : cbs_to_test) { if (verbose) { - VL_PRINTF(" Calling %s (%d) callbacks\t", cb_reason_to_string(i), i); + VL_PRINTF(" Calling %s (%d) callbacks\n >>>>\n", cb_reason_to_string(i), + i); } if (i == cbValueChange) { cbs_called = VerilatedVpi::callValueCbs(); } else { cbs_called = VerilatedVpi::callCbs(i); } - if (verbose) VL_PRINTF(" - any callbacks called? %s\n", cbs_called ? "YES" : "NO"); + if (verbose) + VL_PRINTF(" <<<<\n Any callbacks called? %s\n", cbs_called ? "YES" : "NO"); callbacks_called[i] = cbs_called; } + // Always calling this so we can get code coverage on the Verilator debug routine + VerilatedVpi::dumpCbs(); + VerilatedVpi::callTimedCbs(); int64_t next_time = VerilatedVpi::cbNextDeadline(); @@ -314,3 +408,5 @@ int main(int argc, char** argv) { return 0; } + +#endif diff --git a/test_regress/t/t_vpi_repetitive_cbs.pl b/test_regress/t/t_vpi_repetitive_cbs.pl new file mode 100755 index 000000000..8dab7fd6d --- /dev/null +++ b/test_regress/t/t_vpi_repetitive_cbs.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder and Marlon James. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + make_top_shell => 0, + make_main => 0, + make_pli => 1, + verilator_flags2 => ["--exe --vpi $Self->{t_dir}/$Self->{name}.cpp"], + iv_flags2 => ["-g2005-sv -D USE_VPI_NOT_DPI -DIVERILOG"], + v_flags2 => ["+define+USE_VPI_NOT_DPI"], + ); + +execute( + check_finished => 1, + use_libvpi => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_vpi_repetitive_cbs.v b/test_regress/t/t_vpi_repetitive_cbs.v new file mode 100644 index 000000000..3568bf9d7 --- /dev/null +++ b/test_regress/t/t_vpi_repetitive_cbs.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2020 Wilson Snyder and Marlon James. +// SPDX-License-Identifier: CC0-1.0 + + +module t (/*AUTOARG*/ + // Inputs + input clk + ); + + reg [31:0] count /*verilator public_flat_rd */; + + // Test loop + initial begin + count = 0; + end + + always @(posedge clk) begin + count <= count + 2; + end + +endmodule : t diff --git a/test_regress/t/t_vpi_unimpl.cpp b/test_regress/t/t_vpi_unimpl.cpp index f3797f9eb..da925d42b 100644 --- a/test_regress/t/t_vpi_unimpl.cpp +++ b/test_regress/t/t_vpi_unimpl.cpp @@ -188,7 +188,7 @@ int main(int argc, char** argv) { uint64_t sim_time = 1100; contextp->commandArgs(argc, argv); - contextp->debug(0); + // contextp->debug(9); // we're going to be checking for these errors do don't crash out contextp->fatalOnVpiError(0);