Fix VPI one-time timed callbacks (#2778).

This commit is contained in:
Wilson Snyder 2023-01-21 13:43:27 -05:00
parent 8918f17c17
commit c2bdd06fcc
10 changed files with 689 additions and 125 deletions

View File

@ -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]

View File

@ -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<VerilatedVpioTimedCb*>(reinterpret_cast<VerilatedVpioTimedCb*>(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<VerilatedVpiCbHolder>;
using VpioTimedCbs = std::map<std::pair<QData, uint64_t>, VerilatedVpiCbHolder>;
using VpioFutureCbs = std::map<std::pair<QData, uint64_t>, 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<VpioCbList, CB_ENUM_MAX_VALUE> 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<VerilatedVpioVar*> 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;
}
}

View File

@ -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;

View File

@ -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 <cstdlib>
#else
#include "verilated.h"
#include "verilated_vpi.h"
#include "Vt_vpi_onetime_cbs.h"
#endif
#include "TestSimulator.h"
#include "TestVpi.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <vector>
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<char*>("- [@%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<char*>("- [@%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<char*>("- [@%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<char*>("- [@%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<char*>("- [@%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<VerilatedContext> contextp{new VerilatedContext};
bool cbs_called;
contextp->commandArgs(argc, argv);
// contextp->debug(9);
const std::unique_ptr<VM_PREFIX> 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<char*>("- [@%" 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

View File

@ -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);

View File

@ -9,11 +9,19 @@
//
//*************************************************************************
#ifdef IS_VPI
#include "vpi_user.h"
#include <cstdlib>
#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 <cstdio>
#include <cstdlib>
@ -25,8 +33,7 @@
#include "TestSimulator.h"
#include "TestVpi.h"
const std::vector<int> cbs_to_test{cbReadWriteSynch, cbReadOnlySynch, cbNextSimTime,
cbStartOfSimulation, cbEndOfSimulation, cbValueChange};
const std::vector<int> cbs_to_test{cbValueChange};
enum CallbackState { PRE_REGISTER, ACTIVE, ACTIVE_AGAIN, REM_REREG_ACTIVE, POST_REMOVE };
const std::vector<CallbackState> cb_states{PRE_REGISTER, ACTIVE, ACTIVE_AGAIN, REM_REREG_ACTIVE,
@ -46,17 +53,29 @@ std::vector<CallbackState>::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<char*>(" 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<char*>("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<char*>("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<char*>("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<VerilatedContext> 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

View File

@ -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;

View File

@ -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

View File

@ -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);