V3Fork - transform processes that can outlive their parents into separate tasks (#4253)

Signed-off-by: Krzysztof Boronski <kboronski@antmicro.com>
This commit is contained in:
Krzysztof Boronski 2023-06-14 20:44:53 +02:00
parent b517fb5ee3
commit 21c01ba97b
22 changed files with 704 additions and 46 deletions

View File

@ -876,6 +876,102 @@ List Of Warnings
Ignoring this warning will only suppress the lint check; it will
simulate correctly.
.. option:: LIFETIME
Error when a variable is referenced in a process that can outlive the process
in which it was declared. This can happen when using 'fork..join_none' or
'fork..join_any' blocks, which spawn process that can outlive their parents.
This error occurs only when Verilator can't replace the reference with a
reference to copy of this variable, local to the forked process. For example:
.. code-block:: sv
:linenos:
:emphasize-lines: 3
task foo(int local_var);
fork
#10 local_var++;
#20 $display("local_var = %d", local_var);
join_none
endtask
In the example above 'local_var' exists only within scope of 'foo', once foo
finishes, the stack frame containing 'i' gets removed. However, the process
forked from foo continues, as it contains a delay. After 10 units of time
pass, this process attempts to modify 'local_var'. However, this variable no
longer exits. It can't be made local to the forked process upon spawning, because
it's modified and can be referenced somewhere else, for example in the other
forked process, that was delayed by 20 units of time in this example. Thus,
there's no viable stack allocation for it.
In order to fix it, you can create a local copy of the varible for
each process, if you don't intend to share its state outside of those processes.
Eg.:
.. code-block:: sv
:linenos:
:emphasize-lines: 4
task foo(int local_var);
fork
#10 begin
int forked_var = local_var;
forked_var++;
end
#20 begin
// Note that we are going to print the original value here,
// as `forked_var`is a local copy that was initialized while
// `foo` was still alive.
int forked_var = local_var;
$display("forked_var = %d", forked_var)
end
join_none
endtask
If you need to share its state, another strategy is to ensure it's allocated
statically:
.. code-block:: sv
:linenos:
:emphasize-lines: 1
int static_var;
task foo();
fork
#10 static_var++;
#20 $display("static_var = %d", static_var);
join_none
endtask
However, if you need to be able to instantiate at runtime, the solution would be to
wrap it in an object, since the forked process can hold a reference to that object
and ensure that the variable stays alive this way:
.. code-block:: sv
:linenos:
:emphasize-lines: 2q
class Wrapper;
int m_var;
// Here we implicitly hold a reference to `this`
task foo();
fork
#10 m_var++;
#20 $display("this.m_var = %d", m_var);
join_none
endtask
endclass
// Here we explicitly hold a handle to an object
task bar(Wrapper wrapper);
fork
#10 wrapper.m_var++;
#20 $display("wrapper.m_var = %d", wrapper.m_var);
join_none
endtask
.. option:: LITENDIAN

View File

@ -82,6 +82,7 @@ set(HEADERS
V3File.h
V3FileLine.h
V3Force.h
V3Fork.h
V3FunctionTraits.h
V3Gate.h
V3Global.h
@ -227,6 +228,7 @@ set(COMMON_SOURCES
V3File.cpp
V3FileLine.cpp
V3Force.cpp
V3Fork.cpp
V3Gate.cpp
V3Global.cpp
V3Graph.cpp

View File

@ -205,6 +205,7 @@ RAW_OBJS = \
V3File.o \
V3FileLine.o \
V3Force.o \
V3Fork.o \
V3Gate.o \
V3Global.o \
V3Graph.o \

View File

@ -60,6 +60,7 @@ public:
I_TRACING, // Tracing is on/off from /*verilator tracing_on/off*/
I_UNUSED, // Unused genvar, parameter or signal message (Backward Compatibility)
// Error codes:
E_LIFETIME, // Error: Reference to a variable might outlive the variable.
E_NEEDTIMINGOPT, // Error: --timing/--no-timing option not specified
E_NOTIMING, // Timing control encountered with --no-timing
E_PORTSHORT, // Error: Output port is connected to a constant, electrical short
@ -183,7 +184,7 @@ public:
// Boolean
" I_CELLDEFINE", " I_COVERAGE", " I_DEF_NETTYPE_WIRE", " I_LINT", " I_TIMING", " I_TRACING", " I_UNUSED",
// Errors
"NEEDTIMINGOPT", "NOTIMING", "PORTSHORT", "TASKNSVAR", "UNSUPPORTED",
"LIFETIME", "NEEDTIMINGOPT", "NOTIMING", "PORTSHORT", "TASKNSVAR", "UNSUPPORTED",
// Warnings
" EC_FIRST_WARN",
"ALWCOMBORDER", "ASCRANGE", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA",

245
src/V3Fork.cpp Normal file
View File

@ -0,0 +1,245 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Create separate tasks for forked processes that
// can outlive their parents
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-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
//
//*************************************************************************
// V3Fork's Transformations:
//
// Each module:
// Look for FORKs [JOIN_NONE]/[JOIN_ANY]
// 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.
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Fork.h"
#include "V3Ast.h"
#include "V3AstNodeExpr.h"
#include "V3Global.h"
#include <algorithm>
#include <set>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Fork visitor, transforms asynchronous blocks into separate tasks
class ForkVisitor final : public VNVisitor {
private:
// NODE STATE
// AstNode::user1() -> bool, 1 = Node was created as a call to an asynchronous task
// 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; // Class/module we are currently under
int m_forkDepth = 0; // Nesting level of asynchronous forks
bool m_newProcess = false; // True if we are directly under an asynchronous fork.
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* varp = nullptr;
for (varp = m_capturedVarsp; varp; varp = VN_AS(varp->nextp(), Var))
if (varp->name() == refp->name()) break;
if (!varp) {
// Create a local copy for a capture
varp = new AstVar{refp->fileline(), VVarType::BLOCKTEMP, refp->name(), refp->dtypep()};
varp->direction(VDirection::INPUT);
varp->funcLocal(true);
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);
}
return varp;
}
AstTask* makeTask(FileLine* fl, AstNode* stmtsp, std::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) {
// TODO: Ensure no collisions occur
return "__V" + kind + (!fromp->name().empty() ? (fromp->name() + "__") : "UNNAMED__")
+ cvtToHex(fromp);
}
void visitTaskifiable(AstNode* nodep) {
if (!m_newProcess || nodep->user1()) {
VL_RESTORER(m_forkDepth);
if (nodep->user1()) {
UASSERT(m_forkDepth > 0, "Wrong fork depth!");
--m_forkDepth;
}
iterateChildren(nodep);
return;
}
VL_RESTORER(m_capturedVarsp);
VL_RESTORER(m_capturedVarRefsp);
VL_RESTORER(m_newProcess);
m_capturedVarsp = nullptr;
m_capturedVarRefsp = nullptr;
m_newProcess = false;
iterateChildren(nodep);
// If there are no captures, there's no need to taskify
if (m_forkLocalsp.empty() && (m_capturedVarsp == nullptr) && !v3Global.opt.fTaskifyAll())
return;
VNRelinker handle;
AstTask* taskp = nullptr;
if (AstBegin* beginp = VN_CAST(nodep, Begin)) {
UASSERT(beginp->stmtsp(), "No stmtsp\n");
const std::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_");
taskp = makeTask(stmtp->fileline(), stmtp->unlinkFrBack(&handle), taskName);
} else if (AstFork* forkp = VN_CAST(nodep, Fork)) {
const std::string taskName = generateTaskName(forkp, "__FORK_NESTED_");
taskp = makeTask(forkp->fileline(), forkp->unlinkFrBack(&handle), taskName);
}
m_modp->addStmtsp(taskp);
AstTaskRef* const taskrefp
= new AstTaskRef{nodep->fileline(), taskp->name(), m_capturedVarRefsp};
AstStmtExpr* const taskcallp = new AstStmtExpr{nodep->fileline(), taskrefp};
// Replaced nodes will be revisited, so we don't need to "lift" the arguments
// as captures in case of nested forks.
handle.relink(taskcallp);
taskcallp->user1(true);
}
// VISITORS
void visit(AstFork* nodep) override {
bool nested = m_newProcess;
VL_RESTORER(m_forkLocalsp);
VL_RESTORER(m_newProcess);
VL_RESTORER(m_forkDepth)
if (!nodep->joinType().join()) {
++m_forkDepth;
m_newProcess = true;
m_forkLocalsp.clear();
// Nested forks get moved into separate tasks
if (nested) {
visitTaskifiable(nodep);
return;
}
} else {
m_newProcess = false;
}
iterateChildren(nodep);
}
void visit(AstBegin* nodep) override { visitTaskifiable(nodep); }
void visit(AstNodeStmt* nodep) override { visitTaskifiable(nodep); }
void visit(AstVar* nodep) override {
if (m_forkDepth) m_forkLocalsp.insert(nodep);
}
void visit(AstVarRef* nodep) override {
// VL_KEEP_THIS ensures that we hold a handle to the class
if (m_forkDepth && !nodep->varp()->isFuncLocal() && nodep->varp()->isClassMember()) return;
if (m_forkDepth && (m_forkLocalsp.count(nodep->varp()) == 0)
&& !nodep->varp()->lifetime().isStatic()) {
if (nodep->access().isWriteOrRW()
&& (!nodep->isClassHandleValue() || nodep->user2())) {
nodep->v3warn(
E_LIFETIME,
"Invalid reference: Process might outlive variable `"
<< nodep->varp()->name() << "`.\n"
<< nodep->varp()->warnMore()
<< "... 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.");
return;
}
UASSERT_OBJ(
!nodep->varp()->lifetime().isNone(), nodep,
"Variable's lifetime is unknown. Can't determine if a capture is necessary.");
AstVar* const varp = captureRef(nodep);
nodep->varp(varp);
}
}
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(AstThisRef* nodep) override { return; }
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
m_modp = nodep;
iterateChildren(nodep);
}
void visit(AstNode* nodep) override {
VL_RESTORER(m_newProcess)
VL_RESTORER(m_forkDepth);
if (nodep->user1()) --m_forkDepth;
m_newProcess = false;
iterateChildren(nodep);
}
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;
UINFO(2, __FUNCTION__ << ": " << endl);
{
ForkVisitor fork_visitor(nodep);
createdTasksCount = fork_visitor.createdTasksCount();
}
V3Global::dumpCheckGlobalTree("fork", 0, dumpTreeLevel() >= 3);
return createdTasksCount;
}

35
src/V3Fork.h Normal file
View File

@ -0,0 +1,35 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Create separate tasks for forked processes that
// can outlive their parents
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-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
//
//*************************************************************************
#ifndef VERILATOR_V3FORK_H_
#define VERILATOR_V3FORK_H_
#include "config_build.h"
#include "verilatedos.h"
class AstNetlist;
//============================================================================
class V3Fork final {
public:
// Create tasks out of begin blocks that can outlive processes in which they were forked.
// Return value: number of tasks created
static int makeTasks(AstNetlist* nodep);
};
#endif // Guard

View File

@ -1224,6 +1224,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
DECL_OPTION("-fsubst", FOnOff, &m_fSubst);
DECL_OPTION("-fsubst-const", FOnOff, &m_fSubstConst);
DECL_OPTION("-ftable", FOnOff, &m_fTable);
DECL_OPTION("-ftaskify-all-forked", FOnOff, &m_fTaskifyAll).undocumented(); // Debug
DECL_OPTION("-G", CbPartialMatch, [this](const char* optp) { addParameter(optp, false); });
DECL_OPTION("-gate-stmts", Set, &m_gateStmts);

View File

@ -374,6 +374,7 @@ private:
bool m_fSubst; // main switch: -fno-subst: substitute expression temp values
bool m_fSubstConst; // main switch: -fno-subst-const: final constant substitution
bool m_fTable; // main switch: -fno-table: lookup table creation
bool m_fTaskifyAll = false; // main switch: --ftaskify-all-forked
// clang-format on
bool m_available = false; // Set to true at the end of option parsing
@ -633,6 +634,7 @@ public:
bool fSubst() const { return m_fSubst; }
bool fSubstConst() const { return m_fSubstConst; }
bool fTable() const { return m_fTable; }
bool fTaskifyAll() const { return m_fTaskifyAll; }
string traceClassBase() const { return m_traceFormat.classBase(); }
string traceClassLang() const { return m_traceFormat.classBase() + (systemC() ? "Sc" : "C"); }

View File

@ -299,17 +299,6 @@ void transformForks(AstNetlist* const netlistp) {
// Not func local, or not declared before the fork. Their lifetime is longer
// than the forked process. Skip
return;
} else if (m_forkp->joinType().join()) {
// If it's fork..join, we can refer to variables from the parent process
} else {
// TODO: It is possible to relax this by allowing the use of such variables up
// until the first await. Also, variables defined within a forked process
// (inside a begin) are extracted out by V3Begin, so they also trigger this
// error. Preventing this (or detecting such cases and moving the vars back)
// would also allow for using them freely.
refp->v3warn(E_UNSUPPORTED, "Unsupported: variable local to a forking process "
"accessed in a fork..join_any or fork..join_none");
return;
}
// Remap the reference
AstVarScope* const vscp = refp->varScopep();

View File

@ -49,6 +49,7 @@
#include "V3Expand.h"
#include "V3File.h"
#include "V3Force.h"
#include "V3Fork.h"
#include "V3Gate.h"
#include "V3Global.h"
#include "V3Graph.h"
@ -221,6 +222,10 @@ 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
// Task inlining & pushing BEGINs names to variables/cells
// Begin processing must be after Param, before module inlining
V3Begin::debeginAll(v3Global.rootp()); // Flatten cell names, before inliner

View File

@ -0,0 +1,5 @@
%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

View File

@ -0,0 +1,21 @@
#!/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(linter => 1);
top_filename("t/t_clocking_sched.v");
lint(
verilator_flags2 => ["--timing", "--ftaskify-all-forked"],
expect_filename => $Self->{golden_filename},
fails => 1,
);
ok(1);
1;

