Move variables mutated under fork..join_none/join_any blocks into anonymous objects (#4356)

This commit is contained in:
Krzysztof Boroński 2023-08-29 20:01:00 +02:00 committed by GitHub
parent e24197fd16
commit c3e19f2821
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 575 additions and 87 deletions

View File

@ -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 {

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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 *-*

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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