2022-08-22 14:26:32 +02: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-08-22 14:26:32 +02:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// Functions defined in this file are used by V3Sched.cpp to properly integrate
|
|
|
|
|
// static scheduling with timing features. They create external domains for
|
2026-02-11 19:35:59 +01:00
|
|
|
// variables, remap them to trigger vectors, and create timing resume/ready
|
2022-08-22 14:26:32 +02:00
|
|
|
// calls for the global eval loop. There is also a function that transforms
|
|
|
|
|
// forks into emittable constructs.
|
|
|
|
|
//
|
|
|
|
|
// See the internals documentation docs/internals.rst for more details.
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
|
2023-10-18 02:50:27 +00:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
2023-10-18 06:37:46 -04:00
|
|
|
|
2026-08-11 14:18:18 +02:00
|
|
|
#include "V3AstUserAllocator.h"
|
2026-08-27 14:33:49 +02:00
|
|
|
#include "V3ClassGraph.h"
|
2023-10-18 06:37:46 -04:00
|
|
|
#include "V3EmitCBase.h"
|
2022-08-22 14:26:32 +02:00
|
|
|
#include "V3Sched.h"
|
|
|
|
|
|
|
|
|
|
#include <unordered_map>
|
2026-08-11 14:18:18 +02:00
|
|
|
#include <unordered_set>
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2022-09-22 17:28:42 +01:00
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
2022-08-22 14:26:32 +02:00
|
|
|
namespace V3Sched {
|
|
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
// Remaps external domains using the specified trigger map
|
|
|
|
|
|
|
|
|
|
std::map<const AstVarScope*, std::vector<AstSenTree*>>
|
|
|
|
|
TimingKit::remapDomains(const std::unordered_map<const AstSenTree*, AstSenTree*>& trigMap) const {
|
|
|
|
|
std::map<const AstVarScope*, std::vector<AstSenTree*>> remappedDomainMap;
|
|
|
|
|
for (const auto& vscpDomains : m_externalDomains) {
|
|
|
|
|
const AstVarScope* const vscp = vscpDomains.first;
|
|
|
|
|
const auto& domains = vscpDomains.second;
|
|
|
|
|
auto& remappedDomains = remappedDomainMap[vscp];
|
|
|
|
|
remappedDomains.reserve(domains.size());
|
|
|
|
|
for (AstSenTree* const domainp : domains) {
|
|
|
|
|
remappedDomains.push_back(trigMap.at(domainp));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return remappedDomainMap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
// Creates a timing resume call (if needed, else returns null)
|
|
|
|
|
|
|
|
|
|
AstCCall* TimingKit::createResume(AstNetlist* const netlistp) {
|
|
|
|
|
if (!m_resumeFuncp) {
|
|
|
|
|
if (m_lbs.empty()) return nullptr;
|
|
|
|
|
// Create global resume function
|
|
|
|
|
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
|
|
|
|
m_resumeFuncp = new AstCFunc{netlistp->fileline(), "_timing_resume", scopeTopp, ""};
|
|
|
|
|
m_resumeFuncp->dontCombine(true);
|
|
|
|
|
m_resumeFuncp->isLoose(true);
|
|
|
|
|
m_resumeFuncp->isConst(false);
|
|
|
|
|
m_resumeFuncp->declPrivate(true);
|
2022-09-15 19:43:56 +01:00
|
|
|
scopeTopp->addBlocksp(m_resumeFuncp);
|
2023-12-01 19:08:58 +01:00
|
|
|
|
2026-02-11 19:35:59 +01:00
|
|
|
for (const auto& p : m_lbs) {
|
|
|
|
|
AstActive* const activep = p.second;
|
|
|
|
|
activep->foreach([this](AstCMethodHard* const exprp) {
|
|
|
|
|
if (exprp->method() != VCMethod::SCHED_RESUME) return;
|
|
|
|
|
AstNodeExpr* const fromp = exprp->fromp();
|
|
|
|
|
if (VN_AS(fromp->dtypep(), BasicDType)->keyword()
|
|
|
|
|
!= VBasicDTypeKwd::TRIGGER_SCHEDULER) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
AstCMethodHard* const moveToResumep = new AstCMethodHard{
|
|
|
|
|
fromp->fileline(), fromp->cloneTree(false),
|
|
|
|
|
VCMethod::SCHED_MOVE_TO_RESUME_QUEUE,
|
|
|
|
|
exprp->pinsp() ? exprp->pinsp()->cloneTree(true) : nullptr};
|
|
|
|
|
moveToResumep->dtypeSetVoid();
|
|
|
|
|
m_resumeFuncp->addStmtsp(moveToResumep->makeStmt());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-01 19:08:58 +01:00
|
|
|
// Put all the timing actives in the resume function
|
2025-10-27 11:41:30 +01:00
|
|
|
AstIf* dlyShedIfp = nullptr;
|
2022-08-22 14:26:32 +02:00
|
|
|
for (auto& p : m_lbs) {
|
|
|
|
|
AstActive* const activep = p.second;
|
2026-02-16 03:55:55 +00:00
|
|
|
// Resume time delays last. For no particular reason other than
|
|
|
|
|
// that's what we used to do prior to proper #0 support.
|
2023-12-01 19:08:58 +01:00
|
|
|
AstVarRef* const schedrefp = VN_AS(
|
|
|
|
|
VN_AS(VN_AS(activep->stmtsp(), StmtExpr)->exprp(), CMethodHard)->fromp(), VarRef);
|
2025-10-27 11:41:30 +01:00
|
|
|
|
2026-02-11 19:35:59 +01:00
|
|
|
AstNode* const actionp = activep->stmtsp()->unlinkFrBackWithNext();
|
2023-12-01 19:08:58 +01:00
|
|
|
if (schedrefp->varScopep()->dtypep()->basicp()->isDelayScheduler()) {
|
2026-02-11 19:35:59 +01:00
|
|
|
dlyShedIfp = V3Sched::util::createIfFromSenTree(activep->sentreep());
|
|
|
|
|
dlyShedIfp->addThensp(actionp);
|
2025-10-27 11:41:30 +01:00
|
|
|
} else {
|
2026-02-11 19:35:59 +01:00
|
|
|
m_resumeFuncp->addStmtsp(actionp);
|
2023-12-01 19:08:58 +01:00
|
|
|
}
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2025-10-27 11:41:30 +01:00
|
|
|
if (dlyShedIfp) m_resumeFuncp->addStmtsp(dlyShedIfp);
|
|
|
|
|
|
|
|
|
|
// These are now spent, oispose of now empty AstActive instances
|
|
|
|
|
m_lbs.deleteActives();
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2022-10-12 10:19:21 +01:00
|
|
|
AstCCall* const callp = new AstCCall{m_resumeFuncp->fileline(), m_resumeFuncp};
|
|
|
|
|
callp->dtypeSetVoid();
|
|
|
|
|
return callp;
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
|
|
|
|
|
2026-03-27 23:14:18 -04:00
|
|
|
AstVarScope* TimingKit::getDelayScheduler(AstNetlist* const /*netlistp*/) {
|
2026-02-16 03:55:55 +00:00
|
|
|
for (auto& p : m_lbs) {
|
|
|
|
|
AstActive* const ap = p.second;
|
|
|
|
|
// TODO: this triple VN_AS expression is ridiculous
|
|
|
|
|
AstVarRef* const schedrefp
|
|
|
|
|
= VN_AS(VN_AS(VN_AS(ap->stmtsp(), StmtExpr)->exprp(), CMethodHard)->fromp(), VarRef);
|
|
|
|
|
AstVarScope* const vscp = schedrefp->varScopep();
|
|
|
|
|
if (vscp->dtypep()->basicp()->isDelayScheduler()) return vscp;
|
|
|
|
|
}
|
|
|
|
|
// None found. Design doesn't use any time delays
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-22 14:26:32 +02:00
|
|
|
//============================================================================
|
2026-02-11 19:35:59 +01:00
|
|
|
// Creates a timing ready call (if needed, else returns null)
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2026-02-11 19:35:59 +01:00
|
|
|
AstCCall* TimingKit::createReady(AstNetlist* const netlistp) {
|
|
|
|
|
if (!m_readyFuncp) {
|
2022-08-22 14:26:32 +02:00
|
|
|
for (auto& p : m_lbs) {
|
|
|
|
|
AstActive* const activep = p.second;
|
2022-10-12 10:19:21 +01:00
|
|
|
auto* const resumep = VN_AS(VN_AS(activep->stmtsp(), StmtExpr)->exprp(), CMethodHard);
|
2022-08-22 14:26:32 +02:00
|
|
|
UASSERT_OBJ(!resumep->nextp(), resumep, "Should be the only statement here");
|
|
|
|
|
AstVarScope* const schedulerp = VN_AS(resumep->fromp(), VarRef)->varScopep();
|
|
|
|
|
UASSERT_OBJ(schedulerp->dtypep()->basicp()->isDelayScheduler()
|
2022-10-22 14:05:39 +00:00
|
|
|
|| schedulerp->dtypep()->basicp()->isTriggerScheduler()
|
|
|
|
|
|| schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler(),
|
2022-08-22 14:26:32 +02:00
|
|
|
schedulerp, "Unexpected type");
|
|
|
|
|
if (!schedulerp->dtypep()->basicp()->isTriggerScheduler()) continue;
|
2026-02-11 19:35:59 +01:00
|
|
|
// Create the global ready function only if we have trigger schedulers
|
|
|
|
|
if (!m_readyFuncp) {
|
2022-08-22 14:26:32 +02:00
|
|
|
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
2026-02-11 19:35:59 +01:00
|
|
|
m_readyFuncp = new AstCFunc{netlistp->fileline(), "_timing_ready", scopeTopp, ""};
|
|
|
|
|
m_readyFuncp->dontCombine(true);
|
|
|
|
|
m_readyFuncp->isLoose(true);
|
|
|
|
|
m_readyFuncp->isConst(false);
|
|
|
|
|
m_readyFuncp->declPrivate(true);
|
|
|
|
|
scopeTopp->addBlocksp(m_readyFuncp);
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2025-10-26 16:41:49 +01:00
|
|
|
|
|
|
|
|
AstSenTree* const senTreep = activep->sentreep();
|
|
|
|
|
FileLine* const flp = senTreep->fileline();
|
|
|
|
|
|
|
|
|
|
// Create an 'AstIf' sensitive to the suspending triggers
|
2025-10-27 15:16:28 +00:00
|
|
|
AstIf* const ifp = V3Sched::util::createIfFromSenTree(senTreep);
|
2026-02-11 19:35:59 +01:00
|
|
|
m_readyFuncp->addStmtsp(ifp);
|
2025-10-26 16:41:49 +01:00
|
|
|
|
2026-02-11 19:35:59 +01:00
|
|
|
// Mark as ready the processes resumed on this sensitivity expression
|
2025-10-26 16:41:49 +01:00
|
|
|
AstVarRef* const refp = new AstVarRef{flp, schedulerp, VAccess::READWRITE};
|
2026-02-11 19:35:59 +01:00
|
|
|
AstCMethodHard* const callp = new AstCMethodHard{flp, refp, VCMethod::SCHED_READY};
|
2025-10-26 16:41:49 +01:00
|
|
|
callp->dtypeSetVoid();
|
|
|
|
|
if (resumep->pinsp()) callp->addPinsp(resumep->pinsp()->cloneTree(false));
|
2026-02-11 19:35:59 +01:00
|
|
|
ifp->addThensp(callp->makeStmt());
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2026-02-11 19:35:59 +01:00
|
|
|
// We still haven't created a ready function (no trigger schedulers), return null
|
|
|
|
|
if (!m_readyFuncp) return nullptr;
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2026-02-11 19:35:59 +01:00
|
|
|
AstCCall* const callp = new AstCCall{m_readyFuncp->fileline(), m_readyFuncp};
|
2022-10-12 10:19:21 +01:00
|
|
|
callp->dtypeSetVoid();
|
|
|
|
|
return callp;
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
// Creates the timing kit and marks variables written by suspendables
|
|
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
class AwaitVisitor final : public VNVisitor {
|
|
|
|
|
// NODE STATE
|
2026-08-11 14:18:18 +02:00
|
|
|
// AstSenTree::user1() -> bool. Set true if the sentree has been visited.
|
|
|
|
|
// AstCFunc::user1() -> AstUser1Allocator. See alocator below
|
2025-10-26 09:25:34 +00:00
|
|
|
const VNUser1InUse m_inuser1;
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
// STATE
|
|
|
|
|
bool m_inProcess = false; // Are we in a process?
|
|
|
|
|
bool m_gatherVars = false; // Should we gather vars in m_writtenBySuspendable?
|
|
|
|
|
AstScope* const m_scopeTopp; // Scope at the top
|
|
|
|
|
LogicByScope& m_lbs; // Timing resume actives
|
|
|
|
|
AstNodeStmt*& m_postUpdatesr; // Post updates for the trigger eval function
|
|
|
|
|
// Additional var sensitivities
|
|
|
|
|
std::map<const AstVarScope*, std::set<AstSenTree*>>& m_externalDomains;
|
2026-08-27 14:33:49 +02:00
|
|
|
std::unique_ptr<V3ClassGraph>
|
|
|
|
|
m_classGraphp; // class graph to get possibly called functions from a virtual call
|
2025-10-26 09:25:34 +00:00
|
|
|
std::set<AstSenTree*> m_processDomains; // Sentrees from the current process
|
|
|
|
|
// Variables written by suspendable processes
|
2026-08-11 14:18:18 +02:00
|
|
|
std::set<AstVarScope*> m_writtenBySuspendable;
|
|
|
|
|
struct CFuncCache final {
|
|
|
|
|
std::set<AstSenTree*> m_processDomains; // What shall be added to m_processDomains
|
|
|
|
|
std::set<AstVarScope*>
|
|
|
|
|
m_writtenBySuspendable; // What shall be added to m_writtenBySuspendable
|
|
|
|
|
std::set<AstCFunc*> m_includes; // CFuncs whose CFuncCache shall be included into this
|
|
|
|
|
// m_includes does not need to keep bool with m_gatherVars to know which cache shall be
|
|
|
|
|
// used since:
|
|
|
|
|
// Only possible transition in a graph is from `!m_gatherVars` to `m_gatherVars`
|
|
|
|
|
// (in visit(AstFork*) there is a only possible transition, visit(AstNodeProcedure*) is
|
|
|
|
|
// not considered since it does not occur during a graph traversal)
|
|
|
|
|
// therefore, if change of state occurs it is certain that the first node after transition
|
|
|
|
|
// won't be in visiting state
|
|
|
|
|
// therefore, every node in a m_includes shall take value of m_gatherVars under which
|
|
|
|
|
// current CFuncCache is
|
|
|
|
|
enum State : uint8_t {
|
|
|
|
|
UNINITIALIZED = 0, // Not initialized members are empty
|
|
|
|
|
VISITING, // Visiting - needed for breaking recursion
|
|
|
|
|
INITIALIZED, // Members contains correct values
|
|
|
|
|
} m_state // Current state of Cache
|
|
|
|
|
= UNINITIALIZED;
|
|
|
|
|
};
|
|
|
|
|
std::vector<const AstCFunc*> m_callStack; // Current callstack of AstCFuncs
|
|
|
|
|
// Caches how visiting the function with given value of m_gatherVars changes
|
|
|
|
|
// m_processDomains and m_writtenBySuspendable - only accessed from visit(AstCFunc* nodep)
|
|
|
|
|
AstUser1Allocator<AstCFunc, std::array<CFuncCache, 2>> m_cfuncsCache;
|
|
|
|
|
// Count uses of not inlined writes to signals in suspendables
|
|
|
|
|
VDouble0 m_notInlinedWritesInSuspendableUsage;
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
// METHODS
|
|
|
|
|
// Add arguments to a resume() call based on arguments in the suspending call
|
|
|
|
|
void addResumePins(AstCMethodHard* const resumep, AstNodeExpr* pinsp) {
|
|
|
|
|
AstCExpr* const exprp = VN_CAST(pinsp, CExpr);
|
|
|
|
|
AstText* const textp = VN_CAST(exprp->nodesp(), Text);
|
|
|
|
|
if (textp) {
|
|
|
|
|
// The first argument, vlProcess, isn't used by any of resume() methods, skip it
|
|
|
|
|
if ((pinsp = VN_CAST(pinsp->nextp(), NodeExpr))) {
|
2023-06-01 07:02:08 -07:00
|
|
|
resumep->addPinsp(pinsp->cloneTree(false));
|
|
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
} else {
|
|
|
|
|
resumep->addPinsp(pinsp->cloneTree(false));
|
2023-06-01 07:02:08 -07:00
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
}
|
2026-08-11 14:18:18 +02:00
|
|
|
void addCFuncCachedValues(const AstCFunc* const cfuncp,
|
|
|
|
|
std::unordered_set<const AstCFunc*>& visited) {
|
|
|
|
|
if (!visited.insert(cfuncp).second) return;
|
|
|
|
|
CFuncCache& value = m_cfuncsCache(cfuncp)[static_cast<size_t>(m_gatherVars)];
|
|
|
|
|
const size_t initialSize = m_writtenBySuspendable.size();
|
|
|
|
|
m_writtenBySuspendable.insert(value.m_writtenBySuspendable.begin(),
|
|
|
|
|
value.m_writtenBySuspendable.end());
|
|
|
|
|
m_notInlinedWritesInSuspendableUsage += m_writtenBySuspendable.size() - initialSize;
|
|
|
|
|
m_processDomains.insert(value.m_processDomains.begin(), value.m_processDomains.end());
|
|
|
|
|
for (const AstCFunc* const includedp : value.m_includes) {
|
|
|
|
|
addCFuncCachedValues(includedp, visited);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
// Create an active with a timing scheduler resume() call
|
|
|
|
|
void createResumeActive(AstCAwait* const awaitp) {
|
|
|
|
|
auto* const methodp = VN_AS(awaitp->exprp(), CMethodHard);
|
|
|
|
|
AstVarScope* const schedulerp = VN_AS(methodp->fromp(), VarRef)->varScopep();
|
|
|
|
|
AstSenTree* const sentreep = awaitp->sentreep();
|
|
|
|
|
FileLine* const flp = sentreep->fileline();
|
|
|
|
|
// Create a resume() call on the timing scheduler
|
|
|
|
|
auto* const resumep = new AstCMethodHard{
|
|
|
|
|
flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, VCMethod::SCHED_RESUME};
|
|
|
|
|
resumep->dtypeSetVoid();
|
|
|
|
|
if (schedulerp->dtypep()->basicp()->isTriggerScheduler()) {
|
|
|
|
|
UASSERT_OBJ(methodp->pinsp(), methodp,
|
|
|
|
|
"Trigger method should have pins from V3Timing");
|
2026-02-11 19:35:59 +01:00
|
|
|
// The first pin is the ready boolean, the rest (if any) should be debug info
|
2025-10-26 09:25:34 +00:00
|
|
|
// See V3Timing for details
|
|
|
|
|
if (AstNode* const dbginfop = methodp->pinsp()->nextp()) {
|
|
|
|
|
if (methodp->pinsp()) addResumePins(resumep, static_cast<AstNodeExpr*>(dbginfop));
|
2022-10-22 14:05:39 +00:00
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
} else if (schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler()) {
|
|
|
|
|
auto* const postp = resumep->cloneTree(false);
|
|
|
|
|
postp->method(VCMethod::SCHED_DO_POST_UPDATES);
|
|
|
|
|
m_postUpdatesr = AstNode::addNext(m_postUpdatesr, postp->makeStmt());
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2025-10-27 11:41:30 +01:00
|
|
|
// Put it in an active
|
|
|
|
|
AstActive* const activep = new AstActive{flp, "_timing", sentreep};
|
2025-10-26 09:25:34 +00:00
|
|
|
activep->addStmtsp(resumep->makeStmt());
|
|
|
|
|
m_lbs.emplace_back(m_scopeTopp, activep);
|
|
|
|
|
}
|
2026-08-11 14:18:18 +02:00
|
|
|
// Visit AstCFunc from a AstNodeCCall - this function is rather expensive to call therefore, it
|
|
|
|
|
// only shall be called when necessary
|
|
|
|
|
void visitCalledCFunc(AstCFunc* const nodep) {
|
|
|
|
|
if (!m_inProcess) return; // Skip if not in process - nothing would be added to any set
|
|
|
|
|
const size_t cacheKey = static_cast<size_t>(m_gatherVars);
|
|
|
|
|
CFuncCache& value = m_cfuncsCache(nodep)[cacheKey];
|
|
|
|
|
switch (value.m_state) {
|
|
|
|
|
case CFuncCache::UNINITIALIZED: {
|
|
|
|
|
// Save current state
|
|
|
|
|
VL_RESTORER_CLEAR(m_processDomains);
|
|
|
|
|
VL_RESTORER_CLEAR(m_writtenBySuspendable);
|
|
|
|
|
|
|
|
|
|
// Visit
|
|
|
|
|
value.m_state = CFuncCache::VISITING;
|
|
|
|
|
m_callStack.push_back(nodep);
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
m_callStack.pop_back();
|
|
|
|
|
value.m_state = CFuncCache::INITIALIZED;
|
|
|
|
|
|
|
|
|
|
// Save a cache
|
|
|
|
|
std::swap(m_processDomains, value.m_processDomains);
|
|
|
|
|
std::swap(m_writtenBySuspendable, value.m_writtenBySuspendable);
|
|
|
|
|
} break;
|
|
|
|
|
case CFuncCache::VISITING: {
|
|
|
|
|
for (size_t i = m_callStack.size() - 1; m_callStack.at(i) != nodep; --i) {
|
|
|
|
|
m_cfuncsCache(m_callStack[i])[cacheKey].m_includes.insert(nodep);
|
|
|
|
|
}
|
|
|
|
|
return; // Break recursion
|
|
|
|
|
}
|
|
|
|
|
case CFuncCache::INITIALIZED: break;
|
|
|
|
|
}
|
|
|
|
|
std::unordered_set<const AstCFunc*> visited;
|
|
|
|
|
addCFuncCachedValues(nodep, visited);
|
|
|
|
|
}
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
// VISITORS
|
|
|
|
|
void visit(AstNodeProcedure* const nodep) override {
|
|
|
|
|
UASSERT_OBJ(!m_inProcess && !m_gatherVars && m_processDomains.empty()
|
|
|
|
|
&& m_writtenBySuspendable.empty(),
|
|
|
|
|
nodep, "Process in process?");
|
|
|
|
|
m_inProcess = true;
|
|
|
|
|
m_gatherVars = nodep->isSuspendable(); // Only gather vars in a suspendable
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
for (AstVarScope* const vscp : m_writtenBySuspendable) {
|
|
|
|
|
m_externalDomains[vscp].insert(m_processDomains.begin(), m_processDomains.end());
|
|
|
|
|
vscp->varp()->setWrittenBySuspendable();
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
m_processDomains.clear();
|
|
|
|
|
m_writtenBySuspendable.clear();
|
|
|
|
|
m_inProcess = false;
|
|
|
|
|
m_gatherVars = false;
|
|
|
|
|
}
|
|
|
|
|
void visit(AstFork* nodep) override {
|
|
|
|
|
VL_RESTORER(m_gatherVars);
|
|
|
|
|
if (m_inProcess) m_gatherVars = true;
|
|
|
|
|
// If not in a process, we don't need to gather variables or domains
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstCAwait* nodep) override {
|
|
|
|
|
if (AstSenTree* const sentreep = nodep->sentreep()) {
|
|
|
|
|
if (!sentreep->user1SetOnce()) createResumeActive(nodep);
|
|
|
|
|
if (m_inProcess) m_processDomains.insert(sentreep);
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2026-08-11 14:18:18 +02:00
|
|
|
iterateChildren(nodep);
|
2025-10-26 09:25:34 +00:00
|
|
|
}
|
|
|
|
|
void visit(AstNodeVarRef* nodep) override {
|
2026-08-11 14:18:18 +02:00
|
|
|
if (m_gatherVars && nodep->access().isWriteOrRW() && !nodep->varp()->ignoreSchedWrite()) {
|
|
|
|
|
m_writtenBySuspendable.insert(nodep->varScopep());
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
}
|
2026-08-11 14:18:18 +02:00
|
|
|
void visit(AstNodeCCall* const nodep) override {
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
// We need to visit bodies of non-inlined functions
|
2026-08-27 14:33:49 +02:00
|
|
|
const auto& cfuncps = m_classGraphp->getCallPossibleCFuncs(nodep);
|
|
|
|
|
if (cfuncps.empty()) {
|
|
|
|
|
visitCalledCFunc(nodep->funcp());
|
|
|
|
|
} else {
|
|
|
|
|
for (AstCFunc* const cfuncp : cfuncps) visitCalledCFunc(cfuncp);
|
|
|
|
|
}
|
2026-08-11 14:18:18 +02:00
|
|
|
}
|
|
|
|
|
void visit(AstCFunc* const nodep) override {
|
|
|
|
|
const auto& value = m_cfuncsCache(nodep);
|
|
|
|
|
// Check whether it was already visited by visitCalledCFunc()
|
|
|
|
|
if (value[0].m_state != CFuncCache::UNINITIALIZED
|
|
|
|
|
|| value[1].m_state != CFuncCache::UNINITIALIZED) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
void visit(AstExprStmt* nodep) override { iterateChildren(nodep); }
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
//--------------------
|
|
|
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
public:
|
|
|
|
|
// CONSTRUCTORS
|
|
|
|
|
explicit AwaitVisitor(AstNetlist* nodep, LogicByScope& lbs, AstNodeStmt*& postUpdatesr,
|
|
|
|
|
std::map<const AstVarScope*, std::set<AstSenTree*>>& externalDomains)
|
|
|
|
|
: m_scopeTopp{nodep->topScopep()->scopep()}
|
|
|
|
|
, m_lbs{lbs}
|
|
|
|
|
, m_postUpdatesr{postUpdatesr}
|
2026-08-27 14:33:49 +02:00
|
|
|
, m_externalDomains{externalDomains}
|
|
|
|
|
, m_classGraphp{V3ClassGraph::build(nodep)} {
|
2025-10-26 09:25:34 +00:00
|
|
|
iterate(nodep);
|
|
|
|
|
}
|
2026-08-11 14:18:18 +02:00
|
|
|
~AwaitVisitor() override {
|
|
|
|
|
V3Stats::addStat("Scheduling, count of non-inlined signal writes in suspendables",
|
|
|
|
|
m_notInlinedWritesInSuspendableUsage);
|
|
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TimingKit prepareTiming(AstNetlist* const netlistp) {
|
|
|
|
|
if (!v3Global.usesTiming()) return {};
|
2022-08-22 14:26:32 +02:00
|
|
|
LogicByScope lbs;
|
2022-10-22 14:05:39 +00:00
|
|
|
AstNodeStmt* postUpdates = nullptr;
|
2022-08-22 14:26:32 +02:00
|
|
|
std::map<const AstVarScope*, std::set<AstSenTree*>> externalDomains;
|
2026-03-27 23:14:18 -04:00
|
|
|
{ AwaitVisitor{netlistp, lbs, postUpdates, externalDomains}; }
|
2022-10-22 14:05:39 +00:00
|
|
|
return {std::move(lbs), postUpdates, std::move(externalDomains)};
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
// Visits all forks and transforms their sub-statements into separate functions.
|
|
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
// Transform all forked processes into functions
|
|
|
|
|
class TransformForksVisitor final : public VNVisitor {
|
|
|
|
|
// NODE STATE
|
|
|
|
|
// AstVar::user1() -> bool. Set true if the variable was declared before the current fork.
|
|
|
|
|
const VNUser1InUse m_inuser1;
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
// STATE
|
|
|
|
|
bool m_inClass = false; // Are we in a class?
|
|
|
|
|
bool m_beginHasAwaits = false; // Does the current begin have awaits?
|
|
|
|
|
AstFork* m_forkp = nullptr; // Current fork
|
|
|
|
|
AstCFunc* m_funcp = nullptr; // Current function
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
// METHODS
|
|
|
|
|
// Remap local vars referenced by the given fork function
|
|
|
|
|
// TODO: We should only pass variables to the fork that are
|
|
|
|
|
// live in the fork body, but for that we need a proper data
|
|
|
|
|
// flow analysis framework which we don't have at the moment
|
|
|
|
|
void remapLocals(AstCFunc* const funcp, AstCCall* const callp) {
|
|
|
|
|
const VNUser2InUse user2InUse; // AstVarScope -> AstVarScope: var to remap to
|
|
|
|
|
funcp->foreach([&](AstNodeVarRef* refp) {
|
|
|
|
|
AstVar* const varp = refp->varp();
|
|
|
|
|
AstBasicDType* const dtypep = varp->dtypep()->basicp();
|
|
|
|
|
// If not a fork..join, copy. All write refs should've been handled by V3Fork
|
|
|
|
|
bool passByValue = !m_forkp->joinType().join();
|
|
|
|
|
if (!varp->isFuncLocal()) {
|
|
|
|
|
// Not func local. Its lifetime is longer than the forked process. Skip
|
|
|
|
|
return;
|
|
|
|
|
} else if (!varp->user1()) {
|
|
|
|
|
// Not declared before the fork. It cannot outlive the forked process
|
|
|
|
|
return;
|
|
|
|
|
} else if (dtypep && dtypep->isForkSync()) {
|
|
|
|
|
// We can just pass it by value to the new function
|
|
|
|
|
passByValue = true;
|
|
|
|
|
}
|
|
|
|
|
// Remap the reference
|
|
|
|
|
AstVarScope* const vscp = refp->varScopep();
|
|
|
|
|
if (!vscp->user2p()) {
|
|
|
|
|
// Clone the var to the new function
|
|
|
|
|
AstVar* const newvarp
|
|
|
|
|
= new AstVar{varp->fileline(), VVarType::BLOCKTEMP, varp->name(), varp};
|
|
|
|
|
newvarp->funcLocal(true);
|
|
|
|
|
newvarp->direction(passByValue ? VDirection::INPUT : VDirection::REF);
|
|
|
|
|
funcp->addArgsp(newvarp);
|
|
|
|
|
AstVarScope* const newvscp
|
|
|
|
|
= new AstVarScope{newvarp->fileline(), funcp->scopep(), newvarp};
|
|
|
|
|
funcp->scopep()->addVarsp(newvscp);
|
|
|
|
|
vscp->user2p(newvscp);
|
|
|
|
|
callp->addArgsp(new AstVarRef{refp->fileline(), vscp,
|
|
|
|
|
passByValue ? VAccess::READ : VAccess::READWRITE});
|
|
|
|
|
}
|
|
|
|
|
AstVarScope* const newvscp = VN_AS(vscp->user2p(), VarScope);
|
|
|
|
|
refp->varScopep(newvscp);
|
|
|
|
|
refp->varp(newvscp->varp());
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
// VISITORS
|
|
|
|
|
void visit(AstNodeModule* nodep) override {
|
|
|
|
|
VL_RESTORER(m_inClass);
|
|
|
|
|
m_inClass = VN_IS(nodep, Class);
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstCFunc* nodep) override {
|
|
|
|
|
VL_RESTORER(m_funcp);
|
|
|
|
|
m_funcp = nodep;
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstVar* nodep) override {
|
|
|
|
|
if (!m_forkp) nodep->user1(true);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstFork* nodep) override {
|
|
|
|
|
if (m_forkp) return; // Handle forks in forks after moving them to new functions
|
|
|
|
|
VL_RESTORER(m_forkp);
|
|
|
|
|
m_forkp = nodep;
|
|
|
|
|
iterateChildrenConst(nodep); // Const, so we don't iterate the calls twice
|
|
|
|
|
// Replace self with the function calls (no co_await, as we don't want the main
|
|
|
|
|
// process to suspend whenever any of the children do)
|
|
|
|
|
// V3Dead could have removed all statements from the fork, so guard against it
|
|
|
|
|
// Inline begins now that they are not needed
|
|
|
|
|
AstNode* resp = nullptr;
|
|
|
|
|
if (AstNode* const declsp = nodep->declsp()) {
|
|
|
|
|
resp = AstNode::addNext(resp, declsp->unlinkFrBackWithNext());
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
if (AstNode* const stmtsp = nodep->stmtsp()) {
|
|
|
|
|
resp = AstNode::addNext(resp, stmtsp->unlinkFrBackWithNext());
|
2022-09-05 16:19:19 +02:00
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
while (AstBegin* const beginp = nodep->forksp()) {
|
|
|
|
|
if (AstNode* const declsp = beginp->declsp()) {
|
2025-10-26 08:48:26 +00:00
|
|
|
resp = AstNode::addNext(resp, declsp->unlinkFrBackWithNext());
|
|
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
if (AstNode* const stmtsp = beginp->stmtsp()) {
|
2025-10-26 08:48:26 +00:00
|
|
|
resp = AstNode::addNext(resp, stmtsp->unlinkFrBackWithNext());
|
|
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
VL_DO_DANGLING(pushDeletep(beginp->unlinkFrBack()), beginp);
|
|
|
|
|
}
|
|
|
|
|
if (resp) {
|
|
|
|
|
nodep->replaceWith(resp);
|
|
|
|
|
} else {
|
|
|
|
|
nodep->unlinkFrBack();
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
2025-10-26 09:25:34 +00:00
|
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstBegin* nodep) override {
|
|
|
|
|
UASSERT_OBJ(m_forkp, nodep, "Begin outside of a fork");
|
|
|
|
|
// Start with children, so later we only find awaits that are actually in this begin
|
|
|
|
|
m_beginHasAwaits = false;
|
|
|
|
|
iterateChildrenConst(nodep);
|
|
|
|
|
if (!nodep->stmtsp()) return;
|
|
|
|
|
if (!m_beginHasAwaits && !nodep->needProcess()) return;
|
2023-08-30 17:59:25 -04:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
UASSERT_OBJ(!nodep->name().empty(), nodep, "Begin needs a name");
|
|
|
|
|
// Create a function to put this begin's statements in
|
|
|
|
|
FileLine* const flp = nodep->fileline();
|
|
|
|
|
AstCFunc* const newfuncp = new AstCFunc{flp, m_funcp->name() + "__" + nodep->name(),
|
|
|
|
|
m_funcp->scopep(), "VlCoroutine"};
|
2025-10-26 08:48:26 +00:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
m_funcp->addNextHere(newfuncp);
|
|
|
|
|
newfuncp->isLoose(m_funcp->isLoose());
|
|
|
|
|
newfuncp->slow(m_funcp->slow());
|
|
|
|
|
newfuncp->isConst(m_funcp->isConst());
|
|
|
|
|
newfuncp->declPrivate(true);
|
|
|
|
|
// Create the call to the function
|
|
|
|
|
AstCCall* const callp = new AstCCall{flp, newfuncp};
|
|
|
|
|
callp->dtypeSetVoid();
|
|
|
|
|
// If we're in a class, add a vlSymsp arg
|
|
|
|
|
if (m_inClass) {
|
|
|
|
|
newfuncp->addStmtsp(new AstCStmt{flp, "VL_KEEP_THIS;"});
|
|
|
|
|
newfuncp->argTypes(EmitCUtil::symClassVar());
|
|
|
|
|
callp->argTypes("vlSymsp");
|
|
|
|
|
}
|
|
|
|
|
// Put the begin's statements in the function
|
|
|
|
|
if (AstNode* const declsp = nodep->declsp()) {
|
|
|
|
|
newfuncp->addStmtsp(declsp->unlinkFrBackWithNext());
|
|
|
|
|
}
|
|
|
|
|
if (AstNode* const stmtsp = nodep->stmtsp()) {
|
|
|
|
|
newfuncp->addStmtsp(stmtsp->unlinkFrBackWithNext());
|
|
|
|
|
}
|
|
|
|
|
// Replace the body of the begin with a call to the newly created function
|
|
|
|
|
nodep->addStmtsp(callp->makeStmt());
|
|
|
|
|
// Propagate if needs process
|
2026-09-25 15:26:22 +02:00
|
|
|
if (nodep->needProcess()) newfuncp->setNeedProcess();
|
2025-10-26 09:25:34 +00:00
|
|
|
remapLocals(newfuncp, callp);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstCAwait* nodep) override {
|
|
|
|
|
m_beginHasAwaits = true;
|
|
|
|
|
iterateChildrenConst(nodep);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------
|
|
|
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
public:
|
|
|
|
|
// CONSTRUCTORS
|
|
|
|
|
explicit TransformForksVisitor(AstNetlist* nodep) { iterate(nodep); }
|
|
|
|
|
~TransformForksVisitor() override = default;
|
|
|
|
|
};
|
2022-08-22 14:26:32 +02:00
|
|
|
|
2025-10-26 09:25:34 +00:00
|
|
|
void transformForks(AstNetlist* const netlistp) {
|
|
|
|
|
if (!v3Global.usesTiming()) return;
|
2026-03-27 23:14:18 -04:00
|
|
|
{ TransformForksVisitor{netlistp}; }
|
2025-10-26 09:25:34 +00:00
|
|
|
V3Global::dumpCheckGlobalTree("transform_forks", 0, dumpTreeEitherLevel() >= 3);
|
2022-08-22 14:26:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace V3Sched
|