// -*- 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 header /// /// This file is included automatically by Verilator in some of the C++ files /// it generates if timing features are used. /// /// This file is not part of the Verilated public-facing API. /// It is only for internal use. /// /// See the internals documentation docs/internals.rst for details. /// //************************************************************************* #ifndef VERILATOR_VERILATED_TIMING_H_ #define VERILATOR_VERILATED_TIMING_H_ #include "verilated.h" #include // clang-format off // Some preprocessor magic to support both Clang and GCC coroutines with both libc++ and libstdc++ #if defined _LIBCPP_VERSION // libc++ # if defined(__has_include) && !__has_include() && __has_include() # if __clang_major__ > 13 // Clang > 13 warns that coroutine types in std::experimental are deprecated # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-experimental-coroutine" # endif # include namespace std { using namespace experimental; // Bring std::experimental into the std namespace } # else # include # endif #else # if defined __clang__ && defined __GLIBCXX__ && !defined __cpp_impl_coroutine # define __cpp_impl_coroutine 1 // Clang doesn't define this, but it's needed for libstdc++ # endif # include # if __clang_major__ < 14 namespace std { // Bring coroutine library into std::experimental, as Clang < 14 expects it to be there namespace experimental { using namespace std; } } # endif #endif // clang-format on // Placeholder for compiling with --protect-ids #define VL_UNKNOWN "" //============================================================================= // VlFileLineDebug stores a SystemVerilog source code location. Used in VlCoroutineHandle for // debugging purposes. class VlFileLineDebug final { // MEMBERS #ifdef VL_DEBUG const char* m_filename = nullptr; int m_lineno = 0; #endif public: // CONSTRUCTORS // Construct VlFileLineDebug() = default; VlFileLineDebug(const char* filename, int lineno) #ifdef VL_DEBUG : m_filename{filename} , m_lineno{lineno} #endif { } // METHODS #ifdef VL_DEBUG const char* filename() const { return m_filename; } int lineno() const { return m_lineno; } #endif }; //============================================================================= // VlCoroutineHandle is a non-copyable (but movable) coroutine handle. On resume, the handle is // cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got // suspended, another VlCoroutineHandle was created to manage it. class VlCoroutineHandle final { VL_UNCOPYABLE(VlCoroutineHandle); // MEMBERS std::coroutine_handle<> m_coro; // The wrapped coroutine handle VlProcessRef m_process; // Data of the suspended process, null if not needed VlFileLineDebug m_fileline; public: // CONSTRUCTORS // Construct // non-explicit: // cppcheck-suppress noExplicitConstructor VlCoroutineHandle(VlProcessRef process) : m_coro{nullptr} , m_process{process} { if (m_process) m_process->state(VlProcess::WAITING); } VlCoroutineHandle(std::coroutine_handle<> coro, VlProcessRef process, VlFileLineDebug fileline) : m_coro{coro} , m_process{process} , m_fileline{fileline} { if (m_process) m_process->state(VlProcess::WAITING); } // Move the handle, leaving a nullptr // non-explicit: // cppcheck-suppress noExplicitConstructor VlCoroutineHandle(VlCoroutineHandle&& moved) : m_coro{std::exchange(moved.m_coro, nullptr)} , m_process{std::exchange(moved.m_process, nullptr)} , m_fileline{moved.m_fileline} {} // Destroy if the handle isn't null ~VlCoroutineHandle() { // Usually these coroutines should get resumed; we only need to clean up if we destroy a // model with some coroutines suspended if (VL_UNLIKELY(m_coro)) { m_coro.destroy(); if (m_process && m_process->state() != VlProcess::KILLED) { m_process->state(VlProcess::FINISHED); } } } // METHODS // Move the handle, leaving a null handle auto& operator=(VlCoroutineHandle&& moved) { m_coro = std::exchange(moved.m_coro, nullptr); m_process = std::exchange(moved.m_process, nullptr); m_fileline = moved.m_fileline; return *this; } // Resume the coroutine if the handle isn't null and the process isn't killed void resume(); #ifdef VL_DEBUG void dump() const; #endif }; enum class VlDelayPhase : bool { ACTIVE, INACTIVE }; //============================================================================= // VlDelayScheduler stores coroutines to be resumed at a certain simulation time. If the current // time is equal to a coroutine's resume time, the coroutine gets resumed. class VlDelayScheduler final { // TYPES // Time-sorted queue of timestamps and handles using VlDelayedCoroutineQueue = std::multimap; // MEMBERS VerilatedContext& m_context; VlDelayedCoroutineQueue m_queue; // Coroutines to be restored at a certain simulation time std::vector m_zeroDelayed; // Coroutines waiting for #0 std::vector m_zeroDlyResumed; // Coroutines that waited for #0 and are // to be resumed. Kept as a field to avoid // reallocation. public: // CONSTRUCTORS explicit VlDelayScheduler(VerilatedContext& context) : m_context{context} {} // METHODS // Resume coroutines waiting for the current simulation time void resume(); // Returns the simulation time of the next time slot (aborts if there are no delayed // coroutines) uint64_t nextTimeSlot() const; // Are there no delayed coroutines awaiting? bool empty() const { return m_queue.empty() && m_zeroDelayed.empty(); } // Are there coroutines to resume at the current simulation time? bool awaitingCurrentTime() const { return (!m_queue.empty() && (m_queue.cbegin()->first <= m_context.time())) || !m_zeroDelayed.empty(); } #ifdef VL_DEBUG void dump() const; #endif // Used by coroutines for co_awaiting a certain simulation time auto delay(uint64_t delay, VlProcessRef process, const char* filename = VL_UNKNOWN, int lineno = 0) { struct Awaitable final { VlProcessRef process; // Data of the suspended process, null if not needed VlDelayedCoroutineQueue& queue; std::vector& queueZeroDelay; const uint64_t delay; const VlDelayPhase phase; const VlFileLineDebug fileline; bool await_ready() const { return false; } // Always suspend void await_suspend(std::coroutine_handle<> coro) { if (phase == VlDelayPhase::ACTIVE) { queue.emplace(delay, VlCoroutineHandle{coro, process, fileline}); } else { queueZeroDelay.emplace_back(VlCoroutineHandle{coro, process, fileline}); } } void await_resume() const {} }; const VlDelayPhase phase = (delay == 0) ? VlDelayPhase::INACTIVE : VlDelayPhase::ACTIVE; #ifdef VL_DEBUG if (phase == VlDelayPhase::INACTIVE) { VL_WARN_MT(filename, lineno, VL_UNKNOWN, "Encountered #0 delay. #0 scheduling support is incomplete and the " "process will be resumed before combinational logic evaluation."); } #endif return Awaitable{process, m_queue, m_zeroDelayed, m_context.time() + delay, phase, VlFileLineDebug{filename, lineno}}; } }; //============================================================================= // VlTriggerScheduler stores coroutines to be resumed by a trigger. It does not keep track of its // trigger, relying on calling code to resume when appropriate. Coroutines are kept in two stages // - 'uncommitted' and 'ready'. Whenever a coroutine is suspended, it lands in the 'uncommitted' // stage. Only when commit() is called, these coroutines get moved to the 'ready' stage. That's // when they can be resumed. This is done to avoid resuming processes before they start waiting. class VlTriggerScheduler final { // TYPES using VlCoroutineVec = std::vector; // MEMBERS VlCoroutineVec m_uncommitted; // Coroutines suspended before commit() was called // (not resumable) VlCoroutineVec m_ready; // Coroutines that can be resumed (all coros from m_uncommitted are // moved here in commit()) VlCoroutineVec m_resumeQueue; // Coroutines being resumed by resume(); kept as a field to // avoid reallocation. Resumed coroutines are moved to // m_resumeQueue to allow adding coroutines to m_ready // during resume(). Outside of resume() should always be empty. public: // METHODS // Resumes all coroutines from the 'ready' stage void resume(const char* eventDescription = VL_UNKNOWN); // Moves all coroutines from m_uncommitted to m_ready void commit(const char* eventDescription = VL_UNKNOWN); // Are there no coroutines awaiting? bool empty() const { return m_ready.empty() && m_uncommitted.empty(); } #ifdef VL_DEBUG void dump(const char* eventDescription) const; #endif // Used by coroutines for co_awaiting a certain trigger auto trigger(bool commit, VlProcessRef process, const char* eventDescription = VL_UNKNOWN, const char* filename = VL_UNKNOWN, int lineno = 0) { VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n", eventDescription, filename, lineno);); struct Awaitable final { VlCoroutineVec& suspended; // Coros waiting on trigger VlProcessRef process; // Data of the suspended process, null if not needed VlFileLineDebug fileline; bool await_ready() const { return false; } // Always suspend void await_suspend(std::coroutine_handle<> coro) { suspended.emplace_back(coro, process, fileline); } void await_resume() const {} }; return Awaitable{commit ? m_ready : m_uncommitted, process, VlFileLineDebug{filename, lineno}}; } }; //============================================================================= // VlDynamicTriggerScheduler is used for cases where triggers cannot be statically referenced and // evaluated. Coroutines that make use of this scheduler must adhere to a certain procedure: // __Vtrigger = 0; // // while (!__Vtrigger) { // co_await __VdynSched.evaluation(); //
;
//         __Vtrigger = ;
//         __VdynShed.anyTriggered(__Vtrigger);
//         [optionally] co_await __VdynSched.postUpdate();
//         ;
//     }
//    co_await __VdynSched.resumption();
// The coroutines get resumed at trigger evaluation time, evaluate their local triggers, optionally
// await the post update step, and if the trigger is set, await proper resumption in the 'act' eval
// step.

