diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 5dc90fba4..223e8d0b4 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dc8dcf182..2997f1038 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 617a8880c..4d364ef6c 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -205,6 +205,7 @@ RAW_OBJS = \ V3File.o \ V3FileLine.o \ V3Force.o \ + V3Fork.o \ V3Gate.o \ V3Global.o \ V3Graph.o \ diff --git a/src/V3Error.h b/src/V3Error.h index da7a17a9b..335549dfa 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -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", diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp new file mode 100644 index 000000000..427775cdc --- /dev/null +++ b/src/V3Fork.cpp @@ -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 +#include + +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 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(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(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; +} diff --git a/src/V3Fork.h b/src/V3Fork.h new file mode 100644 index 000000000..8554fa75f --- /dev/null +++ b/src/V3Fork.h @@ -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 diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 219c8e30c..46daafcc8 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -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); diff --git a/src/V3Options.h b/src/V3Options.h index fea00aaf8..dcdf0d96b 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -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"); } diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index ec816bc70..72ccd8453 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -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(); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 9aeb177e1..c1654b524 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -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 diff --git a/test_regress/t/t_clocking_sched_timing_forkproc.out b/test_regress/t/t_clocking_sched_timing_forkproc.out new file mode 100755 index 000000000..8094d59d9 --- /dev/null +++ b/test_regress/t/t_clocking_sched_timing_forkproc.out @@ -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 diff --git a/test_regress/t/t_clocking_sched_timing_forkproc.pl b/test_regress/t/t_clocking_sched_timing_forkproc.pl new file mode 100755 index 000000000..d1bb14c97 --- /dev/null +++ b/test_regress/t/t_clocking_sched_timing_forkproc.pl @@ -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; diff --git a/test_regress/t/t_fork_join_none_any_nested.pl b/test_regress/t/t_fork_join_none_any_nested.pl new file mode 100755 index 000000000..b267631e9 --- /dev/null +++ b/test_regress/t/t_fork_join_none_any_nested.pl @@ -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; diff --git a/test_regress/t/t_fork_join_none_any_nested.v b/test_regress/t/t_fork_join_none_any_nested.v new file mode 100644 index 000000000..69f043e0c --- /dev/null +++ b/test_regress/t/t_fork_join_none_any_nested.v @@ -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 diff --git a/test_regress/t/t_fork_join_none_class_cap.pl b/test_regress/t/t_fork_join_none_class_cap.pl new file mode 100755 index 000000000..b8493bd06 --- /dev/null +++ b/test_regress/t/t_fork_join_none_class_cap.pl @@ -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; diff --git a/test_regress/t/t_fork_join_none_class_cap.v b/test_regress/t/t_fork_join_none_class_cap.v new file mode 100644 index 000000000..8f5fda807 --- /dev/null +++ b/test_regress/t/t_fork_join_none_class_cap.v @@ -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 diff --git a/test_regress/t/t_timing_fork_join_forkproc.out b/test_regress/t/t_timing_fork_join_forkproc.out new file mode 100644 index 000000000..46f216b1e --- /dev/null +++ b/test_regress/t/t_timing_fork_join_forkproc.out @@ -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 *-* diff --git a/test_regress/t/t_timing_fork_join_forkproc.pl b/test_regress/t/t_timing_fork_join_forkproc.pl new file mode 100755 index 000000000..61664fabe --- /dev/null +++ b/test_regress/t/t_timing_fork_join_forkproc.pl @@ -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; diff --git a/test_regress/t/t_timing_fork_unsup.out b/test_regress/t/t_timing_fork_unsup.out index 39713d51f..399dfd1c3 100644 --- a/test_regress/t/t_timing_fork_unsup.out +++ b/test_regress/t/t_timing_fork_unsup.out @@ -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 diff --git a/test_regress/t/t_timing_fork_unsup.v b/test_regress/t/t_timing_fork_unsup.v index 39c141452..72a698e6d 100644 --- a/test_regress/t/t_timing_fork_unsup.v +++ b/test_regress/t/t_timing_fork_unsup.v @@ -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 diff --git a/test_regress/t/t_var_in_fork.pl b/test_regress/t/t_var_in_fork.pl new file mode 100755 index 000000000..b267631e9 --- /dev/null +++ b/test_regress/t/t_var_in_fork.pl @@ -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; diff --git a/test_regress/t/t_var_in_fork.v b/test_regress/t/t_var_in_fork.v new file mode 100644 index 000000000..b47ef920d --- /dev/null +++ b/test_regress/t/t_var_in_fork.v @@ -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