Support weight in coverage (#8472)

This commit is contained in:
Marco Bartoli
2026-09-24 14:01:45 -04:00
committed by GitHub
parent 88082a4e96
commit 1aa5ab2e3a
40 changed files with 1132 additions and 283 deletions
+28
View File
@@ -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;
//=========================================================================
+11
View File
@@ -28,6 +28,8 @@
#include <cstdint>
#include <string>
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
+107 -15
View File
@@ -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<int32_t>(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<void>(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<int>(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<double, double> 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<double, double> 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<double, double> 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<double, double> 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<double>(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<VlCovRegistry*>(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
+62 -19
View File
@@ -41,6 +41,7 @@
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
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<double, double> 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<uint32_t>(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; }
};
+6 -6
View File
@@ -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;
-29
View File
@@ -64,35 +64,6 @@
// Placeholder for compiling with --protect-ids
#define VL_UNKNOWN "<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
+4 -1
View File
@@ -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, "") \
+9 -2
View File
@@ -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]
+2
View File
@@ -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);
+136 -87
View File
@@ -217,6 +217,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
std::vector<AstCoverpoint*> m_coverpoints; // Coverpoints in current covergroup
std::map<std::string, AstCoverpoint*> m_coverpointMap; // Name -> coverpoint for fast lookup
std::vector<AstCoverCross*> m_coverCrosses; // Cross coverage items in current covergroup
std::vector<AstCgOptionAssign*> 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<std::string> m_crossedCpNames; // Coverpoints referenced by a cross
std::vector<AstVar*> m_cpVars; // VlCoverpoint member, one per coverpoint
std::vector<AstVar*> m_crossVars; // VlCoverCross member, one per cross
std::map<std::string, AstVar*> 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<K>();' (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()-><method>()' 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<K>();' (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<uint32_t>(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; <item>->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, ...}; <call> }" 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<uint32_t>(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:
+16 -1
View File
@@ -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 {
+12 -6
View File
@@ -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);
+48 -1
View File
@@ -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 <typename T_Option>
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);
+1 -1
View File
@@ -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;
+2 -1
View File
@@ -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;
+7 -2
View File
@@ -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
+41 -41
View File
@@ -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%
+4 -3
View File
@@ -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
+6 -4
View File
@@ -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
+4 -2
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
Empty covergroup coverage: 100.000000%
*-* All Finished *-*
+1 -1
View File
@@ -10,6 +10,6 @@ test.scenarios('vlt_all')
test.compile()
test.execute(expect_filename=test.golden_filename)
test.execute()
test.passes()
+17 -2
View File
@@ -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
+6 -5
View File
@@ -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;
@@ -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
+7 -5
View File
@@ -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;
+4 -3
View File
@@ -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);
+69
View File
@@ -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
+15
View File
@@ -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)
+207
View File
@@ -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: <a0,b0> and <a1,b0>
`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
@@ -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
+16
View File
@@ -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()
+35
View File
@@ -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
+24
View File
@@ -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()
@@ -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 *-*
+18
View File
@@ -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()
@@ -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
+7 -3
View File
@@ -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);
+4
View File
@@ -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);
+41 -41
View File
@@ -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