From 0743d84bcc34d20b04303451214d19c3cbb0459a Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Tue, 9 Sep 2025 14:12:24 +0200 Subject: [PATCH] Fix timing control under fork under function (#6407) Signed-off-by: Krzysztof Bieganski --- src/V3Fork.cpp | 23 ++++++--- src/V3Width.cpp | 13 +++-- test_regress/t/t_timing_func_fork.py | 16 ++++++ test_regress/t/t_timing_func_fork.v | 61 +++++++++++++++++++++++ test_regress/t/t_timing_func_fork_bad.out | 42 ++++++++++++++++ test_regress/t/t_timing_func_fork_bad.py | 18 +++++++ test_regress/t/t_timing_func_fork_bad.v | 50 +++++++++++++++++++ 7 files changed, 212 insertions(+), 11 deletions(-) create mode 100755 test_regress/t/t_timing_func_fork.py create mode 100644 test_regress/t/t_timing_func_fork.v create mode 100644 test_regress/t/t_timing_func_fork_bad.out create mode 100755 test_regress/t/t_timing_func_fork_bad.py create mode 100644 test_regress/t/t_timing_func_fork_bad.v diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp index b05ea6776..88a318b41 100644 --- a/src/V3Fork.cpp +++ b/src/V3Fork.cpp @@ -286,6 +286,7 @@ class DynScopeVisitor final : public VNVisitor { const VNUser2InUse m_inuser2; // STATE + bool m_inFunc = false; // True if in a function AstNodeModule* m_modp = nullptr; // Module we are currently under AstNode* m_procp = nullptr; // Function/task/block we are currently under std::deque m_frameOrder; // Ordered list of frames (for determinism) @@ -367,6 +368,8 @@ class DynScopeVisitor final : public VNVisitor { void visit(AstNodeFTask* nodep) override { VL_RESTORER(m_procp); m_procp = nodep; + VL_RESTORER(m_inFunc); + m_inFunc = VN_IS(nodep, Func); if (hasAsyncFork(nodep)) pushDynScopeFrame(m_procp); iterateChildren(nodep); } @@ -433,12 +436,20 @@ 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 - nodep->v3warn( - E_UNSUPPORTED, - "Unsupported: Writing to a captured " - << (nodep->varp()->isInout() ? "inout" : "output") << " variable in a " - << (VN_IS(nodep->backp(), AssignDly) ? "non-blocking assignment" : "fork") - << " after a timing control"); + 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"); + } } if (!framep->instance().initialized()) framep->createInstancePrototype(); framep->captureVarInsert(nodep->varp()); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 0daa043cc..6eb0552cc 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -211,6 +211,7 @@ class WidthVisitor final : public VNVisitor { VMemberMap m_memberMap; // Member names cached for fast lookup V3TaskConnectState m_taskConnectState; // State to cache V3Task::taskConnects WidthVP* m_vup = nullptr; // Current node state + bool m_underFork = false; // Visiting under a fork const AstCell* m_cellp = nullptr; // Current cell for arrayed instantiations const AstEnumItem* m_enumItemp = nullptr; // Current enum item const AstNodeFTask* m_ftaskp = nullptr; // Current function/task @@ -704,7 +705,7 @@ class WidthVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return; } - if (VN_IS(m_ftaskp, Func)) { + if (!m_underFork && VN_IS(m_ftaskp, Func)) { nodep->v3error("Delays are not legal in functions. Suggest use a task " "(IEEE 1800-2023 13.4.4)"); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); @@ -727,7 +728,7 @@ class WidthVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstFork* nodep) override { - if (VN_IS(m_ftaskp, Func) && !nodep->joinType().joinNone()) { + if (!m_underFork && VN_IS(m_ftaskp, Func) && !nodep->joinType().joinNone()) { nodep->v3error("Only fork .. join_none is legal in functions. " "(IEEE 1800-2023 13.4.4)"); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); @@ -747,6 +748,8 @@ class WidthVisitor final : public VNVisitor { nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); } else if (v3Global.opt.timing().isSetTrue()) { + VL_RESTORER(m_underFork); + m_underFork = true; iterateChildren(nodep); } else if (v3Global.opt.timing().isSetFalse()) { nodep->v3warn(E_NOTIMING, "Fork statements require --timing"); @@ -1663,7 +1666,7 @@ class WidthVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); } void visit(AstEventControl* nodep) override { - if (VN_IS(m_ftaskp, Func)) { + if (!m_underFork && VN_IS(m_ftaskp, Func)) { nodep->v3error("Event controls are not legal in functions. Suggest use a task " "(IEEE 1800-2023 13.4.4)"); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); @@ -5466,7 +5469,7 @@ class WidthVisitor final : public VNVisitor { } if (VN_IS(nodep, AssignForce)) checkForceReleaseLhs(nodep, nodep->lhsp()); if (AstNode* const controlp = nodep->timingControlp()) { - if (VN_IS(m_ftaskp, Func)) { + if (!m_underFork && VN_IS(m_ftaskp, Func)) { controlp->v3error("Timing controls are not legal in functions. Suggest use a task " "(IEEE 1800-2023 13.4.4)"); VL_DO_DANGLING(controlp->unlinkFrBackWithNext()->deleteTree(), controlp); @@ -6631,7 +6634,7 @@ class WidthVisitor final : public VNVisitor { userIterateChildren(nodep, WidthVP{SELF, PRELIM}.p()); } void visit(AstWait* nodep) override { - if (VN_IS(m_ftaskp, Func)) { + if (!m_underFork && VN_IS(m_ftaskp, Func)) { nodep->v3error("Wait statements are not legal in functions. Suggest use a task " "(IEEE 1800-2023 13.4.4)"); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); diff --git a/test_regress/t/t_timing_func_fork.py b/test_regress/t/t_timing_func_fork.py new file mode 100755 index 000000000..0c6a8cd25 --- /dev/null +++ b/test_regress/t/t_timing_func_fork.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary -Wno-UNOPTFLAT"]) + +test.passes() diff --git a/test_regress/t/t_timing_func_fork.v b/test_regress/t/t_timing_func_fork.v new file mode 100644 index 000000000..898c680d6 --- /dev/null +++ b/test_regress/t/t_timing_func_fork.v @@ -0,0 +1,61 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/); + + function int f1; + fork begin + #1 $stop; + end join_none + f1 = 0; + endfunction + + function int f2; + fork begin + int x; + x = #5 0; $stop; + end join_none + f2 = 0; + endfunction + + event e; + function int f3; + fork begin + int x; + @e $stop; + x = 0; + end join_none + f3 = 0; + endfunction + + function int f4; + fork begin + int x; + x = @e 0; $stop; + end join_none + f4 = 0; + endfunction + + int i; + + function int f5; + fork begin + int x; + wait(i == 0) $stop; + x = 0; + end join_none + f5 = 0; + endfunction + + initial begin + fork begin + i = f1(); + $write("*-* All Finished *-*\n"); + $finish; + end join_none + end + +endmodule diff --git a/test_regress/t/t_timing_func_fork_bad.out b/test_regress/t/t_timing_func_fork_bad.out new file mode 100644 index 000000000..5a45c2d73 --- /dev/null +++ b/test_regress/t/t_timing_func_fork_bad.out @@ -0,0 +1,42 @@ +%Error: t/t_timing_func_fork_bad.v:12:7: Writing to an output variable of a function after a timing control is not allowed + : ... note: In instance 't' + 12 | 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:13:7: Writing to an output variable of a function after a timing control is not allowed + : ... note: In instance 't' + 13 | o1 = 0; + | ^~ +%Error: t/t_timing_func_fork_bad.v:19:7: Writing to an output variable of a function after a timing control is not allowed + : ... note: In instance 't' + 19 | f2 = #5 0; $stop; + | ^~ +%Error: t/t_timing_func_fork_bad.v:20:7: Writing to an inout variable of a function after a timing control is not allowed + : ... note: In instance 't' + 20 | io2 = 0; + | ^~~ +%Error: t/t_timing_func_fork_bad.v:28:7: Writing to an output variable of a function after a timing control is not allowed + : ... note: In instance 't' + 28 | f3 = 0; + | ^~ +%Error: t/t_timing_func_fork_bad.v:29:7: Writing to an output variable of a function after a timing control is not allowed + : ... note: In instance 't' + 29 | o3 = 0; + | ^~ +%Error: t/t_timing_func_fork_bad.v:35:7: Writing to an output variable of a function after a timing control is not allowed + : ... note: In instance 't' + 35 | f4 = @e 0; $stop; + | ^~ +%Error: t/t_timing_func_fork_bad.v:36:7: Writing to an inout variable of a function after a timing control is not allowed + : ... note: In instance 't' + 36 | io4 = 0; + | ^~~ +%Error: t/t_timing_func_fork_bad.v:45:7: Writing to an output variable of a function after a timing control is not allowed + : ... note: In instance 't' + 45 | f5 = 0; + | ^~ +%Error: t/t_timing_func_fork_bad.v:46:7: Writing to an output variable of a function after a timing control is not allowed + : ... note: In instance 't' + 46 | o5 = 0; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_func_fork_bad.py b/test_regress/t/t_timing_func_fork_bad.py new file mode 100755 index 000000000..b36617573 --- /dev/null +++ b/test_regress/t/t_timing_func_fork_bad.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(fails=True, + verilator_flags2=["--binary -Wno-UNOPTFLAT"], + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_timing_func_fork_bad.v b/test_regress/t/t_timing_func_fork_bad.v new file mode 100644 index 000000000..67c2d6455 --- /dev/null +++ b/test_regress/t/t_timing_func_fork_bad.v @@ -0,0 +1,50 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/); + + function int f1(output int o1); + fork begin + #1 $stop; + f1 = 0; + o1 = 0; + end join_none + endfunction + + function int f2(inout io2); + fork begin + f2 = #5 0; $stop; + io2 = 0; + end join_none + endfunction + + event e; + function int f3(output int o3); + fork begin + @e $stop; + f3 = 0; + o3 = 0; + end join_none + endfunction + + function int f4(inout int io4); + fork begin + f4 = @e 0; $stop; + io4 = 0; + end join_none + endfunction + + int i; + + function int f5(output int o5); + fork begin + wait(i == 0) $stop; + f5 = 0; + o5 = 0; + end join_none + endfunction + +endmodule