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