diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 0bb78f8e8..8400f6708 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -3136,6 +3136,7 @@ class LinkDotResolveVisitor final : public VNVisitor { bool m_inPackedArray = false; // Currently traversing a packed array tree bool m_replaceWithAlias = true; // Replace VarScope with an alias. Used in the handling of AstAlias + bool m_isParam = false; // Specifies whether currently visiting param variable struct DotStates final { DotPosition m_dotPos; // Scope part of dotted resolution @@ -5092,6 +5093,8 @@ class LinkDotResolveVisitor final : public VNVisitor { void visit(AstVar* nodep) override { LINKDOT_VISIT_START(); checkNoDot(nodep); + VL_RESTORER(m_isParam); + m_isParam = nodep->varType().isParam(); iterateChildren(nodep); if (m_statep->forPrimary() && nodep->isIO() && !m_ftaskp && !nodep->user4()) { nodep->v3error( @@ -5273,27 +5276,32 @@ class LinkDotResolveVisitor final : public VNVisitor { // which may find std::randomize and overwrite classOrPackagep return; } - if (m_insideClassExtParam) { - // The reference may point to a method declared in a super class, which is proved - // by a parameter. In such a case, it can't be linked at the first stage. - // Must not do any linking, because e.g. might find an extends of an upper class - // because the current class (under parent) isn't yet importing it's extended class - // symbols - return; - } - if (AstClass* const targetClassp = VN_CAST(dotSymp->nodep(), Class)) { - if (m_extendsParam.count(targetClassp)) { - // Target class has parameterized extends not yet resolved. - // Its inherited symbols (e.g. static functions from the base class) - // aren't imported yet - defer to linkDotParamed. + VSymEnt* const foundp + = m_statep->findSymPrefixed(dotSymp, nodep->name(), baddot, first); + AstNodeFTask* const taskp = foundp ? VN_CAST(foundp->nodep(), NodeFTask) : nullptr; + AstNodeModule* const taskModulep = foundp ? foundp->classOrPackagep() : nullptr; + // Ignore deferring linking of functions inside params of classes extending + // parametric classes if referenced function can be linked in the first pass. + if (!m_isParam || !taskp + || (taskp->classMethod() && taskModulep && taskModulep->hasParameterList())) { + if (m_insideClassExtParam) { + // The reference may point to a method declared in a super class, which is + // proved by a parameter. In such a case, it can't be linked at the first + // stage. Must not do any linking, because e.g. might find an extends of an + // upper class because the current class (under parent) isn't yet importing + // it's extended class symbols. return; } + if (AstClass* const targetClassp = VN_CAST(dotSymp->nodep(), Class)) { + if (m_extendsParam.count(targetClassp)) { + // Target class has parameterized extends not yet resolved. + // Its inherited symbols (e.g. static functions from the base class) + // aren't imported yet - defer to linkDotParamed. + return; + } + } } - VSymEnt* const foundp - = m_statep->findSymPrefixed(dotSymp, nodep->name(), baddot, first); - AstNodeFTask* const taskp - = foundp ? VN_CAST(foundp->nodep(), NodeFTask) : nullptr; // Maybe nullptr if (taskp) { if (staticAccess && !taskp->isStatic()) { // TODO bug4077 diff --git a/test_regress/t/t_ext_param_func.py b/test_regress/t/t_ext_param_func.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_ext_param_func.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('vlt') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_ext_param_func.v b/test_regress/t/t_ext_param_func.v new file mode 100644 index 000000000..d3e101b59 --- /dev/null +++ b/test_regress/t/t_ext_param_func.v @@ -0,0 +1,86 @@ +// 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 + +`define stop $stop +`define checkh(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); + +function automatic int align(input int a, input int b); + return a - (a % b); +endfunction + +class Foo; + + static function int mul(input int a, input int b); + return a * b; + endfunction + +endclass : Foo + +class FooParam #(parameter int param = 5); + + static function int mul(input int a); + return a * param; + endfunction + +endclass : FooParam + +class Base #(parameter int param = 5); + + function int get_base_param(); + return param; + endfunction + + static function int alignUp(input int a); + return ((a + param - 1) / param) * param; + endfunction + +endclass : Base + +class Derived #( + parameter int a = 17, + parameter int b = 8, + parameter int c = align(a, b), + parameter int d = Base#(b)::alignUp(a), + parameter int e = Foo::mul(a, b), + parameter int f = FooParam#()::mul(a) +) extends Base #( + .param (a) +); + + function int get_third_param(); + return c; + endfunction + + function int get_fourth_param(); + return d; + endfunction + + function int get_fifth_param(); + return e; + endfunction + + function int get_sixth_param(); + return f; + endfunction + +endclass : Derived + +module t (clk); + input clk; + + Derived inst = new; + + always @(posedge clk) begin + `checkh(inst.get_base_param(), 17) + `checkh(inst.get_third_param(), 16) + `checkh(inst.get_fourth_param(), 24) + `checkh(inst.get_fifth_param(), 136) + `checkh(inst.get_sixth_param(), 85) + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule