Fix timing control under fork under function (#6407)

Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
This commit is contained in:
Krzysztof Bieganski 2025-09-09 14:12:24 +02:00 committed by GitHub
parent b45cf5960a
commit 0743d84bcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 212 additions and 11 deletions

View File

@ -286,6 +286,7 @@ class DynScopeVisitor final : public VNVisitor {
const VNUser2InUse m_inuser2; const VNUser2InUse m_inuser2;
// STATE // STATE
bool m_inFunc = false; // True if in a function
AstNodeModule* m_modp = nullptr; // Module we are currently under AstNodeModule* m_modp = nullptr; // Module we are currently under
AstNode* m_procp = nullptr; // Function/task/block we are currently under AstNode* m_procp = nullptr; // Function/task/block we are currently under
std::deque<AstNode*> m_frameOrder; // Ordered list of frames (for determinism) std::deque<AstNode*> m_frameOrder; // Ordered list of frames (for determinism)
@ -367,6 +368,8 @@ class DynScopeVisitor final : public VNVisitor {
void visit(AstNodeFTask* nodep) override { void visit(AstNodeFTask* nodep) override {
VL_RESTORER(m_procp); VL_RESTORER(m_procp);
m_procp = nodep; m_procp = nodep;
VL_RESTORER(m_inFunc);
m_inFunc = VN_IS(nodep, Func);
if (hasAsyncFork(nodep)) pushDynScopeFrame(m_procp); if (hasAsyncFork(nodep)) pushDynScopeFrame(m_procp);
iterateChildren(nodep); iterateChildren(nodep);
} }
@ -433,12 +436,20 @@ class DynScopeVisitor final : public VNVisitor {
if (!isEvent && m_afterTimingControl && nodep->varp()->isWritable() if (!isEvent && m_afterTimingControl && nodep->varp()->isWritable()
&& nodep->access().isWriteOrRW()) { && nodep->access().isWriteOrRW()) {
// The output variable may not exist after a delay, so we can't just write to it // The output variable may not exist after a delay, so we can't just write to it
nodep->v3warn( if (m_inFunc) {
E_UNSUPPORTED, nodep->v3error(
"Unsupported: Writing to a captured " "Writing to an "
<< (nodep->varp()->isInout() ? "inout" : "output") << " variable in a " << nodep->varp()->verilogKwd()
<< (VN_IS(nodep->backp(), AssignDly) ? "non-blocking assignment" : "fork") << " variable of a function after a timing control is not allowed");
<< " after a timing control"); } 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(); if (!framep->instance().initialized()) framep->createInstancePrototype();
framep->captureVarInsert(nodep->varp()); framep->captureVarInsert(nodep->varp());

View File

@ -211,6 +211,7 @@ class WidthVisitor final : public VNVisitor {
VMemberMap m_memberMap; // Member names cached for fast lookup VMemberMap m_memberMap; // Member names cached for fast lookup
V3TaskConnectState m_taskConnectState; // State to cache V3Task::taskConnects V3TaskConnectState m_taskConnectState; // State to cache V3Task::taskConnects
WidthVP* m_vup = nullptr; // Current node state 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 AstCell* m_cellp = nullptr; // Current cell for arrayed instantiations
const AstEnumItem* m_enumItemp = nullptr; // Current enum item const AstEnumItem* m_enumItemp = nullptr; // Current enum item
const AstNodeFTask* m_ftaskp = nullptr; // Current function/task const AstNodeFTask* m_ftaskp = nullptr; // Current function/task
@ -704,7 +705,7 @@ class WidthVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return; 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 " nodep->v3error("Delays are not legal in functions. Suggest use a task "
"(IEEE 1800-2023 13.4.4)"); "(IEEE 1800-2023 13.4.4)");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
@ -727,7 +728,7 @@ class WidthVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
} }
void visit(AstFork* nodep) override { 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. " nodep->v3error("Only fork .. join_none is legal in functions. "
"(IEEE 1800-2023 13.4.4)"); "(IEEE 1800-2023 13.4.4)");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
@ -747,6 +748,8 @@ class WidthVisitor final : public VNVisitor {
nodep->replaceWith(newp); nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep); VL_DO_DANGLING(nodep->deleteTree(), nodep);
} else if (v3Global.opt.timing().isSetTrue()) { } else if (v3Global.opt.timing().isSetTrue()) {
VL_RESTORER(m_underFork);
m_underFork = true;
iterateChildren(nodep); iterateChildren(nodep);
} else if (v3Global.opt.timing().isSetFalse()) { } else if (v3Global.opt.timing().isSetFalse()) {
nodep->v3warn(E_NOTIMING, "Fork statements require --timing"); nodep->v3warn(E_NOTIMING, "Fork statements require --timing");
@ -1663,7 +1666,7 @@ class WidthVisitor final : public VNVisitor {
VL_DO_DANGLING(nodep->deleteTree(), nodep); VL_DO_DANGLING(nodep->deleteTree(), nodep);
} }
void visit(AstEventControl* nodep) override { 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 " nodep->v3error("Event controls are not legal in functions. Suggest use a task "
"(IEEE 1800-2023 13.4.4)"); "(IEEE 1800-2023 13.4.4)");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); 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 (VN_IS(nodep, AssignForce)) checkForceReleaseLhs(nodep, nodep->lhsp());
if (AstNode* const controlp = nodep->timingControlp()) { 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 " controlp->v3error("Timing controls are not legal in functions. Suggest use a task "
"(IEEE 1800-2023 13.4.4)"); "(IEEE 1800-2023 13.4.4)");
VL_DO_DANGLING(controlp->unlinkFrBackWithNext()->deleteTree(), controlp); VL_DO_DANGLING(controlp->unlinkFrBackWithNext()->deleteTree(), controlp);
@ -6631,7 +6634,7 @@ class WidthVisitor final : public VNVisitor {
userIterateChildren(nodep, WidthVP{SELF, PRELIM}.p()); userIterateChildren(nodep, WidthVP{SELF, PRELIM}.p());
} }
void visit(AstWait* nodep) override { 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 " nodep->v3error("Wait statements are not legal in functions. Suggest use a task "
"(IEEE 1800-2023 13.4.4)"); "(IEEE 1800-2023 13.4.4)");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);

View File

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

View File

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

View File

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

View File

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

View File

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