From 704ec556ea8fd5d3c500b73f8a23ebc9c884ef2b Mon Sep 17 00:00:00 2001 From: Marco Bartoli Date: Wed, 7 Oct 2026 01:12:55 +0200 Subject: [PATCH] Fix coverage merge instances (#8543) --- bin/verilator | 1 + docs/guide/exe_verilator.rst | 22 ++ include/verilated_cov_model.h | 10 + include/verilated_covergroup.cpp | 124 ++++++- include/verilated_covergroup.h | 86 ++++- src/V3AstAttr.h | 3 +- src/V3Covergroup.cpp | 174 +++++++--- src/V3Options.cpp | 1 + src/V3Options.h | 2 + src/V3Width.cpp | 21 +- test_regress/t/t_covergroup_inst_handle.cpp | 2 +- .../t/t_covergroup_merge_instances.out | 95 ++++++ .../t/t_covergroup_merge_instances.py | 15 + test_regress/t/t_covergroup_merge_instances.v | 303 ++++++++++++++++++ .../t/t_covergroup_merge_instances_bad.out | 6 + .../t/t_covergroup_merge_instances_bad.py | 16 + .../t/t_covergroup_merge_instances_bad.v | 18 ++ .../t_covergroup_merge_instances_default.out | 11 + .../t/t_covergroup_merge_instances_default.py | 32 ++ .../t/t_covergroup_merge_instances_default.v | 134 ++++++++ ..._covergroup_merge_instances_no_coverage.py | 20 ++ test_regress/t/t_covergroup_unsup_ign2.out | 4 - test_regress/t/t_vlcov_covergroup_report.out | 18 +- test_regress/t/t_vlcov_covergroup_report.v | 25 ++ 24 files changed, 1064 insertions(+), 79 deletions(-) create mode 100644 test_regress/t/t_covergroup_merge_instances.out create mode 100755 test_regress/t/t_covergroup_merge_instances.py create mode 100644 test_regress/t/t_covergroup_merge_instances.v create mode 100644 test_regress/t/t_covergroup_merge_instances_bad.out create mode 100755 test_regress/t/t_covergroup_merge_instances_bad.py create mode 100644 test_regress/t/t_covergroup_merge_instances_bad.v create mode 100644 test_regress/t/t_covergroup_merge_instances_default.out create mode 100755 test_regress/t/t_covergroup_merge_instances_default.py create mode 100644 test_regress/t/t_covergroup_merge_instances_default.v create mode 100755 test_regress/t/t_covergroup_merge_instances_no_coverage.py diff --git a/bin/verilator b/bin/verilator index bc76fe86b..298c75675 100755 --- a/bin/verilator +++ b/bin/verilator @@ -422,6 +422,7 @@ detailed descriptions of these arguments. --coverage-max-bins Maximum bins of an integral coverpoint bins declaration --coverage-max-real-bins Maximum bins of a real coverpoint bins declaration --coverage-max-width Maximum array depth for coverage + --coverage-merge-instances Default covergroups to merge their instances --coverage-per-instance Enable per-instance coverage counters --coverage-toggle Enable toggle coverage --coverage-underscore Enable coverage of _signals diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 04cba5aaa..ddbacff07 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -349,6 +349,28 @@ Summary: toggle coverage. Defaults to 256, as covering large vectors may greatly slow coverage simulations. +.. option:: --coverage-merge-instances + + Sets the default value of the covergroup type option + ``type_option.merge_instances`` to 1, for the covergroups that do not + set it (IEEE 1800-2023 19.11.3). + + With "--coverage-merge-instances", + the type coverage of a covergroup merges its instances as a union of + their bins. This applies to ``get_coverage()``, to + ``get_inst_coverage()`` unless ``option.get_inst_coverage`` is set, and + to the coverage that :command:`verilator_coverage` reports. This is + like the default behavior of Synopsys VCS. In Questa, it is the + equivalent of ``vsim -cvgmergeinstances``, which sets + ``SVCovergroupMergeInstancesDefault`` to 1. + + With "--no-coverage-merge-instances", the default, the value is 0, as + IEEE specifies, so the type coverage of a covergroup is the average of + the coverage of its instances. + + This is unrelated to :vlopt:`--coverage-per-instance`, which keeps + Verilator-inserted coverage per hierarchy instance. + .. option:: --coverage-per-instance For Verilator-inserted coverage, preserve generated coverage counters and diff --git a/include/verilated_cov_model.h b/include/verilated_cov_model.h index 8d25de5df..029583f7f 100644 --- a/include/verilated_cov_model.h +++ b/include/verilated_cov_model.h @@ -28,6 +28,7 @@ #include #include +class VlCovMergedItems; class VlFileLineDebug; // Per-bin classification. A bin's kind is which set it lives in (structural), @@ -54,6 +55,7 @@ enum class VlCovBinKind : uint8_t { class VlCoverpointIf VL_NOT_FINAL { // MEMBERS int32_t m_weight = 1; // option.weight; never negative + int32_t m_typeWeight = 1; // type_option.weight; never negative public: // CONSTRUCTORS @@ -73,6 +75,14 @@ public: /// Load option.weight, as evaluated by the covergroup constructor. A negative /// weight is reported as an error, and counts as zero. void weight(uint32_t value, VlFileLineDebug fileline); + /// Weight of this item in its covergroup type's coverage when the instances are merged + /// (type_option.weight, IEEE 1800-2023 19.11.3) + int32_t typeWeight() const { return m_typeWeight; } + /// Load type_option.weight, a constant that verilation checked is not negative + void typeWeight(uint32_t value) { m_typeWeight = static_cast(value); } + /// Merge this item's coverable bins into its entry in 'items', the covergroup type's + /// items merged over the instances + virtual void mergeInto(VlCovMergedItems& items) const = 0; }; #endif // Guard diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp index afda0c9bc..01779cb49 100644 --- a/include/verilated_covergroup.cpp +++ b/include/verilated_covergroup.cpp @@ -1093,6 +1093,13 @@ std::string VlCoverpoint::normalBinName(uint32_t crossIdx) const { return declaredBinName(m_crossToBin[crossIdx]); } +void VlCoverpoint::mergeInto(VlCovMergedItems& items) const { + VlCovMergedItem& item = items.findNewItem(m_hier); + item.options(m_atLeast, typeWeight()); + // The bins coverageParts() counts + for (const uint32_t bin : m_crossToBin) item.addBin(declaredBinName(bin), m_counts[bin]); +} + const VlCovNamer& VlCoverpoint::namerFor(uint32_t i) const { // Namers are appended in ascending order covering [0, m_total). const auto it = std::upper_bound( @@ -1434,6 +1441,25 @@ std::string VlCoverCross::autoBinName(uint32_t flat) const { return name; } +void VlCoverCross::mergeInto(VlCovMergedItems& items) const { + VlCovMergedItem& item = items.findNewItem(m_hier); + item.options(1, typeWeight()); // A cross bin is covered once hit + // The bins coverageParts() counts, which share a bin when they share its name. As no bin + // name holds ',' or '<', an automatic bin's, (IEEE 1800-2023 19.11.3), is + // that of no other automatic bin, nor of an explicit bin. + const uint32_t explicitCount + = hasExplicitBins() ? static_cast(m_explicitp->bins.size()) : 0; + for (uint32_t i = 0; i < binCount(); ++i) { + if (i < explicitCount) { + const Bin& bin = m_explicitp->bins[i]; + if (bin.kind == VlCovBinKind::KIND_NORMAL) item.addBin(bin.namep, bin.count); + continue; + } + const uint32_t flat = autoIndex(i - explicitCount); + item.addBin(autoBinName(flat), m_flatCountsp[flat]); + } +} + #if VM_COVERAGE void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* page, uint32_t itemWeight, uint32_t groupWeight) { @@ -1793,10 +1819,83 @@ double VlCovergroupInst::coverage() { return _vl_cov_calculate(sums.first, sums.second, m_weight); } +void VlCovergroupInst::mergeInto(VlCovMergedItems& items) const { + for (const auto& itemp : m_items) itemp->mergeInto(items); +} + +//============================================================================= +// VlCovMergedItem / VlCovMergedItems + +// Call 'f' with the values that sorted maps 'a' and 'b' have for each key of either, or nullptr +// for a map without the key, so as with those of the maps merged, without merging them +template +static void _vl_cov_union_walk(const T_Map& a, const T_Map& b, T_Func f) { + auto ait = a.cbegin(); + auto bit = b.cbegin(); + while (ait != a.cend() || bit != b.cend()) { + if (bit == b.cend() || (ait != a.cend() && ait->first < bit->first)) { + f(&ait->second, nullptr); + ++ait; + } else if (ait == a.cend() || bit->first < ait->first) { + f(nullptr, &bit->second); + ++bit; + } else { + f(&ait->second, &bit->second); + ++ait; + ++bit; + } + } +} + +void VlCovMergedItem::coverageParts(double& covered, double& total) const { + uint64_t numCovered = 0; + for (const auto& it : m_counts) { + if (it.second >= m_atLeast) ++numCovered; + } + covered = static_cast(numCovered); + total = static_cast(m_counts.size()); +} + +void VlCovMergedItem::coverageParts(const VlCovMergedItem& other, double& covered, + double& total) const { + // As merged: a bin is covered once its counts sum to the larger option.at_least + const uint32_t atLeast = std::max(m_atLeast, other.m_atLeast); + uint64_t numCovered = 0; + uint64_t numTotal = 0; + _vl_cov_union_walk(m_counts, other.m_counts, [&](const uint64_t* ap, const uint64_t* bp) { + if ((ap ? *ap : 0) + (bp ? *bp : 0) >= atLeast) ++numCovered; + ++numTotal; + }); + covered = static_cast(numCovered); + total = static_cast(numTotal); +} + +std::pair VlCovMergedItems::coverageSums(const VlCovMergedItems& other) const { + double weighted = 0.0; + double weights = 0.0; + const auto sumItem = [&](const VlCovMergedItem* ap, const VlCovMergedItem* bp) { + double covered = 0.0; + double total = 0.0; + const VlCovMergedItem& itemr = ap ? *ap : *bp; + if (ap && bp) { + ap->coverageParts(*bp, covered, total); + } else { + itemr.coverageParts(covered, total); + } + if (total == 0.0) return; // No bins: excluded from both sums + weighted += itemr.typeWeight() * (covered / total); + weights += itemr.typeWeight(); + }; + _vl_cov_union_walk(m_items, other.m_items, sumItem); + return {100.0 * weighted, weights}; +} + //============================================================================= // VlCovergroupType / VlCovRegistry -VlCovergroupInst* VlCovergroupType::newInstance() { +VlCovergroupInst* VlCovergroupType::newInstance(bool mayMerge) { + // Once set, kept: the bins of the instances that die from now on are kept + m_mayMerge = m_mayMerge || mayMerge; VlCovergroupInst* const instp = new VlCovergroupInst{this, m_nextInstId++}; m_insts.emplace_back(instp); #if !VM_COVERAGE @@ -1807,6 +1906,8 @@ VlCovergroupInst* VlCovergroupType::newInstance() { } void VlCovergroupType::foldResidue(VlCovergroupInst* instp) { + // Merged type coverage needs the dead instance's bins themselves + if (m_mayMerge) instp->mergeInto(m_mergedRetired); const std::pair sums = instp->coverageSums(); // Nothing coverable: excluded from both sums, so it moves neither the mean // nor the denominator. Never-sampled is different: it has bins, none hit, @@ -1863,11 +1964,22 @@ bool VlCovergroupType::anyAttached() const { return false; } -double VlCovergroupType::coverage(IData typeWeight, VlFileLineDebug fileline) { +double VlCovergroupType::coverage(IData typeWeight, bool mergeInstances, + VlFileLineDebug fileline) { if (typeWeight != m_loadedTypeWeight) { // Only a new value, as in loadWeight() m_loadedTypeWeight = typeWeight; m_typeWeight = _vl_cov_load_weight("type_option.weight", typeWeight, fileline); } + if (mergeInstances) { + // The union of the bins of every instance, including those that have died, kept in + // m_mergedRetired, so merged with the live ones' as read, not copied + VlCovMergedItems live; + for (const auto& instp : m_insts) { + if (!instp->retained()) instp->mergeInto(live); // Retained: in m_mergedRetired + } + const std::pair sums = live.coverageSums(m_mergedRetired); + return _vl_cov_calculate(sums.first, sums.second, m_typeWeight); + } // Instances that have died still count: their contribution is the residue double sumCoverage = m_retired.sumCoverage; double sumWeight = m_retired.sumWeight; @@ -1912,14 +2024,14 @@ VlCovergroupType* VlCovRegistry::findOrCreateType(const char* typeName) { return typep; } -VlCovergroupInst* VlCovRegistry::newCovergroupInst(const char* typeName) { - return findOrCreateType(typeName)->newInstance(); +VlCovergroupInst* VlCovRegistry::newCovergroupInst(const char* typeName, bool mayMerge) { + return findOrCreateType(typeName)->newInstance(mayMerge); } -double VlCovRegistry::typeCoverage(const char* typeName, IData typeWeight, +double VlCovRegistry::typeCoverage(const char* typeName, IData typeWeight, bool mergeInstances, VlFileLineDebug fileline) { // Also for a type never instantiated, whose node then remembers type_option.weight - return findOrCreateType(typeName)->coverage(typeWeight, fileline); + return findOrCreateType(typeName)->coverage(typeWeight, mergeInstances, fileline); } // A covergroup object can outlive the registry: models must be destroyed before diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h index afe159f5b..740ce9566 100644 --- a/include/verilated_covergroup.h +++ b/include/verilated_covergroup.h @@ -35,9 +35,11 @@ #include "verilated.h" #include "verilated_cov_model.h" +#include #include #include #include +#include #include #include #include @@ -258,6 +260,7 @@ public: covered = numCovered; total = m_normal; } + void mergeInto(VlCovMergedItems& items) const override; }; //============================================================================= @@ -460,6 +463,7 @@ public: total = hasExplicitBins() ? m_explicitp->normalBins + m_explicitp->autoBins.size() : m_numAutoBins; } + void mergeInto(VlCovMergedItems& items) const override; }; //============================================================================= @@ -632,6 +636,8 @@ public: /// denominator: {the sum of each item's option.weight times its coverage /// (0..100), the sum of those weights}. std::pair coverageSums() const; + /// Merge the items into 'items', the covergroup type's items merged over the instances + void mergeInto(VlCovMergedItems& items) const; /// Instance coverage, as returned by get_inst_coverage(), in 0..100. double coverage(); }; @@ -647,6 +653,52 @@ struct VlCovRetiredAvg final { double sumWeight = 0.0; // Sigma of per-instance weight }; +//============================================================================= +// VlCovMergedItem +/// A coverpoint or cross merged over the instances of its covergroup type: the union of their +/// coverable bins, which share a bin when they share its name, with the counts summed (IEEE +/// 1800-2023 19.11.3, type_option.merge_instances true). + +class VlCovMergedItem final { + // MEMBERS + uint32_t m_atLeast = 0; // The largest option.at_least of the instances + int32_t m_typeWeight = 1; // Its type_option.weight + std::map m_counts; // Coverable bin name -> count, summed + +public: + // METHODS + /// Merge the options of an instance: a merged bin is covered once hit the largest + /// option.at_least of the instances (IEEE 1800-2023 19.11.1); type_option.weight is the + /// same for every instance + void options(uint32_t atLeast, int32_t typeWeight) { + m_atLeast = std::max(m_atLeast, atLeast); + m_typeWeight = typeWeight; + } + void addBin(const std::string& name, uint64_t count) { m_counts[name] += count; } + int32_t typeWeight() const { return m_typeWeight; } + /// Merged bins that reached option.at_least, and all merged bins + void coverageParts(double& covered, double& total) const; + /// As coverageParts(), of this and 'other' merged + void coverageParts(const VlCovMergedItem& other, double& covered, double& total) const; +}; + +//============================================================================= +// VlCovMergedItems +/// The items of a covergroup type merged over its instances, by item name. + +class VlCovMergedItems final { + // MEMBERS + std::map m_items; // By "covergroup.item" name + +public: + // METHODS + VlCovMergedItem& findNewItem(const std::string& name) { return m_items[name]; } + /// Sums as VlCovergroupInst::coverageSums(), of these items and those of 'other' merged, + /// with each item weighted by its type_option.weight (IEEE 1800-2023 19.7.1). Without + /// merging them into a copy, as 'other', those of the instances that have died, may be large. + std::pair coverageSums(const VlCovMergedItems& other) const; +}; + //============================================================================= // VlCovergroupType /// One covergroup type: owns its live instances, in creation order, plus the @@ -660,12 +712,15 @@ class VlCovergroupType final { uint32_t m_createdInsts = 0; // Instances ever created; never decremented uint32_t m_nextInstId = 0; // Monotonic; slots are reused, ids never are VlCovRetiredAvg m_retired; // Contribution of every instance that has died + // The bins of every instance that has died, kept only if the type may merge its instances + VlCovMergedItems m_mergedRetired; + bool m_mayMerge = false; // type_option.merge_instances is, or may become, true IData m_loadedTypeWeight = 1; // Last type_option.weight loaded by coverage() int32_t m_typeWeight = 1; // type_option.weight in use, never negative // PRIVATE METHODS - // Harvest instp's contribution into m_retired. Must run before instp is - // unlinked: it reads the instance's items. + // Harvest instp's contribution into m_retired, and m_mergedRetired. Must run before + // instp is unlinked: it reads the instance's items. void foldResidue(VlCovergroupInst* instp); public: @@ -674,7 +729,8 @@ public: VL_UNCOPYABLE(VlCovergroupType); // METHODS - VlCovergroupInst* newInstance(); + // mayMerge: type_option.merge_instances is, or may become, true + VlCovergroupInst* newInstance(bool mayMerge); // Called when the last handle to instp drops. Folds the residue, then // unlinks and frees the node -- except under VM_COVERAGE, where the coverage // database still holds raw pointers into it and it is only marked retained. @@ -682,12 +738,13 @@ public: // True if any node here still has an SV handle bound to it, and so can be // retired again after the registry is destroyed. See ~VlCovRegistry. bool anyAttached() const; - /// Type coverage, as returned by get_coverage(), in 0..100: the average of - /// every instance's coverage, weighted by its option.weight (IEEE 1800-2023 - /// 19.11.3, type_option.merge_instances false). typeWeight is - /// type_option.weight, which decides the result when no instance contributes; - /// like option.weight, it is checked as it is loaded. - double coverage(IData typeWeight, VlFileLineDebug fileline); + /// Type coverage, as returned by get_coverage(), in 0..100 (IEEE 1800-2023 19.11.3). + /// Unless mergeInstances (type_option.merge_instances), the average of every + /// instance's coverage, weighted by its option.weight; else the coverage of the union + /// of the instances' bins, with each item weighted by its type_option.weight. + /// typeWeight is type_option.weight, which decides the result when no instance or item + /// contributes; like option.weight, it is checked as it is loaded. + double coverage(IData typeWeight, bool mergeInstances, VlFileLineDebug fileline); // ---- introspection ---- // Test and debug only; generated code never calls these, and SV reaches them @@ -733,11 +790,14 @@ public: // Find-or-create the type node, then add an instance to it. typeName is the // covergroup type's name, as $typename names it (e.g. "pkg::cls::cg"), already // --protect-ids obfuscated, and is the same string that keys the coverage - // database's hier/page. - VlCovergroupInst* newCovergroupInst(const char* typeName); + // database's hier/page. mayMerge: type_option.merge_instances is, or may + // become, true. + VlCovergroupInst* newCovergroupInst(const char* typeName, bool mayMerge); /// Type coverage of a covergroup type (get_coverage()); see - /// VlCovergroupType::coverage(). typeWeight is its type_option.weight. - double typeCoverage(const char* typeName, IData typeWeight, VlFileLineDebug fileline); + /// VlCovergroupType::coverage(). typeWeight is its type_option.weight, and + /// mergeInstances its type_option.merge_instances. + double typeCoverage(const char* typeName, IData typeWeight, bool mergeInstances, + VlFileLineDebug fileline); // ---- introspection (see VlCovergroupType) ---- // typeName is the obfuscated generated name, so a test using these under diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 5cbb38956..041f78321 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -904,7 +904,8 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) { macro(COVERGROUP_SIZED_RANGE_W, "sizedRangeW", false, "rr") \ macro(COVERGROUP_SIZED_SAMPLE, "sizedSample", false, "rrr") \ macro(COVERGROUP_SIZED_SAMPLE_W, "sizedSampleW", false, "rrr") \ - macro(COVERGROUP_TYPE_COVERAGE, "typeCoverage", false, "rrr") \ + macro(COVERGROUP_TYPE_COVERAGE, "typeCoverage", false, "rrrr") \ + macro(COVERGROUP_TYPE_WEIGHT, "typeWeight", false, "r") \ macro(COVERGROUP_VALUE_EXCLUDED, "valueExcluded", PURE, "r") \ macro(COVERGROUP_VALUE_EXCLUDED_W, "valueExcludedW", PURE, "r") \ macro(COVERGROUP_VALUE_FINALIZE, "valueFinalize", false, "") \ diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 68a35a2a7..5b4ba20e2 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -43,12 +43,14 @@ VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### -// Embedded covergroup assignment validation +// Embedded covergroup assignment validation; and in the same pass over the netlist, the +// type_option variables through which SystemVerilog may set type_option.merge_instances class CovergroupAssignValidVisitor final : public VNVisitorConst { VMemberMap m_memberMap; std::map m_constructors; // Implicit instance -> constructor + std::set m_mergeableTypeOptions; // See mergeableTypeOptions() const AstNodeFTask* m_ftaskp = nullptr; bool m_collecting = true; bool m_valid = true; @@ -96,6 +98,19 @@ class CovergroupAssignValidVisitor final : public VNVisitorConst { } iterateChildrenConst(nodep); } + void visit(AstNodeVarRef* nodep) override { + // Type options may be set at any time (IEEE 1800-2023 19.7.1), so SystemVerilog may set + // type_option.merge_instances with any use of a type_option but a read of another + // member, as not every write is marked yet (std::randomize) + if (m_collecting && nodep->varp()->name() == "type_option") { + const AstStructSel* const selp = VN_CAST(nodep->backp(), StructSel); + if (!selp || selp->fromp() != nodep || selp->name() == "merge_instances" + || !nodep->access().isReadOnly()) { + m_mergeableTypeOptions.insert(nodep->varp()); + } + } + iterateChildrenConst(nodep); + } void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } public: @@ -106,6 +121,9 @@ public: if (!m_constructors.empty()) iterateConst(nodep); } bool valid() const { return m_valid; } + // type_option variables through which SystemVerilog may set type_option.merge_instances, so + // that covergroups are tested with optionVar(true) + const std::set& mergeableTypeOptions() const { return m_mergeableTypeOptions; } }; //###################################################################### @@ -248,8 +266,14 @@ class FunctionalCoverageVisitor final : public VNVisitor { std::vector m_coverpoints; // Coverpoints in current covergroup std::map m_coverpointMap; // Name -> coverpoint for fast lookup std::vector m_coverCrosses; // Cross coverage items in current covergroup - std::vector m_cgOptions; // Covergroup-level weights, before lowering + std::vector m_cgOptions; // Covergroup-level options, before lowering uint32_t m_cgTypeWeight = 1; // The covergroup's type_option.weight, a constant + bool m_cgMergeInstances = false; // The covergroup's type_option.merge_instances, a constant + // The covergroup's type_option.merge_instances is, or SystemVerilog may make it, true + bool m_cgMayMerge = false; + // type_option members through which SystemVerilog may set type_option.merge_instances, + // see CovergroupAssignValidVisitor::mergeableTypeOptions() + const std::set& m_mergeableTypeOptions; struct EmbeddedEventTrigger final { FileLine* eventFl; // Clocking-event source location @@ -323,32 +347,47 @@ class FunctionalCoverageVisitor final : public VNVisitor { return varp; } - // 'option.weight' or 'type_option.weight', per optionVarp - AstStructSel* newWeightSel(FileLine* fl, AstVar* optionVarp, VAccess access) { - const AstMemberDType* const memberp = VN_AS( - m_memberMap.findMember(optionVarp->dtypep()->skipRefp(), "weight"), MemberDType); - UASSERT_OBJ(memberp, optionVarp, "Coverage option structure missing 'weight'"); + // 'option.' or 'type_option.', per optionVarp + AstStructSel* newOptionSel(FileLine* fl, AstVar* optionVarp, const std::string& member, + VAccess access) { + const AstMemberDType* const memberp + = VN_AS(m_memberMap.findMember(optionVarp->dtypep()->skipRefp(), member), MemberDType); + UASSERT_OBJ(memberp, optionVarp, "Coverage option structure missing '" << member << "'"); AstNodeExpr* const fromp = optionVarp->lifetime().isStatic() ? new AstVarRef{fl, optionVarp, access} : memberRef(fl, optionVarp, access); - AstStructSel* const selp = new AstStructSel{fl, fromp, "weight"}; + AstStructSel* const selp = new AstStructSel{fl, fromp, member}; selp->dtypep(memberp->subDTypep()->skipRefToEnump()); selp->didWidth(true); return selp; } - // Store the covergroup-level weights (IEEE 1800-2023 19.7) where SystemVerilog and the - // runtime read them. option.weight is evaluated by the constructor, as are the other - // instance options; type_option.weight is constant, and initializes the static member. + // Store the covergroup-level options (IEEE 1800-2023 19.7) where SystemVerilog and the + // runtime read them. The instance options, option.weight and option.get_inst_coverage, + // are evaluated by the constructor, as are the other instance options; the type options, + // type_option.weight and type_option.merge_instances, are constant, and initialize the + // static member. Without its own type_option.merge_instances, a covergroup has the + // default of --coverage-merge-instances. void lowerCovergroupOptions() { + bool mergeSet = false; // The covergroup sets type_option.merge_instances for (AstCgOptionAssign* const optp : m_cgOptions) { - UASSERT_OBJ(optp->optType() == VCoverOptionType::WEIGHT, optp, - "Unexpected covergroup option reaching V3Covergroup"); FileLine* const fl = optp->fileline(); - // V3Width left type_option.weight a non-negative constant - if (optp->typeOption()) m_cgTypeWeight = VN_AS(optp->valuep(), Const)->toUInt(); + // V3Width left the type options constant, and type_option.weight non-negative + const AstConst* const constp = VN_CAST(optp->valuep(), Const); + std::string member = "weight"; + if (optp->optType() == VCoverOptionType::MERGE_INSTANCES) { + member = "merge_instances"; + m_cgMergeInstances = !constp->num().isEqZero(); + mergeSet = true; + } else if (optp->optType() == VCoverOptionType::GET_INST_COVERAGE) { + member = "get_inst_coverage"; + } else { + UASSERT_OBJ(optp->optType() == VCoverOptionType::WEIGHT, optp, + "Unexpected covergroup option reaching V3Covergroup"); + if (optp->typeOption()) m_cgTypeWeight = constp->toUInt(); + } AstAssign* const assignp = new AstAssign{ - fl, newWeightSel(fl, optionVar(optp->typeOption()), VAccess::WRITE), + fl, newOptionSel(fl, optionVar(optp->typeOption()), member, VAccess::WRITE), optp->valuep()->unlinkFrBack()}; if (optp->typeOption()) { m_covergroupp->addMembersp(new AstInitialStatic{fl, assignp}); @@ -359,12 +398,21 @@ class FunctionalCoverageVisitor final : public VNVisitor { } } m_cgOptions.clear(); + if (!mergeSet && v3Global.opt.coverageMergeInstances()) { + FileLine* const fl = m_covergroupp->fileline(); + m_covergroupp->addMembersp(new AstInitialStatic{ + fl, new AstAssign{ + fl, newOptionSel(fl, optionVar(true), "merge_instances", VAccess::WRITE), + new AstConst{fl, AstConst::BitTrue{}}}}); + m_cgMergeInstances = true; + } + m_cgMayMerge = m_cgMergeInstances || m_mergeableTypeOptions.count(optionVar(true)); } // The weight of an item in the coverage database, which merges the instances: its - // option.weight if a constant, and so of every instance; else its type_option.weight, the - // weight of type coverage merged over the instances (IEEE 1800-2023 19.7.1) - static uint32_t itemDatabaseWeight(AstNode* optionsp) { + // type_option.weight if the covergroup merges them too (IEEE 1800-2023 19.11.3); else its + // option.weight if a constant, and so of every instance; else its type_option.weight + uint32_t itemDatabaseWeight(AstNode* optionsp) const { const AstNodeExpr* weightp = nullptr; // The option.weight in effect uint32_t typeWeight = 1; for (AstNode* nodep = optionsp; nodep; nodep = nodep->nextp()) { @@ -377,22 +425,30 @@ class FunctionalCoverageVisitor final : public VNVisitor { weightp = optp->valuep(); } } + if (m_cgMergeInstances) return typeWeight; if (!weightp) return 1; if (const AstConst* const constp = VN_CAST(weightp, Const)) return constp->toUInt(); return typeWeight; } // Configure an item's option.weight, its weight in instance coverage (IEEE 1800-2023 - // 19.11). type_option.weight only weighs type coverage merged over the instances, which - // type_option.merge_instances would select; without that, it has no effect. + // 19.11), and, if the covergroup may merge its instances, its type_option.weight, its + // weight in type coverage then (19.11.3). void generateItemWeight(FileLine* fl, AstVar* itemVarp, AstNode* optionsp) { for (AstNode* nodep = optionsp; nodep; nodep = nodep->nextp()) { const AstCoverOption* const optp = VN_AS(nodep, CoverOption); - if (!(optp->optType() == VCoverOptionType::WEIGHT) || optp->typeOption()) continue; - m_constructorp->addStmtsp( - itemCall(fl, itemVarp, VCMethod::COVERGROUP_WEIGHT, - {optp->valuep()->cloneTree(false), fileLineDebug(optp->fileline())}) - ->makeStmt()); + if (!(optp->optType() == VCoverOptionType::WEIGHT)) continue; + if (!optp->typeOption()) { + m_constructorp->addStmtsp( + itemCall(fl, itemVarp, VCMethod::COVERGROUP_WEIGHT, + {optp->valuep()->cloneTree(false), fileLineDebug(optp->fileline())}) + ->makeStmt()); + } else if (m_cgMayMerge) { + // V3Width left type_option.weight a non-negative constant + m_constructorp->addStmtsp(itemCall(fl, itemVarp, VCMethod::COVERGROUP_TYPE_WEIGHT, + {optp->valuep()->cloneTree(false)}) + ->makeStmt()); + } } } @@ -412,6 +468,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_droppedCrosses.clear(); m_cgInstVarp = nullptr; m_cgTypeWeight = 1; + m_cgMergeInstances = false; + m_cgMayMerge = false; lowerCovergroupOptions(); @@ -1067,12 +1125,13 @@ class FunctionalCoverageVisitor final : public VNVisitor { itemCall(fl, m_cgInstVarp, VCMethod::COVERGROUP_ATTACH, {ctext(fl, "vlSymsp->_vm_contextp__->covergroupRegistryp()" "->newCovergroupInst(" - + quoted(covergroupProtectedName()) + ")")}, + + quoted(covergroupProtectedName()) + + (m_cgMayMerge ? ", true" : ", false") + ")")}, /*usePtr=*/false) ->makeStmt()); // The node reads option.weight in place, so procedural assignments take effect AstCExpr* const weightAddrp = new AstCExpr{fl, "&"}; - weightAddrp->add(newWeightSel(fl, optionVar(false), VAccess::READ)); + weightAddrp->add(newOptionSel(fl, optionVar(false), "weight", VAccess::READ)); m_constructorp->addStmtsp(itemCall(fl, m_cgInstVarp, VCMethod::COVERGROUP_LEND_WEIGHT, {weightAddrp, fileLineDebug(fl)}, /*usePtr=*/false) ->makeStmt()); @@ -3924,34 +3983,50 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_memberMap.clear(); // get_inst_coverage(): the average of the coverpoints and crosses, weighted by their - // option.weight (IEEE 1800-2023 19.11). The instance node holds their runtimes. + // option.weight (IEEE 1800-2023 19.11). The instance node holds their runtimes. With + // the instances merged, get_coverage() instead, unless option.get_inst_coverage + // (Table 19-1). AstFunc* const getInstCoveragep = VN_AS(m_memberMap.findMember(m_covergroupp, "get_inst_coverage"), Func); FileLine* const instFl = getInstCoveragep->fileline(); AstCMethodHard* const instCallp = instanceCall(instFl, VCMethod::COVERGROUP_COVERAGE); instCallp->dtypeSetDouble(); + AstNodeExpr* instValuep = instCallp; + if (m_cgMayMerge) { + AstNodeExpr* const mergedp = new AstLogAnd{ + instFl, newOptionSel(instFl, optionVar(true), "merge_instances", VAccess::READ), + new AstLogNot{instFl, newOptionSel(instFl, optionVar(false), "get_inst_coverage", + VAccess::READ)}}; + instValuep = new AstCond{instFl, mergedp, typeCoverageCall(instFl), instCallp}; + } getInstCoveragep->addStmtsp(new AstAssign{ instFl, new AstVarRef{instFl, VN_AS(getInstCoveragep->fvarp(), Var), VAccess::WRITE}, - instCallp}); + instValuep}); // get_coverage(): the average of the covergroup's instances, weighted by their - // option.weight (IEEE 1800-2023 19.11.3). Static, so the registry finds the instances. + // option.weight, or with type_option.merge_instances, the coverage of the union of their + // bins (IEEE 1800-2023 19.11.3). Static, so the registry finds the instances. AstFunc* const getCoveragep = VN_AS(m_memberMap.findMember(m_covergroupp, "get_coverage"), Func); FileLine* const typeFl = getCoveragep->fileline(); - AstCExpr* const registryp - = ctext(typeFl, "vlSymsp->_vm_contextp__->covergroupRegistryp()"); - registryp->dtypeSetVoid(); // Opaque receiver; only ever the 'fromp' of the call below - AstCMethodHard* const typeCallp - = new AstCMethodHard{typeFl, registryp, VCMethod::COVERGROUP_TYPE_COVERAGE}; - typeCallp->addPinsp(ctext(typeFl, quoted(covergroupProtectedName()))); - typeCallp->addPinsp(newWeightSel(typeFl, optionVar(true), VAccess::READ)); - typeCallp->addPinsp(fileLineDebug(m_covergroupp->fileline())); - typeCallp->usePtr(true); - typeCallp->dtypeSetDouble(); getCoveragep->addStmtsp(new AstAssign{ typeFl, new AstVarRef{typeFl, VN_AS(getCoveragep->fvarp(), Var), VAccess::WRITE}, - typeCallp}); + typeCoverageCall(typeFl)}); + } + + // The registry call computing the covergroup's type coverage, per its type options + AstCMethodHard* typeCoverageCall(FileLine* fl) { + AstCExpr* const registryp = ctext(fl, "vlSymsp->_vm_contextp__->covergroupRegistryp()"); + registryp->dtypeSetVoid(); // Opaque receiver; only ever the 'fromp' of the call below + AstCMethodHard* const callp + = new AstCMethodHard{fl, registryp, VCMethod::COVERGROUP_TYPE_COVERAGE}; + callp->addPinsp(ctext(fl, quoted(covergroupProtectedName()))); + callp->addPinsp(newOptionSel(fl, optionVar(true), "weight", VAccess::READ)); + callp->addPinsp(newOptionSel(fl, optionVar(true), "merge_instances", VAccess::READ)); + callp->addPinsp(fileLineDebug(m_covergroupp->fileline())); + callp->usePtr(true); + callp->dtypeSetDouble(); + return callp; } // VISITORS @@ -4505,7 +4580,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { iterateChildren(nodep); } - // V3Width leaves only the covergroup-level weights, for lowerCovergroupOptions() + // V3Width leaves only the covergroup-level options lowerCovergroupOptions() stores void visit(AstCgOptionAssign* nodep) override { m_cgOptions.push_back(nodep); } // A package, interface, or module, so the design unit declaring the classes within @@ -4519,7 +4594,11 @@ class FunctionalCoverageVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit FunctionalCoverageVisitor(AstNetlist* nodep) { iterate(nodep); } + FunctionalCoverageVisitor(AstNetlist* nodep, + const std::set& mergeableTypeOptions) + : m_mergeableTypeOptions{mergeableTypeOptions} { + iterate(nodep); + } ~FunctionalCoverageVisitor() override = default; }; @@ -4528,7 +4607,10 @@ public: void V3Covergroup::covergroup(AstNetlist* nodep) { UINFO(4, __FUNCTION__ << ": "); - if (!CovergroupAssignValidVisitor{nodep}.valid()) V3Error::abortIfErrors(); - { FunctionalCoverageVisitor{nodep}; } // Destruct before checking + const CovergroupAssignValidVisitor validVisitor{nodep}; + if (!validVisitor.valid()) V3Error::abortIfErrors(); + { // Destruct before checking + FunctionalCoverageVisitor{nodep, validVisitor.mergeableTypeOptions()}; + } V3Global::dumpCheckGlobalTree("coveragefunc", 0, dumpTreeEitherLevel() >= 3); } diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 1d009b122..74ea0be4a 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1393,6 +1393,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, parseBinsLimit("--coverage-max-real-bins", valp, m_coverageMaxRealBins); }); DECL_OPTION("-coverage-max-width", Set, &m_coverageMaxWidth); + DECL_OPTION("-coverage-merge-instances", OnOff, &m_coverageMergeInstances); DECL_OPTION("-coverage-per-instance", OnOff, &m_coveragePerInstance); DECL_OPTION("-coverage-toggle", OnOff, &m_coverageToggle); DECL_OPTION("-coverage-underscore", OnOff, &m_coverageUnderscore); diff --git a/src/V3Options.h b/src/V3Options.h index 6e5517463..abdc42c38 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -233,6 +233,7 @@ private: bool m_coverageExpr = false; // main switch: --coverage-expr bool m_coverageFsm = false; // main switch: --coverage-fsm bool m_coverageLine = false; // main switch: --coverage-block + bool m_coverageMergeInstances = false; // main switch: --coverage-merge-instances bool m_coveragePerInstance = false; // main switch: --coverage-per-instance bool m_coverageToggle = false; // main switch: --coverage-toggle bool m_coverageUnderscore = false; // main switch: --coverage-underscore @@ -533,6 +534,7 @@ public: bool coverageExpr() const { return m_coverageExpr; } bool coverageFsm() const { return m_coverageFsm; } bool coverageLine() const { return m_coverageLine; } + bool coverageMergeInstances() const { return m_coverageMergeInstances; } bool coveragePerInstance() const { return m_coveragePerInstance; } bool coverageToggle() const { return m_coverageToggle; } bool coverageUnderscore() const { return m_coverageUnderscore; } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 216ba932f..648a2e867 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2162,13 +2162,22 @@ class WidthVisitor final : public VNVisitor { "'option.auto_bin_max'; using default value"); } } else if (nodep->optType() == VCoverOptionType::MERGE_INSTANCES) { - // get_coverage() always averages the instances (IEEE 1800-2023 19.11.3) - const AstConst* const constp = VN_CAST(nodep->valuep(), Const); - if (!constp || !constp->num().isEqZero()) { - nodep->v3warn(COVERIGN, "Ignoring unsupported: 'type_option.merge_instances';" - " type coverage is the weighted average of the" - " instances"); + // V3Covergroup stores it into type_option; as a type option it is constant + // (IEEE 1800-2023 19.7.1) + iterateCheckBool(nodep, "merge_instances", nodep->valuep(), BOTH); + V3Const::constifyEdit(nodep->valuep()); + AstNodeExpr* const valuep = nodep->valuep(); + if (!VN_IS(valuep, Const)) { + valuep->v3error("Coverage option 'type_option.merge_instances' requires a" + " constant expression (IEEE 1800-2023 19.7.1)"); + valuep->replaceWith(new AstConst{valuep->fileline(), AstConst::BitFalse{}}); + VL_DO_DANGLING(pushDeletep(valuep), valuep); } + return; + } else if (nodep->optType() == VCoverOptionType::GET_INST_COVERAGE) { + // V3Covergroup stores it into option, as the constructor evaluates it + iterateCheckBool(nodep, "get_inst_coverage", nodep->valuep(), BOTH); + return; } else if (nodep->optType() == VCoverOptionType::DISTRIBUTE_FIRST) { // A bins 'with' filter applies before the values are distributed to the bins // (IEEE 1800-2023 19.5.1.1) diff --git a/test_regress/t/t_covergroup_inst_handle.cpp b/test_regress/t/t_covergroup_inst_handle.cpp index bde213526..0dce5b7ce 100644 --- a/test_regress/t/t_covergroup_inst_handle.cpp +++ b/test_regress/t/t_covergroup_inst_handle.cpp @@ -56,7 +56,7 @@ int main(int argc, char* argv[]) { { // ---- Case 1: attach count above 1 ---- VlCovInstHandle first; - first.attach(registryp->newCovergroupInst("cg_handle")); + first.attach(registryp->newCovergroupInst("cg_handle", false)); checkEq("one handle, one node", registryp->liveInstanceCount("cg_handle"), 1); checkEq("nothing retired yet", registryp->retiredInstanceCount("cg_handle"), 0); diff --git a/test_regress/t/t_covergroup_merge_instances.out b/test_regress/t/t_covergroup_merge_instances.out new file mode 100644 index 000000000..c1013b7fe --- /dev/null +++ b/test_regress/t/t_covergroup_merge_instances.out @@ -0,0 +1,95 @@ +t.cg_avg.checked.auto_0: 1 +t.cg_avg.checked.auto_1: 1 +t.cg_avg.helper.auto_0: 2 +t.cg_avg.helper.auto_1: 0 +t.cg_cross.cp_a.auto_0: 1 +t.cg_cross.cp_a.auto_1: 0 +t.cg_cross.cp_b.auto_0: 1 +t.cg_cross.cp_b.auto_1: 0 +t.cg_cross.cp_b.auto_2: 0 +t.cg_cross.cp_b.auto_3: 0 +t.cg_cross.x. [cross]: 1 +t.cg_cross.x. [cross]: 0 +t.cg_cross.x. [cross]: 0 +t.cg_cross.x. [cross]: 0 +t.cg_cross.x. [cross]: 0 +t.cg_cross.x. [cross]: 0 +t.cg_cross.x. [cross]: 0 +t.cg_cross.x. [cross]: 0 +t.cg_cross_ignore.cp_a.auto_0: 1 +t.cg_cross_ignore.cp_a.auto_1: 0 +t.cg_cross_ignore.cp_b.auto_0: 1 +t.cg_cross_ignore.cp_b.auto_1: 0 +t.cg_cross_ignore.x. [cross]: 1 +t.cg_cross_ignore.x. [cross]: 0 +t.cg_cross_ignore.x.skip [ignore,cross]: 0 +t.cg_cross_joined.p.a: 1 +t.cg_cross_joined.p.a_x_b: 0 +t.cg_cross_joined.q.b_x_c: 1 +t.cg_cross_joined.q.c: 0 +t.cg_cross_joined.xy. [cross]: 1 +t.cg_cross_joined.xy. [cross]: 0 +t.cg_cross_joined.xy. [cross]: 0 +t.cg_cross_joined.xy. [cross]: 0 +t.cg_cross_named.cp_a.auto_0: 1 +t.cg_cross_named.cp_a.auto_1: 0 +t.cg_cross_named.cp_b.auto_0: 1 +t.cg_cross_named.cp_b.auto_1: 0 +t.cg_cross_named.x. [cross]: 0 +t.cg_cross_named.x. [cross]: 0 +t.cg_cross_named.x. [cross]: 0 +t.cg_cross_named.x.auto_1_x_auto_1 [cross]: 1 +t.cg_cross_opt.cp_a.auto_0: 1 +t.cg_cross_opt.cp_a.auto_1: 0 +t.cg_cross_opt.cp_b.auto_0: 1 +t.cg_cross_opt.cp_b.auto_1: 0 +t.cg_cross_opt.cp_b.auto_2: 0 +t.cg_cross_opt.cp_b.auto_3: 0 +t.cg_cross_opt.x. [cross]: 1 +t.cg_cross_opt.x. [cross]: 0 +t.cg_cross_opt.x. [cross]: 0 +t.cg_cross_opt.x. [cross]: 0 +t.cg_cross_opt.x. [cross]: 0 +t.cg_cross_opt.x. [cross]: 0 +t.cg_cross_opt.x. [cross]: 0 +t.cg_cross_opt.x. [cross]: 0 +t.cg_cross_union.cp_a.auto_0: 1 +t.cg_cross_union.cp_a.auto_1: 1 +t.cg_cross_union.cp_b.auto_0: 1 +t.cg_cross_union.cp_b.auto_1: 1 +t.cg_cross_union.x. [cross]: 1 +t.cg_cross_union.x. [cross]: 0 +t.cg_cross_union.x. [cross]: 0 +t.cg_cross_union.x. [cross]: 1 +t.cg_dead.cp.auto_0: 1 +t.cg_dead.cp.auto_1: 1 +t.cg_dead_union.a.b[0]: 1 +t.cg_dead_union.a.b[1]: 1 +t.cg_dead_union.a.b[2]: 1 +t.cg_dead_union.a.b[3]: 0 +t.cg_inst.checked.auto_0: 1 +t.cg_inst.checked.auto_1: 1 +t.cg_inst.helper.auto_0: 2 +t.cg_inst.helper.auto_1: 0 +t.cg_merged.checked.auto_0: 1 +t.cg_merged.checked.auto_1: 1 +t.cg_merged.helper.auto_0: 2 +t.cg_merged.helper.auto_1: 0 +t.cg_opt.checked.auto_0: 1 +t.cg_opt.checked.auto_1: 1 +t.cg_opt.helper.auto_0: 2 +t.cg_opt.helper.auto_1: 0 +t.cg_proc.cp.auto_0: 1 +t.cg_proc.cp.auto_1: 1 +t.cg_sum.cp.auto_0: 2 +t.cg_sum.cp.auto_1: 0 +t.cg_union.a.b[0]: 1 +t.cg_union.a.b[1]: 2 +t.cg_union.a.b[2]: 0 +t.cg_union_avg.a.b[0]: 1 +t.cg_union_avg.a.b[1]: 2 +t.cg_union_avg.a.b[2]: 0 +t.cg_zero.cp.auto_0: 1 +t.cg_zero.cp.auto_1: 0 +t.cg_zero_type.cp.auto_0: 1 +t.cg_zero_type.cp.auto_1: 0 diff --git a/test_regress/t/t_covergroup_merge_instances.py b/test_regress/t/t_covergroup_merge_instances.py new file mode 100755 index 000000000..9f6b5465d --- /dev/null +++ b/test_regress/t/t_covergroup_merge_instances.py @@ -0,0 +1,15 @@ +#!/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_merge_instances.v b/test_regress/t/t_covergroup_merge_instances.v new file mode 100644 index 000000000..4e430e7cd --- /dev/null +++ b/test_regress/t/t_covergroup_merge_instances.v @@ -0,0 +1,303 @@ +// 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 + +// Test type_option.merge_instances (IEEE 1800-2023 19.7.1, 19.11.3) + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define checkp(gotv,expv) do if ($sformatf("%0.2f", gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%0.2f exp=%s\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + int cyc = 0; + + // Without merge_instances, type coverage averages the instances, whose items weigh by + // option.weight, so helper's type_option.weight has no effect: 75 + covergroup cg_avg with function sample (bit a, bit b); + helper: coverpoint a { + type_option.weight = 0; + } + checked: coverpoint b; + endgroup + // Merged, the items weigh by type_option.weight: 100, and get_inst_coverage() returns + // get_coverage() (Table 19-1) + covergroup cg_merged with function sample (bit a, bit b); + type_option.merge_instances = 1; + helper: coverpoint a { + type_option.weight = 0; + } + checked: coverpoint b; + endgroup + // With option.get_inst_coverage, get_inst_coverage() is the instance's coverage: 75 + covergroup cg_inst with function sample (bit a, bit b); + type_option.merge_instances = 1; + option.get_inst_coverage = 1; + helper: coverpoint a { + type_option.weight = 0; + } + checked: coverpoint b; + endgroup + // Merged, option.weight has no effect: 75, and the instance's coverage: 100 + covergroup cg_opt with function sample (bit a, bit b); + type_option.merge_instances = 1; + option.get_inst_coverage = 1; + helper: coverpoint a { + option.weight = 0; + } + checked: coverpoint b; + endgroup + // As the example of 19.11.3: bins b[0], b[1] and b[1], b[2] merge by name, 2 of 3 covered + covergroup cg_union(int l, h) with function sample (int a); + type_option.merge_instances = 1; + option.get_inst_coverage = 1; + coverpoint a { + bins b[] = {[0 : 3]} with (item >= l && item <= h); + } + endgroup + // The same averaged: 100 and 50 + covergroup cg_union_avg(int l, h) with function sample (int a); + coverpoint a { + bins b[] = {[0 : 3]} with (item >= l && item <= h); + } + endgroup + // The counts of a merged bin sum: at_least 2, and each instance hits auto[0] once: 50 + covergroup cg_sum with function sample (bit a); + type_option.merge_instances = 1; + cp: coverpoint a { + option.at_least = 2; + } + endgroup + // A cross weighs by type_option.weight: (50 + 25 + 3 * 12.5) / 5 + covergroup cg_cross with function sample (bit a, bit [1:0] b); + type_option.merge_instances = 1; + cp_a: coverpoint a; + cp_b: coverpoint b; + x: cross cp_a, cp_b{type_option.weight = 3;} + endgroup + // And not by option.weight: (50 + 25 + 12.5) / 3 + covergroup cg_cross_opt with function sample (bit a, bit [1:0] b); + type_option.merge_instances = 1; + cp_a: coverpoint a; + cp_b: coverpoint b; + x: cross cp_a, cp_b{option.weight = 3;} + endgroup + // Cross bins merge by name: (100 + 100 + 50) / 3 + covergroup cg_cross_union with function sample (bit a, bit b); + type_option.merge_instances = 1; + cp_a: coverpoint a; + cp_b: coverpoint b; + x: cross cp_a, cp_b; + endgroup + // An explicit cross bin named as an automatic bin's bins joined with _x_ stays apart from it: + // 1 of 4 covered + covergroup cg_cross_named with function sample (bit a, bit b); + type_option.merge_instances = 1; + cp_a: coverpoint a { + type_option.weight = 0; + } + cp_b: coverpoint b { + type_option.weight = 0; + } + x: cross cp_a, cp_b{ + bins auto_1_x_auto_1 = binsof (cp_a) intersect {0} && binsof (cp_b) intersect {0}; + } + endgroup + // Automatic cross bins stay apart though their bins' names joined with _x_ collide, + // and : 1 of 4 covered + covergroup cg_cross_joined with function sample (bit x, bit y); + type_option.merge_instances = 1; + p: coverpoint x { + type_option.weight = 0; + bins a = {0}; + bins a_x_b = {1}; + } + q: coverpoint y { + type_option.weight = 0; + bins b_x_c = {0}; + bins c = {1}; + } + xy: cross p, q; + endgroup + // Ignored cross bins are not coverable: 1 of 2 covered + covergroup cg_cross_ignore with function sample (bit a, bit b); + type_option.merge_instances = 1; + cp_a: coverpoint a { + type_option.weight = 0; + } + cp_b: coverpoint b { + type_option.weight = 0; + } + x: cross cp_a, cp_b{ignore_bins skip = binsof (cp_a) intersect {1};} + endgroup + // Every item of type weight zero: 0, or 100 if the covergroup's type weight is zero too + covergroup cg_zero with function sample (bit a); + type_option.merge_instances = 1; + cp: coverpoint a { + type_option.weight = 0; + } + endgroup + covergroup cg_zero_type with function sample (bit a); + type_option.merge_instances = 1; + type_option.weight = 0; + cp: coverpoint a { + type_option.weight = 0; + } + endgroup + // The bins of an instance that has died still count: 100 + covergroup cg_dead with function sample (bit a); + type_option.merge_instances = 1; + cp: coverpoint a; + endgroup + // The bins of an instance that has died still count, though no live instance has them: b[0] + // and b[1] hit by the instance that dies, b[2] and b[3] of the live one, 3 of 4 covered + covergroup cg_dead_union(int l, h) with function sample (int a); + type_option.merge_instances = 1; + coverpoint a { + bins b[] = {[0 : 3]} with (item >= l && item <= h); + } + endgroup + // Merged by an assignment during simulation (IEEE 1800-2023 19.7.1): 50 averaged, and 100 + // merged, with the bins of an instance that died before + covergroup cg_proc with function sample (bit a); + cp: coverpoint a; + endgroup + + cg_avg avg = new; + cg_merged merged = new; + cg_inst inst = new; + cg_opt opt = new; + cg_union union_1 = new(0, 1); + cg_union union_2 = new(1, 2); + cg_union_avg union_avg_1 = new(0, 1); + cg_union_avg union_avg_2 = new(1, 2); + cg_sum sum_1 = new; + cg_sum sum_2 = new; + cg_cross cross_w = new; + cg_cross_opt cross_opt = new; + cg_cross_union cross_union_1 = new; + cg_cross_union cross_union_2 = new; + cg_cross_named cross_named = new; + cg_cross_joined cross_joined = new; + cg_cross_ignore cross_ignore = new; + cg_zero zero = new; + cg_zero_type zero_type = new; + cg_dead dead_1 = new; + cg_dead dead_2 = new; + cg_dead_union dead_union_1 = new(2, 3); + cg_dead_union dead_union_2 = new(0, 1); + cg_proc proc_1 = new; + cg_proc proc_2 = new; + + initial begin + avg.sample(0, 0); + avg.sample(0, 1); + `checkp(avg.get_coverage(), "75.00"); + `checkp(avg.get_inst_coverage(), "75.00"); + + merged.sample(0, 0); + merged.sample(0, 1); + `checkp(merged.get_coverage(), "100.00"); + `checkp(merged.get_inst_coverage(), "100.00"); + + inst.sample(0, 0); + inst.sample(0, 1); + `checkp(inst.get_coverage(), "100.00"); + `checkp(inst.get_inst_coverage(), "75.00"); + + opt.sample(0, 0); + opt.sample(0, 1); + `checkp(opt.get_coverage(), "75.00"); + `checkp(opt.get_inst_coverage(), "100.00"); + + union_1.sample(0); + union_1.sample(1); + union_2.sample(1); + `checkp(union_1.get_coverage(), "66.67"); + `checkp(union_1.get_inst_coverage(), "100.00"); + `checkp(union_2.get_inst_coverage(), "50.00"); + + union_avg_1.sample(0); + union_avg_1.sample(1); + union_avg_2.sample(1); + `checkp(union_avg_1.get_coverage(), "75.00"); + + sum_1.sample(0); + sum_2.sample(0); + `checkp(sum_1.get_coverage(), "50.00"); + `checkp(sum_1.get_inst_coverage(), "50.00"); + + cross_w.sample(0, 0); + `checkp(cross_w.get_coverage(), "22.50"); + + cross_opt.sample(0, 0); + `checkp(cross_opt.get_coverage(), "29.17"); + + cross_union_1.sample(0, 0); + cross_union_2.sample(1, 1); + `checkp(cross_union_1.get_coverage(), "83.33"); + + cross_named.sample(0, 0); + `checkp(cross_named.get_coverage(), "25.00"); + + cross_joined.sample(0, 0); + `checkp(cross_joined.get_coverage(), "25.00"); + + cross_ignore.sample(0, 0); + `checkp(cross_ignore.get_coverage(), "50.00"); + + zero.sample(0); + `checkp(zero.get_coverage(), "0.00"); + zero_type.sample(0); + `checkp(zero_type.get_coverage(), "100.00"); + + dead_1.sample(0); + dead_2.sample(1); + dead_union_1.sample(2); + dead_union_2.sample(0); + dead_union_2.sample(1); + proc_1.sample(0); + proc_2.sample(1); + end + + // Instances die once their evaluation ends, so after a clock edge + function int retired_dead(); + retired_dead = $c32( + "Verilated::threadContextp()->covergroupRegistryp()->retiredInstanceCount(\"t.cg_dead\")"); + endfunction + function int retired_proc(); + retired_proc = $c32( + "Verilated::threadContextp()->covergroupRegistryp()->retiredInstanceCount(\"t.cg_proc\")"); + endfunction + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) begin + dead_2 = null; + dead_union_2 = null; + proc_2 = null; + end + else if (cyc == 3) begin + `checkd(retired_dead(), 1); + `checkp(dead_1.get_coverage(), "100.00"); + `checkp(dead_union_1.get_coverage(), "75.00"); + + `checkd(retired_proc(), 1); + `checkp(proc_1.get_coverage(), "50.00"); + `checkp(proc_1.get_inst_coverage(), "50.00"); + cg_proc::type_option.merge_instances = 1; + `checkp(proc_1.get_coverage(), "100.00"); + `checkp(proc_1.get_inst_coverage(), "100.00"); + proc_1.type_option.merge_instances = 0; + `checkp(proc_1.get_coverage(), "50.00"); + + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_covergroup_merge_instances_bad.out b/test_regress/t/t_covergroup_merge_instances_bad.out new file mode 100644 index 000000000..f6e62b11d --- /dev/null +++ b/test_regress/t/t_covergroup_merge_instances_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_covergroup_merge_instances_bad.v:13:35: Coverage option 'type_option.merge_instances' requires a constant expression (IEEE 1800-2023 19.7.1) + : ... note: In instance 't' + 13 | type_option.merge_instances = merge; + | ^~~~~ + ... 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_merge_instances_bad.py b/test_regress/t/t_covergroup_merge_instances_bad.py new file mode 100755 index 000000000..344a4e20a --- /dev/null +++ b/test_regress/t/t_covergroup_merge_instances_bad.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 + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_merge_instances_bad.v b/test_regress/t/t_covergroup_merge_instances_bad.v new file mode 100644 index 000000000..40805c656 --- /dev/null +++ b/test_regress/t/t_covergroup_merge_instances_bad.v @@ -0,0 +1,18 @@ +// 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 + +// type_option.merge_instances is a type option, so constant (IEEE 1800-2023 19.7.1) + +module t; + bit a; + + covergroup cg(bit merge); + type_option.merge_instances = merge; // <--- Bad: not constant + cp: coverpoint a; + endgroup + + cg c = new(1); +endmodule diff --git a/test_regress/t/t_covergroup_merge_instances_default.out b/test_regress/t/t_covergroup_merge_instances_default.out new file mode 100644 index 000000000..6774431d4 --- /dev/null +++ b/test_regress/t/t_covergroup_merge_instances_default.out @@ -0,0 +1,11 @@ +$ verilator_coverage --report hierarchy --levels 1 coverage.dat +Covergroup Coverage Summary: + t : 93.8% (21/26) + t.C::cg_emb : 100.0% ( 2/ 2) + t.cg_default : 100.0% ( 3/ 4) + t.cg_inst : 100.0% ( 3/ 4) + t.cg_multi : 100.0% ( 2/ 2) + t.cg_multi_off : 100.0% ( 2/ 2) + t.cg_off : 75.0% ( 3/ 4) + t.cg_on : 100.0% ( 3/ 4) + t.cg_optw : 75.0% ( 3/ 4) diff --git a/test_regress/t/t_covergroup_merge_instances_default.py b/test_regress/t/t_covergroup_merge_instances_default.py new file mode 100755 index 000000000..07c7d35ab --- /dev/null +++ b/test_regress/t/t_covergroup_merge_instances_default.py @@ -0,0 +1,32 @@ +#!/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 + +from coverage_common import init_log, run_vlcov, vlcov_run_context + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage-user', '--coverage-merge-instances']) + +test.execute() + +# The coverage the report computes: as get_coverage() does, except for cg_multi_off, whose +# instances the coverage database merges +log = test.obj_dir + "/vlcov.log" +tmp_log = test.obj_dir + "/vlcov.tmp" +init_log(log) +vlcov = vlcov_run_context(test, log, tmp_log) +run_vlcov(vlcov, + "verilator_coverage --report hierarchy --levels 1 coverage.dat", + args=["--report", "hierarchy", "--levels", "1", test.obj_dir + "/coverage.dat"]) + +test.files_identical(log, test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_merge_instances_default.v b/test_regress/t/t_covergroup_merge_instances_default.v new file mode 100644 index 000000000..25ee1109f --- /dev/null +++ b/test_regress/t/t_covergroup_merge_instances_default.v @@ -0,0 +1,134 @@ +// 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 + +// Test --coverage-merge-instances, the default of type_option.merge_instances (IEEE 1800-2023 +// 19.11.3) + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define checkp(gotv,expv) do if ($sformatf("%0.2f", gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%0.2f exp=%s\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + // Merged by default, the items weigh by type_option.weight: 100 + covergroup cg_default with function sample (bit a, bit b); + helper: coverpoint a { + type_option.weight = 0; + } + checked: coverpoint b; + endgroup + // Its own type_option.merge_instances wins: 75 + covergroup cg_off with function sample (bit a, bit b); + type_option.merge_instances = 0; + helper: coverpoint a { + type_option.weight = 0; + } + checked: coverpoint b; + endgroup + covergroup cg_on with function sample (bit a, bit b); + type_option.merge_instances = 1; + helper: coverpoint a { + type_option.weight = 0; + } + checked: coverpoint b; + endgroup + // get_inst_coverage() is the instance's coverage only with option.get_inst_coverage: 75 + covergroup cg_inst with function sample (bit a, bit b); + option.get_inst_coverage = 1; + helper: coverpoint a { + type_option.weight = 0; + } + checked: coverpoint b; + endgroup + // Merged, option.weight has no effect: 75 + covergroup cg_optw with function sample (bit a, bit b); + helper: coverpoint a { + option.weight = 0; + } + checked: coverpoint b; + endgroup + // Two instances hitting different bins: 100 merged, and 50 averaged + covergroup cg_multi with function sample (bit a); + cp: coverpoint a; + endgroup + covergroup cg_multi_off with function sample (bit a); + type_option.merge_instances = 0; + cp: coverpoint a; + endgroup + + // An embedded covergroup: the objects hit different bins, 100 merged + class C; + bit v; + covergroup cg_emb; + cp: coverpoint v; + endgroup + function new(); + cg_emb = new; + endfunction + endclass + + cg_default d = new; + cg_off off = new; + cg_on on = new; + cg_inst inst = new; + cg_optw optw = new; + cg_multi m1 = new; + cg_multi m2 = new; + cg_multi_off mo1 = new; + cg_multi_off mo2 = new; + C c1 = new; + C c2 = new; + + initial begin + d.sample(0, 0); + d.sample(0, 1); + off.sample(0, 0); + off.sample(0, 1); + on.sample(0, 0); + on.sample(0, 1); + inst.sample(0, 0); + inst.sample(0, 1); + optw.sample(0, 0); + optw.sample(0, 1); + m1.sample(0); + m2.sample(1); + mo1.sample(0); + mo2.sample(1); + c1.v = 0; + c1.cg_emb.sample(); + c2.v = 1; + c2.cg_emb.sample(); + + `checkd(cg_default::type_option.merge_instances, 1'b1); + `checkd(d.type_option.merge_instances, 1'b1); + `checkd(cg_off::type_option.merge_instances, 1'b0); + `checkd(cg_on::type_option.merge_instances, 1'b1); + + `checkp(d.get_coverage(), "100.00"); + `checkp(d.get_inst_coverage(), "100.00"); + `checkp(off.get_coverage(), "75.00"); + `checkp(off.get_inst_coverage(), "75.00"); + `checkp(on.get_coverage(), "100.00"); + `checkp(on.get_inst_coverage(), "100.00"); + `checkp(inst.get_coverage(), "100.00"); + `checkp(inst.get_inst_coverage(), "75.00"); + `checkp(optw.get_coverage(), "75.00"); + `checkp(optw.get_inst_coverage(), "75.00"); + `checkp(m1.get_coverage(), "100.00"); + `checkp(m1.get_inst_coverage(), "100.00"); + `checkp(m2.get_inst_coverage(), "100.00"); + `checkp(mo1.get_coverage(), "50.00"); + `checkp(mo1.get_inst_coverage(), "50.00"); + `checkp(mo2.get_inst_coverage(), "50.00"); + `checkp(c1.cg_emb.get_coverage(), "100.00"); + `checkp(c1.cg_emb.get_inst_coverage(), "100.00"); + `checkp(c2.cg_emb.get_inst_coverage(), "100.00"); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_merge_instances_no_coverage.py b/test_regress/t/t_covergroup_merge_instances_no_coverage.py new file mode 100755 index 000000000..c84e15068 --- /dev/null +++ b/test_regress/t/t_covergroup_merge_instances_no_coverage.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_all') +test.top_filename = 't/t_covergroup_merge_instances.v' + +# The last setting applies, so the IEEE default: instances averaged unless merged explicitly +test.compile(verilator_flags2=['--coverage-merge-instances', '--no-coverage-merge-instances']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_unsup_ign2.out b/test_regress/t/t_covergroup_unsup_ign2.out index 7ad58069c..22febbc16 100644 --- a/test_regress/t/t_covergroup_unsup_ign2.out +++ b/test_regress/t/t_covergroup_unsup_ign2.out @@ -141,10 +141,6 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:171:7: Unsupported: explicit coverage cross bins 171 | illegal_bins lib_cross = binsof(a) with (a); | ^~~~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:46:5: Ignoring unsupported: 'type_option.merge_instances'; type coverage is the weighted average of the instances - : ... note: In instance 't' - 46 | type_option.merge_instances = 1; - | ^~~~~~~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:47:5: Ignoring unsupported: 'type_option.distribute_first'; 'with' filters apply before values are distributed to bins : ... note: In instance 't' 47 | type_option.distribute_first = 1; diff --git a/test_regress/t/t_vlcov_covergroup_report.out b/test_regress/t/t_vlcov_covergroup_report.out index 2b945cea3..e95d279d1 100644 --- a/test_regress/t/t_vlcov_covergroup_report.out +++ b/test_regress/t/t_vlcov_covergroup_report.out @@ -6,14 +6,14 @@ Coverage Summary: expr : 0.0% ( 0/ 0) fsm_state : 0.0% ( 0/ 0) fsm_arc : 0.0% ( 0/ 0) - covergroup : 63.7% (34/57) + covergroup : 66.1% (40/65) Covergroup Coverage Summary: spec#(0.5,"a.\"(b%22") : 50.0% ( 1/ 2) spec#(0.5,"a.\"(b%22").cg : 50.0% ( 1/ 2) spec#(0.5,"a.\"(b%22").cg.cp : 50.0% ( 1/ 2) spec#(0.5,"a.\"(b%22").cg.cp.auto_0 : 0.0% ( 0/ 1) spec#(0.5,"a.\"(b%22").cg.cp.auto_1 : 100.0% ( 1/ 1) - t : 64.5% (33/55) + t : 66.9% (39/63) t.First::twin : 100.0% ( 2/ 2) t.First::twin.cp : 100.0% ( 2/ 2) t.First::twin.cp.hit : 100.0% ( 1/ 1) @@ -70,6 +70,13 @@ Covergroup Coverage Summary: t.heavy.cp : 50.0% ( 1/ 2) t.heavy.cp.auto_0 : 100.0% ( 1/ 1) t.heavy.cp.auto_1 : 0.0% ( 0/ 1) + t.inst_merged : 75.0% ( 3/ 4) + t.inst_merged.cp_a : 100.0% ( 2/ 2) + t.inst_merged.cp_a.auto_0 : 100.0% ( 1/ 1) + t.inst_merged.cp_a.auto_1 : 100.0% ( 1/ 1) + t.inst_merged.cp_b : 50.0% ( 1/ 2) + t.inst_merged.cp_b.auto_0 : 100.0% ( 1/ 1) + t.inst_merged.cp_b.auto_1 : 0.0% ( 0/ 1) t.instance_weight : 100.0% ( 3/ 4) t.instance_weight.cp_a : 50.0% ( 1/ 2) t.instance_weight.cp_a.auto_0 : 100.0% ( 1/ 1) @@ -85,6 +92,13 @@ Covergroup Coverage Summary: t.threshold.cp : 50.0% ( 1/ 2) t.threshold.cp.auto_0 : 100.0% ( 1/ 1) t.threshold.cp.auto_1 : 0.0% ( 0/ 1) + t.type_merged : 100.0% ( 3/ 4) + t.type_merged.cp_a : 50.0% ( 1/ 2) + t.type_merged.cp_a.auto_0 : 100.0% ( 1/ 1) + t.type_merged.cp_a.auto_1 : 0.0% ( 0/ 1) + t.type_merged.cp_b : 100.0% ( 2/ 2) + t.type_merged.cp_b.auto_0 : 100.0% ( 1/ 1) + t.type_merged.cp_b.auto_1 : 100.0% ( 1/ 1) t.type_weight : 75.0% ( 3/ 4) t.type_weight.cp_a : 50.0% ( 1/ 2) t.type_weight.cp_a.auto_0 : 100.0% ( 1/ 1) diff --git a/test_regress/t/t_vlcov_covergroup_report.v b/test_regress/t/t_vlcov_covergroup_report.v index 43860e3d6..f91a99fae 100644 --- a/test_regress/t/t_vlcov_covergroup_report.v +++ b/test_regress/t/t_vlcov_covergroup_report.v @@ -61,6 +61,22 @@ module t; } cp_b: coverpoint b; endgroup + // Merging the instances, type_option.weight weighs the coverpoints, as in get_coverage(): 100 + covergroup type_merged with function sample (bit a, bit b); + type_option.merge_instances = 1; + cp_a: coverpoint a { + type_option.weight = 0; + } + cp_b: coverpoint b; + endgroup + // And option.weight does not; instances hitting distinct bins merge as in get_coverage(): 75 + covergroup inst_merged with function sample (bit a, bit b); + type_option.merge_instances = 1; + cp_a: coverpoint a { + option.weight = 0; + } + cp_b: coverpoint b; + endgroup // A bin is covered once hit option.at_least times: 50 covergroup threshold with function sample (bit a); cp: coverpoint a { @@ -176,6 +192,9 @@ module t; weighted weighted_inst = new; instance_weight instance_weight_inst = new; type_weight type_weight_inst = new; + type_merged type_merged_inst = new; + inst_merged inst_merged_first = new; + inst_merged inst_merged_second = new; threshold threshold_inst = new; crossed crossed_inst = new; empty empty_inst = new; @@ -199,6 +218,10 @@ module t; instance_weight_inst.sample(0, 1); type_weight_inst.sample(0, 0); type_weight_inst.sample(0, 1); + type_merged_inst.sample(0, 0); + type_merged_inst.sample(0, 1); + inst_merged_first.sample(0, 0); + inst_merged_second.sample(1, 0); threshold_inst.sample(0); threshold_inst.sample(0); threshold_inst.sample(1); @@ -227,6 +250,8 @@ module t; `checkr(weighted_inst.get_coverage(), 100.0); `checkr(instance_weight_inst.get_coverage(), 100.0); `checkr(type_weight_inst.get_coverage(), 75.0); + `checkr(type_merged_inst.get_coverage(), 100.0); + `checkr(inst_merged_first.get_coverage(), 75.0); `checkr(threshold_inst.get_coverage(), 50.0); `checkr(crossed_inst.get_coverage(), 30.0); `checkr(empty_inst.get_coverage(), 0.0);