Optimize generated function inlining

Previously V3InlineCFuncs inlined call sites but never deleted the now
dead callees. Also missed a lot of opportunities due to evaluation
order.

Rewrite using a graph based algorithm, using only a single traversal of
the netlist. This is simpler, more accurate, and faster at compile time.

Also add a clean `-fno-inline-cfuncs` disable. Setting the limits to 0
still disables inlining, except of empty functions, which can be inlined
with 0 limits (they are no ops).
This commit is contained in:
Geza Lore 2026-06-19 22:00:31 +01:00
parent 9a231d254d
commit 68ecf8c451
28 changed files with 611 additions and 232 deletions

View File

@ -771,10 +771,24 @@ Summary:
.. option:: -fno-inline .. option:: -fno-inline
Rarely needed. Disable module inlining.
.. option:: -fno-inline-cfuncs
Rarely needed. Disable inlining of small generated C++ functions into their
callers.
This optimization is automatically disabled when :vlopt:`--prof-cfuncs` is
used.
.. option:: -fno-inline-funcs .. option:: -fno-inline-funcs
Rarely needed. Disable inlining of SystemVerilog functions and tasks.
.. option:: -fno-inline-funcs-eager .. option:: -fno-inline-funcs-eager
Rarely needed. Disable eager inlining of SystemVerilog functions and tasks.
.. option:: -fno-life .. option:: -fno-life
.. option:: -fno-life-post .. option:: -fno-life-post
@ -976,27 +990,21 @@ Summary:
.. option:: --inline-cfuncs <value> .. option:: --inline-cfuncs <value>
Inline small C++ function (internal AstCFunc) calls directly into their Tune the inlining of small generated C++ function. Functions no bigger than
callers when the function has at most <value> nodes. This reduces <value> nodes will be inlined if possible. The default is 20.
function call overhead when :vlopt:`--output-split-cfuncs` places
functions in separate compilation units that the C++ compiler cannot
inline.
Set to 0 to disable this optimization. The default is 20. See also :vlopt:`--inline-cfuncs-product` and :vlopt:`-fno-inline-cfuncs`.
This optimization is automatically disabled when :vlopt:`--prof-cfuncs`
or :vlopt:`--trace` is used.
.. option:: --inline-cfuncs-product <value> .. option:: --inline-cfuncs-product <value>
Tune the inlining of C++ function (internal AstCFunc) calls for larger Tune the inlining of small generated C++ function. If a function's node
functions. When a function is too large to always inline (exceeds count multiplied by the number of calls is not bigger than <value>, the
:vlopt:`--inline-cfuncs` threshold), it may still be inlined if the function will be inlined if possible.
function size multiplied by the number of call sites is at most <value>.
This allows functions that are called only once or twice to be inlined This allows functions that are called only once or twice to be inlined even
even if they exceed the small function threshold. Set to 0 to only inline if they exceed the small function threshold. The default is 200.
functions below the :vlopt:`--inline-cfuncs` threshold. The default is 200.
See also :vlopt:`--inline-cfuncs` and :vlopt:`-fno-inline-cfuncs`.
.. option:: --inline-mult <value> .. option:: --inline-mult <value>

View File

@ -15,14 +15,13 @@
//************************************************************************* //*************************************************************************
// V3InlineCFuncs's Transformations: // V3InlineCFuncs's Transformations:
// //
// For each CCall to a small CFunc: // Build a bipartite call graph containing function and call site vertices,
// - Check if function is eligible for inlining (small enough, same scope) // then iterate functions leaf to root, inlining if size heuristics are met.
// - Clone local variables with unique names to avoid collisions // Finally, remove unused functions.
// - Replace CCall with cloned function body statements
// //
// Two tunables control inlining: // Two tunables control inlining:
// --inline-cfuncs <n> : Always inline if size <= n (default 20) // --inline-cfuncs <n> : Inline if size <= n
// --inline-cfuncs-product <n> : Also inline if size * call_count <= n (default 200) // --inline-cfuncs-product <n> : Also inline if size * call_count <= n
// //
//************************************************************************* //*************************************************************************
@ -31,231 +30,460 @@
#include "V3InlineCFuncs.h" #include "V3InlineCFuncs.h"
#include "V3AstUserAllocator.h" #include "V3AstUserAllocator.h"
#include "V3ExecGraph.h"
#include "V3Graph.h"
#include "V3Stats.h" #include "V3Stats.h"
#include <map>
#include <vector> #include <vector>
VL_DEFINE_DEBUG_FUNCTIONS; VL_DEFINE_DEBUG_FUNCTIONS;
//###################################################################### //######################################################################
// Helper visitor to check if a CFunc contains C statements // Bipartite call graph containing function and call site vertices
// Uses clearOptimizable pattern for debugging
class CFuncInlineCheckVisitor final : public VNVisitorConst { namespace InlineCFuncsCallGraph {
// STATE
bool m_optimizable = true; // True if function can be inlined
string m_whyNot; // Reason why not optimizable
AstNode* m_whyNotNodep = nullptr; // Node that caused non-optimizable
// METHODS class FunctionVertex;
void clearOptimizable(AstNode* nodep, const string& why) { class CallSiteVertex;
if (m_optimizable) {
m_optimizable = false;
m_whyNot = why;
m_whyNotNodep = nodep;
UINFO(9, "CFunc not inlineable: " << why);
if (nodep) UINFO(9, ": " << nodep);
UINFO(9, "");
}
}
// VISITORS class CallGraph final : public V3Graph {
void visit(AstCStmt* nodep) override { clearOptimizable(nodep, "contains AstCStmt"); } public:
void visit(AstCExpr* nodep) override { clearOptimizable(nodep, "contains AstCExpr"); } CallGraph()
void visit(AstCStmtUser* nodep) override { clearOptimizable(nodep, "contains AstCStmtUser"); } : V3Graph{} {}
void visit(AstCExprUser* nodep) override { clearOptimizable(nodep, "contains AstCExprUser"); } ~CallGraph() override = default;
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
void addEdge(FunctionVertex& from, CallSiteVertex& top);
void addEdge(CallSiteVertex& from, FunctionVertex& top);
};
class EitherVertex VL_NOT_FINAL : public V3GraphVertex {
VL_RTTI_IMPL(EitherVertex, V3GraphVertex)
protected:
explicit EitherVertex(CallGraph& graph)
: V3GraphVertex{&graph} {}
};
class FunctionVertex final : public EitherVertex {
VL_RTTI_IMPL(FunctionVertex, EitherVertex)
AstCFunc* const m_cfuncp; // The function
const char* m_noInlineWyp = nullptr; // First reason the function should not be inlined
const char* m_keepWyp = nullptr; // Why the function should not be removed
size_t m_size = 0; // The size of the function
public: public:
// CONSTRUCTORS FunctionVertex(CallGraph& graph, AstCFunc* cfuncp)
explicit CFuncInlineCheckVisitor(AstCFunc* cfuncp) { iterateConst(cfuncp); } : EitherVertex{graph}
, m_cfuncp{cfuncp} {}
~FunctionVertex() override = default;
// ACCESSORS // ACCESSORS
bool optimizable() const { return m_optimizable; } AstCFunc* cfuncp() const { return m_cfuncp; }
string whyNot() const { return m_whyNot; } size_t size() const { return m_size; }
AstNode* whyNotNodep() const { return m_whyNotNodep; } void sizeInc(size_t value = 1) { m_size += value; }
bool noInline() const { return m_noInlineWyp; }
void setNoInline(const char* whyp) {
if (!m_noInlineWyp) m_noInlineWyp = whyp;
}
bool keep() const { return m_keepWyp; }
void setKeep(const char* whyp) {
if (!m_keepWyp) m_keepWyp = whyp;
}
std::string dotColor() const override {
return m_noInlineWyp ? "red" : m_keepWyp ? "orange" : "black";
}
// debug
FileLine* fileline() const override { return m_cfuncp->fileline(); }
std::string dotShape() const override { return "box"; }
std::string name() const override VL_MT_STABLE {
std::string str = m_cfuncp->name();
str += "\nsize: " + std::to_string(m_size);
if (m_noInlineWyp) str += "\nNoInline: "s + m_noInlineWyp;
if (m_keepWyp) str += "\nKeep: "s + m_keepWyp;
return str;
}
}; };
class CallSiteVertex final : public EitherVertex {
VL_RTTI_IMPL(CallSiteVertex, EitherVertex)
AstCCall* const m_callp; // The call site
const char* m_noInlineWyp = nullptr; // First reason the function should not be inlined
public:
CallSiteVertex(CallGraph& graph, AstCCall* callp)
: EitherVertex{graph}
, m_callp{callp} {}
~CallSiteVertex() override = default;
// ACCESSORS
AstCCall* callp() const { return m_callp; }
bool noInline() const { return m_noInlineWyp; }
void setNoInline(const char* whyp) {
if (!m_noInlineWyp) m_noInlineWyp = whyp;
}
// debug
FileLine* fileline() const override { return m_callp->fileline(); }
std::string dotColor() const override { return m_noInlineWyp ? "red" : "black"; }
std::string dotShape() const override { return "ellipse"; }
std::string name() const override VL_MT_STABLE {
std::string str = cvtToHex(m_callp);
if (m_noInlineWyp) str += "\nNoInline: "s + m_noInlineWyp;
return str;
}
};
void CallGraph::addEdge(FunctionVertex& caller, CallSiteVertex& callsite) {
UASSERT_OBJ(callsite.inEmpty(), &callsite, "Call site should have at most one incoming edge");
new V3GraphEdge{this, &caller, &callsite, 1, true}; // Can cut caller -> callsite
}
void CallGraph::addEdge(CallSiteVertex& callsite, FunctionVertex& callee) {
UASSERT_OBJ(callsite.outEmpty(), &callsite, "Call site should have at most one outgoing edge");
new V3GraphEdge{this, &callsite, &callee, 1, false};
}
} //namespace InlineCFuncsCallGraph
using namespace InlineCFuncsCallGraph;
//###################################################################### //######################################################################
class InlineCFuncsVisitor final : public VNVisitor { class InlineCFuncsVisitor final : public VNVisitor {
// NODE STATE // NODE STATE
// AstCFunc::user1() -> vector of AstCCall* pointing to this function // AstCFunc::user1p() -> FunctionVertex*, the function vertex
// AstCFunc::user2() -> bool: true if checked for C statements // AstCCall::user1p() -> CallSiteVertex*, the call site vertex
// AstCFunc::user3() -> bool: true if contains C statements (not inlineable) // AstVar::user2p() -> AstVar*, the cloned inlined local variable
const VNUser1InUse m_user1InUse; const VNUser1InUse m_user1InUse;
const VNUser2InUse m_user2InUse;
const VNUser3InUse m_user3InUse;
AstUser1Allocator<AstCFunc, std::vector<AstCCall*>> m_callSites;
// STATE // STATE
VDouble0 m_statInlined; // Statistic tracking CallGraph m_graph; // The call graph
const int m_threshold1; // Size threshold: always inline if size <= this VDouble0 m_statCallsInlined; // Number of calls inlined
const int m_threshold2; // Product threshold: inline if size * calls <= this VDouble0 m_statFuncsInlined; // Number of functions inlined at least once
AstCFunc* m_callerFuncp = nullptr; // Current caller function VDouble0 m_statFuncsRemoved; // Number of fully-inlined functions removed
// Tuples of (StmtExpr to replace, CFunc to inline from, caller func for vars) // Size threshold: always inline if size <= this
std::vector<std::tuple<AstStmtExpr*, AstCFunc*, AstCFunc*>> m_toInline; const size_t m_sizeThreshold = v3Global.opt.inlineCFuncs();
// Product threshold: inline if size * calls <= this
const size_t m_prodThreshold = v3Global.opt.inlineCFuncsProduct();
// Maximum size of caller to consider inlining into
const size_t m_maxCallerSize = []() -> size_t {
int maxCFunc = v3Global.opt.outputSplitCFuncs();
int maxTrace = v3Global.opt.outputSplitCTrace();
int maxFile = v3Global.opt.outputSplit();
if (maxCFunc <= 0) maxCFunc = std::numeric_limits<int>::max();
if (maxTrace <= 0) maxTrace = std::numeric_limits<int>::max();
if (maxFile <= 0) maxFile = std::numeric_limits<int>::max();
return std::min(maxCFunc, std::min(maxTrace, maxFile));
}();
FunctionVertex* m_cfuncVtxp = nullptr; // Vertex of currently iterated function
bool m_inExecGraph = false; // True while inside an AstExecGraph subtree
// METHODS // METHODS
FunctionVertex* getFunctionVertexp(AstCFunc* cfuncp) {
// Check if a function contains any $c() calls (user or internal) if (!cfuncp->user1p()) cfuncp->user1p(new FunctionVertex{m_graph, cfuncp});
// Results are cached in user2/user3 for efficiency return cfuncp->user1u().to<FunctionVertex*>();
bool containsCStatements(AstCFunc* cfuncp) {
if (!cfuncp->user2()) {
// Not yet checked - run the check visitor
cfuncp->user2(true); // Mark as checked
const CFuncInlineCheckVisitor checker{cfuncp};
cfuncp->user3(!checker.optimizable()); // Store result (true = contains C stmts)
}
return cfuncp->user3();
} }
// Check if a function is eligible for inlining into caller CallSiteVertex* getCallSiteVertexp(AstCCall* callp) {
bool isInlineable(const AstCFunc* callerp, AstCFunc* cfuncp) { if (!callp->user1p()) callp->user1p(new CallSiteVertex{m_graph, callp});
// Must be in the same scope (same class) to access the same members return callp->user1u().to<CallSiteVertex*>();
if (callerp->scopep() != cfuncp->scopep()) return false; }
// Check for $c() calls that might use 'this' AstCLocalScope* inlineCall(AstCFunc* const callerp, //
if (containsCStatements(cfuncp)) return false; AstCCall* const callp, //
AstCFunc* const calleep, //
const size_t seqNum) {
UINFO(6, "Inlining CFunc " << calleep->name() << " into " << callerp->name()
<< " at call site " << callp);
// Check it's a void function (not a coroutine) AstNodeStmt* const callSitep = VN_AS(callp->backp(), StmtExpr);
if (cfuncp->rtnTypeVoid() != "void") return false; ++m_statCallsInlined;
// Don't inline functions marked dontCombine (e.g. trace, entryPoint) // Callee might be empty, just delete the call
if (cfuncp->dontCombine()) return false; if (!calleep->stmtsp()) {
VL_DO_DANGLING(pushDeletep(callSitep->unlinkFrBack()), callSitep);
// Don't inline entry point functions return nullptr;
if (cfuncp->entryPoint()) return false;
// Must have statements to inline
if (!cfuncp->stmtsp()) return false;
// Check size thresholds
const size_t funcSize = cfuncp->nodeCount();
// Always inline if small enough
if (funcSize <= static_cast<size_t>(m_threshold1)) return true;
// Also inline if size * call_count is reasonable
const size_t callCount = m_callSites(cfuncp).size();
if (callCount > 0 && funcSize * callCount <= static_cast<size_t>(m_threshold2)) {
return true;
} }
return false; // Replace call site with a local scope
FileLine* const flp = callSitep->fileline();
AstCLocalScope* const lscopep = new AstCLocalScope{flp, nullptr};
callSitep->replaceWith(lscopep);
VL_DO_DANGLING(pushDeletep(callSitep), callSitep);
lscopep->addStmtsp(new AstComment{flp, "Inlined CFunc: " + calleep->name()});
// Although it's in a local scope, we still make names of cloned locals unique
const std::string varPrefix
= "__Vinline_" + std::to_string(seqNum) + "_" + calleep->name() + "_";
// AstVar::user2p() -> AstVar*, the cloned inlined local variable
const VNUser2InUse user2InUse;
// Clone local variables, add them to the local scope
for (AstVar* varp = calleep->varsp(); varp; varp = VN_AS(varp->nextp(), Var)) {
AstVar* const newVarp = varp->cloneTree(false);
newVarp->name(varPrefix + varp->name());
lscopep->addStmtsp(newVarp);
varp->user2p(newVarp);
}
// Clone the function body
AstNode* const bodyp = calleep->stmtsp()->cloneTree(true);
lscopep->addStmtsp(bodyp);
// Retarget local variable references to the cloned locals
// Rename locals defined in the body, TODO: there should be none after #6280
// Reset vertex pointers on calls
bodyp->foreachAndNext([&](AstNode* nodep) {
if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
if (AstVar* const varp = VN_AS(refp->varp()->user2p(), Var)) refp->varp(varp);
} else if (AstVar* const varp = VN_CAST(nodep, Var)) {
varp->name(varPrefix + varp->name());
} else if (AstCCall* const callp = VN_CAST(nodep, CCall)) {
callp->user1p(nullptr);
}
});
// Return the local scope
return lscopep;
}
void doInlining() {
// Need to gather vertices as we are changing the graph
std::vector<FunctionVertex*> m_fVtxps;
for (V3GraphVertex& vtx : m_graph.vertices()) {
if (FunctionVertex* const fVtxp = vtx.cast<FunctionVertex>()) {
m_fVtxps.emplace_back(fVtxp);
}
}
// Iterate functions leaf to root
for (FunctionVertex* const calleeVtxp : vlstd::reverse_view(m_fVtxps)) {
// Should we inline this function?
if (calleeVtxp->noInline()) continue; // Told not to
// Check size heuristics
const bool doIt = [&]() {
// Inline if small enough
if (calleeVtxp->size() <= m_sizeThreshold) return true;
// Inline if not too much bloat
const size_t nCalls = calleeVtxp->inEdges().size();
if (nCalls * calleeVtxp->size() <= m_prodThreshold) return true;
// Otherwise don't inline
return false;
}();
if (!doIt) continue;
// Ok, attempt to inline call sites
size_t nInlined = 0;
for (const V3GraphEdge* const edgep : calleeVtxp->inEdges().unlinkable()) {
CallSiteVertex* const callVtxp = edgep->fromp()->as<CallSiteVertex>();
AstCFunc* const calleep = calleeVtxp->cfuncp();
AstCCall* const callp = callVtxp->callp();
UINFO(6, "Consider inlining " << calleep->name() << " at call site " << callp);
// Should we inline this call site?
if (callVtxp->noInline()) continue; // Told not to
if (callVtxp->inEmpty()) continue; // Don't know where it's called from
// Pick up the caller
UASSERT_OBJ(callVtxp->inSize1(), callVtxp->callp(),
"Expected exactly one input edge for call site");
FunctionVertex* const callerVtxp
= callVtxp->inEdges().frontp()->fromp()->as<FunctionVertex>();
AstCFunc* const callerp = callerVtxp->cfuncp();
// Don't make a function bigger than the limit
if (callerVtxp->size() + calleeVtxp->size() > m_maxCallerSize) continue;
// Can't do it if it's in a different scope, self pointers differ
if (callerp->scopep() != calleep->scopep()) continue;
// Inline it
if (!nInlined) ++m_statFuncsInlined;
AstNode* const inlinedp = inlineCall(callerp, callp, calleep, nInlined++);
// Need to adjust the graph:
// 1. Delete inlined call site
VL_DO_DANGLING(callVtxp->unlinkDelete(&m_graph), callVtxp);
// 2. Add new inlined call sites - also increments size of caller
VL_RESTORER(m_cfuncVtxp);
m_cfuncVtxp = callerVtxp;
if (inlinedp) iterateChildrenConst(inlinedp);
}
}
}
void removeUnusedFuncs() {
// Iterate root to leaves
for (V3GraphVertex* const vtxp : m_graph.vertices().unlinkable()) {
FunctionVertex* const fVtxp = vtxp->cast<FunctionVertex>();
if (!fVtxp) continue;
// Keep if still called
if (!fVtxp->inEmpty()) continue;
// Keep for other reasons
if (fVtxp->keep()) continue;
AstCFunc* const funcp = fVtxp->cfuncp();
UINFO(6, "Removing unused CFunc " << funcp);
++m_statFuncsRemoved;
// Unlink all call sites
for (const V3GraphEdge* const edgep : vtxp->outEdges().unlinkable()) {
edgep->top()->unlinkEdges(&m_graph);
}
// Delete function vertex
vtxp->unlinkDelete(&m_graph);
// Delete the function
VL_DO_DANGLING(pushDeletep(funcp->unlinkFrBack()), funcp);
}
// Delete inlined/deleted call site vertices (for debugging only)
for (V3GraphVertex* const vtxp : m_graph.vertices().unlinkable()) {
CallSiteVertex* const cVtxp = vtxp->cast<CallSiteVertex>();
if (!cVtxp) continue;
if (!cVtxp->inEmpty()) continue;
if (!cVtxp->outEmpty()) continue;
vtxp->unlinkDelete(&m_graph);
}
} }
// VISITORS // VISITORS
void visit(AstCCall* nodep) override {
iterateChildren(nodep);
AstCFunc* const cfuncp = nodep->funcp();
if (!cfuncp) return;
// Track call site for call counting
m_callSites(cfuncp).emplace_back(nodep);
}
void visit(AstCFunc* nodep) override { void visit(AstCFunc* nodep) override {
VL_RESTORER(m_callerFuncp); // Create the function vertex
m_callerFuncp = nodep; FunctionVertex* const vtxp = getFunctionVertexp(nodep);
iterateChildren(nodep);
// Check if the function itself is not inlineable
if (nodep->rtnTypeVoid() != "void") vtxp->setNoInline("Not void");
if (nodep->dpiImportPrototype()) vtxp->setNoInline("DPI import prototype");
if (nodep->recursive()) vtxp->setNoInline("Recursive");
if (nodep->argsp()) vtxp->setNoInline("Has arguments");
if (nodep->isVirtual()) vtxp->setNoInline("Virtual method");
// Check if the function should not be removed
if (nodep->entryPoint()) vtxp->setKeep("Entry point");
if (nodep->dpiImportPrototype()) vtxp->setKeep("DPI import prototype");
if (nodep->dpiExportDispatcher()) vtxp->setKeep("DPI export implementation");
if (nodep->isVirtual()) vtxp->setKeep("Virtual method");
// Iterate children
VL_RESTORER(m_cfuncVtxp);
m_cfuncVtxp = vtxp;
iterateChildrenConst(nodep);
} }
void visit(AstNodeModule* nodep) override { // Inlineable calls
// Process per module for better cache behavior void visit(AstCCall* nodep) override {
m_toInline.clear(); if (m_cfuncVtxp) m_cfuncVtxp->sizeInc();
AstCFunc* const calleep = nodep->funcp();
// Phase 1: Collect call sites within this module // Create the call site vertex
iterateChildren(nodep); CallSiteVertex* const vtxp = getCallSiteVertexp(nodep);
// Phase 2: Determine which calls to inline // Check if the call site is not inlineable
collectInlineCandidates(nodep); if (!VN_IS(nodep->backp(), StmtExpr)) vtxp->setNoInline("Not in statement position");
if (m_inExecGraph) vtxp->setNoInline("In ExecGraph");
if (calleep->isVirtual()) vtxp->setNoInline("Virtual method");
// Phase 3: Perform inlining for this module // Add caller/callee edges
doInlining(); if (m_cfuncVtxp) m_graph.addEdge(*m_cfuncVtxp, *vtxp);
m_graph.addEdge(*vtxp, *getFunctionVertexp(calleep));
// Iterate children
iterateChildrenConst(nodep);
} }
void visit(AstNode* nodep) override { iterateChildren(nodep); } // Nodes that reference functions/calls
void visit(AstNetlist* nodep) override {
UASSERT_OBJ(!nodep->evalp(), nodep, "evalp should not be null at this stage");
UASSERT_OBJ(!nodep->evalNbap(), nodep, "evalNbap should be null at this stage");
iterateChildrenConst(nodep);
}
// Collect calls that should be inlined within this module void visit(AstNodeCCall* nodep) override {
void collectInlineCandidates(AstNodeModule* modp) { if (m_cfuncVtxp) m_cfuncVtxp->sizeInc();
for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { getFunctionVertexp(nodep->funcp())->setKeep("Called elsewhere");
AstCFunc* const callerp = VN_CAST(stmtp, CFunc); iterateChildrenConst(nodep);
if (!callerp) continue; }
callerp->foreach([&](AstCCall* callp) { void visit(AstAddrOfCFunc* nodep) override {
AstCFunc* const cfuncp = callp->funcp(); if (m_cfuncVtxp) m_cfuncVtxp->sizeInc();
if (!cfuncp) return; getFunctionVertexp(nodep->funcp())->setKeep("Referenced by AddressOfCFunc");
if (!isInlineable(callerp, cfuncp)) return; iterateChildrenConst(nodep);
}
// Walk up to find the containing StmtExpr // Nodes preventing inlining
AstNode* stmtNodep = callp; void visit(AstTraceDecl* nodep) override {
while (stmtNodep && !VN_IS(stmtNodep, StmtExpr) && !VN_IS(stmtNodep, CFunc)) { // Referenced by TraceInc
stmtNodep = stmtNodep->backp(); if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains TraceDecl");
}
AstStmtExpr* const stmtExprp = VN_CAST(stmtNodep, StmtExpr); if (AstCCall* const callp = nodep->dtypeCallp()) {
if (!stmtExprp) return; getCallSiteVertexp(callp)->setNoInline("Referenced by TraceDecl");
m_toInline.emplace_back(stmtExprp, cfuncp, callerp);
});
} }
iterateChildrenConst(nodep);
}
void visit(AstExecGraph* nodep) override {
// AstExecGraph is not cloneable, so can't inline the containing function
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains ExecGraph");
// Also mark functions referenced in the dependency graph
for (const V3GraphVertex& vtx : nodep->depGraphp()->vertices()) {
getFunctionVertexp(vtx.as<ExecMTask>()->funcp())->setKeep("MTask function");
}
VL_RESTORER(m_inExecGraph);
m_inExecGraph = true;
iterateChildrenConst(nodep);
}
void visit(AstCStmt* nodep) override {
// Can reference anything in text
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CStmt");
iterateChildrenConst(nodep);
}
void visit(AstCExpr* nodep) override {
// Can reference anything in text
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CExpr");
iterateChildrenConst(nodep);
}
void visit(AstCStmtUser* nodep) override {
// Can reference anything in text
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CStmtUser");
iterateChildrenConst(nodep);
}
void visit(AstCExprUser* nodep) override {
// Can reference anything in text
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CExprUser");
iterateChildrenConst(nodep);
}
void visit(AstCReturn* nodep) override {
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CReturn");
iterateChildrenConst(nodep);
} }
// Perform the actual inlining after iteration is complete // Base node
void doInlining() { void visit(AstNode* nodep) override {
for (const auto& tuple : m_toInline) { if (m_cfuncVtxp) m_cfuncVtxp->sizeInc();
AstStmtExpr* const stmtExprp = std::get<0>(tuple); iterateChildrenConst(nodep);
AstCFunc* const cfuncp = std::get<1>(tuple);
AstCFunc* const callerp = std::get<2>(tuple);
UINFO(6, "Inlining CFunc " << cfuncp->name() << " into " << callerp->name());
++m_statInlined;
// Clone local variables with unique names to avoid collisions
std::map<AstVar*, AstVar*> varMap;
for (AstVar* varp = cfuncp->varsp(); varp; varp = VN_AS(varp->nextp(), Var)) {
const string newName = "__Vinline_" + cfuncp->name() + "_" + varp->name();
AstVar* const newVarp = varp->cloneTree(false);
newVarp->name(newName);
callerp->addVarsp(newVarp);
varMap[varp] = newVarp;
}
// Clone the function body
AstNode* const bodyp = cfuncp->stmtsp()->cloneTree(true);
// Retarget variable references to the cloned variables
// Must iterate all sibling statements, not just the first
if (!varMap.empty()) {
for (AstNode* stmtp = bodyp; stmtp; stmtp = stmtp->nextp()) {
stmtp->foreach([&](AstVarRef* refp) {
auto it = varMap.find(refp->varp());
if (it != varMap.end()) refp->varp(it->second);
});
}
}
// Replace the statement with the inlined body
stmtExprp->addNextHere(bodyp);
VL_DO_DANGLING(stmtExprp->unlinkFrBack()->deleteTree(), stmtExprp);
}
} }
public: public:
// CONSTRUCTORS // CONSTRUCTORS
explicit InlineCFuncsVisitor(const AstNetlist* nodep) explicit InlineCFuncsVisitor(AstNetlist* nodep) {
: m_threshold1{v3Global.opt.inlineCFuncs()} // Phase 1: Build call graph
, m_threshold2{v3Global.opt.inlineCFuncsProduct()} { iterateConst(nodep);
// Don't inline when profiling or tracing // Make acyclic in case there is recursion
if (v3Global.opt.profCFuncs() || v3Global.opt.trace()) return; m_graph.acyclic(V3GraphEdge::followAlwaysTrue);
// Process modules one at a time for better cache behavior // Order vertices (any topological order is fine)
iterateAndNextNull(nodep->modulesp()); m_graph.order();
if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-graph");
// Phase 2: Inline calls
doInlining();
if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-inlined");
// Phase 3: Remove unused functions
removeUnusedFuncs();
if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-kept");
} }
~InlineCFuncsVisitor() override { ~InlineCFuncsVisitor() override {
V3Stats::addStat("Optimizations, Inlined CFuncs", m_statInlined); V3Stats::addStat("Optimizations, Inline CFuncs, calls inlined", m_statCallsInlined);
V3Stats::addStat("Optimizations, Inline CFuncs, functions inlined", m_statFuncsInlined);
V3Stats::addStat("Optimizations, Inline CFuncs, functions removed", m_statFuncsRemoved);
} }
}; };
@ -264,6 +492,8 @@ public:
void V3InlineCFuncs::inlineAll(AstNetlist* nodep) { void V3InlineCFuncs::inlineAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":"); UINFO(2, __FUNCTION__ << ":");
// Don't inline when profiling per-function (it would lose granularity)
if (v3Global.opt.profCFuncs()) return;
{ InlineCFuncsVisitor{nodep}; } // Destruct before checking { InlineCFuncsVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("inlinecfuncs", 0, dumpTreeEitherLevel() >= 6); V3Global::dumpCheckGlobalTree("inlinecfuncs", 0, dumpTreeEitherLevel() >= 6);
} }

View File

@ -1497,6 +1497,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
m_fIcoChangeDetect.setTrueOrFalse(flag); m_fIcoChangeDetect.setTrueOrFalse(flag);
}); });
DECL_OPTION("-finline", FOnOff, &m_fInline); DECL_OPTION("-finline", FOnOff, &m_fInline);
DECL_OPTION("-finline-cfuncs", FOnOff, &m_fInlineCFuncs);
DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs); DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs);
DECL_OPTION("-finline-funcs-eager", FOnOff, &m_fInlineFuncsEager); DECL_OPTION("-finline-funcs-eager", FOnOff, &m_fInlineFuncsEager);
DECL_OPTION("-flife", FOnOff, &m_fLife); DECL_OPTION("-flife", FOnOff, &m_fLife);
@ -2371,6 +2372,7 @@ void V3Options::optimize(int level) {
m_fExpand = flag; m_fExpand = flag;
m_fGate = flag; m_fGate = flag;
m_fInline = flag; m_fInline = flag;
m_fInlineCFuncs = flag;
m_fLife = flag; m_fLife = flag;
m_fLifePost = flag; m_fLifePost = flag;
m_fLocalize = flag; m_fLocalize = flag;

View File

@ -415,6 +415,7 @@ private:
// main switch: -fno-ico-change-detect: input change detection optimization // main switch: -fno-ico-change-detect: input change detection optimization
VOptionBool m_fIcoChangeDetect{VOptionBool::OPT_DEFAULT_TRUE}; VOptionBool m_fIcoChangeDetect{VOptionBool::OPT_DEFAULT_TRUE};
bool m_fInline; // main switch: -fno-inline: module inlining bool m_fInline; // main switch: -fno-inline: module inlining
bool m_fInlineCFuncs; // main switch: -fno-inline-cfuncs: inline small C functions
bool m_fInlineFuncs = true; // main switch: -fno-inline-funcs: function inlining bool m_fInlineFuncs = true; // main switch: -fno-inline-funcs: function inlining
bool m_fInlineFuncsEager = true; // main switch: -fno-inline-funcs-eager: don't inline eagerly bool m_fInlineFuncsEager = true; // main switch: -fno-inline-funcs-eager: don't inline eagerly
bool m_fLife; // main switch: -fno-life: variable lifetime bool m_fLife; // main switch: -fno-life: variable lifetime
@ -753,6 +754,7 @@ public:
bool fGate() const { return m_fGate; } bool fGate() const { return m_fGate; }
VOptionBool fIcoChangeDetect() const { return m_fIcoChangeDetect; } VOptionBool fIcoChangeDetect() const { return m_fIcoChangeDetect; }
bool fInline() const { return m_fInline; } bool fInline() const { return m_fInline; }
bool fInlineCFuncs() const { return m_fInlineCFuncs; }
bool fInlineFuncs() const { return m_fInlineFuncs; } bool fInlineFuncs() const { return m_fInlineFuncs; }
bool fInlineFuncsEager() const { return m_fInlineFuncsEager; } bool fInlineFuncsEager() const { return m_fInlineFuncsEager; }
bool fLife() const { return m_fLife; } bool fLife() const { return m_fLife; }

View File

@ -1094,6 +1094,7 @@ class TraceVisitor final : public VNVisitor {
// Create the trace registration function // Create the trace registration function
m_regFuncp = new AstCFunc{m_topScopep->fileline(), "trace_register", m_topScopep}; m_regFuncp = new AstCFunc{m_topScopep->fileline(), "trace_register", m_topScopep};
m_regFuncp->argTypes(v3Global.opt.traceClassBase() + "* tracep"); m_regFuncp->argTypes(v3Global.opt.traceClassBase() + "* tracep");
m_regFuncp->entryPoint(true);
m_regFuncp->isTrace(true); m_regFuncp->isTrace(true);
m_regFuncp->slow(true); m_regFuncp->slow(true);
m_regFuncp->isStatic(false); m_regFuncp->isStatic(false);

View File

@ -980,6 +980,7 @@ public:
AstCFunc* rootFuncp = nullptr; AstCFunc* rootFuncp = nullptr;
if (!v3Global.opt.libCreate().empty()) { if (!v3Global.opt.libCreate().empty()) {
rootFuncp = newCFunc(flp, "trace_init_root"); rootFuncp = newCFunc(flp, "trace_init_root");
rootFuncp->entryPoint(true);
for (size_t i = 0; i < m_topScopeRootFuncCount; ++i) { for (size_t i = 0; i < m_topScopeRootFuncCount; ++i) {
AstCCall* const callp = new AstCCall{flp, topScopeFuncps.at(i)}; AstCCall* const callp = new AstCCall{flp, topScopeFuncps.at(i)};
callp->dtypeSetVoid(); callp->dtypeSetVoid();
@ -1017,6 +1018,7 @@ public:
// Set name of top level function // Set name of top level function
AstCFunc* const topFuncp = m_topFuncps.front(); AstCFunc* const topFuncp = m_topFuncps.front();
topFuncp->name("trace_init_top"); topFuncp->name("trace_init_top");
topFuncp->entryPoint(true);
if (rootFuncp && v3Global.opt.debugCheck()) checkCallsRecurse(rootFuncp); if (rootFuncp && v3Global.opt.debugCheck()) checkCallsRecurse(rootFuncp);
checkCalls(topFuncp); checkCalls(topFuncp);

View File

@ -582,8 +582,14 @@ static void process() {
// Must be after all Sel/array index based optimizations // Must be after all Sel/array index based optimizations
V3Reloop::reloopAll(v3Global.rootp()); V3Reloop::reloopAll(v3Global.rootp());
} }
}
if (v3Global.opt.inlineCFuncs()) { // These are no longer needed, remove references before CFunc inlining
v3Global.rootp()->evalp(nullptr);
v3Global.rootp()->evalNbap(nullptr);
if (!v3Global.opt.lintOnly() && !v3Global.opt.serializeOnly()) {
if (v3Global.opt.fInlineCFuncs()) {
// Inline small CFuncs to reduce function call overhead // Inline small CFuncs to reduce function call overhead
V3InlineCFuncs::inlineAll(v3Global.rootp()); V3InlineCFuncs::inlineAll(v3Global.rootp());
} }

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator_st') test.scenarios('simulator_st')
test.compile(verilator_flags2=["--stats", "--inline-cfuncs", "0"]) test.compile(verilator_flags2=["--stats", "-fno-inline-cfuncs"])
test.execute(expect_filename=test.golden_filename) test.execute(expect_filename=test.golden_filename)

View File

@ -23,7 +23,7 @@ def check_evals():
test.error("Too few _eval functions found: " + str(got)) test.error("Too few _eval functions found: " + str(got))
test.compile(v_flags2=["--output-split 1 --output-split-cfuncs 20"], test.compile(v_flags2=["--output-split 1 --output-split-cfuncs 20 -fno-inline-cfuncs"],
verilator_make_gmake=False) # Slow to compile, so skip it) verilator_make_gmake=False) # Slow to compile, so skip it)
check_evals() check_evals()

View File

@ -31,9 +31,9 @@ test.compile(v_flags2=[
if test.vltmt: if test.vltmt:
test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt",
r'Optimizations, Thread schedule count\s+(\d+)', 3) r'Optimizations, Thread schedule count\s+(\d+)', 4)
test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt",
r'Optimizations, Thread schedule total tasks\s+(\d+)', 5) r'Optimizations, Thread schedule total tasks\s+(\d+)', 6)
test.execute() test.execute()

View File

@ -35,9 +35,9 @@ test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "_
if test.vltmt: if test.vltmt:
test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt",
r'Optimizations, Thread schedule count\s+(\d+)', 1) r'Optimizations, Thread schedule count\s+(\d+)', 2)
test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt",
r'Optimizations, Thread schedule total tasks\s+(\d+)', 2) r'Optimizations, Thread schedule total tasks\s+(\d+)', 3)
test.execute(all_run_flags=[ test.execute(all_run_flags=[
"+verilator+prof+exec+start+2", "+verilator+prof+exec+start+2",

View File

@ -14,8 +14,8 @@ test.top_filename = "t/t_inst_tree.v"
default_vltmt_threads = test.get_default_vltmt_threads default_vltmt_threads = test.get_default_vltmt_threads
test.compile( test.compile(
# Disable --inline-cfuncs so functions exist to be combined # Disable CFunc inlining so functions exist to be combined
verilator_flags2=['--stats', '--inline-cfuncs', '0', test.t_dir + "/" + test.name + ".vlt"], verilator_flags2=['--stats', '-fno-inline-cfuncs', test.t_dir + "/" + test.name + ".vlt"],
# Force 3 threads even if we have fewer cores # Force 3 threads even if we have fewer cores
threads=(default_vltmt_threads if test.vltmt else 1)) threads=(default_vltmt_threads if test.vltmt else 1))

View File

@ -16,7 +16,7 @@ test.top_filename = "t/t_enum_type_methods.v"
out_filename = test.obj_dir + "/V" + test.name + ".tree.json" out_filename = test.obj_dir + "/V" + test.name + ".tree.json"
test.compile(verilator_flags2=[ test.compile(verilator_flags2=[
'--no-std', '--debug-check', '--no-json-edit-nums', '--flatten', '--inline-cfuncs', '0' '--no-std', '--debug-check', '--no-json-edit-nums', '--flatten', '-fno-inline-cfuncs'
], ],
verilator_make_gmake=False, verilator_make_gmake=False,
make_top_shell=False, make_top_shell=False,

View File

@ -9,17 +9,17 @@
import vltest_bootstrap import vltest_bootstrap
test.scenarios('vlt') test.scenarios('vlt_all')
# Use --output-split-cfuncs to create small functions that can be inlined
# Also test --inline-cfuncs-product option
test.compile(verilator_flags2=[ test.compile(verilator_flags2=[
"--stats", "--binary", "--output-split-cfuncs", "1", "--inline-cfuncs-product", "200" "--stats", "--binary", "--inline-cfuncs-product", "200", "--dumpi-V3InlineCFuncs", "9"
]) ])
# Verify inlining happened with exact count
test.file_grep(test.stats, r'Optimizations, Inlined CFuncs\s+(\d+)', 39)
test.execute() test.execute()
if test.vlt:
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 7)
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 7)
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 7)
test.passes() test.passes()

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt_all')
test.compile(verilator_flags2=["--stats", "--binary"])
test.execute()
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]')
test.passes()

View File

@ -0,0 +1,38 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t ( /*AUTOARG*/
// Inputs
clk
);
input clk;
integer cyc = 0;
reg [31:0] acc;
task automatic add_pair(input [31:0] a, input [31:0] b, inout [31:0] sum);
// verilator no_inline_task
sum = sum + a + b;
endtask
always @(posedge clk) begin
cyc <= cyc + 1;
acc = 0;
add_pair(cyc[31:0], 32'd1, acc); // + cyc + 1
add_pair(32'd1000, cyc[31:0], acc); // + 1000 + cyc
// acc = (cyc + 1) + (1000 + cyc) = 2*cyc + 1001
if (cyc > 1) begin
if (acc !== (2 * cyc[31:0] + 32'd1001)) begin
$write("%%Error: cyc=%0d acc=%0d expected %0d\n", cyc, acc, 2 * cyc + 1001);
$stop;
end
end
if (cyc == 20) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt_all')
test.compile(verilator_flags2=["--stats", "--binary"])
test.execute()
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]')
test.passes()

View File

@ -0,0 +1,32 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t ( /*AUTOARG*/
// Inputs
clk
);
input clk;
integer cyc = 0;
task automatic tick();
// verilator no_inline_task
automatic time t = $time;
$display("TICK: %0t", t);
endtask
always @(posedge clk) begin
cyc <= cyc + 1;
tick();
tick();
tick();
tick();
if (cyc == 20) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -9,15 +9,13 @@
import vltest_bootstrap import vltest_bootstrap
test.scenarios('vlt') test.scenarios('vlt_all')
test.top_filename = "t/t_opt_inline_cfuncs.v" test.top_filename = "t/t_opt_inline_cfuncs.v"
# Disable inlining with --inline-cfuncs 0 test.compile(verilator_flags2=["--stats", "--binary", "-fno-inline-cfuncs"])
test.compile(verilator_flags2=["--stats", "--binary", "--inline-cfuncs", "0"])
# Verify inlining did NOT happen (stat doesn't exist when pass is skipped)
test.file_grep_not(test.stats, r'Optimizations, Inlined CFuncs\s+[1-9]')
test.execute() test.execute()
test.file_grep_not(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]')
test.passes() test.passes()

View File

@ -9,17 +9,16 @@
import vltest_bootstrap import vltest_bootstrap
test.scenarios('vlt') test.scenarios('vlt_all')
# Use thresholds that guarantee rejection to test the "return false" path in isInlineable()
# --inline-cfuncs 1: pass still runs (not skipped)
# --inline-cfuncs-product 0: guarantees all functions rejected (node_count * call_count > 0 always)
test.compile(verilator_flags2=[ test.compile(verilator_flags2=[
"--stats", "--binary", "--inline-cfuncs", "1", "--inline-cfuncs-product", "0" "--stats", "--binary", "--inline-cfuncs", "0", "--inline-cfuncs-product", "0"
]) ])
test.file_grep(test.stats, r'Optimizations, Inlined CFuncs\s+(\d+)', 0)
test.execute() test.execute()
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 0)
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 0)
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 0)
test.passes() test.passes()

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt_all')
test.top_filename = "t/t_opt_inline_cfuncs.v"
test.compile(verilator_flags2=["--stats", "--binary", "--trace", "--inline-cfuncs-product", "200"])
if test.vlt:
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 8)
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 7)
test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 9)
test.execute()
test.passes()

View File

@ -16,13 +16,11 @@
<map from="PSScAO" to="__VactTriggered"/> <map from="PSScAO" to="__VactTriggered"/>
<map from="PSx9Nt" to="__Vconfigure"/> <map from="PSx9Nt" to="__Vconfigure"/>
<map from="PSrjMj" to="__Vdly__secret_cyc"/> <map from="PSrjMj" to="__Vdly__secret_cyc"/>
<map from="PSAW38" to="__Vdly__t__DOT__secret_inst2__DOT__secret_cyc"/>
<map from="PS4o5S" to="__Vdpiexp_dpix_a_func_TOP__t__DOT__secret_inst"/> <map from="PS4o5S" to="__Vdpiexp_dpix_a_func_TOP__t__DOT__secret_inst"/>
<map from="PStVA8" to="__Vdpiexp_dpix_a_task_TOP__t__DOT__secret_inst"/> <map from="PStVA8" to="__Vdpiexp_dpix_a_task_TOP__t__DOT__secret_inst"/>
<map from="PSxbIE" to="__Vdpiimwrap_dpii_a_func_TOP__t__DOT__secret_inst"/> <map from="PSxbIE" to="__Vdpiimwrap_dpii_a_func_TOP__t__DOT__secret_inst"/>
<map from="PSIv2l" to="__Vdpiimwrap_dpii_a_task_TOP__t__DOT__secret_inst"/>
<map from="PS76My" to="__Vfunc_dpii_a_func__0__Vfuncout"/> <map from="PS76My" to="__Vfunc_dpii_a_func__0__Vfuncout"/>
<map from="PSZAsk" to="__Vinline__nba_sequent__TOP__0___Vdly__t__DOT__secret_inst2__DOT__secret_cyc"/> <map from="PSywKw" to="__Vinline_0__eval_nba___Vinline_0__nba_sequent__TOP__0___Vdly__t__DOT__secret_inst2__DOT__secret_cyc"/>
<map from="PSo9XV" to="__VnbaExecute"/> <map from="PSo9XV" to="__VnbaExecute"/>
<map from="PSEtOH" to="__VnbaIterCount"/> <map from="PSEtOH" to="__VnbaIterCount"/>
<map from="PSeNXP" to="__VnbaPhaseResult"/> <map from="PSeNXP" to="__VnbaPhaseResult"/>
@ -36,13 +34,10 @@
<map from="PSKZ7c" to="_eval_debug_assertions"/> <map from="PSKZ7c" to="_eval_debug_assertions"/>
<map from="PSEZzj" to="_eval_final"/> <map from="PSEZzj" to="_eval_final"/>
<map from="PSABAY" to="_eval_initial"/> <map from="PSABAY" to="_eval_initial"/>
<map from="PSjoVa" to="_eval_nba"/>
<map from="PS0BBP" to="_eval_phase__act"/> <map from="PS0BBP" to="_eval_phase__act"/>
<map from="PSfNDT" to="_eval_phase__nba"/> <map from="PSfNDT" to="_eval_phase__nba"/>
<map from="PSBUJ6" to="_eval_settle"/> <map from="PSBUJ6" to="_eval_settle"/>
<map from="PS0mmd" to="_eval_static"/> <map from="PS0mmd" to="_eval_static"/>
<map from="PSf5GY" to="_eval_triggers_vec__act"/>
<map from="PSgp53" to="_nba_sequent__TOP__0"/>
<map from="PSoFVg" to="_nba_sequent__TOP__t__DOT__secret_inst__0"/> <map from="PSoFVg" to="_nba_sequent__TOP__t__DOT__secret_inst__0"/>
<map from="PSMRXn" to="_trigger_anySet__act"/> <map from="PSMRXn" to="_trigger_anySet__act"/>
<map from="PSBKaZ" to="_trigger_clear__act"/> <map from="PSBKaZ" to="_trigger_clear__act"/>