class VlDynamicTriggerScheduler final {
    // TYPES
    using VlCoroutineVec = std::vector;

    // MEMBERS
    bool m_anyTriggered = false;  // If true, at least one trigger was set
    VlCoroutineVec m_suspended;  // Suspended coroutines awaiting trigger evaluation
    VlCoroutineVec m_evaluated;  // Coroutines currently being evaluated (for evaluate())
    VlCoroutineVec m_triggered;  // Coroutines whose triggers were set, and are awaiting resumption
    VlCoroutineVec m_post;  // Coroutines awaiting the post update step (only relevant for triggers
                            // with destructive post updates, e.g. named events)

    // METHODS
    auto awaitable(VlProcessRef process, VlCoroutineVec& queue, const char* filename, int lineno) {
        struct Awaitable final {
            VlProcessRef process;  // Data of the suspended process, null if not needed
            VlCoroutineVec& suspended;  // Coros waiting on trigger
            VlFileLineDebug fileline;

            bool await_ready() const { return false; }  // Always suspend
            void await_suspend(std::coroutine_handle<> coro) {
                suspended.emplace_back(coro, process, fileline);
            }
            void await_resume() const {}
        };
        return Awaitable{process, queue, VlFileLineDebug{filename, lineno}};
    }

public:
    // Evaluates all dynamic triggers (resumed coroutines that co_await evaluation())
    bool evaluate();
    // Called by coroutines that evaluate triggers to notify the scheduler if any triggers were set
    void anyTriggered(bool triggered) { m_anyTriggered = m_anyTriggered || triggered; }
    // Runs post updates for all dynamic triggers (resumes coroutines that co_await postUpdate())
    void doPostUpdates();
    // Resumes all coroutines whose triggers are set (those that co_await resumption())
    void resume();
#ifdef VL_DEBUG
    void dump() const;
#endif
    // Used by coroutines for co_awaiting trigger evaluation
    auto evaluation(VlProcessRef process, const char* eventDescription, const char* filename,
                    int lineno) {
        VL_DEBUG_IF(VL_DBG_MSGF("         Suspending process waiting for %s at %s:%d\n",
                                eventDescription, filename, lineno););
        return awaitable(process, m_suspended, filename, lineno);
    }
    // Used by coroutines for co_awaiting the trigger post update step
    auto postUpdate(VlProcessRef process, const char* eventDescription, const char* filename,
                    int lineno) {
        VL_DEBUG_IF(
            VL_DBG_MSGF("         Process waiting for %s at %s:%d awaiting the post update step\n",
                        eventDescription, filename, lineno););
        return awaitable(process, m_post, filename, lineno);
    }
    // Used by coroutines for co_awaiting the resumption step (in 'act' eval)
    auto resumption(VlProcessRef process, const char* eventDescription, const char* filename,
                    int lineno) {
        VL_DEBUG_IF(VL_DBG_MSGF("         Process waiting for %s at %s:%d awaiting resumption\n",
                                eventDescription, filename, lineno););
        return awaitable(process, m_triggered, filename, lineno);
    }
};

