// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Scheduling
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************

#ifndef VERILATOR_V3SCHED_H_
#define VERILATOR_V3SCHED_H_

#include "config_build.h"
#include "verilatedos.h"

#include "V3Ast.h"

#include <functional>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

class SenExprBuilder;

//============================================================================

namespace V3Sched {

namespace util {
using VarScopeSet = std::unordered_set<const AstVarScope*>;

inline bool isVlForceVec(const AstVarScope* vscp) {
    const AstCDType* const dtypep = VN_CAST(vscp->dtypep()->skipRefp(), CDType);
    return dtypep && dtypep->name() == "VlForceVec";
}

inline bool isForceReadMethod(VCMethod method) {
    return method == VCMethod::FORCE_READ || method == VCMethod::FORCE_READ_INDEX;
}

inline void collectForceReadEdgeIgnores(AstNode* nodep, VarScopeSet& out) {
    VarScopeSet writtenForceVecs;
    VarScopeSet writtenVars;

    nodep->foreach([&](const AstVarRef* refp) {
        AstVarScope* const vscp = refp->varScopep();
        if (!refp->access().isWriteOrRW()) return;
        if (!refp->varp()->ignoreSchedWrite()) writtenVars.emplace(vscp);
        if (!isVlForceVec(vscp)) return;
        writtenForceVecs.emplace(vscp);
        if (!refp->varp()->ignoreSchedWrite()) out.emplace(vscp);
    });

    if (writtenForceVecs.empty() || writtenVars.empty()) return;

    nodep->foreach([&](const AstCMethodHard* callp) {
        if (!isForceReadMethod(callp->method())) return;

        const AstVarRef* const fromRefp = VN_CAST(callp->fromp(), VarRef);
        if (!fromRefp || !writtenForceVecs.count(fromRefp->varScopep())) return;

        AstNodeExpr* const origp = callp->pinsp();
        if (!origp) return;
        origp->foreach([&](const AstVarRef* refp) {
            AstVarScope* const vscp = refp->varScopep();
            if (refp->access().isReadOrRW() && writtenVars.count(vscp)) out.emplace(vscp);
        });
    });
}
}  // namespace util

//============================================================================
// Throughout scheduling, we need to keep hold of AstActive nodes, together with the AstScope that
// they are under. LogicByScope is simply a vector of such pairs, with some additional convenience
// methods.
struct LogicByScope final : public std::vector<std::pair<AstScope*, AstActive*>> {
    // Add logic
    void add(AstScope* scopep, AstSenTree* senTreep, AstNode* logicp) {
        UASSERT_OBJ(!logicp->backp(), logicp, "Already linked");
        if (empty() || back().first != scopep || back().second->sentreep() != senTreep) {
            emplace_back(scopep, new AstActive{logicp->fileline(), "", senTreep});
        }
        back().second->addStmtsp(logicp);
    };

    // Create copy, with the AstActives cloned
    LogicByScope clone() const {
        LogicByScope result;
        for (const auto& pair : *this) {
            result.emplace_back(pair.first, pair.second->cloneTree(false));
        }
        return result;
    }

    // Delete actives (they should all be empty)
    void deleteActives() {
        for (const auto& pair : *this) {
            AstActive* const activep = pair.second;
            if (v3Global.opt.debugCheck()) {
                for (AstNode* nodep = activep->stmtsp(); nodep; nodep = nodep->nextp()) {
                    AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure);
                    UASSERT_OBJ(procp && !procp->stmtsp(), activep, "Leftover logic");
                }
            }
            if (activep->backp()) activep->unlinkFrBack();
            activep->deleteTree();
        }
        clear();
    };

    void foreachLogic(const std::function<void(AstNode*)>& f) const {
        for (const auto& pair : *this) {
            for (AstNode* nodep = pair.second->stmtsp(); nodep; nodep = nodep->nextp()) f(nodep);
        }
    }
};

// Logic in the design is classified based on what can trigger its execution.
// For details see the internals documentation.
struct LogicClasses final {
    LogicByScope m_static;  // Static variable initializers
    LogicByScope m_initial;  // initial blocks
    LogicByScope m_final;  // final blocks
    LogicByScope m_comb;  // Combinational logic (logic with implicit sensitivities)
    LogicByScope m_clocked;  // Clocked (or sequential) logic (logic with explicit sensitivities)
    LogicByScope m_hybrid;  // Hybrid logic (combinational logic with some explicit sensitivities)
    LogicByScope m_postponed;  // Postponed logic ($strobe)
    LogicByScope m_observed;  // Observed logic (contains AstAlwaysObserved)
    LogicByScope m_reactive;  // Reactive logic (contains AstAlwaysReactive)

    LogicClasses() = default;
    VL_UNCOPYABLE(LogicClasses);
    LogicClasses(LogicClasses&&) = default;
    LogicClasses& operator=(LogicClasses&&) = default;
};

// Combinational (including hybrid) logic, and clocked logic in partitioned to compute all clock
// signals in the 'act' region. For details see the internals documentation.
struct LogicRegions final {
    LogicByScope m_pre;  // AstAlwaysPre logic in 'act' region
    LogicByScope m_act;  // 'act' region logic
    LogicByScope m_nba;  // 'nba' region logic
    LogicByScope m_obs;  // AstAlwaysObserved logic in 'obs' region
    LogicByScope m_react;  // AstAlwaysReactive logic in 're' region

    LogicRegions() = default;
    VL_UNCOPYABLE(LogicRegions);
    LogicRegions(LogicRegions&&) = default;
    LogicRegions& operator=(LogicRegions&&) = default;
};

// Combinational (including hybrid) logic is replicated into the various scheduling regions.
// For details see the internals documentation.
struct LogicReplicas final {
    LogicByScope m_ico;  // Logic replicated into the 'ico' (Input Combinational) region
    LogicByScope m_act;  // Logic replicated into the 'act' region
    LogicByScope m_nba;  // Logic replicated into the 'nba' region
    LogicByScope m_obs;  // Logic replicated into the 'obs' region
    LogicByScope m_react;  // Logic replicated into the 're' region

    LogicReplicas() = default;
    VL_UNCOPYABLE(LogicReplicas);
    LogicReplicas(LogicReplicas&&) = default;
    LogicReplicas& operator=(LogicReplicas&&) = default;
};

// A TriggerKit holds all the components related to a scheduling region triggers
//
// Each piece of code is executed when some trigger bits are set. There is no
// code after scheduling which is not conditional on at least one trigger bit.
//
// There are 3 different kinds of triggers
// 1. "Sense" triggers, which correspond to a unique SenItem in the design
// 2. "Extra" triggers, which represent non-SenItem based conditions
// 3. "Pre" triggers, which are only used in the 'act' region. These are a copy
//    of some of the "Sense" triggers but only ever fire during one evaluation
//    of the 'act' loop. They are used for executing AlwaysPre block that
//    reside in the 'act' region. (AlwaysPre in 'nba' use the "Sense" triggers.)
//
// All trigger bits are stored in an unpacked array of fixed sized words, which
// is informally referred to in various places as the "trigger vector".
// There is only one trigger vector for each eval loop (~scheduling region).
//
// The organization of the trigger vector (array) is shown here, LSB on the
// right, MSB on the left. There are 4 main sections:
//
// | <- bit N-1                                                                         bit 0 -> |
// +--------------------+----------------+----------------------------------+--------------------+
// | Pre triggers       | Extra triggers | Sense triggers                   | Pre Sense triggers |
// +--------------------+----------------+----------------------------------+--------------------+
// |        'pre'       |                                 'vec'                                  |
//
// The section labelled "Pre Sense triggers" contains regular "Sense" triggers
// that are also duplicated in the "Pre triggers" section the first time they
// fire.
//
// The "Sense triggers" section contains the rest of the "Sense" triggers that
// do not need a copy in "Pre triggers".
//
// The "Sense triggers" and "Pre Sense triggers" together contain all "Sense"
// triggers described in point 1 above. "Extra triggers" contains the
// non-SenItem based additional conditions described in point 2, and the
// "Per triggers" section contains the "Pre" triggers described in point 3.
//
// All 4 sections in the trigger vector are padded to contain a whole word
// worth of bits (padding bits are always zero). Any one of the 4 sections
// can be empty, but "Pre triggers" and "Pre Sense triggers" are always the
// same size.
//
// The portion holding the Sense triggers and Extra triggers is referred to
// in various places as the 'vec' part, and is also informally referred to as
// the trigger vector.
//
// The portion holding the Pre trigger is named 'pre'
//
// The combination of 'pre' + 'vec' is the "extended trigger vector" referred
// to as 'ext' in various places.
//
// In realistic designs there are often no "Pre" triggers, and only a few
// "Extra" triggers, with "Sense" triggers taking up the bulk of the bits.
//
class TriggerKit final {
public:
    // Triggers are storead as an UnpackedArray with a fixed word size
    static constexpr uint32_t WORD_SIZE_LOG2 = 6;  // 64-bits / VL_QUADSIZE
    static constexpr uint32_t WORD_SIZE = 1 << WORD_SIZE_LOG2;

private:
    const std::string m_name;  // TriggerKit name
    const bool m_slow;  // TriggerKit is for schedulign 'slow' code
    const uint32_t m_nSenseWords;  // Number of words for Sense triggers
    const uint32_t m_nExtraWords;  // Number of words for Extra triggers
    const uint32_t m_nPreWords;  // Number of words for 'pre' part
    const uint32_t m_nVecWords = m_nSenseWords + m_nExtraWords;  // Number of words in 'vec' part

    // SenItems to corresponding bit indexes
    std::unordered_map<VNRef<const AstSenItem>, size_t> m_senItem2TrigIdx;
    // Data type of a single trigger word
    AstNodeDType* m_wordDTypep = nullptr;
    // Data type of a trigger vector holding one copy of all triggers
    AstUnpackArrayDType* m_trigVecDTypep = nullptr;
    // Data type of an extended trigger vector holding one copy of all triggers
    // + additional copy of 'pre' triggers
    AstUnpackArrayDType* m_trigExtDTypep = nullptr;
    // The AstVarScope representing the extended trigger vector
    AstVarScope* m_vscp = nullptr;
    // The AstVarScope representing the trigger accumulator vector
    // It is used to accumulate triggers that were found fired and cleared in beforeTrigger's
    // in current 'act' region iteration
    AstVarScope* m_vscAccp = nullptr;
    // The AstCFunc that computes the current active base triggers
    AstCFunc* m_compVecp = nullptr;
    // The AstCFunc that computes the current active extended triggers
    AstCFunc* m_compExtp = nullptr;
    // The AstCFunc that dumps a trigger vector
    AstCFunc* m_dumpp = nullptr;
    // The AstCFunc that dumps an exended trigger vector - create lazily
    mutable AstCFunc* m_dumpExtp = nullptr;
    // The AstCFunc testing if a trigger vector has any bits set - create lazily
    mutable AstCFunc* m_anySetVecp = nullptr;
    mutable AstCFunc* m_anySetExtp = nullptr;
    // The AstCFunc setting bits in a trigger vector that are set in another - create lazily
    mutable std::array<AstCFunc*, 4> m_orIntoVecps = {nullptr};
    // The AstCFunc setting a trigger vector to all zeroes - create lazily
    mutable AstCFunc* m_clearp = nullptr;

    // The map from 'pre' input SenTree to trigger SenTree
    std::unordered_map<const AstSenTree*, AstSenTree*> m_mapPre;
    // The map from other input SenTree to trigger SenTree
    std::unordered_map<const AstSenTree*, AstSenTree*> m_mapVec;

    // Methods to lazy construct functions processing trigger vectors
    AstCFunc* createDumpExtFunc() const;
    AstCFunc* createAnySetFunc(AstUnpackArrayDType* const dtypep) const;
    AstCFunc* createClearFunc() const;
    AstCFunc* createOrIntoFunc(AstUnpackArrayDType* const oDtypep,
                               AstUnpackArrayDType* const iDtypep) const;

    // Create an AstSenTree that is sensitive to the given trigger indices
    AstSenTree* newTriggerSenTree(AstVarScope* vscp, const std::vector<uint32_t>& indices) const;

    TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords, uint32_t nExtraWords,
               uint32_t nPreWords,
               std::unordered_map<VNRef<const AstSenItem>, size_t> senItem2TrigIdx, bool useAcc);
    VL_UNCOPYABLE(TriggerKit);

