diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 13ead637a..51790b74d 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -875,6 +875,18 @@ class ParamProcessor final { } return true; } + } else if (!entry.cloneCellPath.empty()) { + // Clone entry has no paramTypep stored; look up the type by name. + if (AstParamTypeDType* const ptp + = V3LinkDotIfaceCapture::findParamTypeInModule(targetModp, entry.refp->name())) { + entry.refp->refDTypep(ptp); + entry.refp->dtypep(ptp); + for (AstRefDType* const xrefp : entry.extraRefps) { + xrefp->refDTypep(ptp); + xrefp->dtypep(ptp); + } + return true; + } } return false; } diff --git a/test_regress/t/t_iface_chained_consumer_struct.py b/test_regress/t/t_iface_chained_consumer_struct.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_chained_consumer_struct.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_chained_consumer_struct.v b/test_regress/t/t_iface_chained_consumer_struct.v new file mode 100644 index 000000000..0c0305b4e --- /dev/null +++ b/test_regress/t/t_iface_chained_consumer_struct.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Two-level interface chain. Inner interface has a typedef that +// depends on its parameter. Mid interface aliases that typedef. +// A module aliases the alias and uses it in a packed struct, then +// passes $bits(struct) to a width-parameterized child. All widths +// must use the override value, not the template default. + +interface inner_if #(parameter int N = 1) (); + typedef logic [$clog2(N)-1:0] id_t; +endinterface + +interface mid_if #(parameter int N = 1) (); + inner_if #(.N(N)) inner(); + typedef inner.id_t id_t; +endinterface + +module sink #(parameter int W = 1) (input logic [W-1:0] dat_i); +endmodule + +module dut #(parameter int N = 1) (); + mid_if #(.N(N)) m(); + typedef m.id_t id_t; + typedef struct packed { + id_t id; + logic [7:0] payload; + } pkt_t; + pkt_t pkt_var; + localparam int W = $bits(pkt_t); + sink #(.W(W)) s(.dat_i(pkt_var)); +endmodule + +module t; + // N=8 gives id_t = 3 bits, so pkt_t = 3 + 8 = 11 bits. + dut #(.N(8)) u(); + + initial begin + if (u.W !== 11) begin + $display("%%Error: u.W=%0d expected 11", u.W); + $stop; + end + if ($bits(u.pkt_var) !== 11) begin + $display("%%Error: $bits(u.pkt_var)=%0d expected 11", $bits(u.pkt_var)); + $stop; + end + if ($bits(u.s.dat_i) !== 11) begin + $display("%%Error: $bits(u.s.dat_i)=%0d expected 11", $bits(u.s.dat_i)); + $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_paramgraph_comined_iface_stats.py b/test_regress/t/t_paramgraph_comined_iface_stats.py index 689830310..4732b2b49 100755 --- a/test_regress/t/t_paramgraph_comined_iface_stats.py +++ b/test_regress/t/t_paramgraph_comined_iface_stats.py @@ -23,7 +23,7 @@ test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 8) test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 10) test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 8) test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 10) -test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 2) +test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 0) test.execute() diff --git a/test_regress/t/t_paramgraph_iface_template_nested_stats.py b/test_regress/t/t_paramgraph_iface_template_nested_stats.py index 5ba344e1f..f6e57844b 100755 --- a/test_regress/t/t_paramgraph_iface_template_nested_stats.py +++ b/test_regress/t/t_paramgraph_iface_template_nested_stats.py @@ -23,7 +23,7 @@ test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 11) test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 14) test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 5) test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 10) -test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 2) +test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 0) test.execute() diff --git a/test_regress/t/t_paramgraph_nested_iface_typedef_stats.py b/test_regress/t/t_paramgraph_nested_iface_typedef_stats.py index 841c59c88..e0142486f 100755 --- a/test_regress/t/t_paramgraph_nested_iface_typedef_stats.py +++ b/test_regress/t/t_paramgraph_nested_iface_typedef_stats.py @@ -23,7 +23,7 @@ test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 8) test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 12) test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 8) test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 14) -test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 4) +test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 0) test.execute()