Merge 1ac45cb7e7 into 3afb7e1a21
This commit is contained in:
commit
95e7feef6d
|
|
@ -1889,6 +1889,26 @@ public:
|
|||
bool isPure() override { return false; }
|
||||
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 {
|
||||
// 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());
|
||||
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 {
|
||||
this->AstNode::dump(str);
|
||||
for (int i = 0; i < static_cast<int>(VBasicDTypeKwd::_ENUM_MAX); ++i) {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,14 @@ VL_DEFINE_DEBUG_FUNCTIONS;
|
|||
|
||||
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 {
|
||||
|
||||
//============================================================================
|
||||
|
|
@ -928,6 +936,28 @@ cloneMapWithNewTriggerReferences(const std::unordered_map<const AstSenTree*, Ast
|
|||
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
|
||||
|
||||
|
|
@ -1204,6 +1234,7 @@ void schedule(AstNetlist* netlistp) {
|
|||
|
||||
// Step 16: Clean up
|
||||
netlistp->clearStlFirstIterationp();
|
||||
removeVarScopesForDeletedLogic(netlistp);
|
||||
|
||||
// Haven't split static initializer yet
|
||||
util::splitCheck(staticp);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ namespace V3Sched {
|
|||
namespace util {
|
||||
using VarScopeSet = std::unordered_set<const AstVarScope*>;
|
||||
|
||||
AstNode* cloneLogic(AstNode* logicp);
|
||||
|
||||
inline bool isVlForceVec(const AstVarScope* vscp) {
|
||||
const AstCDType* const dtypep = VN_CAST(vscp->dtypep()->skipRefp(), CDType);
|
||||
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
|
||||
LogicByScope clone() const {
|
||||
LogicByScope result;
|
||||
for (const auto& pair : *this) {
|
||||
result.emplace_back(pair.first, pair.second->cloneTree(false));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
LogicByScope clone() const;
|
||||
|
||||
// Delete actives (they should all be empty)
|
||||
void deleteActives() {
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ LogicReplicas replicate(Graph* graphp) {
|
|||
for (V3GraphVertex& vtx : graphp->vertices()) {
|
||||
if (SchedReplicateLogicVertex* const lvtxp = vtx.cast<SchedReplicateLogicVertex>()) {
|
||||
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();
|
||||
UASSERT(!lvtxp->senTreep()->hasClocked() || targetRegions == 0,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,32 @@ VL_DEFINE_DEBUG_FUNCTIONS;
|
|||
namespace V3Sched {
|
||||
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) {
|
||||
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
||||
AstCFunc* const funcp = new AstCFunc{netlistp->fileline(), name, scopeTopp, ""};
|
||||
|
|
|
|||
|
|
@ -522,9 +522,8 @@ public:
|
|||
m_addAfter[color] = placeholderp;
|
||||
m_newBlocksp->push_back(alwaysp);
|
||||
}
|
||||
// Scan the body of the always. We'll handle if/else
|
||||
// specially, everything else is a leaf node that we can
|
||||
// just clone into one of the split always blocks.
|
||||
// Scan the body of the always. We'll handle if/else specially; every other leaf belongs to
|
||||
// one color and can move into the corresponding split always block.
|
||||
iterateAndNextNull(m_origAlwaysp->stmtsp());
|
||||
}
|
||||
|
||||
|
|
@ -547,12 +546,13 @@ protected:
|
|||
// Each leaf must have a user3p
|
||||
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 uint32_t color = vxp->color();
|
||||
AstNode* const clonedp = nodep->cloneTree(false);
|
||||
m_addAfter[color]->addNextHere(clonedp);
|
||||
m_addAfter[color] = clonedp;
|
||||
nodep->unlinkFrBack();
|
||||
m_addAfter[color]->addNextHere(nodep);
|
||||
m_addAfter[color] = nodep;
|
||||
}
|
||||
|
||||
void visit(AstNodeIf* nodep) override {
|
||||
|
|
|
|||
224
src/V3Task.cpp
224
src/V3Task.cpp
|
|
@ -34,6 +34,7 @@
|
|||
#include "V3Stats.h"
|
||||
#include "V3UniqueNames.h"
|
||||
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
|
@ -483,6 +484,45 @@ class TaskVisitor final : public VNVisitor {
|
|||
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
|
||||
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 inlineTask) {
|
||||
bool connectPort(AstVar* portp, AstArg* argp, const string& namePrefix, AstNode* beginp,
|
||||
bool inlineTask, bool activationLocalPorts) {
|
||||
bool needsCLocalScope = false;
|
||||
AstNodeExpr* pinp = argp->exprp();
|
||||
if (inlineTask) {
|
||||
portp->unlinkFrBack();
|
||||
|
|
@ -619,8 +660,10 @@ class TaskVisitor final : public VNVisitor {
|
|||
} else if (portp->isInout()) {
|
||||
// UINFOTREE(9, pinp, "", "pinrsize-");
|
||||
|
||||
AstTaskLocalVar* localDeclp = nullptr;
|
||||
AstVarScope* const newvscp
|
||||
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
||||
= createPortVarScope(portp, namePrefix + "__" + portp->shortName(),
|
||||
activationLocalPorts, localDeclp);
|
||||
portp->user2p(newvscp);
|
||||
if (!inlineTask) {
|
||||
pinp->replaceWith(
|
||||
|
|
@ -629,42 +672,47 @@ class TaskVisitor final : public VNVisitor {
|
|||
}
|
||||
// Put input assignment in FRONT of all other statements
|
||||
AstAssign* const preassp = connectPortMakeInAssign(pinp, newvscp, true);
|
||||
if (AstNode* const afterp = beginp->nextp()) {
|
||||
afterp->unlinkFrBackWithNext();
|
||||
AstNode::addNext<AstNode, AstNode>(preassp, afterp);
|
||||
}
|
||||
beginp->addNext(preassp);
|
||||
AstNode* const frontp
|
||||
= AstNode::addNextNull<AstNode, AstNode>(localDeclp, preassp);
|
||||
addToFront(beginp, frontp);
|
||||
|
||||
AstAssign* const postassp = connectPortMakeOutAssign(portp, pinp, newvscp, true);
|
||||
beginp->addNext(postassp);
|
||||
needsCLocalScope = localDeclp != nullptr;
|
||||
// if (debug() >= 9) beginp->dumpTreeAndNext(cout, "-pinrsize-out- ");
|
||||
} else if (portp->isWritable()) {
|
||||
// Even if it's referencing a varref, we still make a temporary
|
||||
// Else task(x,x,x) might produce incorrect results
|
||||
AstTaskLocalVar* localDeclp = nullptr;
|
||||
AstVarScope* const newvscp
|
||||
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
||||
= createPortVarScope(portp, namePrefix + "__" + portp->shortName(),
|
||||
activationLocalPorts, localDeclp);
|
||||
portp->user2p(newvscp);
|
||||
if (!inlineTask) {
|
||||
pinp->replaceWith(new AstVarRef{newvscp->fileline(), newvscp, VAccess::WRITE});
|
||||
pushDeletep(pinp); // Cloned by connectPortMakeOutAssign
|
||||
}
|
||||
addToFront(beginp, localDeclp);
|
||||
AstAssign* const postassp = connectPortMakeOutAssign(portp, pinp, newvscp, false);
|
||||
// Put assignment BEHIND of all other statements
|
||||
beginp->addNext(postassp);
|
||||
needsCLocalScope = localDeclp != nullptr;
|
||||
} else if (inlineTask && portp->isNonOutput()) {
|
||||
// Make input variable
|
||||
AstTaskLocalVar* localDeclp = nullptr;
|
||||
AstVarScope* const newvscp
|
||||
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
||||
= createPortVarScope(portp, namePrefix + "__" + portp->shortName(),
|
||||
activationLocalPorts, localDeclp);
|
||||
portp->user2p(newvscp);
|
||||
AstAssign* const preassp = connectPortMakeInAssign(pinp, newvscp, false);
|
||||
// Put assignment in FRONT of all other statements
|
||||
if (AstNode* const afterp = beginp->nextp()) {
|
||||
afterp->unlinkFrBackWithNext();
|
||||
AstNode::addNext<AstNode, AstNode>(preassp, afterp);
|
||||
}
|
||||
beginp->addNext(preassp);
|
||||
AstNode* const frontp
|
||||
= AstNode::addNextNull<AstNode, AstNode>(localDeclp, preassp);
|
||||
addToFront(beginp, frontp);
|
||||
needsCLocalScope = localDeclp != nullptr;
|
||||
}
|
||||
}
|
||||
return needsCLocalScope;
|
||||
}
|
||||
|
||||
bool hasRefArgument(AstNodeFTask* nodep) {
|
||||
|
|
@ -688,13 +736,20 @@ class TaskVisitor final : public VNVisitor {
|
|||
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- newbegi: ");
|
||||
//
|
||||
// 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();
|
||||
{
|
||||
const V3TaskConnects tconnects = V3Task::taskConnects(refp, beginp);
|
||||
for (const auto& itr : tconnects) {
|
||||
AstVar* const portp = itr.first;
|
||||
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");
|
||||
|
|
@ -705,6 +760,29 @@ class TaskVisitor final : public VNVisitor {
|
|||
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
|
||||
// Any I/O variables that fell out of above loop were already linked
|
||||
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
|
||||
AstVarScope* const localVscp
|
||||
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
||||
|
|
@ -734,7 +812,7 @@ class TaskVisitor final : public VNVisitor {
|
|||
relink(beginp);
|
||||
//
|
||||
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- iotask: ");
|
||||
return beginp;
|
||||
return needsCLocalScope ? new AstCLocalScope{refp->fileline(), beginp} : beginp;
|
||||
}
|
||||
|
||||
AstNode* createNonInlinedFTask(AstNodeFTaskRef* refp, const string& namePrefix,
|
||||
|
|
@ -769,13 +847,20 @@ class TaskVisitor final : public VNVisitor {
|
|||
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());
|
||||
for (const auto& itr : tconnects) {
|
||||
AstVar* const portp = itr.first;
|
||||
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
|
||||
|
|
@ -805,7 +890,7 @@ class TaskVisitor final : public VNVisitor {
|
|||
if (outvscp) ccallp->addArgsp(new AstVarRef{refp->fileline(), outvscp, VAccess::WRITE});
|
||||
|
||||
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- nitask: ");
|
||||
return beginp;
|
||||
return needsCLocalScope ? new AstCLocalScope{refp->fileline(), beginp} : beginp;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -2300,3 +2478,9 @@ void V3Task::taskAll(AstNetlist* nodep) {
|
|||
} // Destruct before checking
|
||||
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:
|
||||
static void taskAll(AstNetlist* nodep) VL_MT_DISABLED;
|
||||
static void taskLocalizeAll(AstNetlist* nodep) VL_MT_DISABLED;
|
||||
/// Return vector of [port, pin-connects-to] (SLOW)
|
||||
static V3TaskConnects taskConnects(AstNodeFTaskRef* nodep, AstNode* taskStmtsp,
|
||||
V3TaskConnectState* statep = nullptr,
|
||||
|
|
|
|||
|
|
@ -478,6 +478,7 @@ class TimingControlVisitor final : public VNVisitor {
|
|||
AstScope* m_scopep = nullptr; // Current scope
|
||||
AstActive* m_activep = nullptr; // Current active
|
||||
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
|
||||
int m_forkCnt = 0; // Number of forks inside a module
|
||||
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});
|
||||
}
|
||||
|
||||
// `procp` shall be a NodeProcedure/CFunc/Begin and within it vars from `varsp` will be placed.
|
||||
// `varsp` vector of vars which shall be localized.
|
||||
// `procp` shall be a NodeProcedure/CFunc/Begin/CLocalScope and within it vars from `varsp`
|
||||
// will be placed. `varsp` vector of vars which shall be localized.
|
||||
static void localizeVars(AstNode* const procp, const std::vector<AstVar*>& varsp) {
|
||||
UASSERT(procp, "procp is nullptr");
|
||||
AstNode* firstStmtp;
|
||||
|
|
@ -824,15 +825,19 @@ class TimingControlVisitor final : public VNVisitor {
|
|||
firstStmtp = cfuncp->varsp();
|
||||
} else if (AstBegin* const beginp = VN_CAST(procp, Begin)) {
|
||||
firstStmtp = beginp->stmtsp();
|
||||
} else if (AstCLocalScope* const localScopep = VN_CAST(procp, CLocalScope)) {
|
||||
firstStmtp = localScopep->stmtsp();
|
||||
} else {
|
||||
procp->v3fatalSrc(
|
||||
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());
|
||||
}
|
||||
UASSERT_OBJ(firstStmtp, procp,
|
||||
procp->prettyNameQ() << " has no non-var statement. 'localizeVars()' is ment "
|
||||
"to be called on non-empty NodeProcedure/CFunc/Begin");
|
||||
procp->prettyNameQ()
|
||||
<< " has no non-var statement. 'localizeVars()' is meant to be called on "
|
||||
"non-empty NodeProcedure/CFunc/Begin/CLocalScope");
|
||||
for (AstVar* const varp : varsp) {
|
||||
varp->funcLocal(true);
|
||||
firstStmtp->addHereThisAsNext(varp->unlinkFrBack());
|
||||
|
|
@ -889,8 +894,10 @@ class TimingControlVisitor final : public VNVisitor {
|
|||
}
|
||||
void visit(AstNodeProcedure* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
VL_RESTORER(m_varInsertp);
|
||||
VL_RESTORER(m_hasProcess);
|
||||
m_procp = nodep;
|
||||
m_varInsertp = nodep;
|
||||
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
|
||||
VL_RESTORER(m_underProcedure);
|
||||
m_underProcedure = true;
|
||||
|
|
@ -913,8 +920,10 @@ class TimingControlVisitor final : public VNVisitor {
|
|||
void visit(AstAlways* nodep) override {
|
||||
if (nodep->user1SetOnce()) return;
|
||||
VL_RESTORER(m_procp);
|
||||
VL_RESTORER(m_varInsertp);
|
||||
VL_RESTORER(m_hasProcess);
|
||||
m_procp = nodep;
|
||||
m_varInsertp = nodep;
|
||||
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
|
||||
VL_RESTORER(m_underProcedure);
|
||||
m_underProcedure = true;
|
||||
|
|
@ -947,8 +956,10 @@ class TimingControlVisitor final : public VNVisitor {
|
|||
}
|
||||
void visit(AstCFunc* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
VL_RESTORER(m_varInsertp);
|
||||
VL_RESTORER(m_hasProcess);
|
||||
m_procp = nodep;
|
||||
m_varInsertp = nodep;
|
||||
m_hasProcess = hasFlags(nodep, T_HAS_PROC);
|
||||
iterateChildren(nodep);
|
||||
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
|
||||
|
|
@ -1101,7 +1112,7 @@ class TimingControlVisitor final : public VNVisitor {
|
|||
m_senExprBuilderp->build(sentreep).first};
|
||||
// Get the SenExprBuilder results
|
||||
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
|
||||
// conservative pre-clear once before entering the wait loop so stale state from a
|
||||
// previous wait does not cause an immediate false-positive trigger.
|
||||
|
|
@ -1419,12 +1430,19 @@ class TimingControlVisitor final : public VNVisitor {
|
|||
}
|
||||
void visit(AstBegin* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
VL_RESTORER(m_varInsertp);
|
||||
VL_RESTORER(m_hasProcess);
|
||||
m_hasProcess |= hasFlags(nodep, T_HAS_PROC);
|
||||
m_procp = nodep;
|
||||
m_varInsertp = nodep;
|
||||
if (m_hasProcess) nodep->setNeedProcess();
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstCLocalScope* nodep) override {
|
||||
VL_RESTORER(m_varInsertp);
|
||||
m_varInsertp = nodep;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstFork* nodep) override {
|
||||
if (nodep->user1SetOnce()) return;
|
||||
UINFO(9, "control-visit " << nodep);
|
||||
|
|
|
|||
|
|
@ -397,6 +397,9 @@ static void process() {
|
|||
// Loop unrolling
|
||||
V3Unroll::unrollAll(v3Global.rootp());
|
||||
|
||||
// Materialize task activation locals after statement-cloning loop transforms.
|
||||
V3Task::taskLocalizeAll(v3Global.rootp());
|
||||
|
||||
// Expand slices of arrays
|
||||
V3Slice::sliceAll(v3Global.rootp());
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ test.scenarios('simulator')
|
|||
test.compile(verilator_flags2=["--stats"])
|
||||
|
||||
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()
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,24 @@ module t (
|
|||
l_split_1 <= l_split_2 | m_din;
|
||||
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.)
|
||||
always @(posedge clk) begin
|
||||
if (cyc != 0) begin
|
||||
|
|
@ -68,18 +86,24 @@ module t (
|
|||
m_din <= 16'he11e;
|
||||
//$write(" A %x %x\n", a_split_1, a_split_2);
|
||||
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 (!(h_split_1 == 16'hfeed && h_split_2 == 16'h0112)) $stop;
|
||||
end
|
||||
if (cyc == 5) begin
|
||||
m_din <= 16'he22e;
|
||||
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 (!(h_split_1 == 16'hfeed && h_split_2 == 16'h0112)) $stop;
|
||||
end
|
||||
if (cyc == 6) begin
|
||||
m_din <= 16'he33e;
|
||||
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 (!(h_split_1 == 16'he11e && h_split_2 == 16'h1ee1)) $stop;
|
||||
end
|
||||
|
|
|
|||
|
|
@ -114,7 +114,8 @@ module Vt_debug_emitv_t;
|
|||
logic [11:10] nn3;
|
||||
} [5:4] nibblearray[3:2];
|
||||
task t;
|
||||
$display("stmt");
|
||||
input int signed value;
|
||||
$display("stmt %d", value);
|
||||
endtask
|
||||
function f;
|
||||
input int signed v;
|
||||
|
|
@ -137,7 +138,7 @@ module Vt_debug_emitv_t;
|
|||
other = f(i);
|
||||
$display("stmt %d %d",
|
||||
i, other);
|
||||
t();
|
||||
t(i);
|
||||
end
|
||||
i = (i + 'h1);
|
||||
end
|
||||
|
|
|
|||
|
|
@ -129,8 +129,8 @@ module t (/*AUTOARG*/
|
|||
} nibble_t;
|
||||
nibble_t [5:4] nibblearray[3:2];
|
||||
|
||||
task t;
|
||||
$display("stmt");
|
||||
task t(input int value);
|
||||
$display("stmt %d", value);
|
||||
endtask
|
||||
function int f(input int v);
|
||||
$display("stmt");
|
||||
|
|
@ -146,7 +146,7 @@ module t (/*AUTOARG*/
|
|||
for (int i = 0; i < 3; ++i) begin
|
||||
other = f(i);
|
||||
$display("stmt %d %d", i, other);
|
||||
t();
|
||||
t(i);
|
||||
end
|
||||
end
|
||||
begin : named
|
||||
|
|
|
|||
|
|
@ -4,9 +4,162 @@
|
|||
// SPDX-FileCopyrightText: 2026 by Antmicro Ltd.
|
||||
// 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;
|
||||
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
|
||||
driver drv;
|
||||
|
||||
mbox = new();
|
||||
mbox.put(10);
|
||||
mbox.put(30);
|
||||
|
|
@ -15,14 +168,19 @@ module test;
|
|||
automatic int item;
|
||||
mbox.get(item);
|
||||
fork
|
||||
begin
|
||||
$display("got", item);
|
||||
if (item == 10) $finish;
|
||||
end
|
||||
check_mbox_item(item);
|
||||
join_none
|
||||
end
|
||||
|
||||
#0;
|
||||
$stop;
|
||||
wait fork;
|
||||
|
||||
`checkd(seen_10, 1);
|
||||
`checkd(seen_30, 1);
|
||||
|
||||
drv = new;
|
||||
drv.run();
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
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,
|
||||
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, Unrolled loops\s+(\d+)', 13)
|
||||
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled iterations\s+(\d+)', 77)
|
||||
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+)', 80)
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -14,11 +14,21 @@ module t;
|
|||
|
||||
int nonConst3 = $c("3");
|
||||
|
||||
task automatic copy_task(input int value, output int result);
|
||||
result = value;
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
// Basic loop
|
||||
for (int i = 0; i < 3; ++i) begin : loop_0
|
||||
$display("loop_0 %0d", i);
|
||||
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
|
||||
for (int i = 0, j = 5; i < j; i += 2, j += 1) begin : loop_1
|
||||
$display("loop_1 %0d %0d", i, j);
|
||||
|
|
|
|||
Loading…
Reference in New Issue