diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 416947bd1..36e151053 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -298,12 +298,39 @@ class BeginVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstVar* nodep) override { + if (nodep->user1SetOnce()) { return; } // If static variable, move it outside a function. if (nodep->lifetime().isStatic() && m_ftaskp) { const std::string newName = m_ftaskp->name() + "__Vstatic__" + dot(m_unnamedScope, nodep->name()); + if (nodep->isIO()) { + if (nodep->direction().isRef()) { + nodep->v3error( + "It is illegal to use argument passing by reference for subroutines with " + "a lifetime of static (IEEE 1800-2023 13.5.2)"); + } + // Create a port that is used for passing value between argument and static + // variable + AstVar* const portp = nodep->cloneTreePure(false); + nodep->replaceWith(portp); + + if (nodep->isInput() || nodep->isInout()) { + AstAssign* const initAssignp = new AstAssign{ + nodep->fileline(), new AstVarRef{nodep->fileline(), nodep, VAccess::WRITE}, + new AstVarRef{portp->fileline(), portp, VAccess::READ}}; + portp->addNextHere(initAssignp); + } + + if (nodep->isWritable()) { + AstAssign* const endAssignp = new AstAssign{ + nodep->fileline(), new AstVarRef{portp->fileline(), portp, VAccess::WRITE}, + new AstVarRef{nodep->fileline(), nodep, VAccess::READ}}; + m_ftaskp->addStmtsp(endAssignp); + } + } else { + nodep->unlinkFrBack(); + } nodep->name(newName); - nodep->unlinkFrBack(); m_ftaskp->addHereThisAsNext(nodep); nodep->funcLocal(false); } else if (m_unnamedScope != "") { diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp index 737b31429..0071d8f75 100644 --- a/src/V3Fork.cpp +++ b/src/V3Fork.cpp @@ -443,20 +443,11 @@ class DynScopeVisitor final : public VNVisitor { if (!isEvent && m_afterTimingControl && nodep->varp()->isWritable() && nodep->access().isWriteOrRW()) { // The output variable may not exist after a delay, so we can't just write to it - if (m_inFunc) { - nodep->v3error( - "Writing to an " - << nodep->varp()->verilogKwd() - << " variable of a function after a timing control is not allowed"); - } else { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: Writing to a captured " - << nodep->varp()->verilogKwd() - << " variable in a " - << (VN_IS(nodep->backp(), AssignDly) - ? "non-blocking assignment" - : "fork") - << " after a timing control"); - } + nodep->v3error( + "Writing to an " + << nodep->varp()->verilogKwd() << " automatic variable of a " + << (m_inFunc ? "function" : "task") + << " after a timing control is not allowed (IEEE 1800-2023 13.2.2)"); } if (!framep->instance().initialized()) framep->createInstancePrototype(); framep->captureVarInsert(nodep->varp()); diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index c185e6f52..ff4e57a07 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -70,6 +70,7 @@ class LinkParseVisitor final : public VNVisitor { int m_randSequenceNum = 0; // RandSequence uniqify number VLifetime m_lifetime = VLifetime::STATIC_IMPLICIT; // Propagating lifetime bool m_lifetimeAllowed = false; // True to allow lifetime settings + bool m_hasTimingControl = false; // If current task has timing control bool m_moduleWithGenericIface = false; // If current module contains generic interface std::set m_portDups; // Non-ANSI port datatype duplicating input/output decls @@ -249,6 +250,9 @@ class LinkParseVisitor final : public VNVisitor { VL_RESTORER(m_lifetime); VL_RESTORER(m_lifetimeAllowed); m_lifetimeAllowed = true; + VL_RESTORER(m_hasTimingControl); + m_hasTimingControl + = nodep->exists([](const AstNode* const nodep) { return nodep->isTimingControl(); }); if (!nodep->lifetime().isNone()) { m_lifetime = nodep->lifetime().makeImplicit(); } else { @@ -383,7 +387,13 @@ class LinkParseVisitor final : public VNVisitor { } } } else if (m_ftaskp) { - if (!nodep->lifetime().isAutomatic()) nodep->lifetime(VLifetime::AUTOMATIC_IMPLICIT); + // Variables in static tasks with timing control can be used after the task has ended + // with use of join..fork_none, so they need to be static + if (m_ftaskp->lifetime().isStatic() && m_hasTimingControl) { + nodep->lifetime(VLifetime::STATIC_IMPLICIT); + } else { + nodep->lifetime(VLifetime::AUTOMATIC_IMPLICIT); + } } else if (nodep->lifetime().isNone()) { // lifetime shouldn't be unknown, set static if none nodep->lifetime(VLifetime::STATIC_IMPLICIT); diff --git a/test_regress/t/t_fork_dynscope_unsup.out b/test_regress/t/t_fork_dynscope_unsup.out deleted file mode 100644 index c87c7fd3f..000000000 --- a/test_regress/t/t_fork_dynscope_unsup.out +++ /dev/null @@ -1,10 +0,0 @@ -%Error-UNSUPPORTED: t/t_fork_dynscope_unsup.v:17:7: Unsupported: Writing to a captured inout variable in a fork after a timing control - : ... note: In instance 't' - 17 | p = #1 1; - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_fork_dynscope_unsup.v:22:5: Unsupported: Writing to a captured output variable in a non-blocking assignment after a timing control - : ... note: In instance 't' - 22 | q <= #1 1; - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_fork_write_after_timing.py b/test_regress/t/t_fork_write_after_timing.py new file mode 100755 index 000000000..7ded63f3a --- /dev/null +++ b/test_regress/t/t_fork_write_after_timing.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fork_write_after_timing.v b/test_regress/t/t_fork_write_after_timing.v new file mode 100644 index 000000000..93b778ad6 --- /dev/null +++ b/test_regress/t/t_fork_write_after_timing.v @@ -0,0 +1,118 @@ +// DESCRIPTION: Verilator: Verilog Test module +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + int x1, x2, x3, x4, x5, x6, x7; + + initial begin + x1 = -1; + x2 = -1; + x3 = -1; + x4 = -1; + + #1; + t1(x1); + t2(x2); + t3(x3); + t4(x4); + `checkd(x1, 0) + `checkd(x2, -1) + `checkd(x3, 1) + `checkd(x4, 2) + + #10 t1(x1); + t2(x2); + t3(x3); + t4(x4); + `checkd(x1, 1) + `checkd(x2, -1) + `checkd(x3, 1) + `checkd(x4, 2) + + `checkd(x5, 3) + `checkd(x6, 0) + `checkd(x7, 4) + + #5; + `checkd(x1, 1) + `checkd(x2, -1) + `checkd(x3, 1) + `checkd(x4, 2) + `checkd(x5, 3) + `checkd(x6, 0) + `checkd(x7, 4) + + $write("*-* All Finished *-*\n"); + $finish; + end + always #1 t5(x5); + always #1 t6(x6); + always #1 t7(x7); + + task t1(output int x); + fork + x = #1 1; + join_none + if ($time < 10) begin + `checkd(x, 0) + end + else begin + `checkd(x, 1) + end + endtask + + task t2(inout int x); + fork + x = #1 2; + join_none + `checkd(x, -1) + endtask + + task t3(output int x); + if ($time < 10) begin + `checkd(x, 0) + end + else begin + `checkd(x, 1) + end + fork + x = #1 1; + join_none + #2 `checkd(x, 1); + endtask + + task t4(inout int x); + if ($time < 10) begin + `checkd(x, -1) + end + else begin + `checkd(x, 2) + end + fork + x = #1 2; + join_none + #2 `checkd(x, 2); + endtask + + task t5(output int x); + x <= #1 3; + endtask + + task t6(inout int x); + x <= #1 4; + endtask + + task static t7(inout int x); + int y = 0; + x <= #1 4; + #2 y = x; + `checkd(x, 4) + endtask +endmodule diff --git a/test_regress/t/t_fork_write_after_timing_bad.out b/test_regress/t/t_fork_write_after_timing_bad.out new file mode 100644 index 000000000..99c600148 --- /dev/null +++ b/test_regress/t/t_fork_write_after_timing_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_fork_write_after_timing_bad.v:12:19: It is illegal to use argument passing by reference for subroutines with a lifetime of static (IEEE 1800-2023 13.5.2) + : ... note: In instance 't' + 12 | task t1(ref int x); + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_fork_write_after_timing_bad.py b/test_regress/t/t_fork_write_after_timing_bad.py new file mode 100755 index 000000000..d77726df8 --- /dev/null +++ b/test_regress/t/t_fork_write_after_timing_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.lint(verilator_flags2=["--timing"], fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_fork_dynscope_unsup.v b/test_regress/t/t_fork_write_after_timing_bad.v similarity index 55% rename from test_regress/t/t_fork_dynscope_unsup.v rename to test_regress/t/t_fork_write_after_timing_bad.v index 893c44a8b..27c133140 100644 --- a/test_regress/t/t_fork_dynscope_unsup.v +++ b/test_regress/t/t_fork_write_after_timing_bad.v @@ -1,24 +1,17 @@ // DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro // SPDX-License-Identifier: CC0-1.0 module t; - bit p = 0, q = 0; - + int x1; initial begin - t1(p); - t2(q); + t1(x1); + $finish; end - - task t1(inout p); + task t1(ref int x); fork - p = #1 1; + x = #1 2; join_none endtask - - task t2(output q); - q <= #1 1; - endtask endmodule diff --git a/test_regress/t/t_static_task_args.py b/test_regress/t/t_static_task_args.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_static_task_args.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_static_task_args.v b/test_regress/t/t_static_task_args.v new file mode 100644 index 000000000..1124ffda0 --- /dev/null +++ b/test_regress/t/t_static_task_args.v @@ -0,0 +1,113 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + // test static argument + task t1(output int x); + x = #1 x + 1; + endtask + + // test concurrent executions of static tasks + int expected; + task t2(inout int x); + #5 `checkd(x, expected); + endtask + + task factorial1 (input [31:0] x, output [31:0] out); + if (x >= 2) begin + factorial1 (x - 1, out); + out = out * x; + end + else + out = 1; + endtask + + function int factorial2 (input int x); + if (x >= 2) + factorial2 = factorial2(x - 1) * x; + else + factorial2 = 1; + endfunction + + int result; + initial begin + // t1 + result = 0; + t1(result); + `checkd(result, 1); + t1(result); + `checkd(result, 2); + t1(result); + `checkd(result, 3); + t1(result); + `checkd(result, 4); + + factorial1(1, result); + `checkd(result, 1); + factorial1(3, result); + `checkd(result, 6); + factorial1(5, result); + `checkd(result, 120); + + `checkd(factorial2(1), 1); + `checkd(factorial2(3), 6); + `checkd(factorial2(5), 120); + + // t2 + expected = 3; + fork + begin + static int x1 = 1; + t2(x1); + `checkd(x1, 3); + end + join_none + #2 + fork + begin + static int x2 = 3; + t2(x2); + `checkd(x2, 3); + end + join_none + + #10 + expected = 10; + fork + begin + static int x3 = 99; + t2(x3); + `checkd(x3, 10); + end + join_none + #1 + fork + begin + static int x4 = 123; + t2(x4); + `checkd(x4, 10); + end + join_none + #1 + fork + begin + static int x5 = 10; + t2(x5); + `checkd(x5, 10); + end + join_none + + + #10 + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_static_task_args_bad.out b/test_regress/t/t_static_task_args_bad.out new file mode 100644 index 000000000..6f97f433d --- /dev/null +++ b/test_regress/t/t_static_task_args_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_static_task_args_bad.v:12:19: It is illegal to use argument passing by reference for subroutines with a lifetime of static (IEEE 1800-2023 13.5.2) + : ... note: In instance 't' + 12 | task t1(ref int x); + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_fork_dynscope_unsup.py b/test_regress/t/t_static_task_args_bad.py similarity index 100% rename from test_regress/t/t_fork_dynscope_unsup.py rename to test_regress/t/t_static_task_args_bad.py diff --git a/test_regress/t/t_static_task_args_bad.v b/test_regress/t/t_static_task_args_bad.v new file mode 100644 index 000000000..f10a50727 --- /dev/null +++ b/test_regress/t/t_static_task_args_bad.v @@ -0,0 +1,15 @@ +// DESCRIPTION: Verilator: Verilog Test module +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + int x1; + initial begin + t1(x1); + $finish; + end + task t1(ref int x); + x = #1 1; + endtask +endmodule diff --git a/test_regress/t/t_timing_func_fork_bad.out b/test_regress/t/t_timing_func_fork_bad.out index b9fa6bbce..8e594bf3d 100644 --- a/test_regress/t/t_timing_func_fork_bad.out +++ b/test_regress/t/t_timing_func_fork_bad.out @@ -1,42 +1,46 @@ -%Error: t/t_timing_func_fork_bad.v:13:9: Writing to an output variable of a function after a timing control is not allowed +%Error: t/t_timing_func_fork_bad.v:13:9: Writing to an output automatic variable of a function after a timing control is not allowed (IEEE 1800-2023 13.2.2) : ... note: In instance 't' 13 | f1 = 0; | ^~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_timing_func_fork_bad.v:14:9: Writing to an output variable of a function after a timing control is not allowed +%Error: t/t_timing_func_fork_bad.v:14:9: Writing to an output automatic variable of a function after a timing control is not allowed (IEEE 1800-2023 13.2.2) : ... note: In instance 't' 14 | o1 = 0; | ^~ -%Error: t/t_timing_func_fork_bad.v:22:9: Writing to an output variable of a function after a timing control is not allowed +%Error: t/t_timing_func_fork_bad.v:22:7: Writing to an output automatic variable of a task after a timing control is not allowed (IEEE 1800-2023 13.2.2) : ... note: In instance 't' - 22 | f2 = #5 0; + 22 | o1 = 0; + | ^~ +%Error: t/t_timing_func_fork_bad.v:29:9: Writing to an output automatic variable of a function after a timing control is not allowed (IEEE 1800-2023 13.2.2) + : ... note: In instance 't' + 29 | f2 = #5 0; | ^~ -%Error: t/t_timing_func_fork_bad.v:24:9: Writing to an inout variable of a function after a timing control is not allowed +%Error: t/t_timing_func_fork_bad.v:31:9: Writing to an inout automatic variable of a function after a timing control is not allowed (IEEE 1800-2023 13.2.2) : ... note: In instance 't' - 24 | io2 = 0; + 31 | io2 = 0; | ^~~ -%Error: t/t_timing_func_fork_bad.v:34:9: Writing to an output variable of a function after a timing control is not allowed +%Error: t/t_timing_func_fork_bad.v:41:9: Writing to an output automatic variable of a function after a timing control is not allowed (IEEE 1800-2023 13.2.2) : ... note: In instance 't' - 34 | f3 = 0; + 41 | f3 = 0; | ^~ -%Error: t/t_timing_func_fork_bad.v:35:9: Writing to an output variable of a function after a timing control is not allowed +%Error: t/t_timing_func_fork_bad.v:42:9: Writing to an output automatic variable of a function after a timing control is not allowed (IEEE 1800-2023 13.2.2) : ... note: In instance 't' - 35 | o3 = 0; + 42 | o3 = 0; | ^~ -%Error: t/t_timing_func_fork_bad.v:43:9: Writing to an output variable of a function after a timing control is not allowed +%Error: t/t_timing_func_fork_bad.v:50:9: Writing to an output automatic variable of a function after a timing control is not allowed (IEEE 1800-2023 13.2.2) : ... note: In instance 't' - 43 | f4 = @e 0; + 50 | f4 = @e 0; | ^~ -%Error: t/t_timing_func_fork_bad.v:45:9: Writing to an inout variable of a function after a timing control is not allowed +%Error: t/t_timing_func_fork_bad.v:52:9: Writing to an inout automatic variable of a function after a timing control is not allowed (IEEE 1800-2023 13.2.2) : ... note: In instance 't' - 45 | io4 = 0; + 52 | io4 = 0; | ^~~ -%Error: t/t_timing_func_fork_bad.v:56:9: Writing to an output variable of a function after a timing control is not allowed +%Error: t/t_timing_func_fork_bad.v:63:9: Writing to an output automatic variable of a function after a timing control is not allowed (IEEE 1800-2023 13.2.2) : ... note: In instance 't' - 56 | f5 = 0; + 63 | f5 = 0; | ^~ -%Error: t/t_timing_func_fork_bad.v:57:9: Writing to an output variable of a function after a timing control is not allowed +%Error: t/t_timing_func_fork_bad.v:64:9: Writing to an output automatic variable of a function after a timing control is not allowed (IEEE 1800-2023 13.2.2) : ... note: In instance 't' - 57 | o5 = 0; + 64 | o5 = 0; | ^~ %Error: Exiting due to diff --git a/test_regress/t/t_timing_func_fork_bad.v b/test_regress/t/t_timing_func_fork_bad.v index d3319513a..ff480632f 100644 --- a/test_regress/t/t_timing_func_fork_bad.v +++ b/test_regress/t/t_timing_func_fork_bad.v @@ -6,7 +6,7 @@ module t; - function int f1(output int o1); + function automatic int f1(output int o1); fork begin #1 $stop; @@ -16,7 +16,14 @@ module t; join_none endfunction - function int f2(inout io2); + task automatic t1(output int o1); + fork begin + #1 $stop; + o1 = 0; + end join_none + endtask + + function automatic int f2(inout io2); fork begin f2 = #5 0; @@ -27,7 +34,7 @@ module t; endfunction event e; - function int f3(output int o3); + function automatic int f3(output int o3); fork begin @e $stop; @@ -37,7 +44,7 @@ module t; join_none endfunction - function int f4(inout int io4); + function automatic int f4(inout int io4); fork begin f4 = @e 0; @@ -49,7 +56,7 @@ module t; int i; - function int f5(output int o5); + function automatic int f5(output int o5); fork begin wait (i == 0) $stop;