Files
verilator/src/V3Sched.cpp
T

1150 lines
54 KiB
C++
Raw Normal View History

2022-05-15 16:03:32 +01:00
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Code 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
2022-05-15 16:03:32 +01:00
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// V3Sched::schedule is the top level entry-point to the scheduling algorithm
// at a high level, the process is:
//
// - Gather and classify all logic in the design based on what triggers its execution
// - Schedule static, initial and final logic classes in source order
// - Break combinational cycles by introducing hybrid logic
// - Create 'settle' region that restores the combinational invariant
// - Partition the clocked and combinational (including hybrid) logic into pre/act/nba.
// All clocks (signals referenced in an AstSenTree) generated via a blocking assignment
// (including combinationally generated signals) are computed within the act region.
// - Replicate combinational logic
// - Create input combinational logic region
2022-05-15 16:03:32 +01:00
// - Create the pre/act/nba triggers
// - Create the 'act' region evaluation function
// - Create the 'nba' region evaluation function
//
// The loops iterating these regions are not generated. They live in the
// run-time (VerilatedEvalLoop), which invokes the region evaluation
// functions created here on the generated model.
2022-05-15 16:03:32 +01:00
//
// Details of the algorithm are described in the internals documentation docs/internals.rst
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
2022-08-05 12:15:59 +01:00
#include "V3Sched.h"
#include "V3Const.h"
2022-05-15 16:03:32 +01:00
#include "V3EmitCBase.h"
#include "V3EmitV.h"
#include "V3Order.h"
2022-09-05 16:17:51 +02:00
#include "V3SenExprBuilder.h"
2022-05-15 16:03:32 +01:00
#include "V3Stats.h"
2022-09-22 17:28:42 +01:00
VL_DEFINE_DEBUG_FUNCTIONS;
2022-05-15 16:03:32 +01:00
namespace V3Sched {
namespace {
//============================================================================
// Utility functions
std::vector<const AstSenTree*> getSenTreesUsedBy(const std::vector<const LogicByScope*>& lbsps) {
2022-05-15 16:03:32 +01:00
const VNUser1InUse user1InUse;
std::vector<const AstSenTree*> result;
for (const LogicByScope* const lbsp : lbsps) {
for (const auto& pair : *lbsp) {
AstActive* const activep = pair.second;
AstSenTree* const senTreep = activep->sentreep();
2022-05-15 16:03:32 +01:00
if (senTreep->user1SetOnce()) continue;
if (senTreep->hasClocked() || senTreep->hasHybrid()) result.push_back(senTreep);
}
}
return result;
}
void remapSensitivities(const LogicByScope& lbs,
const std::unordered_map<const AstSenTree*, AstSenTree*>& senTreeMap) {
2022-05-15 16:03:32 +01:00
for (const auto& pair : lbs) {
AstActive* const activep = pair.second;
AstSenTree* const senTreep = activep->sentreep();
2022-05-15 16:03:32 +01:00
if (senTreep->hasCombo()) continue;
activep->sentreep(senTreeMap.at(senTreep));
2022-05-15 16:03:32 +01:00
}
}
void invertAndMergeSenTreeMap(
V3Order::TrigToSenMap& result,
const std::unordered_map<const AstSenTree*, AstSenTree*>& senTreeMap) {
for (const auto& pair : senTreeMap) result.emplace(pair.second, pair.first);
2022-05-15 16:03:32 +01:00
}
// Find VIF triggers that a given VarScope should be sensitive to.
// Case 2 (non-virtual interface read): sensitive to that specific VarScope's trigger only
// Case 3 (virtual interface read): sensitive to all member triggers of the same interface type
std::vector<AstSenTree*> findTriggeredIface(const AstVarScope* vscp,
const VirtIfaceTriggers::VscpSensMap& vscpToSens,
const VirtIfaceTriggers& virtIfaceTriggers) {
std::vector<AstSenTree*> result;
if (vscp->varp()->isVirtIface()) {
// Virtual interface variable -- sensitive to all member triggers of this interface type
const AstIface* const ifacep = VN_AS(vscp->varp()->dtypep(), IfaceRefDType)->ifacep();
for (const auto& entry : virtIfaceTriggers.m_triggers) {
if (entry.m_ifacep == ifacep) {
const auto it = vscpToSens.find(entry.m_vscp);
if (it != vscpToSens.end()) result.push_back(it->second);
}
}
} else {
// Non-virtual interface member -- sensitive to this VarScope's trigger only
const auto it = vscpToSens.find(vscp);
if (it != vscpToSens.end()) result.push_back(it->second);
}
// May be empty if sensIfacep() is set but no VIF write targets this member
return result;
}
//============================================================================
// Eval region builder
// Create the evaluation function of a scheduling region. The loops iterating
// the regions live in the run-time library (see VerilatedEvalLoop), which
// invokes this function once per iteration of the region's loop via a virtual
// method on the model (see V3EmitCModel). The function returns true if the
// region did any work, in which case the loop iterates again.
void createEvalRegion(
2023-12-10 13:32:24 +00:00
AstNetlist* netlistp, //
VEval eval, // The entry point of the current region
// Index of the region's 'first iteration' extra trigger, if it has one, otherwise ignored
uint32_t firstIterTrigger,
const TriggerKit& trigKit, // The trigger kit
AstVarScope* trigp, // The trigger vector - may be nullptr if no triggers or using 'condp'
AstNodeExpr* condp, // Explicit condition that must be true to run 'phaseWorkp'
2023-12-10 13:32:24 +00:00
AstNodeStmt* phasePrepp, // Prep statements run before checking triggers
AstNodeStmt* phaseWorkp, // The work to do if anything triggered
// Extra statements to run after the work, even if no triggers fired. This function is
// passed a variable, which must be set to true if we must continue and loop again,
// and must be unmodified otherwise.
std::function<AstNodeStmt*(AstVarScope*)> phaseExtra = [](AstVarScope*) { return nullptr; } //
) {
UASSERT(!trigp || !condp, "Cannot use both 'trigp' and 'condp' in 'createEvalRegion'");
UASSERT(!eval.firstIteration() || trigp,
"Region without triggers cannot need a first iteration flag");
// All work is under a trigger or condition, so with neither the region has
// nothing to evaluate, and what we create below reduces to a no-op function.
const std::string tag = eval.tag();
const std::string varPrefix = "__V" + tag;
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
FileLine* const flp = netlistp->fileline();
// Populate the trigger dump entry point function
if (trigp) {
UASSERT(eval.hasTriggers(), "Region with a trigger vector must have triggers");
netlistp->dumpTriggersFuncp(eval)->addStmtsp(trigKit.newDumpCall(trigp, tag, false));
}
AstCFunc* const funcp = netlistp->evalFuncp(eval);
// A flag is passed from the run-time eval loop if this is the first iteration of the
// current loop
if (eval.firstIteration()) {
AstVarScope* const firstIterArgp = util::newArgument(funcp, netlistp->findBitDType(),
"firstIteration", VDirection::INPUT);
// Set the region's 'first iteration' trigger straight from the argument
funcp->addStmtsp(trigKit.newExtraTriggerAssignment(firstIterArgp, firstIterTrigger));
// Only 'stl' also needs a module level flag, for design logic that reads
// it directly (see V3Timing). Those reads can be anywhere in the design,
// hence module level. Always created, even if nothing reads it.
// TODO: get rid of this special case
if (eval == VEval::STL) {
AstVarScope* const firstIterp = netlistp->stlFirstIterationp();
firstIterp->varp()->noReset(true);
firstIterp->varp()->isInternal(true);
funcp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, firstIterp, VAccess::WRITE},
new AstVarRef{flp, firstIterArgp, VAccess::READ}});
}
}
{
// Add the preparatory statements
funcp->addStmtsp(phasePrepp);
// The execute flag
AstVarScope* const executeFlagp = scopeTopp->createTemp(varPrefix + "Execute", 1);
executeFlagp->varp()->noReset(true);
// If there is work in this region, execute it if any triggers fired
if (phaseWorkp) {
AstNodeExpr* const lhsp = new AstVarRef{flp, executeFlagp, VAccess::WRITE};
// If using explicit condition, that directly determines whether to execute,
// otherwise check if any triggers are fired
AstNodeExpr* const rhsp = condp ? condp : trigKit.newAnySetCall(trigp);
funcp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
// Add the work
AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, executeFlagp, VAccess::READ}};
ifp->addThensp(phaseWorkp);
funcp->addStmtsp(ifp);
}
2023-12-10 13:32:24 +00:00
// Construct the extra statements
AstNodeStmt* const extraWorkp = phaseExtra(executeFlagp);
if (extraWorkp) funcp->addStmtsp(extraWorkp);
2023-12-10 13:32:24 +00:00
// The function returns true iff it did run work
AstNodeExpr* const retp
= phaseWorkp || extraWorkp
? static_cast<AstNodeExpr*>(new AstVarRef{flp, executeFlagp, VAccess::READ})
: static_cast<AstNodeExpr*>(new AstConst{flp, AstConst::BitFalse{}});
funcp->addStmtsp(new AstCReturn{flp, retp});
}
}
2022-05-15 16:03:32 +01:00
//============================================================================
// Collect and classify all logic in the design
LogicClasses gatherLogicClasses(AstNetlist* netlistp) {
LogicClasses result;
netlistp->foreach([&](AstScope* scopep) {
scopep->foreach([&](AstActive* activep) {
AstSenTree* const senTreep = activep->sentreep();
if (senTreep->hasStatic()) {
2022-05-15 16:03:32 +01:00
UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep,
"static initializer with additional sensitivities");
result.m_static.emplace_back(scopep, activep);
} else if (senTreep->hasInitial()) {
UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep,
"'initial' logic with additional sensitivities");
result.m_initial.emplace_back(scopep, activep);
} else if (senTreep->hasFinal()) {
UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep,
"'final' logic with additional sensitivities");
result.m_final.emplace_back(scopep, activep);
} else if (senTreep->hasCombo()) {
UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep,
"combinational logic with additional sensitivities");
if (VN_IS(activep->stmtsp(), AlwaysPostponed)) {
result.m_postponed.emplace_back(scopep, activep);
} else {
result.m_comb.emplace_back(scopep, activep);
}
2022-05-15 16:03:32 +01:00
} else {
UASSERT_OBJ(senTreep->hasClocked(), activep, "What else could it be?");
2022-12-23 12:34:49 +00:00
if (VN_IS(activep->stmtsp(), AlwaysObserved)) {
result.m_observed.emplace_back(scopep, activep);
} else if (VN_IS(activep->stmtsp(), AlwaysReactive)) {
result.m_reactive.emplace_back(scopep, activep);
} else {
result.m_clocked.emplace_back(scopep, activep);
}
2022-05-15 16:03:32 +01:00
}
});
});
return result;
}
//============================================================================
// Simple ordering in source order
void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) {
2022-08-22 14:26:32 +02:00
// Create new subfunc for scope
const auto createNewSubFuncp = [&](AstScope* const scopep) {
const string subName{funcp->name() + "__" + scopep->nameDotless()};
AstCFunc* const subFuncp = new AstCFunc{scopep->fileline(), subName, scopep};
subFuncp->isLoose(true);
subFuncp->isConst(false);
subFuncp->declPrivate(true);
subFuncp->slow(funcp->slow());
2022-09-15 19:43:56 +01:00
scopep->addBlocksp(subFuncp);
2022-08-22 14:26:32 +02:00
// Call it from the top function
2025-10-27 15:16:28 +00:00
funcp->addStmtsp(util::callVoidFunc(subFuncp));
2022-08-22 14:26:32 +02:00
return subFuncp;
};
2022-05-15 16:03:32 +01:00
const VNUser1InUse user1InUse; // AstScope -> AstCFunc: the sub-function for the scope
2022-08-22 14:26:32 +02:00
const VNUser2InUse user2InUse; // AstScope -> int: sub-function counter used for names
2022-05-15 16:03:32 +01:00
for (const auto& pair : lbs) {
AstScope* const scopep = pair.first;
AstActive* const activep = pair.second;
2022-08-22 14:26:32 +02:00
// Create a sub-function per scope so we can V3Combine them later
if (!scopep->user1p()) scopep->user1p(createNewSubFuncp(scopep));
2022-05-15 16:03:32 +01:00
// Add statements to sub-function
for (AstNode *logicp = activep->stmtsp(), *nextp; logicp; logicp = nextp) {
2022-08-22 14:26:32 +02:00
auto* subFuncp = VN_AS(scopep->user1p(), CFunc);
2022-05-15 16:03:32 +01:00
nextp = logicp->nextp();
if (AstNodeProcedure* const procp = VN_CAST(logicp, NodeProcedure)) {
2022-09-15 19:43:56 +01:00
if (AstNode* bodyp = procp->stmtsp()) {
2022-05-15 16:03:32 +01:00
bodyp->unlinkFrBackWithNext();
2022-08-22 14:26:32 +02:00
// If the process is suspendable, we need a separate function (a coroutine)
if (procp->isSuspendable()) {
funcp->slow(false);
2022-08-22 14:26:32 +02:00
subFuncp = createNewSubFuncp(scopep);
2023-11-21 02:02:56 +00:00
subFuncp->name(subFuncp->name() + "__Vtiming__"
+ cvtToStr(scopep->user2Inc()));
2022-08-22 14:26:32 +02:00
subFuncp->rtnType("VlCoroutine");
if (VN_IS(procp, Always)) {
subFuncp->slow(false);
FileLine* const flp = procp->fileline();
2025-09-29 16:25:25 +02:00
AstNodeExpr* const condp = new AstCExpr{
flp, "VL_LIKELY(!vlSymsp->_vm_contextp__->gotFinish())", 1};
2025-09-29 16:25:25 +02:00
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(new AstLoopTest{flp, loopp, condp});
loopp->addStmtsp(bodyp);
bodyp = loopp;
2022-08-22 14:26:32 +02:00
}
}
2022-05-15 16:03:32 +01:00
subFuncp->addStmtsp(bodyp);
if (procp->needProcess()) subFuncp->setNeedProcess();
2025-10-27 15:16:28 +00:00
util::splitCheck(subFuncp);
2022-05-15 16:03:32 +01:00
}
} else {
logicp->unlinkFrBack();
subFuncp->addStmtsp(logicp);
}
}
if (activep->backp()) activep->unlinkFrBack();
VL_DO_DANGLING(activep->deleteTree(), activep);
}
}
//============================================================================
// Create simply ordered functions
2025-03-18 13:34:04 +00:00
AstCFunc* createStatic(AstNetlist* netlistp, const LogicClasses& logicClasses) {
AstCFunc* const funcp = netlistp->evalFuncp(VEval::STATIC);
const LogicByScope& orig = logicClasses.m_static;
if (orig.size() <= 1) {
orderSequentially(funcp, orig);
return funcp;
}
// Level-based module sorting can reorder packages so that an importing
// package runs before the imported one. Re-sort package entries by source
// file position to restore compilation order (IEEE 1800-2023 26.3).
std::vector<size_t> indices(orig.size());
for (size_t i = 0; i < orig.size(); ++i) indices[i] = i;
std::stable_sort(indices.begin(), indices.end(), [&](size_t a, size_t b) {
const AstNodeModule* const modA = orig[a].first->modp();
const AstNodeModule* const modB = orig[b].first->modp();
const bool isPkgA = VN_IS(modA, Package);
const bool isPkgB = VN_IS(modB, Package);
if (isPkgA != isPkgB) return isPkgA; // Packages before non-packages
if (isPkgA && isPkgB) {
// Sort packages by source file position (compilation order)
return modA->fileline()->operatorCompare(*modB->fileline()) < 0;
}
return false; // Both non-package: preserve original order
});
LogicByScope sorted;
sorted.reserve(orig.size());
for (const size_t i : indices) sorted.emplace_back(orig[i].first, orig[i].second);
orderSequentially(funcp, sorted);
2025-03-18 13:34:04 +00:00
return funcp; // Not splitting yet as it is not final
2022-05-15 16:03:32 +01:00
}
2025-03-18 13:34:04 +00:00
void createInitial(AstNetlist* netlistp, const LogicClasses& logicClasses) {
AstCFunc* const funcp = netlistp->evalFuncp(VEval::INITIAL);
2022-05-15 16:03:32 +01:00
orderSequentially(funcp, logicClasses.m_initial);
2025-10-27 15:16:28 +00:00
util::splitCheck(funcp);
2022-05-15 16:03:32 +01:00
}
void createPostponed(AstNetlist* netlistp, const LogicClasses& logicClasses) {
AstCFunc* const funcp = netlistp->evalFuncp(VEval::POSTPONED);
orderSequentially(funcp, logicClasses.m_postponed);
2025-10-27 15:16:28 +00:00
util::splitCheck(funcp);
}
2022-05-15 16:03:32 +01:00
void createFinal(AstNetlist* netlistp, const LogicClasses& logicClasses) {
AstCFunc* const funcp = netlistp->evalFuncp(VEval::FINAL);
2022-05-15 16:03:32 +01:00
orderSequentially(funcp, logicClasses.m_final);
2025-10-27 15:16:28 +00:00
util::splitCheck(funcp);
2022-05-15 16:03:32 +01:00
}
//============================================================================
// Helper that creates virtual interface value-change triggers
void addVirtIfaceTriggerAssignments(AstNetlist* netlistp, AstCFunc* initFuncp,
const VirtIfaceTriggers& virtIfaceTriggers,
uint32_t firstIndex, const TriggerKit& trigKit) {
uint32_t index = firstIndex;
for (const auto& entry : virtIfaceTriggers.m_triggers) {
trigKit.addValueChangeTriggerAssignment(netlistp, initFuncp, entry.m_vscp, index);
++index;
}
}
// Order the combinational logic to create the 'stl' region
void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilder& senExprBulider,
LogicClasses& logicClasses, const CovergroupRefBindings& cgRefBindings) {
// Clone, because ordering is destructive, but we still need them for the other regions
2022-05-15 16:03:32 +01:00
LogicByScope comb = logicClasses.m_comb.clone();
LogicByScope hybrid = logicClasses.m_hybrid.clone();
// We have an extra trigger denoting this is the first iteration of the settle loop
TriggerKit::ExtraTriggers extraTriggers;
const uint32_t firstIterationTrigger = extraTriggers.allocate("first iteration");
2022-05-15 16:03:32 +01:00
// Gather the relevant sensitivity expressions and create the trigger kit
const auto& senTreeps = getSenTreesUsedBy({&comb, &hybrid});
const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBulider, {},
2026-02-11 19:35:59 +01:00
senTreeps, "stl", extraTriggers, true, false);
2022-05-15 16:03:32 +01:00
// Remap sensitivities (comb has none, so only do the hybrid)
remapSensitivities(hybrid, trigKit.mapVec());
2022-05-15 16:03:32 +01:00
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
V3Order::TrigToSenMap trigToSen;
invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec());
2022-05-15 16:03:32 +01:00
// First trigger is for pure combinational triggers (first iteration)
2022-07-21 17:34:12 +01:00
AstSenTree* const inputChanged
= trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger);
2022-05-15 16:03:32 +01:00
// Create and the body function
2022-07-14 11:06:20 +01:00
AstCFunc* const stlFuncp = V3Order::order(
netlistp, {&comb, &hybrid}, trigToSen, cgRefBindings, "stl", false, true,
2022-07-14 11:06:20 +01:00
[=](const AstVarScope*, std::vector<AstSenTree*>& out) { out.push_back(inputChanged); });
2025-10-27 15:16:28 +00:00
util::splitCheck(stlFuncp);
2022-05-15 16:03:32 +01:00
// Create the region evaluation function
createEvalRegion( //
netlistp, VEval::STL, firstIterationTrigger, trigKit,
// Use trigger
trigKit.vscp(), nullptr,
// Explicit condition
// Prep statements: Compute the current 'stl' triggers
2026-02-11 19:35:59 +01:00
[&trigKit] {
AstNodeStmt* const stmtp = trigKit.newCompBaseCall();
if (stmtp) stmtp->addNext(trigKit.newDumpCall(trigKit.vscp(), trigKit.name(), true));
return stmtp;
}(),
// Work statements: Invoke the 'stl' function
2025-10-27 15:16:28 +00:00
util::callVoidFunc(stlFuncp));
2022-05-15 16:03:32 +01:00
}
//============================================================================
// Order the replicated combinational logic to create the 'ico' region
void createIcoRegion(AstNetlist* netlistp, AstCFunc* const initFuncp,
SenExprBuilder& senExprBuilder, LogicByScope& logic,
const VirtIfaceTriggers& virtIfaceTriggers,
const CovergroupRefBindings& cgRefBindings) {
2022-05-15 16:03:32 +01:00
// SystemC only: Any top level inputs feeding a combinational logic must be marked,
// so we can make them sc_sensitive
if (v3Global.opt.systemC()) {
logic.foreachLogic([](AstNode* logicp) {
logicp->foreach([](AstVarRef* refp) {
2022-05-15 16:03:32 +01:00
if (refp->access().isWriteOnly()) return;
AstVarScope* const vscp = refp->varScopep();
if (vscp->scopep()->isTop() && vscp->varp()->isNonOutput()) {
vscp->varp()->scSensitive(true);
}
});
});
}
// We have some extra trigger denoting external conditions
AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp();
TriggerKit::ExtraTriggers extraTriggers;
const uint32_t firstIterationTrigger = extraTriggers.allocate("first iteration");
const uint32_t dpiExportTriggerIndex = dpiExportTriggerVscp
? extraTriggers.allocate("DPI export trigger")
: std::numeric_limits<uint32_t>::max();
const uint32_t firstVifTriggerIndex = extraTriggers.size();
for (const auto& entry : virtIfaceTriggers.m_triggers) {
extraTriggers.allocate("virtual interface member: " + entry.m_ifacep->name() + "."
+ entry.m_memberp->name());
}
2022-05-15 16:03:32 +01:00
// Create the input change detect SenTrees.
// If there is a lot of combinationallogic hanging of the top level inputs, we can save
// a lot of work by only evaluating it if an input has actually changed. This in
// paticular helps hierarchical models partitioned across combinaitonal boundaries.
// The change detect itself should be fairly cheap otherwise so alway do it.
// For correctness, don't create a change detect for top level inputs also written
// by the design, as the change detect 'previous value' would get out of sync.
// Also omit a SenTree for types that don't have the required '!=' operator.
// Any signal that does not have an explicit change detect trigger will fall back to
// using the 'first iteration' trigger, same as if this optimization was disabled.
std::unordered_map<const AstVarScope*, AstSenTree*> inp2changedp;
std::vector<AstSenTree*> icoChangeSenTreeps;
if (v3Global.opt.fIcoChangeDetect().isTrue()) {
FileLine* const flp = netlistp->fileline();
AstScope* const scopep = netlistp->topScopep()->scopep();
for (AstVarScope* vscp = scopep->varsp(); vscp; vscp = VN_AS(vscp->nextp(), VarScope)) {
// Only for top level ports, assume outputs don't change externally
if (!vscp->varp()->isPrimaryInish()) continue;
// Don't do if written by the design - wouldn't update the change detect 'prev' value
if (vscp->varp()->icoMaybeWritten()) continue;
// Don't do if forceable, as we can't see the actual value - this is belt and braces
if (vscp->varp()->isForced()) continue;
// Can't handle unpacked arrays (they have special types when primary input)
if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) continue;
// Similarly to arrays, can't handle SystemC types
if (vscp->varp()->isSc()) continue;
// Create a sen tree triggered when this input changes
AstSenTree*& senTreepr = inp2changedp[vscp];
UASSERT_OBJ(!senTreepr, vscp, "Duplicate input change detect trigger");
AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ};
AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_CHANGED, refp};
senTreepr = new AstSenTree{flp, senItemp};
icoChangeSenTreeps.push_back(senTreepr);
}
}
V3Stats::addStat("Scheduling, 'ico' change detect triggers", icoChangeSenTreeps.size());
2022-05-15 16:03:32 +01:00
// Gather the relevant sensitivity expressions and create the trigger kit
std::vector<const AstSenTree*> senTreeps = getSenTreesUsedBy({&logic});
senTreeps.insert(senTreeps.end(), icoChangeSenTreeps.begin(), icoChangeSenTreeps.end());
const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, {},
2026-02-11 19:35:59 +01:00
senTreeps, "ico", extraTriggers, false, false);
std::ignore = senExprBuilder.getAndClearResults();
2022-05-15 16:03:32 +01:00
if (dpiExportTriggerVscp) {
trigKit.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
}
addVirtIfaceTriggerAssignments(netlistp, initFuncp, virtIfaceTriggers, firstVifTriggerIndex,
trigKit);
2022-05-15 16:03:32 +01:00
// Remap sensitivities
remapSensitivities(logic, trigKit.mapVec());
for (auto& pair : inp2changedp) pair.second = trigKit.mapVec().at(pair.second);
2022-05-15 16:03:32 +01:00
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
V3Order::TrigToSenMap trigToSen;
invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec());
2022-05-15 16:03:32 +01:00
// The 'first iteration' trigger for top level inputs
AstSenTree* const firstIterTriggerp
= trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger);
2022-05-15 16:03:32 +01:00
// The DPI Export trigger
AstSenTree* const dpiExportTriggered
= dpiExportTriggerVscp
? trigKit.newExtraTriggerSenTree(trigKit.vscp(), dpiExportTriggerIndex)
: nullptr;
const auto& vifVscpToSensIco
= virtIfaceTriggers.makeVscpToSensMap(trigKit, firstVifTriggerIndex, trigKit.vscp());
2022-05-15 16:03:32 +01:00
// Create and Order the body function
AstCFunc* const icoFuncp = V3Order::order(
netlistp, {&logic}, trigToSen, cgRefBindings, "ico", false, false,
[&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
AstVar* const varp = vscp->varp();
// If it has an explicit change detect trigger, use that,
// otherwise fall back to using the 'first iteration' trigger
auto it = inp2changedp.find(vscp);
if (it != inp2changedp.end()) {
out.push_back(it->second);
} else if (varp->isPrimaryInish() || varp->isSigUserRWPublic() || varp->sampled()) {
out.push_back(firstIterTriggerp);
}
// Add other triggers
if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered);
if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) {
const auto& ifaceTriggered
= findTriggeredIface(vscp, vifVscpToSensIco, virtIfaceTriggers);
out.insert(out.end(), ifaceTriggered.begin(), ifaceTriggered.end());
}
});
2025-10-27 15:16:28 +00:00
util::splitCheck(icoFuncp);
2022-05-15 16:03:32 +01:00
// Create the region evaluation function
createEvalRegion( //
netlistp, VEval::ICO, firstIterationTrigger, trigKit,
// Use trigger
trigKit.vscp(), nullptr,
// Prep statements: Compute the current 'ico' triggers
2026-02-11 19:35:59 +01:00
[&trigKit] {
AstNodeStmt* const stmtp = trigKit.newCompBaseCall();
if (stmtp) stmtp->addNext(trigKit.newDumpCall(trigKit.vscp(), trigKit.name(), true));
return stmtp;
}(),
// Work statements: Invoke the 'ico' function
2025-10-27 15:16:28 +00:00
util::callVoidFunc(icoFuncp));
2022-05-15 16:03:32 +01:00
// Release temporary input change detect SenTrees
for (AstSenTree* const senTreep : icoChangeSenTreeps)
VL_DO_DANGLING(senTreep->deleteTree(), senTreep);
icoChangeSenTreeps.clear();
2022-05-15 16:03:32 +01:00
}
//============================================================================
// EvalKit groups items that have to be passed to createEval() for a given eval region
2022-12-23 12:34:49 +00:00
struct EvalKit final {
// The AstVarScope representing the region's trigger vector
AstVarScope* const m_vscp = nullptr;
// The AstCFunc that evaluates the region's logic
AstCFunc* const m_funcp = nullptr;
// Is this kit used/required?
bool empty() const { return !m_funcp; }
};
2022-12-23 12:34:49 +00:00
//============================================================================
// Create the evaluation function of each region of a time step
2022-05-15 16:03:32 +01:00
void createEval(AstNetlist* netlistp, //
const TriggerKit& trigKit, //
2022-12-23 12:34:49 +00:00
const EvalKit& actKit, //
const EvalKit& nbaKit, //
const EvalKit& obsKit, //
const EvalKit& reactKit, //
2022-08-22 14:26:32 +02:00
TimingKit& timingKit //
2022-05-15 16:03:32 +01:00
) {
FileLine* const flp = netlistp->fileline();
// Grab the delay scheduler variable, if any
AstVarScope* const delaySchedVscp = timingKit.getDelayScheduler(netlistp);
2026-02-11 19:35:59 +01:00
// 'createResume' consumes the contents that 'createReady' needs, so do the right order
AstCCall* const timingReadyp = timingKit.createReady(netlistp);
AstCCall* const timingResumep = timingKit.createResume(netlistp);
// Create the 'act' region
createEvalRegion( //
netlistp, VEval::ACT, 0, trigKit,
// Use trigger
actKit.m_vscp, nullptr,
// Prep statements
[&]() {
// Compute the current 'act' triggers - the NBA triggers are the latched value
2026-02-11 19:35:59 +01:00
AstNodeStmt* stmtsp = trigKit.newCompBaseCall();
AstNodeStmt* const dumpp
= stmtsp ? trigKit.newDumpCall(trigKit.vscp(), trigKit.name(), true) : nullptr;
// Mark as ready for triggered awaits
if (timingReadyp) stmtsp = AstNode::addNext(stmtsp, timingReadyp->makeStmt());
if (AstVarScope* const vscAccp = trigKit.vscAccp()) {
stmtsp = AstNode::addNext(stmtsp, trigKit.newOrIntoCall(actKit.m_vscp, vscAccp));
}
stmtsp = AstNode::addNext(stmtsp, trigKit.newCompExtCall(nbaKit.m_vscp));
stmtsp = AstNode::addNext(stmtsp, dumpp);
// Latch the 'act' triggers under the 'nba' triggers
stmtsp = AstNode::addNext(stmtsp, trigKit.newOrIntoCall(nbaKit.m_vscp, actKit.m_vscp));
//
return stmtsp;
}(),
// Work statements
[&]() {
AstNodeStmt* workp = nullptr;
2026-02-11 19:35:59 +01:00
if (AstVarScope* const actAccp = trigKit.vscAccp()) {
AstCMethodHard* const cCallp = new AstCMethodHard{
flp, new AstVarRef{flp, actAccp, VAccess::WRITE}, VCMethod::UNPACKED_FILL,
new AstConst{flp, AstConst::Unsized64{}, 0}};
cCallp->dtypeSetVoid();
workp = AstNode::addNext(workp, cCallp->makeStmt());
}
// Resume triggered timing schedulers
2026-02-11 19:35:59 +01:00
if (timingResumep) workp = AstNode::addNext(workp, timingResumep->makeStmt());
// Invoke the 'act' function
workp = AstNode::addNext(workp, util::callVoidFunc(actKit.m_funcp));
//
return workp;
}());
// Create the 'inact' region
createEvalRegion( //
netlistp, VEval::INACT, 0, trigKit,
// Use explicit condition
nullptr,
[&]() -> AstNodeExpr* {
if (!delaySchedVscp) return nullptr;
// Run if any zero delays are pending
AstNodeExpr* const callp
= new AstCMethodHard{flp, new AstVarRef{flp, delaySchedVscp, VAccess::READ},
VCMethod::SCHED_AWAITING_ZERO_DELAY};
callp->dtypeSetBit();
return callp;
}(),
// Prep statements
nullptr,
// Work statements
[&]() -> AstNodeStmt* {
if (!delaySchedVscp) {
// Nothing to do if there are no delays at all in the design
return nullptr;
} else if (v3Global.usesZeroDelay()) {
// Resume processes watiting for #0 delay
AstCMethodHard* const callp = new AstCMethodHard{
flp, new AstVarRef{flp, delaySchedVscp, VAccess::READWRITE},
VCMethod::SCHED_RESUME_ZERO_DELAY};
callp->dtypeSetVoid();
return callp->makeStmt();
} else {
// Assumption was that the design doesn't use #0 delays.
// Die at run-time if it does.
AstCStmt* const stmtp = new AstCStmt{flp};
const FileLine* const locp = netlistp->topModulep()->fileline();
const std::string& file = VIdProtect::protect(locp->filename());
const std::string& line = std::to_string(locp->lineno());
stmtp->add("VL_FATAL_MT(\"" + V3OutFormatter::quoteNameControls(file) + "\", "
+ line
+ ", \"\", \"ZERODLY: Design Verilated with '--no-sched-zero-delay', "
+ "but #0 delay executed at runtime\");");
return stmtp;
}
}());
// Create the 'nba' region
createEvalRegion( //
netlistp, VEval::NBA, 0, trigKit,
// Use trigger
nbaKit.m_vscp, nullptr,
// Prep statements
nullptr,
// Work statements
[&]() {
AstNodeStmt* workp = nullptr;
// Latch the 'nba' trigger flags under the following region's trigger flags
if (!obsKit.empty()) {
workp = trigKit.newOrIntoCall(obsKit.m_vscp, nbaKit.m_vscp);
} else if (!reactKit.empty()) {
workp = trigKit.newOrIntoCall(reactKit.m_vscp, nbaKit.m_vscp);
}
// Invoke the 'nba' function
2025-10-27 15:16:28 +00:00
workp = AstNode::addNext(workp, util::callVoidFunc(nbaKit.m_funcp));
// Clear the 'nba' triggers
workp = AstNode::addNext(workp, trigKit.newClearCall(nbaKit.m_vscp));
//
return workp;
2023-12-10 13:32:24 +00:00
}(),
// Extra work (not conditional on having had a fired trigger)
[&](AstVarScope* continuep) -> AstNodeStmt* {
// Check if any dynamic NBAs are pending, if there are any in the design
if (!netlistp->nbaEventp()) return nullptr;
AstVarScope* const nbaEventp = netlistp->nbaEventp();
AstVarScope* const nbaEventTriggerp = netlistp->nbaEventTriggerp();
UASSERT(nbaEventTriggerp, "NBA event trigger var should exist");
netlistp->nbaEventp(nullptr);
netlistp->nbaEventTriggerp(nullptr);
2026-02-11 19:35:59 +01:00
// If a dynamic NBA is pending, clear the pending flag and fire the ready event
2023-12-10 13:32:24 +00:00
AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, nbaEventTriggerp, VAccess::READ}};
2025-10-27 15:16:28 +00:00
ifp->addThensp(util::setVar(continuep, 1));
ifp->addThensp(util::setVar(nbaEventTriggerp, 0));
AstCMethodHard* const firep = new AstCMethodHard{
flp, new AstVarRef{flp, nbaEventp, VAccess::WRITE}, VCMethod::EVENT_FIRE};
2023-12-10 13:32:24 +00:00
firep->dtypeSetVoid();
ifp->addThensp(firep->makeStmt());
return ifp;
});
// Create the 'obs' region
createEvalRegion( //
netlistp, VEval::OBS, 0, trigKit,
// Use trigger
obsKit.m_vscp, nullptr,
// Prep statements
nullptr,
// Work statements
[&]() -> AstNodeStmt* {
if (obsKit.empty()) return nullptr;
AstNodeStmt* workp = nullptr;
// Latch the Observed trigger flags under the Reactive trigger flags
if (!reactKit.empty()) {
workp = trigKit.newOrIntoCall(reactKit.m_vscp, obsKit.m_vscp);
}
// Invoke the 'obs' function
workp = AstNode::addNext(workp, util::callVoidFunc(obsKit.m_funcp));
// Clear the 'obs' triggers
workp = AstNode::addNext(workp, trigKit.newClearCall(obsKit.m_vscp));
//
return workp;
}());
// Create the 'react' region
createEvalRegion( //
netlistp, VEval::REACT, 0, trigKit,
// Use trigger
reactKit.m_vscp, nullptr,
// Prep statements
nullptr,
// Work statements
[&]() -> AstNodeStmt* {
if (reactKit.empty()) return nullptr;
// Invoke the 'react' function
AstNodeStmt* workp = util::callVoidFunc(reactKit.m_funcp);
// Clear the 'react' triggers
workp = AstNode::addNext(workp, trigKit.newClearCall(reactKit.m_vscp));
return workp;
}());
2022-05-15 16:03:32 +01:00
}
} // namespace
//============================================================================
// Helper that builds virtual interface trigger sentrees
VirtIfaceTriggers::VscpSensMap VirtIfaceTriggers::makeVscpToSensMap(const TriggerKit& trigKit,
uint32_t firstIndex,
AstVarScope* trigVscp) const {
VscpSensMap map;
uint32_t index = firstIndex;
for (const auto& entry : m_triggers) {
map.emplace(entry.m_vscp, trigKit.newExtraTriggerSenTree(trigVscp, index));
++index;
}
return map;
}
std::unordered_map<const AstSenTree*, AstSenTree*>
cloneMapWithNewTriggerReferences(const std::unordered_map<const AstSenTree*, AstSenTree*>& map,
AstVarScope* vscp) {
AstTopScope* const topScopep = v3Global.rootp()->topScopep();
// Label global SenTrees by the order they are in the Ast
const VNUser1InUse user1InUse;
int n = 0;
for (AstNode* nodep = topScopep->senTreesp(); nodep; nodep = nodep->nextp()) nodep->user1(++n);
// Sort map by key order for determinism
using Pair = std::pair<const AstSenTree*, AstSenTree*>;
std::vector<Pair> pairs{map.begin(), map.end()};
std::sort(pairs.begin(), pairs.end(), [](const Pair& a, const Pair& b) { //
return a.first->user1() < b.first->user1();
});
// Replace references in each mapped value with a reference to the given vscp
for (Pair& pair : pairs) {
pair.second = pair.second->cloneTree(false);
pair.second->foreach([&](AstVarRef* refp) {
UASSERT_OBJ(refp->access() == VAccess::READ, refp, "Should be read ref");
refp->replaceWith(new AstVarRef{refp->fileline(), vscp, VAccess::READ});
VL_DO_DANGLING(refp->deleteTree(), refp);
});
topScopep->addSenTreesp(pair.second);
}
// Convert back to map
return std::unordered_map<const AstSenTree*, AstSenTree*>{pairs.begin(), pairs.end()};
}
//============================================================================
2022-05-15 16:03:32 +01:00
// Top level entry-point to scheduling
void schedule(AstNetlist* netlistp) {
const auto addSizeStat = [](const string& name, const LogicByScope& lbs) {
2022-05-15 16:03:32 +01:00
uint64_t size = 0;
lbs.foreachLogic([&](AstNode* nodep) { size += nodep->nodeCount(); });
V3Stats::addStat("Scheduling, " + name, size);
};
// Step 1: Create every entry point called from the run-time eval loop
for (int i = 0; i < VEval::_ENUM_END; ++i) {
const VEval eval{i};
AstCFunc* const funcp = util::makeTopFunction(netlistp, eval.funcName(), eval.slow());
netlistp->evalFuncp(eval, funcp);
// Only the iterated functions report whether they did any work
if (eval.isIterated()) funcp->rtnType("bool");
// Only a region with a trigger vector has anything to dump
if (eval.hasTriggers()) {
AstCFunc* const dumpp
= util::makeTopFunction(netlistp, eval.dumpTriggersFuncName(), true);
netlistp->dumpTriggersFuncp(eval, dumpp);
}
}
// Step 2: Prepare external domains for timing and virtual interfaces
// Create extra triggers for virtual interfaces
const auto& virtIfaceTriggers = makeVirtIfaceTriggers(netlistp);
// Resolve what covergroup reference formal arguments are bound to, which is visible here
// but not from V3Order, where the reads through them must be modeled
const CovergroupRefBindings& cgRefBindings = makeCovergroupRefBindings(netlistp);
// Prepare timing-related logic and external domains
TimingKit timingKit = prepareTiming(netlistp);
2022-08-22 14:26:32 +02:00
// Step 3: Gather and classify all logic in the design
2022-05-15 16:03:32 +01:00
LogicClasses logicClasses = gatherLogicClasses(netlistp);
if (v3Global.opt.stats()) {
V3Stats::statsStage("sched-gather");
addSizeStat("size of class: static", logicClasses.m_static);
addSizeStat("size of class: initial", logicClasses.m_initial);
addSizeStat("size of class: final", logicClasses.m_final);
}
// Step 4: Schedule static, initial and final logic classes in source order
2025-03-18 13:34:04 +00:00
AstCFunc* const staticp = createStatic(netlistp, logicClasses);
2022-05-15 16:03:32 +01:00
if (v3Global.opt.stats()) V3Stats::statsStage("sched-static");
2025-03-18 13:34:04 +00:00
createInitial(netlistp, logicClasses);
2022-05-15 16:03:32 +01:00
if (v3Global.opt.stats()) V3Stats::statsStage("sched-initial");
createFinal(netlistp, logicClasses);
if (v3Global.opt.stats()) V3Stats::statsStage("sched-final");
// Step 5: Break combinational cycles by introducing hybrid logic
2022-05-15 16:03:32 +01:00
// Note: breakCycles also removes corresponding logic from logicClasses.m_comb;
logicClasses.m_hybrid = breakCycles(netlistp, logicClasses.m_comb);
if (v3Global.opt.stats()) {
addSizeStat("size of class: clocked", logicClasses.m_clocked);
addSizeStat("size of class: combinational", logicClasses.m_comb);
addSizeStat("size of class: hybrid", logicClasses.m_hybrid);
V3Stats::statsStage("sched-break-cycles");
}
// We pass around a single SenExprBuilder instance, as we only need one set of 'prev' variables
// for edge/change detection in sensitivity expressions, which this keeps track of.
AstTopScope* const topScopep = netlistp->topScopep();
AstScope* const scopeTopp = topScopep->scopep();
SenExprBuilder senExprBuilder{scopeTopp};
2022-05-15 16:03:32 +01:00
// Step 6: Create 'settle' region that restores the combinational invariant
createSettle(netlistp, staticp, senExprBuilder, logicClasses, cgRefBindings);
2022-05-15 16:03:32 +01:00
if (v3Global.opt.stats()) V3Stats::statsStage("sched-settle");
// Step 7: Partition the clocked and combinational (including hybrid) logic into pre/act/nba.
2022-05-15 16:03:32 +01:00
// All clocks (signals referenced in an AstSenTree) generated via a blocking assignment
// (including combinationally generated signals) are computed within the act region.
LogicRegions logicRegions
= partition(logicClasses.m_clocked, logicClasses.m_comb, logicClasses.m_hybrid);
logicRegions.m_obs = logicClasses.m_observed;
logicRegions.m_react = logicClasses.m_reactive;
2022-05-15 16:03:32 +01:00
if (v3Global.opt.stats()) {
addSizeStat("size of region: Active Pre", logicRegions.m_pre);
addSizeStat("size of region: Active", logicRegions.m_act);
addSizeStat("size of region: NBA", logicRegions.m_nba);
addSizeStat("size of region: Observed", logicRegions.m_obs);
addSizeStat("size of region: Reactive", logicRegions.m_react);
2022-05-15 16:03:32 +01:00
V3Stats::statsStage("sched-partition");
}
// Step 8: Replicate combinational logic
2022-05-15 16:03:32 +01:00
LogicReplicas logicReplicas = replicateLogic(logicRegions);
if (v3Global.opt.stats()) {
addSizeStat("size of replicated logic: Input", logicReplicas.m_ico);
addSizeStat("size of replicated logic: Active", logicReplicas.m_act);
addSizeStat("size of replicated logic: NBA", logicReplicas.m_nba);
addSizeStat("size of replicated logic: Observed", logicReplicas.m_obs);
addSizeStat("size of replicated logic: Reactive", logicReplicas.m_react);
2022-05-15 16:03:32 +01:00
V3Stats::statsStage("sched-replicate");
}
// Step 9: Create the input combinational logic
createIcoRegion(netlistp, staticp, senExprBuilder, logicReplicas.m_ico, virtIfaceTriggers,
cgRefBindings);
2022-05-15 16:03:32 +01:00
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-ico");
// Step 10: Create the triggers
AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp();
2025-10-26 09:25:34 +00:00
netlistp->dpiExportTriggerp(nullptr); // Finished with this here
// We may have an extra trigger for variable updated in DPI exports
TriggerKit::ExtraTriggers extraTriggers;
const uint32_t dpiExportTriggerIndex = dpiExportTriggerVscp
? extraTriggers.allocate("DPI export trigger")
: std::numeric_limits<uint32_t>::max();
const uint32_t firstVifTriggerIndex = extraTriggers.size();
for (const auto& entry : virtIfaceTriggers.m_triggers) {
extraTriggers.allocate("virtual interface member: " + entry.m_ifacep->name() + "."
+ entry.m_memberp->name());
}
const auto& preTreeps = getSenTreesUsedBy({&logicRegions.m_pre});
const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_act, //
2022-08-22 14:26:32 +02:00
&logicRegions.m_nba, //
&logicRegions.m_obs, //
&logicRegions.m_react, //
2022-08-22 14:26:32 +02:00
&timingKit.m_lbs});
2026-02-11 19:35:59 +01:00
const TriggerKit trigKit
= TriggerKit::create(netlistp, staticp, senExprBuilder, preTreeps, senTreeps, "act",
extraTriggers, false, v3Global.usesTiming());
// Add post updates from the timing kit
2026-02-11 19:35:59 +01:00
if (timingKit.m_postUpdates) trigKit.compBasep()->addStmtsp(timingKit.m_postUpdates);
if (dpiExportTriggerVscp) {
trigKit.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
}
addVirtIfaceTriggerAssignments(netlistp, staticp, virtIfaceTriggers, firstVifTriggerIndex,
trigKit);
2022-05-15 16:03:32 +01:00
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-triggers");
// Note: Experiments so far show that running the Act (or Ico) regions on
// multiple threads is always a net loss, so only use multi-threading for
// NBA for now. This can be revised if evidence is available that it would
// be beneficial
// Step 11: Create the 'act' region evaluation function
2022-05-15 16:03:32 +01:00
// Remap sensitivities of the input logic to the triggers
remapSensitivities(logicRegions.m_pre, trigKit.mapPre());
remapSensitivities(logicRegions.m_act, trigKit.mapVec());
remapSensitivities(logicReplicas.m_act, trigKit.mapVec());
remapSensitivities(timingKit.m_lbs, trigKit.mapVec());
const std::map<const AstVarScope*, std::vector<AstSenTree*>> actTimingDomains
= timingKit.remapDomains(trigKit.mapVec());
2022-05-15 16:03:32 +01:00
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
V3Order::TrigToSenMap trigToSenAct;
invertAndMergeSenTreeMap(trigToSenAct, trigKit.mapPre());
invertAndMergeSenTreeMap(trigToSenAct, trigKit.mapVec());
2022-05-15 16:03:32 +01:00
// The DPI Export trigger AstSenTree
2022-07-21 17:34:12 +01:00
AstSenTree* const dpiExportTriggeredAct
= dpiExportTriggerVscp
? trigKit.newExtraTriggerSenTree(trigKit.vscp(), dpiExportTriggerIndex)
: nullptr;
const auto& vifVscpToSensAct
= virtIfaceTriggers.makeVscpToSensMap(trigKit, firstVifTriggerIndex, trigKit.vscp());
2022-05-15 16:03:32 +01:00
AstCFunc* const actFuncp = V3Order::order(
netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct,
cgRefBindings, "act", false, false,
[&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
2022-08-22 14:26:32 +02:00
auto it = actTimingDomains.find(vscp);
if (it != actTimingDomains.end()) out = it->second;
2022-07-21 17:34:12 +01:00
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredAct);
if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) {
const auto& ifaceTriggered
= findTriggeredIface(vscp, vifVscpToSensAct, virtIfaceTriggers);
out.insert(out.end(), ifaceTriggered.begin(), ifaceTriggered.end());
}
});
2025-10-27 15:16:28 +00:00
util::splitCheck(actFuncp);
2022-05-15 16:03:32 +01:00
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act");
const EvalKit actKit{trigKit.vscp(), actFuncp};
2022-12-23 12:34:49 +00:00
// Orders a region's logic and creates the region eval function
const auto order = [&](const std::string& name,
const std::vector<V3Sched::LogicByScope*>& logic) -> EvalKit {
UINFO(2, "Scheduling " << name << " #logic = " << logic.size());
AstVarScope* const trigVscp = trigKit.newTrigVec(name);
const auto trigMap = cloneMapWithNewTriggerReferences(trigKit.mapVec(), trigVscp);
2022-12-23 12:34:49 +00:00
// Remap sensitivities of the input logic to the triggers
for (LogicByScope* lbs : logic) remapSensitivities(*lbs, trigMap);
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
V3Order::TrigToSenMap trigToSen;
2022-12-23 12:34:49 +00:00
invertAndMergeSenTreeMap(trigToSen, trigMap);
AstSenTree* const dpiExportTriggered
= dpiExportTriggerVscp
? trigKit.newExtraTriggerSenTree(trigVscp, dpiExportTriggerIndex)
: nullptr;
const auto& vifVscpToSens
= virtIfaceTriggers.makeVscpToSensMap(trigKit, firstVifTriggerIndex, trigVscp);
2022-12-23 12:34:49 +00:00
const auto& timingDomains = timingKit.remapDomains(trigMap);
AstCFunc* const funcp = V3Order::order(
netlistp, logic, trigToSen, cgRefBindings, name,
name == "nba" && v3Global.opt.mtasks(), false,
2022-12-23 12:34:49 +00:00
[&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
auto it = timingDomains.find(vscp);
if (it != timingDomains.end()) out = it->second;
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered);
if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) {
const auto& ifaceTriggered
= findTriggeredIface(vscp, vifVscpToSens, virtIfaceTriggers);
out.insert(out.end(), ifaceTriggered.begin(), ifaceTriggered.end());
}
2022-12-23 12:34:49 +00:00
});
2022-05-15 16:03:32 +01:00
return {trigVscp, funcp};
2022-12-23 12:34:49 +00:00
};
// Step 12: Create the 'nba' region evaluation function
const EvalKit nbaKit = order("nba", {&logicRegions.m_nba, &logicReplicas.m_nba});
2025-10-27 15:16:28 +00:00
util::splitCheck(nbaKit.m_funcp);
2022-05-15 16:03:32 +01:00
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba");
2022-12-23 12:34:49 +00:00
// Orders a region's logic and creates the region eval function (only if there is any logic in
// the region)
const auto orderIfNonEmpty
= [&](const std::string& name, const std::vector<LogicByScope*>& logic) -> EvalKit {
if (logic[0]->empty())
return {}; // if region is empty, replica is supposed to be empty as well
const auto& kit = order(name, logic);
2022-12-23 12:34:49 +00:00
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-" + name);
return kit;
};
// Step 13: Create the 'obs' region evaluation function
const EvalKit obsKit = orderIfNonEmpty("obs", {&logicRegions.m_obs, &logicReplicas.m_obs});
2022-12-23 12:34:49 +00:00
// Step 14: Create the 'react' region evaluation function
const EvalKit reactKit
= orderIfNonEmpty("react", {&logicRegions.m_react, &logicReplicas.m_react});
2022-12-23 12:34:49 +00:00
// Step 15: Create the 'postponed' region evaluation function
createPostponed(netlistp, logicClasses);
// Step 16: Populate the eval entry point function of each region of a time step
createEval(netlistp, trigKit, actKit, nbaKit, obsKit, reactKit, timingKit);
2022-08-22 14:26:32 +02:00
// Step 17: Add neccessary evaluation before awaits
2026-02-11 19:35:59 +01:00
if (AstCCall* const readyp = timingKit.createReady(netlistp)) {
staticp->addStmtsp(readyp->makeStmt());
beforeTrigVisitor(netlistp, senExprBuilder, trigKit);
} else {
// beforeTrigVisitor clears Sentree pointers in AstCAwaits (as these sentrees will get
// deleted later) if there was no need to call it, SenTrees have to be cleaned manually
netlistp->foreach([](AstCAwait* const cAwaitp) { cAwaitp->clearSentreep(); });
}
if (AstVarScope* const trigAccp = trigKit.vscAccp()) {
// Copy trigger vector to accumulator at the end of static initialziation so,
// triggers fired during initialization persist to the first resume.
const AstUnpackArrayDType* const trigAccDTypep
= VN_AS(trigAccp->dtypep(), UnpackArrayDType);
UASSERT_OBJ(
trigAccDTypep->right() == 0, trigAccp,
"Expected that trigger vector and accumulator start elements enumeration from 0");
UASSERT_OBJ(trigAccDTypep->left() >= 0, trigAccp,
"Expected that trigger vector and accumulator has no negative indexes");
FileLine* const flp = trigAccp->fileline();
AstVarScope* const vscp = netlistp->topScopep()->scopep()->createTemp("__Vi", 32);
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(
new AstAssign{flp,
new AstArraySel{flp, new AstVarRef{flp, trigAccp, VAccess::WRITE},
new AstVarRef{flp, vscp, VAccess::READ}},
new AstArraySel{flp, new AstVarRef{flp, actKit.m_vscp, VAccess::READ},
new AstVarRef{flp, vscp, VAccess::READ}}});
loopp->addStmtsp(util::incrementVar(vscp));
loopp->addStmtsp(new AstLoopTest{
flp, loopp,
new AstLte{flp, new AstVarRef{flp, vscp, VAccess::READ},
new AstConst{flp, AstConst::WidthedValue{}, 32,
static_cast<uint32_t>(trigAccDTypep->left())}}});
staticp->addStmtsp(loopp);
}
// Step 18: Clean up
2026-01-23 12:53:40 -05:00
netlistp->clearStlFirstIterationp();
if (v3Global.opt.stats()) {
// A sample() call resolved to the union over its covergroup's constructions reads more
// than it can, which orders it against more logic than necessary
V3Stats::addStat("Scheduling, covergroup ref sample calls, per instance",
cgRefBindings.numExactCalls());
V3Stats::addStat("Scheduling, covergroup ref sample calls, per type",
cgRefBindings.numUnionCalls());
}
2025-10-26 09:25:34 +00:00
// Haven't split static initializer yet
2025-10-27 15:16:28 +00:00
util::splitCheck(staticp);
2022-05-15 16:03:32 +01:00
// Record eval stats
netlistp->addEvalStats("sched");
2025-10-26 09:25:34 +00:00
// Dump
V3Global::dumpCheckGlobalTree("sched", 0, dumpTreeEitherLevel() >= 3);
2022-05-15 16:03:32 +01:00
}
} // namespace V3Sched