From e7a644a3fc24686f03697c1e96d3cdc8252b16c4 Mon Sep 17 00:00:00 2001 From: em2machine <92717390+em2machine@users.noreply.github.com> Date: Fri, 3 Apr 2026 11:19:17 -0400 Subject: [PATCH] Fix functions in generate block resulting in "Broken link in node" (#7236) (#7367) Fixes #7236 --- src/V3AstNodeExpr.h | 3 +++ src/V3AstNodes.cpp | 2 ++ src/V3LinkDot.cpp | 11 +++++++++ src/V3Param.cpp | 8 ++++++ test_regress/t/t_function_generate.py | 18 ++++++++++++++ test_regress/t/t_function_generate.v | 35 +++++++++++++++++++++++++++ 6 files changed, 77 insertions(+) create mode 100755 test_regress/t/t_function_generate.py create mode 100644 test_regress/t/t_function_generate.v diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index eeaf92f86..4f50c5083 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -233,6 +233,7 @@ private: string m_dotted; // Dotted part of scope the name()ed task/func is under or "" string m_inlinedDots; // Dotted hierarchy flattened out bool m_pli = false; // Pli system call ($name) + bool m_containsGenBlock = false; // Contains gen block reference VIsCached m_purity; // Pure state protected: @@ -259,6 +260,8 @@ public: void classOrPackagep(AstNodeModule* nodep) { m_classOrPackagep = nodep; } bool pli() const { return m_pli; } void pli(bool flag) { m_pli = flag; } + bool containsGenBlock() const { return m_containsGenBlock; } + void containsGenBlock(const bool flag) { m_containsGenBlock = flag; } bool isPure() override; string emitVerilog() final override { V3ERROR_NA_RETURN(""); } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 97cf8f2b2..9383f542f 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -3111,6 +3111,7 @@ void AstActive::dumpJson(std::ostream& str) const { dumpJsonGen(str); } void AstNodeFTaskRef::dump(std::ostream& str) const { this->AstNodeExpr::dump(str); if (classOrPackagep()) str << " pkg=" << nodeAddr(classOrPackagep()); + if (containsGenBlock()) str << " [GENBLK]"; str << " -> "; if (dotted() != "") str << ".=" << dotted() << " "; if (taskp()) { @@ -3121,6 +3122,7 @@ void AstNodeFTaskRef::dump(std::ostream& str) const { } void AstNodeFTaskRef::dumpJson(std::ostream& str) const { dumpJsonStrFunc(str, dotted); + dumpJsonBoolFuncIf(str, containsGenBlock); dumpJsonGen(str); } void AstNodeFTask::dump(std::ostream& str) const { diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 5d368024d..c9174bcce 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -5067,6 +5067,17 @@ class LinkDotResolveVisitor final : public VNVisitor { m_ds.m_dotPos = DP_MEMBER; } else if (m_ds.m_dotp && m_ds.m_dotPos == DP_FINAL) { nodep->dotted(m_ds.m_dotText); // Maybe "" + // Only flag FTaskRefs under generate-if/case blocks that may be + // pruned. GenFor and plain begin-blocks won't be pruned by V3Param. + // VarXRef uses the broader m_genBlk flag (set for all GenBlocks) + // because genfor unrolling also removes variables. + if (m_ds.m_genBlk && m_ds.m_dotSymp) { + const AstNode* const blkp = m_ds.m_dotSymp->nodep(); + if (VN_IS(blkp, GenBlock) + && (VN_IS(blkp->backp(), GenIf) || VN_IS(blkp->backp(), GenCase))) { + nodep->containsGenBlock(true); + } + } } else if (m_ds.m_dotp && m_ds.m_dotPos == DP_MEMBER) { // Found a Var, everything following is method call. // {scope}.{var}.HERE {method} ( ARGS ) diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 8e6e50746..8d25b0c6f 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -2529,6 +2529,14 @@ class ParamVisitor final : public VNVisitor { } return false; } + void visit(AstNodeFTaskRef* nodep) override { + if (nodep->containsGenBlock()) { + // Needs relink, as may remove pointed-to task/func + nodep->taskp(nullptr); + return; + } + iterateChildren(nodep); + } void visit(AstVarXRef* nodep) override { if (nodep->containsGenBlock()) { // Needs relink, as may remove pointed-to var diff --git a/test_regress/t/t_function_generate.py b/test_regress/t/t_function_generate.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_function_generate.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_function_generate.v b/test_regress/t/t_function_generate.v new file mode 100644 index 000000000..f6f105949 --- /dev/null +++ b/test_regress/t/t_function_generate.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verify function calls through generate-if block references +// +// When a function is defined inside a generate-if block and called via a +// dotted reference (e.g. defs.foo()), the FUNCREF must survive generate +// pruning. Previously the FUNCREF could point to the deleted else-branch +// function, causing a broken-link internal error. +// +// 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 + +module t(/*AUTOARG*/); + generate + if (1) begin : defs + function automatic logic foo; + foo = 1'b1; + endfunction + end else begin : defs + function automatic logic foo; + foo = 1'b0; + endfunction + end + endgenerate + + initial begin + if (defs.foo() !== 1'b1) begin + $display("%%Error: defs.foo() returned wrong value"); + $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule