Fix parameterized class typedef as interface type parameter (#7000) (#7006)

Fixes #7000.
This commit is contained in:
Leela Pakanati 2026-04-12 19:38:27 -05:00 committed by GitHub
parent fac07970e6
commit 14e2f834e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 225 additions and 8 deletions

View File

@ -584,11 +584,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;

View File

@ -1973,6 +1973,28 @@ void AstClassRefDType::dumpSmall(std::ostream& str) const {
}
string AstClassRefDType::prettyDTypeName(bool) const { return "class{}"s + prettyName(); }
string AstClassRefDType::name() const { return classp() ? classp()->name() : "<unlinked>"; }
bool AstClassRefDType::similarDTypeNode(const AstNodeDType* samep) const {
const AstClassRefDType* const asamep = VN_DBG_AS(samep, ClassRefDType);
if (m_classp != asamep->m_classp) return false;
// Compare type parameters so 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()) {
const AstNodeDType* const lDtp = VN_CAST(lp->exprp(), NodeDType);
const AstNodeDType* const rDtp = VN_CAST(rp->exprp(), NodeDType);
if (lDtp && rDtp) {
if (!lDtp->similarDType(rDtp)) return false;
} else {
if (!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->userType().ascii() + "]";

View File

@ -1214,13 +1214,29 @@ class ParamProcessor final {
if (!parseRefp) return;
const AstClass* lhsClassp = VN_CAST(classRefp->classOrPackageSkipp(), Class);
// Specialize parameterized class through type parameter indirection (#7000)
AstParamTypeDType* const paramTypep
= VN_CAST(classRefp->classOrPackageNodep(), ParamTypeDType);
if (paramTypep) {
AstNodeDType* const dtypep = paramTypep->subDTypep();
AstClassRefDType* const classRefDTypep
= dtypep ? VN_CAST(dtypep->skipRefOrNullp(), ClassRefDType) : nullptr;
if (classRefDTypep) {
AstClass* const srcClassp = classRefDTypep->classp();
if (srcClassp && srcClassp->hasGParam() && classRefDTypep->paramsp()) {
if (lhsClassp == srcClassp || !lhsClassp) {
classRefDeparam(classRefDTypep, srcClassp);
lhsClassp = classRefDTypep->classp();
}
}
}
}
if (classRefp->paramsp()) {
// ClassOrPackageRef has parameters - may need to specialize the class
AstClass* const srcClassp = VN_CAST(classRefp->classOrPackageNodep(), Class);
if (srcClassp && srcClassp->hasGParam()) {
// Specialize if the reference still points to the generic class
if (lhsClassp == srcClassp || !lhsClassp) {
UINFO(9, "resolveDotToTypedef: specializing " << srcClassp->name());
classRefDeparam(classRefp, srcClassp);
lhsClassp = VN_CAST(classRefp->classOrPackageSkipp(), Class);
}
@ -1238,6 +1254,49 @@ 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) {
AstRefDType* const refp = dtypep ? VN_CAST(dtypep, RefDType) : nullptr;
if (!refp) return;
if (refp->typedefp() || refp->refDTypep()) return;
AstClassOrPackageRef* const classRefp
= VN_CAST(refp->classOrPackageOpp(), ClassOrPackageRef);
if (!classRefp) return;
AstClass* srcClassp = VN_CAST(classRefp->classOrPackageNodep(), Class);
if (srcClassp && srcClassp->hasGParam()) {
classRefDeparam(classRefp, srcClassp);
return;
}
// Follow typedef chain to find the underlying ClassRefDType
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()) {
classRefDeparam(classRefDTypep, srcClassp);
newClassp = classRefDTypep->classp();
}
AstTypedef* const typedefp
= VN_CAST(m_memberMap.findMember(newClassp, refp->name()), Typedef);
if (typedefp) {
refp->typedefp(typedefp);
refp->classOrPackagep(newClassp);
}
}
// Check if exprp's class matches origp's class after deparameterization.
// Handles both the simple case (user4p link from defaultsResolved) and the
// nested case where the default's inner class has non-default sub-parameters
@ -1299,6 +1358,7 @@ class ParamProcessor final {
string& longnamer, bool& any_overridesr) {
if (!pinp->exprp()) return; // No-connect
if (AstVar* const modvarp = pinp->modVarp()) {
resolveParamClassRefDType(modvarp->subDTypep());
if (!modvarp->isGParam()) {
pinp->v3fatalSrc("Attempted parameter setting of non-parameter: Param "
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
@ -1415,6 +1475,12 @@ 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());
resolveParamClassRefDType(modvarp->subDTypep());
// Also resolve through the pin expression's typedef chain
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);
// Guard against widthing a struct/union still owned by a
@ -1480,6 +1546,15 @@ class ParamProcessor final {
rawTypep = VN_CAST(pinp->exprp(), NodeDType);
exprp = rawTypep ? rawTypep->skipRefToNonRefp() : nullptr;
}
// Deparameterize ClassRefDType before name generation (#7000)
if (AstClassRefDType* const classRefDTypep = VN_CAST(exprp, ClassRefDType)) {
if (classRefDTypep->paramsp() && classRefDTypep->classp()
&& classRefDTypep->classp()->hasGParam()) {
classRefDeparam(classRefDTypep, classRefDTypep->classp());
rawTypep = VN_CAST(pinp->exprp(), NodeDType);
exprp = rawTypep ? rawTypep->skipRefToNonRefp() : nullptr;
}
}
longnamer += "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(exprp);
any_overridesr = true;
}

View File

@ -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()

View File

@ -0,0 +1,86 @@
// 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 (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

View File

@ -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.v"
test.compile(verilator_flags2=['--binary', '-fno-inline'])
test.execute()
test.passes()