diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 1e38536c7..fa51db2e1 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -63,6 +63,7 @@ Daniel Bates Danny Oler Dave Sargeant David Garau +David Harris David Horton David Ledger David Metz diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 44ca49ee8..8542c9197 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -880,10 +880,12 @@ class ParamProcessor final { V3LinkDotIfaceCapture::forEach([&](const V3LinkDotIfaceCapture::CapturedEntry& entry) { if (!entry.refp) return; if (entry.cloneCellPath != cloneCP) return; - UASSERT_OBJ( - entry.ownerModp - && (entry.ownerModp == newModp || entry.ownerModp->name() == srcName), - entry.refp, "clone ledger entry for '" << cloneCP << "' has unexpected owner"); + // Owner may also be a class nested in newModp (e.g. a covergroup). + const AstNodeModule* const ownerp = entry.ownerModp; + UASSERT_OBJ(ownerp == newModp || ownerp->name() == srcName + || ownerp->aboveLoopp() == newModp, + entry.refp, + "clone ledger entry for '" << cloneCP << "' has unexpected owner"); if (entry.cellPath.empty()) return; AstRefDType* const refp = entry.refp; @@ -1022,12 +1024,15 @@ class ParamProcessor final { if (AstRefDType* const clonedRefp = entry.refp->clonep()) { // Use newname (unique specialized module name) as cloneCellPath. const string cloneCP = newname; - // A cloned captured ref lives inside srcModp's tree, so its owner - // is srcModp (SV has no nested module definitions). - UASSERT_OBJ( - entry.ownerModp == srcModp, clonedRefp, - "cloned captured RefDType owner is not the specialized module"); - AstNodeModule* const clonedOwnerp = newModp; + // Owner is srcModp or a class nested in it (e.g. a covergroup); + // cloneTree() populated clonep() for the nested case. + AstNodeModule* clonedOwnerp = newModp; + if (entry.ownerModp != srcModp) { + clonedOwnerp = entry.ownerModp->clonep(); + UASSERT_OBJ(clonedOwnerp, clonedRefp, + "captured RefDType owner was not cloned with the " + "specialized module"); + } const V3LinkDotIfaceCapture::TemplateKey tkey{ entry.ownerModp ? entry.ownerModp->name() : "", entry.refp->name(), entry.cellPath}; diff --git a/test_regress/t/t_covergroup_param_class.out b/test_regress/t/t_covergroup_param_class.out new file mode 100644 index 000000000..c3e6d712e --- /dev/null +++ b/test_regress/t/t_covergroup_param_class.out @@ -0,0 +1,2 @@ +__vlAnonCG_cg.cp_insn.ecall: 1 +__vlAnonCG_cg2.cp_insn.ebreak: 1 diff --git a/test_regress/t/t_covergroup_param_class.py b/test_regress/t/t_covergroup_param_class.py new file mode 100755 index 000000000..c2434ea87 --- /dev/null +++ b/test_regress/t/t_covergroup_param_class.py @@ -0,0 +1,16 @@ +#!/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 + +import coverage_covergroup_common + +test.scenarios('vlt_all') + +coverage_covergroup_common.run(test) diff --git a/test_regress/t/t_covergroup_param_class.v b/test_regress/t/t_covergroup_param_class.v new file mode 100644 index 000000000..8e32636f1 --- /dev/null +++ b/test_regress/t/t_covergroup_param_class.v @@ -0,0 +1,85 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 David Harris +// SPDX-License-Identifier: CC0-1.0 + +interface trace_if #(parameter int ILEN = 32, parameter int XLEN = 32) (); + logic clk; + logic [ILEN-1:0] insn; +endinterface + +package cov_pkg; + + class TraceData #(parameter int ILEN = 32, parameter int XLEN = 32); + logic [ILEN-1:0] insn; + endclass + + class Instr #(parameter int ILEN = 32, parameter int XLEN = 32); + TraceData #(ILEN, XLEN) current; + function new(); + current = new(); + endfunction + endclass + + class CovBase #(parameter int ILEN = 32, parameter int XLEN = 32); + typedef Instr #(ILEN, XLEN) ins_t; + + // A virtual interface member is what puts an interface type reference + // inside this class, alongside the covergroup below. + virtual trace_if #(ILEN, XLEN) vif; + + covergroup cg with function sample(ins_t ins); + cp_insn: coverpoint ins.current.insn { + bins ecall = {32'h00000073}; + } + endgroup + + // A second covergroup, so one nested class is preceded by another + covergroup cg2 with function sample(ins_t ins); + cp_insn: coverpoint ins.current.insn { + bins ebreak = {32'h00100073}; + } + endgroup + + function new(virtual trace_if #(ILEN, XLEN) vif); + this.vif = vif; + cg = new(); + cg2 = new(); + endfunction + + function void do_sample(); + ins_t ins = new(); + ins.current.insn = 32'h00000073; + cg.sample(ins); + ins.current.insn = 32'h00100073; + cg2.sample(ins); + endfunction + endclass + +endpackage + +import cov_pkg::*; + +class Cov #(parameter int ILEN = 32, parameter int XLEN = 32) + extends CovBase #(ILEN, XLEN); + function new(virtual trace_if #(ILEN, XLEN) vif); + super.new(vif); + endfunction +endclass + +module worker(trace_if vif); + // Specialized from parameters read off the interface instance + Cov #(vif.ILEN, vif.XLEN) cov; + initial begin + cov = new(vif); + cov.do_sample(); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule + +module t; + trace_if #(32, 64) vif(); + worker worker(vif); +endmodule