diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 56f664b2f..f6a885ca5 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1300,6 +1300,14 @@ class ParamProcessor final { // Resolve an unresolved RefDType whose classOrPackageOp targets a parameterized // class or a typedef alias of one. Specializes the class and links the typedef. void resolveParamClassRefDType(AstNodeDType* dtypep) { + // Recurse into struct/union members for buried RefDTypes + if (AstNodeUOrStructDType* const sup = VN_CAST(dtypep, NodeUOrStructDType)) { + for (AstMemberDType* memp = sup->membersp(); memp; + memp = VN_AS(memp->nextp(), MemberDType)) { + resolveParamClassRefDType(memp->subDTypep()); + } + return; + } AstRefDType* const refp = dtypep ? VN_CAST(dtypep, RefDType) : nullptr; if (!refp) return; if (refp->typedefp() || refp->refDTypep()) return; diff --git a/test_regress/t/t_struct_with_param_class.py b/test_regress/t/t_struct_with_param_class.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_struct_with_param_class.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_struct_with_param_class.v b/test_regress/t/t_struct_with_param_class.v new file mode 100644 index 000000000..78c19eb69 --- /dev/null +++ b/test_regress/t/t_struct_with_param_class.v @@ -0,0 +1,74 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// References to a parameterized-class typedef (r#(N)::t) buried inside +// unit-scope struct/union typedefs, passed as interface type-parameter +// values. Exercises resolveParamClassRefDType's recursion into +// NodeUOrStructDType members across several container shapes and a +// non-default parameter value. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +// verilog_format: on + +class r #(parameter int W = 1); + typedef struct packed { logic [W-1:0] unused; } t; +endclass + +// Plain struct member, default param +typedef struct packed { r#(1)::t f; } tf_struct; + +// Union member, default param +typedef union packed { r#(1)::t a; logic [0:0] b; } tf_union; + +// Non-default param value (forces class r to be specialized, not reused) +typedef struct packed { r#(7)::t f; } tf_nondefault; + +// Reference two levels deep, exercising the self-recursion +typedef struct packed { + struct packed { r#(1)::t f; } inner; +} tf_nested; + +interface ifc #(parameter type T = logic); +endinterface + +module t; + ifc #(.T(tf_struct)) bad_struct (); + ifc #(.T(tf_union)) bad_union (); + ifc #(.T(tf_nondefault)) bad_nondefault (); + ifc #(.T(tf_nested)) bad_nested (); + + initial begin + tf_struct v_struct; + tf_union v_union; + tf_nondefault v_nondefault; + tf_nested v_nested; + + // Widths follow the specialized class parameter + `checkh($bits(tf_struct), 32'd1); + `checkh($bits(tf_union), 32'd1); + `checkh($bits(tf_nondefault), 32'd7); + `checkh($bits(tf_nested), 32'd1); + + // Write/read through the buried r#(N)::t member to make sure + // the resolved type behaves as a real packed value + v_struct.f.unused = 1'b1; + `checkh(v_struct.f.unused, 1'b1); + + v_nondefault.f.unused = 7'h5a; + `checkh(v_nondefault.f.unused, 7'h5a); + + v_nested.inner.f.unused = 1'b1; + `checkh(v_nested.inner.f.unused, 1'b1); + + v_union.a.unused = 1'b1; + `checkh(v_union.b, 1'b1); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule