diff --git a/include/verilated_cov_key.h b/include/verilated_cov_key.h index 64566d552..a2af2b8d3 100644 --- a/include/verilated_cov_key.h +++ b/include/verilated_cov_key.h @@ -39,26 +39,34 @@ VLCOVGEN_ITEM("'name':'per_instance','short':'P', 'group':1, 'default':0, 'd VLCOVGEN_ITEM("'name':'thresh', 'short':'s', 'group':1, 'default':None, 'descr':'Number of hits to consider covered (aka at_least)'") VLCOVGEN_ITEM("'name':'type', 'short':'t', 'group':1, 'default':'', 'descr':'Type of coverage (block, line, fsm, etc)'") // Bin attributes -VLCOVGEN_ITEM("'name':'cross_bins', 'short':'Cb', 'group':0, 'default':'', 'descr':'Comma-separated per-dimension bin names for cross coverage points'") +VLCOVGEN_ITEM("'name':'bin', 'short':'B', 'group':0, 'default':'', 'descr':'Bin name for covergroup coverage points'") +VLCOVGEN_ITEM("'name':'bin_type', 'short':'Bt', 'group':0, 'default':'', 'descr':'Kind of a covergroup bin that is not coverable: ignore, illegal, or default'") VLCOVGEN_ITEM("'name':'comment', 'short':'o', 'group':0, 'default':'', 'descr':'Textual description for the item'") +VLCOVGEN_ITEM("'name':'cross', 'short':'C', 'group':0, 'default':0, 'descr':'True for cross coverage points'") +VLCOVGEN_ITEM("'name':'cross_bins', 'short':'Cb', 'group':0, 'default':'', 'descr':'Comma-separated per-dimension bin names for cross coverage points'") VLCOVGEN_ITEM("'name':'fsm_from', 'short':'Ff', 'group':0, 'default':'', 'descr':'FSM source state name for structured FSM coverage points'") VLCOVGEN_ITEM("'name':'fsm_tag', 'short':'Fg', 'group':0, 'default':'', 'descr':'FSM point tag such as reset, reset_include, or default'") VLCOVGEN_ITEM("'name':'fsm_to', 'short':'Ft', 'group':0, 'default':'', 'descr':'FSM destination state name for structured FSM coverage points'") VLCOVGEN_ITEM("'name':'fsm_var', 'short':'Fv', 'group':0, 'default':'', 'descr':'FSM state variable name for structured FSM coverage points'") +VLCOVGEN_ITEM("'name':'group_weight','short':'Gw', 'group':0, 'default':None, 'descr':'For totaling covergroups, type_option.weight of the covergroup of this item'") VLCOVGEN_ITEM("'name':'hier', 'short':'h', 'group':0, 'default':'', 'descr':'Hierarchy path name for the item'") VLCOVGEN_ITEM("'name':'lineno', 'short':'l', 'group':0, 'default':0, 'descr':'Line number for the item'") VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'descr':'For totaling items, weight of this item'") // clang-format on // VLCOVGEN_CIK_AUTO_EDIT_BEGIN +#define VL_CIK_BIN "B" +#define VL_CIK_BIN_TYPE "Bt" #define VL_CIK_COLUMN "n" #define VL_CIK_COMMENT "o" +#define VL_CIK_CROSS "C" #define VL_CIK_CROSS_BINS "Cb" #define VL_CIK_FILENAME "f" #define VL_CIK_FSM_FROM "Ff" #define VL_CIK_FSM_TAG "Fg" #define VL_CIK_FSM_TO "Ft" #define VL_CIK_FSM_VAR "Fv" +#define VL_CIK_GROUP_WEIGHT "Gw" #define VL_CIK_HIER "h" #define VL_CIK_LINENO "l" #define VL_CIK_LINESCOV "S" @@ -77,14 +85,18 @@ public: // Return the short key code for a given a long coverage key static std::string shortKey(const std::string& key) VL_PURE { // VLCOVGEN_SHORT_AUTO_EDIT_BEGIN + if (key == "bin") return VL_CIK_BIN; + if (key == "bin_type") return VL_CIK_BIN_TYPE; if (key == "column") return VL_CIK_COLUMN; if (key == "comment") return VL_CIK_COMMENT; + if (key == "cross") return VL_CIK_CROSS; if (key == "cross_bins") return VL_CIK_CROSS_BINS; if (key == "filename") return VL_CIK_FILENAME; if (key == "fsm_from") return VL_CIK_FSM_FROM; if (key == "fsm_tag") return VL_CIK_FSM_TAG; if (key == "fsm_to") return VL_CIK_FSM_TO; if (key == "fsm_var") return VL_CIK_FSM_VAR; + if (key == "group_weight") return VL_CIK_GROUP_WEIGHT; if (key == "hier") return VL_CIK_HIER; if (key == "lineno") return VL_CIK_LINENO; if (key == "linescov") return VL_CIK_LINESCOV; diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp index b223d809a..8809ad083 100644 --- a/include/verilated_covergroup.cpp +++ b/include/verilated_covergroup.cpp @@ -1126,7 +1126,20 @@ uint32_t VlCoverpoint::binCount() const { std::string VlCoverpoint::binName(uint32_t i) const { return declaredBinName(reportedBin(i)); } #if VM_COVERAGE -void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) { +// Key of the coverage computation (IEEE 1800-2023 19.11), or "", which leaves the key out, for +// the default value 1, so that the records of the common case keep their names +static const char* _vl_cov_score_key(const char* keyp, uint32_t value) VL_PURE { + return value == 1 ? "" : keyp; +} + +void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page, + uint32_t itemWeight, uint32_t groupWeight) { + const std::string threshStr = std::to_string(m_atLeast); + const std::string weightStr = std::to_string(itemWeight); + const std::string groupWeightStr = std::to_string(groupWeight); + const char* const threshKeyp = _vl_cov_score_key("thresh", m_atLeast); + const char* const weightKeyp = _vl_cov_score_key("weight", itemWeight); + const char* const groupWeightKeyp = _vl_cov_score_key("group_weight", groupWeight); for (uint32_t reported = 0; reported < binCount(); ++reported) { const uint32_t i = reportedBin(reported); const VlCovNamer& nm = namerFor(i); @@ -1135,18 +1148,16 @@ void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* pa const std::string full = m_hier + "." + binp; const std::string lineStr = std::to_string(nm.line()); const std::string colStr = std::to_string(nm.col()); - if (kind == VlCovBinKind::KIND_NORMAL) { - VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", - nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", - binp.c_str()); - } else { - const char* const binType = kind == VlCovBinKind::KIND_IGNORE ? "ignore" - : kind == VlCovBinKind::KIND_ILLEGAL ? "illegal" - : "default"; - VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", - nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", - binp.c_str(), "bin_type", binType); - } + // An empty key leaves out the bin type of a Normal bin + const char* const binType = kind == VlCovBinKind::KIND_NORMAL ? "" + : kind == VlCovBinKind::KIND_IGNORE ? "ignore" + : kind == VlCovBinKind::KIND_ILLEGAL ? "illegal" + : "default"; + VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", + nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", + binp.c_str(), binType[0] ? "bin_type" : "", binType, threshKeyp, + threshStr.c_str(), weightKeyp, weightStr.c_str(), groupWeightKeyp, + groupWeightStr.c_str()); } } #endif // VM_COVERAGE @@ -1422,9 +1433,15 @@ std::string VlCoverCross::autoBinName(uint32_t flat) const { } #if VM_COVERAGE -void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* page) { +void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* page, + uint32_t itemWeight, uint32_t groupWeight) { const std::string lineStr = std::to_string(m_line); const std::string colStr = std::to_string(m_col); + // A cross bin is covered once hit, which needs no option.at_least + const std::string weightStr = std::to_string(itemWeight); + const std::string groupWeightStr = std::to_string(groupWeight); + const char* const weightKeyp = _vl_cov_score_key("weight", itemWeight); + const char* const groupWeightKeyp = _vl_cov_score_key("group_weight", groupWeight); const uint32_t explicitCount = hasExplicitBins() ? static_cast(m_explicitp->bins.size()) : 0; // Use the same indexed names for registration and the runtime read interface. @@ -1435,18 +1452,15 @@ void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* pa Bin& userBin = m_explicitp->bins[i]; const std::string binLineStr = std::to_string(userBin.line); const std::string binColStr = std::to_string(userBin.col); - if (userBin.kind == VlCovBinKind::KIND_NORMAL) { - VL_COVER_INSERT(covcontextp, full.c_str(), &userBin.count, "page", page, - "filename", userBin.filep, "lineno", binLineStr.c_str(), "column", - binColStr.c_str(), "bin", bin.c_str(), "cross", "1"); - } else { - const char* const binType - = userBin.kind == VlCovBinKind::KIND_IGNORE ? "ignore" : "illegal"; - VL_COVER_INSERT(covcontextp, full.c_str(), &userBin.count, "page", page, - "filename", userBin.filep, "lineno", binLineStr.c_str(), "column", - binColStr.c_str(), "bin", bin.c_str(), "cross", "1", "bin_type", - binType); - } + // An empty key leaves out the bin type of a Normal bin + const char* const binType = userBin.kind == VlCovBinKind::KIND_NORMAL ? "" + : userBin.kind == VlCovBinKind::KIND_IGNORE ? "ignore" + : "illegal"; + VL_COVER_INSERT(covcontextp, full.c_str(), &userBin.count, "page", page, "filename", + userBin.filep, "lineno", binLineStr.c_str(), "column", + binColStr.c_str(), "bin", bin.c_str(), "cross", "1", + binType[0] ? "bin_type" : "", binType, weightKeyp, weightStr.c_str(), + groupWeightKeyp, groupWeightStr.c_str()); continue; } const uint32_t flat = autoIndex(i - explicitCount); @@ -1460,7 +1474,8 @@ void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* pa } VL_COVER_INSERT(covcontextp, full.c_str(), &m_flatCountsp[flat], "page", page, "filename", m_file, "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", - bin.c_str(), "cross", "1", "cross_bins", crossBins.c_str()); + bin.c_str(), "cross", "1", "cross_bins", crossBins.c_str(), weightKeyp, + weightStr.c_str(), groupWeightKeyp, groupWeightStr.c_str()); } } #endif // VM_COVERAGE diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h index 639b4fb13..09bdcb98c 100644 --- a/include/verilated_covergroup.h +++ b/include/verilated_covergroup.h @@ -165,7 +165,12 @@ public: int line, int col) { addNamer(set, count, VlCovBinNaming::Numbered, name, file, line, col); } - void registerBins(VerilatedCovContext* covcontextp, const char* page); + /// Register the bins in the coverage database, with what verilator_coverage needs to + /// compute coverage (IEEE 1800-2023 19.11): option.at_least, and the weights of the + /// coverpoint, itemWeight, and of its covergroup, groupWeight. The weights are those of + /// every instance, as the database merges the instances. + void registerBins(VerilatedCovContext* covcontextp, const char* page, uint32_t itemWeight, + uint32_t groupWeight); /// Configure construction-time value metadata for exclusions and cross selections. void valueType(uint32_t bits, bool isSigned); @@ -433,7 +438,9 @@ public: const char* filep, int line, int col); /// Retain only automatic cross bins not selected by any explicit bin. virtual void finalizeBins(); - void registerBins(VerilatedCovContext* covcontextp, const char* page); + /// Register the bins in the coverage database; see VlCoverpoint::registerBins(). + void registerBins(VerilatedCovContext* covcontextp, const char* page, uint32_t itemWeight, + uint32_t groupWeight); // ---- hot path (from generated sample(), after all coverpoints sampled) ---- /// Sample automatic and explicit bins, optionally applying per-bin iff guards. diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 729b4c77e..b3680a428 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -886,7 +886,7 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) { 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_REGISTER_BINS, "registerBins", false, "rrrr") \ macro(COVERGROUP_SAMPLE, "sample", false, "") \ macro(COVERGROUP_SAMPLE_IFFS, "sample", false, "r") \ macro(COVERGROUP_SELECT_ALL, "selectAll", false, "") \ diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 44e4bd805..3356cee88 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -247,6 +247,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { 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 + uint32_t m_cgTypeWeight = 1; // The covergroup's type_option.weight, a constant struct EmbeddedEventTrigger final { FileLine* eventFl; // Clocking-event source location @@ -342,6 +343,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { UASSERT_OBJ(optp->optType() == VCoverOptionType::WEIGHT, optp, "Unexpected covergroup option reaching V3Covergroup"); FileLine* const fl = optp->fileline(); + // V3Width left type_option.weight a non-negative constant + if (optp->typeOption()) m_cgTypeWeight = VN_AS(optp->valuep(), Const)->toUInt(); AstAssign* const assignp = new AstAssign{ fl, newWeightSel(fl, optionVar(optp->typeOption()), VAccess::WRITE), optp->valuep()->unlinkFrBack()}; @@ -356,6 +359,27 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_cgOptions.clear(); } + // The weight of an item in the coverage database, which merges the instances: its + // option.weight if a constant, and so of every instance; else its type_option.weight, the + // weight of type coverage merged over the instances (IEEE 1800-2023 19.7.1) + static uint32_t itemDatabaseWeight(AstNode* optionsp) { + const AstNodeExpr* weightp = nullptr; // The option.weight in effect + uint32_t typeWeight = 1; + for (AstNode* nodep = optionsp; nodep; nodep = nodep->nextp()) { + const AstCoverOption* const optp = VN_AS(nodep, CoverOption); + if (!(optp->optType() == VCoverOptionType::WEIGHT)) continue; + // V3Width left type_option.weight a non-negative constant + if (optp->typeOption()) { + typeWeight = VN_AS(optp->valuep(), Const)->toUInt(); + } else { + weightp = optp->valuep(); + } + } + if (!weightp) return 1; + if (const AstConst* const constp = VN_CAST(weightp, Const)) return constp->toUInt(); + return typeWeight; + } + // Configure an item's option.weight, its weight in instance coverage (IEEE 1800-2023 // 19.11). type_option.weight only weighs type coverage merged over the instances, which // type_option.merge_instances would select; without that, it has no effect. @@ -385,6 +409,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_excludedVars.clear(); m_droppedCrosses.clear(); m_cgInstVarp = nullptr; + m_cgTypeWeight = 1; lowerCovergroupOptions(); @@ -2375,10 +2400,13 @@ class FunctionalCoverageVisitor final : public VNVisitor { if (v3Global.opt.coverage()) { const std::string page = VIdProtect::protectIf("v_covergroup/" + m_covergroupp->name(), prot); - m_constructorp->addStmtsp(itemCall(fl, cpVarp, VCMethod::COVERGROUP_REGISTER_BINS, - {ctext(fl, "vlSymsp->_vm_contextp__->coveragep()"), - ctext(fl, quoted(page))}) - ->makeStmt()); + m_constructorp->addStmtsp( + itemCall(fl, cpVarp, VCMethod::COVERGROUP_REGISTER_BINS, + {ctext(fl, "vlSymsp->_vm_contextp__->coveragep()"), + ctext(fl, quoted(page)), + cnum(fl, itemDatabaseWeight(coverpointp->optionsp())), + cnum(fl, m_cgTypeWeight)}) + ->makeStmt()); } } @@ -3678,7 +3706,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { = VIdProtect::protectIf("v_covergroup/" + m_covergroupp->name(), prot); m_constructorp->addStmtsp(itemCall(fl, cxVarp, VCMethod::COVERGROUP_REGISTER_BINS, {ctext(fl, "vlSymsp->_vm_contextp__->coveragep()"), - ctext(fl, quoted(page))}) + ctext(fl, quoted(page)), + cnum(fl, itemDatabaseWeight(crossp->optionsp())), + cnum(fl, m_cgTypeWeight)}) ->makeStmt()); } diff --git a/src/VlcPoint.h b/src/VlcPoint.h index 4537ef182..bd7b9f30d 100644 --- a/src/VlcPoint.h +++ b/src/VlcPoint.h @@ -66,11 +66,15 @@ public: string hier() const { return keyExtract(VL_CIK_HIER, m_name.c_str()); } string page() const { return keyExtract("page", m_name.c_str()); } string type() const { return typeExtract(m_name.c_str()); } - // Covergroup-specific key accessors (long keys, no short-key alias) - string bin() const { return keyExtract("bin", m_name.c_str()); } - string binType() const { return keyExtract("bin_type", m_name.c_str()); } - bool isCross() const { return !keyExtract("cross", m_name.c_str()).empty(); } + // Covergroup-specific key accessors + string bin() const { return keyExtract(VL_CIK_BIN, m_name.c_str()); } + string binType() const { return keyExtract(VL_CIK_BIN_TYPE, m_name.c_str()); } + bool isCross() const { return !keyExtract(VL_CIK_CROSS, m_name.c_str()).empty(); } string crossBins() const { return keyExtract(VL_CIK_CROSS_BINS, m_name.c_str()); } + // The covergroup's type_option.weight; "" for the default 1 + string groupWeight() const { return keyExtract(VL_CIK_GROUP_WEIGHT, m_name.c_str()); } + // The coverpoint's or cross's option.weight; "" for the default 1 + string weight() const { return keyExtract(VL_CIK_WEIGHT, m_name.c_str()); } string thresh() const { // string as maybe "" return keyExtract(VL_CIK_THRESH, m_name.c_str()); diff --git a/src/VlcTop.cpp b/src/VlcTop.cpp index 298356ae8..a1933fdc7 100644 --- a/src/VlcTop.cpp +++ b/src/VlcTop.cpp @@ -41,11 +41,42 @@ namespace { // fields already exposed by VlcPoint; they do not affect coverage point // identity, merging, or .dat writing. -// Map coverage type to (covered points, total points). -using TypeTally = std::map>; +// Coverage of a report row: its covered and total points, and for covergroups, their +// coverage, which weights coverpoints and crosses rather than counting bins +class Tally final { + // MEMBERS + uint64_t m_hit = 0; // Covered points + uint64_t m_total = 0; // Points + bool m_scored = false; // Coverage is m_score, not the ratio of the points + double m_score = 0.0; // Percent coverage, if m_scored + +public: + // ACCESSORS + uint64_t hit() const { return m_hit; } + uint64_t total() const { return m_total; } + bool scored() const { return m_scored; } + double score() const { return m_score; } + void score(double value) { + m_scored = true; + m_score = value; + } + // METHODS + void addPoint(bool covered) { + if (covered) ++m_hit; + ++m_total; + } + void addPoints(const Tally& other) { + m_hit += other.m_hit; + m_total += other.m_total; + } +}; + +// Map coverage type to its tally. +using TypeTally = std::map; static constexpr const char* const s_orderedTypes[] = {"line", "toggle", "branch", "expr", "fsm_state", "fsm_arc"}; +static constexpr const char* const s_covergroupType = "covergroup"; static constexpr size_t s_summaryIndent = 2; static constexpr size_t s_reportRowIndent = 4; @@ -96,41 +127,243 @@ string duName(const VlcPoint& point) { } void tallyPoint(TypeTally& tally, const string& type, uint64_t count) { - std::pair& entry = tally[type]; - if (count > 0) ++entry.first; - ++entry.second; + tally[type].addPoint(count > 0); } -// Return percentage as a string, handling 100%, and protecting from div zero. -string pctString(uint64_t hit, uint64_t total) { - double value = 0.0; - if (total) value = 100.0 * static_cast(hit) / static_cast(total); - if (hit < total && value > 99.9) value = 99.9; // Presumes precision 1 +// Keep the percentage calculation in one place so flat summaries and hierarchy +// reports cannot drift in zero-total handling. +double pct(uint64_t hit, uint64_t total) { + return total ? (100.0 * static_cast(hit) / static_cast(total)) : 0.0; +} + +// Return a percentage as a string, handling 100%: coverage not complete shows below it. +string pctValueString(double value, bool complete) { + if (!complete && value > 99.9) value = 99.9; // Presumes precision 1 std::stringstream os; os << std::fixed << std::setprecision(1) // 1 matters, see above << value << "%"; return os.str(); } +// Return percentage as a string, handling 100%, and protecting from div zero. +string pctString(uint64_t hit, uint64_t total) { + return pctValueString(pct(hit, total), hit >= total); +} + +// Covergroup coverage (IEEE 1800-2023 19.11). An item, a coverpoint or cross, covers the +// ratio of its bins that reached option.at_least, excluding ignore, illegal and default bins; +// a covergroup, the average of its items, weighted by their weight in the records (a constant +// option.weight, else type_option.weight); and the covergroups together, their average +// weighted by type_option.weight. The coverage database merges the instances of a covergroup +// type, so this is the coverage of the merged instances, which is what get_coverage() returns +// for a single instance. + +// A coverable bin of a coverpoint or cross, of all its records +class CgBin final { + // MEMBERS + string m_name; // Name of the bin + uint64_t m_count = 0; // Hits, of all the records + uint64_t m_atLeast = 0; // option.at_least, the largest of the records + +public: + // ACCESSORS + const string& name() const { return m_name; } + bool covered() const { return m_count >= m_atLeast; } + // METHODS + void addRecord(const string& name, uint64_t count, uint64_t atLeast) { + m_name = name; + m_count += count; + m_atLeast = std::max(m_atLeast, atLeast); + } +}; + +// A coverpoint or cross +class CgItem final { + // MEMBERS + uint64_t m_weight = 0; // The weight of the records, the largest + std::map m_bins; // Coverable bins, by binIdentity() + +public: + // ACCESSORS + uint64_t weight() const { return m_weight; } + const std::map& bins() const { return m_bins; } + // METHODS + void addRecord(uint64_t weight) { m_weight = std::max(m_weight, weight); } + CgBin& findNewBin(const string& identity) { return m_bins[identity]; } +}; + +// A covergroup +class CgGroup final { + // MEMBERS + string m_hier; // Hierarchy above the items + uint64_t m_weight = 0; // type_option.weight, the largest of the records + std::map m_items; // By hierarchy + +public: + // ACCESSORS + const string& hier() const { return m_hier; } + uint64_t weight() const { return m_weight; } + const std::map& items() const { return m_items; } + // METHODS + void addRecord(const string& hier, uint64_t weight) { + m_hier = hier; + m_weight = std::max(m_weight, weight); + } + CgItem& findNewItem(const string& hier) { return m_items[hier]; } +}; +using CgGroups = std::map; // By page + +// A number of a record, or 1 if the record leaves it out +uint64_t keyNumber(const string& value) { + return value.empty() ? 1 : std::strtoull(value.c_str(), nullptr, 10); +} + +// If a field of a record's name, '\001\002', has a key of the coverage computation +bool isScoreField(const string& field) { + for (const char* const keyp : {VL_CIK_THRESH, VL_CIK_WEIGHT, VL_CIK_GROUP_WEIGHT}) { + const string prefix = string{"\001"} + keyp + "\002"; + if (field.compare(0, prefix.size(), prefix) == 0) return true; + } + return false; +} + +// The name of a bin's record without the keys of the coverage computation, which the records of +// a bin may differ in, so identifying the bin. Its name does not: covergroups of distinct scopes +// may share a name, as may cross bins, of the names of their coverpoints' bins joined. +string binIdentity(const string& recordName) { + string identity; + string::size_type start = 0; + while (start < recordName.size()) { + string::size_type end = recordName.find('\001', start + 1); + if (end == string::npos) end = recordName.size(); + const string field = recordName.substr(start, end - start); + if (!isScoreField(field)) identity += field; + start = end; + } + return identity; +} + +bool isCovergroup(const VlcPoint& point) { return point.type() == s_covergroupType; } + +CgGroups covergroups(VlcPoints& points) { + CgGroups groups; + for (const VlcPoints::ByName::value_type& i : points) { + const VlcPoint& pt = points.pointNumber(i.second); + if (!isCovergroup(pt)) continue; + // Named '..', where only the bin, of its own key, may have dots + const string hier = pt.hier(); + string bin = pt.bin(); + if (bin.empty() || bin.size() >= hier.size()) bin = hier.substr(hier.rfind('.') + 1); + const string itemHier + = hier.substr(0, hier.size() - std::min(hier.size(), bin.size() + 1)); + CgGroup& group = groups[pt.page()]; + group.addRecord(itemHier.substr(0, itemHier.rfind('.')), keyNumber(pt.groupWeight())); + CgItem& item = group.findNewItem(itemHier); + item.addRecord(keyNumber(pt.weight())); + if (!pt.binType().empty()) continue; // Not coverable + item.findNewBin(binIdentity(pt.name())).addRecord(bin, pt.count(), keyNumber(pt.thresh())); + } + return groups; +} + +Tally itemTally(const CgItem& item) { + Tally tally; + for (const std::pair& bin : item.bins()) { + tally.addPoint(bin.second.covered()); + } + // Without bins, 0.0, or 100.0 if of zero weight (IEEE 1800-2023 19.11.1) + tally.score(tally.total() ? pct(tally.hit(), tally.total()) : item.weight() ? 0.0 : 100.0); + return tally; +} + +// The tally of a covergroup; contributesp, if its items have weight and bins, so that it counts +// in the coverage of covergroups together +Tally groupTally(const CgGroup& group, bool* contributesp = nullptr) { + Tally tally; + double weighted = 0.0; + double weights = 0.0; + for (const std::pair& it : group.items()) { + const Tally item = itemTally(it.second); + tally.addPoints(item); + if (!item.total()) continue; // Excluded from both sums + weighted += static_cast(it.second.weight()) * item.score(); + weights += static_cast(it.second.weight()); + } + if (contributesp) *contributesp = weights != 0.0; + // Without items of weight and bins, 0.0, or 100.0 if of zero weight + tally.score(weights != 0.0 ? weighted / weights : group.weight() ? 0.0 : 100.0); + return tally; +} + +Tally groupsTally(const std::vector& groups) { + Tally tally; + double weighted = 0.0; + double weights = 0.0; + bool anyWeight = false; + for (const CgGroup* const groupp : groups) { + bool contributes = false; + const Tally group = groupTally(*groupp, &contributes); + tally.addPoints(group); + if (groupp->weight()) anyWeight = true; + if (!contributes) continue; + weighted += static_cast(groupp->weight()) * group.score(); + weights += static_cast(groupp->weight()); + } + // Without covergroups of weight that contribute, 0.0, or 100.0 if all have zero weight + tally.score(weights != 0.0 ? weighted / weights : anyWeight ? 0.0 : 100.0); + return tally; +} + +// The tallies of covergroups, of their coverpoints and crosses, and of the coverable bins of +// those, by name; a covergroup of a dotted name also tallies in each node of its name +std::map covergroupTallies(const CgGroups& groups) { + std::map tallies; + std::map> nodeGroups; + for (const CgGroups::value_type& it : groups) { + const CgGroup& group = it.second; + string path; + for (const string& part : splitHier(group.hier())) { + path = path.empty() ? part : path + "." + part; + nodeGroups[path].push_back(&group); + } + for (const std::pair& item : group.items()) { + tallies[item.first] = itemTally(item.second); + for (const std::pair& bin : item.second.bins()) { + // Of the bins of the name + tallies[item.first + "." + bin.second.name()].addPoint(bin.second.covered()); + } + } + } + for (const std::pair>& it : nodeGroups) { + tallies[it.first] + = it.second.size() == 1 ? groupTally(*it.second.front()) : groupsTally(it.second); + } + return tallies; +} + // Shared row formatter. The callers choose which rows to print; this only keeps // the text layout identical between the flat and hierarchy reports. void printIndent(size_t indent) { for (size_t i = 0; i < indent; ++i) std::cout << ' '; } -void printTallyRow(const string& type, uint64_t hit, uint64_t total, size_t indent, - size_t typeWidth, size_t countWidth) { +void printTallyRow(const string& type, const Tally& tally, size_t indent, size_t typeWidth, + size_t countWidth) { printIndent(indent); + // A score is complete at 100%, which it may reach with bins of no weight uncovered + const string percent = tally.scored() ? pctValueString(tally.score(), tally.score() >= 100.0) + : pctString(tally.hit(), tally.total()); + // Right-align percentages to the width of "100.0%", so that rows line up std::cout << std::left << std::setw(typeWidth) << type << " : " << std::right << std::fixed - << pctString(hit, total) << " (" << std::setw(countWidth) << hit << "/" - << std::setw(countWidth) << total << ")\n"; + << std::setw(6) << percent << " (" << std::setw(countWidth) << tally.hit() << "/" + << std::setw(countWidth) << tally.total() << ")\n"; } size_t countWidth(const TypeTally& tally) { size_t width = cvtToStr(0).size(); for (TypeTally::const_iterator it = tally.begin(); it != tally.end(); ++it) { - width = std::max(width, cvtToStr(it->second.first).size()); - width = std::max(width, cvtToStr(it->second.second).size()); + width = std::max(width, cvtToStr(it->second.hit()).size()); + width = std::max(width, cvtToStr(it->second.total()).size()); } return width; } @@ -157,19 +390,35 @@ void printTypeTally(const TypeTally& tally, size_t indent, bool includeMissingOr const string type = typep; const TypeTally::const_iterator it = tally.find(type); if (it != tally.end()) { - printTallyRow(type, it->second.first, it->second.second, indent, typWidth, cntWidth); + printTallyRow(type, it->second, indent, typWidth, cntWidth); } else if (includeMissingOrdered) { - printTallyRow(type, 0, 0, indent, typWidth, cntWidth); + printTallyRow(type, Tally{}, indent, typWidth, cntWidth); } } for (TypeTally::const_iterator it = tally.begin(); it != tally.end(); ++it) { if (!isOrderedType(it->first)) { - printTallyRow(it->first, it->second.first, it->second.second, indent, typWidth, - cntWidth); + printTallyRow(it->first, it->second, indent, typWidth, cntWidth); } } } +// Print covergroups, coverpoints, crosses, and bins one per line, so that searching for a name +// shows its coverage +void printCovergroupTallies(const std::map& tallies, int levels) { + std::map shown; + size_t nameWidth = 0; + for (const std::pair& it : tallies) { + if (levels >= 0 && static_cast(splitHier(it.first).size()) > levels + 1) continue; + shown.insert(it); + nameWidth = std::max(nameWidth, it.first.size()); + } + const size_t cntWidth = countWidth(shown); + std::cout << "Covergroup Coverage Summary:\n"; + for (const std::pair& it : shown) { + printTallyRow(it.first, it.second, s_summaryIndent, nameWidth, cntWidth); + } +} + } // namespace void VlcTop::readCoverage(const string& filename, bool nonfatal) { @@ -520,8 +769,15 @@ void VlcTop::printTypeSummary() { TypeTally tally; for (VlcPoints::ByName::value_type& i : m_points) { const VlcPoint& pt = m_points.pointNumber(i.second); + if (isCovergroup(pt)) continue; // Tallied below, by covergroup tallyPoint(tally, displayType(pt), pt.count()); } + const CgGroups groups = covergroups(m_points); + if (!groups.empty()) { + std::vector groupps; + for (const CgGroups::value_type& it : groups) groupps.push_back(&it.second); + tally[s_covergroupType] = groupsTally(groupps); + } if (tally.empty()) return; std::cout << "Coverage Summary:\n"; // Keep the legacy summary behavior of showing standard coverage types even @@ -536,6 +792,7 @@ void VlcTop::printHierarchyReport() { bool hasCollapsedHier = false; for (VlcPoints::ByName::value_type& i : m_points) { const VlcPoint& pt = m_points.pointNumber(i.second); + if (isCovergroup(pt)) continue; // Reported below, by covergroup const string hier = reportHier(pt); if (hier.empty()) continue; hasHier = true; @@ -549,7 +806,12 @@ void VlcTop::printHierarchyReport() { } tallyPoint(duTallies[duName(pt)], type, pt.count()); } + const std::map cgTallies = covergroupTallies(covergroups(m_points)); + if (!hasHier && !cgTallies.empty()) { + printCovergroupTallies(cgTallies, opt.reportLevels()); + return; + } if (!hasHier) { std::cout << "%Warning: --report hierarchy input has no hierarchy fields; " << "printing flat summary instead.\n"; @@ -582,4 +844,5 @@ void VlcTop::printHierarchyReport() { // types only, but in the same stable order as the flat summary. printTypeTally(it->second, s_reportRowIndent, false); } + if (!cgTallies.empty()) printCovergroupTallies(cgTallies, levels); } diff --git a/test_regress/t/coverage_covergroup_common.py b/test_regress/t/coverage_covergroup_common.py index 8524a7cc2..13ac2269e 100644 --- a/test_regress/t/coverage_covergroup_common.py +++ b/test_regress/t/coverage_covergroup_common.py @@ -29,8 +29,8 @@ def covergroup_coverage_report(test, outfile=None): if not h_m: continue hier = h_m.group(1) - bt_m = re.search(r'\x01bin_type\x02([^\x01]+)', entry) - cross_m = re.search(r'\x01cross\x021', entry) + bt_m = re.search(r'\x01Bt\x02([^\x01]+)', entry) + cross_m = re.search(r'\x01C\x021', entry) annotations = [] if bt_m: annotations.append(bt_m.group(1)) diff --git a/test_regress/t/t_cover_fsm_multi_inst.out b/test_regress/t/t_cover_fsm_multi_inst.out index 46a613220..8b3dd8231 100644 --- a/test_regress/t/t_cover_fsm_multi_inst.out +++ b/test_regress/t/t_cover_fsm_multi_inst.out @@ -1,15 +1,15 @@ $ verilator_coverage --report hierarchy --levels 3 coverage.dat Hierarchy Coverage Summary: t - fsm_state : 70.0% ( 7/10) - fsm_arc : 58.3% ( 7/12) + fsm_state : 70.0% ( 7/10) + fsm_arc : 58.3% ( 7/12) t.u1 - fsm_state : 80.0% (4/5) - fsm_arc : 66.7% (4/6) + fsm_state : 80.0% (4/5) + fsm_arc : 66.7% (4/6) t.u2 - fsm_state : 60.0% (3/5) - fsm_arc : 50.0% (3/6) + fsm_state : 60.0% (3/5) + fsm_arc : 50.0% (3/6) Design Unit Coverage Summary: fsm - fsm_state : 70.0% ( 7/10) - fsm_arc : 58.3% ( 7/12) + fsm_state : 70.0% ( 7/10) + fsm_arc : 58.3% ( 7/12) diff --git a/test_regress/t/t_covergroup_bin_counts.py b/test_regress/t/t_covergroup_bin_counts.py index 16258ed8c..2ca8fd5a6 100755 --- a/test_regress/t/t_covergroup_bin_counts.py +++ b/test_regress/t/t_covergroup_bin_counts.py @@ -16,9 +16,9 @@ coverage_covergroup_common.run(test) # Verify coverage.dat format contains covergroup entries (replaces t_covergroup_database) test.file_grep(test.coverage_filename, r'covergroup') -test.file_grep(test.coverage_filename, r'bin.{0,2}low') -test.file_grep(test.coverage_filename, r'bin.{0,2}high') +test.file_grep(test.coverage_filename, r'\x01B\x02low') +test.file_grep(test.coverage_filename, r'\x01B\x02high') test.file_grep(test.coverage_filename, r'cg_db\.cp\.low') test.file_grep(test.coverage_filename, r'cg_db\.cp\.high') -test.file_grep(test.coverage_filename, r'.*bin.{0,2}low.*\' [1-9]') -test.file_grep(test.coverage_filename, r'.*bin.{0,2}high.*\' [1-9]') +test.file_grep(test.coverage_filename, r'.*\x01B\x02low.*\' [1-9]') +test.file_grep(test.coverage_filename, r'.*\x01B\x02high.*\' [1-9]') diff --git a/test_regress/t/t_vlcov_covergroup_report.out b/test_regress/t/t_vlcov_covergroup_report.out new file mode 100644 index 000000000..571351e54 --- /dev/null +++ b/test_regress/t/t_vlcov_covergroup_report.out @@ -0,0 +1,184 @@ +$ verilator_coverage --report summary,hierarchy coverage.dat +Coverage Summary: + line : 0.0% ( 0/ 0) + toggle : 0.0% ( 0/ 0) + branch : 0.0% ( 0/ 0) + expr : 0.0% ( 0/ 0) + fsm_state : 0.0% ( 0/ 0) + fsm_arc : 0.0% ( 0/ 0) + covergroup : 67.6% (31/51) +Covergroup Coverage Summary: + __vlAnonCG_twin : 50.0% ( 2/ 4) + __vlAnonCG_twin.cp : 50.0% ( 2/ 4) + __vlAnonCG_twin.cp.hit : 50.0% ( 1/ 2) + __vlAnonCG_twin.cp.miss : 50.0% ( 1/ 2) + collide : 91.7% ( 7/ 8) + collide.p : 100.0% ( 2/ 2) + collide.p.a : 100.0% ( 1/ 1) + collide.p.a_x_b : 100.0% ( 1/ 1) + collide.q : 100.0% ( 2/ 2) + collide.q.b_x_c : 100.0% ( 1/ 1) + collide.q.c : 100.0% ( 1/ 1) + collide.x : 75.0% ( 3/ 4) + collide.x.a_x_b_x_b_x_c : 100.0% ( 1/ 1) + collide.x.a_x_b_x_c : 50.0% ( 1/ 2) + collide.x.a_x_c : 100.0% ( 1/ 1) + crossed : 30.0% ( 3/10) + crossed.cp_a : 50.0% ( 1/ 2) + crossed.cp_a.auto_0 : 100.0% ( 1/ 1) + crossed.cp_a.auto_1 : 0.0% ( 0/ 1) + crossed.cp_b : 25.0% ( 1/ 4) + crossed.cp_b.auto_0 : 100.0% ( 1/ 1) + crossed.cp_b.auto_1 : 0.0% ( 0/ 1) + crossed.cp_b.auto_2 : 0.0% ( 0/ 1) + crossed.cp_b.auto_3 : 0.0% ( 0/ 1) + crossed.x : 25.0% ( 1/ 4) + crossed.x.auto_0_x_auto_0 : 100.0% ( 1/ 1) + crossed.x.auto_0_x_auto_1 : 0.0% ( 0/ 1) + crossed.x.auto_0_x_auto_2 : 0.0% ( 0/ 1) + crossed.x.auto_0_x_auto_3 : 0.0% ( 0/ 1) + empty : 0.0% ( 0/ 0) + empty.cp : 0.0% ( 0/ 0) + empty.unweighted : 100.0% ( 0/ 0) + excluded : 100.0% ( 1/ 1) + excluded.cp : 100.0% ( 1/ 1) + excluded.cp.hit : 100.0% ( 1/ 1) + free : 100.0% ( 0/ 0) + free.cp : 0.0% ( 0/ 0) + heavy : 50.0% ( 1/ 2) + heavy.cp : 50.0% ( 1/ 2) + heavy.cp.auto_0 : 100.0% ( 1/ 1) + heavy.cp.auto_1 : 0.0% ( 0/ 1) + instance_weight : 100.0% ( 3/ 4) + instance_weight.cp_a : 50.0% ( 1/ 2) + instance_weight.cp_a.auto_0 : 100.0% ( 1/ 1) + instance_weight.cp_a.auto_1 : 0.0% ( 0/ 1) + instance_weight.cp_b : 100.0% ( 2/ 2) + instance_weight.cp_b.auto_0 : 100.0% ( 1/ 1) + instance_weight.cp_b.auto_1 : 100.0% ( 1/ 1) + merged : 100.0% ( 2/ 2) + merged.cp : 100.0% ( 2/ 2) + merged.cp.auto_0 : 100.0% ( 1/ 1) + merged.cp.auto_1 : 100.0% ( 1/ 1) + threshold : 50.0% ( 1/ 2) + threshold.cp : 50.0% ( 1/ 2) + threshold.cp.auto_0 : 100.0% ( 1/ 1) + threshold.cp.auto_1 : 0.0% ( 0/ 1) + type_weight : 75.0% ( 3/ 4) + type_weight.cp_a : 50.0% ( 1/ 2) + type_weight.cp_a.auto_0 : 100.0% ( 1/ 1) + type_weight.cp_a.auto_1 : 0.0% ( 0/ 1) + type_weight.cp_b : 100.0% ( 2/ 2) + type_weight.cp_b.auto_0 : 100.0% ( 1/ 1) + type_weight.cp_b.auto_1 : 100.0% ( 1/ 1) + unequal : 37.5% ( 2/ 6) + unequal.cp_a : 50.0% ( 1/ 2) + unequal.cp_a.auto_0 : 100.0% ( 1/ 1) + unequal.cp_a.auto_1 : 0.0% ( 0/ 1) + unequal.cp_b : 25.0% ( 1/ 4) + unequal.cp_b.auto_0 : 100.0% ( 1/ 1) + unequal.cp_b.auto_1 : 0.0% ( 0/ 1) + unequal.cp_b.auto_2 : 0.0% ( 0/ 1) + unequal.cp_b.auto_3 : 0.0% ( 0/ 1) + varying : 62.5% ( 3/ 4) + varying.cp : 50.0% ( 1/ 2) + varying.cp.auto_0 : 100.0% ( 1/ 1) + varying.cp.auto_1 : 0.0% ( 0/ 1) + varying.cq : 100.0% ( 2/ 2) + varying.cq.auto_0 : 100.0% ( 1/ 1) + varying.cq.auto_1 : 100.0% ( 1/ 1) + weighted : 100.0% ( 3/ 4) + weighted.cp_a : 50.0% ( 1/ 2) + weighted.cp_a.auto_0 : 100.0% ( 1/ 1) + weighted.cp_a.auto_1 : 0.0% ( 0/ 1) + weighted.cp_b : 100.0% ( 2/ 2) + weighted.cp_b.auto_0 : 100.0% ( 1/ 1) + weighted.cp_b.auto_1 : 100.0% ( 1/ 1) + +$ verilator_coverage --report summary,hierarchy edge.dat +Coverage Summary: + line : 0.0% (0/0) + toggle : 0.0% (0/0) + branch : 0.0% (0/0) + expr : 0.0% (0/0) + fsm_state : 0.0% (0/0) + fsm_arc : 0.0% (0/0) + covergroup : 58.3% (3/5) +Covergroup Coverage Summary: + pkg : 75.0% (2/3) + pkg.alpha : 50.0% (1/2) + pkg.alpha.cp : 50.0% (1/2) + pkg.alpha.cp.b0 : 100.0% (1/1) + pkg.alpha.cp.b1 : 0.0% (0/1) + pkg.beta : 100.0% (1/1) + pkg.beta.cp : 100.0% (1/1) + pkg.beta.cp.b0 : 100.0% (1/1) + split : 25.0% (1/2) + split.cp : 0.0% (0/1) + split.cp.b0 : 0.0% (0/1) + split.cq : 100.0% (1/1) + split.cq.b0 : 100.0% (1/1) + +$ verilator_coverage --report summary zero.dat +Coverage Summary: + line : 0.0% (0/0) + toggle : 0.0% (0/0) + branch : 0.0% (0/0) + expr : 0.0% (0/0) + fsm_state : 0.0% (0/0) + fsm_arc : 0.0% (0/0) + covergroup : 100.0% (0/1) + +$ verilator_coverage --report summary none.dat +Coverage Summary: + line : 0.0% (0/0) + toggle : 0.0% (0/0) + branch : 0.0% (0/0) + expr : 0.0% (0/0) + fsm_state : 0.0% (0/0) + fsm_arc : 0.0% (0/0) + covergroup : 0.0% (0/0) + +$ verilator_coverage --report summary near.dat +Coverage Summary: + line : 0.0% ( 0/ 0) + toggle : 0.0% ( 0/ 0) + branch : 0.0% ( 0/ 0) + expr : 0.0% ( 0/ 0) + fsm_state : 0.0% ( 0/ 0) + fsm_arc : 0.0% ( 0/ 0) + covergroup : 99.9% (2000/2001) + +$ verilator_coverage --report hierarchy mixed.dat +Hierarchy Coverage Summary: + top + line : 100.0% (1/1) + toggle : 0.0% (0/1) + top.t + line : 100.0% (1/1) + toggle : 0.0% (0/1) +Design Unit Coverage Summary: + t + line : 100.0% (1/1) + toggle : 0.0% (0/1) +Covergroup Coverage Summary: + cg : 50.0% (1/2) + cg.cp : 50.0% (1/2) + cg.cp.a : 100.0% (1/1) + cg.cp.b : 0.0% (0/1) + +$ verilator_coverage --report hierarchy --levels 1 mixed.dat +Hierarchy Coverage Summary: + top + line : 100.0% (1/1) + toggle : 0.0% (0/1) + top.t + line : 100.0% (1/1) + toggle : 0.0% (0/1) +Design Unit Coverage Summary: + t + line : 100.0% (1/1) + toggle : 0.0% (0/1) +Covergroup Coverage Summary: + cg : 50.0% (1/2) + cg.cp : 50.0% (1/2) diff --git a/test_regress/t/t_vlcov_covergroup_report.py b/test_regress/t/t_vlcov_covergroup_report.py new file mode 100755 index 000000000..3f530ab4c --- /dev/null +++ b/test_regress/t/t_vlcov_covergroup_report.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +from coverage_common import init_log, run_vlcov, vlcov_run_context + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage-user']) + +test.execute() + +log = test.obj_dir + "/vlcov.log" +tmp_log = test.obj_dir + "/vlcov.tmp" +init_log(log) +vlcov = vlcov_run_context(test, log, tmp_log) + + +def dat_line(fields, count): + return "C '" + "".join("\001" + key + "\002" + value + for key, value in fields) + "' " + str(count) + "\n" + + +def write_dat(name, records, points=()): + filename = test.obj_dir + "/" + name + with open(filename, "w", encoding="utf-8") as fh: + fh.write("# SystemC::Coverage-3\n") + for group, item, bin_name, count, keys in records: + fields = [("t", "covergroup"), ("page", "v_covergroup/" + group), ("f", "t/cg.v"), + ("l", "1")] + fields += keys + fields.append(("h", group + "." + item + "." + bin_name)) + fh.write(dat_line(fields, count)) + for kind, hier, lineno, count in points: + fields = [("t", kind), ("page", "v_" + kind + "/t"), ("f", "t/t.v"), ("l", lineno), + ("h", hier)] + fh.write(dat_line(fields, count)) + return filename + + +# Covergroups of dotted names under one node; a record without its bin's name; and records of a +# bin with different weights and thresholds, which merge with the largest of those +edge_cov = write_dat("edge.dat", [ + ("pkg.alpha", "cp", "b0", 1, [("B", "b0")]), + ("pkg.alpha", "cp", "b1", 0, [("B", "b1")]), + ("pkg.beta", "cp", "b0", 1, []), + ("split", "cp", "b0", 1, [("B", "b0"), ("s", "2"), ("w", "2")]), + ("split", "cp", "b0", 0, [("B", "b0"), ("w", "3")]), + ("split", "cq", "b0", 1, [("B", "b0")]), +]) +# Covergroups of zero weight only: 100 +zero_cov = write_dat("zero.dat", [("idle", "cp", "b0", 0, [("B", "b0"), ("Gw", "0")])]) +# Covergroups without coverable bins only: 0 +none_cov = write_dat("none.dat", [("void", "cp", "b0", 0, [("B", "b0"), ("Bt", "ignore")])]) +# Coverage that is not complete shows below 100%: 99.95 as 99.9 +near_cov = write_dat("near.dat", [("near", "cp", "b" + str(i), int(i != 0), [("B", "b" + str(i))]) + for i in range(2001)]) +# A module shows a row per coverage type under its name; covergroups show one line each +mixed_cov = write_dat("mixed.dat", [("cg", "cp", "a", 1, [("B", "a")]), + ("cg", "cp", "b", 0, [("B", "b")])], + points=[("line", "top.t", "10", 1), ("toggle", "top.t", "11", 0)]) + +run_vlcov(vlcov, + "verilator_coverage --report summary,hierarchy coverage.dat", + args=["--report", "summary,hierarchy", test.obj_dir + "/coverage.dat"]) +run_vlcov(vlcov, + "verilator_coverage --report summary,hierarchy edge.dat", + args=["--report", "summary,hierarchy", edge_cov]) +run_vlcov(vlcov, + "verilator_coverage --report summary zero.dat", + args=["--report", "summary", zero_cov]) +run_vlcov(vlcov, + "verilator_coverage --report summary none.dat", + args=["--report", "summary", none_cov]) +run_vlcov(vlcov, + "verilator_coverage --report summary near.dat", + args=["--report", "summary", near_cov]) +run_vlcov(vlcov, + "verilator_coverage --report hierarchy mixed.dat", + args=["--report", "hierarchy", mixed_cov]) +run_vlcov(vlcov, + "verilator_coverage --report hierarchy --levels 1 mixed.dat", + args=["--report", "hierarchy", "--levels", "1", mixed_cov]) + +test.files_identical(log, test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_vlcov_covergroup_report.v b/test_regress/t/t_vlcov_covergroup_report.v new file mode 100644 index 000000000..cbc04200c --- /dev/null +++ b/test_regress/t/t_vlcov_covergroup_report.v @@ -0,0 +1,208 @@ +// 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 + +// Covergroup coverage in verilator_coverage reports is as get_coverage() computes it +// (IEEE 1800-2023 19.11), not a ratio of bins + +// verilog_format: off +`define stop $stop +`define checkr(gotv,expv) do if ((gotv) > (expv) + 0.001 || (gotv) < (expv) - 0.001) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + // Ignore, illegal and default bins are not coverable: 100 + covergroup excluded with function sample (int value); + cp: coverpoint value { + bins hit = {0}; + ignore_bins ignored = {1}; + illegal_bins illegal = {2}; + bins other = default; + } + endgroup + // The average of the coverpoints, 50 and 25: 37.5 + covergroup unequal with function sample (bit a, bit [1:0] b); + cp_a: coverpoint a; + cp_b: coverpoint b; + endgroup + // A coverpoint of zero weight does not count: 100 + covergroup weighted with function sample (bit a, bit b); + cp_a: coverpoint a { + option.weight = 0; + type_option.weight = 0; + } + cp_b: coverpoint b; + endgroup + // option.weight weighs the coverpoints, as in get_coverage(): 100, then 75 + covergroup instance_weight with function sample (bit a, bit b); + cp_a: coverpoint a { + option.weight = 0; + } + cp_b: coverpoint b; + endgroup + covergroup type_weight with function sample (bit a, bit b); + cp_a: coverpoint a { + type_option.weight = 0; + } + cp_b: coverpoint b; + endgroup + // A bin is covered once hit option.at_least times: 50 + covergroup threshold with function sample (bit a); + cp: coverpoint a { + option.at_least = 2; + } + endgroup + // A cross is an item, of its weight, whose ignored bins do not count: 50, 25, and 25 thrice: + // 30 + covergroup crossed with function sample (bit a, bit [1:0] b); + cp_a: coverpoint a; + cp_b: coverpoint b; + x: cross cp_a, cp_b{ + option.weight = 3; + ignore_bins ignored = binsof (cp_a) intersect {1}; + } + endgroup + // Bins of a name are distinct bins: the cross bins of 'a' and 'b_x_c', and of 'a_x_b' and + // 'c', are both named a_x_b_x_c: 100, 100 and 75: 91.67 + covergroup collide with function sample (bit u, bit v); + p: coverpoint u { + bins a = {0}; + bins a_x_b = {1}; + } + q: coverpoint v { + bins c = {0}; + bins b_x_c = {1}; + } + x: cross p, q; + endgroup + // Weighs as its type_option.weight, a coverpoint whose option.weight is not a constant, and + // so may differ in the instances the coverage database merges: 50 three times, and 100: 62.5, + // where get_coverage() averages the instances: 50 + covergroup varying(int w) with function sample (bit v, bit u); + cp: coverpoint v { + option.weight = w; + type_option.weight = 3; + } + cq: coverpoint u; + endgroup + // Without coverable bins: 0, so not counted in the coverage of the covergroups, and a + // coverpoint of zero weight without them, 100 + covergroup empty with function sample (bit a); + cp: coverpoint a { + ignore_bins ignored = {[0 : 1]}; + } + unweighted: coverpoint a { + option.weight = 0; + ignore_bins ignored = {[0 : 1]}; + } + endgroup + // Of zero weight, without coverable bins: 100 + covergroup free with function sample (bit a); + type_option.weight = 0; + cp: coverpoint a { + ignore_bins ignored = {[0 : 1]}; + } + endgroup + // Of three times the weight of the others in the coverage of the covergroups: 50 + covergroup heavy with function sample (bit a); + type_option.weight = 3; + cp: coverpoint a; + endgroup + // The coverage database merges the bins of the instances: 100, where get_coverage() averages + // the instances: 50 + covergroup merged with function sample (bit a); + cp: coverpoint a; + endgroup + + // Covergroups of a name in distinct classes are both named __vlAnonCG_twin, with distinct + // bins: 50, and get_coverage() averages their instances: 50 + class First; + bit v; + covergroup twin; + cp: coverpoint v { + bins hit = {0}; + bins miss = {1}; + } + endgroup + function new; + twin = new; + endfunction + endclass + class Second; + bit v; + covergroup twin; + cp: coverpoint v { + bins hit = {0}; + bins miss = {1}; + } + endgroup + function new; + twin = new; + endfunction + endclass + + excluded excluded_inst = new; + unequal unequal_inst = new; + weighted weighted_inst = new; + instance_weight instance_weight_inst = new; + type_weight type_weight_inst = new; + threshold threshold_inst = new; + crossed crossed_inst = new; + empty empty_inst = new; + free free_inst = new; + heavy heavy_inst = new; + merged merged_first = new; + merged merged_second = new; + collide collide_inst = new; + varying varying_none = new(0); + varying varying_two = new(2); + First first = new; + Second second = new; + + initial begin + excluded_inst.sample(0); + unequal_inst.sample(0, 0); + weighted_inst.sample(0, 0); + weighted_inst.sample(0, 1); + instance_weight_inst.sample(0, 0); + instance_weight_inst.sample(0, 1); + type_weight_inst.sample(0, 0); + type_weight_inst.sample(0, 1); + threshold_inst.sample(0); + threshold_inst.sample(0); + threshold_inst.sample(1); + crossed_inst.sample(0, 0); + empty_inst.sample(0); + free_inst.sample(0); + heavy_inst.sample(0); + merged_first.sample(0); + merged_second.sample(1); + collide_inst.sample(0, 0); + collide_inst.sample(0, 1); + collide_inst.sample(1, 1); + varying_none.sample(0, 0); + varying_two.sample(0, 1); + first.v = 0; + first.twin.sample(); + second.v = 1; + second.twin.sample(); + `checkr(excluded_inst.get_coverage(), 100.0); + `checkr(unequal_inst.get_coverage(), 37.5); + `checkr(weighted_inst.get_coverage(), 100.0); + `checkr(instance_weight_inst.get_coverage(), 100.0); + `checkr(type_weight_inst.get_coverage(), 75.0); + `checkr(threshold_inst.get_coverage(), 50.0); + `checkr(crossed_inst.get_coverage(), 30.0); + `checkr(empty_inst.get_coverage(), 0.0); + `checkr(free_inst.get_coverage(), 100.0); + `checkr(heavy_inst.get_coverage(), 50.0); + `checkr(merged_first.get_coverage(), 50.0); + `checkr(collide_inst.get_coverage(), 275.0 / 3); + `checkr(varying_none.get_coverage(), 50.0); + `checkr(first.twin.get_coverage(), 50.0); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_vlcov_hier_report.out b/test_regress/t/t_vlcov_hier_report.out index 1c37b8e6a..76a6374e7 100644 --- a/test_regress/t/t_vlcov_hier_report.out +++ b/test_regress/t/t_vlcov_hier_report.out @@ -1,30 +1,30 @@ $ verilator_coverage --report summary t/t_vlcov_data_e.dat Coverage Summary: - line : 88.9% ( 40/ 45) - toggle : 33.3% ( 35/105) - branch : 78.1% ( 50/ 64) - expr : 66.7% ( 8/ 12) - fsm_state : 0.0% ( 0/ 0) - fsm_arc : 0.0% ( 0/ 0) + line : 88.9% ( 40/ 45) + toggle : 33.3% ( 35/105) + branch : 78.1% ( 50/ 64) + expr : 66.7% ( 8/ 12) + fsm_state : 0.0% ( 0/ 0) + fsm_arc : 0.0% ( 0/ 0) $ verilator_coverage t/t_cover_hier.out Coverage Summary: line : 100.0% (4/4) - toggle : 0.0% (0/0) + toggle : 0.0% (0/0) branch : 100.0% (4/4) - expr : 0.0% (0/0) - fsm_state : 0.0% (0/0) - fsm_arc : 0.0% (0/0) + expr : 0.0% (0/0) + fsm_state : 0.0% (0/0) + fsm_arc : 0.0% (0/0) user : 100.0% (2/2) $ verilator_coverage --report summary,hier t/t_cover_hier.out Coverage Summary: line : 100.0% (4/4) - toggle : 0.0% (0/0) + toggle : 0.0% (0/0) branch : 100.0% (4/4) - expr : 0.0% (0/0) - fsm_state : 0.0% (0/0) - fsm_arc : 0.0% (0/0) + expr : 0.0% (0/0) + fsm_state : 0.0% (0/0) + fsm_arc : 0.0% (0/0) user : 100.0% (2/2) Hierarchy Coverage Summary: top @@ -58,11 +58,11 @@ Design Unit Coverage Summary: $ verilator_coverage --report hier,summary --levels 0 t/t_cover_hier.out Coverage Summary: line : 100.0% (4/4) - toggle : 0.0% (0/0) + toggle : 0.0% (0/0) branch : 100.0% (4/4) - expr : 0.0% (0/0) - fsm_state : 0.0% (0/0) - fsm_arc : 0.0% (0/0) + expr : 0.0% (0/0) + fsm_state : 0.0% (0/0) + fsm_arc : 0.0% (0/0) user : 100.0% (2/2) Hierarchy Coverage Summary: top @@ -80,11 +80,11 @@ Design Unit Coverage Summary: $ verilator_coverage --report hier,summary --levels -4 t/t_cover_hier.out Coverage Summary: line : 100.0% (4/4) - toggle : 0.0% (0/0) + toggle : 0.0% (0/0) branch : 100.0% (4/4) - expr : 0.0% (0/0) - fsm_state : 0.0% (0/0) - fsm_arc : 0.0% (0/0) + expr : 0.0% (0/0) + fsm_state : 0.0% (0/0) + fsm_arc : 0.0% (0/0) user : 100.0% (2/2) Hierarchy Coverage Summary: top @@ -233,19 +233,19 @@ $ verilator_coverage --report hierarchy collapsed.dat Note: hierarchy report contains collapsed hierarchy paths; it is not precise per-instance coverage. Hierarchy Coverage Summary: tb - line : 50.0% (1/2) - branch : 50.0% (1/2) + line : 50.0% (1/2) + branch : 50.0% (1/2) tb.cluster* - line : 50.0% (1/2) + line : 50.0% (1/2) branch : 100.0% (1/1) tb.cluster*.u_core - line : 50.0% (1/2) + line : 50.0% (1/2) branch : 100.0% (1/1) tb.cluster? - branch : 0.0% (0/1) + branch : 0.0% (0/1) tb.cluster?.u_core - branch : 0.0% (0/1) + branch : 0.0% (0/1) Design Unit Coverage Summary: core - line : 50.0% (1/2) - branch : 50.0% (1/2) + line : 50.0% (1/2) + branch : 50.0% (1/2) diff --git a/test_regress/t/t_vlcov_hier_report_bad.out b/test_regress/t/t_vlcov_hier_report_bad.out index 64c71c728..6b61b93b5 100644 --- a/test_regress/t/t_vlcov_hier_report_bad.out +++ b/test_regress/t/t_vlcov_hier_report_bad.out @@ -10,8 +10,8 @@ $ verilator_coverage --report hierarchy missing.dat %Warning: --report hierarchy input has no hierarchy fields; printing flat summary instead. Coverage Summary: line : 100.0% (1/1) - toggle : 0.0% (0/0) - branch : 0.0% (0/0) - expr : 0.0% (0/0) - fsm_state : 0.0% (0/0) - fsm_arc : 0.0% (0/0) + toggle : 0.0% (0/0) + branch : 0.0% (0/0) + expr : 0.0% (0/0) + fsm_state : 0.0% (0/0) + fsm_arc : 0.0% (0/0) diff --git a/test_regress/t/t_vlcov_hier_report_runtime.out b/test_regress/t/t_vlcov_hier_report_runtime.out index 169975f7d..4417d6a2a 100644 --- a/test_regress/t/t_vlcov_hier_report_runtime.out +++ b/test_regress/t/t_vlcov_hier_report_runtime.out @@ -1,64 +1,64 @@ $ verilator_coverage --report summary coverage.dat Coverage Summary: - line : 82.9% (29/35) - toggle : 85.4% (82/96) - branch : 78.6% (22/28) - expr : 57.1% ( 8/14) - fsm_state : 91.7% (11/12) - fsm_arc : 71.4% (10/14) + line : 82.9% (29/35) + toggle : 85.4% (82/96) + branch : 78.6% (22/28) + expr : 57.1% ( 8/14) + fsm_state : 91.7% (11/12) + fsm_arc : 71.4% (10/14) $ verilator_coverage --report hierarchy --levels 3 coverage.dat Hierarchy Coverage Summary: tb - line : 82.9% (29/35) - toggle : 85.4% (82/96) - branch : 78.6% (22/28) - expr : 57.1% ( 8/14) - fsm_state : 91.7% (11/12) - fsm_arc : 71.4% (10/14) + line : 82.9% (29/35) + toggle : 85.4% (82/96) + branch : 78.6% (22/28) + expr : 57.1% ( 8/14) + fsm_state : 91.7% (11/12) + fsm_arc : 71.4% (10/14) tb.cluster_a - line : 73.3% (11/15) - toggle : 76.1% (35/46) - branch : 66.7% ( 8/12) + line : 73.3% (11/15) + toggle : 76.1% (35/46) + branch : 66.7% ( 8/12) expr : 100.0% ( 6/ 6) - fsm_state : 83.3% ( 5/ 6) - fsm_arc : 57.1% ( 4/ 7) + fsm_state : 83.3% ( 5/ 6) + fsm_arc : 57.1% ( 4/ 7) tb.cluster_a.u_core - line : 66.7% ( 8/12) - toggle : 83.3% (20/24) - branch : 62.5% ( 5/ 8) - fsm_state : 83.3% ( 5/ 6) - fsm_arc : 57.1% ( 4/ 7) + line : 66.7% ( 8/12) + toggle : 83.3% (20/24) + branch : 62.5% ( 5/ 8) + fsm_state : 83.3% ( 5/ 6) + fsm_arc : 57.1% ( 4/ 7) tb.cluster_b - line : 86.7% (13/15) - toggle : 93.5% (43/46) - branch : 83.3% (10/12) - expr : 0.0% ( 0/ 6) + line : 86.7% (13/15) + toggle : 93.5% (43/46) + branch : 83.3% (10/12) + expr : 0.0% ( 0/ 6) fsm_state : 100.0% ( 6/ 6) - fsm_arc : 85.7% ( 6/ 7) + fsm_arc : 85.7% ( 6/ 7) tb.cluster_b.u_core - line : 83.3% (10/12) + line : 83.3% (10/12) toggle : 100.0% (24/24) - branch : 87.5% ( 7/ 8) + branch : 87.5% ( 7/ 8) fsm_state : 100.0% ( 6/ 6) - fsm_arc : 85.7% ( 6/ 7) + fsm_arc : 85.7% ( 6/ 7) Design Unit Coverage Summary: cluster line : 100.0% ( 3/ 3) - toggle : 68.2% (15/22) - branch : 75.0% ( 3/ 4) + toggle : 68.2% (15/22) + branch : 75.0% ( 3/ 4) expr : 100.0% ( 6/ 6) cluster__M1 line : 100.0% ( 3/ 3) - toggle : 86.4% (19/22) - branch : 75.0% ( 3/ 4) - expr : 0.0% ( 0/ 6) + toggle : 86.4% (19/22) + branch : 75.0% ( 3/ 4) + expr : 0.0% ( 0/ 6) fsm_core - line : 75.0% (18/24) - toggle : 91.7% (44/48) - branch : 75.0% (12/16) - fsm_state : 91.7% (11/12) - fsm_arc : 71.4% (10/14) + line : 75.0% (18/24) + toggle : 91.7% (44/48) + branch : 75.0% (12/16) + fsm_state : 91.7% (11/12) + fsm_arc : 71.4% (10/14) tb line : 100.0% (5/5) toggle : 100.0% (4/4) diff --git a/test_regress/t/t_vlcov_summary_99pct.out b/test_regress/t/t_vlcov_summary_99pct.out index 763b3caa4..b3bdcdc23 100644 --- a/test_regress/t/t_vlcov_summary_99pct.out +++ b/test_regress/t/t_vlcov_summary_99pct.out @@ -1,7 +1,7 @@ Coverage Summary: - line : 0.0% ( 0/ 0) - toggle : 0.0% ( 0/ 0) - branch : 99.9% (10999/11000) - expr : 0.0% ( 0/ 0) - fsm_state : 0.0% ( 0/ 0) - fsm_arc : 0.0% ( 0/ 0) + line : 0.0% ( 0/ 0) + toggle : 0.0% ( 0/ 0) + branch : 99.9% (10999/11000) + expr : 0.0% ( 0/ 0) + fsm_state : 0.0% ( 0/ 0) + fsm_arc : 0.0% ( 0/ 0) diff --git a/test_regress/t/t_vlcov_summary_typed.out b/test_regress/t/t_vlcov_summary_typed.out index f9700d96d..5fe2996be 100644 --- a/test_regress/t/t_vlcov_summary_typed.out +++ b/test_regress/t/t_vlcov_summary_typed.out @@ -1,7 +1,7 @@ Coverage Summary: - line : 88.9% ( 40/ 45) - toggle : 33.3% ( 35/105) - branch : 78.1% ( 50/ 64) - expr : 66.7% ( 8/ 12) - fsm_state : 0.0% ( 0/ 0) - fsm_arc : 0.0% ( 0/ 0) + line : 88.9% ( 40/ 45) + toggle : 33.3% ( 35/105) + branch : 78.1% ( 50/ 64) + expr : 66.7% ( 8/ 12) + fsm_state : 0.0% ( 0/ 0) + fsm_arc : 0.0% ( 0/ 0)