public:
    // Move constructible
    TriggerKit(TriggerKit&&) = default;
    ~TriggerKit() = default;
    TriggerKit& operator=(TriggerKit&&) = delete;

    // Utility for extra trigger allocation
    class ExtraTriggers final {
        friend class TriggerKit;
        std::vector<string> m_descriptions;  // Human readable description of extra triggers

    public:
        ExtraTriggers() = default;
        ~ExtraTriggers() = default;

        uint32_t allocate(const string& description) {
            m_descriptions.push_back(description);
            return m_descriptions.size() - 1;
        }
        uint32_t size() const { return m_descriptions.size(); }
    };
    // Generates list of assignments that fills
    static AstAssign* createSenTrigVecAssignment(AstVarScope* const target,
                                                 std::vector<AstNodeExpr*>& trigps);

    // Create a TriggerKit for the given AstSenTree vector
    static TriggerKit create(AstNetlist* netlistp,  //
                             AstCFunc* const initFuncp,  //
                             SenExprBuilder& senExprBuilder,  //
                             const std::vector<const AstSenTree*>& preTreeps,  //
                             const std::vector<const AstSenTree*>& senTreeps,  //
                             const string& name,  //
                             const ExtraTriggers& extraTriggers,  //
                             bool slow,  //
                             bool useAcc);

    // ACCESSORS
    AstVarScope* vscp() const { return m_vscp; }
    AstVarScope* vscAccp() const { return m_vscAccp; }
    size_t senItem2TrigIdx(const AstSenItem* senItemp) const {
        return m_senItem2TrigIdx.at(*senItemp);
    }
    AstCFunc* compBasep() const { return m_compVecp; }
    const std::string& name() const { return m_name; }
    const std::unordered_map<const AstSenTree*, AstSenTree*>& mapPre() const { return m_mapPre; }
    const std::unordered_map<const AstSenTree*, AstSenTree*>& mapVec() const { return m_mapVec; }

    // Helpers for code generation - lazy construct relevant functions
    AstNodeStmt* newAndNotCall(AstVarScope* op, AstVarScope* ap, AstVarScope* bp) const;
    AstNodeExpr* newAnySetCall(AstVarScope* vscp) const;
    AstNodeStmt* newClearCall(AstVarScope* vscp) const;
    AstNodeStmt* newOrIntoCall(AstVarScope* op, AstVarScope* ip) const;
    // Helpers for code generation
    AstNodeStmt* newCompBaseCall() const;
    AstNodeStmt* newCompExtCall(AstVarScope* vscp) const;
    AstNodeStmt* newDumpCall(AstVarScope* vscp, const std::string& tag, bool debugOnly) const;
    // Create a new (non-extended) trigger vector - might return nullptr if there are no triggers
    AstVarScope* newTrigVec(const std::string& name) const;

    // Create an AstSenTree that is sensitive to the given Extra trigger
    AstSenTree* newExtraTriggerSenTree(AstVarScope* vscp, uint32_t index) const;

    // Set then extra trigger bit at 'index' to the value of 'vscp', then set 'vscp' to 0
    void addExtraTriggerAssignment(AstVarScope* vscp, uint32_t index, bool clear = true) const;

    // Set trigger bit at 'index' when vscp's value changes from previous evaluation.
    // Creates a prev variable: trigger[bit] = (vscp != prev); prev = vscp
    void addValueChangeTriggerAssignment(AstNetlist* netlistp, AstCFunc* initFuncp,
                                         AstVarScope* vscp, uint32_t index) const;
};

// Everything needed for combining timing with static scheduling.
class TimingKit final {
    AstCFunc* m_resumeFuncp = nullptr;  // Global timing resume function
    AstCFunc* m_readyFuncp = nullptr;  // Global timing ready function

    // Additional var sensitivities for V3Order
    std::map<const AstVarScope*, std::set<AstSenTree*>> m_externalDomains;

public:
    LogicByScope m_lbs;  // Actives that resume timing schedulers
    AstNodeStmt* m_postUpdates = nullptr;  // Post updates for the trigger eval function

    // Remaps external domains using the specified trigger map
    std::map<const AstVarScope*, std::vector<AstSenTree*>> remapDomains(
        const std::unordered_map<const AstSenTree*, AstSenTree*>& trigMap) const VL_MT_DISABLED;
    // Get the delay scheduler variable
    AstVarScope* getDelayScheduler(AstNetlist* const netlistp) VL_MT_DISABLED;
    // Creates a timing resume call (if needed, else returns null)
    AstCCall* createResume(AstNetlist* const netlistp) VL_MT_DISABLED;
    // Creates a timing ready call (if needed, else returns null)
    AstCCall* createReady(AstNetlist* const netlistp) VL_MT_DISABLED;

    TimingKit() = default;
    TimingKit(LogicByScope&& lbs, AstNodeStmt* postUpdates,
              std::map<const AstVarScope*, std::set<AstSenTree*>>&& externalDomains)
        : m_externalDomains{externalDomains}
        , m_lbs{lbs}
        , m_postUpdates{postUpdates} {}
    VL_UNCOPYABLE(TimingKit);
    TimingKit(TimingKit&&) = default;
    TimingKit& operator=(TimingKit&&) = default;
};

class VirtIfaceTriggers final {
public:
    // One entry per VarScope of an interface member that may be written through a virtual
    // interface. Each gets its own extra trigger bit with value-change detection.
    struct TriggerEntry final {
        const AstIface* m_ifacep;  // Interface type
        const AstVar* m_memberp;  // Member variable in the interface definition
        AstVarScope* m_vscp;  // VarScope to monitor
    };

    std::vector<TriggerEntry> m_triggers;

    void addTrigger(const AstIface* ifacep, const AstVar* memberp, AstVarScope* vscp) {
        m_triggers.push_back(TriggerEntry{ifacep, memberp, vscp});
    }

    // Maps each triggered VarScope to its AstSenTree. findTriggeredIface() uses this
    // to look up triggers -- by specific VarScope for non-virtual reads, or by iterating
    // all entries of the same interface type for virtual interface reads.
    using VscpSensMap = std::map<const AstVarScope*, AstSenTree*>;
    VscpSensMap makeVscpToSensMap(const TriggerKit& trigKit, uint32_t firstIndex,
                                  AstVarScope* trigVscp) const;

    VL_UNCOPYABLE(VirtIfaceTriggers);
    VirtIfaceTriggers() = default;
    VirtIfaceTriggers(VirtIfaceTriggers&&) = default;
    VirtIfaceTriggers& operator=(VirtIfaceTriggers&&) = default;
};

// Creates trigger vars for signals driven via virtual interfaces
VirtIfaceTriggers makeVirtIfaceTriggers(AstNetlist* nodep) VL_MT_DISABLED;

// Creates the timing kit and marks variables written by suspendables
TimingKit prepareTiming(AstNetlist* const netlistp) VL_MT_DISABLED;

// Transforms fork sub-statements into separate functions
void transformForks(AstNetlist* const netlistp) VL_MT_DISABLED;

// Top level entry point to scheduling
void schedule(AstNetlist*) VL_MT_DISABLED;

// Sub-steps
LogicByScope breakCycles(AstNetlist* netlistp,
                         const LogicByScope& combinationalLogic) VL_MT_DISABLED;
LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLogic,
                       LogicByScope& hybridLogic) VL_MT_DISABLED;
LogicReplicas replicateLogic(LogicRegions&) VL_MT_DISABLED;

// Utility functions used by various steps in scheduling
namespace util {
// Create a new top level entry point
AstCFunc* makeTopFunction(AstNetlist* netlistp, const string& name, bool slow);
// Create a new sub function (not an entry point)
AstCFunc* makeSubFunction(AstNetlist* netlistp, const string& name, bool slow);
// Create statement that sets the given 'vscp' to 'val'
AstNodeStmt* setVar(AstVarScope* vscp, uint32_t val);
// Create statement that increments the given 'vscp' by one
AstNodeStmt* incrementVar(AstVarScope* vscp);
// Create statement that calls the given 'void' returning function
AstNodeStmt* callVoidFunc(AstCFunc* funcp);
// Create statement that checks counterp' to see if the eval loop iteration limit is reached
AstNodeStmt* checkIterationLimit(AstNetlist* netlistp, const string& name, AstVarScope* counterp,
                                 AstNodeStmt* dumpCallp);
// Split large function according to --output-split-cfuncs
void splitCheck(AstCFunc* ofuncp);
// Build an AstIf conditional on the given SenTree being triggered
AstIf* createIfFromSenTree(AstSenTree* senTreep);
}  // namespace util

void beforeTrigVisitor(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
                       const TriggerKit& trigKit);

}  // namespace V3Sched

#endif  // Guard
