diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index d55196555..9cf45ceb0 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -585,11 +585,7 @@ public: const AstClassRefDType* const asamep = VN_DBG_AS(samep, ClassRefDType); return (m_classp == asamep->m_classp && m_classOrPackagep == asamep->m_classOrPackagep); } - bool similarDTypeNode(const AstNodeDType* samep) const override { - // Doesn't need to compare m_classOrPackagep - const AstClassRefDType* const asamep = VN_DBG_AS(samep, ClassRefDType); - return m_classp == asamep->m_classp; - } + bool similarDTypeNode(const AstNodeDType* samep) const override; void dump(std::ostream& str = std::cout) const override; void dumpJson(std::ostream& str = std::cout) const override; void dumpSmall(std::ostream& str) const override; diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index da424cfa8..e37c85add 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1893,6 +1893,21 @@ void AstClassRefDType::dumpSmall(std::ostream& str) const { } string AstClassRefDType::prettyDTypeName(bool) const { return "class{}"s + prettyName(); } string AstClassRefDType::name() const { return classp() ? classp()->name() : ""; } +bool AstClassRefDType::similarDTypeNode(const AstNodeDType* samep) const { + // Doesn't need to compare m_classOrPackagep + const AstClassRefDType* const asamep = VN_DBG_AS(samep, ClassRefDType); + if (m_classp != asamep->m_classp) return false; + // Also compare type parameters - C#(int) != C#(string) + const AstPin* lp = paramsp(); + const AstPin* rp = asamep->paramsp(); + while (lp && rp) { + if (!lp->exprp() != !rp->exprp()) return false; + if (lp->exprp() && !lp->exprp()->sameTree(rp->exprp())) return false; + lp = VN_CAST(lp->nextp(), Pin); + rp = VN_CAST(rp->nextp(), Pin); + } + return !lp && !rp; +} void AstNodeCoverOrAssert::dump(std::ostream& str) const { this->AstNodeStmt::dump(str); str << " ["s + this->type().ascii() + "]"; diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 4c5009f9a..856e46ea2 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -5593,6 +5593,11 @@ class LinkDotResolveVisitor final : public VNVisitor { iterate(cpackagep); return; } + // Also defer if target is a typedef to a parameterized class (#5977) + if (m_statep->forPrimary() && isParamedClassRef(cpackagerefp)) { + iterate(cpackagep); + return; + } if (!cpackagerefp->classOrPackageSkipp()) { VSymEnt* const foundp = m_statep->resolveClassOrPackage( m_ds.m_dotSymp, cpackagerefp, true, false, "class/package reference"); diff --git a/src/V3Param.cpp b/src/V3Param.cpp index aeddbbeb4..b2b360ce9 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -948,6 +948,39 @@ class ParamProcessor final { if (!parseRefp) return; const AstClass* lhsClassp = VN_CAST(classRefp->classOrPackageSkipp(), Class); + + // If the ClassOrPackageRef points to a type parameter (ParamTypeDType), we need + // to check if the parameter's value contains a parameterized class that needs + // specialization. This handles patterns like: + // interface outer #(parameter type C = class_with_type_param#(T)); + // inner #(.P(C::typedef_name)) i(); // C is a type parameter + // endinterface + AstParamTypeDType* const paramTypep + = VN_CAST(classRefp->classOrPackageNodep(), ParamTypeDType); + if (paramTypep) { + // Traverse through the type parameter to find if there's a ClassRefDType + // with parameters that needs specialization + AstNodeDType* const dtypep = paramTypep->subDTypep(); + AstClassRefDType* const classRefDTypep + = dtypep ? VN_CAST(dtypep->skipRefp(), ClassRefDType) : nullptr; + if (classRefDTypep) { + AstClass* const srcClassp = classRefDTypep->classp(); + if (srcClassp && srcClassp->hasGParam() && classRefDTypep->paramsp()) { + // The type parameter's value is a parameterized class - specialize it + if (lhsClassp == srcClassp || !lhsClassp) { + UINFO(9, "resolveDotToTypedef: specializing type param class " + << srcClassp->name() << endl); + classRefDeparam(classRefDTypep, srcClassp); + lhsClassp = classRefDTypep->classp(); + } else { + UINFO(9, "resolveDotToTypedef: type param class " + << srcClassp->name() + << " already specialized to " << lhsClassp->name() << endl); + } + } + } + } + if (classRefp->paramsp()) { // ClassOrPackageRef has parameters - may need to specialize the class AstClass* const srcClassp = VN_CAST(classRefp->classOrPackageNodep(), Class); @@ -957,6 +990,10 @@ class ParamProcessor final { UINFO(9, "resolveDotToTypedef: specializing " << srcClassp->name() << endl); classRefDeparam(classRefp, srcClassp); lhsClassp = VN_CAST(classRefp->classOrPackageSkipp(), Class); + } else { + UINFO(9, "resolveDotToTypedef: class " << srcClassp->name() + << " already specialized to " + << lhsClassp->name() << endl); } } } @@ -972,10 +1009,65 @@ class ParamProcessor final { } } + // Resolve a deferred RefDType referencing a typedef inside a parameterized class (#5461). + // When a formal parameter's type is `cls1#()::bool_t`, the RefDType for `bool_t` is + // left unresolved during linkDotPrimary. We resolve it here by specializing the class + // and looking up the typedef in the specialized class. + void resolveParamClassRefDType(AstNodeDType* dtypep) { + AstRefDType* const refp = dtypep ? VN_CAST(dtypep, RefDType) : nullptr; + if (!refp) return; + if (refp->typedefp() || refp->refDTypep()) return; // Already resolved + + AstClassOrPackageRef* const classRefp + = VN_CAST(refp->classOrPackageOpp(), ClassOrPackageRef); + if (!classRefp) return; + + AstClass* srcClassp = VN_CAST(classRefp->classOrPackageNodep(), Class); + if (srcClassp && srcClassp->hasGParam()) { + // Direct class reference with parameters on the ClassOrPackageRef + // (classRefDeparam also resolves the RefDType via its backp() check + // when the ClassOrPackageRef is a child of RefDType) + classRefDeparam(classRefp, srcClassp); + return; + } + + // ClassOrPackageRef targets a typedef to a parameterized class (e.g., + // typedef cls#(N) alias; typedef alias::member_t m_t;). + // Follow the typedef chain to find the ClassRefDType with the actual class. + AstNode* targetp = classRefp->classOrPackageNodep(); + while (const AstTypedef* const tdefp = VN_CAST(targetp, Typedef)) + targetp = tdefp->subDTypep(); + if (AstNodeDType* const dtargetp = VN_CAST(targetp, NodeDType)) + targetp = dtargetp->skipRefOrNullp(); + if (!targetp) return; + AstClassRefDType* const classRefDTypep = VN_CAST(targetp, ClassRefDType); + if (!classRefDTypep) return; + srcClassp = classRefDTypep->classp(); + if (!srcClassp) return; + + AstClass* newClassp = srcClassp; + if (srcClassp->hasGParam()) { + // Specialize using the ClassRefDType (which has the actual parameters) + classRefDeparam(classRefDTypep, srcClassp); + newClassp = classRefDTypep->classp(); + } + // else: class already specialized, use it directly + + // Look up the member typedef in the specialized class + AstTypedef* const typedefp + = VN_CAST(m_memberMap.findMember(newClassp, refp->name()), Typedef); + if (typedefp) { + refp->typedefp(typedefp); + refp->classOrPackagep(newClassp); + } + } + void cellPinCleanup(AstNode* nodep, AstPin* pinp, AstNodeModule* srcModp, string& longnamer, bool& any_overridesr) { if (!pinp->exprp()) return; // No-connect if (AstVar* const modvarp = pinp->modVarp()) { + // Resolve deferred formal param type referencing parameterized class typedef (#5461) + resolveParamClassRefDType(modvarp->subDTypep()); if (!modvarp->isGParam()) { pinp->v3fatalSrc("Attempted parameter setting of non-parameter: Param " << pinp->prettyNameQ() << " of " << nodep->prettyNameQ()); @@ -1034,6 +1126,16 @@ class ParamProcessor final { // Handle DOT with ParseRef RHS (e.g., p_class#(8)::p_type) // by this point ClassOrPackageRef should be updated to point to the specialized class. resolveDotToTypedef(pinp->exprp()); + // Resolve deferred formal param type referencing parameterized class typedef (#5461) + resolveParamClassRefDType(modvarp->subDTypep()); + // Also resolve deferred RefDType inside the pin expression's typedef chain. + // Handles: typedef cls#(N) alias; typedef alias::member_t m_t; mod#(.T(m_t)) + // where alias::member_t was deferred during linkDotPrimary (#5977). + if (const AstRefDType* const pinRefp = VN_CAST(pinp->exprp(), RefDType)) { + if (const AstTypedef* const tdefp = pinRefp->typedefp()) { + resolveParamClassRefDType(tdefp->subDTypep()); + } + } AstNodeDType* rawTypep = VN_CAST(pinp->exprp(), NodeDType); if (rawTypep) V3Width::widthParamsEdit(rawTypep); diff --git a/test_regress/t/t_class_param_typedef7.py b/test_regress/t/t_class_param_typedef7.py new file mode 100644 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_class_param_typedef7.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_class_param_typedef7.v b/test_regress/t/t_class_param_typedef7.v new file mode 100644 index 000000000..257e7d805 --- /dev/null +++ b/test_regress/t/t_class_param_typedef7.v @@ -0,0 +1,61 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Leela Pakanati +// SPDX-License-Identifier: CC0-1.0 + +// Test for issue #5461: Class parameter type using cls#()::typedef + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +// Basic parameterized class with a typedef +class cls1 #(bit PARAM = 0); + typedef bit bool_t; + typedef logic [7:0] byte_t; +endclass + +// Class whose parameter TYPE is a typedef from a parameterized class (default params) +class cls2 #(cls1#()::bool_t PARAM = 1); + function int get_param(); + return int'(PARAM); + endfunction +endclass + +// Class using non-default params for the referenced class +class cls3 #(cls1#(1)::bool_t PARAM = 0); + function int get_param(); + return int'(PARAM); + endfunction +endclass + +// Class using a wider typedef from the parameterized class +class cls4 #(cls1#()::byte_t PARAM = 8'hAB); + function int get_param(); + return int'(PARAM); + endfunction +endclass + +module t; + initial begin + automatic cls2#(1) obj2 = new; + automatic cls3#(1) obj3 = new; + automatic cls4#(8'hCD) obj4 = new; + // Default param + automatic cls2 obj2d = new; + + `checkd(obj2.get_param(), 1); + `checkd(obj3.get_param(), 1); + `checkd(obj4.get_param(), 'hCD); + `checkd(obj2d.get_param(), 1); + + `checkd($bits(cls1#()::bool_t), 1); + `checkd($bits(cls1#(1)::bool_t), 1); + `checkd($bits(cls1#()::byte_t), 8); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_param_typedef7_noinl.py b/test_regress/t/t_class_param_typedef7_noinl.py new file mode 100644 index 000000000..32db738ac --- /dev/null +++ b/test_regress/t/t_class_param_typedef7_noinl.py @@ -0,0 +1,20 @@ +#!/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_class_param_typedef7.v" + +test.compile(verilator_flags2=['--binary', '-fno-inline']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_param_typedef8.py b/test_regress/t/t_class_param_typedef8.py new file mode 100644 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_class_param_typedef8.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_class_param_typedef8.v b/test_regress/t/t_class_param_typedef8.v new file mode 100644 index 000000000..e516c3e87 --- /dev/null +++ b/test_regress/t/t_class_param_typedef8.v @@ -0,0 +1,45 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Leela Pakanati +// SPDX-License-Identifier: CC0-1.0 + +// Test for issue #5977: Typedef of parameterized class for member access + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +package pkg; + class cls_l0 #(parameter AW = 32); + logic [AW-1:0] addr; + function new(); + addr = '0; + endfunction + endclass + + class cls_l1 #(parameter int AW = 32); + typedef cls_l0 #(.AW(AW)) beat_t; + endclass +endpackage + +module t; + // Typedef of parameterized class, then access member typedef via :: + typedef pkg::cls_l1 #(.AW(64)) drv64_t; + typedef pkg::cls_l1 #(.AW(128)) drv128_t; + + initial begin + // Access class-type typedef member through module-level typedef + automatic drv64_t::beat_t item1 = new; + automatic drv128_t::beat_t item2 = new; + item1.addr = 64'hDEAD_BEEF_CAFE_BABE; + + `checkd(item1.addr, 64'hDEAD_BEEF_CAFE_BABE); + `checkd($bits(item1.addr), 64); + `checkd($bits(item2.addr), 128); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_param_typedef8_noinl.py b/test_regress/t/t_class_param_typedef8_noinl.py new file mode 100644 index 000000000..2aecdefad --- /dev/null +++ b/test_regress/t/t_class_param_typedef8_noinl.py @@ -0,0 +1,20 @@ +#!/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_class_param_typedef8.v" + +test.compile(verilator_flags2=['--binary', '-fno-inline']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_iface_param_class_type_param.py b/test_regress/t/t_iface_param_class_type_param.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_param_class_type_param.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_iface_param_class_type_param.v b/test_regress/t/t_iface_param_class_type_param.v new file mode 100644 index 000000000..bf05026e7 --- /dev/null +++ b/test_regress/t/t_iface_param_class_type_param.v @@ -0,0 +1,87 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Leela Pakanati +// SPDX-License-Identifier: CC0-1.0 + +// Test that parameterized class typedefs work as interface type parameters +// when the class itself has type parameters. +// See issue #7000 + +// Class with single type parameter +class C #(parameter type T = logic); + typedef struct packed { T data; } td_t; +endclass + +// Class with multiple type parameters and multiple typedefs +class multi_param #(parameter type ADDR_T = logic, parameter type DATA_T = logic); + typedef struct packed { ADDR_T addr; } addr_td_t; + typedef struct packed { DATA_T data; } data_td_t; +endclass + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +// Leaf interface: holds a value of parameterized type +interface l0 #(type P = logic); + P p; +endinterface + +// 1-level nesting: wraps l0 with a class typedef parameter +interface l1 #(parameter type X = C#(logic)); + l0 #(.P(X::td_t)) sub(); +endinterface + +// 2-level nesting: forwards type param through l1 to l0 +interface l2 #(parameter type X = C#(logic)); + l1 #(.X(X)) sub(); +endinterface + +// Multi-param leaf: holds two values of different parameterized types +interface multi_l0 #(type P = logic, type Q = logic); + P p; + Q q; +endinterface + +// Multi-param nesting: accesses different typedefs from same class +interface multi_l1 #( + parameter type CFG = multi_param#(logic, logic) +); + multi_l0 #(.P(CFG::addr_td_t), .Q(CFG::data_td_t)) sub(); +endinterface + +module t; + // Test 1-level nesting with different parameterizations + l1 #(.X(C#(logic[7:0]))) l1_i1(); + l1 #(.X(C#(logic[15:0]))) l1_i2(); + // Test default type parameter (C#(logic) -> td_t is struct packed { logic data; }) + l1 l1_default(); + + // Test 2-level nesting - type parameter passed through multiple levels + l2 #(.X(C#(logic[31:0]))) l2_i(); + + // Test multiple type params - different parameterizations accessing multiple typedefs + multi_l1 #(.CFG(multi_param#(logic[7:0], logic[31:0]))) ml1_i1(); + multi_l1 #(.CFG(multi_param#(logic[15:0], logic[63:0]))) ml1_i2(); + + initial begin + // 1-level nesting + `checkd($bits(l1_i1.sub.p), 8); + `checkd($bits(l1_i2.sub.p), 16); + `checkd($bits(l1_default.sub.p), 1); // default C#(logic) -> 1-bit + + // 2-level nesting + `checkd($bits(l2_i.sub.sub.p), 32); + + // Multiple type params passed to sub-interface - two different typedefs + `checkd($bits(ml1_i1.sub.p), 8); // addr_td_t from ADDR_T=logic[7:0] + `checkd($bits(ml1_i1.sub.q), 32); // data_td_t from DATA_T=logic[31:0] + `checkd($bits(ml1_i2.sub.p), 16); // addr_td_t from ADDR_T=logic[15:0] + `checkd($bits(ml1_i2.sub.q), 64); // data_td_t from DATA_T=logic[63:0] + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_param_class_type_param_noinl.py b/test_regress/t/t_iface_param_class_type_param_noinl.py new file mode 100755 index 000000000..dbae0db07 --- /dev/null +++ b/test_regress/t/t_iface_param_class_type_param_noinl.py @@ -0,0 +1,20 @@ +#!/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_iface_param_class_type_param.v" + +test.compile(verilator_flags2=['--binary', '-fno-inline']) + +test.execute() + +test.passes()