mirror of
https://github.com/verilator/verilator.git
synced 2024-12-29 10:47:34 +00:00
252 lines
8.5 KiB
C++
252 lines
8.5 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(););
|
|
if (m_process) { // If process state is managed with std::process
|
|
if (m_process->state() == VlProcess::KILLED) {
|
|
m_coro.destroy();
|
|
} else {
|
|
m_process->state(VlProcess::RUNNING);
|
|
m_coro();
|
|
}
|
|
} else {
|
|
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
|
|
|
|
void VlDelayScheduler::resume() {
|
|
#ifdef VL_DEBUG
|
|
VL_DEBUG_IF(dump(); VL_DBG_MSGF(" Resuming delayed processes\n"););
|
|
#endif
|
|
bool resumed = false;
|
|
|
|
while (!m_queue.empty() && (m_queue.cbegin()->first == m_context.time())) {
|
|
VlCoroutineHandle handle = std::move(m_queue.begin()->second);
|
|
m_queue.erase(m_queue.begin());
|
|
handle.resume();
|
|
resumed = true;
|
|
}
|
|
|
|
if (!m_zeroDelayed.empty()) {
|
|
// First, we need to move the coroutines out of the queue, as a resumed coroutine can
|
|
// suspend on #0 again, adding itself to the queue, which can result in reallocating the
|
|
// queue mid-iteration.
|
|
// We swap with the m_zeroDlyResumed field to keep the allocated buffer.
|
|
m_zeroDlyResumed.swap(m_zeroDelayed);
|
|
for (auto&& handle : m_zeroDlyResumed) handle.resume();
|
|
m_zeroDlyResumed.clear();
|
|
resumed = true;
|
|
// We are now in the Active region, so any coroutines added to m_zeroDelayed in the
|
|
// meantime will have to wait until the next Inactive region.
|
|
}
|
|
|
|
if (!resumed) {
|
|
VL_FATAL_MT(__FILE__, __LINE__, "",
|
|
"%Error: Encountered process that should've been resumed at an "
|
|
"earlier simulation time. Missed a time slot?\n");
|
|
}
|
|
}
|
|
|
|
uint64_t VlDelayScheduler::nextTimeSlot() const {
|
|
if (!m_queue.empty()) return m_queue.cbegin()->first;
|
|
if (m_zeroDelayed.empty())
|
|
VL_FATAL_MT(__FILE__, __LINE__, "", "There is no next time slot scheduled");
|
|
return m_context.time();
|
|
}
|
|
|
|
#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 (auto& susp : m_zeroDelayed) {
|
|
VL_DBG_MSGF(" Awaiting #0-delayed resumption, "
|
|
"time () %" PRIu64 ": ",
|
|
m_context.time());
|
|
susp.dump();
|
|
}
|
|
for (const auto& susp : m_queue) {
|
|
VL_DBG_MSGF(" Awaiting time %" PRIu64 ": ", susp.first);
|
|
susp.second.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
|
|
std::swap(m_ready, m_resumeQueue);
|
|
for (VlCoroutineHandle& coro : m_resumeQueue) coro.resume();
|
|
m_resumeQueue.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() {
|
|
m_anyTriggered = false;
|
|
VL_DEBUG_IF(dump(););
|
|
std::swap(m_suspended, m_evaluated);
|
|
for (auto& coro : m_evaluated) coro.resume();
|
|
m_evaluated.clear();
|
|
return m_anyTriggered;
|
|
}
|
|
|
|
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 {};
|
|
}
|