2025-10-27 16:16:28 +01:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: Utility functions used by code scheduling
|
|
|
|
|
//
|
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2026-01-27 02:24:34 +01: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
|
2025-10-27 16:16:28 +01: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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
2025-10-31 19:29:11 +01:00
|
|
|
if (!funcp) return nullptr;
|
2025-10-27 16:16:28 +01:00
|
|
|
AstCCall* const callp = new AstCCall{funcp->fileline(), funcp};
|
|
|
|
|
callp->dtypeSetVoid();
|
|
|
|
|
return callp->makeStmt();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AstNodeStmt* checkIterationLimit(AstNetlist* netlistp, const string& name, AstVarScope* counterp,
|
2025-10-31 19:29:11 +01:00
|
|
|
AstNodeStmt* dumpCallp) {
|
2025-10-27 16:16:28 +01:00
|
|
|
FileLine* const flp = netlistp->fileline();
|
|
|
|
|
|
|
|
|
|
// If we exceeded the iteration limit, die
|
|
|
|
|
const uint32_t limit = v3Global.opt.convergeLimit();
|
|
|
|
|
AstVarRef* const counterRefp = new AstVarRef{flp, counterp, VAccess::READ};
|
|
|
|
|
AstConst* const constp = new AstConst{flp, AstConst::DTyped{}, counterp->dtypep()};
|
|
|
|
|
constp->num().setLong(limit);
|
|
|
|
|
AstNodeExpr* const condp = new AstGt{flp, counterRefp, constp};
|
|
|
|
|
AstIf* const ifp = new AstIf{flp, condp};
|
|
|
|
|
ifp->branchPred(VBranchPred::BP_UNLIKELY);
|
Support #0 delays with IEEE-1800 compliant semantics (#7079)
This patch adds IEEE-1800 compliant scheduling support for the Inactive
scheduling region used for #0 delays.
Implementing this requires that **all** IEEE-1800 active region events
are placed in the internal 'act' section. This has simulation
performance implications. It prevents some optimizations (e.g.
V3LifePost), which reduces single threaded performance. It also reduces
the available work and parallelism in the internal 'nba' section, which
reduced the effectiveness of multi-threading severely.
Performance impact on RTLMeter when using scheduling adjusted to support
proper #0 delays is ~10-20% slowdown in single-threaded mode, and ~100%
(2x slower) with --threads 4.
To avoid paying this performance penalty unconditionally, the scheduling
is only adjusted if either:
1. The input contains a statically known #0 delay
2. The input contains a variable #x delay unknown at compile time
If no #0 is present, but #x variable delays are, a ZERODLY warning is
issued advising the use of '--no-sched-zero-delay' which is a promise
by the user that none of the variable delays will evaluate to a zero
delay at run-time. This warning is turned off if '--sched-zero-delay'
is explicitly given. This is similar to the '--timing' option.
If '--no-sched-zero-delay' was used at compile time, then executing
a zero delay will fail at runtime.
A ZERODLY warning is also issued if a static #0 if found, but the user
specified '--no-sched-zero-delay'. In this case the scheduling is not
adjusted to support #0, so executing it will fail at runtime. Presumably
the user knows it won't be executed.
The intended behaviour with all this is the following:
No #0, no #var in the design (#constant is OK)
-> Same as current behaviour, scheduling not adjusted,
same code generated as before
Has static #0 and '--no-sched-zero-delay' is NOT given:
-> No warnings, scheduling adjusted so it just works, runs slow
Has static #0 and '--no-sched-zero-delay' is given:
-> ZERODLY on the #0, scheduling not adjusted, fails at runtime if hit
No static #0, but has #var and no option is given:
-> ZERODLY on the #var advising use of '--no-sched-zero-delay' or
'--sched-zero-delay' (similar to '--timing'), scheduling adjusted
assuming it can be a zero delay and it just works
No static #0, but has #var and '--no-sched-zero-delay' is given:
-> No warning, scheduling not adjusted, fails at runtime if zero delay
No static #0, but has #var and '--sched-zero-delay' is given:
-> No warning, scheduling adjusted so it just works
2026-02-16 04:55:55 +01:00
|
|
|
if (dumpCallp) ifp->addThensp(dumpCallp);
|
2025-10-27 16:16:28 +01:00
|
|
|
AstCStmt* const stmtp = new AstCStmt{flp};
|
|
|
|
|
ifp->addThensp(stmtp);
|
2025-11-13 00:54:22 +01:00
|
|
|
const FileLine* const locp = netlistp->topModulep()->fileline();
|
2025-10-27 16:16:28 +01:00
|
|
|
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
|
2026-01-29 00:32:50 +01:00
|
|
|
+ ", \"\", \"DIDNOTCONVERGE: " + name
|
|
|
|
|
+ " region did not converge after '--converge-limit' of " + std::to_string(limit)
|
|
|
|
|
+ " tries\");");
|
2025-10-27 16:16:28 +01:00
|
|
|
return ifp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static AstCFunc* splitCheckCreateNewSubFunc(AstCFunc* ofuncp) {
|
2025-10-28 01:49:41 +01:00
|
|
|
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 16:16:28 +01:00
|
|
|
const std::string name = ofuncp->name() + "__" + cvtToStr(funcNum);
|
2025-10-31 19:29:11 +01:00
|
|
|
AstScope* const scopep = ofuncp->scopep();
|
|
|
|
|
AstCFunc* const subFuncp = new AstCFunc{ofuncp->fileline(), name, scopep};
|
|
|
|
|
scopep->addBlocksp(subFuncp);
|
2025-10-27 16:16:28 +01:00
|
|
|
subFuncp->dontCombine(true);
|
2025-10-31 19:29:11 +01:00
|
|
|
subFuncp->isStatic(ofuncp->isStatic());
|
2025-10-27 16:16:28 +01:00
|
|
|
subFuncp->isLoose(true);
|
|
|
|
|
subFuncp->slow(ofuncp->slow());
|
|
|
|
|
subFuncp->declPrivate(ofuncp->declPrivate());
|
|
|
|
|
if (ofuncp->needProcess()) subFuncp->setNeedProcess();
|
2025-10-31 19:29:11 +01:00
|
|
|
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 16:16:28 +01:00
|
|
|
return subFuncp;
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
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;
|
2026-02-14 21:15:32 +01:00
|
|
|
subFuncp->foreach([&](AstNode* nodep) {
|
2025-10-31 19:29:11 +01:00
|
|
|
// Record if it has a CAwait
|
2026-02-14 21:15:32 +01:00
|
|
|
if (VN_IS(nodep, CAwait)) containsAwait = true;
|
2025-10-31 19:29:11 +01:00
|
|
|
// Redirect references to arguments to the clone in the sub-function
|
2026-02-14 21:15:32 +01:00
|
|
|
if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
|
2025-10-31 19:29:11 +01:00
|
|
|
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");
|
2026-02-14 21:15:32 +01:00
|
|
|
ofuncp->addStmtsp(new AstCAwait{flp, callp});
|
2025-10-31 19:29:11 +01:00
|
|
|
} else {
|
|
|
|
|
ofuncp->addStmtsp(callp->makeStmt());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 16:16:28 +01:00
|
|
|
// Split large function according to --output-split-cfuncs
|
2025-10-31 19:29:11 +01:00
|
|
|
void splitCheck(AstCFunc* const ofuncp) {
|
|
|
|
|
if (!ofuncp) return;
|
|
|
|
|
UASSERT_OBJ(!ofuncp->varsp(), ofuncp, "Can't split function with local variables");
|
2025-10-27 16:16:28 +01:00
|
|
|
if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return;
|
|
|
|
|
if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return;
|
|
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
// 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 16:16:28 +01:00
|
|
|
}
|
2025-10-31 19:29:11 +01: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();
|
|
|
|
|
while (AstNode* const itemp = stmtsp) {
|
|
|
|
|
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())) {
|
|
|
|
|
if (subFuncp) splitCheckFinishSubFunc(ofuncp, subFuncp, argVscps);
|
|
|
|
|
subFuncp = nullptr;
|
|
|
|
|
size = itemSize;
|
2025-10-27 16:16:28 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
if (!subFuncp) subFuncp = splitCheckCreateNewSubFunc(ofuncp);
|
|
|
|
|
subFuncp->addStmtsp(itemp);
|
2025-10-27 16:16:28 +01:00
|
|
|
}
|
2025-10-31 19:29:11 +01:00
|
|
|
if (subFuncp) splitCheckFinishSubFunc(ofuncp, subFuncp, argVscps);
|
2025-10-27 16:16:28 +01: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
|
|
|
|
|
} // namespace V3Sched
|