diff --git a/include/verilated.h b/include/verilated.h index 2190f6e06..1ffdced3e 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -305,6 +305,34 @@ public: #endif }; +// Internals: VlFileLineDebug stores a SystemVerilog source code location. Used in +// VlCoroutineHandle for debugging purposes, and to locate covergroup weight errors. + +class VlFileLineDebug final { + // MEMBERS +#ifdef VL_DEBUG + const char* m_filename = nullptr; // Filename from sources, nullptr for unlnown + int m_lineno = 0; // Line number from sources +#endif + +public: + // CONSTRUCTORS + VlFileLineDebug() = default; + VlFileLineDebug(const char* filename, int lineno) +#ifdef VL_DEBUG + : m_filename{filename} + , m_lineno{lineno} +#endif + { + } + + // METHODS +#ifdef VL_DEBUG + const char* filename() const { return m_filename; } + int lineno() const { return m_lineno; } +#endif +}; + class VlExecutionProfilerBase; //========================================================================= diff --git a/include/verilated_cov_model.h b/include/verilated_cov_model.h index 6d8b28f9d..2b502ebda 100644 --- a/include/verilated_cov_model.h +++ b/include/verilated_cov_model.h @@ -28,6 +28,8 @@ #include #include +class VlFileLineDebug; + // Per-bin classification. A bin's kind is which set it lives in (structural), // not a per-bin field. Only Normal feeds coverage(); the rest are recorded. // Enumerators are 'KIND_'-prefixed because the bare LRM terms collide with @@ -50,6 +52,9 @@ enum class VlCovBinKind : uint8_t { /// Bounded bin count, so random access by index is the primary usage. class VlCoverpointIf VL_NOT_FINAL { + // MEMBERS + int32_t m_weight = 1; // option.weight; never negative + public: // CONSTRUCTORS virtual ~VlCoverpointIf() = default; @@ -62,6 +67,12 @@ public: virtual std::string binName(uint32_t i) const = 0; // Bins covered / effective total (Normal set only) for the coverage calc virtual void coverageParts(double& covered, double& total) const = 0; + /// Weight of this item in its covergroup instance's coverage (option.weight, + /// IEEE 1800-2023 19.11) + int32_t weight() const { return m_weight; } + /// 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); }; #endif // Guard diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp index 50ee42bc4..0f3cff09e 100644 --- a/include/verilated_covergroup.cpp +++ b/include/verilated_covergroup.cpp @@ -1155,12 +1155,75 @@ void VlCoverCrossDyn::finalizeBins() { //============================================================================= // VlCovergroupInst +// IEEE 1800-2023 19.11: coverage is the weighted average of the contributions; with a zero +// denominator it is 0.0, or 100.0 when the covergroup's weight is zero +static double _vl_cov_calculate(double weighted, double weights, int32_t weight) VL_PURE { + if (weights == 0.0) return weight ? 0.0 : 100.0; + return weighted / weights; +} + +// IEEE 1800-2023 19.7: a weight shall be non-negative. A negative constant is rejected when +// verilating; a weight that is negative only at run time is reported as it is loaded, and +// counts as zero, so that coverage stays within 0..100. +static int32_t _vl_cov_load_weight(const char* optionp, IData value, + VlFileLineDebug fileline) VL_MT_SAFE { + const int32_t weight = static_cast(value); + if (VL_LIKELY(weight >= 0)) return weight; + const char* filep = ""; // VlFileLineDebug keeps the location only under VL_DEBUG + int line = 0; +#ifdef VL_DEBUG + filep = fileline.filename(); + line = fileline.lineno(); +#else + static_cast(fileline); +#endif + const std::string where = filep && filep[0] + ? std::string{filep} + ":" + std::to_string(line) + ": " + : std::string{}; + VL_PRINTF_MT("%%Error: %sCoverage option '%s' is set to negative value '%d';" + " weights must be non-negative (IEEE 1800-2023 19.7)\n", + where.c_str(), optionp, static_cast(weight)); + VL_STOP_MT(filep, line, ""); + return 0; +} + +void VlCoverpointIf::weight(uint32_t value, VlFileLineDebug fileline) { + m_weight = _vl_cov_load_weight("option.weight", value, fileline); +} + VlCoverCrossDyn* VlCovergroupInst::addCrossDyn() { VlCoverCrossDyn* const cxp = new VlCoverCrossDyn{}; m_items.emplace_back(cxp); return cxp; } +void VlCovergroupInst::loadWeight() { + // Only a new value, so that each negative value is reported once + if (!m_weightp || *m_weightp == m_loadedWeight) return; + m_loadedWeight = *m_weightp; + m_weight = _vl_cov_load_weight("option.weight", m_loadedWeight, m_fileline); +} + +std::pair VlCovergroupInst::coverageSums() const { + double weighted = 0.0; + double weights = 0.0; + for (const auto& itemp : m_items) { + double covered = 0.0; + double total = 0.0; + itemp->coverageParts(covered, total); + if (total == 0.0) continue; // No bins: excluded from both sums + weighted += itemp->weight() * (covered / total); + weights += itemp->weight(); + } + return {100.0 * weighted, weights}; +} + +double VlCovergroupInst::coverage() { + loadWeight(); + const std::pair sums = coverageSums(); + return _vl_cov_calculate(sums.first, sums.second, m_weight); +} + //============================================================================= // VlCovergroupType / VlCovRegistry @@ -1174,19 +1237,19 @@ VlCovergroupInst* VlCovergroupType::newInstance() { return instp; } -void VlCovergroupType::foldResidue(const VlCovergroupInst* instp) { - double covered = 0.0; - double total = 0.0; - instp->coverageParts(covered, total); +void VlCovergroupType::foldResidue(VlCovergroupInst* instp) { + 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, // and folds as 0%. - if (total == 0.0) return; - // TODO(P5): IEEE 1800-2023 19.5 defines covergroup coverage as the weighted - // mean of the per-item ratios, not the ratio of the summed parts. This - // matches what the generated get_inst_coverage() computes today, so that a - // live instance and the same instance one delta after death never disagree. - m_retired.sumCoverage += 100.0 * covered / total; + if (sums.second == 0.0) return; + // The same weighted average of the items as get_inst_coverage(), so that a live + // instance and the same instance one delta after death never disagree. With the + // weight last loaded: the object that lent option.weight is gone, and nothing may + // be reported here, as this can run after ~VerilatedContext (see ~VlCovRegistry). + const int32_t weight = instp->weight(); + m_retired.sumCoverage += weight * (sums.first / sums.second); + m_retired.sumWeight += weight; ++m_retired.count; } @@ -1231,9 +1294,28 @@ bool VlCovergroupType::anyAttached() const { return false; } +double VlCovergroupType::coverage(IData typeWeight, 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); + } + // Instances that have died still count: their contribution is the residue + double sumCoverage = m_retired.sumCoverage; + double sumWeight = m_retired.sumWeight; + for (const auto& instp : m_insts) { + if (instp->retained()) continue; // Already folded into the residue + instp->loadWeight(); + const std::pair sums = instp->coverageSums(); + if (sums.second == 0.0) continue; // A covergroup without coverage does not contribute + sumCoverage += instp->weight() * (sums.first / sums.second); + sumWeight += instp->weight(); + } + return _vl_cov_calculate(sumCoverage, sumWeight, m_typeWeight); +} + double VlCovergroupType::retiredCoverage() const { - if (m_retired.count == 0) return -1.0; - return m_retired.sumCoverage / static_cast(m_retired.count); + if (m_retired.count == 0 || m_retired.sumWeight == 0.0) return -1.0; + return m_retired.sumCoverage / m_retired.sumWeight; } // Defined here, not in verilated.cpp, so that the registry costs nothing in a model with no @@ -1252,13 +1334,23 @@ VlCovRegistry* VerilatedContext::covergroupRegistryp() VL_MT_SAFE { return static_cast(m_covergroupsp.get()); } -VlCovergroupInst* VlCovRegistry::newCovergroupInst(const char* typeName) { +VlCovergroupType* VlCovRegistry::findOrCreateType(const char* typeName) { VlCovergroupType*& typep = m_byName[typeName]; - if (!typep) { // First instance of this type + if (!typep) { // First use of this type m_types.emplace_back(new VlCovergroupType{}); typep = m_types.back().get(); } - return typep->newInstance(); + return typep; +} + +VlCovergroupInst* VlCovRegistry::newCovergroupInst(const char* typeName) { + return findOrCreateType(typeName)->newInstance(); +} + +double VlCovRegistry::typeCoverage(const char* typeName, IData typeWeight, + VlFileLineDebug fileline) { + // Also for a type never instantiated, whose node then remembers type_option.weight + return findOrCreateType(typeName)->coverage(typeWeight, 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 92cdf8541..69b031b62 100644 --- a/include/verilated_covergroup.h +++ b/include/verilated_covergroup.h @@ -41,6 +41,7 @@ #include #include #include +#include #include class VerilatedCovContext; @@ -490,6 +491,12 @@ class VlCovergroupInst final { uint32_t m_slot = 0; // Index into m_typep->m_insts; unlink-by-swap rewrites #endif uint32_t m_attachCount = 1; // SV handles bound here; 1 from construction + // option.weight of the SV object that created this node, while that object + // lives; borrowed through VlCovInstHandle::lendWeight(). + const IData* m_weightp = nullptr; + IData m_loadedWeight = 1; // Last option.weight loaded through m_weightp + int32_t m_weight = 1; // Weight in use, never negative; kept once the object is gone + VlFileLineDebug m_fileline; // Covergroup declaration, where a negative weight is reported bool m_retained = false; // VM_COVERAGE: dead, but kept for registered count pointers // Reads m_items to fold the residue; owns m_slot and m_retained. @@ -525,35 +532,49 @@ public: // here, and because it frees 'this'. bool attachDec() { return --m_attachCount == 0; } + // ---- instance weight (from VlCovInstHandle) ---- + void lendWeight(const IData* weightp, VlFileLineDebug fileline) { + m_weightp = weightp; + m_fileline = fileline; + loadWeight(); + } + // The lending object is being destroyed. Its members may already be gone, so + // the weight is not read again; the last loaded value stays in effect. + void unlendWeight(const IData* weightp) { + if (m_weightp == weightp) m_weightp = nullptr; + } + /// Load option.weight from the lending object. SV writes the member directly + /// (assignments, ref and output arguments, $value$plusargs, ...), so this is + /// where a new value is seen, and checked once: a negative weight is reported as + /// an error, and counts as zero. + void loadWeight(); + /// Weight of this instance in its type's coverage (option.weight, IEEE + /// 1800-2023 19.11.3), as last loaded; never negative. + int32_t weight() const { return m_weight; } + // ---- introspection ---- VlCovergroupType* typep() const { return m_typep; } uint32_t instId() const { return m_instId; } // True once retired but kept alive because the coverage database holds raw // pointers into this node's bin counts (VM_COVERAGE); see retire(). bool retained() const { return m_retained; } - // Sum of the instance's items' covered/total bin counts. Matches what the - // generated get_inst_coverage() computes; see foldResidue(). - void coverageParts(double& covered, double& total) const { - covered = 0.0; - total = 0.0; - for (const auto& itemp : m_items) { - double c = 0.0; - double t = 0.0; - itemp->coverageParts(c, t); - covered += c; - total += t; - } - } + /// IEEE 1800-2023 19.11 sums over the items whose coverage has a nonzero + /// denominator: {the sum of each item's option.weight times its coverage + /// (0..100), the sum of those weights}. + std::pair coverageSums() const; + /// Instance coverage, as returned by get_inst_coverage(), in 0..100. + double coverage(); }; //============================================================================= // VlCovRetiredAvg /// Per-type residue: what survives an instance's death. Fixed size, so it does -/// not grow with churn. Weight is 1 everywhere until option.weight is plumbed. +/// not grow with churn. Each instance contributes with its option.weight. struct VlCovRetiredAvg final { uint64_t count = 0; // Retired instances that contributed (nonzero denominator) - double sumCoverage = 0.0; // Sigma of per-instance coverage, each in 0..100 + double sumCoverage = 0.0; // Sigma of per-instance weight * coverage (0..100) + double sumWeight = 0.0; // Sigma of per-instance weight }; //============================================================================= @@ -569,11 +590,13 @@ 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 + 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. - void foldResidue(const VlCovergroupInst* instp); + void foldResidue(VlCovergroupInst* instp); public: // CONSTRUCTORS @@ -589,6 +612,12 @@ 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); // ---- introspection ---- // Test and debug only; generated code never calls these, and SV reaches them @@ -603,7 +632,8 @@ public: uint32_t createdInstanceCount() const { return m_createdInsts; } // Instances that have died and contributed to the residue. uint32_t retiredInstanceCount() const { return static_cast(m_retired.count); } - // Mean coverage over the retired instances only, in 0..100; -1.0 if none. + // Weighted mean coverage over the retired instances only, in 0..100; -1.0 if + // none contributed or their weights sum to zero. double retiredCoverage() const; }; @@ -621,6 +651,7 @@ class VlCovRegistry final : public VerilatedVirtualBase { // PRIVATE METHODS VlCovergroupType* findType(const char* typeName) const; // nullptr if unknown + VlCovergroupType* findOrCreateType(const char* typeName); public: // CONSTRUCTORS @@ -633,6 +664,9 @@ public: // generated covergroup class name, already --protect-ids obfuscated, and is // the same string that keys the coverage database's hier/page. VlCovergroupInst* newCovergroupInst(const char* typeName); + /// 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); // ---- introspection (see VlCovergroupType) ---- // typeName is the obfuscated generated name, so a test using these under @@ -658,18 +692,21 @@ public: class VlCovInstHandle final { // MEMBERS VlCovergroupInst* m_p = nullptr; // Attach-counted; the registry owns the node + const IData* m_weightp = nullptr; // Owning object's option.weight, if lent to m_p // PRIVATE METHODS // Drop one attach count, retiring the node if that was the last handle. // Nothing may touch instp afterwards: retire() may have freed it. - static void release(VlCovergroupInst* instp) { + static void release(VlCovergroupInst* instp, const IData* weightp) { if (VL_UNCOVERABLE(!instp)) return; // Never attach()ed; codegen always does + instp->unlendWeight(weightp); if (instp->attachDec()) instp->typep()->retire(instp); } public: // CONSTRUCTORS VlCovInstHandle() = default; + // The copy's owning object lends no weight; the node keeps reading the lender's. VlCovInstHandle(const VlCovInstHandle& o) : m_p{o.m_p} { if (VL_UNCOVERABLE(!m_p)) return; // Unbound source; see release above @@ -678,12 +715,18 @@ public: // Deleted, not implemented: nothing generates an assignment, and the // implicit one would copy m_p raw -- no attachInc, no release. VlCovInstHandle& operator=(const VlCovInstHandle&) = delete; - ~VlCovInstHandle() { release(m_p); } + ~VlCovInstHandle() { release(m_p, m_weightp); } // METHODS // Bind to a freshly created node, taking over the attach count of 1 it was // created with. Called once, from the generated covergroup constructor. void attach(VlCovergroupInst* p) { m_p = p; } + // Let the node read the owning object's option.weight until this handle is + // destroyed. Called once, from the generated constructor, after attach(). + void lendWeight(const IData* weightp, VlFileLineDebug fileline) { + m_weightp = weightp; + m_p->lendWeight(weightp, fileline); + } VlCovergroupInst* p() const { return m_p; } }; diff --git a/include/verilated_std.sv b/include/verilated_std.sv index 087454228..4d3f04e92 100644 --- a/include/verilated_std.sv +++ b/include/verilated_std.sv @@ -253,7 +253,7 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc // them as such currently, so named with a unique prefix typedef struct { string name; - int weight; + int weight = 1; int goal; string comment; int at_least; @@ -266,7 +266,7 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc } vl_covergroup_options_t; typedef struct { - int weight; + int weight = 1; int goal; string comment; int at_least; @@ -275,7 +275,7 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc } vl_coverpoint_options_t; typedef struct { - int weight; + int weight = 1; int goal; string comment; int at_least; @@ -284,7 +284,7 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc } vl_cross_options_t; typedef struct { - int weight; + int weight = 1; int goal; string comment; bit strobe; @@ -294,14 +294,14 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc } vl_covergroup_type_options_t; typedef struct { - int weight; + int weight = 1; int goal; string comment; real real_interval; } vl_coverpoint_type_options_t; typedef struct { - int weight; + int weight = 1; int goal; string comment; } vl_cross_type_options_t; diff --git a/include/verilated_timing.h b/include/verilated_timing.h index 81025d9c9..b7d586cb8 100644 --- a/include/verilated_timing.h +++ b/include/verilated_timing.h @@ -64,35 +64,6 @@ // Placeholder for compiling with --protect-ids #define VL_UNKNOWN "" -//============================================================================= -// VlFileLineDebug stores a SystemVerilog source code location. Used in VlCoroutineHandle for -// debugging purposes. - -class VlFileLineDebug final { - // MEMBERS -#ifdef VL_DEBUG - const char* m_filename = nullptr; // Filename from sources, nullptr for unlnown - int m_lineno = 0; // Line number from sources -#endif - -public: - // CONSTRUCTORS - VlFileLineDebug() = default; - VlFileLineDebug(const char* filename, int lineno) -#ifdef VL_DEBUG - : m_filename{filename} - , m_lineno{lineno} -#endif - { - } - - // METHODS -#ifdef VL_DEBUG - const char* filename() const { return m_filename; } - int lineno() const { return m_lineno; } -#endif -}; - //============================================================================= // VlCoroutineHandle is a non-copyable (but movable) coroutine handle. On resume, the handle is // cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 8a479dd6f..9b36f3229 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -878,11 +878,12 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) { macro(COVERGROUP_ADD_SINGLE_NAMER, "addSingleNamer", false, "r+") \ macro(COVERGROUP_ATTACH, "attach", false, "r") \ macro(COVERGROUP_CLEAR_HIT_LIST, "clearHitList", false, "") \ - macro(COVERGROUP_COVERAGE_PARTS, "coverageParts", false, "TODO") \ + macro(COVERGROUP_COVERAGE, "coverage", false, "") \ macro(COVERGROUP_FINALIZE_BINS, "finalizeBins", false, "") \ macro(COVERGROUP_INCREMENT_BIN, "incrementBin", false, "r") \ macro(COVERGROUP_INIT, "init", false, "r+") \ macro(COVERGROUP_INST_P, "p", PURE, "") \ + macro(COVERGROUP_LEND_WEIGHT, "lendWeight", false, "rr") \ macro(COVERGROUP_RECORD_HIT, "recordHit", false, "r") \ macro(COVERGROUP_REGISTER_BINS, "registerBins", false, "rr") \ macro(COVERGROUP_SAMPLE, "sample", false, "") \ @@ -895,6 +896,7 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) { macro(COVERGROUP_SELECT_OR, "selectOr", false, "") \ macro(COVERGROUP_SELECT_RANGE, "selectRange", false, "rr") \ macro(COVERGROUP_SELECT_RANGE_W, "selectRangeW", false, "rr") \ + macro(COVERGROUP_TYPE_COVERAGE, "typeCoverage", false, "rrr") \ macro(COVERGROUP_VALUE_EXCLUDED, "valueExcluded", PURE, "r") \ macro(COVERGROUP_VALUE_EXCLUDED_W, "valueExcludedW", PURE, "r") \ macro(COVERGROUP_VALUE_FINALIZE, "valueFinalize", false, "") \ @@ -903,6 +905,7 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) { macro(COVERGROUP_VALUE_RELEASE, "valueRelease", false, "") \ macro(COVERGROUP_VALUE_TRANSITIONS, "valueTransitions", false, "r") \ macro(COVERGROUP_VALUE_TYPE, "valueType", false, "rr") \ + macro(COVERGROUP_WEIGHT, "weight", false, "rr") \ macro(DYN_AT_WRITE_APPEND, "atWriteAppend", false, "r") \ macro(DYN_AT_WRITE_APPEND_BACK, "atWriteAppendBack", false, "r") \ macro(DYN_CLEAR, "clear", false, "") \ diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 42c75a1ad..a960d7e79 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1214,17 +1214,24 @@ class AstCoverOption final : public AstNode { // Coverage-option assignment // @astgen op1 := valuep : AstNodeExpr const VCoverOptionType m_optType; // Option being assigned + const bool m_typeOption; // type_option (vs option) public: - AstCoverOption(FileLine* fl, VCoverOptionType optType, AstNodeExpr* valuep) + AstCoverOption(FileLine* fl, bool typeOption, VCoverOptionType optType, AstNodeExpr* valuep) : ASTGEN_SUPER_CoverOption(fl) - , m_optType{optType} { + , m_optType{optType} + , m_typeOption{typeOption} { this->valuep(valuep); } ASTGEN_MEMBERS_AstCoverOption; void dump(std::ostream& str) const override; void dumpJson(std::ostream& str) const override; VCoverOptionType optType() const { return m_optType; } + bool typeOption() const { return m_typeOption; } + bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START + const AstCoverOption* const asamep = VN_DBG_AS(samep, CoverOption); + return m_optType.m_e == asamep->m_optType.m_e && m_typeOption == asamep->m_typeOption; + } // LCOV_EXCL_STOP }; class AstCoverTransItem final : public AstNode { // Represents a single transition item: value or value[*N] or value[->N] or value[=N] diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index eee8b2671..f5203c98d 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1262,10 +1262,12 @@ void AstCoverInc::dumpJson(std::ostream& str) const { dumpJsonGen(str); } void AstCoverOption::dump(std::ostream& str) const { Super::dump(str); str << " " << m_optType.ascii(); + if (typeOption()) str << " [TYPEOPT]"; } void AstCoverOption::dumpJson(std::ostream& str) const { Super::dumpJson(str); str << ", \"optType\": \"" << m_optType.ascii() << "\""; + dumpJsonBoolFuncIf(str, typeOption); } void AstCoverOtherDecl::dump(std::ostream& str) const { Super::dump(str); diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index e013d23e8..25db6d73c 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -217,6 +217,7 @@ 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 struct EmbeddedEventTrigger final { FileLine* eventFl; // Clocking-event source location @@ -234,8 +235,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { }; std::set m_crossedCpNames; // Coverpoints referenced by a cross - std::vector m_cpVars; // VlCoverpoint member, one per coverpoint - std::vector m_crossVars; // VlCoverCross member, one per cross std::map m_cpVarMap; // Coverpoint name -> its VlCoverpoint member struct CrossBinValues final { AstCoverBin* binp; // Declaration owning this Normal bin @@ -264,14 +263,70 @@ class FunctionalCoverageVisitor final : public VNVisitor { VMemberMap m_memberMap; // Member names cached for fast lookup // METHODS + // The covergroup's 'option' or static 'type_option' member (V3LinkParse creates both) + AstVar* optionVar(bool typeOption) { + AstVar* const varp = VN_AS( + m_memberMap.findMember(m_covergroupp, typeOption ? "type_option" : "option"), Var); + UASSERT_OBJ(varp, m_covergroupp, "Covergroup missing option member"); + 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'"); + AstNodeExpr* const fromp = optionVarp->lifetime().isStatic() + ? new AstVarRef{fl, optionVarp, access} + : memberRef(fl, optionVarp, access); + AstStructSel* const selp = new AstStructSel{fl, fromp, "weight"}; + 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. + void lowerCovergroupOptions() { + for (AstCgOptionAssign* const optp : m_cgOptions) { + UASSERT_OBJ(optp->optType() == VCoverOptionType::WEIGHT, optp, + "Unexpected covergroup option reaching V3Covergroup"); + FileLine* const fl = optp->fileline(); + AstAssign* const assignp = new AstAssign{ + fl, newWeightSel(fl, optionVar(optp->typeOption()), VAccess::WRITE), + optp->valuep()->unlinkFrBack()}; + if (optp->typeOption()) { + m_covergroupp->addMembersp(new AstInitialStatic{fl, assignp}); + VL_DO_DANGLING(pushDeletep(optp->unlinkFrBack()), optp); + } else { + optp->replaceWith(assignp); + VL_DO_DANGLING(pushDeletep(optp), optp); + } + } + m_cgOptions.clear(); + } + + // 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. + 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()); + } + } + void processCovergroup() { UINFO(4, "Processing covergroup: " << m_covergroupp->name() << " with " << m_coverpoints.size() << " coverpoints and " << m_coverCrosses.size() << " crosses"); m_crossedCpNames.clear(); - m_cpVars.clear(); - m_crossVars.clear(); m_cpVarMap.clear(); m_cpBins.clear(); m_runtimePoints.clear(); @@ -280,6 +335,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_droppedCrosses.clear(); m_cgInstVarp = nullptr; + lowerCovergroupOptions(); + // Scan every cross item to record the coverpoints it references (the cross dimensions) // and to flag any cross naming a bare variable -- a would-be implicit coverpoint, which // Verilator does not synthesize. An unresolvable item drops only that one cross (with a @@ -356,13 +413,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Generate coverage computation code (even for empty covergroups). Bin registration // with the coverage database is handled per coverpoint/cross by their runtime // registerBins() calls (emitted in generateCoverpoint/generateCross). - - // TODO: Generate instance registry infrastructure for static get_coverage() - // This requires: - // - Static registry members (t_instances, s_mutex) - // - registerInstance() / unregisterInstance() methods - // - Proper C++ emission in EmitC backend - // For now, get_coverage() returns 0.0 (placeholder) generateCoverageComputationCode(); } @@ -465,6 +515,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { autoBinMaxOut = -1; // -1 = not set at coverpoint level for (AstNode* optionp = coverpointp->optionsp(); optionp; optionp = optionp->nextp()) { AstCoverOption* const optp = VN_AS(optionp, CoverOption); + // Weights may be non-constant; generateItemWeight() handles them + if (optp->optType() == VCoverOptionType::WEIGHT) continue; AstConst* const constp = VN_CAST(optp->valuep(), Const); if (!constp) { optp->valuep()->v3warn(COVERIGN, "Ignoring unsupported: non-constant 'option." @@ -475,8 +527,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { if (optp->optType() == VCoverOptionType::AT_LEAST) { atLeastOut = constp->toSInt(); } else { - // V3LinkParse only converts at_least/auto_bin_max coverpoint options into - // AstCoverOption (others are dropped there), so this is the only alternative. + // V3LinkParse only converts at_least/auto_bin_max/weight coverpoint options + // into AstCoverOption (others are dropped there), so this is the only + // alternative. UASSERT_OBJ(optp->optType() == VCoverOptionType::AUTO_BIN_MAX, optp, "Unexpected coverpoint option type reaching V3Covergroup"); autoBinMaxOut = constp->toSInt(); @@ -750,6 +803,10 @@ class FunctionalCoverageVisitor final : public VNVisitor { return typep; } + std::string covergroupProtectedName() const { + return VIdProtect::protectWordsIf(m_covergroupp->name(), v3Global.opt.protectIds()); + } + // Emit the covergroup's instance handle member and the constructor statement that creates // its node in the per-context coverage registry. Runs before any coverpoint or cross is // generated, so their runtimes can be added to the node as they are created. @@ -762,30 +819,37 @@ class FunctionalCoverageVisitor final : public VNVisitor { basicDType(fl, VBasicDTypeKwd::COVERGROUP_INSTHANDLE)}; m_covergroupp->addMembersp(m_cgInstVarp); - // The type node is keyed by the covergroup type name -- the same string that keys this - // covergroup's coverage-database hierarchy, so it is exactly as unique. Obfuscated the - // same way, so --protect-ids exposes no new identifier. - const std::string typeName - = VIdProtect::protectWordsIf(m_covergroupp->name(), v3Global.opt.protectIds()); m_constructorp->addStmtsp( itemCall(fl, m_cgInstVarp, VCMethod::COVERGROUP_ATTACH, {ctext(fl, "vlSymsp->_vm_contextp__->covergroupRegistryp()" "->newCovergroupInst(" - + quoted(typeName) + ")")}, + + quoted(covergroupProtectedName()) + ")")}, /*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)); + m_constructorp->addStmtsp(itemCall(fl, m_cgInstVarp, VCMethod::COVERGROUP_LEND_WEIGHT, + {weightAddrp, fileLineDebug(fl)}, /*usePtr=*/false) + ->makeStmt()); } - // Emit 'this->__Vcp_x = this->__Vcg_inst.p()->addCoverpoint();' (or addCross), which - // creates the item runtime in the instance node and borrows a pointer to it. - AstAssign* makeItemCreate(FileLine* fl, AstVar* itemVarp, VCMethod method) { + // A '__Vcg_inst.p()->()' call on the covergroup's instance node + AstCMethodHard* instanceCall(FileLine* fl, VCMethod method) { // '__Vcg_inst.p()' -- a value handle, so '.' not '->' AstCMethodHard* const instp = new AstCMethodHard{fl, memberRef(fl, m_cgInstVarp), VCMethod::COVERGROUP_INST_P}; instp->usePtr(false); instp->dtypeSetVoid(); // Opaque receiver; only ever the 'fromp' of the call below - AstCMethodHard* const createp = new AstCMethodHard{fl, instp, method}; - createp->usePtr(true); + AstCMethodHard* const callp = new AstCMethodHard{fl, instp, method}; + callp->usePtr(true); + return callp; + } + + // Emit 'this->__Vcp_x = this->__Vcg_inst.p()->addCoverpoint();' (or addCross), which + // creates the item runtime in the instance node and borrows a pointer to it. + AstAssign* makeItemCreate(FileLine* fl, AstVar* itemVarp, VCMethod method) { + AstCMethodHard* const createp = instanceCall(fl, method); createp->dtypep(itemVarp->dtypep()); return new AstAssign{fl, memberRef(fl, itemVarp, VAccess::WRITE), createp}; } @@ -954,8 +1018,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // A literal C++ argument with no AST equivalent: a 'const char*' string literal (an SV // string AstConst emits '"..."s', a std::string temporary the runtime cannot borrow), a - // VlCovBinKind enum token, a constant selection-word initializer list, or a '__V' temporary - // declared by the enclosing AstCStmt. + // VlCovBinKind enum token, a constant selection-word initializer list, a VlFileLineDebug, or + // a '__V' temporary declared by the enclosing AstCStmt. static AstCExpr* ctext(FileLine* fl, const std::string& text) { return new AstCExpr{fl, text}; } @@ -967,6 +1031,14 @@ class FunctionalCoverageVisitor final : public VNVisitor { return "\"" + V3OutFormatter::quoteNameControls(text) + "\""; } + // A 'VlFileLineDebug' argument: where the runtime reports an error about fl's construct + static AstCExpr* fileLineDebug(FileLine* fl) { + const std::string filename + = VIdProtect::protectIf(fl->filename(), v3Global.opt.protectIds()); + return ctext(fl, "VlFileLineDebug{" + quoted(filename) + ", " + + std::to_string(fl->lineno()) + "}"); + } + // Individual equality targets of an array bin (bins b[] = {values/ranges}), in order. // An open-ended bound ('$', AstUnbounded) resolves to the coverpoint domain: '[lo:$]' // covers [lo:maxVal] and '[$:hi]' covers [0:hi]. One target is produced per value; a @@ -1135,12 +1207,12 @@ class FunctionalCoverageVisitor final : public VNVisitor { AstVar* const cpVarp = new AstVar{fl, VVarType::MEMBER, "__Vcp_" + coverpointp->name(), coverpointDType(fl, static_cast(hitBound))}; m_covergroupp->addMembersp(cpVarp); - m_cpVars.push_back(cpVarp); m_cpVarMap[coverpointp->name()] = cpVarp; m_cpBins.emplace(cpVarp, CoverpointBins{}); m_cpBins.at(cpVarp).exprp = exprp; // Create the runtime in the instance node first; everything below configures it. m_constructorp->addStmtsp(makeItemCreate(fl, cpVarp, VCMethod::COVERGROUP_ADD_COVERPOINT)); + generateItemWeight(fl, cpVarp, coverpointp->optionsp()); // A cross reads this coverpoint's hit list, so clear it at the start of the // coverpoint's sample() contribution (before any incrementBin appends to it). @@ -1580,18 +1652,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { } } - // "{ double __Vc = 0.0; double __Vt = 0.0; ->coverageParts(__Vc, __Vt); - // __Vcov += __Vc; __Vtot += __Vt; }" -- one item's contribution to get_coverage(). - // The out-param temporaries make this a block, so only the call itself is a node. - AstCStmt* makeCoveragePartsBlock(FileLine* fl, AstVar* itemVarp) { - AstCStmt* const cs = new AstCStmt{fl}; - cs->add("{ double __Vc = 0.0; double __Vt = 0.0; "); - cs->add(itemCall(fl, itemVarp, VCMethod::COVERGROUP_COVERAGE_PARTS, - {ctext(fl, "__Vc"), ctext(fl, "__Vt")})); - cs->add("; __Vcov += __Vc; __Vtot += __Vt; }"); - return cs; - } - // Append a "{ VlCoverpoint* __Vcx_cps[] = {cp0, cp1, ...}; }" statement. The brace // and the temporary array stay literal text -- a CMethodHard is one call, not a block -- // but callp itself carries the member, method and '->'. Construction only: init() copies @@ -2435,10 +2495,10 @@ class FunctionalCoverageVisitor final : public VNVisitor { = new AstVar{fl, VVarType::MEMBER, "__Vcx_" + crossp->name(), crossDType(fl, static_cast(dims), layout, dynamic)}; m_covergroupp->addMembersp(cxVarp); - m_crossVars.push_back(cxVarp); m_constructorp->addStmtsp(makeItemCreate(fl, cxVarp, dynamic ? VCMethod::COVERGROUP_ADD_CROSS_DYN : VCMethod::COVERGROUP_ADD_CROSS)); + generateItemWeight(fl, cxVarp, crossp->optionsp()); // Constructor: init (after the coverpoints, which generate earlier) then registration. // Obfuscate the hierarchy/filename/page under --protect-ids as for coverpoints above. @@ -2661,56 +2721,35 @@ class FunctionalCoverageVisitor final : public VNVisitor { // have added new members since the last scan, so clear before re-querying. m_memberMap.clear(); - // Find get_coverage() and get_inst_coverage() methods - AstFunc* const getCoveragep - = VN_CAST(m_memberMap.findMember(m_covergroupp, "get_coverage"), Func); + // 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. AstFunc* const getInstCoveragep - = VN_CAST(m_memberMap.findMember(m_covergroupp, "get_inst_coverage"), Func); + = 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(); + getInstCoveragep->addStmtsp(new AstAssign{ + instFl, new AstVarRef{instFl, VN_AS(getInstCoveragep->fvarp(), Var), VAccess::WRITE}, + instCallp}); - // Generate code for get_inst_coverage() (an empty covergroup returns 100%). - generateCoverageMethodBody(getInstCoveragep); - - // Generate code for get_coverage() (type-level) - // NOTE: Full type-level coverage requires instance tracking infrastructure - // For now, return 0.0 as a placeholder - AstVar* const coverageReturnVarp = VN_AS(getCoveragep->fvarp(), Var); - // TODO: Implement proper type-level coverage aggregation - // This requires tracking all instances and averaging their coverage - // For now, return 0.0 + // 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. + 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{ - getCoveragep->fileline(), - new AstVarRef{getCoveragep->fileline(), coverageReturnVarp, VAccess::WRITE}, - new AstConst{getCoveragep->fileline(), AstConst::RealDouble{}, 0.0}}); - UINFO(4, " Added placeholder get_coverage() (returns 0.0)"); - } - - void generateCoverageMethodBody(AstFunc* funcp) { - FileLine* const fl = funcp->fileline(); - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - - // Every coverpoint and cross holds its bins in the runtime (VlCoverpoint/VlCoverCross). - // Sum their covered/total contributions via coverageParts (Normal bins only; ignore, - // illegal, and default are excluded per LRM 19.5). A covergroup with no coverpoints - // (and hence no crosses) has nothing to cover and reports 100%. - if (m_cpVars.empty()) { - funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, - new AstConst{fl, AstConst::RealDouble{}, 100.0}}); - return; - } - AstCStmt* const headp = new AstCStmt{fl}; - headp->add("double __Vcov = 0.0; double __Vtot = 0.0;"); - funcp->addStmtsp(headp); - for (AstVar* const cpVarp : m_cpVars) { - funcp->addStmtsp(makeCoveragePartsBlock(fl, cpVarp)); - } - // Crosses contribute the same covered/total ratio as their per-tuple bins. - for (AstVar* const cxVarp : m_crossVars) { - funcp->addStmtsp(makeCoveragePartsBlock(fl, cxVarp)); - } - AstCStmt* const retp = new AstCStmt{fl}; - retp->add(new AstVarRef{fl, returnVarp, VAccess::WRITE}); - retp->add(" = (__Vtot != 0.0) ? (100.0 * __Vcov / __Vtot) : 100.0;"); - funcp->addStmtsp(retp); + typeFl, new AstVarRef{typeFl, VN_AS(getCoveragep->fvarp(), Var), VAccess::WRITE}, + typeCallp}); } // VISITORS @@ -2954,6 +2993,11 @@ class FunctionalCoverageVisitor final : public VNVisitor { for (AstCoverCross* const crossp : m_coverCrosses) { VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp); } + // Options not lowered: the covergroup was not processed + for (AstCgOptionAssign* const optp : m_cgOptions) { + VL_DO_DANGLING(pushDeletep(optp->unlinkFrBack()), optp); + } + m_cgOptions.clear(); } class FormalRefVisitor final : public VNVisitor { @@ -3093,6 +3137,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { }; for (AstCoverpoint* const cpp : m_coverpoints) scan(cpp); for (AstCoverCross* const crossp : m_coverCrosses) scan(crossp); + for (AstCgOptionAssign* const optp : m_cgOptions) scan(optp); if (invalidp || !offenderp) return invalidp; UASSERT_OBJ(m_embeddedVarp, m_covergroupp, "Embedded covergroup variable not found"); @@ -3140,6 +3185,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { VL_RESTORER_CLEAR(m_coverpoints); VL_RESTORER_CLEAR(m_coverpointMap); VL_RESTORER_CLEAR(m_coverCrosses); + VL_RESTORER_CLEAR(m_cgOptions); m_covergroupp = nodep; m_embeddedVarp = findEmbeddedCovergroupVar(); m_sampleFuncp = nullptr; @@ -3255,6 +3301,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { iterateChildren(nodep); } + // V3Width leaves only the covergroup-level weights, for lowerCovergroupOptions() + void visit(AstCgOptionAssign* nodep) override { m_cgOptions.push_back(nodep); } + void visit(AstNode* nodep) override { iterateChildren(nodep); } public: diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 1a58ef0d1..555bb3e03 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -328,6 +328,20 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { } puts(";\n"); } + // An option or type_option assignment, from a covergroup, coverpoint, or cross + void emitCoverageOption(AstNode* nodep, bool typeOption, VCoverOptionType optType, + AstNodeExpr* valuep) { + putfs(nodep, + std::string{typeOption ? "type_option." : "option."} + optType.ascii() + " = "); + iterateConst(valuep); + puts(";\n"); + } + void visit(AstCgOptionAssign* nodep) override { + emitCoverageOption(nodep, nodep->typeOption(), nodep->optType(), nodep->valuep()); + } + void visit(AstCoverOption* nodep) override { + emitCoverageOption(nodep, nodep->typeOption(), nodep->optType(), nodep->valuep()); + } void visit(AstCoverBin* nodep) override { switch (nodep->binsType()) { case VCoverBinsType::BINS_IGNORE: putfs(nodep, "ignore_bins "); break; @@ -403,8 +417,9 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { iterateConst(nodep->iffp()); puts(")"); } - if (nodep->binsp()) { + if (nodep->binsp() || nodep->optionsp()) { puts(" {\n"); + iterateAndNextConstNull(nodep->optionsp()); iterateAndNextConstNull(nodep->binsp()); puts("}\n"); } else { diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 9c9391838..04dc537a6 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -1213,12 +1213,14 @@ class LinkParseVisitor final : public VNVisitor { nodep->addMembersp(varp); } { + // IEEE 1800-2023 19.10: type_option is a static member, shared by all instances AstVar* const varp = new AstVar{nodep->fileline(), VVarType::MEMBER, "type_option", VFlagChildDType{}, new AstRefDType{nodep->fileline(), "vl_covergroup_type_options_t", new AstClassOrPackageRef{nodep->fileline(), "std", nullptr, nullptr}, nullptr}}; + varp->lifetime(VLifetime::STATIC_EXPLICIT); nodep->addMembersp(varp); } @@ -1421,8 +1423,10 @@ class LinkParseVisitor final : public VNVisitor { if (dropDeprecatedCoverageOption(optp)) continue; optp->unlinkFrBack(); if (optp->optType() == VCoverOptionType::AT_LEAST - || optp->optType() == VCoverOptionType::AUTO_BIN_MAX) { - nodep->addOptionsp(new AstCoverOption{optp->fileline(), optp->optType(), + || optp->optType() == VCoverOptionType::AUTO_BIN_MAX + || optp->optType() == VCoverOptionType::WEIGHT) { + nodep->addOptionsp(new AstCoverOption{optp->fileline(), optp->typeOption(), + optp->optType(), optp->valuep()->cloneTree(false)}); } else { optp->v3warn(COVERIGN, @@ -1485,12 +1489,14 @@ class LinkParseVisitor final : public VNVisitor { if (dropDeprecatedCoverageOption(optp)) continue; itemp->unlinkFrBack(); const VCoverOptionType optType = optp->optType(); - optp->v3warn(COVERIGN, - "Ignoring unsupported coverage cross option: " + optp->prettyNameQ()); + if (!(optType == VCoverOptionType::WEIGHT)) { + optp->v3warn(COVERIGN, + "Ignoring unsupported coverage cross option: " + optp->prettyNameQ()); + } // Always preserve the option node so V3Coverage can track its source line // for coverage annotation, even when the option itself is unsupported. - nodep->addOptionsp( - new AstCoverOption{optp->fileline(), optType, optp->valuep()->cloneTree(false)}); + nodep->addOptionsp(new AstCoverOption{optp->fileline(), optp->typeOption(), optType, + optp->valuep()->cloneTree(false)}); VL_DO_DANGLING(optp->deleteTree(), optp); } iterateChildren(nodep); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 7cd2bb912..1fe5f3367 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2075,6 +2075,32 @@ class WidthVisitor final : public VNVisitor { assertAtExpr(nodep); if (m_vup->prelim()) iterateCheckSizedSelf(nodep, "LHS", nodep->lhsp(), SELF, BOTH); } + // Width a 'weight' coverage option as the signed 'int' member it sets (IEEE 1800-2023 + // 19.10); a weight is a non-negative integral value, constant for type_option (19.7) + template + void widthCoverWeight(T_Option* nodep) { + iterateCheckSigned32(nodep, "weight", nodep->valuep(), BOTH); + V3Const::constifyEdit(nodep->valuep()); + AstNodeExpr* const valuep = nodep->valuep(); + const std::string optName + = std::string{nodep->typeOption() ? "type_option" : "option"} + ".weight"; + if (const AstConst* const constp = VN_CAST(valuep, Const)) { + if (!constp->num().isNegative()) return; + valuep->v3error("Coverage option '" << optName << "' is set to negative value '" + << constp->toSInt() + << "'; weights must be non-negative" + " (IEEE 1800-2023 19.7)"); + } else if (nodep->typeOption()) { + valuep->v3error("Coverage option '" << optName + << "' requires a constant expression" + " (IEEE 1800-2023 19.7.1)"); + } else { + return; + } + // Continue with the default weight + valuep->replaceWith(new AstConst{valuep->fileline(), AstConst::Signed32{}, 1}); + VL_DO_DANGLING(pushDeletep(valuep), valuep); + } void visit(AstCgOptionAssign* nodep) override { // Recursive function widthing can reach a covergroup constructor without first visiting // its class, so find the owning covergroup structurally instead of using visit context. @@ -2082,6 +2108,12 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(cgClassp && cgClassp->isCovergroup(), nodep, "Covergroup option is not under a covergroup class"); + // V3Covergroup stores the weight into the covergroup's option or type_option + if (nodep->optType() == VCoverOptionType::WEIGHT) { + widthCoverWeight(nodep); + return; + } + // Extract covergroup option values and store in AstClass before deleting. if (nodep->optType() == VCoverOptionType::AUTO_BIN_MAX) { // By V3Width time, V3Param has already folded any parameter references. @@ -2094,12 +2126,27 @@ class WidthVisitor final : public VNVisitor { nodep->valuep()->v3warn(COVERIGN, "Ignoring unsupported: non-constant " "'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"); + } } - // Add more options here as needed (weight, goal, at_least, per_instance, comment) + // Add more options here as needed (goal, at_least, per_instance, comment) // Delete the assignment node (we've extracted the value) VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } + void visit(AstCoverOption* nodep) override { + if (nodep->optType() == VCoverOptionType::WEIGHT) { + widthCoverWeight(nodep); + return; + } + userIterateChildren(nodep, nullptr); + } void visit(AstCoverCross* nodep) override { userIterateAndNext(nodep->itemsp(), nullptr); if (nodep->iffp()) iterateCheckBool(nodep, "iff condition", nodep->iffp(), BOTH); diff --git a/test_regress/t/t_covergroup_auto_bin_max.v b/test_regress/t/t_covergroup_auto_bin_max.v index 33ad76229..847611f94 100644 --- a/test_regress/t/t_covergroup_auto_bin_max.v +++ b/test_regress/t/t_covergroup_auto_bin_max.v @@ -138,7 +138,7 @@ module t; cg7_inst.sample(); // [-4:-3] sdata3 = 3; cg7_inst.sample(); // [0:3] - `checkr(cg7_inst.get_inst_coverage(), 100.0 * 2.0 / 3.0); + `checkr(cg7_inst.get_inst_coverage(), 100.0 * (2.0 / 3.0)); sdata3 = 0; cg7_inst.sample(); // [0:3] sdata3 = -1; diff --git a/test_regress/t/t_covergroup_bin_counts.v b/test_regress/t/t_covergroup_bin_counts.v index 715106ad9..4b349e224 100644 --- a/test_regress/t/t_covergroup_bin_counts.v +++ b/test_regress/t/t_covergroup_bin_counts.v @@ -217,7 +217,8 @@ module t; cg_namers_inst.sample(); cg_empty_namers_inst.sample(); `checkr(cg_namers_inst.get_inst_coverage(), 100.0); - `checkr(cg_empty_namers_inst.get_inst_coverage(), 100.0); + // Only empty bins: nothing contributes, so a nonzero weight gives 0 (IEEE 1800-2023 19.11) + `checkr(cg_empty_namers_inst.get_inst_coverage(), 0.0); $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_covergroup_binsof.v b/test_regress/t/t_covergroup_binsof.v index e36d38feb..9b508724f 100644 --- a/test_regress/t/t_covergroup_binsof.v +++ b/test_regress/t/t_covergroup_binsof.v @@ -81,8 +81,13 @@ module t ( empty_cov.sample(1'(cyc / 2), 1'(cyc)); if (cyc < 6) trans_cov.sample(7'(cyc % 3), 1'(cyc / 3)); if (cyc == 0) begin - `checkr(cov.get_inst_coverage(), 20.0); - `checkr(auto_cov.get_inst_coverage(), 60.0); + // IEEE 1800-2023 19.11: the mean of the items' coverage. cp_a 1/4, cp_b 2/3, + // all_products 1/1, named 1/10, other_axis 2/9, array_bins 2/7, overlapping 2/8, + // guarded 0/11, and empty_selection 2/12 + `checkr(cov.get_inst_coverage(), + 100.0 * (1.0 / 4 + 2.0 / 3 + 1.0 + 1.0 / 10 + 2.0 / 9 + 2.0 / 7 + 2.0 / 8 + 0.0 + + 2.0 / 12) / 9); + `checkr(auto_cov.get_inst_coverage(), (50.0 + 50.0 + 100.0) / 3); end end else begin diff --git a/test_regress/t/t_covergroup_cross.v b/test_regress/t/t_covergroup_cross.v index 1078db87b..52de6600b 100644 --- a/test_regress/t/t_covergroup_cross.v +++ b/test_regress/t/t_covergroup_cross.v @@ -278,34 +278,35 @@ module t; initial begin // Sample 2-way: hit all 4 combinations - // cg2: 2 cp bins + 2 cp bins + 4 cross bins = 8 bins total (flat count) + // cg2: coverage is the mean of cp_addr (2 bins), cp_cmd (2 bins), and the cross (4 bins) + // (IEEE 1800-2023 19.11) addr = 0; cmd = 0; mode = 0; parity = 0; cg2_inst.sample(); // addr0 x read - `checkr(cg2_inst.get_inst_coverage(), 37.5); // 3/8: addr0, read, addr0_x_read + `checkr(cg2_inst.get_inst_coverage(), (50.0 + 50.0 + 25.0) / 3); // addr0, read, one cross addr = 1; cmd = 1; mode = 0; parity = 0; cg2_inst.sample(); // addr1 x write - `checkr(cg2_inst.get_inst_coverage(), 75.0); // 6/8: all cp bins + 2 cross bins + `checkr(cg2_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); // 2 cross bins addr = 0; cmd = 1; mode = 0; parity = 0; cg2_inst.sample(); // addr0 x write - `checkr(cg2_inst.get_inst_coverage(), 87.5); // 7/8: 3 cross bins hit + `checkr(cg2_inst.get_inst_coverage(), (100.0 + 100.0 + 75.0) / 3); // 3 cross bins addr = 1; cmd = 0; mode = 0; parity = 0; cg2_inst.sample(); // addr1 x read - `checkr(cg2_inst.get_inst_coverage(), 100.0); // 8/8: all 4 cross bins hit + `checkr(cg2_inst.get_inst_coverage(), 100.0); // all 4 cross bins hit // Sample 3-way: hit 4 of 12 combinations - // cg3: 3+2+2+12=19 bins; 4 cross bins hit -> 11/19=57.9% (not clean; no intermediate checkr) + // cg3: 4 of 12 cross bins hit (not clean; no intermediate checkr) addr = 0; cmd = 0; mode = 0; @@ -324,7 +325,7 @@ module t; cg3_inst.sample(); // addr0 x write x debug // Sample 4-way: hit 4 of 16 combinations - // cg4: 2+2+2+2+16=24 bins; 4 cross bins hit -> 12/24=50% + // cg4: four 2-bin coverpoints and a 16-bin cross; 4 cross bins hit addr = 0; cmd = 0; mode = 0; @@ -335,7 +336,7 @@ module t; mode = 0; parity = 1; cg4_inst.sample(); - `checkr(cg4_inst.get_inst_coverage(), 37.5); // 9/24: all cp bins + 2 cross bins + `checkr(cg4_inst.get_inst_coverage(), 72.5); // cp_mode 1/2, cross 2/16, others full addr = 0; cmd = 1; mode = 1; @@ -346,112 +347,111 @@ module t; mode = 1; parity = 1; cg4_inst.sample(); - `checkr(cg4_inst.get_inst_coverage(), 50.0); // 12/24: all cp bins + 4 cross bins + `checkr(cg4_inst.get_inst_coverage(), 85.0); // all cp bins, cross 4/16 - // Sample cg5 (cross with option.weight=2; weight is ignored in flat bin count) - // cg5: 2+2+4=8 bins; 2 cross bins hit -> 6/8=75% + // Sample cg5 (cross with option.weight=2): the cross counts twice in the mean addr = 0; cmd = 0; cg5_inst.sample(); - `checkr(cg5_inst.get_inst_coverage(), 37.5); // 3/8: addr0, read, addr0_x_read + `checkr(cg5_inst.get_inst_coverage(), (50.0 + 50.0 + 2 * 25.0) / 4); // addr0, read, 1/4 addr = 1; cmd = 1; cg5_inst.sample(); - `checkr(cg5_inst.get_inst_coverage(), 75.0); // 6/8: all cp bins + 2 cross bins + `checkr(cg5_inst.get_inst_coverage(), (100.0 + 100.0 + 2 * 50.0) / 4); // cross 2/4 // Sample cg_ignore: addr=3 is in ignore_bins so no cross bins for it - // cg_ignore: 2+2+4=8 bins total + // cg_ignore: 2-bin coverpoints and a 4-bin cross addr = 0; cmd = 0; cg_ignore_inst.sample(); // a0 x read - `checkr(cg_ignore_inst.get_inst_coverage(), 37.5); // 3/8 + `checkr(cg_ignore_inst.get_inst_coverage(), (50.0 + 50.0 + 25.0) / 3); addr = 1; cmd = 1; cg_ignore_inst.sample(); // a1 x write - `checkr(cg_ignore_inst.get_inst_coverage(), 75.0); // 6/8 + `checkr(cg_ignore_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); addr = 0; cmd = 1; cg_ignore_inst.sample(); // a0 x write - `checkr(cg_ignore_inst.get_inst_coverage(), 87.5); // 7/8 + `checkr(cg_ignore_inst.get_inst_coverage(), (100.0 + 100.0 + 75.0) / 3); addr = 1; cmd = 0; cg_ignore_inst.sample(); // a1 x read - `checkr(cg_ignore_inst.get_inst_coverage(), 100.0); // 8/8 + `checkr(cg_ignore_inst.get_inst_coverage(), 100.0); addr = 3; cmd = 0; cg_ignore_inst.sample(); // ignored (addr=3 in ignore_bins) `checkr(cg_ignore_inst.get_inst_coverage(), 100.0); // still 100% // Sample range-bin cross - // cg_range: 2+2+4=8 bins + // cg_range: 2-bin coverpoints and a 4-bin cross addr = 0; cmd = 0; cg_range_inst.sample(); // lo_range x read - `checkr(cg_range_inst.get_inst_coverage(), 37.5); // 3/8 + `checkr(cg_range_inst.get_inst_coverage(), (50.0 + 50.0 + 25.0) / 3); addr = 2; cmd = 1; cg_range_inst.sample(); // hi_range x write - `checkr(cg_range_inst.get_inst_coverage(), 75.0); // 6/8 + `checkr(cg_range_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); addr = 1; cmd = 1; cg_range_inst.sample(); // lo_range x write - `checkr(cg_range_inst.get_inst_coverage(), 87.5); // 7/8 + `checkr(cg_range_inst.get_inst_coverage(), (100.0 + 100.0 + 75.0) / 3); addr = 3; cmd = 0; cg_range_inst.sample(); // hi_range x read - `checkr(cg_range_inst.get_inst_coverage(), 100.0); // 8/8 + `checkr(cg_range_inst.get_inst_coverage(), 100.0); // Sample cg_at_least (option.at_least in cross body; Verilator uses at_least=1 for bins) - // cg_at_least: 2+2+4=8 bins; 2 cross bins hit (count=1, at_least effectively 1) -> 6/8=75% + // cg_at_least: 2 of 4 cross bins hit (count=1, at_least effectively 1) addr = 0; cmd = 0; cg_at_least_inst.sample(); // addr0 x read addr = 1; cmd = 1; cg_at_least_inst.sample(); // addr1 x write - `checkr(cg_at_least_inst.get_inst_coverage(), 75.0); + `checkr(cg_at_least_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); // Sample cg_goal (option.goal in cross body; does not affect hit counting) - // cg_goal: 2+2+4=8 bins; 2 cross bins hit -> 6/8=75% + // cg_goal: 2 of 4 cross bins hit addr = 0; cmd = 0; cg_goal_inst.sample(); // addr0 x read addr = 1; cmd = 1; cg_goal_inst.sample(); // addr1 x write - `checkr(cg_goal_inst.get_inst_coverage(), 75.0); + `checkr(cg_goal_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); // Sample cg_unsup_cross_opt - // cg_unsup_cross_opt: 2+2+4=8 bins; 2 cross bins hit -> 6/8=75% + // cg_unsup_cross_opt: 2 of 4 cross bins hit; the dropped cross contributes nothing addr = 0; cmd = 0; cg_unsup_cross_opt_inst.sample(); // addr0 x read addr = 1; cmd = 1; cg_unsup_cross_opt_inst.sample(); // addr1 x write - `checkr(cg_unsup_cross_opt_inst.get_inst_coverage(), 75.0); + `checkr(cg_unsup_cross_opt_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); // Sample cg_unnamed_cross - // cg_unnamed_cross: 2+2+4=8 bins; 2 cross bins hit -> 6/8=75% + // cg_unnamed_cross: 2 of 4 cross bins hit addr = 0; cmd = 0; cg_unnamed_cross_inst.sample(); // a0 x read addr = 1; cmd = 1; cg_unnamed_cross_inst.sample(); // a1 x write - `checkr(cg_unnamed_cross_inst.get_inst_coverage(), 75.0); + `checkr(cg_unnamed_cross_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); - // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) + // Sample cg_mixed: cp_addr, cp_cmd, and cp_solo have 2 bins each, and cross ab 4 addr = 0; cmd = 0; mode = 0; cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) - `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 + `checkr(cg_mixed_inst.get_inst_coverage(), (50.0 + 50.0 + 50.0 + 25.0) / 4); addr = 0; cmd = 1; mode = 1; cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) addr = 1; cmd = 0; mode = 0; cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) addr = 1; cmd = 1; mode = 1; cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) - `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 + `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) mode = 1; @@ -532,23 +532,23 @@ module t; `checkr(cg_noNormal_inst.get_inst_coverage(), 100.0); // 2/2 // Sample cg_trans: transition coverpoint crossed with a value coverpoint - // cg_trans: 1+2+2=5 bins; t01_x_v6 never completes -> 4/5=80% + // cg_trans: t01_x_v6 never completes, so only the cross is partial (1/2) // __Vprev_cp_t initializes to 0. state = 0; val = 5; cg_trans_inst.sample(); // prev=0,cur=0: no t01; v5 state = 1; val = 5; cg_trans_inst.sample(); // prev=0,cur=1: t01 completes; t01_x_v5 state = 0; val = 6; cg_trans_inst.sample(); // prev=1,cur=0: no t01; v6 (no cross) - `checkr(cg_trans_inst.get_inst_coverage(), 80.0); // 4/5: t01_x_v6 not hit + `checkr(cg_trans_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); // Sample cg_be: range bins over an ascending-declared coverpoint; cross 2x2 - // cg_be: 2+2+4=8 bins; endianness must not change results (mirror of cg_range) + // cg_be: endianness must not change results (mirror of cg_range) be_addr = 0; be_cmd = 0; cg_be_inst.sample(); // lo x read - `checkr(cg_be_inst.get_inst_coverage(), 37.5); // 3/8 + `checkr(cg_be_inst.get_inst_coverage(), (50.0 + 50.0 + 25.0) / 3); be_addr = 2; be_cmd = 1; cg_be_inst.sample(); // hi x write - `checkr(cg_be_inst.get_inst_coverage(), 75.0); // 6/8 + `checkr(cg_be_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); be_addr = 1; be_cmd = 1; cg_be_inst.sample(); // lo x write - `checkr(cg_be_inst.get_inst_coverage(), 87.5); // 7/8 + `checkr(cg_be_inst.get_inst_coverage(), (100.0 + 100.0 + 75.0) / 3); be_addr = 3; be_cmd = 0; cg_be_inst.sample(); // hi x read - `checkr(cg_be_inst.get_inst_coverage(), 100.0); // 8/8 + `checkr(cg_be_inst.get_inst_coverage(), 100.0); // Sample cg_be_arr: array range bin over an ascending-declared coverpoint; cross 2x2 // cg_be_arr: 2+2+4=8 bins; sample all combinations -> 100% diff --git a/test_regress/t/t_covergroup_cross_auto_max.v b/test_regress/t/t_covergroup_cross_auto_max.v index 9faa3d61e..d39a21d65 100644 --- a/test_regress/t/t_covergroup_cross_auto_max.v +++ b/test_regress/t/t_covergroup_cross_auto_max.v @@ -40,10 +40,11 @@ module t; for (int i = 0; i < 4; ++i) begin group_cov.sample(1'(i / 2), 1'(i)); cross_cov.sample(1'(i / 2), 1'(i)); + // IEEE 1800-2023 19.11: the mean of cp_a, cp_b, and the 4-bin cross case (i) - 0: check_coverage(37.5); - 1: check_coverage(62.5); - 2: check_coverage(87.5); + 0: check_coverage((50.0 + 50.0 + 25.0) / 3); + 1: check_coverage((50.0 + 100.0 + 50.0) / 3); + 2: check_coverage((100.0 + 100.0 + 75.0) / 3); 3: check_coverage(100.0); default: `stop; endcase diff --git a/test_regress/t/t_covergroup_cross_bins.v b/test_regress/t/t_covergroup_cross_bins.v index 3f76a55ab..93fd4658a 100644 --- a/test_regress/t/t_covergroup_cross_bins.v +++ b/test_regress/t/t_covergroup_cross_bins.v @@ -175,13 +175,15 @@ module t ( trans_cov.sample(1'(cyc), 1'(cyc / 2)); three_cov.sample(1'(cyc / 4), 1'(cyc / 2), 1'(cyc)); zero_cov.sample(1'(cyc / 2), 1'(cyc)); + // IEEE 1800-2023 19.11: the mean of cp_a (4 bins), cp_b (2 bins), and the crosses. + // Each cg_order cross keeps only 'combined'; cg_auto's cross keeps 4 automatic bins. if (cyc == 1) begin - `checkr(order_cov.get_inst_coverage(), 37.5); - `checkr(auto_cov.get_inst_coverage(), 30.0); + `checkr(order_cov.get_inst_coverage(), (25.0 + 100.0 + 0.0 + 0.0) / 4); + `checkr(auto_cov.get_inst_coverage(), (25.0 + 100.0 + 0.0) / 3); end if (cyc == 5) begin - `checkr(order_cov.get_inst_coverage(), 87.5); - `checkr(auto_cov.get_inst_coverage(), 90.0); + `checkr(order_cov.get_inst_coverage(), (75.0 + 100.0 + 100.0 + 100.0) / 4); + `checkr(auto_cov.get_inst_coverage(), (75.0 + 100.0 + 100.0) / 3); end end if (cyc < 65) begin diff --git a/test_regress/t/t_covergroup_default_bins.v b/test_regress/t/t_covergroup_default_bins.v index e64c938b7..87dd9fa7a 100644 --- a/test_regress/t/t_covergroup_default_bins.v +++ b/test_regress/t/t_covergroup_default_bins.v @@ -134,7 +134,9 @@ module t; data = 2; cg_inst.sample(); cg2_inst.sample(); - `checkr(cg2_inst.get_inst_coverage(), 100.0); // cg2 has 1 bin (default) -> 100% after first sample + // A default bin never counts toward coverage (IEEE 1800-2023 19.5), so nothing in cg2 + // contributes, and its nonzero weight gives 0 (IEEE 1800-2023 19.11) + `checkr(cg2_inst.get_inst_coverage(), 0.0); // Hit high bin data = 14; @@ -152,7 +154,7 @@ module t; cg_inst.sample(); cg2_inst.sample(); `checkr(cg_inst.get_inst_coverage(), 100.0); - `checkr(cg2_inst.get_inst_coverage(), 100.0); + `checkr(cg2_inst.get_inst_coverage(), 0.0); // Sample cg3: verify ignore/illegal bins do not contribute to coverage data = 2; diff --git a/test_regress/t/t_covergroup_empty.out b/test_regress/t/t_covergroup_empty.out deleted file mode 100644 index fad58640a..000000000 --- a/test_regress/t/t_covergroup_empty.out +++ /dev/null @@ -1,2 +0,0 @@ -Empty covergroup coverage: 100.000000% -*-* All Finished *-* diff --git a/test_regress/t/t_covergroup_empty.py b/test_regress/t/t_covergroup_empty.py index 36de5885f..4a5759c7f 100755 --- a/test_regress/t/t_covergroup_empty.py +++ b/test_regress/t/t_covergroup_empty.py @@ -10,6 +10,6 @@ test.scenarios('vlt_all') test.compile() -test.execute(expect_filename=test.golden_filename) +test.execute() test.passes() diff --git a/test_regress/t/t_covergroup_empty.v b/test_regress/t/t_covergroup_empty.v index 23afc93af..1cd9c33bf 100644 --- a/test_regress/t/t_covergroup_empty.v +++ b/test_regress/t/t_covergroup_empty.v @@ -4,7 +4,13 @@ // SPDX-License-Identifier: CC0-1.0 // Test: Empty covergroup (no coverpoints) -// Expected: Should compile, coverage should be 100% (nothing to cover) +// Expected: Should compile; with nothing to cover, a covergroup of nonzero weight reports 0% +// and one of zero weight 100% (IEEE 1800-2023 19.11) + +// verilog_format: off +`define stop $stop +`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on module t ( input clk @@ -17,7 +23,13 @@ module t ( // Intentionally empty endgroup + // Empty covergroup of zero weight + covergroup cg_empty_w0; + option.weight = 0; + endgroup + cg_empty cg_inst = new; + cg_empty_w0 cg_w0_inst = new; int cyc = 0; @@ -26,11 +38,14 @@ module t ( value <= value + 1; cg_inst.sample(); + cg_w0_inst.sample(); if (cyc == 5) begin real cov; cov = cg_inst.get_inst_coverage(); - $display("Empty covergroup coverage: %f%%", cov); + `checkr(cov, 0.0); + cov = cg_w0_inst.get_inst_coverage(); + `checkr(cov, 100.0); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_illegal_bins.v b/test_regress/t/t_covergroup_illegal_bins.v index e55249fdb..100a63166 100644 --- a/test_regress/t/t_covergroup_illegal_bins.v +++ b/test_regress/t/t_covergroup_illegal_bins.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // Test that illegal_bins are excluded from coverage (like ignore_bins). -// Also tests coverpoints where all bins are ignore/illegal - get_coverage returns 100.0. +// Also tests coverpoints where all bins are ignore/illegal - nothing contributes to coverage. // // This file ONLY is placed under the Creative Commons Public Domain, for // any use, without warranty, 2026 by Wilson Snyder. @@ -36,7 +36,7 @@ module t; } endgroup - // cg3: all bins are ignore_bins or illegal_bins - get_coverage returns 100.0 + // cg3: all bins are ignore_bins or illegal_bins - nothing contributes to coverage covergroup cg3; cp: coverpoint data {ignore_bins ign = {0, 1}; illegal_bins ill = {2, 3};} endgroup @@ -60,13 +60,14 @@ module t; cg2_inst.sample(); `checkr(cg2_inst.get_inst_coverage(), 100.0); - // Sample cg3 - values that only hit ignore_bins, never illegal_bins + // Sample cg3 - values that only hit ignore_bins, never illegal_bins. With no coverage + // to contribute, the covergroup's nonzero weight gives 0 (IEEE 1800-2023 19.11) data = 0; cg3_inst.sample(); - `checkr(cg3_inst.get_inst_coverage(), 100.0); + `checkr(cg3_inst.get_inst_coverage(), 0.0); data = 1; cg3_inst.sample(); - `checkr(cg3_inst.get_inst_coverage(), 100.0); + `checkr(cg3_inst.get_inst_coverage(), 0.0); $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_covergroup_member_event_unsup.v b/test_regress/t/t_covergroup_member_event_unsup.v index e2ff92710..c8d9cb6d2 100644 --- a/test_regress/t/t_covergroup_member_event_unsup.v +++ b/test_regress/t/t_covergroup_member_event_unsup.v @@ -35,6 +35,7 @@ module t; bit [3:0] value; covergroup cov_cplx @(posedge a or posedge mid.lvl.ev); + option.weight = 2; // Dropped along with the covergroup coverpoint value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} endgroup diff --git a/test_regress/t/t_covergroup_multi_inst.v b/test_regress/t/t_covergroup_multi_inst.v index fb16c95a4..888360dae 100644 --- a/test_regress/t/t_covergroup_multi_inst.v +++ b/test_regress/t/t_covergroup_multi_inst.v @@ -53,13 +53,15 @@ module t; // inst_none is never sampled. - // Today: raw covered/total over all bins of all items. - // inst_full 6/6 -> 100.0 - // inst_part 2/6 -> 33.333 (weighted average over items would be 37.5) - // inst_none 0/6 -> 0.0 + // Instance coverage is the average of the items (IEEE 1800-2023 19.11); type coverage + // is the average of the instances (IEEE 1800-2023 19.11.3), all at the default weight 1. + // inst_full cp_a 4/4, cp_b 2/2 -> 100.0 + // inst_part cp_a 1/4, cp_b 1/2 -> 37.5 (the raw covered/total sum would be 33.333) + // inst_none cp_a 0/4, cp_b 0/2 -> 0.0 `checkr(inst_full.get_inst_coverage(), 100.0); - `checkr(inst_part.get_inst_coverage(), 33.333); + `checkr(inst_part.get_inst_coverage(), 37.5); `checkr(inst_none.get_inst_coverage(), 0.0); + `checkr(cg_mi::get_coverage(), (100.0 + 37.5 + 0.0) / 3); $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_covergroup_unsup_ign2.out b/test_regress/t/t_covergroup_unsup_ign2.out index bf3faa158..71ef992ff 100644 --- a/test_regress/t/t_covergroup_unsup_ign2.out +++ b/test_regress/t/t_covergroup_unsup_ign2.out @@ -114,9 +114,6 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:98:18: Ignoring unsupported coverage cross option: 'comment' 98 | cross a, b { option.comment = "cross"; option.weight = 12; option.per_instance = 1; } | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:98:44: Ignoring unsupported coverage cross option: 'weight' - 98 | cross a, b { option.comment = "cross"; option.weight = 12; option.per_instance = 1; } - | ^~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:98:64: Ignoring unsupported coverage cross option: 'per_instance' 98 | cross a, b { option.comment = "cross"; option.weight = 12; option.per_instance = 1; } | ^~~~~~ @@ -150,6 +147,10 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:176:7: Unsupported: explicit coverage cross bins 176 | 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-WIDTHTRUNC: t/t_covergroup_unsup.v:76:5: Logical operator COVERPOINT 'a' expects 1 bit on the iff condition, but iff condition's VARREF 'b' generates 32 bits. : ... note: In instance 't' 76 | coverpoint a iff (b); diff --git a/test_regress/t/t_covergroup_weight.out b/test_regress/t/t_covergroup_weight.out new file mode 100644 index 000000000..a7d33739d --- /dev/null +++ b/test_regress/t/t_covergroup_weight.out @@ -0,0 +1,69 @@ +__vlAnonCG_cg_emb.cpv.auto_0: 1 +__vlAnonCG_cg_emb.cpv.auto_1: 0 +__vlAnonCG_cg_emb.cpv.auto_2: 0 +__vlAnonCG_cg_emb.cpv.auto_3: 0 +__vlAnonCG_cg_emb.cpz.zero: 1 +cg_all_zero.cpa.auto_0: 1 +cg_all_zero.cpa.auto_1: 1 +cg_all_zero.cpa.auto_2: 0 +cg_all_zero.cpa.auto_3: 0 +cg_all_zero.cpb.auto_0: 2 +cg_all_zero.cpb.auto_1: 0 +cg_all_zero.cpb.auto_2: 0 +cg_all_zero.cpb.auto_3: 0 +cg_all_zero.cpb.auto_4: 0 +cg_all_zero.cpb.auto_5: 0 +cg_all_zero.cpb.auto_6: 0 +cg_all_zero.cpb.auto_7: 0 +cg_cross.cpa.a0: 1 +cg_cross.cpa.a1: 1 +cg_cross.cpb.b0: 2 +cg_cross.cpb.b1: 0 +cg_cross.x.a0_x_b0 [cross]: 1 +cg_cross.x.a0_x_b1 [cross]: 0 +cg_cross.x.a1_x_b0 [cross]: 1 +cg_cross.x.a1_x_b1 [cross]: 0 +cg_group.cpa.auto_0: 1 +cg_group.cpa.auto_1: 1 +cg_group.cpa.auto_2: 0 +cg_group.cpa.auto_3: 0 +cg_group.cpb.auto_0: 2 +cg_group.cpb.auto_1: 0 +cg_group.cpb.auto_2: 0 +cg_group.cpb.auto_3: 0 +cg_group.cpb.auto_4: 0 +cg_group.cpb.auto_5: 0 +cg_group.cpb.auto_6: 0 +cg_group.cpb.auto_7: 0 +cg_item.cpa.auto_0: 1 +cg_item.cpa.auto_1: 1 +cg_item.cpa.auto_2: 0 +cg_item.cpa.auto_3: 0 +cg_item.cpb.auto_0: 2 +cg_item.cpb.auto_1: 0 +cg_item.cpb.auto_2: 0 +cg_item.cpb.auto_3: 0 +cg_item.cpb.auto_4: 0 +cg_item.cpb.auto_5: 0 +cg_item.cpb.auto_6: 0 +cg_item.cpb.auto_7: 0 +cg_item_zero.cpa.auto_0: 1 +cg_item_zero.cpa.auto_1: 1 +cg_item_zero.cpa.auto_2: 0 +cg_item_zero.cpa.auto_3: 0 +cg_item_zero.cpb.auto_0: 2 +cg_item_zero.cpb.auto_1: 0 +cg_item_zero.cpb.auto_2: 0 +cg_item_zero.cpb.auto_3: 0 +cg_item_zero.cpb.auto_4: 0 +cg_item_zero.cpb.auto_5: 0 +cg_item_zero.cpb.auto_6: 0 +cg_item_zero.cpb.auto_7: 0 +cg_type.cpa.auto_0: 1 +cg_type.cpa.auto_1: 1 +cg_type.cpa.auto_2: 1 +cg_type.cpa.auto_3: 0 +cg_type_w0.cpa.auto_0: 1 +cg_type_w0.cpa.auto_1: 0 +cg_type_w0.cpa.auto_2: 0 +cg_type_w0.cpa.auto_3: 0 diff --git a/test_regress/t/t_covergroup_weight.py b/test_regress/t/t_covergroup_weight.py new file mode 100755 index 000000000..9f6b5465d --- /dev/null +++ b/test_regress/t/t_covergroup_weight.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_weight.v b/test_regress/t/t_covergroup_weight.v new file mode 100644 index 000000000..e224f250c --- /dev/null +++ b/test_regress/t/t_covergroup_weight.v @@ -0,0 +1,207 @@ +// 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 option.weight and type_option.weight (IEEE 1800-2023 19.7, 19.11) + +// 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 checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + bit [1:0] a; // 4 automatic bins + bit [2:0] b; // 8 automatic bins + + // Instance coverage is the average of the items, weighted by their option.weight + covergroup cg_item; + cpa: coverpoint a { + option.weight = 3; + } + cpb: coverpoint b; + endgroup + + // A zero weight removes an item from the instance coverage + covergroup cg_item_zero; + cpa: coverpoint a { + option.weight = 0; + } + cpb: coverpoint b; + endgroup + + // Nothing contributes, so the covergroup's option.weight decides between 0 and 100 + covergroup cg_all_zero; + cpa: coverpoint a { + option.weight = 0; + } + cpb: coverpoint b { + option.weight = 0; + } + endgroup + + // Weights from a constructor argument and an expression, and a cross weight + covergroup cg_cross(int w); + option.weight = w + 1; + cpa: coverpoint a { + bins a0 = {0}; + bins a1 = {1}; + option.weight = w; + } + cpb: coverpoint b { + bins b0 = {0}; + bins b1 = {1}; + } + x: cross cpa, cpb{option.weight = 4;} + endgroup + + // The covergroup's own weights do not scale its coverage, and an item's + // type_option.weight only weighs type coverage merged over the instances + covergroup cg_group; + option.weight = 5; + type_option.weight = 2; + type_option.merge_instances = 0; + cpa: coverpoint a { + type_option.weight = 7; + } + cpb: coverpoint b { + type_option.weight = 0; + } + endgroup + + // Type coverage is the average of the instances, weighted by their option.weight + covergroup cg_type; + cpa: coverpoint a; + endgroup + + covergroup cg_type_w0; + type_option.weight = 0; + cpa: coverpoint a; + endgroup + + // Never constructed + covergroup cg_never; + cpa: coverpoint a; + endgroup + + covergroup cg_never_w0; + type_option.weight = 0; + cpa: coverpoint a; + endgroup + + // Embedded covergroup weighted by a member of the enclosing class + class Cls; + int m_weight; + covergroup cg_emb with function sample (bit [1:0] v); + option.weight = m_weight; + cpv: coverpoint v { + option.weight = m_weight; + } + cpz: coverpoint v { + bins zero = {0}; + } + endgroup + function new(int weight); + m_weight = weight; + cg_emb = new; + endfunction + endclass + + cg_item c_item = new; + cg_item_zero c_item_zero = new; + cg_all_zero c_all_zero = new; + cg_cross c_cross = new(3); + cg_group c_group = new; + cg_type t1, t2, t3; + cg_type_w0 u1; + Cls obj; + + initial begin + // Declaration values and defaults are visible (IEEE 1800-2023 19.10) + `checkd(c_item.option.weight, 1); + `checkd(c_item.type_option.weight, 1); + `checkd(c_cross.option.weight, 4); + `checkd(c_group.option.weight, 5); + `checkd(c_group.type_option.weight, 2); + `checkd(cg_group::type_option.weight, 2); + `checkd(cg_never_w0::type_option.weight, 0); + + a = 0; + b = 0; + c_item.sample(); + c_item_zero.sample(); + c_all_zero.sample(); + c_cross.sample(); + c_group.sample(); + a = 1; + c_item.sample(); + c_item_zero.sample(); + c_all_zero.sample(); + c_cross.sample(); + c_group.sample(); + + // cpa: 2/4 automatic bins, cpb: 1/8 automatic bins + `checkr(c_item.get_inst_coverage(), (3 * 50.0 + 12.5) / 4); + `checkr(c_item_zero.get_inst_coverage(), 12.5); + `checkr(c_all_zero.get_inst_coverage(), 0.0); + `checkr(c_group.get_inst_coverage(), (50.0 + 12.5) / 2); + // cpa: a0 and a1, cpb: b0, x: and + `checkr(c_cross.get_inst_coverage(), (3 * 100.0 + 50.0 + 4 * 50.0) / 8); + + // A procedural option.weight takes effect immediately + c_all_zero.option.weight = 0; + `checkr(c_all_zero.get_inst_coverage(), 100.0); + c_all_zero.option.weight = 1; + `checkr(c_all_zero.get_inst_coverage(), 0.0); + + // An instance without coverage does not contribute to type coverage + `checkr(cg_all_zero::get_coverage(), 0.0); + `checkr(cg_item::get_coverage(), c_item.get_inst_coverage()); + `checkr(cg_group::get_coverage(), (50.0 + 12.5) / 2); + + t1 = new; + t2 = new; + t3 = new; + t1.option.weight = 3; + a = 0; + t1.sample(); + a = 1; + t1.sample(); + a = 2; + t2.sample(); + // t1 50%, t2 25%, and t3 0% + `checkr(cg_type::get_coverage(), (3 * 50.0 + 25.0 + 0.0) / 5); + t3.option.weight = 0; + `checkr(t3.get_coverage(), (3 * 50.0 + 25.0) / 4); + // A destroyed instance still contributes, with its last weight + t2 = null; + `checkr(cg_type::get_coverage(), (25.0 + 3 * 50.0) / 4); + t1.option.weight = 0; + `checkr(cg_type::get_coverage(), 25.0); + + // With no contribution, type_option.weight decides between 0 and 100 + u1 = new; + a = 0; + u1.sample(); + `checkr(cg_type_w0::get_coverage(), 25.0); + u1.option.weight = 0; + `checkr(cg_type_w0::get_coverage(), 100.0); + `checkr(cg_never::get_coverage(), 0.0); + `checkr(cg_never_w0::get_coverage(), 100.0); + cg_never::type_option.weight = 0; + `checkr(cg_never::get_coverage(), 100.0); + // type_option is shared by every instance + cg_group::type_option.weight = 9; + `checkd(c_group.type_option.weight, 9); + + obj = new(2); + `checkd(obj.cg_emb.option.weight, 2); + obj.cg_emb.sample(0); + `checkr(obj.cg_emb.get_inst_coverage(), (2 * 25.0 + 100.0) / 3); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_weight_bad.out b/test_regress/t/t_covergroup_weight_bad.out new file mode 100644 index 000000000..f1ce59822 --- /dev/null +++ b/test_regress/t/t_covergroup_weight_bad.out @@ -0,0 +1,30 @@ +%Error: t/t_covergroup_weight_bad.v:13:21: Coverage option 'option.weight' is set to negative value '-1'; weights must be non-negative (IEEE 1800-2023 19.7) + : ... note: In instance 't' + 13 | option.weight = -1; + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_covergroup_weight_bad.v:14:26: Coverage option 'type_option.weight' is set to negative value '-2'; weights must be non-negative (IEEE 1800-2023 19.7) + : ... note: In instance 't' + 14 | type_option.weight = -2; + | ^ +%Error: t/t_covergroup_weight_bad.v:16:23: Coverage option 'option.weight' is set to negative value '-3'; weights must be non-negative (IEEE 1800-2023 19.7) + : ... note: In instance 't' + 16 | option.weight = -3; + | ^ +%Error: t/t_covergroup_weight_bad.v:19:28: Coverage option 'type_option.weight' is set to negative value '-4'; weights must be non-negative (IEEE 1800-2023 19.7) + : ... note: In instance 't' + 19 | type_option.weight = -4; + | ^ +%Error: t/t_covergroup_weight_bad.v:21:41: Coverage option 'option.weight' is set to negative value '-5'; weights must be non-negative (IEEE 1800-2023 19.7) + : ... note: In instance 't' + 21 | x: cross cpa, cpb{option.weight = 2 - 7;} + | ^ +%Error: t/t_covergroup_weight_bad.v:26:26: Coverage option 'type_option.weight' requires a constant expression (IEEE 1800-2023 19.7.1) + : ... note: In instance 't' + 26 | type_option.weight = w; + | ^ +%Error: t/t_covergroup_weight_bad.v:28:28: Coverage option 'type_option.weight' requires a constant expression (IEEE 1800-2023 19.7.1) + : ... note: In instance 't' + 28 | type_option.weight = w; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_weight_bad.py b/test_regress/t/t_covergroup_weight_bad.py new file mode 100755 index 000000000..344a4e20a --- /dev/null +++ b/test_regress/t/t_covergroup_weight_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_weight_bad.v b/test_regress/t/t_covergroup_weight_bad.v new file mode 100644 index 000000000..af75c3a92 --- /dev/null +++ b/test_regress/t/t_covergroup_weight_bad.v @@ -0,0 +1,35 @@ +// 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 + +// Invalid coverage weights (IEEE 1800-2023 19.7, 19.7.1) + +module t; + bit [1:0] a; + + covergroup cg_negative; + option.weight = -1; // <--- Bad: negative + type_option.weight = -2; // <--- Bad: negative + cpa: coverpoint a { + option.weight = -3; // <--- Bad: negative + } + cpb: coverpoint a { + type_option.weight = -4; // <--- Bad: negative + } + x: cross cpa, cpb{option.weight = 2 - 7;} // <--- Bad: negative + endgroup + + covergroup cg_nonconst(int w); + option.weight = w; // ok + type_option.weight = w; // <--- Bad: not constant + cpa: coverpoint a { + type_option.weight = w; // <--- Bad: not constant + } + endgroup + + cg_negative cg_negative_inst = new; + cg_nonconst cg_nonconst_inst = new(1); + initial $finish; +endmodule diff --git a/test_regress/t/t_covergroup_weight_no_coverage.py b/test_regress/t/t_covergroup_weight_no_coverage.py new file mode 100755 index 000000000..cfc15aff9 --- /dev/null +++ b/test_regress/t/t_covergroup_weight_no_coverage.py @@ -0,0 +1,24 @@ +#!/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_weight.v' + +# Without --coverage, instances are freed when their last handle drops. Under --protect-ids, +# get_coverage() must find the instances under the same obfuscated type name. +test.compile(verilator_flags2=['--protect-ids', '--protect-key WEIGHT_KEY', '-Wno-INSECURE']) + +test.execute() + +for filename in test.glob_some(test.obj_dir + '/*.cpp'): + test.file_grep_not(filename, r'cg_type|cg_never|cg_item') + +test.passes() diff --git a/test_regress/t/t_covergroup_weight_run_bad.out b/test_regress/t/t_covergroup_weight_run_bad.out new file mode 100644 index 000000000..87ea0ece9 --- /dev/null +++ b/test_regress/t/t_covergroup_weight_run_bad.out @@ -0,0 +1,10 @@ +%Error: t/t_covergroup_weight_run_bad.v:24: Coverage option 'option.weight' is set to negative value '-2'; weights must be non-negative (IEEE 1800-2023 19.7) +-Info: t/t_covergroup_weight_run_bad.v:24: Verilog $stop, ignored due to +verilator+error+limit +%Error: t/t_covergroup_weight_run_bad.v:24: Coverage option 'option.weight' is set to negative value '-2'; weights must be non-negative (IEEE 1800-2023 19.7) +%Error: t/t_covergroup_weight_run_bad.v:37: Coverage option 'option.weight' is set to negative value '-1'; weights must be non-negative (IEEE 1800-2023 19.7) +%Error: t/t_covergroup_weight_run_bad.v:41: Coverage option 'option.weight' is set to negative value '-1'; weights must be non-negative (IEEE 1800-2023 19.7) +%Error: t/t_covergroup_weight_run_bad.v:41: Coverage option 'option.weight' is set to negative value '-3'; weights must be non-negative (IEEE 1800-2023 19.7) +%Error: t/t_covergroup_weight_run_bad.v:41: Coverage option 'option.weight' is set to negative value '-4'; weights must be non-negative (IEEE 1800-2023 19.7) +%Error: t/t_covergroup_weight_run_bad.v:46: Coverage option 'option.weight' is set to negative value '-5'; weights must be non-negative (IEEE 1800-2023 19.7) +%Error: t/t_covergroup_weight_run_bad.v:49: Coverage option 'type_option.weight' is set to negative value '-6'; weights must be non-negative (IEEE 1800-2023 19.7) +*-* All Finished *-* diff --git a/test_regress/t/t_covergroup_weight_run_bad.py b/test_regress/t/t_covergroup_weight_run_bad.py new file mode 100755 index 000000000..7c250e570 --- /dev/null +++ b/test_regress/t/t_covergroup_weight_run_bad.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile() + +test.execute(all_run_flags=['+verilator+error+limit+100'], expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_weight_run_bad.v b/test_regress/t/t_covergroup_weight_run_bad.v new file mode 100644 index 000000000..f52f4714f --- /dev/null +++ b/test_regress/t/t_covergroup_weight_run_bad.v @@ -0,0 +1,114 @@ +// 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 + +// Coverage weights that are negative only at run time (IEEE 1800-2023 19.7): each +// negative value is reported once, and counts as zero + +// verilog_format: off +`define stop $stop +`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + bit [1:0] a; // 4 automatic bins + bit [2:0] b; // 8 automatic bins + + covergroup cg_item(int wa, int wb); + cpa: coverpoint a { + option.weight = wa; + } + cpb: coverpoint b { + option.weight = wb; // <--- Bad: negative at run time + } + endgroup + + covergroup cg_cross(int w); + cpa: coverpoint a { + bins a0 = {0}; + bins a1 = {1}; + } + cpb: coverpoint b { + bins b0 = {0}; + bins b1 = {1}; + } + x: cross cpa, cpb{option.weight = w;} // <--- Bad: negative at run time + endgroup + + // A covergroup's own weights are reported at its declaration + covergroup cg_inst(int w); // <--- Bad: option.weight negative at run time + option.weight = w; + cpa: coverpoint a; + endgroup + + covergroup cg_empty; // <--- Bad: option.weight negative at run time + endgroup + + covergroup cg_never; // <--- Bad: type_option.weight negative at run time + cpa: coverpoint a; + endgroup + + cg_item c_cancel, c_over; + cg_cross c_cross; + cg_inst d1, d2; + cg_empty e1; + + initial begin + // Item weights summing to zero, or nearly, would otherwise give a zero + // denominator, or coverage outside 0..100 + c_cancel = new(2, -2); + c_over = new(3, -2); + c_cross = new(-1); + a = 0; + b = 0; + c_cancel.sample(); + c_over.sample(); + c_cross.sample(); + a = 1; + c_cancel.sample(); + c_over.sample(); + c_cross.sample(); + // cpa: 2/4 automatic bins, and cpb weighs nothing + `checkr(c_cancel.get_inst_coverage(), 50.0); + `checkr(c_over.get_inst_coverage(), 50.0); + // cpa: a0 and a1, cpb: b0, and x weighs nothing + `checkr(c_cross.get_inst_coverage(), (100.0 + 50.0) / 2); + + // Reported by the constructor + d1 = new(-1); + d2 = new(1); + a = 0; + d1.sample(); + a = 1; + d1.sample(); + a = 2; + d2.sample(); + // d1 50% weighs nothing, d2 25% + `checkr(cg_inst::get_coverage(), 25.0); + // A procedural assignment is reported when coverage is next computed, once + d2.option.weight = -3; + `checkr(d2.get_inst_coverage(), 25.0); + `checkr(cg_inst::get_coverage(), 0.0); + d2.option.weight = -4; + `checkr(cg_inst::get_coverage(), 0.0); + d2.option.weight = 2; + `checkr(cg_inst::get_coverage(), 25.0); + // A destroyed instance keeps weighing nothing + d1 = null; + `checkr(cg_inst::get_coverage(), 25.0); + + // With nothing to cover, a negative weight decides as a zero weight + e1 = new; + e1.option.weight = -5; + `checkr(e1.get_inst_coverage(), 100.0); + `checkr(e1.get_inst_coverage(), 100.0); + cg_never::type_option.weight = -6; + `checkr(cg_never::get_coverage(), 100.0); + `checkr(cg_never::get_coverage(), 100.0); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 217c6170e..d0bde2bbb 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -791,6 +791,8 @@ module Vt_debug_emitv_t; logic [1:0] cg_sig2; covergroup Vt_debug_emitv_cg_basic; function new; + option.weight = 'sh2; + type_option.weight = 'sh3; cp_sig: coverpoint cg_sig { bins low = {['sh0:'sh3]} iff (cg_sig2[0]); bins high = {['sh4:'sh6]}; @@ -800,9 +802,10 @@ module Vt_debug_emitv_t; illegal_bins ill = {'sh5}; }; cp_options: coverpoint cg_sig2 { - - ???? // COVEROPTION - 'sh2}; + option.at_least = 'sh2; + option.weight = 'sh4; + type_option.weight = 'sh5; + }; endfunction int signed __Vint; struct { @@ -959,6 +962,7 @@ module Vt_debug_emitv_t; }; cx: cross cp_x, cp_y iff ((cg_sig[0] == cg_sig2[0])); cx_select: cross cp_x, cp_y { + option.weight = 'sh2; bins entire = cx_select; bins plain = binsof(cp_x); bins named = binsof(cp_x.x0); diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index adf34a968..7662763f6 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -394,6 +394,7 @@ module t (/*AUTOARG*/ covergroup cg_basic; option.per_instance = 1; option.weight = 2; + type_option.weight = 3; cp_sig: coverpoint cg_sig { bins low = {[0:3]} iff (cg_sig2[0]); bins high = {[4:6]}; @@ -405,6 +406,8 @@ module t (/*AUTOARG*/ // Coverpoint with per-coverpoint option but no explicit bins cp_options: coverpoint cg_sig2 { option.at_least = 2; + option.weight = 4; + type_option.weight = 5; } endgroup @@ -436,6 +439,7 @@ module t (/*AUTOARG*/ } cx: cross cp_x, cp_y iff (cg_sig[0] == cg_sig2[0]); cx_select: cross cp_x, cp_y{ + option.weight = 2; bins entire = cx_select; bins plain = binsof (cp_x); bins named = binsof (cp_x.x0); diff --git a/test_regress/t/t_vlcov_covergroup.annotate.out b/test_regress/t/t_vlcov_covergroup.annotate.out index 1d2bb0bb5..b49b34d0f 100644 --- a/test_regress/t/t_vlcov_covergroup.annotate.out +++ b/test_regress/t/t_vlcov_covergroup.annotate.out @@ -777,7 +777,8 @@ %000001 initial begin -000001 point: type=line comment=block hier=top.t // Sample 2-way: hit all 4 combinations - // cg2: 2 cp bins + 2 cp bins + 4 cross bins = 8 bins total (flat count) + // cg2: coverage is the mean of cp_addr (2 bins), cp_cmd (2 bins), and the cross (4 bins) + // (IEEE 1800-2023 19.11) %000001 addr = 0; -000001 point: type=line comment=block hier=top.t %000001 cmd = 0; @@ -788,7 +789,7 @@ -000001 point: type=line comment=block hier=top.t %000001 cg2_inst.sample(); // addr0 x read -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg2_inst.get_inst_coverage(), 37.5); // 3/8: addr0, read, addr0_x_read +%000001 `checkr(cg2_inst.get_inst_coverage(), (50.0 + 50.0 + 25.0) / 3); // addr0, read, one cross -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -802,7 +803,7 @@ -000001 point: type=line comment=block hier=top.t %000001 cg2_inst.sample(); // addr1 x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg2_inst.get_inst_coverage(), 75.0); // 6/8: all cp bins + 2 cross bins +%000001 `checkr(cg2_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); // 2 cross bins -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -816,7 +817,7 @@ -000001 point: type=line comment=block hier=top.t %000001 cg2_inst.sample(); // addr0 x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg2_inst.get_inst_coverage(), 87.5); // 7/8: 3 cross bins hit +%000001 `checkr(cg2_inst.get_inst_coverage(), (100.0 + 100.0 + 75.0) / 3); // 3 cross bins -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -830,13 +831,13 @@ -000001 point: type=line comment=block hier=top.t %000001 cg2_inst.sample(); // addr1 x read -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg2_inst.get_inst_coverage(), 100.0); // 8/8: all 4 cross bins hit +%000001 `checkr(cg2_inst.get_inst_coverage(), 100.0); // all 4 cross bins hit -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t // Sample 3-way: hit 4 of 12 combinations - // cg3: 3+2+2+12=19 bins; 4 cross bins hit -> 11/19=57.9% (not clean; no intermediate checkr) + // cg3: 4 of 12 cross bins hit (not clean; no intermediate checkr) %000001 addr = 0; -000001 point: type=line comment=block hier=top.t %000001 cmd = 0; @@ -871,7 +872,7 @@ -000001 point: type=line comment=block hier=top.t // Sample 4-way: hit 4 of 16 combinations - // cg4: 2+2+2+2+16=24 bins; 4 cross bins hit -> 12/24=50% + // cg4: four 2-bin coverpoints and a 16-bin cross; 4 cross bins hit %000001 addr = 0; -000001 point: type=line comment=block hier=top.t %000001 cmd = 0; @@ -892,7 +893,7 @@ -000001 point: type=line comment=block hier=top.t %000001 cg4_inst.sample(); -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg4_inst.get_inst_coverage(), 37.5); // 9/24: all cp bins + 2 cross bins +%000001 `checkr(cg4_inst.get_inst_coverage(), 72.5); // cp_mode 1/2, cross 2/16, others full -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -916,20 +917,19 @@ -000001 point: type=line comment=block hier=top.t %000001 cg4_inst.sample(); -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg4_inst.get_inst_coverage(), 50.0); // 12/24: all cp bins + 4 cross bins +%000001 `checkr(cg4_inst.get_inst_coverage(), 85.0); // all cp bins, cross 4/16 -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t - // Sample cg5 (cross with option.weight=2; weight is ignored in flat bin count) - // cg5: 2+2+4=8 bins; 2 cross bins hit -> 6/8=75% + // Sample cg5 (cross with option.weight=2): the cross counts twice in the mean %000001 addr = 0; -000001 point: type=line comment=block hier=top.t %000001 cmd = 0; -000001 point: type=line comment=block hier=top.t %000001 cg5_inst.sample(); -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg5_inst.get_inst_coverage(), 37.5); // 3/8: addr0, read, addr0_x_read +%000001 `checkr(cg5_inst.get_inst_coverage(), (50.0 + 50.0 + 2 * 25.0) / 4); // addr0, read, 1/4 -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -939,20 +939,20 @@ -000001 point: type=line comment=block hier=top.t %000001 cg5_inst.sample(); -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg5_inst.get_inst_coverage(), 75.0); // 6/8: all cp bins + 2 cross bins +%000001 `checkr(cg5_inst.get_inst_coverage(), (100.0 + 100.0 + 2 * 50.0) / 4); // cross 2/4 -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t // Sample cg_ignore: addr=3 is in ignore_bins so no cross bins for it - // cg_ignore: 2+2+4=8 bins total + // cg_ignore: 2-bin coverpoints and a 4-bin cross %000001 addr = 0; -000001 point: type=line comment=block hier=top.t %000001 cmd = 0; -000001 point: type=line comment=block hier=top.t %000001 cg_ignore_inst.sample(); // a0 x read -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_ignore_inst.get_inst_coverage(), 37.5); // 3/8 +%000001 `checkr(cg_ignore_inst.get_inst_coverage(), (50.0 + 50.0 + 25.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -962,7 +962,7 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_ignore_inst.sample(); // a1 x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_ignore_inst.get_inst_coverage(), 75.0); // 6/8 +%000001 `checkr(cg_ignore_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -972,7 +972,7 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_ignore_inst.sample(); // a0 x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_ignore_inst.get_inst_coverage(), 87.5); // 7/8 +%000001 `checkr(cg_ignore_inst.get_inst_coverage(), (100.0 + 100.0 + 75.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -982,7 +982,7 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_ignore_inst.sample(); // a1 x read -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_ignore_inst.get_inst_coverage(), 100.0); // 8/8 +%000001 `checkr(cg_ignore_inst.get_inst_coverage(), 100.0); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -998,14 +998,14 @@ -000001 point: type=line comment=else hier=top.t // Sample range-bin cross - // cg_range: 2+2+4=8 bins + // cg_range: 2-bin coverpoints and a 4-bin cross %000001 addr = 0; -000001 point: type=line comment=block hier=top.t %000001 cmd = 0; -000001 point: type=line comment=block hier=top.t %000001 cg_range_inst.sample(); // lo_range x read -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_range_inst.get_inst_coverage(), 37.5); // 3/8 +%000001 `checkr(cg_range_inst.get_inst_coverage(), (50.0 + 50.0 + 25.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -1015,7 +1015,7 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_range_inst.sample(); // hi_range x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_range_inst.get_inst_coverage(), 75.0); // 6/8 +%000001 `checkr(cg_range_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -1025,7 +1025,7 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_range_inst.sample(); // lo_range x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_range_inst.get_inst_coverage(), 87.5); // 7/8 +%000001 `checkr(cg_range_inst.get_inst_coverage(), (100.0 + 100.0 + 75.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -1035,13 +1035,13 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_range_inst.sample(); // hi_range x read -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_range_inst.get_inst_coverage(), 100.0); // 8/8 +%000001 `checkr(cg_range_inst.get_inst_coverage(), 100.0); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t // Sample cg_at_least (option.at_least in cross body; Verilator uses at_least=1 for bins) - // cg_at_least: 2+2+4=8 bins; 2 cross bins hit (count=1, at_least effectively 1) -> 6/8=75% + // cg_at_least: 2 of 4 cross bins hit (count=1, at_least effectively 1) %000001 addr = 0; -000001 point: type=line comment=block hier=top.t %000001 cmd = 0; @@ -1054,13 +1054,13 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_at_least_inst.sample(); // addr1 x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_at_least_inst.get_inst_coverage(), 75.0); +%000001 `checkr(cg_at_least_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t // Sample cg_goal (option.goal in cross body; does not affect hit counting) - // cg_goal: 2+2+4=8 bins; 2 cross bins hit -> 6/8=75% + // cg_goal: 2 of 4 cross bins hit %000001 addr = 0; -000001 point: type=line comment=block hier=top.t %000001 cmd = 0; @@ -1073,13 +1073,13 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_goal_inst.sample(); // addr1 x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_goal_inst.get_inst_coverage(), 75.0); +%000001 `checkr(cg_goal_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t // Sample cg_unsup_cross_opt - // cg_unsup_cross_opt: 2+2+4=8 bins; 2 cross bins hit -> 6/8=75% + // cg_unsup_cross_opt: 2 of 4 cross bins hit; the dropped cross contributes nothing %000001 addr = 0; -000001 point: type=line comment=block hier=top.t %000001 cmd = 0; @@ -1092,13 +1092,13 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_unsup_cross_opt_inst.sample(); // addr1 x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_unsup_cross_opt_inst.get_inst_coverage(), 75.0); +%000001 `checkr(cg_unsup_cross_opt_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t // Sample cg_unnamed_cross - // cg_unnamed_cross: 2+2+4=8 bins; 2 cross bins hit -> 6/8=75% + // cg_unnamed_cross: 2 of 4 cross bins hit %000001 addr = 0; -000001 point: type=line comment=block hier=top.t %000001 cmd = 0; @@ -1111,17 +1111,17 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_unnamed_cross_inst.sample(); // a1 x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_unnamed_cross_inst.get_inst_coverage(), 75.0); +%000001 `checkr(cg_unnamed_cross_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t - // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) + // Sample cg_mixed: cp_addr, cp_cmd, and cp_solo have 2 bins each, and cross ab 4 %000001 addr = 0; cmd = 0; mode = 0; -000001 point: type=line comment=block hier=top.t %000001 cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), (50.0 + 50.0 + 50.0 + 25.0) / 4); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -1137,7 +1137,7 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t @@ -1287,7 +1287,7 @@ -000001 point: type=line comment=else hier=top.t // Sample cg_trans: transition coverpoint crossed with a value coverpoint - // cg_trans: 1+2+2=5 bins; t01_x_v6 never completes -> 4/5=80% + // cg_trans: t01_x_v6 never completes, so only the cross is partial (1/2) // __Vprev_cp_t initializes to 0. %000001 state = 0; val = 5; cg_trans_inst.sample(); // prev=0,cur=0: no t01; v5 -000001 point: type=line comment=block hier=top.t @@ -1295,34 +1295,34 @@ -000001 point: type=line comment=block hier=top.t %000001 state = 0; val = 6; cg_trans_inst.sample(); // prev=1,cur=0: no t01; v6 (no cross) -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_trans_inst.get_inst_coverage(), 80.0); // 4/5: t01_x_v6 not hit +%000001 `checkr(cg_trans_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t // Sample cg_be: range bins over an ascending-declared coverpoint; cross 2x2 - // cg_be: 2+2+4=8 bins; endianness must not change results (mirror of cg_range) + // cg_be: endianness must not change results (mirror of cg_range) %000001 be_addr = 0; be_cmd = 0; cg_be_inst.sample(); // lo x read -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_be_inst.get_inst_coverage(), 37.5); // 3/8 +%000001 `checkr(cg_be_inst.get_inst_coverage(), (50.0 + 50.0 + 25.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t %000001 be_addr = 2; be_cmd = 1; cg_be_inst.sample(); // hi x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_be_inst.get_inst_coverage(), 75.0); // 6/8 +%000001 `checkr(cg_be_inst.get_inst_coverage(), (100.0 + 100.0 + 50.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t %000001 be_addr = 1; be_cmd = 1; cg_be_inst.sample(); // lo x write -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_be_inst.get_inst_coverage(), 87.5); // 7/8 +%000001 `checkr(cg_be_inst.get_inst_coverage(), (100.0 + 100.0 + 75.0) / 3); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t %000001 be_addr = 3; be_cmd = 0; cg_be_inst.sample(); // hi x read -000001 point: type=line comment=block hier=top.t -%000001 `checkr(cg_be_inst.get_inst_coverage(), 100.0); // 8/8 +%000001 `checkr(cg_be_inst.get_inst_coverage(), 100.0); -000001 point: type=line comment=block hier=top.t -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t