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
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2026-01-26 20:24:34 -05:00
|
|
|
// 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
|
2026-08-27 13:53:02 +02:00
|
|
|
// - 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
|
2026-08-27 13:53:02 +02:00
|
|
|
//
|
|
|
|
|
// 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
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
|
2023-10-18 06:37:46 -04:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
|
|
2022-08-05 12:15:59 +01:00
|
|
|
#include "V3Sched.h"
|
|
|
|
|
|
2025-10-27 11:41:30 +01:00
|
|
|
#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
|
|
|
|
|
|
2022-07-30 12:49:30 -04:00
|
|
|
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;
|
2025-08-17 19:14:34 -04:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-02 23:04:55 -04:00
|
|
|
void remapSensitivities(const LogicByScope& lbs,
|
2025-08-21 09:43:37 +01:00
|
|
|
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;
|
2025-08-17 19:14:34 -04:00
|
|
|
AstSenTree* const senTreep = activep->sentreep();
|
2022-05-15 16:03:32 +01:00
|
|
|
if (senTreep->hasCombo()) continue;
|
2025-08-17 19:14:34 -04:00
|
|
|
activep->sentreep(senTreeMap.at(senTreep));
|
2022-05-15 16:03:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-30 12:49:30 -04:00
|
|
|
void invertAndMergeSenTreeMap(
|
2024-03-09 12:43:09 +00:00
|
|
|
V3Order::TrigToSenMap& result,
|
2022-07-30 12:49:30 -04:00
|
|
|
const std::unordered_map<const AstSenTree*, AstSenTree*>& senTreeMap) {
|
2024-09-25 10:35:50 +01:00
|
|
|
for (const auto& pair : senTreeMap) result.emplace(pair.second, pair.first);
|
2022-05-15 16:03:32 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-01 22:42:42 +02: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;
|
2026-02-06 23:20:10 +01:00
|
|
|
if (vscp->varp()->isVirtIface()) {
|
2026-04-01 22:42:42 +02:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-06 23:20:10 +01:00
|
|
|
} else {
|
2026-04-01 22:42:42 +02:00
|
|
|
// 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);
|
2025-07-18 15:07:31 +02:00
|
|
|
}
|
2026-04-01 22:42:42 +02:00
|
|
|
// May be empty if sensIfacep() is set but no VIF write targets this member
|
2025-08-23 16:45:13 +02:00
|
|
|
return result;
|
2025-07-18 15:07:31 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-28 08:14:38 +01:00
|
|
|
//============================================================================
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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, //
|
2026-08-27 13:53:02 +02:00
|
|
|
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,
|
2025-10-31 19:29:11 +01:00
|
|
|
const TriggerKit& trigKit, // The trigger kit
|
2026-02-16 03:55:55 +00:00
|
|
|
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; } //
|
2023-10-28 08:14:38 +01:00
|
|
|
) {
|
2026-08-27 13:53:02 +02:00
|
|
|
UASSERT(!trigp || !condp, "Cannot use both 'trigp' and 'condp' in 'createEvalRegion'");
|
|
|
|
|
UASSERT(!eval.firstIteration() || trigp,
|
|
|
|
|
"Region without triggers cannot need a first iteration flag");
|
2026-02-16 03:55:55 +00:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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.
|
2025-10-31 19:29:11 +01:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
const std::string tag = eval.tag();
|
2023-10-28 08:14:38 +01:00
|
|
|
const std::string varPrefix = "__V" + tag;
|
|
|
|
|
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
|
|
|
|
FileLine* const flp = netlistp->fileline();
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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}});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-28 08:14:38 +01:00
|
|
|
{
|
|
|
|
|
// Add the preparatory statements
|
2026-08-27 13:53:02 +02:00
|
|
|
funcp->addStmtsp(phasePrepp);
|
2023-10-28 08:14:38 +01:00
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
// The execute flag
|
|
|
|
|
AstVarScope* const executeFlagp = scopeTopp->createTemp(varPrefix + "Execute", 1);
|
|
|
|
|
executeFlagp->varp()->noReset(true);
|
2023-10-28 08:14:38 +01:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// If there is work in this region, execute it if any triggers fired
|
2025-10-31 19:29:11 +01:00
|
|
|
if (phaseWorkp) {
|
|
|
|
|
AstNodeExpr* const lhsp = new AstVarRef{flp, executeFlagp, VAccess::WRITE};
|
2026-02-16 03:55:55 +00:00
|
|
|
// 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);
|
2026-08-27 13:53:02 +02:00
|
|
|
funcp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
|
2025-10-31 19:29:11 +01:00
|
|
|
|
|
|
|
|
// Add the work
|
|
|
|
|
AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, executeFlagp, VAccess::READ}};
|
|
|
|
|
ifp->addThensp(phaseWorkp);
|
2026-08-27 13:53:02 +02:00
|
|
|
funcp->addStmtsp(ifp);
|
2025-10-31 19:29:11 +01:00
|
|
|
}
|
2023-10-28 08:14:38 +01:00
|
|
|
|
2023-12-10 13:32:24 +00:00
|
|
|
// Construct the extra statements
|
2025-10-31 19:29:11 +01:00
|
|
|
AstNodeStmt* const extraWorkp = phaseExtra(executeFlagp);
|
2026-08-27 13:53:02 +02:00
|
|
|
if (extraWorkp) funcp->addStmtsp(extraWorkp);
|
2023-12-10 13:32:24 +00:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// The function returns true iff it did run work
|
2025-10-31 19:29:11 +01:00
|
|
|
AstNodeExpr* const retp
|
|
|
|
|
= phaseWorkp || extraWorkp
|
|
|
|
|
? static_cast<AstNodeExpr*>(new AstVarRef{flp, executeFlagp, VAccess::READ})
|
|
|
|
|
: static_cast<AstNodeExpr*>(new AstConst{flp, AstConst::BitFalse{}});
|
2026-08-27 13:53:02 +02:00
|
|
|
funcp->addStmtsp(new AstCReturn{flp, retp});
|
2025-11-25 05:53:59 +00:00
|
|
|
}
|
2023-10-28 08:14:38 +01:00
|
|
|
}
|
|
|
|
|
|
2022-05-15 16:03:32 +01:00
|
|
|
//============================================================================
|
|
|
|
|
// Collect and classify all logic in the design
|
|
|
|
|
|
|
|
|
|
LogicClasses gatherLogicClasses(AstNetlist* netlistp) {
|
|
|
|
|
LogicClasses result;
|
|
|
|
|
|
2022-10-20 20:48:44 +08:00
|
|
|
netlistp->foreach([&](AstScope* scopep) {
|
|
|
|
|
scopep->foreach([&](AstActive* activep) {
|
2025-08-17 19:14:34 -04:00
|
|
|
AstSenTree* const senTreep = activep->sentreep();
|
2024-03-16 10:35:56 +00:00
|
|
|
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");
|
2022-10-13 21:04:43 +02:00
|
|
|
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()) {
|
2022-12-11 14:44:18 -05:00
|
|
|
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{
|
2025-10-19 10:44:33 +02:00
|
|
|
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);
|
2023-07-14 17:12:02 +02:00
|
|
|
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) {
|
2026-08-27 13:53:02 +02:00
|
|
|
AstCFunc* const funcp = netlistp->evalFuncp(VEval::STATIC);
|
2026-03-27 17:53:45 +01:00
|
|
|
|
|
|
|
|
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) {
|
2026-08-27 13:53:02 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
void createPostponed(AstNetlist* netlistp, const LogicClasses& logicClasses) {
|
|
|
|
|
AstCFunc* const funcp = netlistp->evalFuncp(VEval::POSTPONED);
|
2022-10-13 21:04:43 +02:00
|
|
|
orderSequentially(funcp, logicClasses.m_postponed);
|
2025-10-27 15:16:28 +00:00
|
|
|
util::splitCheck(funcp);
|
2022-10-13 21:04:43 +02:00
|
|
|
}
|
|
|
|
|
|
2022-05-15 16:03:32 +01:00
|
|
|
void createFinal(AstNetlist* netlistp, const LogicClasses& logicClasses) {
|
2026-08-27 13:53:02 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2023-12-05 04:11:07 +01:00
|
|
|
//============================================================================
|
2026-04-01 22:42:42 +02: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;
|
2025-10-29 22:27:15 +01:00
|
|
|
}
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Order the combinational logic to create the 'stl' region
|
2022-10-22 14:05:39 +00:00
|
|
|
void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilder& senExprBulider,
|
2026-09-05 11:08:03 -07:00
|
|
|
LogicClasses& logicClasses, const CovergroupRefBindings& cgRefBindings) {
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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
|
2025-10-31 19:29:11 +01:00
|
|
|
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});
|
2025-11-01 16:43:20 +01:00
|
|
|
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)
|
2025-11-01 16:43:20 +01:00
|
|
|
remapSensitivities(hybrid, trigKit.mapVec());
|
2022-05-15 16:03:32 +01:00
|
|
|
|
|
|
|
|
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
|
2024-03-09 12:43:09 +00:00
|
|
|
V3Order::TrigToSenMap trigToSen;
|
2025-11-01 16:43:20 +01:00
|
|
|
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
|
2025-11-01 16:43:20 +01:00
|
|
|
= 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(
|
2026-09-05 11:08:03 -07:00
|
|
|
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
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Create the region evaluation function
|
|
|
|
|
createEvalRegion( //
|
|
|
|
|
netlistp, VEval::STL, firstIterationTrigger, trigKit,
|
2026-02-16 03:55:55 +00:00
|
|
|
// Use trigger
|
|
|
|
|
trigKit.vscp(), nullptr,
|
|
|
|
|
// Explicit condition
|
2023-10-28 08:14:38 +01:00
|
|
|
// 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;
|
|
|
|
|
}(),
|
2023-10-28 08:14:38 +01:00
|
|
|
// 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
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
void createIcoRegion(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
|
|
|
|
SenExprBuilder& senExprBuilder, LogicByScope& logic,
|
2026-09-05 11:08:03 -07:00
|
|
|
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) {
|
2022-10-20 20:48:44 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-14 12:35:44 +01:00
|
|
|
// We have some extra trigger denoting external conditions
|
|
|
|
|
AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp();
|
|
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
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();
|
2026-04-01 22:42:42 +02:00
|
|
|
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());
|
2025-10-29 22:27:15 +01:00
|
|
|
}
|
2022-05-15 16:03:32 +01:00
|
|
|
|
2026-06-15 05:42:00 +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
|
2026-06-15 05:42:00 +01:00
|
|
|
std::vector<const AstSenTree*> senTreeps = getSenTreesUsedBy({&logic});
|
|
|
|
|
senTreeps.insert(senTreeps.end(), icoChangeSenTreeps.begin(), icoChangeSenTreeps.end());
|
2025-11-01 16:43:20 +01:00
|
|
|
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
|
|
|
|
2022-07-14 12:35:44 +01:00
|
|
|
if (dpiExportTriggerVscp) {
|
2025-10-31 19:29:11 +01:00
|
|
|
trigKit.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
|
2022-07-14 12:35:44 +01:00
|
|
|
}
|
2026-04-01 22:42:42 +02:00
|
|
|
addVirtIfaceTriggerAssignments(netlistp, initFuncp, virtIfaceTriggers, firstVifTriggerIndex,
|
|
|
|
|
trigKit);
|
2022-07-14 12:35:44 +01:00
|
|
|
|
2022-05-15 16:03:32 +01:00
|
|
|
// Remap sensitivities
|
2025-11-01 16:43:20 +01:00
|
|
|
remapSensitivities(logic, trigKit.mapVec());
|
2026-06-15 05:42:00 +01:00
|
|
|
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
|
2024-03-09 12:43:09 +00:00
|
|
|
V3Order::TrigToSenMap trigToSen;
|
2025-11-01 16:43:20 +01:00
|
|
|
invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec());
|
2022-05-15 16:03:32 +01:00
|
|
|
|
2026-08-27 13:53:02 +02: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
|
|
|
|
2022-07-14 12:35:44 +01:00
|
|
|
// The DPI Export trigger
|
|
|
|
|
AstSenTree* const dpiExportTriggered
|
2025-11-01 16:43:20 +01:00
|
|
|
= dpiExportTriggerVscp
|
|
|
|
|
? trigKit.newExtraTriggerSenTree(trigKit.vscp(), dpiExportTriggerIndex)
|
|
|
|
|
: nullptr;
|
2026-04-01 22:42:42 +02:00
|
|
|
const auto& vifVscpToSensIco
|
|
|
|
|
= virtIfaceTriggers.makeVscpToSensMap(trigKit, firstVifTriggerIndex, trigKit.vscp());
|
2022-07-14 12:35:44 +01:00
|
|
|
|
2022-05-15 16:03:32 +01:00
|
|
|
// Create and Order the body function
|
2025-08-23 16:45:13 +02:00
|
|
|
AstCFunc* const icoFuncp = V3Order::order(
|
2026-09-05 11:08:03 -07:00
|
|
|
netlistp, {&logic}, trigToSen, cgRefBindings, "ico", false, false,
|
2026-04-01 22:42:42 +02:00
|
|
|
[&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
|
2025-08-23 16:45:13 +02:00
|
|
|
AstVar* const varp = vscp->varp();
|
2026-06-15 05:42:00 +01:00
|
|
|
// 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);
|
2026-06-15 09:17:41 +01:00
|
|
|
} else if (varp->isPrimaryInish() || varp->isSigUserRWPublic() || varp->sampled()) {
|
2026-06-15 05:42:00 +01:00
|
|
|
out.push_back(firstIterTriggerp);
|
2025-08-23 16:45:13 +02:00
|
|
|
}
|
2026-06-15 05:42:00 +01:00
|
|
|
// Add other triggers
|
2025-08-23 16:45:13 +02:00
|
|
|
if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered);
|
2026-04-01 22:42:42 +02:00
|
|
|
if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) {
|
|
|
|
|
const auto& ifaceTriggered
|
|
|
|
|
= findTriggeredIface(vscp, vifVscpToSensIco, virtIfaceTriggers);
|
2025-08-23 16:45:13 +02:00
|
|
|
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
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Create the region evaluation function
|
|
|
|
|
createEvalRegion( //
|
|
|
|
|
netlistp, VEval::ICO, firstIterationTrigger, trigKit,
|
2026-02-16 03:55:55 +00:00
|
|
|
// Use trigger
|
|
|
|
|
trigKit.vscp(), nullptr,
|
2023-10-28 08:14:38 +01:00
|
|
|
// 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;
|
|
|
|
|
}(),
|
2023-10-28 08:14:38 +01:00
|
|
|
// 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
|
|
|
|
2026-06-15 05:42:00 +01:00
|
|
|
// Release temporary input change detect SenTrees
|
|
|
|
|
for (AstSenTree* const senTreep : icoChangeSenTreeps) senTreep->deleteTree();
|
|
|
|
|
icoChangeSenTreeps.clear();
|
2022-05-15 16:03:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//============================================================================
|
2025-10-31 19:29:11 +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
|
|
|
|
2025-10-31 19:29:11 +01: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
|
|
|
|
|
|
|
|
//============================================================================
|
2026-08-27 13:53:02 +02:00
|
|
|
// Create the evaluation function of each region of a time step
|
2022-05-15 16:03:32 +01:00
|
|
|
|
|
|
|
|
void createEval(AstNetlist* netlistp, //
|
2025-10-31 19:29:11 +01:00
|
|
|
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();
|
|
|
|
|
|
2026-02-16 03:55:55 +00:00
|
|
|
// 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);
|
2025-10-27 11:41:30 +01:00
|
|
|
AstCCall* const timingResumep = timingKit.createResume(netlistp);
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Create the 'act' region
|
|
|
|
|
createEvalRegion( //
|
|
|
|
|
netlistp, VEval::ACT, 0, trigKit,
|
2026-02-16 03:55:55 +00:00
|
|
|
// Use trigger
|
|
|
|
|
actKit.m_vscp, nullptr,
|
2023-10-28 08:14:38 +01:00
|
|
|
// Prep statements
|
|
|
|
|
[&]() {
|
2025-11-01 16:43:20 +01:00
|
|
|
// 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);
|
2025-10-31 19:29:11 +01:00
|
|
|
// Latch the 'act' triggers under the 'nba' triggers
|
|
|
|
|
stmtsp = AstNode::addNext(stmtsp, trigKit.newOrIntoCall(nbaKit.m_vscp, actKit.m_vscp));
|
2023-10-28 08:14:38 +01:00
|
|
|
//
|
|
|
|
|
return stmtsp;
|
|
|
|
|
}(),
|
|
|
|
|
// Work statements
|
|
|
|
|
[&]() {
|
2025-10-31 19:29:11 +01:00
|
|
|
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());
|
|
|
|
|
}
|
2023-10-21 02:01:45 +02:00
|
|
|
// Resume triggered timing schedulers
|
2026-02-11 19:35:59 +01:00
|
|
|
if (timingResumep) workp = AstNode::addNext(workp, timingResumep->makeStmt());
|
2023-10-28 08:14:38 +01:00
|
|
|
// Invoke the 'act' function
|
2025-10-31 19:29:11 +01:00
|
|
|
workp = AstNode::addNext(workp, util::callVoidFunc(actKit.m_funcp));
|
2023-10-28 08:14:38 +01:00
|
|
|
//
|
|
|
|
|
return workp;
|
|
|
|
|
}());
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
}());
|
2026-02-16 03:55:55 +00:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Create the 'nba' region
|
|
|
|
|
createEvalRegion( //
|
|
|
|
|
netlistp, VEval::NBA, 0, trigKit,
|
2026-02-16 03:55:55 +00:00
|
|
|
// Use trigger
|
|
|
|
|
nbaKit.m_vscp, nullptr,
|
2023-10-28 08:14:38 +01:00
|
|
|
// Prep statements
|
|
|
|
|
nullptr,
|
|
|
|
|
// Work statements
|
|
|
|
|
[&]() {
|
|
|
|
|
AstNodeStmt* workp = nullptr;
|
|
|
|
|
// Latch the 'nba' trigger flags under the following region's trigger flags
|
|
|
|
|
if (!obsKit.empty()) {
|
2025-10-31 19:29:11 +01:00
|
|
|
workp = trigKit.newOrIntoCall(obsKit.m_vscp, nbaKit.m_vscp);
|
2023-10-28 08:14:38 +01:00
|
|
|
} else if (!reactKit.empty()) {
|
2025-10-31 19:29:11 +01:00
|
|
|
workp = trigKit.newOrIntoCall(reactKit.m_vscp, nbaKit.m_vscp);
|
2023-10-21 02:01:45 +02:00
|
|
|
}
|
2023-10-28 08:14:38 +01:00
|
|
|
// Invoke the 'nba' function
|
2025-10-27 15:16:28 +00:00
|
|
|
workp = AstNode::addNext(workp, util::callVoidFunc(nbaKit.m_funcp));
|
2023-10-28 08:14:38 +01:00
|
|
|
// Clear the 'nba' triggers
|
2025-10-31 19:29:11 +01:00
|
|
|
workp = AstNode::addNext(workp, trigKit.newClearCall(nbaKit.m_vscp));
|
2023-10-28 08:14:38 +01:00
|
|
|
//
|
|
|
|
|
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));
|
2025-09-27 08:22:17 -04:00
|
|
|
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;
|
|
|
|
|
});
|
2023-10-21 02:01:45 +02:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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;
|
|
|
|
|
}());
|
2023-10-28 08:14:38 +01:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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
|
|
|
|
|
|
2023-12-05 04:11:07 +01:00
|
|
|
//============================================================================
|
|
|
|
|
// Helper that builds virtual interface trigger sentrees
|
|
|
|
|
|
2026-04-01 22:42:42 +02:00
|
|
|
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;
|
2025-07-12 03:04:51 +02:00
|
|
|
}
|
2025-10-29 22:27:15 +01:00
|
|
|
return map;
|
2025-07-12 03:04:51 +02:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 16:43:20 +01:00
|
|
|
std::unordered_map<const AstSenTree*, AstSenTree*>
|
|
|
|
|
cloneMapWithNewTriggerReferences(const std::unordered_map<const AstSenTree*, AstSenTree*>& map,
|
|
|
|
|
AstVarScope* vscp) {
|
|
|
|
|
AstTopScope* const topScopep = v3Global.rootp()->topScopep();
|
2026-03-01 12:44:59 +00:00
|
|
|
// 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();
|
|
|
|
|
});
|
2025-11-01 16:43:20 +01:00
|
|
|
// Replace references in each mapped value with a reference to the given vscp
|
2026-03-01 12:44:59 +00:00
|
|
|
for (Pair& pair : pairs) {
|
2025-11-01 16:43:20 +01:00
|
|
|
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);
|
|
|
|
|
}
|
2026-03-01 12:44:59 +00:00
|
|
|
// Convert back to map
|
|
|
|
|
return std::unordered_map<const AstSenTree*, AstSenTree*>{pairs.begin(), pairs.end()};
|
2025-11-01 16:43:20 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-27 11:41:30 +01:00
|
|
|
//============================================================================
|
2022-05-15 16:03:32 +01:00
|
|
|
// Top level entry-point to scheduling
|
|
|
|
|
|
|
|
|
|
void schedule(AstNetlist* netlistp) {
|
2022-07-30 12:49:30 -04:00
|
|
|
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);
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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
|
2023-12-05 04:11:07 +01:00
|
|
|
// Create extra triggers for virtual interfaces
|
|
|
|
|
const auto& virtIfaceTriggers = makeVirtIfaceTriggers(netlistp);
|
2026-09-05 11:08:03 -07:00
|
|
|
// 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);
|
2023-12-05 04:11:07 +01:00
|
|
|
// Prepare timing-related logic and external domains
|
2023-10-28 08:14:38 +01:00
|
|
|
TimingKit timingKit = prepareTiming(netlistp);
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2026-08-27 13:53:02 +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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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");
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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.
|
2022-10-22 14:05:39 +00:00
|
|
|
AstTopScope* const topScopep = netlistp->topScopep();
|
|
|
|
|
AstScope* const scopeTopp = topScopep->scopep();
|
|
|
|
|
SenExprBuilder senExprBuilder{scopeTopp};
|
2022-05-15 16:03:32 +01:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Step 6: Create 'settle' region that restores the combinational invariant
|
2026-09-05 11:08:03 -07:00
|
|
|
createSettle(netlistp, staticp, senExprBuilder, logicClasses, cgRefBindings);
|
2022-05-15 16:03:32 +01:00
|
|
|
if (v3Global.opt.stats()) V3Stats::statsStage("sched-settle");
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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);
|
2024-07-10 00:31:58 +02:00
|
|
|
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);
|
2024-07-10 00:31:58 +02:00
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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);
|
2024-07-10 00:31:58 +02:00
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Step 9: Create the input combinational logic
|
2026-09-05 11:08:03 -07:00
|
|
|
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");
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Step 10: Create the triggers
|
2022-07-14 12:35:44 +01:00
|
|
|
AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp();
|
2025-10-26 09:25:34 +00:00
|
|
|
netlistp->dpiExportTriggerp(nullptr); // Finished with this here
|
2022-07-14 12:35:44 +01:00
|
|
|
|
|
|
|
|
// We may have an extra trigger for variable updated in DPI exports
|
2025-10-31 19:29:11 +01:00
|
|
|
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();
|
2026-04-01 22:42:42 +02:00
|
|
|
for (const auto& entry : virtIfaceTriggers.m_triggers) {
|
|
|
|
|
extraTriggers.allocate("virtual interface member: " + entry.m_ifacep->name() + "."
|
|
|
|
|
+ entry.m_memberp->name());
|
2025-10-29 22:27:15 +01:00
|
|
|
}
|
2022-07-14 12:35:44 +01:00
|
|
|
|
2025-11-01 16:43:20 +01:00
|
|
|
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, //
|
2024-07-10 00:31:58 +02:00
|
|
|
&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());
|
2022-10-22 14:05:39 +00:00
|
|
|
|
|
|
|
|
// 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);
|
2022-07-14 12:35:44 +01:00
|
|
|
|
|
|
|
|
if (dpiExportTriggerVscp) {
|
2025-10-31 19:29:11 +01:00
|
|
|
trigKit.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
|
2022-07-14 12:35:44 +01:00
|
|
|
}
|
2026-04-01 22:42:42 +02:00
|
|
|
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
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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
|
2025-11-01 16:43:20 +01:00
|
|
|
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
|
2024-03-09 12:43:09 +00:00
|
|
|
V3Order::TrigToSenMap trigToSenAct;
|
2025-11-01 16:43:20 +01:00
|
|
|
invertAndMergeSenTreeMap(trigToSenAct, trigKit.mapPre());
|
|
|
|
|
invertAndMergeSenTreeMap(trigToSenAct, trigKit.mapVec());
|
2022-05-15 16:03:32 +01:00
|
|
|
|
2022-07-14 12:35:44 +01:00
|
|
|
// The DPI Export trigger AstSenTree
|
2022-07-21 17:34:12 +01:00
|
|
|
AstSenTree* const dpiExportTriggeredAct
|
2025-11-01 16:43:20 +01:00
|
|
|
= dpiExportTriggerVscp
|
|
|
|
|
? trigKit.newExtraTriggerSenTree(trigKit.vscp(), dpiExportTriggerIndex)
|
|
|
|
|
: nullptr;
|
2022-07-14 12:35:44 +01:00
|
|
|
|
2026-04-01 22:42:42 +02:00
|
|
|
const auto& vifVscpToSensAct
|
|
|
|
|
= virtIfaceTriggers.makeVscpToSensMap(trigKit, firstVifTriggerIndex, trigKit.vscp());
|
2023-12-05 04:11:07 +01:00
|
|
|
|
2022-05-15 16:03:32 +01:00
|
|
|
AstCFunc* const actFuncp = V3Order::order(
|
|
|
|
|
netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct,
|
2026-09-05 11:08:03 -07:00
|
|
|
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);
|
2026-04-01 22:42:42 +02:00
|
|
|
if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) {
|
|
|
|
|
const auto& ifaceTriggered
|
|
|
|
|
= findTriggeredIface(vscp, vifVscpToSensAct, virtIfaceTriggers);
|
2025-08-23 16:45:13 +02:00
|
|
|
out.insert(out.end(), ifaceTriggered.begin(), ifaceTriggered.end());
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
2022-07-14 12:35:44 +01:00
|
|
|
});
|
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");
|
|
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
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 {
|
2025-05-22 20:29:32 -04:00
|
|
|
UINFO(2, "Scheduling " << name << " #logic = " << logic.size());
|
2025-10-31 19:29:11 +01:00
|
|
|
AstVarScope* const trigVscp = trigKit.newTrigVec(name);
|
2025-11-01 16:43:20 +01:00
|
|
|
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
|
2024-03-09 12:43:09 +00:00
|
|
|
V3Order::TrigToSenMap trigToSen;
|
2022-12-23 12:34:49 +00:00
|
|
|
invertAndMergeSenTreeMap(trigToSen, trigMap);
|
|
|
|
|
|
|
|
|
|
AstSenTree* const dpiExportTriggered
|
2025-11-01 16:43:20 +01:00
|
|
|
= dpiExportTriggerVscp
|
|
|
|
|
? trigKit.newExtraTriggerSenTree(trigVscp, dpiExportTriggerIndex)
|
|
|
|
|
: nullptr;
|
2026-04-01 22:42:42 +02:00
|
|
|
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(
|
2026-09-05 11:08:03 -07:00
|
|
|
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);
|
2026-02-06 23:20:10 +01:00
|
|
|
if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) {
|
2026-04-01 22:42:42 +02:00
|
|
|
const auto& ifaceTriggered
|
|
|
|
|
= findTriggeredIface(vscp, vifVscpToSens, virtIfaceTriggers);
|
2025-08-23 16:45:13 +02:00
|
|
|
out.insert(out.end(), ifaceTriggered.begin(), ifaceTriggered.end());
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
2022-12-23 12:34:49 +00:00
|
|
|
});
|
2022-05-15 16:03:32 +01:00
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
return {trigVscp, funcp};
|
2022-12-23 12:34:49 +00:00
|
|
|
};
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Step 12: Create the 'nba' region evaluation function
|
2025-10-31 19:29:11 +01:00
|
|
|
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)
|
2024-07-10 00:31:58 +02:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Step 13: Create the 'obs' region evaluation function
|
2025-10-31 19:29:11 +01:00
|
|
|
const EvalKit obsKit = orderIfNonEmpty("obs", {&logicRegions.m_obs, &logicReplicas.m_obs});
|
2022-12-23 12:34:49 +00:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Step 14: Create the 'react' region evaluation function
|
2025-10-31 19:29:11 +01:00
|
|
|
const EvalKit reactKit
|
2024-07-10 00:31:58 +02:00
|
|
|
= orderIfNonEmpty("react", {&logicRegions.m_react, &logicReplicas.m_react});
|
2022-12-23 12:34:49 +00:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Step 15: Create the 'postponed' region evaluation function
|
|
|
|
|
createPostponed(netlistp, logicClasses);
|
2022-10-13 21:04:43 +02:00
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// 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
|
|
|
|
2026-08-27 13:53:02 +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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Step 18: Clean up
|
2026-01-23 12:53:40 -05:00
|
|
|
netlistp->clearStlFirstIterationp();
|
|
|
|
|
|
2026-09-05 11:08:03 -07:00
|
|
|
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
|
|
|
|
2026-08-27 13:53:02 +02:00
|
|
|
// Record eval stats
|
|
|
|
|
netlistp->addEvalStats("sched");
|
|
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
// Dump
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("sched", 0, dumpTreeEitherLevel() >= 3);
|
2022-05-15 16:03:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace V3Sched
|