Move variables mutated under fork..join_none/join_any blocks into anonymous objects (#4356)
This commit is contained in:
parent
e24197fd16
commit
c3e19f2821
|
|
@ -64,7 +64,9 @@ class CUseVisitor final : public VNVisitor {
|
|||
|
||||
// VISITORS
|
||||
void visit(AstClassRefDType* nodep) override {
|
||||
if (nodep->user1SetOnce()) return; // Process once
|
||||
if (nodep->user1()) return; // Process once
|
||||
if (!m_dtypesImplOnly) // We might need to revisit this type for interface
|
||||
nodep->user1(true);
|
||||
addNewUse(nodep, VUseType::INT_FWD_CLASS, nodep->classp()->name());
|
||||
}
|
||||
void visit(AstCFunc* nodep) override {
|
||||
|
|
|
|||
389
src/V3Fork.cpp
389
src/V3Fork.cpp
|
|
@ -18,11 +18,23 @@
|
|||
//
|
||||
// Each module:
|
||||
// Look for FORKs [JOIN_NONE]/[JOIN_ANY]
|
||||
// VARREF(var) -> MEMBERSEL(var->name, VARREF(dynscope)) (for write/RW refs)
|
||||
// FORK(stmts) -> TASK(stmts), FORK(TASKREF(inits))
|
||||
//
|
||||
// FORKs that spawn tasks which might outlive their parents require those
|
||||
// tasks to carry their own frames and as such they require their own
|
||||
// variable scopes.
|
||||
// There are two mechanisms that work together to achieve that. ForkVisitor
|
||||
// moves bodies of forked prcesses into new tasks, which results in them getting their
|
||||
// own scopes. The original statements get replaced with a call to the task which
|
||||
// passes the required variables by value.
|
||||
// The second mechanism, DynScopeVisitor, is designed to handle variables which can't be
|
||||
// captured by value and instead require a reference. Those variables get moved into an
|
||||
// "anonymous" object, ie. a class with appropriate fields gets generated and an object
|
||||
// of this class gets instantiated in place of the original variable declarations.
|
||||
// Any references to those variables are replaced with references to the object's field.
|
||||
// Since objects are reference-counted this ensures that the variables are accessible
|
||||
// as long as both the parent and the forked processes require them to be.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
|
|
@ -34,12 +46,344 @@
|
|||
#include "V3Ast.h"
|
||||
#include "V3AstNodeExpr.h"
|
||||
#include "V3Global.h"
|
||||
#include "V3MemberMap.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
class ForkDynScopeInstance final {
|
||||
public:
|
||||
AstClass* m_classp = nullptr; // Class for holding variables of dynamic scope
|
||||
AstClassRefDType* m_refDTypep = nullptr; // RefDType for the above
|
||||
AstVar* m_handlep = nullptr; // Class handle for holding variables of dynamic scope
|
||||
|
||||
// True if the instance exists
|
||||
bool initialized() const { return m_classp != nullptr; }
|
||||
};
|
||||
|
||||
class ForkDynScopeFrame final {
|
||||
private:
|
||||
// MEMBERS
|
||||
AstNodeModule* const m_modp; // Module to insert the scope into
|
||||
AstNode* const m_procp; // Procedure/block associated with that dynscope
|
||||
std::set<AstVar*> m_captures; // Variables to be moved into the dynscope
|
||||
ForkDynScopeInstance m_instance; // Nodes to be injected into the AST to create the dynscope
|
||||
|
||||
public:
|
||||
ForkDynScopeFrame(AstNodeModule* modp, AstNode* procp)
|
||||
: m_modp{modp}
|
||||
, m_procp{procp} {}
|
||||
|
||||
ForkDynScopeInstance& createInstancePrototype() {
|
||||
UASSERT_OBJ(!m_instance.initialized(), m_procp, "Dynamic scope already instantiated.");
|
||||
|
||||
m_instance.m_classp
|
||||
= new AstClass{m_procp->fileline(), generateDynScopeClassName(m_procp)};
|
||||
m_instance.m_refDTypep
|
||||
= new AstClassRefDType{m_procp->fileline(), m_instance.m_classp, nullptr};
|
||||
v3Global.rootp()->typeTablep()->addTypesp(m_instance.m_refDTypep);
|
||||
m_instance.m_handlep
|
||||
= new AstVar{m_procp->fileline(), VVarType::BLOCKTEMP,
|
||||
generateDynScopeHandleName(m_procp), m_instance.m_refDTypep};
|
||||
m_instance.m_handlep->funcLocal(true);
|
||||
m_instance.m_handlep->lifetime(VLifetime::AUTOMATIC);
|
||||
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
const ForkDynScopeInstance& instance() const { return m_instance; }
|
||||
void captureVarInsert(AstVar* varp) { m_captures.insert(varp); }
|
||||
bool captured(AstVar* varp) { return m_captures.count(varp) != 0; }
|
||||
AstNode* procp() const { return m_procp; }
|
||||
|
||||
void populateClass() {
|
||||
UASSERT_OBJ(m_instance.initialized(), m_procp, "No DynScope prototype");
|
||||
|
||||
// Move variables into the class
|
||||
for (AstVar* varp : m_captures) {
|
||||
if (varp->direction() == VDirection::INPUT) {
|
||||
varp = varp->cloneTree(false);
|
||||
varp->direction(VDirection::NONE);
|
||||
} else {
|
||||
varp->unlinkFrBack();
|
||||
}
|
||||
varp->funcLocal(false);
|
||||
varp->varType(VVarType::MEMBER);
|
||||
varp->lifetime(VLifetime::AUTOMATIC);
|
||||
varp->usedLoopIdx(false); // No longer unrollable
|
||||
m_instance.m_classp->addStmtsp(varp);
|
||||
}
|
||||
|
||||
// Create class's constructor
|
||||
AstFunc* const newp
|
||||
= new AstFunc{m_instance.m_classp->fileline(), "new", nullptr, nullptr};
|
||||
newp->isConstructor(true);
|
||||
newp->classMethod(true);
|
||||
newp->dtypep(newp->findVoidDType());
|
||||
m_instance.m_classp->addStmtsp(newp);
|
||||
}
|
||||
|
||||
void linkNodes(VMemberMap& memberMap) {
|
||||
UASSERT_OBJ(m_instance.initialized(), m_procp, "No dynamic scope prototype");
|
||||
UASSERT_OBJ(!linked(), m_instance.m_handlep, "Handle already linked");
|
||||
|
||||
AstNode* stmtp = getProcStmts();
|
||||
UASSERT(stmtp, "trying to instantiate dynamic scope while not under proc");
|
||||
VNRelinker stmtpHandle;
|
||||
stmtp->unlinkFrBackWithNext(&stmtpHandle);
|
||||
|
||||
// Find node after last variable declaration
|
||||
AstNode* initp = stmtp;
|
||||
while (initp && VN_IS(initp, Var)) initp = initp->nextp();
|
||||
UASSERT(stmtp, "Procedure lacks body");
|
||||
UASSERT(initp, "Procedure lacks statements besides declarations");
|
||||
|
||||
AstNew* const newp = new AstNew{m_procp->fileline(), nullptr};
|
||||
newp->taskp(VN_AS(memberMap.findMember(m_instance.m_classp, "new"), NodeFTask));
|
||||
newp->dtypep(m_instance.m_refDTypep);
|
||||
newp->classOrPackagep(m_instance.m_classp);
|
||||
|
||||
AstNode* const asgnp = new AstAssign{
|
||||
m_procp->fileline(),
|
||||
new AstVarRef{m_procp->fileline(), m_instance.m_handlep, VAccess::WRITE}, newp};
|
||||
|
||||
AstNode* initsp = nullptr; // Arguments need to be copied
|
||||
for (AstVar* varp : m_captures) {
|
||||
if (varp->direction() != VDirection::INPUT) continue;
|
||||
|
||||
AstMemberSel* const memberselp = new AstMemberSel{
|
||||
varp->fileline(),
|
||||
new AstVarRef{varp->fileline(), m_instance.m_handlep, VAccess::WRITE},
|
||||
varp->dtypep()};
|
||||
memberselp->name(varp->name());
|
||||
memberselp->varp(VN_AS(memberMap.findMember(m_instance.m_classp, varp->name()), Var));
|
||||
AstNode* initAsgnp
|
||||
= new AstAssign{varp->fileline(), memberselp,
|
||||
new AstVarRef{varp->fileline(), varp, VAccess::READ}};
|
||||
initsp = AstNode::addNext(initsp, initAsgnp);
|
||||
}
|
||||
if (initsp) AstNode::addNext(asgnp, initsp);
|
||||
|
||||
if (initp != stmtp) {
|
||||
initp->addHereThisAsNext(asgnp);
|
||||
} else {
|
||||
AstNode::addNext(asgnp, static_cast<AstNode*>(initp));
|
||||
stmtp = asgnp;
|
||||
}
|
||||
|
||||
AstNode::addNext(static_cast<AstNode*>(m_instance.m_handlep), stmtp);
|
||||
stmtpHandle.relink(m_instance.m_handlep);
|
||||
m_modp->addStmtsp(m_instance.m_classp);
|
||||
}
|
||||
|
||||
bool linked() const { return m_instance.initialized() && m_instance.m_handlep->backp(); }
|
||||
|
||||
private:
|
||||
static string generateDynScopeClassName(const AstNode* fromp) {
|
||||
string n = "__VDynScope__" + (!fromp->name().empty() ? (fromp->name() + "__") : "ANON__")
|
||||
+ cvtToHex(fromp);
|
||||
return n;
|
||||
}
|
||||
|
||||
static string generateDynScopeHandleName(const AstNode* fromp) {
|
||||
return "__VDynScope_" + (fromp->name().empty() ? cvtToHex(fromp) : fromp->name());
|
||||
}
|
||||
|
||||
AstNode* getProcStmts() {
|
||||
AstNode* stmtsp = nullptr;
|
||||
if (!m_procp) return nullptr;
|
||||
if (AstBegin* beginp = VN_CAST(m_procp, Begin)) {
|
||||
stmtsp = beginp->stmtsp();
|
||||
} else if (AstNodeFTask* taskp = VN_CAST(m_procp, NodeFTask)) {
|
||||
stmtsp = taskp->stmtsp();
|
||||
} else {
|
||||
v3fatal("m_procp is not a begin block or a procedure");
|
||||
}
|
||||
return stmtsp;
|
||||
}
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Dynamic scope visitor, creates classes and objects for dynamic scoping of variables and
|
||||
// replaces references to varibles that need a dynamic scope with references to object's
|
||||
// members
|
||||
|
||||
class DynScopeVisitor final : public VNVisitor {
|
||||
private:
|
||||
// NODE STATE
|
||||
// AstVar::user1() -> int, timing-control fork nesting level of that variable
|
||||
// AstVarRef::user2() -> bool, 1 = Node is a class handle reference. The handle gets
|
||||
// modified in the context of this reference.
|
||||
const VNUser1InUse m_inuser1;
|
||||
const VNUser2InUse m_inuser2;
|
||||
|
||||
// STATE
|
||||
AstNodeModule* m_modp = nullptr; // Module we are currently under
|
||||
AstNode* m_procp = nullptr; // Function/task/block we are currently under
|
||||
std::map<AstNode*, ForkDynScopeFrame*>
|
||||
m_frames; // Mapping from nodes to related DynScopeFrames
|
||||
VMemberMap m_memberMap; // Class member look-up
|
||||
int m_forkDepth = 0; // Number of asynchronous forks we are currently under
|
||||
bool m_afterTimingControl = false; // A timing control might've be executed in the current
|
||||
// process
|
||||
|
||||
// METHODS
|
||||
|
||||
ForkDynScopeFrame* frameOf(AstNode* nodep) {
|
||||
auto frameIt = m_frames.find(nodep);
|
||||
if (frameIt == m_frames.end()) return nullptr;
|
||||
return frameIt->second;
|
||||
}
|
||||
|
||||
const ForkDynScopeFrame* frameOf(AstNode* nodep) const {
|
||||
auto frameIt = m_frames.find(nodep);
|
||||
if (frameIt == m_frames.end()) return nullptr;
|
||||
return frameIt->second;
|
||||
}
|
||||
|
||||
ForkDynScopeFrame* pushDynScopeFrame() {
|
||||
ForkDynScopeFrame* const frame = new ForkDynScopeFrame{m_modp, m_procp};
|
||||
auto r = m_frames.emplace(std::make_pair(m_procp, frame));
|
||||
UASSERT_OBJ(r.second, m_modp, "Procedure already contains a frame");
|
||||
return frame;
|
||||
}
|
||||
|
||||
void replaceWithMemberSel(AstVarRef* refp, const ForkDynScopeInstance& dynScope) {
|
||||
VNRelinker handle;
|
||||
refp->unlinkFrBack(&handle);
|
||||
AstMemberSel* const membersel = new AstMemberSel{
|
||||
refp->fileline(), new AstVarRef{refp->fileline(), dynScope.m_handlep, refp->access()},
|
||||
refp->dtypep()};
|
||||
membersel->name(refp->varp()->name());
|
||||
if (refp->varp()->direction() == VDirection::INPUT) {
|
||||
membersel->varp(
|
||||
VN_AS(m_memberMap.findMember(dynScope.m_classp, refp->varp()->name()), Var));
|
||||
} else {
|
||||
membersel->varp(refp->varp());
|
||||
}
|
||||
handle.relink(membersel);
|
||||
VL_DO_DANGLING(refp->deleteTree(), refp);
|
||||
}
|
||||
|
||||
static bool hasAsyncFork(AstNode* nodep) {
|
||||
bool afork = false;
|
||||
nodep->foreach([&](AstFork* forkp) {
|
||||
if (!forkp->joinType().join()) afork = true;
|
||||
});
|
||||
return afork;
|
||||
}
|
||||
|
||||
void bindNodeToDynScope(AstNode* nodep, ForkDynScopeFrame* frame) {
|
||||
m_frames.emplace(std::make_pair(nodep, frame));
|
||||
}
|
||||
|
||||
bool needsDynScope(const AstVarRef* refp) const {
|
||||
return
|
||||
// Can this variable escape the scope
|
||||
((m_forkDepth > refp->varp()->user1()) && refp->varp()->isFuncLocal())
|
||||
&& (
|
||||
// Is it mutated
|
||||
(refp->varp()->isClassHandleValue() ? refp->user2() : refp->access().isWriteOrRW())
|
||||
// Or is it after a timing-control event
|
||||
|| m_afterTimingControl);
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
void visit(AstNodeModule* nodep) override {
|
||||
VL_RESTORER(m_modp);
|
||||
if (!VN_IS(nodep, Class)) m_modp = nodep;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstNodeFTask* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
if (hasAsyncFork(nodep)) pushDynScopeFrame();
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstBegin* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
if (hasAsyncFork(nodep)) pushDynScopeFrame();
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstFork* nodep) override {
|
||||
VL_RESTORER(m_forkDepth);
|
||||
if (!nodep->joinType().join()) ++m_forkDepth;
|
||||
|
||||
const bool oldAfterTimingControl = m_afterTimingControl;
|
||||
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
m_afterTimingControl = false;
|
||||
iterate(stmtp);
|
||||
}
|
||||
m_afterTimingControl = oldAfterTimingControl;
|
||||
if (nodep->isTimingControl()) m_afterTimingControl = true;
|
||||
}
|
||||
void visit(AstNodeFTaskRef* nodep) override {
|
||||
visit(static_cast<AstNodeExpr*>(nodep));
|
||||
// We are before V3Timing, so unfortnately we need to treat any calls as suspending,
|
||||
// just to be safe. This might be improved if we could propagate suspendability
|
||||
// before doing all the other timing-related stuff.
|
||||
m_afterTimingControl = true;
|
||||
}
|
||||
void visit(AstVar* nodep) override {
|
||||
nodep->user1(m_forkDepth);
|
||||
ForkDynScopeFrame* const framep = frameOf(m_procp);
|
||||
if (!framep) return; // Cannot be legally referenced from a fork
|
||||
bindNodeToDynScope(nodep, framep);
|
||||
}
|
||||
void visit(AstVarRef* nodep) override {
|
||||
ForkDynScopeFrame* const framep = frameOf(nodep->varp());
|
||||
if (!framep) return;
|
||||
|
||||
if (needsDynScope(nodep)) {
|
||||
if (!framep->instance().initialized()) framep->createInstancePrototype();
|
||||
framep->captureVarInsert(nodep->varp());
|
||||
}
|
||||
bindNodeToDynScope(nodep, framep);
|
||||
}
|
||||
void visit(AstAssign* nodep) override {
|
||||
if (VN_IS(nodep->lhsp(), VarRef) && nodep->lhsp()->isClassHandleValue()) {
|
||||
nodep->lhsp()->user2(true);
|
||||
}
|
||||
visit(static_cast<AstNodeStmt*>(nodep));
|
||||
}
|
||||
void visit(AstNode* nodep) override {
|
||||
if (nodep->isTimingControl()) m_afterTimingControl = true;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
DynScopeVisitor(AstNetlist* nodep) {
|
||||
// Create Dynamic scope class prototypes and objects
|
||||
visit(nodep);
|
||||
|
||||
// Commit changes to AST
|
||||
bool typesAdded = false;
|
||||
for (auto frameIt : m_frames) {
|
||||
ForkDynScopeFrame* frame = frameIt.second;
|
||||
if (!frame->instance().initialized()) continue;
|
||||
|
||||
if (!frame->linked()) {
|
||||
frame->populateClass();
|
||||
frame->linkNodes(m_memberMap);
|
||||
typesAdded = true;
|
||||
}
|
||||
|
||||
if (AstVarRef* refp = VN_CAST(frameIt.first, VarRef)) {
|
||||
if (frame->captured(refp->varp())) replaceWithMemberSel(refp, frame->instance());
|
||||
}
|
||||
}
|
||||
|
||||
if (typesAdded) v3Global.rootp()->typeTablep()->repairCache();
|
||||
}
|
||||
~DynScopeVisitor() override = default;
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Fork visitor, transforms asynchronous blocks into separate tasks
|
||||
|
||||
|
|
@ -59,10 +403,10 @@ private:
|
|||
AstVar* m_capturedVarsp = nullptr; // Local copies of captured variables
|
||||
std::set<AstVar*> m_forkLocalsp; // Variables local to a given fork
|
||||
AstArg* m_capturedVarRefsp = nullptr; // References to captured variables (as args)
|
||||
int m_createdTasksCount = 0; // Number of tasks created by this visitor
|
||||
|
||||
// METHODS
|
||||
AstVar* captureRef(AstNodeExpr* refp) {
|
||||
|
||||
AstVar* captureRef(AstVarRef* refp) {
|
||||
AstVar* varp = nullptr;
|
||||
for (varp = m_capturedVarsp; varp; varp = VN_AS(varp->nextp(), Var))
|
||||
if (varp->name() == refp->name()) break;
|
||||
|
|
@ -74,20 +418,20 @@ private:
|
|||
varp->lifetime(VLifetime::AUTOMATIC);
|
||||
m_capturedVarsp = AstNode::addNext(m_capturedVarsp, varp);
|
||||
// Use the original ref as an argument for call
|
||||
AstArg* arg = new AstArg{refp->fileline(), refp->name(), refp->cloneTree(false)};
|
||||
m_capturedVarRefsp = AstNode::addNext(m_capturedVarRefsp, arg);
|
||||
m_capturedVarRefsp
|
||||
= AstNode::addNext(m_capturedVarRefsp, new AstArg{refp->fileline(), refp->name(),
|
||||
refp->cloneTree(false)});
|
||||
}
|
||||
return varp;
|
||||
}
|
||||
|
||||
AstTask* makeTask(FileLine* fl, AstNode* stmtsp, std::string name) {
|
||||
AstTask* makeTask(FileLine* fl, AstNode* stmtsp, string name) {
|
||||
stmtsp = AstNode::addNext(static_cast<AstNode*>(m_capturedVarsp), stmtsp);
|
||||
AstTask* const taskp = new AstTask{fl, name, stmtsp};
|
||||
++m_createdTasksCount;
|
||||
return taskp;
|
||||
}
|
||||
|
||||
std::string generateTaskName(AstNode* fromp, std::string kind) {
|
||||
string generateTaskName(AstNode* fromp, string kind) {
|
||||
// TODO: Ensure no collisions occur
|
||||
return "__V" + kind + (!fromp->name().empty() ? (fromp->name() + "__") : "UNNAMED__")
|
||||
+ cvtToHex(fromp);
|
||||
|
|
@ -122,16 +466,16 @@ private:
|
|||
|
||||
if (AstBegin* beginp = VN_CAST(nodep, Begin)) {
|
||||
UASSERT(beginp->stmtsp(), "No stmtsp\n");
|
||||
const std::string taskName = generateTaskName(beginp, "__FORK_BEGIN_");
|
||||
const string taskName = generateTaskName(beginp, "__FORK_BEGIN_");
|
||||
taskp
|
||||
= makeTask(beginp->fileline(), beginp->stmtsp()->unlinkFrBackWithNext(), taskName);
|
||||
beginp->unlinkFrBack(&handle);
|
||||
VL_DO_DANGLING(beginp->deleteTree(), beginp);
|
||||
} else if (AstNodeStmt* stmtp = VN_CAST(nodep, NodeStmt)) {
|
||||
const std::string taskName = generateTaskName(stmtp, "__FORK_STMT_");
|
||||
const string taskName = generateTaskName(stmtp, "__FORK_STMT_");
|
||||
taskp = makeTask(stmtp->fileline(), stmtp->unlinkFrBack(&handle), taskName);
|
||||
} else if (AstFork* forkp = VN_CAST(nodep, Fork)) {
|
||||
const std::string taskName = generateTaskName(forkp, "__FORK_NESTED_");
|
||||
const string taskName = generateTaskName(forkp, "__FORK_NESTED_");
|
||||
taskp = makeTask(forkp->fileline(), forkp->unlinkFrBack(&handle), taskName);
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +483,8 @@ private:
|
|||
|
||||
AstTaskRef* const taskrefp
|
||||
= new AstTaskRef{nodep->fileline(), taskp->name(), m_capturedVarRefsp};
|
||||
AstStmtExpr* const taskcallp = new AstStmtExpr{nodep->fileline(), taskrefp};
|
||||
taskrefp->taskp(taskp);
|
||||
AstStmtExpr* const taskcallp = taskrefp->makeStmt();
|
||||
// Replaced nodes will be revisited, so we don't need to "lift" the arguments
|
||||
// as captures in case of nested forks.
|
||||
handle.relink(taskcallp);
|
||||
|
|
@ -223,23 +568,19 @@ public:
|
|||
// CONSTRUCTORS
|
||||
ForkVisitor(AstNetlist* nodep) { visit(nodep); }
|
||||
~ForkVisitor() override = default;
|
||||
|
||||
// UTILITY
|
||||
int createdTasksCount() { return m_createdTasksCount; }
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Fork class functions
|
||||
|
||||
int V3Fork::makeTasks(AstNetlist* nodep) {
|
||||
int createdTasksCount;
|
||||
|
||||
void V3Fork::makeDynamicScopes(AstNetlist* nodep) {
|
||||
UINFO(2, __FUNCTION__ << ": " << endl);
|
||||
{
|
||||
ForkVisitor fork_visitor(nodep);
|
||||
createdTasksCount = fork_visitor.createdTasksCount();
|
||||
}
|
||||
V3Global::dumpCheckGlobalTree("fork", 0, dumpTreeLevel() >= 3);
|
||||
|
||||
return createdTasksCount;
|
||||
{ DynScopeVisitor{nodep}; }
|
||||
V3Global::dumpCheckGlobalTree("fork_dynscope", 0, dumpTreeLevel() >= 3);
|
||||
}
|
||||
|
||||
void V3Fork::makeTasks(AstNetlist* nodep) {
|
||||
UINFO(2, __FUNCTION__ << ": " << endl);
|
||||
{ ForkVisitor{nodep}; }
|
||||
V3Global::dumpCheckGlobalTree("fork", 0, dumpTreeLevel() >= 3);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,12 @@ class AstNetlist;
|
|||
|
||||
class V3Fork final {
|
||||
public:
|
||||
// Create tasks out of begin blocks that can outlive processes in which they were forked.
|
||||
// Move/copy variables to "anonymous" objects if their lifetime might exceed the scope of a
|
||||
// procedure that declared them. Update the references apropriately.
|
||||
static void makeDynamicScopes(AstNetlist* nodep);
|
||||
// Create tasks out of blocks/statments that can outlive processes in which they were forked.
|
||||
// Return value: number of tasks created
|
||||
static int makeTasks(AstNetlist* nodep);
|
||||
static void makeTasks(AstNetlist* nodep);
|
||||
};
|
||||
|
||||
#endif // Guard
|
||||
|
|
|
|||
|
|
@ -222,9 +222,9 @@ static void process() {
|
|||
V3Inst::dearrayAll(v3Global.rootp());
|
||||
V3LinkDot::linkDotArrayed(v3Global.rootp());
|
||||
|
||||
// Create dedicated tasks for fork..join_any / fork_join_none processes
|
||||
if (V3Fork::makeTasks(v3Global.rootp()))
|
||||
V3LinkDot::linkDotPrimary(v3Global.rootp()); // Link newly created tasks
|
||||
// Generate classes and tasks required to maintain proper lifetimes for references in forks
|
||||
V3Fork::makeDynamicScopes(v3Global.rootp());
|
||||
V3Fork::makeTasks(v3Global.rootp());
|
||||
|
||||
// Task inlining & pushing BEGINs names to variables/cells
|
||||
// Begin processing must be after Param, before module inlining
|
||||
|
|
|
|||
|
|
@ -1,5 +1,58 @@
|
|||
%Error: t/t_clocking_sched.v:11:9: Input/output/inout does not appear in port list: 'clk'
|
||||
: ... In instance t
|
||||
11 | input clk;
|
||||
| ^~~
|
||||
%Error: Exiting due to
|
||||
0 | posedge
|
||||
0 | cb.y=0
|
||||
0 | b=0
|
||||
0 | z<=0
|
||||
0 | x<=0
|
||||
0 | y=0
|
||||
0 | c<=0
|
||||
0 | c<=1
|
||||
0 | cb.a=1
|
||||
0 | cb.b=1
|
||||
0 | posedge
|
||||
0 | x<=1
|
||||
0 | y=1
|
||||
0 | c<=0
|
||||
0 | cb.a=0
|
||||
0 | cb.b=1
|
||||
0 | cb.y=1
|
||||
0 | b=1
|
||||
0 | x<=0
|
||||
0 | y=0
|
||||
0 | 0 1 0 0 0 0
|
||||
2 | z<=1
|
||||
3 | posedge
|
||||
3 | c<=1
|
||||
3 | cb.a=1
|
||||
3 | cb.b=1
|
||||
3 | cb.y=0
|
||||
3 | b=0
|
||||
3 | posedge
|
||||
3 | x<=1
|
||||
3 | y=1
|
||||
3 | c<=0
|
||||
3 | cb.a=0
|
||||
3 | cb.b=1
|
||||
3 | cb.y=1
|
||||
3 | b=1
|
||||
3 | x<=0
|
||||
3 | y=0
|
||||
3 | 0 1 0 0 0 1
|
||||
5 | posedge
|
||||
5 | z<=0
|
||||
5 | c<=1
|
||||
5 | cb.a=1
|
||||
5 | cb.b=1
|
||||
5 | cb.y=0
|
||||
5 | b=0
|
||||
5 | posedge
|
||||
5 | x<=1
|
||||
5 | y=1
|
||||
5 | c<=0
|
||||
5 | cb.a=0
|
||||
5 | cb.b=1
|
||||
5 | cb.y=1
|
||||
5 | b=1
|
||||
5 | x<=0
|
||||
5 | y=0
|
||||
5 | 0 1 0 0 0 0
|
||||
*-* All Finished *-*
|
||||
|
|
|
|||
|
|
@ -7,14 +7,18 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
scenarios(linter => 1);
|
||||
scenarios(vlt => 1);
|
||||
|
||||
top_filename("t/t_clocking_sched.v");
|
||||
|
||||
lint(
|
||||
verilator_flags2 => ["--timing", "--ftaskify-all-forked"],
|
||||
expect_filename => $Self->{golden_filename},
|
||||
fails => 1,
|
||||
compile(
|
||||
timing_loop => 1,
|
||||
verilator_flags2 => ["--timing"],
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
expect_filename => $Self->{golden_filename}
|
||||
);
|
||||
|
||||
ok(1);
|
||||
|
|
|
|||
|
|
@ -2,18 +2,21 @@
|
|||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2022 by Antmicro Ltd. This program is free software; you
|
||||
# Copyright 2023 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
scenarios(linter => 1);
|
||||
scenarios(simulator => 1);
|
||||
|
||||
lint(
|
||||
verilator_flags2 => ["--timing"],
|
||||
fails => 1,
|
||||
expect_filename => $Self->{golden_filename},
|
||||
compile(
|
||||
verilator_flags2 => ["--exe --main --timing"],
|
||||
make_main => 0,
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
);
|
||||
|
||||
ok(1);
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2023 by Antmicro Ltd.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
class Foo;
|
||||
task do_something(int arg_v);
|
||||
int dynscope_var;
|
||||
int x;
|
||||
dynscope_var = 0;
|
||||
|
||||
fork
|
||||
#10 begin
|
||||
x = 0;
|
||||
// Test capturing a variable that needs to be modified
|
||||
$display("Incremented dynscope_var: %d", ++dynscope_var);
|
||||
if (dynscope_var != 1)
|
||||
$stop;
|
||||
|
||||
// Check nested access
|
||||
fork
|
||||
#10 begin
|
||||
$display("Incremented x: %d", ++x);
|
||||
$display("Incremented dynscope_var: %d", ++dynscope_var);
|
||||
if (dynscope_var != 2)
|
||||
$stop;
|
||||
end
|
||||
join_none
|
||||
end
|
||||
#10 begin
|
||||
// Same as the first check, but with an argument
|
||||
// (so it needs to be copied to the dynamic scope instead of being moved there)
|
||||
$display("Incremented arg_v: %d", ++arg_v);
|
||||
if (arg_v != 2)
|
||||
$stop;
|
||||
end
|
||||
join_none
|
||||
|
||||
// Check if regular access to arg_v has been substituted with access to its copy from
|
||||
// a dynamic scope
|
||||
$display("Incremented arg_v: %d", ++arg_v);
|
||||
if (arg_v != 1)
|
||||
$stop;
|
||||
endtask
|
||||
endclass
|
||||
|
||||
module t();
|
||||
initial begin
|
||||
Foo foo;
|
||||
foo = new;
|
||||
foo.do_something(0);
|
||||
|
||||
#99 begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2023 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--exe --main --timing"],
|
||||
make_main => 0,
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
);
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2023 by Antmicro Ltd.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
`timescale 1ns / 1ns
|
||||
|
||||
static int counts[10];
|
||||
|
||||
class Foo;
|
||||
static task do_something();
|
||||
for (int i = 0; i < 10; i++) begin // Should create a dynamic scope for `i`
|
||||
int ci = i; // Should create another dynamic scope for `ci`, local to the begin block
|
||||
fork begin
|
||||
#10;
|
||||
$display("ci: %d, i: %d", ci, i);
|
||||
if (i != 10)
|
||||
$stop;
|
||||
if (counts[ci-1]++ > 0)
|
||||
$stop;
|
||||
end join_none
|
||||
ci++;
|
||||
end
|
||||
endtask
|
||||
endclass
|
||||
|
||||
module t();
|
||||
initial begin
|
||||
int desired_counts[10];
|
||||
counts = '{10{0}};
|
||||
desired_counts = '{10{1}};
|
||||
|
||||
Foo::do_something();
|
||||
#20;
|
||||
if (counts != desired_counts)
|
||||
$stop;
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
%Error-LIFETIME: t/t_timing_fork_unsup.v:12:12: Invalid reference: Process might outlive variable `x`.
|
||||
: ... In instance t
|
||||
: ... Suggest use it as read-only to initialize a local copy at the beginning of the process, or declare it as static. It is also possible to refer by reference to objects and their members.
|
||||
12 | #6 x = 4;
|
||||
| ^
|
||||
... For error description see https://verilator.org/warn/LIFETIME?v=latest
|
||||
%Error-LIFETIME: t/t_timing_fork_unsup.v:13:12: Invalid reference: Process might outlive variable `x`.
|
||||
: ... In instance t
|
||||
: ... Suggest use it as read-only to initialize a local copy at the beginning of the process, or declare it as static. It is also possible to refer by reference to objects and their members.
|
||||
13 | #2 x++;
|
||||
| ^
|
||||
%Error-LIFETIME: t/t_timing_fork_unsup.v:14:9: Invalid reference: Process might outlive variable `x`.
|
||||
: ... In instance t
|
||||
: ... Suggest use it as read-only to initialize a local copy at the beginning of the process, or declare it as static. It is also possible to refer by reference to objects and their members.
|
||||
14 | x = #4 x * 3;
|
||||
| ^
|
||||
%Error: Exiting due to
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2022 by Antmicro Ltd.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t;
|
||||
class C;
|
||||
task f;
|
||||
int x = 0;
|
||||
fork
|
||||
#6 x = 4;
|
||||
#2 x++;
|
||||
x = #4 x * 3;
|
||||
join_none
|
||||
x = 1;
|
||||
endtask
|
||||
endclass
|
||||
|
||||
initial begin
|
||||
C o = new;
|
||||
o.f;
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue