From caa3d6512f04c2fef70e06bffe373a38bf28a473 Mon Sep 17 00:00:00 2001 From: wsxarcher Date: Fri, 26 Jun 2026 12:22:35 +0200 Subject: [PATCH] Fix external clock driven coverage capture --- include/verilated_types.h | 16 +- src/V3Covergroup.cpp | 174 ++++++++++++------ test_regress/t/coverage_covergroup_common.py | 4 +- .../t/t_covergroup_embedded_timing.out | 4 + .../t/t_covergroup_embedded_timing.py | 19 ++ test_regress/t/t_covergroup_embedded_timing.v | 90 +++++++++ .../t/t_covergroup_member_event_unsup.out | 15 +- .../t/t_covergroup_member_event_unsup.py | 6 +- .../t/t_covergroup_member_event_unsup.v | 64 ++++++- test_regress/t/t_covergroup_unsup_ign2.out | 8 +- 10 files changed, 328 insertions(+), 72 deletions(-) create mode 100644 test_regress/t/t_covergroup_embedded_timing.out create mode 100644 test_regress/t/t_covergroup_embedded_timing.py create mode 100644 test_regress/t/t_covergroup_embedded_timing.v diff --git a/include/verilated_types.h b/include/verilated_types.h index ca450290b..2f1464080 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -2103,7 +2103,7 @@ public: VlClassRef() = default; // Init with nullptr // cppcheck-suppress noExplicitConstructor - VlClassRef(VlNull){}; + VlClassRef(VlNull) {}; template VlClassRef(VlDeleter& deleter, T_Args&&... args) : m_objp{new T_Class} { @@ -2158,6 +2158,12 @@ public: ~VlClassRef() { refCountDec(); } // METHODS + /// Create a non-owning ("weak") reference: it does not change the reference count, so the + /// pointed-to object must be kept alive by some other (strong) reference for as long as + /// this one is used. Dereferencing a weak reference after the object has been freed is + /// undefined. Used only for an embedded covergroup's back-pointer to its enclosing + /// object, which the enclosing object transitively owns, so the back-pointer is always + /// outlived by a strong reference (IEEE 1800-2023 19.4). static VlClassRef weak(T_Class* objp) { VlClassRef ref; ref.m_objp = objp; @@ -2252,6 +2258,14 @@ static inline bool VL_CAST_DYNAMIC(VlClassRef in, VlClassRef& outr return false; } +// Create a non-owning ("weak") reference to an existing object, deducing the class type from +// the pointer. See VlClassRef::weak; used for an embedded covergroup's back-pointer to its +// enclosing object so the back-pointer does not keep the enclosing object alive. +template +static inline VlClassRef VL_WEAK_REF(T_Class* objp) { + return VlClassRef::weak(objp); +} + template static inline bool VL_CAST_DYNAMIC(VlNull, VlClassRef& outr) { outr = VlNull{}; diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 9ba2d6a15..2114422cb 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -73,13 +73,15 @@ class FunctionalCoverageVisitor final : public VNVisitor { std::vector m_binInfos; // All bins in current covergroup struct EmbeddedEventTrigger final { - AstVar* baseVarp; - AstVar* memberVarp; - VEdgeType edgeType; - AstVar* prevVarp; - EmbeddedEventTrigger(AstVar* baseVarp, AstVar* memberVarp, VEdgeType edgeType, - AstVar* prevVarp) - : baseVarp{baseVarp} + FileLine* eventFl; // Source location of the clocking-event item (for diagnostics) + AstVar* baseVarp; // Enclosing-class member naming the event signal + AstVar* memberVarp; // Sub-member when the event is 'base.member', else nullptr + VEdgeType edgeType; // Edge sensitivity (posedge/negedge/changed) + AstVar* prevVarp; // Previous-value member; created lazily during install + EmbeddedEventTrigger(FileLine* eventFl, AstVar* baseVarp, AstVar* memberVarp, + VEdgeType edgeType, AstVar* prevVarp) + : eventFl{eventFl} + , baseVarp{baseVarp} , memberVarp{memberVarp} , edgeType{edgeType} , prevVarp{prevVarp} {} @@ -1728,6 +1730,10 @@ class FunctionalCoverageVisitor final : public VNVisitor { } AstVar* findEmbeddedCovergroupVar() const { + // Return the enclosing-class member that holds this embedded covergroup instance. + // An embedded covergroup declaration declares a single anonymous type and a single + // instance variable of that type (IEEE 1800-2023 19.4); a second variable of the same + // anonymous type is not expressible, so the first match is the sole instance. if (!m_enclosingClassp) return nullptr; for (AstNode* itemp = m_enclosingClassp->membersp(); itemp; itemp = itemp->nextp()) { if (AstVar* const varp = VN_CAST(itemp, Var)) { @@ -1743,7 +1749,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { bool rewriteThisRef(AstThisRef* refp, AstVar* handleVarp) { if (!m_enclosingClassp) return false; - const AstClassRefDType* const refDTypep = VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType); + const AstClassRefDType* const refDTypep + = VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType); if (!refDTypep || refDTypep->classp() != m_covergroupp) return false; AstNodeExpr* const newp = newEnclosingHandleRef(refp->fileline(), handleVarp); refp->replaceWith(newp); @@ -1825,6 +1832,36 @@ class FunctionalCoverageVisitor final : public VNVisitor { return new AstVarRef{fl, varp, VAccess::READ}; } + AstNodeExpr* newEmbeddedVarNonNull(FileLine* fl) { + return new AstNeq{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ}, + new AstConst{fl, AstConst::Null{}}}; + } + + void installEmbeddedEventFork(AstSenTree* eventp) { + // Full clocking-event support for an embedded covergroup (IEEE 1800-2023 19.8.1): + // a coverage point is sampled the instant the clocking event takes place, on every + // occurrence, regardless of where the event signal is written. The covergroup is a + // dynamic per-instance object, so static sensitivity blocks (V3Active) cannot model + // this. Instead spawn, in the enclosing constructor right after construction, a + // persistent process: + // fork forever begin @(event); if (cgvar != null) cgvar.sample(); end join_none + // The event SenTree references enclosing-class members, which resolve directly against + // 'this' in the constructor. This requires --timing (fork + event control); the caller + // only reaches here when timing is enabled. 'eventp' is an unlinked SenTree template + // owned here. + const std::vector constructps = findCovergroupConstructions(); + for (AstNodeAssign* const constructp : constructps) { + FileLine* const fl = constructp->fileline(); + AstLoop* const loopp = new AstLoop{fl}; + loopp->addStmtsp(new AstEventControl{fl, eventp->cloneTree(false), nullptr}); + loopp->addStmtsp(new AstIf{fl, newEmbeddedVarNonNull(fl), newSampleStmt(fl)}); + AstFork* const forkp = new AstFork{fl, VJoinType::JOIN_NONE}; + forkp->addForksp(new AstBegin{fl, "", loopp, true}); + constructp->addNextHere(forkp); + } + VL_DO_DANGLING(pushDeletep(eventp), eventp); + } + AstNodeExpr* newEventReadyCondition(FileLine* fl, const EmbeddedEventTrigger& trigger) { AstNodeExpr* const curp = newEventRead(fl, trigger); AstNodeExpr* const prevp = newVarRead(fl, trigger.prevVarp); @@ -1836,16 +1873,15 @@ class FunctionalCoverageVisitor final : public VNVisitor { } else { edgep = new AstNeq{fl, curp, prevp}; } - return new AstLogAnd{ - fl, new AstNeq{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ}, - new AstConst{fl, AstConst::Null{}}}, - edgep}; + return new AstLogAnd{fl, + new AstNeq{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ}, + new AstConst{fl, AstConst::Null{}}}, + edgep}; } AstNodeStmt* newSampleStmt(FileLine* fl) { - AstMethodCall* const callp - = new AstMethodCall{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ}, "sample", - nullptr}; + AstMethodCall* const callp = new AstMethodCall{ + fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ}, "sample", nullptr}; callp->taskp(m_sampleFuncp); callp->dtypeSetVoid(); return callp->makeStmt(); @@ -1871,28 +1907,45 @@ class FunctionalCoverageVisitor final : public VNVisitor { AstVar* memberVarp = nullptr; UASSERT_OBJ(parseEmbeddedEventExpr(itemp->sensp(), baseVarp, memberVarp), itemp, "Bad embedded covergroup event expression"); - FileLine* const fl = itemp->fileline(); - EmbeddedEventTrigger trigger{baseVarp, memberVarp, itemp->edgeType(), nullptr}; - AstVar* const prevVarp = newEventPrevVar(fl, trigger); - m_enclosingClassp->addMembersp(prevVarp); - trigger.prevVarp = prevVarp; - triggers.push_back(trigger); + // Defer the previous-value member to installEmbeddedEventTriggers so it is only + // created once an in-class assignment to instrument is known to exist. + triggers.emplace_back(itemp->fileline(), baseVarp, memberVarp, itemp->edgeType(), + nullptr); } return triggers; } - void installEmbeddedEventTriggers(const std::vector& triggers) { - if (triggers.empty()) return; - m_enclosingClassp->foreach([&](AstNodeAssign* asgnp) { - for (const EmbeddedEventTrigger& trigger : triggers) { - if (!isEventLvalue(asgnp->lhsp(), trigger)) continue; + void installEmbeddedEventTriggers(std::vector& triggers) { + // An embedded covergroup is lowered into a sibling class, so a clocking event on an + // enclosing-class member cannot be placed in a static sensitivity list. Approximate + // it by sampling immediately after each blocking assignment to the event signal that + // appears textually within the enclosing class. This is only an approximation: it + // cannot observe changes made from outside the class (e.g. a testbench poking a public + // member) nor model the exact scheduling region of a real clocking event. When the + // signal is never assigned inside the class no sampling is possible at all, so warn + // rather than silently producing zero coverage (IEEE 1800-2023 19.4). + for (EmbeddedEventTrigger& trigger : triggers) { + std::vector assignps; + m_enclosingClassp->foreach([&](AstNodeAssign* asgnp) { + if (isEventLvalue(asgnp->lhsp(), trigger)) assignps.push_back(asgnp); + }); + if (assignps.empty()) { + trigger.eventFl->v3warn( + COVERIGN, "Unsupported: 'covergroup' clocking event signal has no assignment " + "within the enclosing class; no coverage sampled"); + continue; + } + AstVar* const prevVarp = newEventPrevVar(trigger.eventFl, trigger); + m_enclosingClassp->addMembersp(prevVarp); + trigger.prevVarp = prevVarp; + for (AstNodeAssign* const asgnp : assignps) { FileLine* const fl = asgnp->fileline(); AstIf* const ifp = new AstIf{fl, newEventReadyCondition(fl, trigger), newSampleStmt(fl)}; ifp->addNextHere(newPrevUpdate(fl, trigger)); asgnp->addNextHere(ifp); } - }); + } } AstNode* findEnclosingMemberRef() { @@ -1937,7 +1990,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // assignments so each constructed instance receives its back-pointer. std::vector foundps; if (!m_embeddedVarp) return foundps; - AstFunc* const enclosingNewp = VN_CAST(m_memberMap.findMember(m_enclosingClassp, "new"), Func); + AstFunc* const enclosingNewp + = VN_CAST(m_memberMap.findMember(m_enclosingClassp, "new"), Func); if (!enclosingNewp) return foundps; enclosingNewp->foreach([&](AstNodeAssign* asgnp) { const AstNew* const newp = VN_CAST(asgnp->rhsp(), New); @@ -1992,7 +2046,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { rootp->foreach([&](AstThisRef* refp) { const AstClassRefDType* const refDTypep = VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType); - if (refDTypep && refDTypep->classp() == m_covergroupp) thisRefsToRewrite.push_back(refp); + if (refDTypep && refDTypep->classp() == m_covergroupp) + thisRefsToRewrite.push_back(refp); }); }; for (AstCoverpoint* const cpp : m_coverpoints) scan(cpp); @@ -2009,8 +2064,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Commit: add the back-pointer member, rewrite the references, initialize the handle. FileLine* const fl = m_covergroupp->fileline(); - AstClassRefDType* const enclDTypep - = new AstClassRefDType{fl, m_enclosingClassp, nullptr}; + AstClassRefDType* const enclDTypep = new AstClassRefDType{fl, m_enclosingClassp, nullptr}; v3Global.rootp()->typeTablep()->addTypesp(enclDTypep); AstVar* const handleVarp = new AstVar{fl, VVarType::MEMBER, "__Vcg_enclosingp", enclDTypep}; @@ -2018,23 +2072,21 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_covergroupp->addMembersp(handleVarp); // Route each enclosing-member reference through the back-pointer: 'm' -> 'h.m'. - for (AstVarRef* const refp : refsToRewrite) { - rewriteVarRef(refp, handleVarp); - } - for (AstThisRef* const refp : thisRefsToRewrite) { - rewriteThisRef(refp, handleVarp); - } + for (AstVarRef* const refp : refsToRewrite) { rewriteVarRef(refp, handleVarp); } + for (AstThisRef* const refp : thisRefsToRewrite) { rewriteThisRef(refp, handleVarp); } // Initialize the handle right after construction: 'cgvar.__Vcg_enclosingp = this;'. + // The back-pointer must be a non-owning (weak) reference so it does not keep the + // enclosing object alive (the enclosing object owns the covergroup). There is no AST + // node for a weak class-handle literal, so emit the runtime helper VL_WEAK_REF; it + // contains no source identifiers, so '--protect' is unaffected. for (AstNodeAssign* const constructp : constructps) { FileLine* const cfl = constructp->fileline(); AstMemberSel* const lhsp = new AstMemberSel{cfl, constructp->lhsp()->cloneTree(false), handleVarp}; lhsp->access(VAccess::WRITE); AstCExpr* const weakThisp = new AstCExpr{cfl}; - weakThisp->add("decltype("); - weakThisp->add(lhsp->cloneTree(false)); - weakThisp->add(")::weak(this)"); + weakThisp->add("VL_WEAK_REF(this)"); weakThisp->dtypep(enclDTypep); constructp->addNextHere(new AstAssign{cfl, lhsp, weakThisp}); } @@ -2060,6 +2112,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_coverCrosses.clear(); m_embeddedVarp = findEmbeddedCovergroupVar(); std::vector embeddedEventTriggers; + // Clocking event referencing an enclosing member, lowered via a forked sampling + // process when --timing is available (full support); null otherwise. + AstSenTree* embeddedEventForkp = nullptr; // Extract and store the clocking event from AstCovergroup node // The parser creates this node to preserve the event information @@ -2072,23 +2127,33 @@ class FunctionalCoverageVisitor final : public VNVisitor { // event exists, so cgp->eventp() is always non-null here. UASSERT_OBJ(cgp->eventp(), cgp, "Sentinel AstCovergroup in class must have non-null eventp"); - embeddedEventTriggers = collectEmbeddedEventTriggers(cgp); - if (!embeddedEventTriggers.empty()) { - VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp); - itemp = nextp; - continue; - } if (hasEmbeddedEventExpr(cgp)) { - cgp->v3warn(COVERIGN, "Unsupported: 'covergroup' clocking event " - "on complex member expression"); - hasUnsupportedEvent = true; + // The clocking event references a member of the enclosing class, so it + // names a per-instance signal that V3Active's static-sensitivity path + // cannot express. Under --timing, lower it fully: a forked + // 'forever @(event) sample()' process spawned in the enclosing + // constructor samples on every occurrence of the event regardless of + // where the signal is written (IEEE 1800-2023 19.8.1). Without + // --timing, dynamic per-instance event waits are unavailable, so fall + // back to a best-effort instrumentation of in-class assignments. + if (v3Global.opt.timing().isSetTrue()) { + embeddedEventForkp = cgp->eventp()->unlinkFrBack(); + } else { + embeddedEventTriggers = collectEmbeddedEventTriggers(cgp); + if (embeddedEventTriggers.empty()) { + cgp->v3warn(COVERIGN, + "Unsupported: 'covergroup' clocking event on complex " + "member expression; use --timing for full support"); + hasUnsupportedEvent = true; + } + } VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp); itemp = nextp; continue; } - // Leave cgp in the class membersp so the SenTree stays linked in the AST. - // V3Active will find it via membersp, use the event, then delete the - // AstCovergroup itself. + // Event references only static/global signals. Leave cgp in the class + // membersp so the SenTree stays linked in the AST. V3Active will find it + // via membersp, use the event, then delete the AstCovergroup itself. UINFO(4, "Keeping covergroup event node for V3Active: " << nodep->name()); itemp = nextp; continue; @@ -2139,10 +2204,15 @@ class FunctionalCoverageVisitor final : public VNVisitor { for (AstCoverCross* crossp : m_coverCrosses) { VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp); } + // The covergroup is ignored, so its sampling process is moot. + if (embeddedEventForkp) { + VL_DO_DANGLING(pushDeletep(embeddedEventForkp), embeddedEventForkp); + } return; } } installEmbeddedEventTriggers(embeddedEventTriggers); + if (embeddedEventForkp) installEmbeddedEventFork(embeddedEventForkp); processCovergroup(); // Remove lowered coverpoints/crosses from the class - they have been diff --git a/test_regress/t/coverage_covergroup_common.py b/test_regress/t/coverage_covergroup_common.py index 5bea9d373..7c33beddc 100644 --- a/test_regress/t/coverage_covergroup_common.py +++ b/test_regress/t/coverage_covergroup_common.py @@ -45,8 +45,8 @@ def covergroup_coverage_report(test, outfile=None): return outfile -def run(test, *, verilator_flags2=()): - test.compile(verilator_flags2=['--coverage', *verilator_flags2]) +def run(test, *, verilator_flags2=(), timing_loop=False): + test.compile(verilator_flags2=['--coverage', *verilator_flags2], timing_loop=timing_loop) test.execute() covergroup_coverage_report(test) test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) diff --git a/test_regress/t/t_covergroup_embedded_timing.out b/test_regress/t/t_covergroup_embedded_timing.out new file mode 100644 index 000000000..70d5bfc0f --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_timing.out @@ -0,0 +1,4 @@ +__vlAnonCG_chain_cg.cp_val.hi: 4 +__vlAnonCG_chain_cg.cp_val.lo: 3 +__vlAnonCG_ext_cg.cp_val.hi: 8 +__vlAnonCG_ext_cg.cp_val.lo: 0 diff --git a/test_regress/t/t_covergroup_embedded_timing.py b/test_regress/t/t_covergroup_embedded_timing.py new file mode 100644 index 000000000..4c3b0290b --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_timing.py @@ -0,0 +1,19 @@ +#!/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') + +# --timing enables full embedded-covergroup clocking-event support: the covergroup is +# sampled on every occurrence of the event, including events driven from outside the +# enclosing class and complex member-chain events. +coverage_covergroup_common.run(test, verilator_flags2=['--timing'], timing_loop=True) diff --git a/test_regress/t/t_covergroup_embedded_timing.v b/test_regress/t/t_covergroup_embedded_timing.v new file mode 100644 index 000000000..0c7c8f01c --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_timing.v @@ -0,0 +1,90 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Full clocking-event support for embedded covergroups (IEEE 1800-2023 19.8.1): +// with --timing a covergroup is sampled on every occurrence of its clocking event, +// regardless of where the event signal is written. This exercises events driven +// from OUTSIDE the enclosing class (impossible to model without --timing) and a +// complex member-chain event, both of which the --no-timing path cannot sample. + +class ExtClkMonitor; + bit clk; // Clocking-event signal driven only from outside the class + bit [3:0] value; + + covergroup ext_cg @(posedge clk); + cp_val: coverpoint value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + function new(); + ext_cg = new; + endfunction +endclass + +class Lvl; + bit ev; +endclass +class Mid; + Lvl lvl; +endclass + +class ChainMonitor; + bit a; // First clocking term, driven externally + Mid mid; // Second clocking term reached via a member chain, driven externally + bit [3:0] value; + + covergroup chain_cg @(posedge a or posedge mid.lvl.ev); + cp_val: coverpoint value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + function new(); + mid = new; + mid.lvl = new; + chain_cg = new; + endfunction +endclass + +module t; + ExtClkMonitor extm; + ChainMonitor chain; + int i; + + initial begin + extm = new; + chain = new; + + // Drive the clocking event from OUTSIDE the class: 8 external posedges, all + // sampling the 'hi' range -> cp_value.hi = 8, cp_value.lo = 0. + for (i = 0; i < 8; ++i) begin + extm.value = 4'h8 | i[3:0]; + extm.clk = 1'b0; + #1; + extm.clk = 1'b1; + #1; + end + + // Complex member-chain event: 4 posedges on 'a' (hi range) and 3 posedges on + // 'mid.lvl.ev' (lo range), all driven externally -> cp_value.hi = 4, .lo = 3. + for (i = 0; i < 4; ++i) begin + chain.value = 4'hC; + chain.a = 1'b0; + #1; + chain.a = 1'b1; + #1; + end + for (i = 0; i < 3; ++i) begin + chain.value = 4'h2; + chain.mid.lvl.ev = 1'b0; + #1; + chain.mid.lvl.ev = 1'b1; + #1; + end + + #1; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_member_event_unsup.out b/test_regress/t/t_covergroup_member_event_unsup.out index e9abfdcd4..38e1831df 100644 --- a/test_regress/t/t_covergroup_member_event_unsup.out +++ b/test_regress/t/t_covergroup_member_event_unsup.out @@ -1,7 +1,14 @@ -%Warning-COVERIGN: t/t_covergroup_member_event_unsup.v:13:5: Unsupported: 'covergroup' clocking event on member variable - : ... note: In instance 't' - 13 | covergroup cov1 @m_z; - | ^~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_member_event_unsup.v:19:18: Unsupported: 'covergroup' coverage construct referencing enclosing class member; ignoring covergroup '__vlAnonCG_cov_nonew' + : ... note: In instance 't' + 19 | coverpoint m_x {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + | ^~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_covergroup_member_event_unsup.v:28:29: Unsupported: 'covergroup' clocking event signal has no assignment within the enclosing class; no coverage sampled + 28 | covergroup cov_extclk @(posedge clk); + | ^~~~~~~ +%Warning-COVERIGN: t/t_covergroup_member_event_unsup.v:48:5: Unsupported: 'covergroup' clocking event on complex member expression; use --timing for full support + : ... note: In instance 't' + 48 | covergroup cov_cplx @(posedge a or posedge mid.lvl.ev); + | ^~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_member_event_unsup.py b/test_regress/t/t_covergroup_member_event_unsup.py index 695572bb1..189ca8a23 100755 --- a/test_regress/t/t_covergroup_member_event_unsup.py +++ b/test_regress/t/t_covergroup_member_event_unsup.py @@ -11,6 +11,10 @@ import vltest_bootstrap test.scenarios('vlt') -test.lint(expect_filename=test.golden_filename, fails=True) +# Without --timing, an embedded covergroup clocked on (or otherwise referencing) an +# enclosing-class member cannot be fully supported: dynamic per-instance event waits are +# unavailable. Verilator emits a clean COVERIGN warning for each such limitation. With +# --timing these same covergroups are fully supported (see t_covergroup_embedded_timing). +test.lint(verilator_flags2=['--no-timing'], expect_filename=test.golden_filename, fails=True) test.passes() diff --git a/test_regress/t/t_covergroup_member_event_unsup.v b/test_regress/t/t_covergroup_member_event_unsup.v index d08249b48..32aad8437 100644 --- a/test_regress/t/t_covergroup_member_event_unsup.v +++ b/test_regress/t/t_covergroup_member_event_unsup.v @@ -4,17 +4,65 @@ // SPDX-FileCopyrightText: 2024 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 -module t ( - input clk -); - class Packet; - int m_z; - int m_x; - covergroup cov1 @m_z; - coverpoint m_x; +// Embedded covergroups (IEEE 1800-2023 19.4) whose clocking event or coverage +// constructs reference enclosing-class members in ways Verilator cannot fully +// support. Each must emit a clean COVERIGN warning rather than silently +// producing wrong coverage or uncompilable C++. + +module t; + // Coverpoint references an enclosing member, but the covergroup is never + // constructed (no 'new'), so it cannot be lowered through a back-pointer. + class NoConstruct; + bit [3:0] m_x; + bit m_z; + covergroup cov_nonew @m_z; + coverpoint m_x {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} endgroup endclass + + // Clocking event on a member that is never assigned inside the class, so no + // in-class sampling point exists (e.g. the signal is driven only externally). + class ExternalClk; + bit clk; + bit [3:0] m_x; + covergroup cov_extclk @(posedge clk); + coverpoint m_x {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + function new(); + cov_extclk = new; + endfunction + endclass + + // Clocking event mixing a member signal with a complex member chain that the + // member-event lowering cannot represent. + class Lvl; + bit ev; + endclass + class Mid; + Lvl lvl; + endclass + class ComplexEvt; + bit a; + Mid mid; + bit [3:0] m_x; + covergroup cov_cplx @(posedge a or posedge mid.lvl.ev); + coverpoint m_x {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + function new(); + mid = new; + mid.lvl = new; + cov_cplx = new; + endfunction + endclass + + NoConstruct nc; + ExternalClk ec; + ComplexEvt cx; + initial begin + nc = new; + ec = new; + cx = new; $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_unsup_ign2.out b/test_regress/t/t_covergroup_unsup_ign2.out index 73065a5c5..a5bfa6bd1 100644 --- a/test_regress/t/t_covergroup_unsup_ign2.out +++ b/test_regress/t/t_covergroup_unsup_ign2.out @@ -300,7 +300,7 @@ : ... note: In instance 't' 177 | cross a, b { | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:209:5: Unsupported: 'covergroup' clocking event on member variable - : ... note: In instance 't' - 209 | covergroup cov1 @m_z; - | ^~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:210:24: Unsupported: 'covergroup' coverage construct referencing enclosing class member; ignoring covergroup '__vlAnonCG_cov1' + : ... note: In instance 't' + 210 | cp_x: coverpoint m_x; + | ^~~