From a534a1d1bca737f3b0397b931650cea28648b61b Mon Sep 17 00:00:00 2001 From: em2machine <92717390+em2machine@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:03:28 +0200 Subject: [PATCH] Fix parameter pollution when using class parameters (#7711) (#7763) Fixes #7711. --- src/V3Param.cpp | 14 +++++++ .../t/t_interface_param_class_bits.py | 18 ++++++++ test_regress/t/t_interface_param_class_bits.v | 42 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100755 test_regress/t/t_interface_param_class_bits.py create mode 100644 test_regress/t/t_interface_param_class_bits.v diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 063c55d3a..0cf109211 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1348,6 +1348,17 @@ class ParamProcessor final { } } + // True if a $bits/$size type query in nodep's parameters reads another type parameter. + static bool defaultParamsHaveTypeQueryOnParamType(const AstClassRefDType* nodep) { + bool found = false; + nodep->foreach([&](const AstAttrOf* attrp) { + if (found || !attrp->attrType().isTypeQuery()) return; + const AstRefDType* const refp = VN_CAST(attrp->fromp(), RefDType); + if (refp && VN_IS(refp->refDTypep(), ParamTypeDType)) found = true; + }); + return found; + } + // 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 @@ -1362,6 +1373,9 @@ class ParamProcessor final { const AstNodeModule* const defaultClonep = VN_CAST(origClassRefp->classp()->user4p(), Class); if (defaultClonep && defaultClonep == exprClassRefp->classp()) return true; + // Skip the comparison when the default's $bits/$size reads another type parameter, as + // deparameterizing it below would resolve that shared type at the wrong width (#7711). + if (defaultParamsHaveTypeQueryOnParamType(origClassRefp)) return false; // Slow path: deparameterize the default type and compare the result. // Different templates can never match; use origName() because exprp's // class may already be a specialization (clone) of the template. diff --git a/test_regress/t/t_interface_param_class_bits.py b/test_regress/t/t_interface_param_class_bits.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_interface_param_class_bits.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_interface_param_class_bits.v b/test_regress/t/t_interface_param_class_bits.v new file mode 100644 index 000000000..8994a3152 --- /dev/null +++ b/test_regress/t/t_interface_param_class_bits.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// A class type parameter whose default reads $bits of a sibling type +// parameter must not freeze that sibling at its default width when other +// parameters of the interface are overridden (#7711). +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +class cls #(int width); +endclass + +interface ifc + #(parameter int width = 8, + parameter type dtype = logic[width-1:0], + parameter type cparam = cls#($bits(dtype))); + dtype data; +endinterface + +module t; + // width is overridden, dtype keeps its default logic[width-1:0], and the + // class type parameter is overridden. dtype must follow width (1 bit). + ifc #(.width(1), .cparam(cls#(1))) inst1(); + // Same interface left at its default width (8 bits) must still work. + ifc inst8(); + + always_comb inst1.data = 1'b0; + + initial begin + if ($bits(inst1.data) != 1) begin + $write("%%Error: $bits(inst1.data)=%0d exp=1\n", $bits(inst1.data)); + $stop; + end + if ($bits(inst8.data) != 8) begin + $write("%%Error: $bits(inst8.data)=%0d exp=8\n", $bits(inst8.data)); + $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule