// -*- 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 {}; }