From f6ff5b4cfbdf54327f4b7d9ee435a9785c99584c Mon Sep 17 00:00:00 2001 From: Ethan Sifferman Date: Fri, 25 Sep 2026 14:34:47 -0700 Subject: [PATCH] Fix elaboration of classes reached through deferred references (#8440) (#8510) --- src/V3Param.cpp | 21 +++++++++++ .../t/t_class_lparam_type_param_override.py | 16 +++++++++ .../t/t_class_lparam_type_param_override.v | 36 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100755 test_regress/t/t_class_lparam_type_param_override.py create mode 100644 test_regress/t/t_class_lparam_type_param_override.v diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 6465b7ce8..9ca2b66ba 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -309,6 +309,8 @@ class ParamProcessor final { // Guard against infinite recursion in classTypeMatchesDefaultClone slow path std::unordered_set m_defaultCloneInProgress; + std::vector m_specializedClassps; // Classes specialized since last drained + // member names cached for fast lookup VMemberMap m_memberMap; @@ -2143,6 +2145,14 @@ class ParamProcessor final { const bool cloned = (newModp != srcModp); UINFO(9, "nodeDeparamCommon result: " << newModp->prettyNameQ() << " cloned=" << cloned); + // ParamVisitor skips the body of a class still marked hasGParam(), relying on it + // being visited through a reference instead. A class reached only by a deferred + // class-scoped reference (e.g. the 'C#(V)::t' default of a type parameter that the + // instantiation overrides) is never visited, so record specializations here. + // user2() is set once processWorkQ has elaborated the body, which needs no re-queue. + AstClass* const newClassp = VN_CAST(newModp, Class); + if (newClassp && !newClassp->user2()) m_specializedClassps.push_back(newClassp); + // Link source class to its specialized version for later relinking of method references if (defaultsResolved) srcModp->user4p(newModp); @@ -2455,6 +2465,13 @@ public: return newModp; } + // Return, and forget, the classes specialized since the previous call + std::vector takeSpecializedClassps() { + std::vector taken; + taken.swap(m_specializedClassps); + return taken; + } + // CONSTRUCTORS explicit ParamProcessor(AstNetlist* nodep) : m_hierBlocks{v3Global.opt.hierBlocks(), nodep} { @@ -2775,6 +2792,10 @@ class ParamVisitor final : public VNVisitor { // Visit all cells under module, recursively while (true) { + // Classes specialized since the last pass still need their bodies elaborated + for (AstClass* const classp : m_processor.takeSpecializedClassps()) { + m_state.m_workQueueNext.emplace(ParamState::WQKey{true, classp->level()}, classp); + } if (workQueue.empty()) std::swap(workQueue, m_state.m_workQueueNext); if (workQueue.empty()) break; diff --git a/test_regress/t/t_class_lparam_type_param_override.py b/test_regress/t/t_class_lparam_type_param_override.py new file mode 100755 index 000000000..a5ea6627e --- /dev/null +++ b/test_regress/t/t_class_lparam_type_param_override.py @@ -0,0 +1,16 @@ +#!/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('linter') + +test.lint() + +test.passes() diff --git a/test_regress/t/t_class_lparam_type_param_override.v b/test_regress/t/t_class_lparam_type_param_override.v new file mode 100644 index 000000000..0e8c160f0 --- /dev/null +++ b/test_regress/t/t_class_lparam_type_param_override.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// SPDX-FileCopyrightText: 2026 Ethan Sifferman +// SPDX-License-Identifier: CC0-1.0 + +// Overriding a type parameter whose default is a parameterized class scope +// still specializes the class, so its body must be elaborated (#8440). + +package pkg; + function automatic integer func(); + return 1; + endfunction +endpackage + +class class_with_package_call #( + parameter int unused_param = 0 +); + typedef int value_t; + localparam int local_param = pkg::func(); +endclass + +module module_with_type_parameter #( + parameter type value_t = class_with_package_call#(0)::value_t +); +endmodule + +interface iface_with_type_parameter #( + parameter type value_t = class_with_package_call#(1)::value_t +) (); + value_t value; +endinterface + +module top; + module_with_type_parameter #(.value_t(logic signed [15:0])) dut (); + iface_with_type_parameter #(.value_t(logic signed [15:0])) itf (); +endmodule