mirror of
https://github.com/verilator/verilator.git
synced 2025-01-01 04:07:34 +00:00
fcf0d03cd4
In non-static contexts like class objects or stack frames, the use of global trigger evaluation is not feasible. The concept of dynamic triggers allows for trigger evaluation in such cases. These triggers are simply local variables, and coroutines are themselves responsible for evaluating them. They await the global dynamic trigger scheduler object, which is responsible for resuming them during the trigger evaluation step in the 'act' eval region. Once the trigger is set, they await the dynamic trigger scheduler once again, and then get resumed during the resumption step in the 'act' eval region. Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
224 lines
7.3 KiB
C++
224 lines
7.3 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
// Copyright 2022 by Wilson Snyder. 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
|
|
//
|
|
//=========================================================================
|
|
///
|
|
/// \file
|
|
/// \brief Verilated timing implementation code
|
|
///
|
|
/// This file must be compiled and linked against all Verilated objects
|
|
/// that use timing features.
|
|
///
|
|
/// See the internals documentation docs/internals.rst for details.
|
|
///
|
|
//=========================================================================
|
|
|
|
#include "verilated_timing.h"
|
|
|
|
//======================================================================
|
|
// VlCoroutineHandle:: Methods
|
|
|
|
void VlCoroutineHandle::resume() {
|
|
// Only null if we have a fork..join_any and one of the other child processes resumed the
|
|
// main process
|
|
if (VL_LIKELY(m_coro)) {
|
|
VL_DEBUG_IF(VL_DBG_MSGF(" Resuming: "); dump(););
|
|
m_coro();
|
|
m_coro = nullptr;
|
|
}
|
|
}
|
|
|
|
#ifdef VL_DEBUG
|
|
void VlCoroutineHandle::dump() const {
|
|
VL_PRINTF("Process waiting at %s:%d\n", m_fileline.filename(), m_fileline.lineno());
|
|
}
|
|
#endif
|
|
|
|
//======================================================================
|
|
// VlDelayScheduler:: Methods
|
|
|
|
#ifdef VL_DEBUG
|
|
void VlDelayScheduler::VlDelayedCoroutine::dump() const {
|
|
VL_DBG_MSGF(" Awaiting time %" PRIu64 ": ", m_timestep);
|
|
m_handle.dump();
|
|
}
|
|
#endif
|
|
|
|
void VlDelayScheduler::resume() {
|
|
#ifdef VL_DEBUG
|
|
VL_DEBUG_IF(dump(); VL_DBG_MSGF(" Resuming delayed processes\n"););
|
|
#endif
|
|
while (awaitingCurrentTime()) {
|
|
if (m_queue.front().m_timestep != m_context.time()) {
|
|
VL_FATAL_MT(__FILE__, __LINE__, "",
|
|
"%Error: Encountered process that should've been resumed at an "
|
|
"earlier simulation time. Missed a time slot?");
|
|
}
|
|
// Move max element in the heap to the end
|
|
std::pop_heap(m_queue.begin(), m_queue.end());
|
|
VlCoroutineHandle handle = std::move(m_queue.back().m_handle);
|
|
m_queue.pop_back();
|
|
handle.resume();
|
|
}
|
|
}
|
|
|
|
uint64_t VlDelayScheduler::nextTimeSlot() const {
|
|
if (empty()) {
|
|
VL_FATAL_MT(__FILE__, __LINE__, "", "%Error: There is no next time slot scheduled");
|
|
}
|
|
return m_queue.front().m_timestep;
|
|
}
|
|
|
|
#ifdef VL_DEBUG
|
|
void VlDelayScheduler::dump() const {
|
|
if (m_queue.empty()) {
|
|
VL_DBG_MSGF(" No delayed processes:\n");
|
|
} else {
|
|
VL_DBG_MSGF(" Delayed processes:\n");
|
|
for (const auto& susp : m_queue) susp.dump();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//======================================================================
|
|
// VlTriggerScheduler:: Methods
|
|
|
|
void VlTriggerScheduler::resume(const char* eventDescription) {
|
|
#ifdef VL_DEBUG
|
|
VL_DEBUG_IF(dump(eventDescription);
|
|
VL_DBG_MSGF(" Resuming processes waiting for %s\n", eventDescription););
|
|
#endif
|
|
for (auto& susp : m_ready) susp.resume();
|
|
m_ready.clear();
|
|
commit(eventDescription);
|
|
}
|
|
|
|
void VlTriggerScheduler::commit(const char* eventDescription) {
|
|
#ifdef VL_DEBUG
|
|
if (!m_uncommitted.empty()) {
|
|
VL_DEBUG_IF(
|
|
VL_DBG_MSGF(" Committing processes waiting for %s:\n", eventDescription);
|
|
for (const auto& susp
|
|
: m_uncommitted) {
|
|
VL_DBG_MSGF(" - ");
|
|
susp.dump();
|
|
});
|
|
}
|
|
#endif
|
|
m_ready.reserve(m_ready.size() + m_uncommitted.size());
|
|
m_ready.insert(m_ready.end(), std::make_move_iterator(m_uncommitted.begin()),
|
|
std::make_move_iterator(m_uncommitted.end()));
|
|
m_uncommitted.clear();
|
|
}
|
|
|
|
#ifdef VL_DEBUG
|
|
void VlTriggerScheduler::dump(const char* eventDescription) const {
|
|
if (m_ready.empty()) {
|
|
VL_DBG_MSGF(" No ready processes waiting for %s\n", eventDescription);
|
|
} else {
|
|
for (const auto& susp : m_ready) {
|
|
VL_DBG_MSGF(" Ready processes waiting for %s:\n", eventDescription);
|
|
VL_DBG_MSGF(" - ");
|
|
susp.dump();
|
|
}
|
|
}
|
|
if (!m_uncommitted.empty()) {
|
|
VL_DBG_MSGF(" Uncommitted processes waiting for %s:\n", eventDescription);
|
|
for (const auto& susp : m_uncommitted) {
|
|
VL_DBG_MSGF(" - ");
|
|
susp.dump();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//======================================================================
|
|
// VlDynamicTriggerScheduler:: Methods
|
|
|
|
bool VlDynamicTriggerScheduler::evaluate() {
|
|
VL_DEBUG_IF(dump(););
|
|
std::swap(m_suspended, m_evaluated);
|
|
for (auto& coro : m_evaluated) coro.resume();
|
|
m_evaluated.clear();
|
|
return !m_triggered.empty();
|
|
}
|
|
|
|
void VlDynamicTriggerScheduler::doPostUpdates() {
|
|
VL_DEBUG_IF(if (!m_post.empty())
|
|
VL_DBG_MSGF(" Doing post updates for processes:\n"); //
|
|
for (const auto& susp
|
|
: m_post) {
|
|
VL_DBG_MSGF(" - ");
|
|
susp.dump();
|
|
});
|
|
for (auto& coro : m_post) coro.resume();
|
|
m_post.clear();
|
|
}
|
|
|
|
void VlDynamicTriggerScheduler::resume() {
|
|
VL_DEBUG_IF(if (!m_triggered.empty()) VL_DBG_MSGF(" Resuming processes:\n"); //
|
|
for (const auto& susp
|
|
: m_triggered) {
|
|
VL_DBG_MSGF(" - ");
|
|
susp.dump();
|
|
});
|
|
for (auto& coro : m_triggered) coro.resume();
|
|
m_triggered.clear();
|
|
}
|
|
|
|
#ifdef VL_DEBUG
|
|
void VlDynamicTriggerScheduler::dump() const {
|
|
if (m_suspended.empty()) {
|
|
VL_DBG_MSGF(" No suspended processes waiting for dynamic trigger evaluation\n");
|
|
} else {
|
|
for (const auto& susp : m_suspended) {
|
|
VL_DBG_MSGF(" Suspended processes waiting for dynamic trigger evaluation:\n");
|
|
VL_DBG_MSGF(" - ");
|
|
susp.dump();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//======================================================================
|
|
// VlForkSync:: Methods
|
|
|
|
void VlForkSync::done(const char* filename, int lineno) {
|
|
VL_DEBUG_IF(VL_DBG_MSGF(" Process forked at %s:%d finished\n", filename, lineno););
|
|
if (m_join->m_counter > 0) m_join->m_counter--;
|
|
if (m_join->m_counter == 0) m_join->m_susp.resume();
|
|
}
|
|
|
|
//======================================================================
|
|
// VlCoroutine:: Methods
|
|
|
|
VlCoroutine::VlPromise::~VlPromise() {
|
|
// Indicate to the return object that the coroutine has finished or been destroyed
|
|
if (m_corop) m_corop->m_promisep = nullptr;
|
|
// If there is a continuation, destroy it
|
|
if (m_continuation) m_continuation.destroy();
|
|
}
|
|
|
|
std::suspend_never VlCoroutine::VlPromise::final_suspend() noexcept {
|
|
// Indicate to the return object that the coroutine has finished
|
|
if (m_corop) {
|
|
m_corop->m_promisep = nullptr;
|
|
// Forget the return value, we won't need it and it won't be able to let us know if
|
|
// it's destroyed
|
|
m_corop = nullptr;
|
|
}
|
|
// If there is a continuation, resume it
|
|
if (m_continuation) {
|
|
m_continuation();
|
|
m_continuation = nullptr;
|
|
}
|
|
return {};
|
|
}
|