diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 0238f89cb..062a5603f 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -119,6 +119,8 @@ class TaskStateVisitor final : public VNVisitor { V3Graph m_callGraph; // Task call graph TaskBaseVertex* m_curVxp; // Current vertex we're adding to std::vector m_initialps; // Initial blocks to move + bool m_underPortVar = false; // Visiting under a port AstVar; any expression there + // is a default value, evaluated at call sites only public: // METHODS @@ -289,11 +291,14 @@ private: } } void visit(AstVar* nodep) override { + VL_RESTORER(m_underPortVar); + if (nodep->isIO()) m_underPortVar = true; iterateChildren(nodep); nodep->user4p(m_curVxp); // Remember what task it's under } void visit(AstVarRef* nodep) override { iterateChildren(nodep); + if (m_underPortVar) return; AstVar* const varp = nodep->varp(); if (varp->user4u().toGraphVertex() != m_curVxp) { if (m_curVxp->pure() && !varp->isXTemp() && !varp->isParam()) m_curVxp->impure(nodep); @@ -1404,6 +1409,7 @@ class TaskVisitor final : public VNVisitor { // Move it to new function unlinkAndClone(nodep, portp, false); portp->funcLocal(true); + if (portp->valuep()) pushDeletep(portp->valuep()->unlinkFrBack()); cfuncp->addArgsp(portp); // Pass inputs to DPI import wrappers by reference, unless fits in register if (cfuncp->dpiImportWrapper() && portp->isReadOnly()) { @@ -2139,6 +2145,14 @@ AstNodeFTask* V3Task::taskConnectWrapNew(AstNodeFTask* taskp, const string& newn newTaskp->addStmtsp(newPortp); } else { // Defaulting arg AstNodeExpr* const valuep = VN_AS(portp->valuep(), NodeExpr); + if ((portp->isRef() || portp->isConstRef()) && VN_IS(valuep, VarRef)) { + const VAccess refAccess = portp->isWritable() ? VAccess::WRITE : VAccess::READ; + AstVarRef* const refp = VN_AS(valuep->cloneTree(false), VarRef); + refp->access(refAccess); + AstArg* const newArgp = new AstArg{portp->fileline(), portp->name(), refp}; + newCallp->addArgsp(newArgp); + continue; + } // Create local temporary newPortp = new AstVar{portp->fileline(), VVarType::BLOCKTEMP, portp->name(), portp->dtypep()}; diff --git a/test_regress/t/t_func_ref_arg_default.py b/test_regress/t/t_func_ref_arg_default.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_func_ref_arg_default.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_func_ref_arg_default.v b/test_regress/t/t_func_ref_arg_default.v new file mode 100644 index 000000000..e3079c456 --- /dev/null +++ b/test_regress/t/t_func_ref_arg_default.v @@ -0,0 +1,52 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// A task argument that defaults to a plain variable must alias that variable, +// not a copy of it: a write through a 'ref' default must propagate back, and a +// 'const ref' default must observe later updates to the variable. + +module t; + int shared = 5; + logic flag = 0; + logic done = 0; + + // writable 'ref' default: a write through it must reach 'shared' + task automatic incr(ref int r = shared); +`ifdef TEST_NOINLINE + // verilator no_inline_task +`endif + r = r + 10; + endtask + + // 'const ref' default: must observe a later update to 'flag' + task automatic waitflag(output logic odone, const ref logic r = flag); +`ifdef TEST_NOINLINE + // verilator no_inline_task +`endif + while (!r) #1; + odone = 1'b1; + endtask + + initial begin + incr(); + if (shared !== 15) begin + $write("%%Error: write through default 'ref' lost (shared=%0d)\n", shared); + $stop; + end + fork + waitflag(done); + join_none + #5; + flag = 1'b1; + #5; + if (done !== 1'b1) begin + $write("%%Error: default 'const ref' did not observe update\n"); + $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_func_ref_arg_default_noinl.py b/test_regress/t/t_func_ref_arg_default_noinl.py new file mode 100755 index 000000000..f8a597ade --- /dev/null +++ b/test_regress/t/t_func_ref_arg_default_noinl.py @@ -0,0 +1,19 @@ +#!/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.top_filename = "t/t_func_ref_arg_default.v" + +test.compile(verilator_flags2=["--binary"], v_flags2=["+define+TEST_NOINLINE"]) + +test.execute() + +test.passes()