Keep task variables activation-local across forks
Context: Task inlining lowered automatic task arguments, locals, and output staging temporaries into object-scope storage. Concurrent activations from one call site could then share state. Keeping declarations lexical exposed dangling AstVarScope links when statement subtrees were cloned by unrolling, splitting, or scheduler replication. Changes: - Represent activation-local declarations with a transient AstTaskLocalVar whose scoped template survives statement cloning. - Materialize independent local variables and scopes immediately after loop unrolling, including references captured by nested task activations. - Move single-color V3Split leaves instead of cloning them, preserving external lexical-scope links without changing split membership. - Use activation-local storage only for task bodies that can suspend or fork, leaving non-suspending task and function paths unchanged. - Keep timing sensitivity temporaries in their nearest lexical CLocalScope. - Relink scheduler logic clones to independent AstVarScopes and remove only unreferenced scopes whose cloned declarations were discarded. - Cover input, local, output, inout, nested capture, unroll, split, scheduler replication, and AST tree/JSON dump paths. Evidence: - Before the fix, the unroll, nested-split, dynamic-process, and scheduler-replication reproducers fail --debug-check with broken or dangling local links. - The final focused task/fork set passes 31/31 vlt/vltmt scenarios. - Scheduler/ICO and fork/timing-fork regression groups pass; the sole initial FST failure passes after supplying the Homebrew lz4 include and library paths. - make format completes with clang-format 18; git diff --check passes. - make cppcheck and make lint-py exit successfully; lint-py reports only the existing macOS sched_getaffinity mypy diagnostic. Boundary: This changes activation-local task lowering and ownership-preserving cloning of task-local declarations. It does not change disable-fork process-queue semantics, non-suspending function storage, or scheduler region assignment.
This commit is contained in:
parent
dde8de0a0d
commit
09e20633bb
|
|
@ -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
|
||||
//
|
||||
|
|
|
|||
|
|
@ -3066,6 +3066,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
|
||||
|
|
@ -811,8 +812,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;
|
||||
|
|
@ -829,15 +830,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());
|
||||
|
|
@ -894,8 +899,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;
|
||||
|
|
@ -918,8 +925,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;
|
||||
|
|
@ -952,8 +961,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();
|
||||
|
|
@ -1106,7 +1117,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.
|
||||
|
|
@ -1424,12 +1435,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