View File

@ -0,0 +1,22 @@
#!/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 => ["--timing"],
);
execute(
check_finished => 1,
);
ok(1);
1;

View File

@ -0,0 +1,56 @@
// 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
event evt1, evt2, evt3;
class Foo;
task do_something(int cap1, int cap2);
fork
begin
$display("outer fork: %d", cap1);
fork
$display("inner fork: %d", cap2);
->evt2;
fork
$display("innermost fork: %d", cap2);
->evt3;
join_none
join_none
end
->evt1;
join_any
endtask
endclass
module t();
reg a, b, c;
initial begin
Foo foo;
a = 1'b0;
b = 1'b0;
foo = new;
foo.do_something(1, 2);
end
always @(evt1) begin
a <= 1;
end
always @(evt2) begin
b <= 1;
end
always @(evt3) begin
c <= 1;
end
always @(a, b, c) begin
if (a & b & c) 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,52 @@
// 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
event evt1;
class Foo;
int m_member;
task do_something();
fork
#20 begin
m_member++;
$display("this's m_member: ", m_member);
if (m_member != 3)
$stop;
->evt1;
end
#10 begin
m_member++;
bar(this);
end
join_none
endtask
static task bar(Foo foo);
fork
begin
foo.m_member++;
$display("foo's m_member: %d", foo.m_member);
if (foo.m_member != 2)
$stop;
end
join_none
endtask
endclass
module t();
initial begin
Foo foo;
foo = new;
foo.m_member = 0;
foo.do_something();
end
always @(evt1) begin
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,25 @@
[0] fork..join process 4
[2] fork..join process 3
[4] fork..join process 2
[8] fork..join process 1
[16] fork in fork starts
[16] fork..join process 8
[20] fork..join process 7
[24] fork..join process 6
[32] fork..join process 5
[32] fork..join in fork ends
[64] main process
fork..join_any process 2
back in main process
fork..join_any process 1
fork..join_any process 1
back in main process
fork..join_any process 2
in main process
fork..join_none process 1
fork..join_none process 2
fork..join_none process 3
fork..join_none process 2 again
fork..join_none process 1 again
fork..join_none process 3 again
*-* All Finished *-*

View File

@ -0,0 +1,26 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2022 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);
top_filename("t/t_timing_fork_join.v");
compile(
verilator_flags2 => ["--exe --main --timing --ftaskify-all-forked"],
make_main => 0,
);
execute(
check_finished => 1,
expect_filename => $Self->{golden_filename},
);
ok(1);
1;

View File

@ -1,34 +1,17 @@
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:12:12: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
: ... In instance t::C
%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/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:13:12: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
: ... In instance t::C
... 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-UNSUPPORTED: t/t_timing_fork_unsup.v:14:16: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
: ... In instance t::C
14 | x = #4 x * 3;
| ^
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:14:9: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
: ... In instance t::C
%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-UNSUPPORTED: t/t_timing_fork_unsup.v:16:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
: ... In instance t::C
16 | #1 if (x != 1) $stop;
| ^
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:17:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
: ... In instance t::C
17 | #2 if (x != 2) $stop;
| ^
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:18:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
: ... In instance t::C
18 | #2 if (x != 3) $stop;
| ^
%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:19:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none
: ... In instance t::C
19 | #2 if (x != 4) $stop;
| ^
%Error: Exiting due to

View File

@ -12,13 +12,6 @@ module t;
#6 x = 4;
#2 x++;
x = #4 x * 3;
begin
#1 if (x != 1) $stop;
#2 if (x != 2) $stop;
#2 if (x != 3) $stop;
#2 if (x != 4) $stop;
$finish;
end
join_none
x = 1;
endtask

22
test_regress/t/t_var_in_fork.pl Executable file
View File

@ -0,0 +1,22 @@
#!/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 => ["--timing"],
);
execute(
check_finished => 1,
);
ok(1);
1;

View File

@ -0,0 +1,53 @@
// 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
int static_var;
module t();
event evt;
task send_event();
->evt;
endtask
class Foo;
function void do_something(int captured_var);
fork
begin
int my_var;
int my_other_var;
my_var = captured_var;
my_other_var = captured_var; /* Capture the same value "twice" */
my_var++;
static_var++; /* Write to a value with static lifetime (valid) */
$display("Vars in forked process: %d %d", my_var, my_other_var);
if (my_var != 2)
$stop;
if (my_other_var != 1)
$stop;
send_event();
end
join_none
$display("Leaving fork's parent");
endfunction
endclass
initial begin
Foo foo;
foo = new;
static_var = 0;
foo.do_something(1);
end
always @(evt) begin
$display("Static variable: %d", static_var);
if (static_var != 1)
$stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule