diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index fbbceaec7..6f016a185 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -1568,15 +1568,23 @@ class AstFork final : public AstNodeBlock { // // @astgen op3 := forksp : List[AstBegin] const VJoinType m_joinType; // Join keyword type + bool m_immediateStart = false; // Fork starts before its parent blocks or exits + public: AstFork(FileLine* fl, VJoinType joinType, const string& name = "") : ASTGEN_SUPER_Fork(fl, name) , m_joinType{joinType} {} ASTGEN_MEMBERS_AstFork; + bool sameNode(const AstNode* samep) const override { + const AstFork* const asamep = VN_DBG_AS(samep, Fork); + return joinType() == asamep->joinType() && immediateStart() == asamep->immediateStart(); + } bool isTimingControl() const override { return !joinType().joinNone(); } void dump(std::ostream& str) const override; void dumpJson(std::ostream& str) const override; VJoinType joinType() const { return m_joinType; } + bool immediateStart() const { return m_immediateStart; } + void immediateStart(bool flag) { m_immediateStart = flag; } }; // === AstNodeCoverOrAssert === diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 4dcad4a3e..e6f4f72ca 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -3649,9 +3649,11 @@ void AstCoverInc::dumpJson(std::ostream& str) const { dumpJsonGen(str); } void AstFork::dump(std::ostream& str) const { this->AstNodeBlock::dump(str); str << " [" << joinType() << "]"; + if (immediateStart()) str << " [IMMEDIATE]"; } void AstFork::dumpJson(std::ostream& str) const { dumpJsonStr(str, "joinType", joinType().ascii()); + dumpJsonBoolFuncIf(str, immediateStart); dumpJsonGen(str); } void AstStop::dump(std::ostream& str) const { diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 67b612914..dc1b5a2e7 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -45,7 +45,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // STATE AstClass* m_covergroupp = nullptr; // Current covergroup being processed - AstClass* m_enclosingClassp = nullptr; // Class lexically enclosing the covergroup (if any) + AstClass* m_enclosingClassp = nullptr; // Class lexically enclosing the covergroup, if any + AstVar* m_embeddedVarp = nullptr; // Embedded covergroup member of m_enclosingClassp, if any AstFunc* m_sampleFuncp = nullptr; // Current sample() function AstFunc* m_constructorp = nullptr; // Current constructor std::vector m_coverpoints; // Coverpoints in current covergroup @@ -71,6 +72,21 @@ class FunctionalCoverageVisitor final : public VNVisitor { }; std::vector m_binInfos; // All bins in current covergroup + struct EmbeddedEventTrigger final { + FileLine* eventFl; // Clocking-event source location + AstVar* baseVarp; // Base enclosing-class member in the event expression + AstVar* memberVarp; // Selected member in a 'base.member' expression, or nullptr + VEdgeType edgeType; // Clocking-event edge qualifier + AstVar* prevVarp; // Member containing the previous event value, or nullptr + EmbeddedEventTrigger(FileLine* eventFl, AstVar* baseVarp, AstVar* memberVarp, + VEdgeType edgeType) + : eventFl{eventFl} + , baseVarp{baseVarp} + , memberVarp{memberVarp} + , edgeType{edgeType} + , prevVarp{nullptr} {} + }; + std::set m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy) std::vector m_convCpVars; // VlCoverpoint members of converted coverpoints AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type @@ -1718,6 +1734,220 @@ class FunctionalCoverageVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(refp), refp); } + bool isEmbeddedCovergroupVar(const AstVar* varp) const { + if (!varp || !varp->isClassMember()) return false; + const AstClassRefDType* const refp = VN_CAST(varp->dtypep()->skipRefp(), ClassRefDType); + return refp && refp->classp() == m_covergroupp; + } + + AstVar* findEmbeddedCovergroupVar() const { + if (!m_enclosingClassp) return nullptr; + for (AstNode* itemp = m_enclosingClassp->membersp(); itemp; itemp = itemp->nextp()) { + if (AstVar* const varp = VN_CAST(itemp, Var)) { + if (isEmbeddedCovergroupVar(varp)) return varp; + } + } + // V3LinkParse always creates an implicit variable for an embedded covergroup. + return nullptr; // LCOV_EXCL_LINE + } + + std::vector findCovergroupConstructions() { + std::vector foundps; + if (!m_embeddedVarp) return foundps; + 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); + const AstVarRef* const lhsRefp = VN_CAST(asgnp->lhsp(), VarRef); + if (!newp || !lhsRefp || lhsRefp->varp() != m_embeddedVarp) return; + const AstClassRefDType* const refp = VN_CAST(newp->dtypep(), ClassRefDType); + if (refp && refp->classp() == m_covergroupp) foundps.push_back(asgnp); + }); + return foundps; + } + + AstNodeAssign* findInvalidEmbeddedCovergroupAssignment() { + if (!m_embeddedVarp) return nullptr; + std::set constructorAssignps; + AstFunc* const enclosingNewp + = VN_CAST(m_memberMap.findMember(m_enclosingClassp, "new"), Func); + if (enclosingNewp) { + enclosingNewp->foreach([&](AstNodeAssign* asgnp) { + const AstVarRef* const refp = VN_CAST(asgnp->lhsp(), VarRef); + if (refp && refp->varp() == m_embeddedVarp) constructorAssignps.insert(asgnp); + }); + } + AstNodeAssign* invalidp = nullptr; + m_enclosingClassp->foreach([&](AstNodeAssign* asgnp) { + if (invalidp || constructorAssignps.count(asgnp)) return; + const AstVarRef* const refp = VN_CAST(asgnp->lhsp(), VarRef); + if (refp && refp->varp() == m_embeddedVarp) invalidp = asgnp; + }); + return invalidp; + } + + std::set enclosingInstanceVars() const { + std::set vars; + if (m_enclosingClassp) { + m_enclosingClassp->foreachMember([&](AstClass* const, AstVar* const varp) { + if (isEnclosingInstanceVar(varp)) vars.insert(varp); + }); + } + return vars; + } + + bool hasEnclosingEventRef(AstCovergroup* cgp) const { + if (!m_embeddedVarp || !cgp->eventp()) return false; + const std::set enclosingVars = enclosingInstanceVars(); + bool found = false; + cgp->eventp()->foreach([&](AstVarRef* refp) { + if (enclosingVars.count(refp->varp())) found = true; + }); + return found; + } + + static bool parseEmbeddedEventExpr(AstNodeExpr* exprp, AstVar*& baseVarp, + AstVar*& memberVarp) { + if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) { + baseVarp = refp->varp(); + memberVarp = nullptr; + return true; + } + AstMemberSel* const selp = VN_CAST(exprp, MemberSel); + if (!selp) return false; + AstVarRef* const baseRefp = VN_CAST(selp->fromp(), VarRef); + if (!baseRefp) return false; + baseVarp = baseRefp->varp(); + memberVarp = selp->varp(); + return true; + } + + bool isEventLvalue(AstNodeExpr* exprp, const EmbeddedEventTrigger& trigger) const { + if (AstSel* const selp = VN_CAST(exprp, Sel)) exprp = selp->fromp(); + AstVar* baseVarp = nullptr; + AstVar* memberVarp = nullptr; + if (!parseEmbeddedEventExpr(exprp, baseVarp, memberVarp)) return false; + return baseVarp == trigger.baseVarp && memberVarp == trigger.memberVarp; + } + + AstNodeExpr* newEventRead(FileLine* fl, const EmbeddedEventTrigger& trigger) const { + AstNodeExpr* const basep = new AstVarRef{fl, trigger.baseVarp, VAccess::READ}; + if (!trigger.memberVarp) return basep; + AstMemberSel* const selp = new AstMemberSel{fl, basep, trigger.memberVarp}; + selp->access(VAccess::READ); + return selp; + } + + string eventPrevName(const EmbeddedEventTrigger& trigger, size_t triggerIndex) const { + string name = "__Vcg_prev_" + m_embeddedVarp->name() + "_" + std::to_string(triggerIndex) + + "_" + trigger.baseVarp->name(); + if (trigger.memberVarp) name += "_" + trigger.memberVarp->name(); + return name; + } + + AstNodeExpr* newEmbeddedVarNonNull(FileLine* fl) const { + return new AstNeq{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ}, + new AstConst{fl, AstConst::Null{}}}; + } + + AstNodeStmt* newSampleStmt(FileLine* fl) const { + AstMethodCall* const callp = new AstMethodCall{ + fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ}, "sample", nullptr}; + callp->taskp(m_sampleFuncp); + callp->dtypeSetVoid(); + return callp->makeStmt(); + } + + void installEmbeddedEventFork(AstSenTree* eventp, + const std::vector& constructps) { + // IEEE 1800-2023 19.3 samples coverpoints whenever their clocking event occurs. A + // per-instance event cannot use V3Active's static sensitivity path, so spawn + // 'fork forever begin @(event); cg.sample(); end join_none' after each construction. + 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->immediateStart(true); + forkp->addForksp(new AstBegin{fl, "", loopp, true}); + constructp->addNextHere(forkp); + } + VL_DO_DANGLING(pushDeletep(eventp), eventp); + } + + AstNodeExpr* newEventReadyCondition(FileLine* fl, const EmbeddedEventTrigger& trigger) const { + AstNodeExpr* const curp = newEventRead(fl, trigger); + AstNodeExpr* const prevp = new AstVarRef{fl, trigger.prevVarp, VAccess::READ}; + AstNodeExpr* edgep = nullptr; + // IEEE 1800-2023 9.4.2 detects edge-qualified events only on the expression's LSB, + // while an implicit change event observes the complete expression. + if (trigger.edgeType == VEdgeType::ET_POSEDGE) { + edgep = new AstSel{fl, new AstAnd{fl, curp, new AstNot{fl, prevp}}, 0, 1}; + } else if (trigger.edgeType == VEdgeType::ET_NEGEDGE) { + edgep = new AstSel{fl, new AstAnd{fl, new AstNot{fl, curp}, prevp}, 0, 1}; + } else if (trigger.edgeType == VEdgeType::ET_BOTHEDGE) { + edgep = new AstSel{fl, new AstXor{fl, curp, prevp}, 0, 1}; + } else { + edgep = new AstNeq{fl, curp, prevp}; + } + return new AstLogAnd{fl, newEmbeddedVarNonNull(fl), edgep}; + } + + std::vector collectEmbeddedEventTriggers(AstCovergroup* cgp) { + std::vector triggers; + const std::set enclosingVars = enclosingInstanceVars(); + for (AstNode* senp = cgp->eventp()->sensesp(); senp; senp = senp->nextp()) { + AstSenItem* const itemp = VN_AS(senp, SenItem); + AstVar* baseVarp = nullptr; + AstVar* memberVarp = nullptr; + if (!parseEmbeddedEventExpr(itemp->sensp(), baseVarp, memberVarp) + || !enclosingVars.count(baseVarp)) { + return {}; + } + triggers.emplace_back(itemp->fileline(), baseVarp, memberVarp, itemp->edgeType()); + } + return triggers; + } + + void installEmbeddedEventTriggers(std::vector& triggers, + const std::vector& constructps) { + // Without --timing, approximate a per-instance event by sampling after assignments + // within the enclosing class. External writes and exact scheduling cannot be observed. + if (constructps.empty()) return; + for (size_t triggerIndex = 0; triggerIndex < triggers.size(); ++triggerIndex) { + EmbeddedEventTrigger& trigger = triggers[triggerIndex]; + 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. Use --timing for " + "full support."); + continue; + } + AstNodeDType* const dtypep + = trigger.memberVarp ? trigger.memberVarp->dtypep() : trigger.baseVarp->dtypep(); + AstVar* const prevVarp = new AstVar{trigger.eventFl, VVarType::MEMBER, + eventPrevName(trigger, triggerIndex), dtypep}; + prevVarp->isStatic(false); + 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(new AstAssign{fl, + new AstVarRef{fl, trigger.prevVarp, VAccess::WRITE}, + newEventRead(fl, trigger)}); + asgnp->addNextHere(ifp); + } + } + } + void deleteCoverageItems() { for (AstCoverpoint* const cpp : m_coverpoints) { VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp); @@ -1771,7 +2001,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { return visitor.offenderp(); } - AstVarRef* installEnclosingBackPointer() { + AstVarRef* installEnclosingBackPointer(const std::vector& constructps) { // Simple-case support for embedded covergroups (IEEE 1800-2023 19.4) whose // coverpoints reference members of the enclosing class ("Class members can be used // in coverpoint expressions"). The covergroup is lowered into a sibling class with @@ -1789,11 +2019,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { for (AstNode* itemp = m_covergroupp->membersp(); itemp; itemp = itemp->nextp()) { if (const AstVar* const varp = VN_CAST(itemp, Var)) ownVars.insert(varp); } - std::set enclosingVars; - m_enclosingClassp->foreachMember([&](AstClass* const, AstVar* const varp) { - if (isEnclosingInstanceVar(varp)) enclosingVars.insert(varp); - }); - + const std::set enclosingVars = enclosingInstanceVars(); std::vector refsToRewrite; std::vector thisRefsToRewrite; const auto scan = [&](AstNode* rootp) { @@ -1822,32 +2048,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { for (AstCoverCross* const crossp : m_coverCrosses) scan(crossp); if (invalidp || !offenderp) return invalidp; - AstVar* embeddedVarp = nullptr; - for (AstNode* itemp = m_enclosingClassp->membersp(); itemp; itemp = itemp->nextp()) { - AstVar* const varp = VN_CAST(itemp, Var); - if (!varp) continue; - const AstClassRefDType* const refp - = VN_CAST(varp->dtypep()->skipRefp(), ClassRefDType); - if (refp && refp->classp() == m_covergroupp) { - embeddedVarp = varp; - break; - } - } - UASSERT_OBJ(embeddedVarp, m_covergroupp, "Embedded covergroup variable not found"); - - std::vector constructps; - AstFunc* const enclosingNewp - = VN_CAST(m_memberMap.findMember(m_enclosingClassp, "new"), Func); - if (enclosingNewp) { - enclosingNewp->foreach([&](AstNodeAssign* asgnp) { - const AstNew* const newp = VN_CAST(asgnp->rhsp(), New); - const AstVarRef* const lhsRefp = VN_CAST(asgnp->lhsp(), VarRef); - if (newp && lhsRefp && lhsRefp->varp() == embeddedVarp) { - const AstClassRefDType* const refp = VN_CAST(newp->dtypep(), ClassRefDType); - if (refp && refp->classp() == m_covergroupp) constructps.push_back(asgnp); - } - }); - } + UASSERT_OBJ(m_embeddedVarp, m_covergroupp, "Embedded covergroup variable not found"); // 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}; @@ -1880,14 +2081,18 @@ class FunctionalCoverageVisitor final : public VNVisitor { UINFO(9, "Visiting class: " << nodep->name() << " isCovergroup=" << nodep->isCovergroup()); if (nodep->isCovergroup()) { VL_RESTORER(m_covergroupp); + VL_RESTORER(m_embeddedVarp); VL_RESTORER(m_sampleFuncp); VL_RESTORER(m_constructorp); VL_RESTORER_CLEAR(m_coverpoints); VL_RESTORER_CLEAR(m_coverpointMap); VL_RESTORER_CLEAR(m_coverCrosses); m_covergroupp = nodep; + m_embeddedVarp = findEmbeddedCovergroupVar(); m_sampleFuncp = nullptr; m_constructorp = nullptr; + std::vector embeddedEventTriggers; + AstSenTree* embeddedEventForkp = nullptr; // Extract and store the clocking event from AstCovergroup node // The parser creates this node to preserve the event information @@ -1900,33 +2105,42 @@ 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"); - // Check if the clocking event references a member variable (unsupported) - // Clocking events should be on signals/nets, not class members - bool eventUnsupported = false; - for (AstNode* senp = cgp->eventp()->sensesp(); senp; senp = senp->nextp()) { - AstSenItem* const senItemp = VN_AS(senp, SenItem); - if (AstVarRef* const varrefp // LCOV_EXCL_BR_LINE - = VN_CAST(senItemp->sensp(), VarRef)) { - if (varrefp->varp()->isClassMember()) { - cgp->v3warn(COVERIGN, "Unsupported: 'covergroup' clocking event " - "on member variable"); - eventUnsupported = true; + if (hasEnclosingEventRef(cgp)) { + UASSERT_OBJ(m_embeddedVarp, cgp, + "Embedded covergroup event has no instance variable"); + // IEEE 1800-2023 19.4 permits assignment to an embedded covergroup + // variable only in the enclosing class's new method. + if (AstNodeAssign* const invalidp + = findInvalidEmbeddedCovergroupAssignment()) { + invalidp->v3error( + "Embedded covergroup variable " + << m_embeddedVarp->prettyNameQ() + << " may only be assigned in the enclosing class's 'new' method " + "(IEEE 1800-2023 19.4)."); + hasUnsupportedEvent = true; + VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp); + itemp = nextp; + continue; + } + 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; - break; } } - } - - if (!eventUnsupported) { - // 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()); + VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp); itemp = nextp; continue; } - // Remove the AstCovergroup node - either unsupported event or no event - VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp); + // V3Active handles events that do not depend on an enclosing instance. + UINFO(4, "Keeping covergroup event node for V3Active: " << nodep->name()); + itemp = nextp; + continue; } itemp = nextp; } @@ -1956,21 +2170,31 @@ class FunctionalCoverageVisitor final : public VNVisitor { "class handle member; ignoring covergroup " << nodep->prettyNameQ()); deleteCoverageItems(); + if (embeddedEventForkp) { + VL_DO_DANGLING(pushDeletep(embeddedEventForkp), embeddedEventForkp); + } return; } + const std::vector constructps = findCovergroupConstructions(); + // Embedded covergroups (IEEE 1800-2023 19.4): coverpoints, iff expressions, and // crosses may reference members of the enclosing class. The covergroup is lowered // into a sibling class with no implicit handle to the enclosing instance. Install // an explicit back-pointer and route the references through it. - if (AstVarRef* const invalidp = installEnclosingBackPointer()) { + if (AstVarRef* const invalidp = installEnclosingBackPointer(constructps)) { invalidp->v3error("Non-static member " << invalidp->varp()->prettyNameQ() << " of an outer class requires an explicit " "object handle (IEEE 1800-2023 8.23)."); deleteCoverageItems(); + if (embeddedEventForkp) { + VL_DO_DANGLING(pushDeletep(embeddedEventForkp), embeddedEventForkp); + } return; } + installEmbeddedEventTriggers(embeddedEventTriggers, constructps); + if (embeddedEventForkp) installEmbeddedEventFork(embeddedEventForkp, constructps); processCovergroup(); // Remove lowered coverpoints/crosses from the class - they have been // fully translated into C++ code and must not reach downstream passes diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp index 59edc9e78..a418be1d2 100644 --- a/src/V3Fork.cpp +++ b/src/V3Fork.cpp @@ -658,8 +658,10 @@ class ForkVisitor final : public VNVisitor { // join_any block the parent process, deferring branch start with a synthetic #0 delay is // normally only needed for join_none. A fork that can be disabled by name needs the same // deferral for every join type so all branches register their processes before any branch - // body can disable the block. - if (nodep->joinType().joinNone() || forkIsDisableable(nodep)) { + // body can disable the block. Compiler-generated immediate-start forks already have the + // required ordering and must arm their event controls before the parent continues. + if ((nodep->joinType().joinNone() && !nodep->immediateStart()) + || forkIsDisableable(nodep)) { UINFO(9, "Adding fork branch start sentinels " << nodep); FileLine* fl = nodep->fileline(); // We use a sentinel value of UINT64_MAX to mark this delay so that it goes to the diff --git a/src/V3Hasher.cpp b/src/V3Hasher.cpp index 4df1dbf63..4259d1dec 100644 --- a/src/V3Hasher.cpp +++ b/src/V3Hasher.cpp @@ -528,6 +528,7 @@ class HasherVisitor final : public VNVisitorConst { m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [this, nodep]() { // m_hash += nodep->name(); m_hash += nodep->joinType(); + m_hash += nodep->immediateStart(); }); } void visit(AstPin* nodep) override { 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.out b/test_regress/t/t_covergroup_embedded.out index 6daad856d..1ede10263 100644 --- a/test_regress/t/t_covergroup_embedded.out +++ b/test_regress/t/t_covergroup_embedded.out @@ -16,6 +16,8 @@ GlobalCg.cp_global.auto_8: 0 GlobalCg.cp_global.auto_9: 0 __vlAnonCG_base_cg.cp_base.hi: 8 __vlAnonCG_base_cg.cp_base.lo: 8 +__vlAnonCG_both_cg.cp_both.hi: 0 +__vlAnonCG_both_cg.cp_both.lo: 4 __vlAnonCG_branch_cg.cp_branch.hi: 8 __vlAnonCG_branch_cg.cp_branch.lo: 8 __vlAnonCG_cg.cp_base.auto_0: 0 @@ -50,6 +52,10 @@ __vlAnonCG_cg.cp_derived.auto_6: 0 __vlAnonCG_cg.cp_derived.auto_7: 0 __vlAnonCG_cg.cp_derived.auto_8: 0 __vlAnonCG_cg.cp_derived.auto_9: 0 +__vlAnonCG_change_cg.cp_change.hi: 2 +__vlAnonCG_change_cg.cp_change.lo: 4 +__vlAnonCG_clock_cg.cp_clocked.hi: 8 +__vlAnonCG_clock_cg.cp_clocked.lo: 8 __vlAnonCG_copy_cg.cp_copy.hi: 0 __vlAnonCG_copy_cg.cp_copy.lo: 1 __vlAnonCG_derived_cg.cp_cross.hi_x_hi [cross]: 0 @@ -62,6 +68,8 @@ __vlAnonCG_derived_cg.cp_inherited.hi: 8 __vlAnonCG_derived_cg.cp_inherited.lo: 8 __vlAnonCG_derived_cg.cp_this_inherited.hi: 8 __vlAnonCG_derived_cg.cp_this_inherited.lo: 8 +__vlAnonCG_edge_cg.cp_edge.hi: 0 +__vlAnonCG_edge_cg.cp_edge.lo: 4 __vlAnonCG_first_cg.cp_first.hi: 8 __vlAnonCG_first_cg.cp_first.lo: 8 __vlAnonCG_leaf_cg.cp_cross.hi_x_hi [cross]: 0 @@ -92,6 +100,8 @@ __vlAnonCG_mon_cg.cp_op_b.auto_0: 4 __vlAnonCG_mon_cg.cp_op_b.auto_1: 4 __vlAnonCG_mon_cg.cp_op_b.auto_2: 4 __vlAnonCG_mon_cg.cp_op_b.auto_3: 4 +__vlAnonCG_neg_cg.cp_neg.hi: 0 +__vlAnonCG_neg_cg.cp_neg.lo: 2 __vlAnonCG_nested_cg.cp_container.hi: 8 __vlAnonCG_nested_cg.cp_container.lo: 8 __vlAnonCG_nested_cg.cp_local.hi: 8 @@ -100,6 +110,8 @@ __vlAnonCG_nested_cg.cp_static.hi: 8 __vlAnonCG_nested_cg.cp_static.lo: 8 __vlAnonCG_parameterized_cg.cp_parameterized.hi: 8 __vlAnonCG_parameterized_cg.cp_parameterized.lo: 8 +__vlAnonCG_pos_cg.cp_pos.hi: 0 +__vlAnonCG_pos_cg.cp_pos.lo: 2 __vlAnonCG_second_cg.cp_second.hi: 8 __vlAnonCG_second_cg.cp_second.lo: 8 __vlAnonCG_static_cg.cp_instance.hi: 8 diff --git a/test_regress/t/t_covergroup_embedded.v b/test_regress/t/t_covergroup_embedded.v index 7b5f863a9..a78cab6ff 100644 --- a/test_regress/t/t_covergroup_embedded.v +++ b/test_regress/t/t_covergroup_embedded.v @@ -252,6 +252,75 @@ class ThisHandleMonitor; endclass `endif +class ClockEvent; + bit clk; +endclass + +class ClockMonitor; + ClockEvent ev; + bit [3:0] sampled; + + covergroup clock_cg @(posedge ev.clk); + cp_clocked: coverpoint sampled {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + function new(); + ev = new; + ev.clk = 0; + clock_cg = new; + endfunction + + function void observe(bit [3:0] v); + sampled = v; + ev.clk = 0; + ev.clk = 1; + endfunction +endclass + +class VectorClockMonitor; + bit [2:0] clk_vec; + bit [3:0] sampled; + + covergroup pos_cg @(posedge clk_vec); + cp_pos: coverpoint sampled {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + covergroup neg_cg @(negedge clk_vec); + cp_neg: coverpoint sampled {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + covergroup both_cg @(posedge clk_vec or negedge clk_vec); + cp_both: coverpoint sampled {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + covergroup edge_cg @(edge clk_vec); + cp_edge: coverpoint sampled {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + covergroup change_cg @(clk_vec); + cp_change: coverpoint sampled {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + function new(); + clk_vec = 3'b000; + pos_cg = new; + neg_cg = new; + both_cg = new; + edge_cg = new; + change_cg = new; + endfunction + + function void observe(bit [2:0] next_clk, bit [3:0] value); + sampled = value; + clk_vec = next_clk; + endfunction + + function void observe_bit(bit next_clk, bit [3:0] value); + sampled = value; + clk_vec[0] = next_clk; + endfunction +endclass + class CopyMonitor; bit [3:0] value; @@ -412,6 +481,8 @@ module t; `ifdef VERILATOR ThisHandleMonitor this_handle_mon; `endif + ClockMonitor clock_mon; + VectorClockMonitor vector_clock_mon; CopyMonitor copy_src; CopyMonitor copy_dst; GlobalCgHolder global_src; @@ -439,6 +510,8 @@ module t; `ifdef VERILATOR this_handle_mon = new; `endif + clock_mon = new; + vector_clock_mon = new; copy_src = new; global_src = new; clone_src = new; @@ -460,26 +533,44 @@ module t; `ifdef VERILATOR this_handle_mon.observe(i[3:0]); `endif + clock_mon.observe(i[3:0]); static_mon.observe(i[3:0]); static_only_mon.observe(i[3:0]); multiple_mon.observe(i[3:0], 15 - i[3:0]); nested_mon.observe(i[3:0]); end + vector_clock_mon.observe(3'b010, 4'hf); + vector_clock_mon.observe(3'b011, 4'h1); + vector_clock_mon.observe(3'b010, 4'h2); + vector_clock_mon.observe(3'b000, 4'he); + vector_clock_mon.observe_bit(1'b1, 4'h3); + vector_clock_mon.observe_bit(1'b0, 4'h4); + for (i = 0; i < 8; ++i) begin branch_a.observe(i[2:0]); branch_b.observe(i[2:0]); end copy_src.observe(4'h1); +`ifdef VERILATOR + // IEEE 1800-2023 8.12 requires embedded covergroups to be null after a shallow copy. + // No new coverage object is created, so the copied object's properties are not covered. + // Questa instead aliases the source coverage object; Xcelium retains a non-null handle + // whose source and copied instances both report zero coverage. copy_dst = new copy_src; `checkd(copy_dst.copy_cg == null, 1); - global_dst = new global_src; - `checkd(global_dst.cg == global_src.cg, 1); clone_dst = new clone_src; clone_base_view = clone_dst; `checkd(clone_dst.cg == null, 1); `checkd(clone_base_view.cg == null, 1); +`endif + + global_dst = new global_src; +`ifndef NC + // Comparing a covergroup variable with a non-null value is unsupported in Xcelium. + `checkd(global_dst.cg == global_src.cg, 1); +`endif $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_covergroup_embedded_event_bad.out b/test_regress/t/t_covergroup_embedded_event_bad.out new file mode 100644 index 000000000..96da2a262 --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_event_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_covergroup_embedded_event_bad.v:31:8: Embedded covergroup variable 'cg' may only be assigned in the enclosing class's 'new' method (IEEE 1800-2023 19.4). + : ... note: In instance 't' + 31 | cg = new; + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_embedded_event_bad.py b/test_regress/t/t_covergroup_embedded_event_bad.py new file mode 100755 index 000000000..fd007ac72 --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_event_bad.py @@ -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('vlt') + +test.compile( + verilator_flags2=['--no-timing'], + fails=True, + expect_filename=test.golden_filename, +) + +test.passes() diff --git a/test_regress/t/t_covergroup_embedded_event_bad.v b/test_regress/t/t_covergroup_embedded_event_bad.v new file mode 100644 index 000000000..0f6e5f516 --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_event_bad.v @@ -0,0 +1,42 @@ +// 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 + +class Level; + bit event_signal; +endclass + +class Middle; + Level level; +endclass + +class Monitor; + bit clk; + bit [3:0] value; + Middle middle; + + covergroup cg @(posedge clk or posedge middle.level.event_signal); + cp: coverpoint value; + endgroup + + function new(); + middle = new; + middle.level = new; + endfunction + + function void build(); + cg = new; + endfunction +endclass + +module t; + Monitor mon; + + initial begin + mon = new; + mon.build(); + end +endmodule diff --git a/test_regress/t/t_covergroup_embedded_nested_bad.out b/test_regress/t/t_covergroup_embedded_nested_bad.out index 22dc53f4d..554ae7a65 100644 --- a/test_regress/t/t_covergroup_embedded_nested_bad.out +++ b/test_regress/t/t_covergroup_embedded_nested_bad.out @@ -1,18 +1,25 @@ -%Error-ENCAPSULATED: t/t_covergroup_embedded_nested_bad.v:42:20: 'local_value' is hidden as 'local' within this context (IEEE 1800-2023 8.18) +%Error-ENCAPSULATED: t/t_covergroup_embedded_nested_bad.v:44:20: 'local_value' is hidden as 'local' within this context (IEEE 1800-2023 8.18) : ... note: In instance 't' - 42 | cp: coverpoint local_value; + 44 | cp: coverpoint local_value; | ^~~~~~~~~~~ - t/t_covergroup_embedded_nested_bad.v:42:20: ... Location of definition - 37 | local bit [3:0] local_value; + t/t_covergroup_embedded_nested_bad.v:44:20: ... Location of definition + 39 | local bit [3:0] local_value; | ^~~~~~~~~~~ ... For error description see https://verilator.org/warn/ENCAPSULATED?v=latest -%Error: t/t_covergroup_embedded_nested_bad.v:13:22: Non-static member 'outer_value' of an outer class requires an explicit object handle (IEEE 1800-2023 8.23). +%Error-ENCAPSULATED: t/t_covergroup_embedded_nested_bad.v:57:27: 'local_event' is hidden as 'local' within this context (IEEE 1800-2023 8.18) + : ... note: In instance 't' + 57 | covergroup cg @(posedge local_event); + | ^~~~~~~~~~~ + t/t_covergroup_embedded_nested_bad.v:57:27: ... Location of definition + 53 | local bit local_event; + | ^~~~~~~~~~~ +%Error: t/t_covergroup_embedded_nested_bad.v:15:22: Non-static member 'outer_value' of an outer class requires an explicit object handle (IEEE 1800-2023 8.23). : ... note: In instance 't' - 13 | cp: coverpoint outer_value; + 15 | cp: coverpoint outer_value; | ^~~~~~~~~~~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_covergroup_embedded_nested_bad.v:25:39: Non-static member 'outer_value' of an outer class requires an explicit object handle (IEEE 1800-2023 8.23). +%Error: t/t_covergroup_embedded_nested_bad.v:27:39: Non-static member 'outer_value' of an outer class requires an explicit object handle (IEEE 1800-2023 8.23). : ... note: In instance 't' - 25 | cp: coverpoint inner_value iff (outer_value != 0); + 27 | cp: coverpoint inner_value iff (outer_value != 0); | ^~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_embedded_nested_bad.py b/test_regress/t/t_covergroup_embedded_nested_bad.py index 38cf36b43..5228692b4 100755 --- a/test_regress/t/t_covergroup_embedded_nested_bad.py +++ b/test_regress/t/t_covergroup_embedded_nested_bad.py @@ -11,6 +11,6 @@ import vltest_bootstrap test.scenarios('linter') -test.lint(fails=True, expect_filename=test.golden_filename) +test.lint(verilator_flags2=['--timing'], fails=True, expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_covergroup_embedded_nested_bad.v b/test_regress/t/t_covergroup_embedded_nested_bad.v index 3ddaccf82..44d7a1ae7 100644 --- a/test_regress/t/t_covergroup_embedded_nested_bad.v +++ b/test_regress/t/t_covergroup_embedded_nested_bad.v @@ -9,7 +9,9 @@ class Outer; bit [3:0] outer_value; class CoverpointInner; - covergroup cg; + bit clk; + + covergroup cg @(posedge clk); cp: coverpoint outer_value; endgroup @@ -47,14 +49,30 @@ class LocalDerived extends LocalBase; endfunction endclass +class LocalEventBase; + local bit local_event; +endclass + +class LocalEventDerived extends LocalEventBase; + covergroup cg @(posedge local_event); + cp: coverpoint 1'b1; + endgroup + + function new(); + cg = new; + endfunction +endclass + module t; Outer::CoverpointInner coverpoint_inner; Outer::IffInner iff_inner; LocalDerived local_derived; + LocalEventDerived local_event_derived; initial begin coverpoint_inner = new; iff_inner = new; local_derived = new; + local_event_derived = new; end endmodule 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..8ddb9d240 --- /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: 1 diff --git a/test_regress/t/t_covergroup_embedded_timing.py b/test_regress/t/t_covergroup_embedded_timing.py new file mode 100755 index 000000000..c1ef59356 --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_timing.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') + +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..87e2ae26d --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_timing.v @@ -0,0 +1,86 @@ +// 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 + +class ExtClkMonitor; + bit clk; + 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; + Mid mid; + 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; + extm.value = 4'h3; + // This edge must be sampled before the caller first blocks at #1 (IEEE 1800-2023 19.3); + // normal fork-join_none startup deferral (IEEE 1800-2023 9.3.2) would miss it. + extm.clk = 1'b1; + #1; + chain = new; + + 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 + + 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_embedded_unsup.out b/test_regress/t/t_covergroup_embedded_unsup.out index d40285096..3d5ecc1fb 100644 --- a/test_regress/t/t_covergroup_embedded_unsup.out +++ b/test_regress/t/t_covergroup_embedded_unsup.out @@ -1,11 +1,11 @@ -%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:44:23: Unsupported: 'covergroup' coverpoint dereferencing a class handle member; ignoring covergroup '__vlAnonCG_cov_param' +%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:45:23: Unsupported: 'covergroup' coverpoint dereferencing a class handle member; ignoring covergroup '__vlAnonCG_cov_param' : ... note: In instance 't' - 44 | cp: coverpoint st.test; + 45 | cp: coverpoint st.test; | ^~~~ ... 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_embedded_unsup.v:60:37: Unsupported: 'covergroup' coverpoint dereferencing a class handle member; ignoring covergroup '__vlAnonCG_cov_mixed' +%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:61:37: Unsupported: 'covergroup' coverpoint dereferencing a class handle member; ignoring covergroup '__vlAnonCG_cov_mixed' : ... note: In instance 't' - 60 | cp: coverpoint local_value + st.test; + 61 | cp: coverpoint local_value + st.test; | ^~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_embedded_unsup.py b/test_regress/t/t_covergroup_embedded_unsup.py index 77a0ac64b..18c00bc38 100755 --- a/test_regress/t/t_covergroup_embedded_unsup.py +++ b/test_regress/t/t_covergroup_embedded_unsup.py @@ -11,6 +11,6 @@ import vltest_bootstrap test.scenarios('vlt') -test.lint(expect_filename=test.golden_filename, fails=True) +test.lint(verilator_flags2=['--timing'], expect_filename=test.golden_filename, fails=True) test.passes() diff --git a/test_regress/t/t_covergroup_embedded_unsup.v b/test_regress/t/t_covergroup_embedded_unsup.v index f02302ae6..5a1fb6a9d 100644 --- a/test_regress/t/t_covergroup_embedded_unsup.v +++ b/test_regress/t/t_covergroup_embedded_unsup.v @@ -36,11 +36,12 @@ endclass class parameterized_monitor; coverage_state cs; + bit clk; // Parameterized covergroup: the coverpoints dereference the class-handle argument 'st'. // Two handle-dereferencing coverpoints ensure the safety net reports only the first // offender (a second AstMemberSel is seen with the offender already latched). - covergroup cov_param(coverage_state st); + covergroup cov_param(coverage_state st) @(posedge clk); cp: coverpoint st.test; cp2: coverpoint st.test2; endgroup diff --git a/test_regress/t/t_covergroup_member_event_unsup.out b/test_regress/t/t_covergroup_member_event_unsup.out index e9abfdcd4..44afaaabb 100644 --- a/test_regress/t/t_covergroup_member_event_unsup.out +++ b/test_regress/t/t_covergroup_member_event_unsup.out @@ -1,7 +1,10 @@ -%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:15:29: Unsupported: 'covergroup' clocking event signal has no assignment within the enclosing class; no coverage sampled. Use --timing for full support. + 15 | covergroup cov_extclk @(posedge clk); + | ^~~~~~~ ... 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:37:5: Unsupported: 'covergroup' clocking event on complex member expression; use --timing for full support. + : ... note: In instance 't' + 37 | 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..99305a1c4 100755 --- a/test_regress/t/t_covergroup_member_event_unsup.py +++ b/test_regress/t/t_covergroup_member_event_unsup.py @@ -11,6 +11,8 @@ import vltest_bootstrap test.scenarios('vlt') -test.lint(expect_filename=test.golden_filename, fails=True) +# Dynamic per-instance event waits require --timing. Without it, simple events use +# best-effort in-class assignment instrumentation and unsupported cases warn. +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..e2ff92710 100644 --- a/test_regress/t/t_covergroup_member_event_unsup.v +++ b/test_regress/t/t_covergroup_member_event_unsup.v @@ -4,17 +4,53 @@ // 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 covergroup events that cannot be approximated without --timing must +// emit COVERIGN rather than silently producing zero coverage. + +module t; + class ExternalClk; + bit clk; + bit [3:0] value; + + covergroup cov_extclk @(posedge clk); + coverpoint value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} endgroup + + function new(); + cov_extclk = new; + endfunction endclass + + class Lvl; + bit ev; + endclass + + class Mid; + Lvl lvl; + endclass + + class ComplexEvt; + bit a; + Mid mid; + bit [3:0] value; + + covergroup cov_cplx @(posedge a or posedge mid.lvl.ev); + coverpoint value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + function new(); + mid = new; + mid.lvl = new; + cov_cplx = new; + endfunction + endclass + + ExternalClk ec; + ComplexEvt cx; + initial begin + 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..607a0dc52 100644 --- a/test_regress/t/t_covergroup_unsup_ign2.out +++ b/test_regress/t/t_covergroup_unsup_ign2.out @@ -300,7 +300,3 @@ : ... 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; - | ^~~~~~~~~~ diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 7a60f15f8..4e96f7c27 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -1228,12 +1228,13 @@ package Vt_debug_emitv_std; endpackage package Vt_debug_emitv___024unit; class Vt_debug_emitv_Cls; + bit cg_clk; int signed member; member = 'sh1; int signed rmember1; int signed rmember2; covergroup Vt_debug_emitv___vlAnonCG_cg_in_class; - function new; + @(posedge cg_clk)function new; cp_m: coverpoint member { bins one = {'sh1}; bins two = {'sh2}; @@ -1283,13 +1284,14 @@ package Vt_debug_emitv___024unit; endfunction endcovergroup Vt_debug_emitv___vlAnonCG_cg_in_class cg_in_classVt_debug_emitv___vlAnonCG_cg_in_class; + function new; + cg_in_class = new(); + endfunction task method; if ((this != this)) begin $stop; end endtask - function new; - endfunction function randomize; endfunction endclass diff --git a/test_regress/t/t_debug_emitv.py b/test_regress/t/t_debug_emitv.py index 29c864976..237078afc 100755 --- a/test_regress/t/t_debug_emitv.py +++ b/test_regress/t/t_debug_emitv.py @@ -20,6 +20,7 @@ test.lint( # unsupported (dropped) but the bin is still created with a non-NONE VTransRepType, which # is what exercises VTransRepType::ascii() and AstCoverTransItem::dump()'s repType arm. "--Wno-COVERIGN", + "--timing", "--dumpi-tree 9 --dumpi-V3EmitV 9 --debug-emitv", # Dev coverage of the V3EmitV code "--dump-graph --dumpi-tree-json 9 --no-json-ids" ]) diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index 3058f9260..8e58f2897 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -20,15 +20,19 @@ package PkgImp; endpackage class Cls; + bit cg_clk; int member = 1; rand int rmember1; rand int rmember2; - covergroup cg_in_class; + covergroup cg_in_class @(posedge cg_clk); cp_m: coverpoint member { bins one = {1}; bins two = {2}; } endgroup + function new; + cg_in_class = new; + endfunction function void method; if (this != this) $stop; endfunction diff --git a/test_regress/t/t_debug_emitv_addrids.py b/test_regress/t/t_debug_emitv_addrids.py index dec2a3a47..53c1c6134 100755 --- a/test_regress/t/t_debug_emitv_addrids.py +++ b/test_regress/t/t_debug_emitv_addrids.py @@ -18,6 +18,6 @@ test.lint( # --Wno-COVERIGN: shares t_debug_emitv.v, whose cg_trans uses a goto-repetition transition # bin ([->N]); the count is unsupported (dropped) but the bin is still created with a # non-NONE VTransRepType. - v_flags=["--lint-only --dumpi-tree 9 --dump-tree-addrids --Wno-COVERIGN"]) + v_flags=["--lint-only --dumpi-tree 9 --dump-tree-addrids --Wno-COVERIGN --timing"]) test.passes()