View File

@ -13,7 +13,7 @@ test.scenarios('vlt_all')
test.top_filename = "t/t_timing_sched.v" test.top_filename = "t/t_timing_sched.v"
test.compile( test.compile(
verilator_flags2=["--binary", "--timing", "--inline-cfuncs", "0", "-CFLAGS", "-DVL_DEBUG"]) verilator_flags2=["--binary", "--timing", "-fno-inline-cfuncs", "-CFLAGS", "-DVL_DEBUG"])
test.execute(all_run_flags=["+verilator+debug"]) test.execute(all_run_flags=["+verilator+debug"])

View File

@ -12,8 +12,8 @@ import vltest_bootstrap
test.scenarios('vlt_all') test.scenarios('vlt_all')
test.top_filename = "t/t_timing_class.v" test.top_filename = "t/t_timing_class.v"
# Disable --inline-cfuncs so debug traces show all function entries # Disable CFunc inlining so debug traces show all function entries
test.compile(verilator_flags2=["--exe --main --timing --inline-cfuncs 0"]) test.compile(verilator_flags2=["--exe --main --timing -fno-inline-cfuncs"])
test.execute(all_run_flags=["+verilator+debug"]) test.execute(all_run_flags=["+verilator+debug"])

View File

@ -4,6 +4,7 @@
-V{t#,#}+ Vt_timing_eval_act___024root___eval_debug_assertions -V{t#,#}+ Vt_timing_eval_act___024root___eval_debug_assertions
-V{t#,#}+ Initial -V{t#,#}+ Initial
-V{t#,#}+ Vt_timing_eval_act___024root___eval_static -V{t#,#}+ Vt_timing_eval_act___024root___eval_static
-V{t#,#}+ Vt_timing_eval_act___024root___eval_static__TOP
-V{t#,#}+ Vt_timing_eval_act___024root___timing_ready -V{t#,#}+ Vt_timing_eval_act___024root___timing_ready
-V{t#,#}+ Vt_timing_eval_act___024root___eval_initial -V{t#,#}+ Vt_timing_eval_act___024root___eval_initial
-V{t#,#}+ Vt_timing_eval_act___024root___eval_initial__TOP__Vtiming__0 -V{t#,#}+ Vt_timing_eval_act___024root___eval_initial__TOP__Vtiming__0

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('vlt') test.scenarios('vlt')
test.compile(verilator_flags2=["--binary", "--runtime-debug"]) test.compile(verilator_flags2=["--binary", "--runtime-debug", "-fno-inline-cfuncs"])
test.file_grep( test.file_grep(
test.obj_dir + "/" + test.vm_prefix + "___024root__0.cpp", r'void\s+' + test.vm_prefix + test.obj_dir + "/" + test.vm_prefix + "___024root__0.cpp", r'void\s+' + test.vm_prefix +

View File

@ -37,6 +37,7 @@ internalsDump:
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba -V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act -V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_nba -V{t#,#}+ Vt_verilated_debug___024root___eval_nba
-V{t#,#}+ Vt_verilated_debug___024root___nba_sequent__TOP__0
*-* All Finished *-* *-* All Finished *-*
-V{t#,#}+ Vt_verilated_debug___024root___trigger_clear__act -V{t#,#}+ Vt_verilated_debug___024root___trigger_clear__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act -V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act

View File

@ -12,7 +12,7 @@ import vltest_bootstrap
test.scenarios('vlt_all') test.scenarios('vlt_all')
test.verilated_debug = True test.verilated_debug = True
test.compile(verilator_flags2=[]) test.compile(verilator_flags2=['-fno-inline-cfuncs'])
test.execute() test.execute()