//=============================================================================
// VlForever is a helper awaitable type for suspending coroutines forever. Used for constant
// wait statements.

struct VlForever final {
    bool await_ready() const { return false; }  // Always suspend
    void await_suspend(std::coroutine_handle<> coro) const { coro.destroy(); }
    void await_resume() const {}
};

//=============================================================================
// VlForkSync is used to manage fork..join and fork..join_any constructs.

class VlForkSync final {
    // VlJoin stores the handle of a suspended coroutine that did a fork..join or fork..join_any.
    // If the counter reaches 0, the suspended coroutine shall be resumed.
    struct VlJoin final {
        size_t m_counter = 0;  // When reaches 0, resume suspended coroutine
        VlCoroutineHandle m_susp;  // Coroutine to resume
    };

    // The join info is shared among all forked processes
    std::shared_ptr m_join;

public:
    // Create the join object and set the counter to the specified number
    void init(size_t count, VlProcessRef process) { m_join.reset(new VlJoin{count, {process}}); }
    // Called whenever any of the forked processes finishes. If the join counter reaches 0, the
    // main process gets resumed
    void done(const char* filename = VL_UNKNOWN, int lineno = 0);
    // Used by coroutines for co_awaiting a join
    auto join(VlProcessRef process, const char* filename = VL_UNKNOWN, int lineno = 0) {
        assert(m_join);
        VL_DEBUG_IF(
            VL_DBG_MSGF("             Awaiting join of fork at: %s:%d\n", filename, lineno););
        struct Awaitable final {
            VlProcessRef process;  // Data of the suspended process, null if not needed
            const std::shared_ptr join;  // Join to await on
            VlFileLineDebug fileline;

            bool await_ready() { return join->m_counter == 0; }  // Suspend if join still exists
            void await_suspend(std::coroutine_handle<> coro) {
                join->m_susp = {coro, process, fileline};
            }
            void await_resume() const {}
        };
        return Awaitable{process, m_join, VlFileLineDebug{filename, lineno}};
    }
};

//=============================================================================
// VlCoroutine
// Return value of a coroutine. Used for chaining coroutine suspension/resumption.

class VlCoroutine final {
private:
    // TYPES
    struct VlPromise final {
        std::coroutine_handle<> m_continuation;  // Coroutine to resume after this one finishes
        VlCoroutine* m_corop = nullptr;  // Pointer to the coroutine return object

        ~VlPromise();

        VlCoroutine get_return_object() { return {this}; }

        // Never suspend at the start of the coroutine
        std::suspend_never initial_suspend() const { return {}; }

        // Never suspend at the end of the coroutine (thanks to this, the coroutine will clean up
        // after itself)
        std::suspend_never final_suspend() noexcept;

        void unhandled_exception() const { std::abort(); }
        void return_void() const {}
    };

    // MEMBERS
    VlPromise* m_promisep;  // The promise created for this coroutine

public:
    // TYPES
    using promise_type = VlPromise;  // promise_type has to be public

    // CONSTRUCTORS
    // Construct
    // cppcheck-suppress noExplicitConstructor
    VlCoroutine(VlPromise* promisep)
        : m_promisep{promisep} {
        m_promisep->m_corop = this;
    }
    // Move. Update the pointers each time the return object is moved
    // cppcheck-suppress noExplicitConstructor
    VlCoroutine(VlCoroutine&& other)
        : m_promisep{std::exchange(other.m_promisep, nullptr)} {
        if (m_promisep) m_promisep->m_corop = this;
    }
    ~VlCoroutine() {
        // Indicate to the promise that the return object is gone
        if (m_promisep) m_promisep->m_corop = nullptr;
    }

    // METHODS
    // Suspend the awaiter if the coroutine is suspended (the promise exists)
    bool await_ready() const noexcept { return !m_promisep; }
    // Set the awaiting coroutine as the continuation of the current coroutine
    void await_suspend(std::coroutine_handle<> coro) { m_promisep->m_continuation = coro; }
    void await_resume() const noexcept {}
};

#endif  // Guard