mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 10:03:44 +02:00
The loops modelling the SystemVerilog scheduling regions are no longer generated. They now live in 'VerilatedEvalLoop' in the runtime library. The generated model holds one as a member, passing itself to it, and exposes each evaluation entry point to it as a pure virtual method on VerilatedModel. The model's 'eval' and 'eval_step' remain the top level entry points, and are backward compatible. V3Sched no longer emits '_eval' or '_eval_settle', etc.. Instead every evaluation entry point called from the runtime is enumerated by 'VEval', Scheduling creates all entry points, for all scheduling regions, even if they are empty, and the runtime eval loop calls everything unconditionally. If regions are empty, this is simply a call to an empty function. This will hurt performance on very small models, but should not be noticeable on anything meaningful, so it is likely best to keep to reduce complexity. A scheduling entry points evaluate a single iteration and returns whether it did any work, they are effectively the previous `_eval_phase_*` functions.
253 lines
10 KiB
C++
253 lines
10 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Utility functions used by 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
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
//
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
#include "V3Const.h"
|
|
#include "V3EmitCBase.h"
|
|
#include "V3EmitV.h"
|
|
#include "V3Order.h"
|
|
#include "V3Sched.h"
|
|
#include "V3SenExprBuilder.h"
|
|
#include "V3Stats.h"
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
namespace V3Sched {
|
|
namespace util {
|
|
|
|
AstCFunc* makeSubFunction(AstNetlist* netlistp, const string& name, bool slow) {
|
|
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
|
AstCFunc* const funcp = new AstCFunc{netlistp->fileline(), name, scopeTopp, ""};
|
|
funcp->dontCombine(true);
|
|
funcp->isStatic(false);
|
|
funcp->isLoose(true);
|
|
funcp->slow(slow);
|
|
funcp->isConst(false);
|
|
funcp->declPrivate(true);
|
|
scopeTopp->addBlocksp(funcp);
|
|
return funcp;
|
|
}
|
|
|
|
AstVarScope* newArgument(AstCFunc* funcp, AstNodeDType* dtypep, const string& name,
|
|
VDirection direction) {
|
|
FileLine* const flp = funcp->fileline();
|
|
AstScope* const scopep = funcp->scopep();
|
|
AstVar* const varp = new AstVar{flp, VVarType::BLOCKTEMP, name, dtypep};
|
|
varp->funcLocal(true);
|
|
varp->direction(direction);
|
|
funcp->addArgsp(varp);
|
|
AstVarScope* const vscp = new AstVarScope{flp, scopep, varp};
|
|
scopep->addVarsp(vscp);
|
|
return vscp;
|
|
}
|
|
|
|
AstCFunc* makeTopFunction(AstNetlist* netlistp, const string& name, bool slow) {
|
|
AstCFunc* const funcp = makeSubFunction(netlistp, name, slow);
|
|
funcp->entryPoint(true);
|
|
funcp->keepIfEmpty(true);
|
|
return funcp;
|
|
}
|
|
|
|
AstNodeStmt* setVar(AstVarScope* vscp, uint32_t val) {
|
|
FileLine* const flp = vscp->fileline();
|
|
AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::WRITE};
|
|
AstConst* const valp = new AstConst{flp, AstConst::DTyped{}, vscp->dtypep()};
|
|
valp->num().setLong(val);
|
|
return new AstAssign{flp, refp, valp};
|
|
}
|
|
|
|
AstNodeStmt* incrementVar(AstVarScope* vscp) {
|
|
FileLine* const flp = vscp->fileline();
|
|
AstVarRef* const wrefp = new AstVarRef{flp, vscp, VAccess::WRITE};
|
|
AstVarRef* const rrefp = new AstVarRef{flp, vscp, VAccess::READ};
|
|
AstConst* const onep = new AstConst{flp, AstConst::DTyped{}, vscp->dtypep()};
|
|
onep->num().setLong(1);
|
|
return new AstAssign{flp, wrefp, new AstAdd{flp, rrefp, onep}};
|
|
}
|
|
|
|
AstNodeStmt* callVoidFunc(AstCFunc* funcp) {
|
|
if (!funcp) return nullptr;
|
|
AstCCall* const callp = new AstCCall{funcp->fileline(), funcp};
|
|
callp->dtypeSetVoid();
|
|
return callp->makeStmt();
|
|
}
|
|
|
|
static AstCFunc* splitCheckCreateNewSubFunc(AstCFunc* ofuncp) {
|
|
static std::map<AstCFunc*, uint32_t> s_funcNums; // What split number to attach to a function
|
|
const uint32_t funcNum = s_funcNums[ofuncp]++;
|
|
const std::string name = ofuncp->name() + "__" + cvtToStr(funcNum);
|
|
AstScope* const scopep = ofuncp->scopep();
|
|
AstCFunc* const subFuncp = new AstCFunc{ofuncp->fileline(), name, scopep};
|
|
scopep->addBlocksp(subFuncp);
|
|
subFuncp->dontCombine(true);
|
|
subFuncp->isStatic(ofuncp->isStatic());
|
|
subFuncp->isLoose(true);
|
|
subFuncp->slow(ofuncp->slow());
|
|
subFuncp->declPrivate(ofuncp->declPrivate());
|
|
if (ofuncp->needProcess()) subFuncp->setNeedProcess();
|
|
for (AstVar* argp = ofuncp->argsp(); argp; argp = VN_AS(argp->nextp(), Var)) {
|
|
AstVar* const clonep = argp->cloneTree(false);
|
|
subFuncp->addArgsp(clonep);
|
|
AstVarScope* const vscp = new AstVarScope{clonep->fileline(), scopep, clonep};
|
|
scopep->addVarsp(vscp);
|
|
argp->user3p(vscp);
|
|
}
|
|
return subFuncp;
|
|
};
|
|
|
|
void splitCheckFinishSubFunc(AstCFunc* ofuncp, AstCFunc* subFuncp,
|
|
const std::unordered_map<const AstVar*, AstVarScope*>& argVscps) {
|
|
FileLine* const flp = subFuncp->fileline();
|
|
AstCCall* const callp = new AstCCall{subFuncp->fileline(), subFuncp};
|
|
callp->dtypeSetVoid();
|
|
// Pass arguments through to subfunction
|
|
for (AstVar* argp = ofuncp->argsp(); argp; argp = VN_AS(argp->nextp(), Var)) {
|
|
UASSERT_OBJ(argp->direction() == VDirection::CONSTREF, argp, "Unexpected direction");
|
|
callp->addArgsp(new AstVarRef{flp, argVscps.at(argp), VAccess::READ});
|
|
}
|
|
|
|
bool containsAwait = false;
|
|
subFuncp->foreach([&](AstNode* nodep) {
|
|
// Record if it has a CAwait
|
|
if (VN_IS(nodep, CAwait)) containsAwait = true;
|
|
// Redirect references to arguments to the clone in the sub-function
|
|
if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
|
|
if (AstVarScope* const vscp = VN_AS(refp->varp()->user3p(), VarScope)) {
|
|
refp->varp(vscp->varp());
|
|
refp->varScopep(vscp);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (ofuncp->isCoroutine() && containsAwait) { // Wrap call with co_await
|
|
subFuncp->rtnType("VlCoroutine");
|
|
ofuncp->addStmtsp(new AstCAwait{flp, callp});
|
|
} else {
|
|
ofuncp->addStmtsp(callp->makeStmt());
|
|
}
|
|
}
|
|
|
|
// Compute, for each top level statement of 'ofuncp', whether a new sub-function may begin there.
|
|
// Sub-functions are emitted as separate C++ functions, so they cannot see each other's automatic
|
|
// storage. A function-local AstVar declared among the top level statements must therefore end up
|
|
// in the same sub-function as every reference to it, otherwise the emitted C++ refers to an
|
|
// undeclared identifier (and the AstVar can be deleted from under the still-live references).
|
|
static std::vector<bool> splitCheckBreakable(const AstCFunc* ofuncp) {
|
|
// Gather the top level statements, and where each locally declared variable is declared
|
|
std::vector<const AstNode*> stmtps;
|
|
std::unordered_map<const AstVar*, size_t> declIdx; // Local var -> index it is declared at
|
|
for (const AstNode* nodep = ofuncp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
if (const AstVar* const varp = VN_CAST(nodep, Var)) declIdx.emplace(varp, stmtps.size());
|
|
stmtps.push_back(nodep);
|
|
}
|
|
|
|
// Find the last statement referencing each locally declared variable
|
|
std::vector<size_t> lastUse(stmtps.size());
|
|
for (size_t i = 0; i < stmtps.size(); ++i) {
|
|
lastUse[i] = i; // A declaration is live at least where it is declared
|
|
stmtps[i]->foreach([&](const AstNodeVarRef* refp) {
|
|
const auto it = declIdx.find(refp->varp()); // 'end()' if not one of our locals
|
|
if (it != declIdx.end()) lastUse[it->second] = std::max(lastUse[it->second], i);
|
|
});
|
|
}
|
|
|
|
// A break before statement 'i' is allowed only if no local declared before 'i' is still live
|
|
std::vector<bool> breakable(stmtps.size(), true);
|
|
size_t liveEnd = 0; // Last index any so far declared local is referenced at
|
|
for (size_t i = 0; i < stmtps.size(); ++i) {
|
|
breakable[i] = liveEnd < i;
|
|
if (VN_IS(stmtps[i], Var)) liveEnd = std::max(liveEnd, lastUse[i]);
|
|
}
|
|
return breakable;
|
|
}
|
|
|
|
// Split large function according to --output-split-cfuncs
|
|
void splitCheck(AstCFunc* const ofuncp) {
|
|
if (!ofuncp) return;
|
|
UASSERT_OBJ(!ofuncp->varsp(), ofuncp, "Can't split function with local variables");
|
|
if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return;
|
|
if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return;
|
|
|
|
// Statement boundaries that would separate a local declaration from a reference to it
|
|
const std::vector<bool> breakable = splitCheckBreakable(ofuncp);
|
|
|
|
// Need to find the AstVarScopes for the function arguments. They should be in the same Scope.
|
|
std::unordered_map<const AstVar*, AstVarScope*> argVscps;
|
|
for (AstVar* argp = ofuncp->argsp(); argp; argp = VN_AS(argp->nextp(), Var)) {
|
|
UASSERT_OBJ(argVscps.size() < 2, argp, "There should be at most 2 arguments, or O(n^2)");
|
|
bool found = false;
|
|
for (AstVarScope *vscp = ofuncp->scopep()->varsp(), *nextp; vscp; vscp = nextp) {
|
|
nextp = VN_AS(vscp->nextp(), VarScope);
|
|
if (vscp->varp() != argp) continue;
|
|
argVscps[argp] = vscp;
|
|
found = true;
|
|
break;
|
|
}
|
|
UASSERT_OBJ(found, argp, "Can't find VarScope for function argument");
|
|
}
|
|
|
|
// AstVar::user3p(): AstVarScope for function argument in clone
|
|
const VNUser3InUse user3InUse;
|
|
|
|
size_t size = 0;
|
|
AstCFunc* subFuncp = nullptr;
|
|
|
|
// Move statements one by one to the new sub-functions
|
|
AstNode* stmtsp = ofuncp->stmtsp()->unlinkFrBackWithNext();
|
|
for (size_t index = 0; AstNode* const itemp = stmtsp; ++index) {
|
|
stmtsp = stmtsp->nextp();
|
|
if (stmtsp) stmtsp->unlinkFrBackWithNext();
|
|
const size_t itemSize = static_cast<size_t>(itemp->nodeCount());
|
|
size += itemSize;
|
|
|
|
if (size > static_cast<size_t>(v3Global.opt.outputSplitCFuncs()) && breakable[index]) {
|
|
if (subFuncp) splitCheckFinishSubFunc(ofuncp, subFuncp, argVscps);
|
|
subFuncp = nullptr;
|
|
size = itemSize;
|
|
}
|
|
|
|
if (!subFuncp) subFuncp = splitCheckCreateNewSubFunc(ofuncp);
|
|
subFuncp->addStmtsp(itemp);
|
|
}
|
|
if (subFuncp) splitCheckFinishSubFunc(ofuncp, subFuncp, argVscps);
|
|
}
|
|
|
|
// Build an AstIf conditional on the given SenTree being triggered
|
|
AstIf* createIfFromSenTree(AstSenTree* senTreep) {
|
|
senTreep = VN_AS(V3Const::constifyExpensiveEdit(senTreep), SenTree);
|
|
UASSERT_OBJ(senTreep->sensesp(), senTreep, "No sensitivity list during scheduling");
|
|
// Convert the SenTree to a boolean expression that is true when triggered
|
|
AstNodeExpr* senEqnp = nullptr;
|
|
for (AstSenItem *senp = senTreep->sensesp(), *nextp; senp; senp = nextp) {
|
|
nextp = VN_AS(senp->nextp(), SenItem);
|
|
// They should all be ET_TRUE, as set up by V3Sched
|
|
UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Bad scheduling trigger type");
|
|
AstNodeExpr* const senOnep = senp->sensp()->cloneTree(false);
|
|
senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep;
|
|
}
|
|
// Create the if statement conditional on the triggers
|
|
return new AstIf{senTreep->fileline(), senEqnp};
|
|
}
|
|
|
|
} // namespace util
|
|
|
|
} // namespace V3Sched
|