Merge 1ac45cb7e7 into 3afb7e1a21
This commit is contained in:
commit
95e7feef6d
|
|
@ -1889,6 +1889,26 @@ public:
|
||||||
bool isPure() override { return false; }
|
bool isPure() override { return false; }
|
||||||
bool isOutputter() override { return true; }
|
bool isOutputter() override { return true; }
|
||||||
};
|
};
|
||||||
|
class AstTaskLocalVar final : public AstNode {
|
||||||
|
// A task activation-local variable before loop unrolling is complete. The local variable is
|
||||||
|
// deliberately unscoped so cloning this node cannot invalidate an external AstVarScope. The
|
||||||
|
// temporary scoped variable carries references until V3Task materializes the local variable.
|
||||||
|
// @astgen op1 := varp : AstVar
|
||||||
|
// @astgen ptr := m_templateVscp : AstVarScope
|
||||||
|
public:
|
||||||
|
AstTaskLocalVar(FileLine* fl, AstVar* varp, AstVarScope* templateVscp)
|
||||||
|
: ASTGEN_SUPER_TaskLocalVar(fl)
|
||||||
|
, m_templateVscp{templateVscp} {
|
||||||
|
if (varp) this->varp(varp);
|
||||||
|
}
|
||||||
|
ASTGEN_MEMBERS_AstTaskLocalVar;
|
||||||
|
AstVarScope* templateVscp() const { return m_templateVscp; }
|
||||||
|
void dump(std::ostream& str) const override;
|
||||||
|
void dumpJson(std::ostream& str) const override;
|
||||||
|
bool sameNode(const AstNode* samep) const override {
|
||||||
|
return templateVscp() == VN_DBG_AS(samep, TaskLocalVar)->templateVscp();
|
||||||
|
}
|
||||||
|
};
|
||||||
class AstText final : public AstNode {
|
class AstText final : public AstNode {
|
||||||
// Represents a piece of text to be emitted into the output
|
// Represents a piece of text to be emitted into the output
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -3077,6 +3077,16 @@ void AstSystemCSection::dumpJson(std::ostream& str) const {
|
||||||
dumpJsonStr(str, "sectionType", sectionType().ascii());
|
dumpJsonStr(str, "sectionType", sectionType().ascii());
|
||||||
dumpJsonGen(str);
|
dumpJsonGen(str);
|
||||||
}
|
}
|
||||||
|
void AstTaskLocalVar::dump(std::ostream& str) const {
|
||||||
|
this->AstNode::dump(str);
|
||||||
|
str << " -> ";
|
||||||
|
if (templateVscp()) {
|
||||||
|
templateVscp()->dump(str);
|
||||||
|
} else {
|
||||||
|
str << "%E:UNLINKED";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void AstTaskLocalVar::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
|
||||||
void AstTypeTable::dump(std::ostream& str) const {
|
void AstTypeTable::dump(std::ostream& str) const {
|
||||||
this->AstNode::dump(str);
|
this->AstNode::dump(str);
|
||||||
for (int i = 0; i < static_cast<int>(VBasicDTypeKwd::_ENUM_MAX); ++i) {
|
for (int i = 0; i < static_cast<int>(VBasicDTypeKwd::_ENUM_MAX); ++i) {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,14 @@ VL_DEFINE_DEBUG_FUNCTIONS;
|
||||||
|
|
||||||
namespace V3Sched {
|
namespace V3Sched {
|
||||||
|
|
||||||
|
LogicByScope LogicByScope::clone() const {
|
||||||
|
LogicByScope result;
|
||||||
|
for (const auto& pair : *this) {
|
||||||
|
result.emplace_back(pair.first, VN_AS(util::cloneLogic(pair.second), Active));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
|
|
@ -928,6 +936,28 @@ cloneMapWithNewTriggerReferences(const std::unordered_map<const AstSenTree*, Ast
|
||||||
return std::unordered_map<const AstSenTree*, AstSenTree*>{pairs.begin(), pairs.end()};
|
return std::unordered_map<const AstSenTree*, AstSenTree*>{pairs.begin(), pairs.end()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void removeVarScopesForDeletedLogic(AstNetlist* netlistp) {
|
||||||
|
// Scheduler ordering deletes logic replicas after moving their statements. Remove only
|
||||||
|
// unreferenced scopes whose cloned local declaration was deleted with such a replica.
|
||||||
|
std::unordered_set<const AstVar*> liveVarps;
|
||||||
|
std::unordered_set<const AstVarScope*> referencedVscps;
|
||||||
|
std::vector<AstVarScope*> staleVscps;
|
||||||
|
|
||||||
|
netlistp->foreach([&](const AstVar* varp) { liveVarps.emplace(varp); });
|
||||||
|
netlistp->foreach([&](const AstNodeVarRef* refp) {
|
||||||
|
if (refp->varScopep()) referencedVscps.emplace(refp->varScopep());
|
||||||
|
});
|
||||||
|
netlistp->foreach([&](AstVarScope* vscp) {
|
||||||
|
if (!liveVarps.count(vscp->varp())) staleVscps.push_back(vscp);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (AstVarScope* const vscp : staleVscps) {
|
||||||
|
UASSERT_OBJ(!referencedVscps.count(vscp), vscp,
|
||||||
|
"Variable scope still referenced after its declaration was deleted");
|
||||||
|
VL_DO_DANGLING(vscp->unlinkFrBack()->deleteTree(), vscp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
// Top level entry-point to scheduling
|
// Top level entry-point to scheduling
|
||||||
|
|
||||||
|
|
@ -1204,6 +1234,7 @@ void schedule(AstNetlist* netlistp) {
|
||||||
|
|
||||||
// Step 16: Clean up
|
// Step 16: Clean up
|
||||||
netlistp->clearStlFirstIterationp();
|
netlistp->clearStlFirstIterationp();
|
||||||
|
removeVarScopesForDeletedLogic(netlistp);
|
||||||
|
|
||||||
// Haven't split static initializer yet
|
// Haven't split static initializer yet
|
||||||
util::splitCheck(staticp);
|
util::splitCheck(staticp);
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ namespace V3Sched {
|
||||||
namespace util {
|
namespace util {
|
||||||
using VarScopeSet = std::unordered_set<const AstVarScope*>;
|
using VarScopeSet = std::unordered_set<const AstVarScope*>;
|
||||||
|
|
||||||
|
AstNode* cloneLogic(AstNode* logicp);
|
||||||
|
|
||||||
inline bool isVlForceVec(const AstVarScope* vscp) {
|
inline bool isVlForceVec(const AstVarScope* vscp) {
|
||||||
const AstCDType* const dtypep = VN_CAST(vscp->dtypep()->skipRefp(), CDType);
|
const AstCDType* const dtypep = VN_CAST(vscp->dtypep()->skipRefp(), CDType);
|
||||||
return dtypep && dtypep->name() == "VlForceVec";
|
return dtypep && dtypep->name() == "VlForceVec";
|
||||||
|
|
@ -92,13 +94,7 @@ struct LogicByScope final : public std::vector<std::pair<AstScope*, AstActive*>>
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create copy, with the AstActives cloned
|
// Create copy, with the AstActives cloned
|
||||||
LogicByScope clone() const {
|
LogicByScope clone() const;
|
||||||
LogicByScope result;
|
|
||||||
for (const auto& pair : *this) {
|
|
||||||
result.emplace_back(pair.first, pair.second->cloneTree(false));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete actives (they should all be empty)
|
// Delete actives (they should all be empty)
|
||||||
void deleteActives() {
|
void deleteActives() {
|
||||||
|
|
|
||||||
|
|
@ -274,7 +274,7 @@ LogicReplicas replicate(Graph* graphp) {
|
||||||
for (V3GraphVertex& vtx : graphp->vertices()) {
|
for (V3GraphVertex& vtx : graphp->vertices()) {
|
||||||
if (SchedReplicateLogicVertex* const lvtxp = vtx.cast<SchedReplicateLogicVertex>()) {
|
if (SchedReplicateLogicVertex* const lvtxp = vtx.cast<SchedReplicateLogicVertex>()) {
|
||||||
const auto replicateTo = [&](LogicByScope& lbs) {
|
const auto replicateTo = [&](LogicByScope& lbs) {
|
||||||
lbs.add(lvtxp->scopep(), lvtxp->senTreep(), lvtxp->logicp()->cloneTree(false));
|
lbs.add(lvtxp->scopep(), lvtxp->senTreep(), util::cloneLogic(lvtxp->logicp()));
|
||||||
};
|
};
|
||||||
const uint8_t targetRegions = lvtxp->drivingRegions() & ~lvtxp->assignedRegion();
|
const uint8_t targetRegions = lvtxp->drivingRegions() & ~lvtxp->assignedRegion();
|
||||||
UASSERT(!lvtxp->senTreep()->hasClocked() || targetRegions == 0,
|
UASSERT(!lvtxp->senTreep()->hasClocked() || targetRegions == 0,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,32 @@ VL_DEFINE_DEBUG_FUNCTIONS;
|
||||||
namespace V3Sched {
|
namespace V3Sched {
|
||||||
namespace util {
|
namespace util {
|
||||||
|
|
||||||
|
AstNode* cloneLogic(AstNode* logicp) {
|
||||||
|
AstNode* const clonep = logicp->cloneTree(false);
|
||||||
|
std::map<AstVarScope*, AstVarScope*> clonedVscps;
|
||||||
|
clonep->foreach([&](AstNodeVarRef* refp) {
|
||||||
|
AstVarScope* const oldVscp = refp->varScopep();
|
||||||
|
// cloneTree relinks an in-tree local AstVar, but its out-of-tree AstVarScope still points
|
||||||
|
// at the original declaration. Give each cloned declaration an independent scope.
|
||||||
|
if (!oldVscp || oldVscp->varp() == refp->varp()) return;
|
||||||
|
|
||||||
|
const auto pair = clonedVscps.emplace(oldVscp, nullptr);
|
||||||
|
if (pair.second) {
|
||||||
|
AstVarScope* const newVscp
|
||||||
|
= new AstVarScope{refp->fileline(), oldVscp->scopep(), refp->varp()};
|
||||||
|
newVscp->trace(oldVscp->isTrace());
|
||||||
|
newVscp->optimizeLifePost(oldVscp->optimizeLifePost());
|
||||||
|
oldVscp->scopep()->addVarsp(newVscp);
|
||||||
|
pair.first->second = newVscp;
|
||||||
|
} else {
|
||||||
|
UASSERT_OBJ(pair.first->second->varp() == refp->varp(), refp,
|
||||||
|
"Cloned variable scope maps to multiple variables");
|
||||||
|
}
|
||||||
|
refp->varScopep(pair.first->second);
|
||||||
|
});
|
||||||
|
return clonep;
|
||||||
|
}
|
||||||
|
|
||||||
AstCFunc* makeSubFunction(AstNetlist* netlistp, const string& name, bool slow) {
|
AstCFunc* makeSubFunction(AstNetlist* netlistp, const string& name, bool slow) {
|
||||||
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
||||||
AstCFunc* const funcp = new AstCFunc{netlistp->fileline(), name, scopeTopp, ""};
|
AstCFunc* const funcp = new AstCFunc{netlistp->fileline(), name, scopeTopp, ""};
|
||||||
|
|
|
||||||
|
|
@ -522,9 +522,8 @@ public:
|
||||||
m_addAfter[color] = placeholderp;
|
m_addAfter[color] = placeholderp;
|
||||||
m_newBlocksp->push_back(alwaysp);
|
m_newBlocksp->push_back(alwaysp);
|
||||||
}
|
}
|
||||||
// Scan the body of the always. We'll handle if/else
|
// Scan the body of the always. We'll handle if/else specially; every other leaf belongs to
|
||||||
// specially, everything else is a leaf node that we can
|
// one color and can move into the corresponding split always block.
|
||||||
// just clone into one of the split always blocks.
|
|
||||||
iterateAndNextNull(m_origAlwaysp->stmtsp());
|
iterateAndNextNull(m_origAlwaysp->stmtsp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -547,12 +546,13 @@ protected:
|
||||||
// Each leaf must have a user3p
|
// Each leaf must have a user3p
|
||||||
UASSERT_OBJ(nodep->user3p(), nodep, "null user3p in V3Split leaf");
|
UASSERT_OBJ(nodep->user3p(), nodep, "null user3p in V3Split leaf");
|
||||||
|
|
||||||
// Clone the leaf into its new always block
|
// A leaf belongs to exactly one color, so move it into its new always block. Moving also
|
||||||
|
// preserves lexical declarations whose AstVarScopes are owned outside the leaf subtree.
|
||||||
const SplitLogicVertex* const vxp = reinterpret_cast<SplitLogicVertex*>(nodep->user3p());
|
const SplitLogicVertex* const vxp = reinterpret_cast<SplitLogicVertex*>(nodep->user3p());
|
||||||
const uint32_t color = vxp->color();
|
const uint32_t color = vxp->color();
|
||||||
AstNode* const clonedp = nodep->cloneTree(false);
|
nodep->unlinkFrBack();
|
||||||
m_addAfter[color]->addNextHere(clonedp);
|
m_addAfter[color]->addNextHere(nodep);
|
||||||
m_addAfter[color] = clonedp;
|
m_addAfter[color] = nodep;
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstNodeIf* nodep) override {
|
void visit(AstNodeIf* nodep) override {
|
||||||
|
|
|
||||||
224
src/V3Task.cpp
224
src/V3Task.cpp
|
|
@ -34,6 +34,7 @@
|
||||||
#include "V3Stats.h"
|
#include "V3Stats.h"
|
||||||
#include "V3UniqueNames.h"
|
#include "V3UniqueNames.h"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||||
|
|
@ -483,6 +484,45 @@ class TaskVisitor final : public VNVisitor {
|
||||||
return newvscp;
|
return newvscp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AstVarScope* createTaskLocalVarScope(AstVar* invarp, const string& name,
|
||||||
|
AstTaskLocalVar*& localDeclpr) {
|
||||||
|
AstVarScope* const templateVscp = createVarScope(invarp, name);
|
||||||
|
AstVar* const localVarp
|
||||||
|
= new AstVar{invarp->fileline(), VVarType::BLOCKTEMP, name, invarp};
|
||||||
|
localVarp->funcLocal(true);
|
||||||
|
localVarp->propagateAttrFrom(invarp);
|
||||||
|
localVarp->isInternal(true);
|
||||||
|
localDeclpr = new AstTaskLocalVar{localVarp->fileline(), localVarp, templateVscp};
|
||||||
|
return templateVscp;
|
||||||
|
}
|
||||||
|
AstVarScope* createPortVarScope(AstVar* invarp, const string& name, bool activationLocalPorts,
|
||||||
|
AstTaskLocalVar*& localDeclpr) {
|
||||||
|
localDeclpr = nullptr;
|
||||||
|
if (activationLocalPorts) return createTaskLocalVarScope(invarp, name, localDeclpr);
|
||||||
|
return createVarScope(invarp, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToFront(AstNode* const beginp, AstNode* const newp) const {
|
||||||
|
if (!newp) return;
|
||||||
|
if (AstNode* const afterp = beginp->nextp()) {
|
||||||
|
afterp->unlinkFrBackWithNext();
|
||||||
|
AstNode::addNext<AstNode, AstNode>(newp, afterp);
|
||||||
|
}
|
||||||
|
beginp->addNext(newp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isTaskActivationLocal(AstVar* const varp, const bool activationLocalPorts) {
|
||||||
|
return activationLocalPorts && !varp->direction().isAny() && varp->lifetime().isAutomatic()
|
||||||
|
&& (varp->isFuncLocal()
|
||||||
|
|| (varp->varType() == VVarType::BLOCKTEMP
|
||||||
|
&& VN_IS(varp->dtypep()->skipRefp(), ClassRefDType)));
|
||||||
|
}
|
||||||
|
static bool bodyNeedsActivationLocals(const AstNode* const bodyp) {
|
||||||
|
return bodyp && bodyp->existsAndNext([](const AstNode* const nodep) {
|
||||||
|
// A join_none child can outlive the task without itself containing a timing control.
|
||||||
|
return nodep->isTimingControl() || VN_IS(nodep, Fork);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Replace varrefs with new var pointer
|
// Replace varrefs with new var pointer
|
||||||
void relink(AstNode* nodep) {
|
void relink(AstNode* nodep) {
|
||||||
|
|
@ -552,8 +592,9 @@ class TaskVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void connectPort(AstVar* portp, AstArg* argp, const string& namePrefix, AstNode* beginp,
|
bool connectPort(AstVar* portp, AstArg* argp, const string& namePrefix, AstNode* beginp,
|
||||||
bool inlineTask) {
|
bool inlineTask, bool activationLocalPorts) {
|
||||||
|
bool needsCLocalScope = false;
|
||||||
AstNodeExpr* pinp = argp->exprp();
|
AstNodeExpr* pinp = argp->exprp();
|
||||||
if (inlineTask) {
|
if (inlineTask) {
|
||||||
portp->unlinkFrBack();
|
portp->unlinkFrBack();
|
||||||
|
|
@ -619,8 +660,10 @@ class TaskVisitor final : public VNVisitor {
|
||||||
} else if (portp->isInout()) {
|
} else if (portp->isInout()) {
|
||||||
// UINFOTREE(9, pinp, "", "pinrsize-");
|
// UINFOTREE(9, pinp, "", "pinrsize-");
|
||||||
|
|
||||||
|
AstTaskLocalVar* localDeclp = nullptr;
|
||||||
AstVarScope* const newvscp
|
AstVarScope* const newvscp
|
||||||
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
= createPortVarScope(portp, namePrefix + "__" + portp->shortName(),
|
||||||
|
activationLocalPorts, localDeclp);
|
||||||
portp->user2p(newvscp);
|
portp->user2p(newvscp);
|
||||||
if (!inlineTask) {
|
if (!inlineTask) {
|
||||||
pinp->replaceWith(
|
pinp->replaceWith(
|
||||||
|
|
@ -629,42 +672,47 @@ class TaskVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
// Put input assignment in FRONT of all other statements
|
// Put input assignment in FRONT of all other statements
|
||||||
AstAssign* const preassp = connectPortMakeInAssign(pinp, newvscp, true);
|
AstAssign* const preassp = connectPortMakeInAssign(pinp, newvscp, true);
|
||||||
if (AstNode* const afterp = beginp->nextp()) {
|
AstNode* const frontp
|
||||||
afterp->unlinkFrBackWithNext();
|
= AstNode::addNextNull<AstNode, AstNode>(localDeclp, preassp);
|
||||||
AstNode::addNext<AstNode, AstNode>(preassp, afterp);
|
addToFront(beginp, frontp);
|
||||||
}
|
|
||||||
beginp->addNext(preassp);
|
|
||||||
|
|
||||||
AstAssign* const postassp = connectPortMakeOutAssign(portp, pinp, newvscp, true);
|
AstAssign* const postassp = connectPortMakeOutAssign(portp, pinp, newvscp, true);
|
||||||
beginp->addNext(postassp);
|
beginp->addNext(postassp);
|
||||||
|
needsCLocalScope = localDeclp != nullptr;
|
||||||
// if (debug() >= 9) beginp->dumpTreeAndNext(cout, "-pinrsize-out- ");
|
// if (debug() >= 9) beginp->dumpTreeAndNext(cout, "-pinrsize-out- ");
|
||||||
} else if (portp->isWritable()) {
|
} else if (portp->isWritable()) {
|
||||||
// Even if it's referencing a varref, we still make a temporary
|
// Even if it's referencing a varref, we still make a temporary
|
||||||
// Else task(x,x,x) might produce incorrect results
|
// Else task(x,x,x) might produce incorrect results
|
||||||
|
AstTaskLocalVar* localDeclp = nullptr;
|
||||||
AstVarScope* const newvscp
|
AstVarScope* const newvscp
|
||||||
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
= createPortVarScope(portp, namePrefix + "__" + portp->shortName(),
|
||||||
|
activationLocalPorts, localDeclp);
|
||||||
portp->user2p(newvscp);
|
portp->user2p(newvscp);
|
||||||
if (!inlineTask) {
|
if (!inlineTask) {
|
||||||
pinp->replaceWith(new AstVarRef{newvscp->fileline(), newvscp, VAccess::WRITE});
|
pinp->replaceWith(new AstVarRef{newvscp->fileline(), newvscp, VAccess::WRITE});
|
||||||
pushDeletep(pinp); // Cloned by connectPortMakeOutAssign
|
pushDeletep(pinp); // Cloned by connectPortMakeOutAssign
|
||||||
}
|
}
|
||||||
|
addToFront(beginp, localDeclp);
|
||||||
AstAssign* const postassp = connectPortMakeOutAssign(portp, pinp, newvscp, false);
|
AstAssign* const postassp = connectPortMakeOutAssign(portp, pinp, newvscp, false);
|
||||||
// Put assignment BEHIND of all other statements
|
// Put assignment BEHIND of all other statements
|
||||||
beginp->addNext(postassp);
|
beginp->addNext(postassp);
|
||||||
|
needsCLocalScope = localDeclp != nullptr;
|
||||||
} else if (inlineTask && portp->isNonOutput()) {
|
} else if (inlineTask && portp->isNonOutput()) {
|
||||||
// Make input variable
|
// Make input variable
|
||||||
|
AstTaskLocalVar* localDeclp = nullptr;
|
||||||
AstVarScope* const newvscp
|
AstVarScope* const newvscp
|
||||||
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
= createPortVarScope(portp, namePrefix + "__" + portp->shortName(),
|
||||||
|
activationLocalPorts, localDeclp);
|
||||||
portp->user2p(newvscp);
|
portp->user2p(newvscp);
|
||||||
AstAssign* const preassp = connectPortMakeInAssign(pinp, newvscp, false);
|
AstAssign* const preassp = connectPortMakeInAssign(pinp, newvscp, false);
|
||||||
// Put assignment in FRONT of all other statements
|
// Put assignment in FRONT of all other statements
|
||||||
if (AstNode* const afterp = beginp->nextp()) {
|
AstNode* const frontp
|
||||||
afterp->unlinkFrBackWithNext();
|
= AstNode::addNextNull<AstNode, AstNode>(localDeclp, preassp);
|
||||||
AstNode::addNext<AstNode, AstNode>(preassp, afterp);
|
addToFront(beginp, frontp);
|
||||||
}
|
needsCLocalScope = localDeclp != nullptr;
|
||||||
beginp->addNext(preassp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return needsCLocalScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasRefArgument(AstNodeFTask* nodep) {
|
bool hasRefArgument(AstNodeFTask* nodep) {
|
||||||
|
|
@ -688,13 +736,20 @@ class TaskVisitor final : public VNVisitor {
|
||||||
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- newbegi: ");
|
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- newbegi: ");
|
||||||
//
|
//
|
||||||
// Create input variables
|
// Create input variables
|
||||||
|
// Task bodies that can suspend or fork need activation-local input temporaries. Inlined
|
||||||
|
// functions cannot suspend; keeping those temporaries as normal scoped temps avoids
|
||||||
|
// exposing AstCLocalScope to pre-timing constant/table simulation.
|
||||||
|
const bool activationLocalPorts
|
||||||
|
= !refp->taskp()->verilogFunction() && bodyNeedsActivationLocals(newbodysp);
|
||||||
|
bool needsCLocalScope = false;
|
||||||
AstNode::user2ClearTree();
|
AstNode::user2ClearTree();
|
||||||
{
|
{
|
||||||
const V3TaskConnects tconnects = V3Task::taskConnects(refp, beginp);
|
const V3TaskConnects tconnects = V3Task::taskConnects(refp, beginp);
|
||||||
for (const auto& itr : tconnects) {
|
for (const auto& itr : tconnects) {
|
||||||
AstVar* const portp = itr.first;
|
AstVar* const portp = itr.first;
|
||||||
AstArg* const argp = itr.second;
|
AstArg* const argp = itr.second;
|
||||||
connectPort(portp, argp, namePrefix, beginp, true);
|
needsCLocalScope
|
||||||
|
|= connectPort(portp, argp, namePrefix, beginp, true, activationLocalPorts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UASSERT_OBJ(!refp->argsp(), refp, "Arg wasn't removed by above loop");
|
UASSERT_OBJ(!refp->argsp(), refp, "Arg wasn't removed by above loop");
|
||||||
|
|
@ -705,6 +760,29 @@ class TaskVisitor final : public VNVisitor {
|
||||||
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
|
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
|
||||||
// Any I/O variables that fell out of above loop were already linked
|
// Any I/O variables that fell out of above loop were already linked
|
||||||
if (!portp->user2p()) {
|
if (!portp->user2p()) {
|
||||||
|
// Preserve automatic locals introduced by earlier passes inside the
|
||||||
|
// inlined task body so each activation keeps its own state.
|
||||||
|
if (isTaskActivationLocal(portp, activationLocalPorts)) {
|
||||||
|
const string localName = namePrefix + "__" + portp->shortName();
|
||||||
|
portp->name(localName);
|
||||||
|
portp->funcLocal(true);
|
||||||
|
AstVarScope* const templateVscp = createVarScope(portp, localName);
|
||||||
|
portp->user2p(templateVscp);
|
||||||
|
AstTaskLocalVar* const localDeclp
|
||||||
|
= new AstTaskLocalVar{portp->fileline(), nullptr, templateVscp};
|
||||||
|
portp->replaceWith(localDeclp);
|
||||||
|
localDeclp->varp(portp);
|
||||||
|
if (portp->needsCReset() && !portp->valuep()) {
|
||||||
|
// Reset automatic var to its default on each invocation of task.
|
||||||
|
AstNode* const crstp = new AstAssign{
|
||||||
|
portp->fileline(),
|
||||||
|
new AstVarRef{portp->fileline(), portp, VAccess::WRITE},
|
||||||
|
new AstCReset{portp->fileline(), portp, false}};
|
||||||
|
localDeclp->addNextHere(crstp);
|
||||||
|
}
|
||||||
|
needsCLocalScope = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Move it to a new localized variable
|
// Move it to a new localized variable
|
||||||
AstVarScope* const localVscp
|
AstVarScope* const localVscp
|
||||||
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
||||||
|
|
@ -734,7 +812,7 @@ class TaskVisitor final : public VNVisitor {
|
||||||
relink(beginp);
|
relink(beginp);
|
||||||
//
|
//
|
||||||
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- iotask: ");
|
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- iotask: ");
|
||||||
return beginp;
|
return needsCLocalScope ? new AstCLocalScope{refp->fileline(), beginp} : beginp;
|
||||||
}
|
}
|
||||||
|
|
||||||
AstNode* createNonInlinedFTask(AstNodeFTaskRef* refp, const string& namePrefix,
|
AstNode* createNonInlinedFTask(AstNodeFTaskRef* refp, const string& namePrefix,
|
||||||
|
|
@ -769,13 +847,20 @@ class TaskVisitor final : public VNVisitor {
|
||||||
ccallp->superReference(taskRefp->superReference());
|
ccallp->superReference(taskRefp->superReference());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert complicated outputs to temp signals
|
// Convert complicated outputs to temp signals. Task calls that can suspend or fork need
|
||||||
|
// staging temps in the caller activation. Non-inlined functions cannot suspend; keep their
|
||||||
|
// temps as normal scoped temps to avoid exposing AstCLocalScope to earlier function/table
|
||||||
|
// simulation.
|
||||||
|
bool needsCLocalScope = false;
|
||||||
|
const bool activationLocalPorts
|
||||||
|
= !refp->taskp()->verilogFunction() && bodyNeedsActivationLocals(cfuncp->stmtsp());
|
||||||
{
|
{
|
||||||
const V3TaskConnects tconnects = V3Task::taskConnects(refp, refp->taskp()->stmtsp());
|
const V3TaskConnects tconnects = V3Task::taskConnects(refp, refp->taskp()->stmtsp());
|
||||||
for (const auto& itr : tconnects) {
|
for (const auto& itr : tconnects) {
|
||||||
AstVar* const portp = itr.first;
|
AstVar* const portp = itr.first;
|
||||||
AstArg* const argp = itr.second;
|
AstArg* const argp = itr.second;
|
||||||
connectPort(portp, argp, namePrefix, beginp, false);
|
needsCLocalScope
|
||||||
|
|= connectPort(portp, argp, namePrefix, beginp, false, activationLocalPorts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// First argument is symbol table, then output if a function
|
// First argument is symbol table, then output if a function
|
||||||
|
|
@ -805,7 +890,7 @@ class TaskVisitor final : public VNVisitor {
|
||||||
if (outvscp) ccallp->addArgsp(new AstVarRef{refp->fileline(), outvscp, VAccess::WRITE});
|
if (outvscp) ccallp->addArgsp(new AstVarRef{refp->fileline(), outvscp, VAccess::WRITE});
|
||||||
|
|
||||||
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- nitask: ");
|
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- nitask: ");
|
||||||
return beginp;
|
return needsCLocalScope ? new AstCLocalScope{refp->fileline(), beginp} : beginp;
|
||||||
}
|
}
|
||||||
|
|
||||||
string dpiSignature(AstNodeFTask* nodep, AstVar* rtnvarp) const {
|
string dpiSignature(AstNodeFTask* nodep, AstVar* rtnvarp) const {
|
||||||
|
|
@ -1879,6 +1964,99 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//######################################################################
|
||||||
|
// Materialize task activation-local variables after loop unrolling
|
||||||
|
|
||||||
|
using TaskLocalVarScopeMap = std::map<AstVarScope*, AstVarScope*>;
|
||||||
|
|
||||||
|
class TaskLocalDeclVisitor final : public VNVisitor {
|
||||||
|
std::vector<AstTaskLocalVar*> m_declps;
|
||||||
|
|
||||||
|
void visit(AstCLocalScope*) override {
|
||||||
|
// A nested C-local scope is a separate task activation.
|
||||||
|
}
|
||||||
|
void visit(AstTaskLocalVar* nodep) override { m_declps.push_back(nodep); }
|
||||||
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TaskLocalDeclVisitor(AstNode* nodep) { iterateAndNextNull(nodep); }
|
||||||
|
const std::vector<AstTaskLocalVar*>& declps() const { return m_declps; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class TaskLocalRefVisitor final : public VNVisitor {
|
||||||
|
const TaskLocalVarScopeMap& m_replacements;
|
||||||
|
|
||||||
|
void visit(AstCLocalScope* nodep) override {
|
||||||
|
// A nested activation can capture variables from this outer activation. Its own locals
|
||||||
|
// have different template scopes, so only references to this replacement map are changed.
|
||||||
|
iterateChildren(nodep);
|
||||||
|
}
|
||||||
|
void visit(AstVarRef* nodep) override {
|
||||||
|
const auto it = m_replacements.find(nodep->varScopep());
|
||||||
|
if (it == m_replacements.end()) return;
|
||||||
|
nodep->varScopep(it->second);
|
||||||
|
nodep->varp(it->second->varp());
|
||||||
|
}
|
||||||
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||||
|
|
||||||
|
public:
|
||||||
|
TaskLocalRefVisitor(AstNode* nodep, const TaskLocalVarScopeMap& replacements)
|
||||||
|
: m_replacements{replacements} {
|
||||||
|
iterateAndNextNull(nodep);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TaskLocalizeVisitor final : public VNVisitor {
|
||||||
|
std::set<AstVarScope*> m_templateVscps;
|
||||||
|
AstCLocalScope* m_localScopep = nullptr;
|
||||||
|
|
||||||
|
void visit(AstNetlist* nodep) override {
|
||||||
|
iterateChildren(nodep);
|
||||||
|
|
||||||
|
std::set<AstVar*> templateVarps;
|
||||||
|
for (AstVarScope* const vscp : m_templateVscps) {
|
||||||
|
templateVarps.insert(vscp->varp());
|
||||||
|
VL_DO_DANGLING(pushDeletep(vscp->unlinkFrBack()), vscp);
|
||||||
|
}
|
||||||
|
for (AstVar* const varp : templateVarps) {
|
||||||
|
VL_DO_DANGLING(pushDeletep(varp->unlinkFrBack()), varp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void visit(AstCLocalScope* nodep) override {
|
||||||
|
VL_RESTORER(m_localScopep);
|
||||||
|
m_localScopep = nodep;
|
||||||
|
|
||||||
|
// Process nested task activations before replacing declarations in this one.
|
||||||
|
iterateChildren(nodep);
|
||||||
|
|
||||||
|
const TaskLocalDeclVisitor declVisitor{nodep->stmtsp()};
|
||||||
|
TaskLocalVarScopeMap replacements;
|
||||||
|
for (AstTaskLocalVar* const declp : declVisitor.declps()) {
|
||||||
|
AstVarScope* const templateVscp = declp->templateVscp();
|
||||||
|
UASSERT_OBJ(templateVscp, declp, "Task-local declaration has no template scope");
|
||||||
|
AstVar* const localVarp = declp->varp();
|
||||||
|
localVarp->unlinkFrBack();
|
||||||
|
UASSERT_OBJ(localVarp->isFuncLocal(), localVarp, "Task-local variable is not local");
|
||||||
|
AstVarScope* const localVscp
|
||||||
|
= new AstVarScope{localVarp->fileline(), templateVscp->scopep(), localVarp};
|
||||||
|
templateVscp->scopep()->addVarsp(localVscp);
|
||||||
|
UASSERT_OBJ(replacements.emplace(templateVscp, localVscp).second, declp,
|
||||||
|
"Duplicate task-local template in one activation");
|
||||||
|
m_templateVscps.insert(templateVscp);
|
||||||
|
declp->replaceWith(localVarp);
|
||||||
|
VL_DO_DANGLING(pushDeletep(declp), declp);
|
||||||
|
}
|
||||||
|
if (!replacements.empty()) TaskLocalRefVisitor{nodep->stmtsp(), replacements};
|
||||||
|
}
|
||||||
|
void visit(AstTaskLocalVar* nodep) override {
|
||||||
|
UASSERT_OBJ(m_localScopep, nodep, "Task-local declaration is outside a C-local scope");
|
||||||
|
}
|
||||||
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TaskLocalizeVisitor(AstNetlist* nodep) { iterate(nodep); }
|
||||||
|
};
|
||||||
|
|
||||||
//######################################################################
|
//######################################################################
|
||||||
// Task class functions
|
// Task class functions
|
||||||
|
|
||||||
|
|
@ -2300,3 +2478,9 @@ void V3Task::taskAll(AstNetlist* nodep) {
|
||||||
} // Destruct before checking
|
} // Destruct before checking
|
||||||
V3Global::dumpCheckGlobalTree("task", 0, dumpTreeEitherLevel() >= 3);
|
V3Global::dumpCheckGlobalTree("task", 0, dumpTreeEitherLevel() >= 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void V3Task::taskLocalizeAll(AstNetlist* nodep) {
|
||||||
|
UINFO(2, __FUNCTION__ << ":");
|
||||||
|
{ const TaskLocalizeVisitor visitor{nodep}; }
|
||||||
|
V3Global::dumpCheckGlobalTree("tasklocal", 0, dumpTreeEitherLevel() >= 3);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ class V3Task final {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void taskAll(AstNetlist* nodep) VL_MT_DISABLED;
|
static void taskAll(AstNetlist* nodep) VL_MT_DISABLED;
|
||||||
|
static void taskLocalizeAll(AstNetlist* nodep) VL_MT_DISABLED;
|
||||||
/// Return vector of [port, pin-connects-to] (SLOW)
|
/// Return vector of [port, pin-connects-to] (SLOW)
|
||||||
static V3TaskConnects taskConnects(AstNodeFTaskRef* nodep, AstNode* taskStmtsp,
|
static V3TaskConnects taskConnects(AstNodeFTaskRef* nodep, AstNode* taskStmtsp,
|
||||||
V3TaskConnectState* statep = nullptr,
|
V3TaskConnectState* statep = nullptr,
|
||||||
|
|
|
||||||
|
|
@ -478,6 +478,7 @@ class TimingControlVisitor final : public VNVisitor {
|
||||||
AstScope* m_scopep = nullptr; // Current scope
|
AstScope* m_scopep = nullptr; // Current scope
|
||||||
AstActive* m_activep = nullptr; // Current active
|
AstActive* m_activep = nullptr; // Current active
|
||||||
AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Begin we're under
|
AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Begin we're under
|
||||||
|
AstNode* m_varInsertp = nullptr; // Nearest node that owns function-local variables
|
||||||
bool m_hasProcess = false; // True if current scope has a VlProcess handle available
|
bool m_hasProcess = false; // True if current scope has a VlProcess handle available
|
||||||
int m_forkCnt = 0; // Number of forks inside a module
|
int m_forkCnt = 0; // Number of forks inside a module
|
||||||
bool m_underJumpBlock = false; // True if we are inside of a jump-block
|
bool m_underJumpBlock = false; // True if we are inside of a jump-block
|
||||||
|
|
@ -806,8 +807,8 @@ class TimingControlVisitor final : public VNVisitor {
|
||||||
forkp->addNextHere(new AstCAwait{flp, joinp});
|
forkp->addNextHere(new AstCAwait{flp, joinp});
|
||||||
}
|
}
|
||||||
|
|
||||||
// `procp` shall be a NodeProcedure/CFunc/Begin and within it vars from `varsp` will be placed.
|
// `procp` shall be a NodeProcedure/CFunc/Begin/CLocalScope and within it vars from `varsp`
|
||||||
// `varsp` vector of vars which shall be localized.
|
// will be placed. `varsp` vector of vars which shall be localized.
|
||||||
static void localizeVars(AstNode* const procp, const std::vector<AstVar*>& varsp) {
|
static void localizeVars(AstNode* const procp, const std::vector<AstVar*>& varsp) {
|
||||||
UASSERT(procp, "procp is nullptr");
|
UASSERT(procp, "procp is nullptr");
|
||||||
AstNode* firstStmtp;
|
AstNode* firstStmtp;
|
||||||
|
|
@ -824,15 +825,19 @@ class TimingControlVisitor final : public VNVisitor {
|
||||||
firstStmtp = cfuncp->varsp();
|
firstStmtp = cfuncp->varsp();
|
||||||
} else if (AstBegin* const beginp = VN_CAST(procp, Begin)) {
|
} else if (AstBegin* const beginp = VN_CAST(procp, Begin)) {
|
||||||
firstStmtp = beginp->stmtsp();
|
firstStmtp = beginp->stmtsp();
|
||||||
|
} else if (AstCLocalScope* const localScopep = VN_CAST(procp, CLocalScope)) {
|
||||||
|
firstStmtp = localScopep->stmtsp();
|
||||||
} else {
|
} else {
|
||||||
procp->v3fatalSrc(
|
procp->v3fatalSrc(
|
||||||
procp->prettyNameQ()
|
procp->prettyNameQ()
|
||||||
<< " is not of an expected type NodeProcedure/CFunc/Begin instead it is: "
|
<< " is not of an expected type NodeProcedure/CFunc/Begin/CLocalScope instead it "
|
||||||
|
"is: "
|
||||||
<< procp->prettyTypeName());
|
<< procp->prettyTypeName());
|
||||||
}
|
}
|
||||||
UASSERT_OBJ(firstStmtp, procp,
|
UASSERT_OBJ(firstStmtp, procp,
|
||||||
procp->prettyNameQ() << " has no non-var statement. 'localizeVars()' is ment "
|
procp->prettyNameQ()
|
||||||
"to be called on non-empty NodeProcedure/CFunc/Begin");
|
<< " has no non-var statement. 'localizeVars()' is meant to be called on "
|
||||||
|
"non-empty NodeProcedure/CFunc/Begin/CLocalScope");
|
||||||
for (AstVar* const varp : varsp) {
|
for (AstVar* const varp : varsp) {
|
||||||
varp->funcLocal(true);
|
varp->funcLocal(true);
|
||||||
firstStmtp->addHereThisAsNext(varp->unlinkFrBack());
|
firstStmtp->addHereThisAsNext(varp->unlinkFrBack());
|
||||||
|
|
@ -889,8 +894,10 @@ class TimingControlVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
void visit(AstNodeProcedure* nodep) override {
|
void visit(AstNodeProcedure* nodep) override {
|
||||||
VL_RESTORER(m_procp);
|
VL_RESTORER(m_procp);
|
||||||
|
VL_RESTORER(m_varInsertp);
|
||||||
VL_RESTORER(m_hasProcess);
|
VL_RESTORER(m_hasProcess);
|
||||||
m_procp = nodep;
|
m_procp = nodep;
|
||||||
|
m_varInsertp = nodep;
|
||||||
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
|
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
|
||||||
VL_RESTORER(m_underProcedure);
|
VL_RESTORER(m_underProcedure);
|
||||||
m_underProcedure = true;
|
m_underProcedure = true;
|
||||||
|
|
@ -913,8 +920,10 @@ class TimingControlVisitor final : public VNVisitor {
|
||||||
void visit(AstAlways* nodep) override {
|
void visit(AstAlways* nodep) override {
|
||||||
if (nodep->user1SetOnce()) return;
|
if (nodep->user1SetOnce()) return;
|
||||||
VL_RESTORER(m_procp);
|
VL_RESTORER(m_procp);
|
||||||
|
VL_RESTORER(m_varInsertp);
|
||||||
VL_RESTORER(m_hasProcess);
|
VL_RESTORER(m_hasProcess);
|
||||||
m_procp = nodep;
|
m_procp = nodep;
|
||||||
|
m_varInsertp = nodep;
|
||||||
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
|
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
|
||||||
VL_RESTORER(m_underProcedure);
|
VL_RESTORER(m_underProcedure);
|
||||||
m_underProcedure = true;
|
m_underProcedure = true;
|
||||||
|
|
@ -947,8 +956,10 @@ class TimingControlVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
void visit(AstCFunc* nodep) override {
|
void visit(AstCFunc* nodep) override {
|
||||||
VL_RESTORER(m_procp);
|
VL_RESTORER(m_procp);
|
||||||
|
VL_RESTORER(m_varInsertp);
|
||||||
VL_RESTORER(m_hasProcess);
|
VL_RESTORER(m_hasProcess);
|
||||||
m_procp = nodep;
|
m_procp = nodep;
|
||||||
|
m_varInsertp = nodep;
|
||||||
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
|
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
|
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
|
||||||
|
|
@ -1101,7 +1112,7 @@ class TimingControlVisitor final : public VNVisitor {
|
||||||
m_senExprBuilderp->build(sentreep).first};
|
m_senExprBuilderp->build(sentreep).first};
|
||||||
// Get the SenExprBuilder results
|
// Get the SenExprBuilder results
|
||||||
const SenExprBuilder::Results senResults = m_senExprBuilderp->getAndClearResults();
|
const SenExprBuilder::Results senResults = m_senExprBuilderp->getAndClearResults();
|
||||||
localizeVars(m_procp, senResults.m_vars);
|
localizeVars(m_varInsertp, senResults.m_vars);
|
||||||
// If post updates are destructive (e.g. clearFired on events), perform a
|
// If post updates are destructive (e.g. clearFired on events), perform a
|
||||||
// conservative pre-clear once before entering the wait loop so stale state from a
|
// conservative pre-clear once before entering the wait loop so stale state from a
|
||||||
// previous wait does not cause an immediate false-positive trigger.
|
// previous wait does not cause an immediate false-positive trigger.
|
||||||
|
|
@ -1419,12 +1430,19 @@ class TimingControlVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
void visit(AstBegin* nodep) override {
|
void visit(AstBegin* nodep) override {
|
||||||
VL_RESTORER(m_procp);
|
VL_RESTORER(m_procp);
|
||||||
|
VL_RESTORER(m_varInsertp);
|
||||||
VL_RESTORER(m_hasProcess);
|
VL_RESTORER(m_hasProcess);
|
||||||
m_hasProcess |= hasFlags(nodep, T_HAS_PROC);
|
m_hasProcess |= hasFlags(nodep, T_HAS_PROC);
|
||||||
m_procp = nodep;
|
m_procp = nodep;
|
||||||
|
m_varInsertp = nodep;
|
||||||
if (m_hasProcess) nodep->setNeedProcess();
|
if (m_hasProcess) nodep->setNeedProcess();
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
|
void visit(AstCLocalScope* nodep) override {
|
||||||
|
VL_RESTORER(m_varInsertp);
|
||||||
|
m_varInsertp = nodep;
|
||||||
|
iterateChildren(nodep);
|
||||||
|
}
|
||||||
void visit(AstFork* nodep) override {
|
void visit(AstFork* nodep) override {
|
||||||
if (nodep->user1SetOnce()) return;
|
if (nodep->user1SetOnce()) return;
|
||||||
UINFO(9, "control-visit " << nodep);
|
UINFO(9, "control-visit " << nodep);
|
||||||
|
|
|
||||||
|
|
@ -397,6 +397,9 @@ static void process() {
|
||||||
// Loop unrolling
|
// Loop unrolling
|
||||||
V3Unroll::unrollAll(v3Global.rootp());
|
V3Unroll::unrollAll(v3Global.rootp());
|
||||||
|
|
||||||
|
// Materialize task activation locals after statement-cloning loop transforms.
|
||||||
|
V3Task::taskLocalizeAll(v3Global.rootp());
|
||||||
|
|
||||||
// Expand slices of arrays
|
// Expand slices of arrays
|
||||||
V3Slice::sliceAll(v3Global.rootp());
|
V3Slice::sliceAll(v3Global.rootp());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ test.scenarios('simulator')
|
||||||
test.compile(verilator_flags2=["--stats"])
|
test.compile(verilator_flags2=["--stats"])
|
||||||
|
|
||||||
if test.vlt_all:
|
if test.vlt_all:
|
||||||
test.file_grep(test.stats, r'Optimizations, Split always\s+(\d+)', 4)
|
test.file_grep(test.stats, r'Optimizations, Split always\s+(\d+)', 6)
|
||||||
|
|
||||||
test.execute()
|
test.execute()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,24 @@ module t (
|
||||||
l_split_1 <= l_split_2 | m_din;
|
l_split_1 <= l_split_2 | m_din;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
task automatic copy_task(input logic [15:0] value, output logic [15:0] result);
|
||||||
|
result = value;
|
||||||
|
endtask
|
||||||
|
|
||||||
|
reg [15:0] t_split_1, t_split_2;
|
||||||
|
always @( /*AS*/ m_din) begin
|
||||||
|
copy_task(m_din, t_split_1);
|
||||||
|
t_split_2 = ~m_din;
|
||||||
|
end
|
||||||
|
|
||||||
|
reg [15:0] n_split_1, n_split_2;
|
||||||
|
always @( /*AS*/ m_din) begin
|
||||||
|
for (int i = 0; i < m_din[15]; ++i) begin
|
||||||
|
copy_task(m_din, n_split_1);
|
||||||
|
end
|
||||||
|
n_split_2 = ~m_din;
|
||||||
|
end
|
||||||
|
|
||||||
// (The checker block is an exception, it won't split.)
|
// (The checker block is an exception, it won't split.)
|
||||||
always @(posedge clk) begin
|
always @(posedge clk) begin
|
||||||
if (cyc != 0) begin
|
if (cyc != 0) begin
|
||||||
|
|
@ -68,18 +86,24 @@ module t (
|
||||||
m_din <= 16'he11e;
|
m_din <= 16'he11e;
|
||||||
//$write(" A %x %x\n", a_split_1, a_split_2);
|
//$write(" A %x %x\n", a_split_1, a_split_2);
|
||||||
if (!(a_split_1 == 16'hfeed && a_split_2 == 16'hfeed)) $stop;
|
if (!(a_split_1 == 16'hfeed && a_split_2 == 16'hfeed)) $stop;
|
||||||
|
if (!(t_split_1 == 16'hfeed && t_split_2 == 16'h0112)) $stop;
|
||||||
|
if (!(n_split_1 == 16'hfeed && n_split_2 == 16'h0112)) $stop;
|
||||||
if (!(d_split_1 == 16'h0112 && d_split_2 == 16'h0112)) $stop;
|
if (!(d_split_1 == 16'h0112 && d_split_2 == 16'h0112)) $stop;
|
||||||
if (!(h_split_1 == 16'hfeed && h_split_2 == 16'h0112)) $stop;
|
if (!(h_split_1 == 16'hfeed && h_split_2 == 16'h0112)) $stop;
|
||||||
end
|
end
|
||||||
if (cyc == 5) begin
|
if (cyc == 5) begin
|
||||||
m_din <= 16'he22e;
|
m_din <= 16'he22e;
|
||||||
if (!(a_split_1 == 16'he11e && a_split_2 == 16'he11e)) $stop;
|
if (!(a_split_1 == 16'he11e && a_split_2 == 16'he11e)) $stop;
|
||||||
|
if (!(t_split_1 == 16'he11e && t_split_2 == 16'h1ee1)) $stop;
|
||||||
|
if (!(n_split_1 == 16'he11e && n_split_2 == 16'h1ee1)) $stop;
|
||||||
if (!(d_split_1 == 16'h0112 && d_split_2 == 16'h0112)) $stop;
|
if (!(d_split_1 == 16'h0112 && d_split_2 == 16'h0112)) $stop;
|
||||||
if (!(h_split_1 == 16'hfeed && h_split_2 == 16'h0112)) $stop;
|
if (!(h_split_1 == 16'hfeed && h_split_2 == 16'h0112)) $stop;
|
||||||
end
|
end
|
||||||
if (cyc == 6) begin
|
if (cyc == 6) begin
|
||||||
m_din <= 16'he33e;
|
m_din <= 16'he33e;
|
||||||
if (!(a_split_1 == 16'he22e && a_split_2 == 16'he22e)) $stop;
|
if (!(a_split_1 == 16'he22e && a_split_2 == 16'he22e)) $stop;
|
||||||
|
if (!(t_split_1 == 16'he22e && t_split_2 == 16'h1dd1)) $stop;
|
||||||
|
if (!(n_split_1 == 16'he22e && n_split_2 == 16'h1dd1)) $stop;
|
||||||
if (!(d_split_1 == 16'h1ee1 && d_split_2 == 16'h0112)) $stop;
|
if (!(d_split_1 == 16'h1ee1 && d_split_2 == 16'h0112)) $stop;
|
||||||
if (!(h_split_1 == 16'he11e && h_split_2 == 16'h1ee1)) $stop;
|
if (!(h_split_1 == 16'he11e && h_split_2 == 16'h1ee1)) $stop;
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,8 @@ module Vt_debug_emitv_t;
|
||||||
logic [11:10] nn3;
|
logic [11:10] nn3;
|
||||||
} [5:4] nibblearray[3:2];
|
} [5:4] nibblearray[3:2];
|
||||||
task t;
|
task t;
|
||||||
$display("stmt");
|
input int signed value;
|
||||||
|
$display("stmt %d", value);
|
||||||
endtask
|
endtask
|
||||||
function f;
|
function f;
|
||||||
input int signed v;
|
input int signed v;
|
||||||
|
|
@ -137,7 +138,7 @@ module Vt_debug_emitv_t;
|
||||||
other = f(i);
|
other = f(i);
|
||||||
$display("stmt %d %d",
|
$display("stmt %d %d",
|
||||||
i, other);
|
i, other);
|
||||||
t();
|
t(i);
|
||||||
end
|
end
|
||||||
i = (i + 'h1);
|
i = (i + 'h1);
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -129,8 +129,8 @@ module t (/*AUTOARG*/
|
||||||
} nibble_t;
|
} nibble_t;
|
||||||
nibble_t [5:4] nibblearray[3:2];
|
nibble_t [5:4] nibblearray[3:2];
|
||||||
|
|
||||||
task t;
|
task t(input int value);
|
||||||
$display("stmt");
|
$display("stmt %d", value);
|
||||||
endtask
|
endtask
|
||||||
function int f(input int v);
|
function int f(input int v);
|
||||||
$display("stmt");
|
$display("stmt");
|
||||||
|
|
@ -146,7 +146,7 @@ module t (/*AUTOARG*/
|
||||||
for (int i = 0; i < 3; ++i) begin
|
for (int i = 0; i < 3; ++i) begin
|
||||||
other = f(i);
|
other = f(i);
|
||||||
$display("stmt %d %d", i, other);
|
$display("stmt %d %d", i, other);
|
||||||
t();
|
t(i);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
begin : named
|
begin : named
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,162 @@
|
||||||
// SPDX-FileCopyrightText: 2026 by Antmicro Ltd.
|
// SPDX-FileCopyrightText: 2026 by Antmicro Ltd.
|
||||||
// SPDX-License-Identifier: CC0-1.0
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
// verilog_format: off
|
||||||
|
`define stop $stop;
|
||||||
|
`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__, `__LINE__, (gotv), (expv)); `stop; end while (0);
|
||||||
|
// verilog_format: on
|
||||||
|
|
||||||
module test;
|
module test;
|
||||||
mailbox #(int) mbox;
|
mailbox #(int) mbox;
|
||||||
|
int seen_10 = 0;
|
||||||
|
int seen_30 = 0;
|
||||||
|
|
||||||
|
task automatic check_mbox_item(input int item);
|
||||||
|
#0;
|
||||||
|
if (item == 10) begin
|
||||||
|
seen_10++;
|
||||||
|
end
|
||||||
|
else if (item == 30) begin
|
||||||
|
seen_30++;
|
||||||
|
end
|
||||||
|
else begin
|
||||||
|
$write("%%Error: unexpected item '%0d'\n", item);
|
||||||
|
`stop;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
class driver;
|
||||||
|
string names[string];
|
||||||
|
int seen_a = 0;
|
||||||
|
int seen_b = 0;
|
||||||
|
|
||||||
|
task automatic check_name(input string name);
|
||||||
|
#0;
|
||||||
|
if (name == "a") begin
|
||||||
|
seen_a++;
|
||||||
|
end
|
||||||
|
else if (name == "b") begin
|
||||||
|
seen_b++;
|
||||||
|
end
|
||||||
|
else begin
|
||||||
|
$write("%%Error: unexpected name '%0s'\n", name);
|
||||||
|
`stop;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task automatic make_name(input bit pick_b, output string name);
|
||||||
|
name = pick_b ? "b" : "a";
|
||||||
|
#0;
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task automatic append_name(inout string name);
|
||||||
|
// verilator no_inline_task
|
||||||
|
#0;
|
||||||
|
name = {name, "_done"};
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task automatic check_arg_capture();
|
||||||
|
names["a"] = "alpha";
|
||||||
|
names["b"] = "bravo";
|
||||||
|
|
||||||
|
foreach (names[name]) begin
|
||||||
|
fork
|
||||||
|
check_name(name);
|
||||||
|
join_none
|
||||||
|
end
|
||||||
|
|
||||||
|
wait fork;
|
||||||
|
|
||||||
|
`checkd(seen_a, 1);
|
||||||
|
`checkd(seen_b, 1);
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task automatic check_local_lifetime();
|
||||||
|
seen_a = 0;
|
||||||
|
seen_b = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i) begin
|
||||||
|
automatic bit pick_b = i[0];
|
||||||
|
fork
|
||||||
|
begin
|
||||||
|
string name;
|
||||||
|
name = pick_b ? "b" : "a";
|
||||||
|
#0;
|
||||||
|
check_name(name);
|
||||||
|
end
|
||||||
|
join_none
|
||||||
|
end
|
||||||
|
|
||||||
|
wait fork;
|
||||||
|
|
||||||
|
`checkd(seen_a, 1);
|
||||||
|
`checkd(seen_b, 1);
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task automatic check_output_capture();
|
||||||
|
seen_a = 0;
|
||||||
|
seen_b = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i) begin
|
||||||
|
automatic bit pick_b = i[0];
|
||||||
|
fork
|
||||||
|
begin
|
||||||
|
string name;
|
||||||
|
make_name(pick_b, name);
|
||||||
|
#0;
|
||||||
|
check_name(name);
|
||||||
|
end
|
||||||
|
join_none
|
||||||
|
end
|
||||||
|
|
||||||
|
wait fork;
|
||||||
|
|
||||||
|
`checkd(seen_a, 1);
|
||||||
|
`checkd(seen_b, 1);
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task automatic check_inout_capture();
|
||||||
|
seen_a = 0;
|
||||||
|
seen_b = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i) begin
|
||||||
|
automatic bit pick_b = i[0];
|
||||||
|
fork
|
||||||
|
begin
|
||||||
|
string name = pick_b ? "b" : "a";
|
||||||
|
append_name(name);
|
||||||
|
#0;
|
||||||
|
if (name == "a_done") begin
|
||||||
|
seen_a++;
|
||||||
|
end
|
||||||
|
else if (name == "b_done") begin
|
||||||
|
seen_b++;
|
||||||
|
end
|
||||||
|
else begin
|
||||||
|
$write("%%Error: unexpected inout name '%0s'\n", name);
|
||||||
|
`stop;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
join_none
|
||||||
|
end
|
||||||
|
|
||||||
|
wait fork;
|
||||||
|
|
||||||
|
`checkd(seen_a, 1);
|
||||||
|
`checkd(seen_b, 1);
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task automatic run();
|
||||||
|
check_arg_capture();
|
||||||
|
check_local_lifetime();
|
||||||
|
check_output_capture();
|
||||||
|
check_inout_capture();
|
||||||
|
endtask
|
||||||
|
endclass
|
||||||
|
|
||||||
initial begin
|
initial begin
|
||||||
|
driver drv;
|
||||||
|
|
||||||
mbox = new();
|
mbox = new();
|
||||||
mbox.put(10);
|
mbox.put(10);
|
||||||
mbox.put(30);
|
mbox.put(30);
|
||||||
|
|
@ -15,14 +168,19 @@ module test;
|
||||||
automatic int item;
|
automatic int item;
|
||||||
mbox.get(item);
|
mbox.get(item);
|
||||||
fork
|
fork
|
||||||
begin
|
check_mbox_item(item);
|
||||||
$display("got", item);
|
|
||||||
if (item == 10) $finish;
|
|
||||||
end
|
|
||||||
join_none
|
join_none
|
||||||
end
|
end
|
||||||
|
|
||||||
#0;
|
wait fork;
|
||||||
$stop;
|
|
||||||
|
`checkd(seen_10, 1);
|
||||||
|
`checkd(seen_30, 1);
|
||||||
|
|
||||||
|
drv = new;
|
||||||
|
drv.run();
|
||||||
|
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
end
|
end
|
||||||
endmodule
|
endmodule
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# DESCRIPTION: Verilator: Task-local variables survive scheduler logic replication
|
||||||
|
#
|
||||||
|
# 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('simulator')
|
||||||
|
|
||||||
|
test.lint(verilator_flags2=["--timing"])
|
||||||
|
|
||||||
|
test.passes()
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// DESCRIPTION: Verilator: Task-local variables survive scheduler logic replication
|
||||||
|
//
|
||||||
|
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||||
|
// SPDX-FileCopyrightText: 2026 by Wilson Snyder
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
module t (
|
||||||
|
input logic a,
|
||||||
|
output logic unused
|
||||||
|
);
|
||||||
|
|
||||||
|
task automatic spawn(input logic value);
|
||||||
|
fork
|
||||||
|
unused = value;
|
||||||
|
join_none
|
||||||
|
endtask
|
||||||
|
|
||||||
|
always @* spawn(a);
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
@ -26,7 +26,7 @@ test.file_grep(test.stats,
|
||||||
test.file_grep(test.stats,
|
test.file_grep(test.stats,
|
||||||
r'Optimizations, Loop unrolling, Failed - unknown loop condition\s+(\d+)', 0)
|
r'Optimizations, Loop unrolling, Failed - unknown loop condition\s+(\d+)', 0)
|
||||||
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Pragma unroll_disable\s+(\d+)', 0)
|
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Pragma unroll_disable\s+(\d+)', 0)
|
||||||
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 13)
|
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 14)
|
||||||
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled iterations\s+(\d+)', 77)
|
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled iterations\s+(\d+)', 80)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,21 @@ module t;
|
||||||
|
|
||||||
int nonConst3 = $c("3");
|
int nonConst3 = $c("3");
|
||||||
|
|
||||||
|
task automatic copy_task(input int value, output int result);
|
||||||
|
result = value;
|
||||||
|
endtask
|
||||||
|
|
||||||
initial begin
|
initial begin
|
||||||
// Basic loop
|
// Basic loop
|
||||||
for (int i = 0; i < 3; ++i) begin : loop_0
|
for (int i = 0; i < 3; ++i) begin : loop_0
|
||||||
$display("loop_0 %0d", i);
|
$display("loop_0 %0d", i);
|
||||||
end
|
end
|
||||||
|
// Loop containing activation-local task staging
|
||||||
|
for (int i = 0; i < 2; ++i) begin : loop_task
|
||||||
|
int result;
|
||||||
|
copy_task(i, result);
|
||||||
|
if (result != i) $stop;
|
||||||
|
end
|
||||||
// Loop with 2 init/step
|
// Loop with 2 init/step
|
||||||
for (int i = 0, j = 5; i < j; i += 2, j += 1) begin : loop_1
|
for (int i = 0, j = 5; i < j; i += 2, j += 1) begin : loop_1
|
||||||
$display("loop_1 %0d %0d", i, j);
|
$display("loop_1 %0d %0d", i, j);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue