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
+173 -106
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;
}
}