Files
verilator/src/V3SchedUtil.cpp
T

253 lines
10 KiB
C++
Raw Normal View History

2025-10-27 15:16:28 +00:00
// -*- 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
2025-10-27 15:16:28 +00:00
// 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;
}
2025-10-27 15:16:28 +00:00
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;
2025-10-27 15:16:28 +00:00
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]++;
2025-10-27 15:16:28 +00:00
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);
2025-10-27 15:16:28 +00:00
subFuncp->dontCombine(true);
subFuncp->isStatic(ofuncp->isStatic());
2025-10-27 15:16:28 +00:00
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);
}
2025-10-27 15:16:28 +00:00
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;
}
2025-10-27 15:16:28 +00:00
// 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");
2025-10-27 15:16:28 +00:00
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;
2025-10-27 15:16:28 +00:00
}
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;
2025-10-27 15:16:28 +00:00
}
if (!subFuncp) subFuncp = splitCheckCreateNewSubFunc(ofuncp);
subFuncp->addStmtsp(itemp);
2025-10-27 15:16:28 +00:00
}
if (subFuncp) splitCheckFinishSubFunc(ofuncp, subFuncp, argVscps);
2025-10-27 15:16:28 +00:00
}
// 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
2025-10-27 15:16:28 +00:00
} // namespace V3Sched