Support dynamic cross layouts and fix covergroup bin exclusions priority (#8424)

This commit is contained in:
Marco Bartoli
2026-09-22 20:44:25 -04:00
committed by GitHub
parent 525d1fade3
commit 39e2d8e853
45 changed files with 2739 additions and 641 deletions
+766 -17
View File
@@ -25,6 +25,9 @@
#include "verilated.h"
#include <map>
#include <tuple>
// This file is compiled whenever covergroups are used, with or without
// "verilator --coverage" (see V3Global::verilatedCppFiles). Bin counts are
// owned by the covergroup instance nodes in the VerilatedContext's registry, so
@@ -38,6 +41,491 @@
#include "verilated_cov.h"
#endif
struct VlCoverpoint::ValueData final {
// CONSTANTS
static constexpr uint32_t QUERY_WORK_LIMIT = 1U << 20; // Maximum graph steps per query
static constexpr uint32_t QUERY_DEPTH_LIMIT = 1024; // Max width for recursive queries
// TYPES
// A value's words, held inline up to INLINE_WORDS so that common widths do not allocate
class Value final {
static constexpr uint32_t INLINE_WORDS = 2; // Words stored without an allocation
uint32_t m_size = 0; // Number of words
EData m_inline[INLINE_WORDS] = {0, 0}; // Words of a value up to INLINE_WORDS wide
std::vector<EData> m_heap; // Words of a wider value
public:
Value() = default;
Value(const EData* beginp, const EData* endp)
: m_size{static_cast<uint32_t>(endp - beginp)} {
if (m_size <= INLINE_WORDS) {
std::copy(beginp, endp, m_inline);
} else {
m_heap.assign(beginp, endp);
}
}
EData* data() { return m_size <= INLINE_WORDS ? m_inline : m_heap.data(); }
const EData* data() const { return m_size <= INLINE_WORDS ? m_inline : m_heap.data(); }
bool empty() const { return !m_size; }
void clear() {
m_size = 0;
m_heap.clear();
}
EData& operator[](uint32_t i) { return data()[i]; }
const EData& operator[](uint32_t i) const { return data()[i]; }
EData& back() { return data()[m_size - 1]; }
EData* begin() { return data(); }
EData* end() { return data() + m_size; }
const EData* begin() const { return data(); }
const EData* end() const { return data() + m_size; }
bool operator==(const Value& other) const {
return std::equal(begin(), end(), other.begin(), other.end());
}
};
struct Range final {
Value m_lo; // Inclusive lower bound and fixed-bit values for wildcard patterns
Value m_hi; // Inclusive upper bound in coverpoint value order
Value m_mask; // Significant wildcard bits; empty for an ordinary interval
};
struct Values final {
std::vector<Range> m_ranges; // Source intervals and patterns associated with this bin
bool m_transition = false; // State-value exclusions must not alter this transition bin
};
// Outcome of searching a range for a value outside every exclusion
enum class Search : uint8_t { EMPTY, VALUE, WORK_LIMIT, DEPTH_LIMIT };
class Query;
// MEMBERS
const uint32_t m_bits; // Width of the coverpoint's effective integral type
const uint32_t m_words; // EData words required to store one value
const bool m_isSigned; // Use signed ordering when comparing values
bool m_frozen = false; // Construction-time value metadata has been finalized
std::vector<Values> m_values; // Values by declared bin, released once crosses are built
std::vector<Range> m_exclusions; // Normalized state ignore/illegal ranges and patterns
uint32_t m_regularExclusions = 0; // Length of the merged interval prefix in m_exclusions
std::vector<uint32_t> m_reported; // Declared bins that have values, in declaration order
ValueData(uint32_t bits, bool isSigned, uint32_t bins)
: m_bits{bits}
, m_words{VL_WORDS_I(bits)}
, m_isSigned{isSigned}
, m_values{bins} {
assert(m_bits);
}
static WDataInP view(const Value& value) { return WDataInP::external(value.data()); }
Value read(WDataInP valuep) const {
Value result(valuep.datap(), valuep.datap() + m_words);
return result;
}
// Operands have already been cleaned to m_bits.
bool less(WDataInP lhs, WDataInP rhs) const {
if (m_isSigned) {
const EData leftSign = VL_SIGN_E(m_bits, lhs[m_words - 1]);
const EData rightSign = VL_SIGN_E(m_bits, rhs[m_words - 1]);
if (leftSign != rightSign) return leftSign;
}
for (uint32_t i = m_words; i > 0; --i) {
const EData left = lhs[i - 1];
const EData right = rhs[i - 1];
if (left != right) return left < right;
}
return false;
}
bool less(const Value& lhs, const Value& rhs) const { return less(view(lhs), view(rhs)); }
bool less(WDataInP lhs, const Value& rhs) const { return less(lhs, view(rhs)); }
bool less(const Value& lhs, WDataInP rhs) const { return less(view(lhs), rhs); }
void increment(Value& value) const {
for (EData& word : value) {
if (++word) break;
}
value.back() &= VL_MASK_E(m_bits);
}
bool contains(const Range& range, WDataInP value) const {
if (less(value, range.m_lo) || less(range.m_hi, value)) return false;
if (!range.m_mask.empty()) {
EData mismatch = 0;
for (uint32_t i = 0; i < m_words; ++i) {
mismatch |= (value[i] & range.m_mask[i]) ^ (range.m_lo[i] & range.m_mask[i]);
}
return mismatch == 0;
}
return true;
}
const Range* interval(const std::vector<Range>& ranges, uint32_t count, WDataInP value) const {
auto it = std::upper_bound(
ranges.begin(), ranges.begin() + count, value,
[&](WDataInP candidate, const Range& range) { return less(candidate, range.m_lo); });
if (it == ranges.begin()) return nullptr;
--it;
return contains(*it, value) ? &*it : nullptr;
}
uint32_t normalize(std::vector<Range>& ranges) const {
// Most bins have one ordered range, which needs no sorting or merging.
if (ranges.size() == 1 && !less(ranges[0].m_hi, ranges[0].m_lo)) {
return ranges[0].m_mask.empty() ? 1 : 0;
}
const auto middle = std::stable_partition(
ranges.begin(), ranges.end(), [](const Range& range) { return range.m_mask.empty(); });
std::sort(ranges.begin(), middle,
[&](const Range& lhs, const Range& rhs) { return less(lhs.m_lo, rhs.m_lo); });
std::vector<Range> merged;
for (auto it = ranges.begin(); it != middle; ++it) {
if (less(it->m_hi, it->m_lo)) continue;
if (!merged.empty()) {
Value adjacent = merged.back().m_hi;
increment(adjacent);
if (!less(merged.back().m_hi, it->m_lo) || adjacent == it->m_lo) {
if (less(merged.back().m_hi, it->m_hi)) merged.back().m_hi = it->m_hi;
continue;
}
}
merged.push_back(std::move(*it));
}
const uint32_t count = static_cast<uint32_t>(merged.size());
merged.insert(merged.end(), std::make_move_iterator(middle),
std::make_move_iterator(ranges.end()));
ranges = std::move(merged);
return count;
}
bool excluded(WDataInP value) const {
return interval(m_exclusions, m_regularExclusions, value)
|| std::any_of(m_exclusions.begin() + m_regularExclusions, m_exclusions.end(),
[&](const Range& range) { return contains(range, value); });
}
// Value bits, unlike wildcard mask bits, flip the sign bit for signed ordering.
bool orderBit(const Value& value, uint32_t bit) const {
return (VL_BITISSET_W(value, bit) != 0) ^ (m_isSigned && bit == m_bits - 1);
}
void setOrderBit(Value& value, uint32_t bit, bool ordered) const {
VL_ASSIGNBIT_II(bit, value[VL_BITWORD_E(bit)],
ordered ^ (m_isSigned && bit == m_bits - 1));
}
bool patternAtLeast(const Range& range, const Value& lower, Value& result) const {
if (range.m_mask.empty()) {
result = lower;
return true;
}
result = range.m_lo;
int32_t carry = -1;
bool greater = false;
for (uint32_t pos = m_bits; pos > 0;) {
const uint32_t bit = --pos;
const bool fixed = VL_BITISSET_W(range.m_mask, bit);
const bool low = orderBit(lower, bit);
const bool chosen = fixed ? orderBit(range.m_lo, bit) : greater ? false : low;
if (!greater && fixed && !chosen && low) {
if (carry < 0) return false;
setOrderBit(result, static_cast<uint32_t>(carry), true);
for (uint32_t tail = 0; tail < static_cast<uint32_t>(carry); ++tail) {
const uint32_t w = VL_BITWORD_E(tail);
VL_ASSIGNBIT_II(tail, result[w],
VL_BITISSET_E(range.m_lo[w] & range.m_mask[w], tail) != 0);
}
return true;
}
if (!greater && !fixed && !chosen) carry = static_cast<int32_t>(bit);
if (chosen != low) greater |= chosen && !low;
setOrderBit(result, bit, chosen);
}
return true;
}
bool clip(Range& range, const Value& lo, const Value& hi) const {
const Value lower = less(lo, range.m_lo) ? range.m_lo : lo;
const Value upper = less(range.m_hi, hi) ? range.m_hi : hi;
Value first;
if (less(upper, lower) || !patternAtLeast(range, lower, first) || less(upper, first)) {
return false;
}
range.m_lo = std::move(first);
range.m_hi = upper;
return true;
}
bool pattern(WDataInP valuep, WDataInP maskp, WDataInP lop, WDataInP hip,
Range& result) const {
result = {read(valuep), read(valuep), read(maskp)};
for (uint32_t i = 0; i < m_words; ++i) {
result.m_lo[i] &= result.m_mask[i];
result.m_hi[i] |= ~result.m_mask[i];
}
result.m_hi.back() &= VL_MASK_E(m_bits);
if (m_isSigned && !VL_SIGN_E(m_bits, result.m_mask.back())) {
VL_ASSIGNBIT_IO(m_bits - 1, result.m_lo.back());
VL_ASSIGNBIT_II(m_bits - 1, result.m_hi.back(), 0);
}
bool fixed = false;
bool contiguous = true;
for (uint32_t bit = 0; bit < m_bits; ++bit) {
if (VL_BITISSET_W(result.m_mask, bit)) {
fixed = true;
} else if (fixed) {
contiguous = false;
}
}
if (contiguous) result.m_mask.clear();
return clip(result, read(lop), read(hip));
}
Value lastValue(const Range& range) const {
Range reversed = range;
Value lower = range.m_hi;
for (uint32_t word = 0; word < m_words; ++word) {
reversed.m_lo[word] = ~reversed.m_lo[word];
lower[word] = ~lower[word];
}
reversed.m_lo.back() &= VL_MASK_E(m_bits);
lower.back() &= VL_MASK_E(m_bits);
Value result;
const bool found VL_ATTR_UNUSED = patternAtLeast(reversed, lower, result);
assert(found);
for (EData& word : result) word = ~word;
result.back() &= VL_MASK_E(m_bits);
return result;
}
Search hasValue(uint32_t bin, const Range& range) const;
Search intersects(uint32_t bin, const Range& filter) const;
};
// One bounded search. Shared ordered decisions avoid expanding the complement of wildcard
// exclusions; the graph is discarded with the query, so instances retain none of it.
class VlCoverpoint::ValueData::Query final {
struct Decision final {
uint32_t m_position; // One-based value-bit position; zero denotes a terminal
uint32_t m_low; // Child node ID for a zero-valued ordering bit
uint32_t m_high; // Child node ID for a one-valued ordering bit
uint32_t m_inverse; // Complement node ID, or UINT32_MAX if not cached
};
const ValueData& m_data; // Value width and ordering of the queried coverpoint
std::vector<Decision> m_decisions{{0, 0, 0, 1}, {0, 1, 1, 0}}; // Nodes; 0=false, 1=true
// (Position, low, high) -> canonical node ID
std::map<std::tuple<uint32_t, uint32_t, uint32_t>, uint32_t> m_unique;
std::map<std::pair<uint32_t, uint32_t>, uint32_t> m_combined; // Cached intersection roots
uint32_t m_work = 0; // Graph steps consumed by this query
bool m_limited = false; // The work limit was exceeded, so the result is unknown
bool step() {
if (m_limited) return false;
if (++m_work <= QUERY_WORK_LIMIT) return true;
m_limited = true;
return false;
}
uint32_t decision(uint32_t position, uint32_t low, uint32_t high) {
if (m_limited) return 0;
if (low == high) return low;
const auto key = std::make_tuple(position, low, high);
const auto it = m_unique.find(key);
if (it != m_unique.end()) return it->second;
const uint32_t result = static_cast<uint32_t>(m_decisions.size());
m_decisions.push_back({position, low, high, UINT32_MAX});
m_unique.emplace(key, result);
return result;
}
uint32_t rangeDecision(const Range& range, uint32_t position, uint32_t bounds,
std::vector<std::array<uint32_t, 4>>& cache) {
if (!step()) return 0;
uint32_t& cached = cache[position][bounds];
if (cached != UINT32_MAX) return cached;
const uint32_t bit = position - 1;
const uint32_t lower = m_data.orderBit(range.m_lo, bit);
const uint32_t upper = m_data.orderBit(range.m_hi, bit);
uint32_t children[2] = {0, 0};
for (uint32_t value = 0; value < 2; ++value) {
if ((!range.m_mask.empty() && VL_BITISSET_W(range.m_mask, bit) && value != lower)
|| ((bounds & 1U) && value < lower) || ((bounds & 2U) && value > upper)) {
continue;
}
const uint32_t next = ((bounds & 1U) && value == lower ? 1U : 0U)
| ((bounds & 2U) && value == upper ? 2U : 0U);
children[value] = rangeDecision(range, position - 1, next, cache);
}
cached = decision(position, children[0], children[1]);
return cached;
}
public:
explicit Query(const ValueData& data)
: m_data{data} {}
bool limited() const { return m_limited; }
uint32_t intersect(uint32_t lhs, uint32_t rhs) {
if (!step()) return 0;
if (lhs == rhs) return lhs;
if (!lhs || !rhs) return 0;
if (lhs == 1) return rhs;
if (rhs == 1) return lhs;
if (rhs < lhs) std::swap(lhs, rhs);
const auto key = std::make_pair(lhs, rhs);
const auto it = m_combined.find(key);
if (it != m_combined.end()) return it->second;
// Recursive calls can grow decisions, so do not retain references into it.
const Decision left = m_decisions[lhs];
const Decision right = m_decisions[rhs];
const uint32_t position = std::max(left.m_position, right.m_position);
const uint32_t low = intersect(left.m_position == position ? left.m_low : lhs,
right.m_position == position ? right.m_low : rhs);
const uint32_t high = intersect(left.m_position == position ? left.m_high : lhs,
right.m_position == position ? right.m_high : rhs);
const uint32_t result = decision(position, low, high);
m_combined.emplace(key, result);
return result;
}
uint32_t negate(uint32_t root) {
if (!step()) return 0;
if (m_decisions[root].m_inverse != UINT32_MAX) return m_decisions[root].m_inverse;
const Decision node = m_decisions[root];
const uint32_t low = negate(node.m_low);
const uint32_t high = negate(node.m_high);
const uint32_t result = decision(node.m_position, low, high);
m_decisions[root].m_inverse = result;
m_decisions[result].m_inverse = root;
return result;
}
uint32_t rangeRoot(const Range& range) {
if (m_data.less(range.m_hi, range.m_lo)) return 0;
std::vector<std::array<uint32_t, 4>> cache(m_data.m_bits + 1);
for (auto& entry : cache) entry.fill(UINT32_MAX);
cache[0].fill(1);
return rangeDecision(range, m_data.m_bits, 3, cache);
}
};
VlCoverpoint::ValueData::Search VlCoverpoint::ValueData::hasValue(uint32_t bin,
const Range& range) const {
if (m_values[bin].m_transition || m_exclusions.empty() || !excluded(view(range.m_lo))) {
return Search::VALUE;
}
const Value last = lastValue(range);
if (!excluded(view(last))) return Search::VALUE;
if (last == range.m_lo) return Search::EMPTY;
// Try cheap witnesses first; only difficult queries need a bounded symbolic search.
if (m_bits > QUERY_DEPTH_LIMIT) return Search::DEPTH_LIMIT;
Query query{*this};
uint32_t root = query.rangeRoot(range);
for (const Range& exclusion : m_exclusions) {
root = query.intersect(root, query.negate(query.rangeRoot(exclusion)));
if (!root) break;
}
if (query.limited()) return Search::WORK_LIMIT;
return root ? Search::VALUE : Search::EMPTY;
}
VlCoverpoint::ValueData::Search VlCoverpoint::ValueData::intersects(uint32_t bin,
const Range& filter) const {
Search result = Search::EMPTY;
for (const Range& source : m_values[bin].m_ranges) {
Range range = source;
if (!clip(range, filter.m_lo, filter.m_hi)) continue;
const Search search = hasValue(bin, range);
if (search == Search::VALUE) return search;
if (search != Search::EMPTY) result = search;
}
return result;
}
VlCoverpoint::VlCoverpoint() = default;
VlCoverpoint::~VlCoverpoint() = default;
void VlCoverpoint::valueType(uint32_t bits, bool isSigned) {
assert(!m_valuesp);
m_valuesp.reset(new ValueData{bits, isSigned, m_total});
}
void VlCoverpoint::valueRanges(std::initializer_list<EData> entries) {
ValueData& data = *m_valuesp;
assert(!data.m_frozen);
const uint32_t words = data.m_words;
assert(entries.size() % (1 + 2 * words) == 0);
for (const EData* entryp = entries.begin(); entryp != entries.end(); entryp += 1 + 2 * words) {
data.m_values[entryp[0]].m_ranges.push_back(
{data.read(WDataInP::external(entryp + 1)),
data.read(WDataInP::external(entryp + 1 + words)),
{}});
}
}
void VlCoverpoint::valuePatterns(std::initializer_list<EData> entries) {
ValueData& data = *m_valuesp;
assert(!data.m_frozen);
const uint32_t words = data.m_words;
assert(entries.size() % (1 + 4 * words) == 0);
for (const EData* entryp = entries.begin(); entryp != entries.end(); entryp += 1 + 4 * words) {
ValueData::Range range;
if (data.pattern(WDataInP::external(entryp + 1), WDataInP::external(entryp + 1 + words),
WDataInP::external(entryp + 1 + 2 * words),
WDataInP::external(entryp + 1 + 3 * words), range)) {
data.m_values[entryp[0]].m_ranges.push_back(std::move(range));
}
}
}
void VlCoverpoint::valueTransitions(std::initializer_list<uint32_t> bins) {
for (const uint32_t bin : bins) m_valuesp->m_values[bin].m_transition = true;
}
bool VlCoverpoint::liveBin(uint32_t bin) const {
const ValueData& data = *m_valuesp;
ValueData::Search limit = ValueData::Search::EMPTY;
for (const ValueData::Range& range : data.m_values[bin].m_ranges) {
const ValueData::Search search = data.hasValue(bin, range);
if (search == ValueData::Search::VALUE) return true;
if (search != ValueData::Search::EMPTY) limit = search;
}
if (limit == ValueData::Search::EMPTY) return false;
// Keep a bin whose exclusions cannot be analyzed, rather than stop the simulation.
const VlCovNamer& namer = namerFor(bin);
VL_WARN_MT(
namer.file(), namer.line(), "",
limit == ValueData::Search::WORK_LIMIT
? "Coverage bin exclusions exceed the decision-graph work limit; bin retained"
: "Coverage bin exclusions exceed the decision-graph depth limit; bin retained");
return true;
}
void VlCoverpoint::valueFinalize() {
ValueData& data = *m_valuesp;
assert(!data.m_frozen);
for (const VlCovNamer& namer : m_namers) {
const bool exclusion = namer.set() == VlCovBinKind::KIND_IGNORE
|| namer.set() == VlCovBinKind::KIND_ILLEGAL;
for (uint32_t bin = namer.base(); bin < namer.base() + namer.count(); ++bin) {
ValueData::Values& values = data.m_values[bin];
data.normalize(values.m_ranges);
if (exclusion && !values.m_transition) {
data.m_exclusions.insert(data.m_exclusions.end(), values.m_ranges.begin(),
values.m_ranges.end());
}
}
}
data.m_regularExclusions = data.normalize(data.m_exclusions);
m_crossToBin.clear();
std::fill(m_crossIdx.begin(), m_crossIdx.end(), -1);
m_normal = 0;
// Normal bins without values leave the report and the coverage denominator.
for (const VlCovNamer& namer : m_namers) {
const bool normal = namer.set() == VlCovBinKind::KIND_NORMAL;
for (uint32_t bin = namer.base(); bin < namer.base() + namer.count(); ++bin) {
if (normal && !liveBin(bin)) continue;
data.m_reported.push_back(bin);
if (!normal) continue;
m_crossIdx[bin] = static_cast<int>(m_normal++);
m_crossToBin.push_back(bin);
}
}
data.m_frozen = true;
}
void VlCoverpoint::valueRelease() {
ValueData& data = *m_valuesp;
assert(data.m_frozen);
std::vector<ValueData::Values>{}.swap(data.m_values);
}
bool VlCoverpoint::valueExcluded(QData value) const {
VlWide<VL_WQ_WORDS_E> words;
VL_SET_WQ(words, value);
return valueExcludedW(words);
}
bool VlCoverpoint::valueExcludedW(WDataInP valuep) const { return m_valuesp->excluded(valuep); }
void VlCoverpoint::init(const char* hier, uint32_t atLeast, uint32_t nBins) {
m_hier = hier;
m_atLeast = atLeast;
@@ -63,7 +551,7 @@ void VlCoverpoint::addNamer(VlCovBinKind set, uint32_t count, VlCovBinNaming nam
std::string VlCoverpoint::normalBinName(uint32_t crossIdx) const {
// Build the bin name based on the bin index
return binName(m_crossToBin[crossIdx]);
return declaredBinName(m_crossToBin[crossIdx]);
}
const VlCovNamer& VlCoverpoint::namerFor(uint32_t i) const {
@@ -75,19 +563,30 @@ const VlCovNamer& VlCoverpoint::namerFor(uint32_t i) const {
return *std::prev(it);
}
std::string VlCoverpoint::binName(uint32_t i) const {
const VlCovNamer& nm = namerFor(i);
std::string VlCoverpoint::declaredBinName(uint32_t bin) const {
const VlCovNamer& nm = namerFor(bin);
std::string name = nm.name();
if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(i - nm.base()) + ']';
if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(bin - nm.base()) + ']';
return name;
}
uint32_t VlCoverpoint::reportedBin(uint32_t i) const {
return m_valuesp ? m_valuesp->m_reported[i] : i;
}
uint32_t VlCoverpoint::binCount() const {
return m_valuesp ? static_cast<uint32_t>(m_valuesp->m_reported.size()) : m_total;
}
std::string VlCoverpoint::binName(uint32_t i) const { return declaredBinName(reportedBin(i)); }
#if VM_COVERAGE
void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) {
for (uint32_t i = 0; i < binCount(); ++i) {
for (uint32_t reported = 0; reported < binCount(); ++reported) {
const uint32_t i = reportedBin(reported);
const VlCovNamer& nm = namerFor(i);
const VlCovBinKind kind = binKind(i);
const std::string binp = binName(i);
const VlCovBinKind kind = binKind(reported);
const std::string binp = binName(reported);
const std::string full = m_hier + "." + binp;
const std::string lineStr = std::to_string(nm.line());
const std::string colStr = std::to_string(nm.col());
@@ -137,12 +636,18 @@ void VlCoverCross::init(const char* hier, uint32_t dims, VlCoverpoint* const* cp
void VlCoverCross::addBin(VlCovBinKind kind, std::initializer_list<uint64_t> selection,
const char* namep, const char* filep, int line, int col) {
if (!m_numAutoBins) return; // An empty product creates no cross bin.
addBinImpl(kind, selection.begin(), static_cast<uint32_t>(selection.size()), namep, filep,
line, col, m_explicitp->numBins);
}
void VlCoverCross::addBinImpl(VlCovBinKind kind, const uint64_t* sourcep, uint32_t words,
const char* namep, const char* filep, int line, int col,
uint32_t iffIndex) {
Explicit& data = *m_explicitp;
const uint32_t words = m_numAutoBins / 64 + (m_numAutoBins % 64 != 0);
assert(selection.size() == words);
assert(words == VL_BITWORD_Q(static_cast<uint64_t>(m_numAutoBins) + VL_QUADSIZE - 1));
assert(data.numBins < data.bins.size());
uint64_t* const selectionp = data.selectionp + static_cast<uint64_t>(data.numBins) * words;
std::copy(selection.begin(), selection.end(), selectionp);
std::copy(sourcep, sourcep + words, selectionp);
Bin& bin = data.bins[data.numBins++];
bin.selectionp = selectionp;
bin.namep = namep;
@@ -150,9 +655,11 @@ void VlCoverCross::addBin(VlCovBinKind kind, std::initializer_list<uint64_t> sel
bin.line = line;
bin.col = col;
bin.kind = kind;
bin.iffIndex = iffIndex;
if (kind == VlCovBinKind::KIND_NORMAL) ++data.normalBins;
uint32_t word = 0;
for (const uint64_t bits : selection) { data.wordsp[word++].autoExcluded |= bits; }
for (uint32_t word = 0; word < words; ++word) {
data.wordsp[word].autoExcluded |= selectionp[word];
}
}
void VlCoverCross::finalizeBins() {
@@ -226,7 +733,7 @@ void VlCoverCross::sampleSingleTuple(uint32_t idx, const bool* binIffs) {
return;
}
for (Bin& bin : data.bins) {
if (T_ApplyIffs && !*binIffs++) continue;
if (T_ApplyIffs && !binIffs[bin.iffIndex]) continue;
if (bin.selectionp[word] & bit) incrementBin(bin);
}
}
@@ -247,8 +754,8 @@ void VlCoverCross::sampleBins(const bool* binIffs) {
cached[i] = {word, wordsp[word].hitBits};
}
for (uint64_t binIdx = 0; binIdx < bins; ++binIdx) {
if (T_ApplyIffs && !*binIffs++) continue;
Bin& bin = data.bins[binIdx];
if (T_ApplyIffs && !binIffs[bin.iffIndex]) continue;
bool matched = false;
if (T_Touched == 1) {
matched = (bin.selectionp[cached[0].index] & cached[0].bits) != 0;
@@ -289,6 +796,7 @@ void VlCoverCross::sampleHitWords(const bool* binIffs) {
}
void VlCoverCross::sample(const bool* binIffs) {
if (VL_UNLIKELY(!m_numAutoBins)) return;
// Fast path: if any dimension had no Normal-bin hit, the cross cannot hit.
bool single = true;
for (uint32_t d = 0; d < m_dims; ++d) {
@@ -313,9 +821,9 @@ void VlCoverCross::sample(const bool* binIffs) {
return;
}
bool enabled = true;
if (hasExplicitBins() && binIffs && !binIffs[0]) {
const bool* const endp = binIffs + m_explicitp->bins.size();
enabled = std::find(binIffs + 1, endp, true) != endp;
if (hasExplicitBins() && binIffs && !binIffs[m_explicitp->bins[0].iffIndex]) {
enabled = std::any_of(m_explicitp->bins.begin() + 1, m_explicitp->bins.end(),
[binIffs](const Bin& bin) { return binIffs[bin.iffIndex]; });
if (!enabled && m_explicitp->autoBins.empty()) return;
}
for (uint32_t d = 0; d < m_dims; ++d) {
@@ -412,6 +920,247 @@ void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* pa
}
#endif // VM_COVERAGE
//=============================================================================
// VlCoverCrossDyn
class VlCoverCrossDyn::Layout final {
friend class VlCoverCrossDyn;
using Mask = std::vector<uint64_t>;
using Search = VlCoverpoint::ValueData::Search;
struct Selected final {
Bin m_info{}; // Declaration metadata, bin kind, and original iff index
Mask m_mask; // Tuple-selection bitmap for the declared bin
};
uint32_t m_tuples = 0; // Cartesian product of live coverpoint-bin counts
uint32_t m_words = 0; // 64-bit words per tuple-selection bitmap
std::vector<Dimension> m_dimensions; // Coverpoint bindings, hit lists, and tuple strides
std::vector<uint32_t> m_counts; // Dense automatic-bin counters, one slot per flat tuple ID
std::vector<Bin> m_bins; // Final compacted explicit-bin records
std::vector<Word> m_hitWords; // Auto-exclusion and hit masks, plus touched-word IDs
std::vector<uint32_t> m_autoBins; // Flat tuple IDs retained as automatic cross bins
std::vector<uint32_t> m_binWords; // Packed nonzero selection-word indices per explicit bin
std::vector<uint64_t> m_selections; // Contiguous bitmaps for finalized explicit bins
Explicit m_explicitData{{nullptr, 0},
nullptr,
{nullptr, 0},
{nullptr, 0},
nullptr}; // Storage views bound to the base sampler
std::vector<Mask> m_stack; // Postfix evaluation stack for construction-time selections
std::vector<Selected> m_selected; // Declared bins pending exclusion and compaction
uint32_t m_selectDimension = 0; // Dimension whose binsof selection is being built
uint32_t m_selectFirst = 0; // First declared coverpoint bin named by the binsof term
uint32_t m_selectEnd = 0; // One past the last declared coverpoint bin named by binsof
bool m_negate = false; // Complement the completed dimension membership mask
Search m_limit = Search::EMPTY; // A search limit left the current bin's selection unknown
std::vector<bool> m_allowed; // Normal-bin membership mask for the current dimension
static void setRange(Mask& mask, uint64_t first, uint64_t end) {
while (first < end) {
const uint64_t bit = VL_BITBIT_Q(first);
const uint64_t bits = std::min<uint64_t>(VL_QUADSIZE - bit, end - first);
mask[VL_BITWORD_Q(first)]
|= (bits == VL_QUADSIZE ? ~uint64_t{0} : (uint64_t{1} << bits) - 1) << bit;
first += bits;
}
}
bool named(uint32_t index) const {
const uint32_t bin = m_dimensions[m_selectDimension].cpp->m_crossToBin[index];
return bin >= m_selectFirst && bin < m_selectEnd;
}
void range(WDataInP lop, WDataInP hip) {
const VlCoverpoint* const cpp = m_dimensions[m_selectDimension].cpp;
const VlCoverpoint::ValueData& data = *cpp->m_valuesp;
const VlCoverpoint::ValueData::Range filter{data.read(lop), data.read(hip), {}};
for (uint32_t i = 0; i < m_allowed.size(); ++i) {
if (m_allowed[i] || !named(i)) continue;
const Search search = data.intersects(cpp->m_crossToBin[i], filter);
if (search == Search::VALUE) {
m_allowed[i] = true;
} else if (search != Search::EMPTY) {
m_limit = search;
}
}
}
};
VlCoverCrossDyn::VlCoverCrossDyn()
: VlCoverCross{0, 0}
, m_layoutp{new Layout} {}
VlCoverCrossDyn::~VlCoverCrossDyn() = default;
void VlCoverCrossDyn::init(const char* hier, uint32_t dims, VlCoverpoint* const* cps,
const char* file, int line, int col) {
Layout& data = *m_layoutp;
uint64_t tuples = std::any_of(cps, cps + dims,
[](const VlCoverpoint* cpp) { return !cpp->normalBinCount(); })
? 0
: 1;
for (uint32_t i = 0; i < dims; ++i) tuples *= cps[i]->normalBinCount();
// Verilation bounds the product of the declared bins, which live bins cannot exceed.
assert(tuples <= UINT32_MAX);
data.m_tuples = static_cast<uint32_t>(tuples);
data.m_words = VL_BITWORD_Q(static_cast<uint64_t>(data.m_tuples) + VL_QUADSIZE - 1);
data.m_dimensions.resize(dims);
data.m_counts.resize(data.m_tuples, 0);
shape(dims, data.m_tuples);
bindStorage(data.m_dimensions.data(), data.m_counts.data());
VlCoverCross::init(hier, dims, cps, file, line, col);
}
void VlCoverCrossDyn::selectAll() {
Layout& data = *m_layoutp;
data.m_stack.emplace_back(data.m_words, ~uint64_t{0});
if (data.m_words) data.m_stack.back().back() &= VL_MASK_Q(data.m_tuples);
}
void VlCoverCrossDyn::selectDim(uint32_t dim, uint32_t first, uint32_t end, bool negated,
bool intersect) {
Layout& data = *m_layoutp;
data.m_selectDimension = dim;
data.m_selectFirst = first;
data.m_selectEnd = end;
data.m_negate = negated;
data.m_allowed.assign(data.m_dimensions[dim].bins, false);
if (!intersect) {
for (uint32_t i = 0; i < data.m_allowed.size(); ++i) data.m_allowed[i] = data.named(i);
}
}
void VlCoverCrossDyn::selectRange(QData lo, QData hi) {
VlWide<VL_WQ_WORDS_E> low;
VlWide<VL_WQ_WORDS_E> high;
VL_SET_WQ(low, lo);
VL_SET_WQ(high, hi);
m_layoutp->range(low, high);
}
void VlCoverCrossDyn::selectRangeW(WDataInP lop, WDataInP hip) { m_layoutp->range(lop, hip); }
void VlCoverCrossDyn::selectDimEnd() {
Layout& data = *m_layoutp;
data.m_stack.emplace_back(data.m_words, 0);
Layout::Mask& mask = data.m_stack.back();
const Dimension& dim = data.m_dimensions[data.m_selectDimension];
// Each run of adjacent selected bins covers one contiguous tuple span per period.
std::vector<std::pair<uint32_t, uint32_t>> runs; // [first, end) bin indices
for (uint32_t i = 0; i < dim.bins;) {
if (data.m_allowed[i] == data.m_negate) {
++i;
continue;
}
const uint32_t begin = i++;
while (i < dim.bins && data.m_allowed[i] != data.m_negate) ++i;
runs.emplace_back(begin, i);
}
const uint64_t stride = dim.stride;
const uint64_t period = stride * dim.bins;
for (uint64_t base = 0; base < data.m_tuples; base += period) {
for (const auto& run : runs) {
Layout::setRange(mask, base + run.first * stride, base + run.second * stride);
}
}
}
void VlCoverCrossDyn::selectAnd() {
Layout& data = *m_layoutp;
Layout::Mask rhs = std::move(data.m_stack.back());
data.m_stack.pop_back();
for (uint32_t word = 0; word < data.m_words; ++word) data.m_stack.back()[word] &= rhs[word];
}
void VlCoverCrossDyn::selectOr() {
Layout& data = *m_layoutp;
Layout::Mask rhs = std::move(data.m_stack.back());
data.m_stack.pop_back();
for (uint32_t word = 0; word < data.m_words; ++word) data.m_stack.back()[word] |= rhs[word];
}
void VlCoverCrossDyn::selectBin(VlCovBinKind kind, const char* namep, const char* filep, int line,
int col, uint32_t iffIndex) {
Layout& data = *m_layoutp;
if (VL_UNLIKELY(data.m_limit != Layout::Search::EMPTY)) {
// Ignore a bin whose selection cannot be analyzed, rather than stop the simulation.
VL_WARN_MT(
filep, line, "",
data.m_limit == Layout::Search::WORK_LIMIT
? "Cross bin selection exceeds the decision-graph work limit; bin ignored"
: "Cross bin selection exceeds the decision-graph depth limit; bin ignored");
data.m_limit = Layout::Search::EMPTY;
data.m_stack.pop_back();
return;
}
Bin bin{};
bin.kind = kind;
bin.namep = namep;
bin.filep = filep;
bin.line = line;
bin.col = col;
bin.iffIndex = iffIndex;
data.m_selected.push_back({bin, std::move(data.m_stack.back())});
data.m_stack.pop_back();
}
void VlCoverCrossDyn::finalizeBins() {
Layout& data = *m_layoutp;
Layout::Mask excluded(data.m_words, 0);
for (const Layout::Selected& bin : data.m_selected) {
if (bin.m_info.kind == VlCovBinKind::KIND_NORMAL) continue;
for (uint32_t word = 0; word < data.m_words; ++word) excluded[word] |= bin.m_mask[word];
}
for (Layout::Selected& bin : data.m_selected) {
if (bin.m_info.kind != VlCovBinKind::KIND_NORMAL) continue;
for (uint32_t word = 0; word < data.m_words; ++word) bin.m_mask[word] &= ~excluded[word];
}
data.m_selected.erase(std::remove_if(data.m_selected.begin(), data.m_selected.end(),
[](const Layout::Selected& bin) {
return std::all_of(
bin.m_mask.begin(), bin.m_mask.end(),
[](uint64_t word) { return !word; });
}),
data.m_selected.end());
if (data.m_selected.empty()) return;
Layout::Mask occupied(data.m_words, 0);
uint64_t binWords = 0;
for (const Layout::Selected& bin : data.m_selected) {
for (uint32_t word = 0; word < data.m_words; ++word) {
occupied[word] |= bin.m_mask[word];
if (bin.m_mask[word]) ++binWords;
}
}
// Selection masks have no bits past m_tuples, so this counts the automatic bins.
uint32_t autoBins = data.m_tuples;
for (const uint64_t word : occupied) autoBins -= VL_COUNTONES_Q(word);
data.m_autoBins.resize(autoBins);
data.m_bins.resize(data.m_selected.size());
data.m_hitWords.resize(data.m_words);
data.m_binWords.resize(binWords);
data.m_selections.resize(data.m_selected.size() * data.m_words);
data.m_explicitData = {{data.m_bins.data(), data.m_bins.size()},
data.m_hitWords.data(),
{data.m_autoBins.data(), data.m_autoBins.size()},
{data.m_binWords.data(), data.m_binWords.size()},
data.m_selections.data()};
bindStorage(data.m_dimensions.data(), data.m_counts.data(), &data.m_explicitData);
for (const Layout::Selected& bin : data.m_selected) {
addBinImpl(bin.m_info.kind, bin.m_mask.data(), data.m_words, bin.m_info.namep,
bin.m_info.filep, bin.m_info.line, bin.m_info.col, bin.m_info.iffIndex);
}
VlCoverCross::finalizeBins();
data.m_selected.clear();
data.m_stack.clear();
}
//=============================================================================
// VlCovergroupInst
VlCoverCrossDyn* VlCovergroupInst::addCrossDyn() {
VlCoverCrossDyn* const cxp = new VlCoverCrossDyn{};
m_items.emplace_back(cxp);
return cxp;
}
//=============================================================================
// VlCovergroupType / VlCovRegistry
+76 -15
View File
@@ -99,6 +99,10 @@ public:
// VlCoverpointT<MaxHits> adds the inline hit-list array and the incrementBin write
// path; the cross holds VlCoverpoint* and reads via hitCount()/hitList().
class VlCoverpoint VL_NOT_FINAL : public VlCoverpointIf {
struct ValueData;
std::unique_ptr<ValueData> m_valuesp; // Optional value metadata and exclusion state
friend class VlCoverCrossDyn;
protected:
// MEMBERS (protected so VlCoverpointT::incrementBin can update them)
std::string m_hier; // "covergroup.coverpoint"
@@ -120,10 +124,15 @@ private:
const VlCovNamer& namerFor(uint32_t i) const; // obtain the bin-specific name producer
void addNamer(VlCovBinKind set, uint32_t count, VlCovBinNaming naming, const char* name,
const char* file, int line, int col);
// Declared bin index of the i-th bin reported through VlCoverpointIf
uint32_t reportedBin(uint32_t i) const;
std::string declaredBinName(uint32_t bin) const; // Name of a declared bin index
bool liveBin(uint32_t bin) const; // Normal bin keeps a value outside the exclusions
public:
// CONSTRUCTORS
VlCoverpoint() = default;
VlCoverpoint();
~VlCoverpoint() override;
// METHODS
// ---- configuration (from generated constructor) ----
@@ -137,6 +146,22 @@ public:
}
void registerBins(VerilatedCovContext* covcontextp, const char* page);
/// Configure construction-time value metadata for exclusions and cross selections.
void valueType(uint32_t bits, bool isSigned);
/// Describe bin values as {bin, low words, high words} entries, without enumerating them.
void valueRanges(std::initializer_list<EData> entries);
/// Describe wildcard patterns as {bin, value words, mask words, low words, high words}.
void valuePatterns(std::initializer_list<EData> entries);
/// State exclusions do not remove values from these transition bins.
void valueTransitions(std::initializer_list<uint32_t> bins);
/// Apply exclusions and freeze the live Normal-bin index space used by crosses.
void valueFinalize();
/// Drop the per-bin values once every cross has been built; sampling needs only exclusions.
void valueRelease();
/// Test state exclusions independently of sampling-time iff guards.
bool valueExcluded(QData value) const;
bool valueExcludedW(WDataInP valuep) const;
// ---- hot path (from generated sample()) ----
// Clear the hit list at the start of each sample() for a cross-fed coverpoint.
void clearHitList() { m_hitCount = 0; }
@@ -152,19 +177,17 @@ public:
std::string normalBinName(uint32_t crossIdx) const; // name of the crossIdx-th Normal bin
// ---- VlCoverpointIf ----
uint32_t binCount() const override { return m_total; }
/// Bins removed for having no value (IEEE 1800-2023 19.11.1) are not reported.
uint32_t binCount() const override;
std::string binName(uint32_t i) const override;
// Deliberately not on VlCoverpointIf: only coverage-database registration needs it.
VlCovBinKind binKind(uint32_t i) const { return namerFor(i).set(); }
VlCovBinKind binKind(uint32_t i) const { return namerFor(reportedBin(i)).set(); }
void coverageParts(double& covered, double& total) const override {
// Count Normal bins that reached option.at_least on demand, so the hot
// path (incrementBin) stays a plain counter bump.
uint32_t numCovered = 0;
for (const VlCovNamer& nm : m_namers) {
if (nm.set() != VlCovBinKind::KIND_NORMAL) continue;
for (uint32_t i = nm.base(); i < nm.base() + nm.count(); ++i) {
if (m_counts[i] >= m_atLeast) ++numCovered;
}
for (const uint32_t bin : m_crossToBin) {
if (m_counts[bin] >= m_atLeast) ++numCovered;
}
covered = numCovered;
total = m_normal;
@@ -211,7 +234,7 @@ public:
/// built on demand for automatic bins; explicit bins select sets of tuples
/// and replace the corresponding automatic cross bins. Explicit selections
/// are intersected with hit-tuple words once per sample.
/// VlCoverCrossT owns the fixed arrays. This shared core does not allocate bin
/// VlCoverCrossT and VlCoverCrossDyn own their storage. This shared core does not allocate bin
/// storage, and its borrowed storage pointers remain valid for the instance.
class VlCoverCross VL_NOT_FINAL : public VlCoverpointIf {
@@ -223,15 +246,16 @@ protected:
uint32_t stride; // Flat-index stride
};
struct Bin final {
const uint64_t* selectionp; // Slice of the fixed selection storage
const uint64_t* selectionp; // Slice of the cross's selection storage
const char* namep; // Explicit bin name
const char* filep; // Bin declaration file
const uint32_t* wordIndicesp = nullptr; // Slice of the packed selection-word indices
int line; // Bin declaration line
int col; // Bin declaration column
VlCovBinKind kind = VlCovBinKind::KIND_NORMAL; // Normal, ignore, or illegal bin
uint32_t count = 0; // Samples matching the selection and guard
uint32_t numWords = 0; // Number of nonzero selection-word indices
const uint32_t* wordIndicesp = nullptr; // Slice of the packed selection-word indices
uint32_t iffIndex = 0; // Original guard index, including bins removed during finalization
};
struct Word final {
uint64_t autoExcluded = 0; // Tuples replaced by explicit bins
@@ -278,7 +302,7 @@ private:
// storable anyway: m_flatCountsp alone would need 16GB.
uint32_t m_numAutoBins = 0; // Product of per-dim Normal bin counts
uint32_t m_numCovered = 0; // Distinct bins hit >= 1 (maintained incrementally)
Dimension* m_dimensionsp = nullptr; // [m_dims], owned by VlCoverCrossT
Dimension* m_dimensionsp = nullptr; // [m_dims], owned by the concrete cross runtime
uint32_t* m_flatCountsp = nullptr; // [m_numAutoBins] Per-bin hit counts
Explicit* m_explicitp = nullptr; // Absent for automatic-only crosses
@@ -326,19 +350,25 @@ protected:
m_flatCountsp = countsp;
m_explicitp = explicitp;
}
void shape(uint32_t dims, uint32_t tuples) {
m_dims = dims;
m_numAutoBins = tuples;
}
void addBinImpl(VlCovBinKind kind, const uint64_t* selectionp, uint32_t words,
const char* namep, const char* filep, int line, int col, uint32_t iffIndex);
public:
VL_UNCOPYABLE(VlCoverCross);
// METHODS
// ---- configuration (from generated constructor, after coverpoints init'd) ----
void init(const char* hier, uint32_t dims, VlCoverpoint* const* cps, const char* file,
int line, int col);
virtual void init(const char* hier, uint32_t dims, VlCoverpoint* const* cps, const char* file,
int line, int col);
/// Add a cross bin using a verilation-time bitmap of selected Normal-bin tuples.
void addBin(VlCovBinKind kind, std::initializer_list<uint64_t> selection, const char* namep,
const char* filep, int line, int col);
/// Retain only automatic cross bins not selected by any explicit bin.
void finalizeBins();
virtual void finalizeBins();
void registerBins(VerilatedCovContext* covcontextp, const char* page);
// ---- hot path (from generated sample(), after all coverpoints sampled) ----
@@ -406,6 +436,36 @@ public:
}
};
// Construction-time cross layout over finalized coverpoints, sharing the sampling core.
class VlCoverCrossDyn final : public VlCoverCross {
class Layout;
std::unique_ptr<Layout> m_layoutp; // Owned cross storage and construction-time selections
public:
// CONSTRUCTORS
VlCoverCrossDyn();
~VlCoverCrossDyn() override;
// METHODS
/// Initialize after all feeding coverpoints have finalized their live bins.
void init(const char* hier, uint32_t dims, VlCoverpoint* const* cps, const char* file,
int line, int col) override;
/// Build cross-bin selections in postfix order.
void selectAll();
/// Start a binsof term over the live bins declared in [first, end) of dimension 'dim'.
void selectDim(uint32_t dim, uint32_t first, uint32_t end, bool negated, bool intersect);
void selectRange(QData lo, QData hi);
void selectRangeW(WDataInP lop, WDataInP hip);
void selectDimEnd();
void selectAnd();
void selectOr();
// Save a selection without renumbering guards when empty bins are removed.
void selectBin(VlCovBinKind kind, const char* namep, const char* filep, int line, int col,
uint32_t iffIndex);
/// Apply cross exclusions and bind finalized storage to the sampling core.
void finalizeBins() override;
};
class VlCovergroupType;
//=============================================================================
@@ -456,6 +516,7 @@ public:
m_items.emplace_back(cxp);
return cxp; // borrowed by the generated class
}
VlCoverCrossDyn* addCrossDyn();
// ---- attach counting (from VlCovInstHandle) ----
void attachInc() { ++m_attachCount; }
+17
View File
@@ -874,6 +874,7 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) {
macro(COVERGROUP_ADD_BIN, "addBin", false, "r+") \
macro(COVERGROUP_ADD_COVERPOINT, "addCoverpoint", false, "") \
macro(COVERGROUP_ADD_CROSS, "addCross", false, "") \
macro(COVERGROUP_ADD_CROSS_DYN, "addCrossDyn", false, "") \
macro(COVERGROUP_ADD_SINGLE_NAMER, "addSingleNamer", false, "r+") \
macro(COVERGROUP_ATTACH, "attach", false, "r") \
macro(COVERGROUP_CLEAR_HIT_LIST, "clearHitList", false, "") \
@@ -886,6 +887,22 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) {
macro(COVERGROUP_REGISTER_BINS, "registerBins", false, "rr") \
macro(COVERGROUP_SAMPLE, "sample", false, "") \
macro(COVERGROUP_SAMPLE_IFFS, "sample", false, "r") \
macro(COVERGROUP_SELECT_ALL, "selectAll", false, "") \
macro(COVERGROUP_SELECT_AND, "selectAnd", false, "") \
macro(COVERGROUP_SELECT_BIN, "selectBin", false, "r+") \
macro(COVERGROUP_SELECT_DIM, "selectDim", false, "r+") \
macro(COVERGROUP_SELECT_DIM_END, "selectDimEnd", false, "") \
macro(COVERGROUP_SELECT_OR, "selectOr", false, "") \
macro(COVERGROUP_SELECT_RANGE, "selectRange", false, "rr") \
macro(COVERGROUP_SELECT_RANGE_W, "selectRangeW", false, "rr") \
macro(COVERGROUP_VALUE_EXCLUDED, "valueExcluded", PURE, "r") \
macro(COVERGROUP_VALUE_EXCLUDED_W, "valueExcludedW", PURE, "r") \
macro(COVERGROUP_VALUE_FINALIZE, "valueFinalize", false, "") \
macro(COVERGROUP_VALUE_PATTERNS, "valuePatterns", false, "r") \
macro(COVERGROUP_VALUE_RANGES, "valueRanges", false, "r") \
macro(COVERGROUP_VALUE_RELEASE, "valueRelease", false, "") \
macro(COVERGROUP_VALUE_TRANSITIONS, "valueTransitions", false, "r") \
macro(COVERGROUP_VALUE_TYPE, "valueType", false, "rr") \
macro(DYN_AT_WRITE_APPEND, "atWriteAppend", false, "r") \
macro(DYN_AT_WRITE_APPEND_BACK, "atWriteAppendBack", false, "r") \
macro(DYN_CLEAR, "clear", false, "") \
+10 -6
View File
@@ -691,22 +691,24 @@ public:
bool isCompound() const override { return false; }
};
class AstCoverCrossDType final : public AstNodeDType {
// Borrowed pointer to VlCoverCrossT<dimensions, tuples, bins, autoBins, binWords>.
// Borrowed pointer to VlCoverCrossT<...> or construction-time VlCoverCrossDyn.
const uint32_t m_dimensions;
const uint32_t m_tuples; // Fixed capacity number of cross tuples (VlCoverCrossT's Tuples)
const uint32_t m_bins; // Fixed capacity number of explicit bins (VlCoverCrossT's Bins)
const uint32_t m_autoBins; // Fixed capacity number of auto bins (VlCoverCrossT's AutoBins)
const uint64_t m_binWords; // Fixed capacity selection words (VlCoverCrossT's BinWords)
const bool m_dynamic; // Bin layout is determined at covergroup construction
public:
AstCoverCrossDType(FileLine* fl, uint32_t dimensions, uint32_t tuples, uint32_t bins,
uint32_t autoBins, uint64_t binWords)
uint32_t autoBins, uint64_t binWords, bool dynamic = false)
: ASTGEN_SUPER_CoverCrossDType(fl)
, m_dimensions{dimensions}
, m_tuples{tuples}
, m_bins{bins}
, m_autoBins{autoBins}
, m_binWords{binWords} {
, m_binWords{binWords}
, m_dynamic{dynamic} {
dtypep(this);
}
ASTGEN_MEMBERS_AstCoverCrossDType;
@@ -714,11 +716,12 @@ public:
BROKEN_RTN(m_dimensions == 0);
return nullptr;
}
bool sameNode(const AstNode* samep) const override {
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
const AstCoverCrossDType* const sp = VN_DBG_AS(samep, CoverCrossDType);
return dimensions() == sp->dimensions() && tuples() == sp->tuples() && bins() == sp->bins()
&& autoBins() == sp->autoBins() && binWords() == sp->binWords();
}
&& autoBins() == sp->autoBins() && binWords() == sp->binWords()
&& isDynamic() == sp->isDynamic();
} // LCOV_EXCL_STOP
bool similarDTypeNode(const AstNodeDType* samep) const override { return this == samep; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
@@ -728,6 +731,7 @@ public:
uint32_t bins() const { return m_bins; }
uint32_t autoBins() const { return m_autoBins; }
uint64_t binWords() const { return m_binWords; }
bool isDynamic() const { return m_dynamic; }
string cppTemplateArgs() const;
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
int widthAlignBytes() const override { return sizeof(void*); }
+4 -1
View File
@@ -1224,6 +1224,7 @@ string AstCoverCrossDType::cppTemplateArgs() const {
void AstCoverCrossDType::dump(std::ostream& str) const {
Super::dump(str);
str << " [" << cppTemplateArgs() << "]";
if (isDynamic()) str << " [DYNAMIC]";
}
void AstCoverCrossDType::dumpJson(std::ostream& str) const {
dumpJsonNumFunc(str, dimensions);
@@ -1231,6 +1232,7 @@ void AstCoverCrossDType::dumpJson(std::ostream& str) const {
dumpJsonNumFunc(str, bins);
dumpJsonNumFunc(str, autoBins);
dumpJsonNumFunc(str, binWords);
dumpJsonBoolFuncIf(str, isDynamic);
dumpJsonGen(str);
}
void AstCoverCrossDType::dumpSmall(std::ostream& str) const {
@@ -2182,7 +2184,8 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe
info.m_type += ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, CoverCrossDType)) {
UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union");
info.m_type = "VlCoverCrossT<" + adtypep->cppTemplateArgs() + ">*";
info.m_type = adtypep->isDynamic() ? "VlCoverCrossDyn*"
: "VlCoverCrossT<" + adtypep->cppTemplateArgs() + ">*";
} else if (const auto* const adtypep = VN_CAST(dtypep, CoverpointDType)) {
UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union");
info.m_type = "VlCoverpointT<" + cvtToStr(adtypep->hitBound()) + ">*";
+681 -468
View File
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -12,16 +12,18 @@ cg2.cp_data3.auto_2: 1
cg2.cp_data3.auto_3: 0
cg3.cp_data3.auto_0: 1
cg3.cp_data3.auto_1: 1
cg4.cp.auto_0: 0
cg4.cp.auto_1: 1
cg4.cp.auto_2: 1
cg4.cp.auto_3: 1
cg4.cp.ign [ignore]: 0
cg5.cp_data64.auto_0: 1
cg5.cp_data64.auto_1: 1
cg5.cp_data64.auto_2: 1
cg5.cp_data64.auto_0: 2
cg5.cp_data64.auto_1: 0
cg5.cp_data64.auto_2: 0
cg5.cp_data64.auto_3: 1
cg6.cp_data3.auto_0: 1
cg6.cp_data3.auto_1: 0
cg6.cp_data3.auto_2: 0
cg6.cp_data3.auto_3: 1
cg7.cp_sdata3.auto_0: 1
cg7.cp_sdata3.auto_1: 1
cg7.cp_sdata3.auto_2: 2
+27 -4
View File
@@ -16,6 +16,7 @@ module t;
logic [2:0] data3;
logic [3:0] data4;
logic [63:0] data64; // 64-bit signal
logic signed [2:0] sdata3;
// Test 1: auto_bin_max default (64) - creates 8 bins for 3-bit signal
covergroup cg1;
@@ -58,6 +59,13 @@ module t;
cp_data3: coverpoint data3;
endgroup
// Test 7: signed values are partitioned in value order, the last bin taking the remainder:
// [-4:-3],[-2:-1],[0:3]
covergroup cg7;
option.auto_bin_max = 3;
cp_sdata3: coverpoint sdata3;
endgroup
initial begin
cg1 cg1_inst;
cg2 cg2_inst;
@@ -65,6 +73,7 @@ module t;
cg4 cg4_inst;
cg5 cg5_inst;
cg6 cg6_inst;
cg7 cg7_inst;
cg1_inst = new;
cg2_inst = new;
@@ -72,6 +81,7 @@ module t;
cg4_inst = new;
cg5_inst = new;
cg6_inst = new;
cg7_inst = new;
data3 = 0;
cg1_inst.sample();
@@ -97,23 +107,25 @@ module t;
// Sample valid (non-ignored) values for cg4
// cg4: auto_bin_max=4 creates 4 bins [0:3],[4:7],[8:11],[12:15].
// ignore_bins ign={[0:3]} excludes [0:3] values; Verilator keeps all 4 bins in denominator.
// 3 of 4 bins hit -> 75% (the [0:3] bin is included in denominator but can never be hit)
// The empty [0:3] bin is excluded from the denominator (IEEE 1800-2023 19.11.1).
data4 = 4;
cg4_inst.sample(); // [4:7] bin
data4 = 8;
cg4_inst.sample(); // [8:11] bin
data4 = 12;
cg4_inst.sample(); // [12:15] bin
`checkr(cg4_inst.get_inst_coverage(), 75.0);
`checkr(cg4_inst.get_inst_coverage(), 100.0);
// Sample cg5: 64-bit coverpoint - SKIP: Verilator 64-bit bin boundary bug causes 100% at first sample
// Sample cg5: the full 64-bit domain is partitioned into four bins.
data64 = 64'h0;
cg5_inst.sample();
`checkr(cg5_inst.get_inst_coverage(), 25.0);
data64 = 64'h1111111111111111;
cg5_inst.sample();
`checkr(cg5_inst.get_inst_coverage(), 25.0);
data64 = 64'hffffffffffffffff;
cg5_inst.sample();
`checkr(cg5_inst.get_inst_coverage(), 50.0);
data3 = 0;
cg6_inst.sample();
@@ -122,6 +134,17 @@ module t;
cg6_inst.sample();
`checkr(cg6_inst.get_inst_coverage(), 50.0); // 2/4 bins hit: [0:1],[6:7]
sdata3 = -4;
cg7_inst.sample(); // [-4:-3]
sdata3 = 3;
cg7_inst.sample(); // [0:3]
`checkr(cg7_inst.get_inst_coverage(), 100.0 * 2.0 / 3.0);
sdata3 = 0;
cg7_inst.sample(); // [0:3]
sdata3 = -1;
cg7_inst.sample(); // [-2:-1]
`checkr(cg7_inst.get_inst_coverage(), 100.0);
$write("*-* All Finished *-*\n");
$finish;
end
@@ -0,0 +1,183 @@
cg_cross.aa.auto_1: 4
cg_cross.aa.auto_2: 5
cg_cross.aa.high [illegal]: 0
cg_cross.aa.low [ignore]: 0
cg_cross.automatic_cross.auto_1_x_values[0] [cross]: 1
cg_cross.automatic_cross.auto_1_x_values[2] [cross]: 1
cg_cross.automatic_cross.auto_1_x_values[3] [cross]: 1
cg_cross.automatic_cross.auto_2_x_values[0] [cross]: 1
cg_cross.automatic_cross.auto_2_x_values[2] [cross]: 2
cg_cross.automatic_cross.auto_2_x_values[3] [cross]: 1
cg_cross.bb.ignored [ignore]: 2
cg_cross.bb.values[0]: 2
cg_cross.bb.values[2]: 3
cg_cross.bb.values[3]: 2
cg_cross.xx.all [cross]: 2
cg_cross.xx.column [cross]: 1
cg_cross.xx.forbidden [illegal,cross]: 0
cg_cross.xx.grouped [cross]: 3
cg_cross.xx.ignored [ignore,cross]: 1
cg_cross.xx.row [cross]: 1
cg_empty.aa.ignored [ignore]: 0
cg_empty.bb.auto_0: 0
cg_empty.bb.auto_1: 1
cg_empty.bb.auto_2: 1
cg_empty.bb.auto_3: 0
cg_explicit.cp.all: 5
cg_explicit.cp.forbidden [illegal]: 0
cg_explicit.cp.ignored[0] [ignore]: 1
cg_explicit.cp.ignored[1] [ignore]: 1
cg_explicit.cp.mixed: 1
cg_explicit.cp.values[1]: 1
cg_explicit.cp.values[2]: 1
cg_many_values.cp.ignored [ignore]: 0
cg_many_values.cp.odd: 2
cg_partition.cp.auto_1: 2
cg_partition.cp.auto_2: 1
cg_partition.cp.auto_3: 1
cg_partition.cp.ignored [ignore]: 4
cg_pattern_carry.cp_signed.ignored [ignore]: 1
cg_pattern_carry.cp_signed.patterned: 4
cg_pattern_carry.cp_unsigned.ignored [ignore]: 1
cg_pattern_carry.cp_unsigned.patterned: 4
cg_pattern_carry.other.auto_0: 8
cg_pattern_carry.other.auto_1: 8
cg_pattern_carry.signed_cross.carry [cross]: 4
cg_pattern_carry.unsigned_cross.carry [cross]: 4
cg_projection.negative.keep: 1
cg_projection.negative.positive [ignore]: 1
cg_projection.positive.keep: 1
cg_projection.positive.negative [ignore]: 1
cg_real_point.cp.low: 1
cg_real_point.cp.middle: 1
cg_real_point.cp.three: 1
cg_real_values.cp.high: 1
cg_real_values.cp.ignored [ignore]: 1
cg_real_values.cp.low: 1
cg_real_values.cp.middle: 2
cg_real_values.cp.two: 1
cg_real_values.plain.middle: 2
cg_real_values.plain.two: 1
cg_resolution.cp.clipped: 4
cg_resolution.cp.kept: 4
cg_resolution.cp.rest [default]: 4
cg_resolution.cp.values[0]: 2
cg_resolution.cx.clipped_x_auto_0 [cross]: 2
cg_resolution.cx.clipped_x_auto_1 [cross]: 2
cg_resolution.cx.kept_values [cross]: 4
cg_resolution.cx.values[0]_x_auto_0 [cross]: 1
cg_resolution.cx.values[0]_x_auto_1 [cross]: 1
cg_resolution.other.auto_0: 7
cg_resolution.other.auto_1: 7
cg_resolution_excl.cp.clipped: 4
cg_resolution_excl.cp.kept: 4
cg_resolution_excl.cp.rest [default]: 4
cg_resolution_excl.cp.unused [ignore]: 0
cg_resolution_excl.cp.values[0]: 2
cg_resolution_excl.cx.clipped_x_auto_0 [cross]: 2
cg_resolution_excl.cx.clipped_x_auto_1 [cross]: 2
cg_resolution_excl.cx.kept_values [cross]: 4
cg_resolution_excl.cx.values[0]_x_auto_0 [cross]: 1
cg_resolution_excl.cx.values[0]_x_auto_1 [cross]: 1
cg_resolution_excl.other.auto_0: 7
cg_resolution_excl.other.auto_1: 7
cg_signed.cp.auto_1: 1
cg_signed.cp.auto_2: 1
cg_signed.cp.ignored [ignore]: 1
cg_signed_wide.cp.auto_1: 2
cg_signed_wide.cp.auto_2: 1
cg_signed_wide.cp.auto_3: 1
cg_signed_wide.cp.ignored [ignore]: 1
cg_signed_wide.cx.auto_2_x_auto_0 [cross]: 1
cg_signed_wide.cx.auto_2_x_auto_1 [cross]: 0
cg_signed_wide.cx.auto_3_x_auto_0 [cross]: 0
cg_signed_wide.cx.negative [cross]: 2
cg_signed_wide.cx.positive [cross]: 1
cg_signed_wide.other.auto_0: 3
cg_signed_wide.other.auto_1: 2
cg_source_width.cp.ignored [ignore]: 0
cg_source_width.cp.keep: 2
cg_unsigned_pattern.cp.all_values: 1
cg_unsigned_pattern.cp.ignored [ignore]: 0
cg_unsigned_pattern.cp.negative: 1
cg_unsigned_pattern.cp.negative_pair: 1
cg_wide.cp.auto_1: 1
cg_wide.cp.auto_2: 1
cg_wide.cp.auto_3: 1
cg_wide.cp.ignored [ignore]: 1
cg_wide_cross.aa.all: 129
cg_wide_cross.aa.high[0]: 2
cg_wide_cross.aa.high[10]: 2
cg_wide_cross.aa.high[11]: 2
cg_wide_cross.aa.high[12]: 2
cg_wide_cross.aa.high[13]: 2
cg_wide_cross.aa.high[14]: 2
cg_wide_cross.aa.high[15]: 2
cg_wide_cross.aa.high[16]: 2
cg_wide_cross.aa.high[17]: 2
cg_wide_cross.aa.high[18]: 2
cg_wide_cross.aa.high[19]: 2
cg_wide_cross.aa.high[1]: 2
cg_wide_cross.aa.high[20]: 2
cg_wide_cross.aa.high[21]: 2
cg_wide_cross.aa.high[22]: 2
cg_wide_cross.aa.high[23]: 2
cg_wide_cross.aa.high[24]: 2
cg_wide_cross.aa.high[25]: 2
cg_wide_cross.aa.high[26]: 2
cg_wide_cross.aa.high[27]: 2
cg_wide_cross.aa.high[28]: 2
cg_wide_cross.aa.high[29]: 2
cg_wide_cross.aa.high[2]: 2
cg_wide_cross.aa.high[30]: 2
cg_wide_cross.aa.high[31]: 2
cg_wide_cross.aa.high[3]: 2
cg_wide_cross.aa.high[4]: 2
cg_wide_cross.aa.high[5]: 2
cg_wide_cross.aa.high[6]: 2
cg_wide_cross.aa.high[7]: 2
cg_wide_cross.aa.high[8]: 2
cg_wide_cross.aa.high[9]: 2
cg_wide_cross.aa.ignored [ignore]: 2
cg_wide_cross.aa.low[10]: 2
cg_wide_cross.aa.low[11]: 2
cg_wide_cross.aa.low[12]: 2
cg_wide_cross.aa.low[13]: 2
cg_wide_cross.aa.low[14]: 2
cg_wide_cross.aa.low[15]: 2
cg_wide_cross.aa.low[16]: 2
cg_wide_cross.aa.low[17]: 2
cg_wide_cross.aa.low[18]: 2
cg_wide_cross.aa.low[19]: 2
cg_wide_cross.aa.low[1]: 3
cg_wide_cross.aa.low[20]: 2
cg_wide_cross.aa.low[21]: 2
cg_wide_cross.aa.low[22]: 2
cg_wide_cross.aa.low[23]: 2
cg_wide_cross.aa.low[24]: 2
cg_wide_cross.aa.low[25]: 2
cg_wide_cross.aa.low[26]: 2
cg_wide_cross.aa.low[27]: 2
cg_wide_cross.aa.low[28]: 2
cg_wide_cross.aa.low[29]: 2
cg_wide_cross.aa.low[2]: 2
cg_wide_cross.aa.low[30]: 2
cg_wide_cross.aa.low[31]: 2
cg_wide_cross.aa.low[32]: 2
cg_wide_cross.aa.low[3]: 2
cg_wide_cross.aa.low[4]: 2
cg_wide_cross.aa.low[5]: 2
cg_wide_cross.aa.low[6]: 2
cg_wide_cross.aa.low[7]: 2
cg_wide_cross.aa.low[8]: 2
cg_wide_cross.aa.low[9]: 2
cg_wide_cross.aa.middle: 129
cg_wide_cross.bb.auto_0: 66
cg_wide_cross.bb.auto_1: 65
cg_wide_cross.xx.disabled [cross]: 0
cg_wide_cross.xx.enabled_bin [cross]: 128
cg_wide_cross.xx.tail [cross]: 128
cg_wild.cp.ignored [ignore]: 1
cg_wild.cp.odd_values: 2
cg_wild_empty.cp.even_values [ignore]: 2
cg_wild_empty.cp.odd_values [ignore]: 2
+19
View File
@@ -0,0 +1,19 @@
#!/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,
verilator_flags2=['--timing', '--dump-tree', '--dump-tree-json'],
threads=(2 if test.vltmt else 1))
test.passes()
@@ -0,0 +1,384 @@
// 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
// 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
);
int cyc = 0;
// IEEE 1800-2023 19.11.1: retain auto_1={2,3}, auto_2={4}, auto_3={7}.
covergroup cg_partition with function sample (bit [2:0] value);
option.auto_bin_max = 4;
cp: coverpoint value {
ignore_bins ignored = {0, 1, 5, 6};
}
endgroup
covergroup cg_explicit with function sample (bit [2:0] value);
cp: coverpoint value {
bins all = {[0 : 7]};
bins empty = {0, 1};
bins mixed = {1, 3, 5};
bins values[] = {0, 2, 4};
wildcard bins outside_domain = {4'b1???};
ignore_bins ignored[] = {0, 1};
illegal_bins forbidden = {5} iff (0);
}
endgroup
covergroup cg_cross with function sample (bit [3:0] a, bit [1:0] b, bit enabled);
option.auto_bin_max = 4;
aa: coverpoint a {
ignore_bins low = {[0 : 3]};
illegal_bins high = {[12 : 15]} iff (0);
}
bb: coverpoint b {
bins values[] = {[0 : 3]};
ignore_bins ignored = {1};
}
automatic_cross: cross aa, bb;
xx: cross aa, bb{
bins empty = binsof (aa) intersect {0} iff (0);
bins removed = binsof (aa) intersect {8} && binsof (bb) intersect {3} iff (0);
bins all = xx iff (enabled);
bins row = binsof (aa) intersect {4} iff (!enabled);
bins column = binsof (bb.values) intersect {2} iff (enabled);
bins grouped = (xx && binsof (aa) intersect {8}) || binsof (bb) intersect {2};
ignore_bins ignored = binsof (aa) intersect {8} && binsof (bb) intersect {3} iff (enabled);
illegal_bins forbidden = binsof (aa) intersect {4} && binsof (bb) intersect {2} iff (0);
}
endgroup
covergroup cg_wide_cross with function sample (bit [6:0] a, bit b, bit enabled);
aa: coverpoint a {
bins low[] = {[0 : 32]};
bins middle = {[0 : 64]};
bins high[] = {[33 : 64]};
bins all = {[0 : 64]};
ignore_bins ignored = {0};
}
bb: coverpoint b;
xx: cross aa, bb{
bins empty = binsof (aa.low) intersect {0} iff (0);
bins disabled = xx iff (0);
bins enabled_bin = xx iff (enabled);
bins tail = binsof (aa.all) iff (enabled);
}
endgroup
covergroup cg_empty with function sample (bit [1:0] a, bit [1:0] b);
aa: coverpoint a {
ignore_bins ignored = {[0 : 3]} iff (0);
}
bb: coverpoint b;
xx: cross aa, bb{bins empty = xx iff (0);}
endgroup
covergroup cg_signed with function sample (bit signed [6:0] value);
option.auto_bin_max = 3;
cp: coverpoint value {
ignore_bins ignored = {[-64 : -23]};
}
endgroup
covergroup cg_wide with function sample (bit [64:0] value);
option.auto_bin_max = 4;
cp: coverpoint value {
ignore_bins ignored = {[65'h0 : 65'h0_7fffffffffffffff]};
}
endgroup
covergroup cg_wild with function sample (bit [64:0] value);
cp: coverpoint value {
wildcard bins even_values = {65'bx0};
wildcard bins odd_values = {65'bx1};
wildcard ignore_bins ignored = {65'bx0};
}
endgroup
covergroup cg_wild_empty with function sample (bit [64:0] value);
option.auto_bin_max = 4;
cp: coverpoint value {
wildcard ignore_bins even_values = {65'bx0};
wildcard ignore_bins odd_values = {65'bx1};
}
endgroup
covergroup cg_projection with function sample (bit signed [2:0] value);
negative: coverpoint value {
bins keep = {3'sb111};
wildcard bins empty = {5'b01???};
wildcard bins signed_empty = {5'sb01???};
wildcard ignore_bins positive = {4'sb0???};
}
positive: coverpoint value {
bins keep = {3'sb001};
wildcard ignore_bins negative = {4'sb1???};
}
endgroup
covergroup cg_source_width with function sample (bit signed [6:0] value);
cp: coverpoint value {
wildcard bins keep = {2'sbx0};
ignore_bins ignored = {1};
}
endgroup
covergroup cg_unsigned_pattern with function sample (bit signed [2:0] value);
cp: coverpoint value {
wildcard bins all_values = {4'b0???};
wildcard bins negative_pair = {4'b011?};
bins negative = {4'b0111};
ignore_bins ignored = {0};
}
endgroup
covergroup cg_zero_limit with function sample (bit value);
option.auto_bin_max = 0;
cp: coverpoint value;
endgroup
covergroup cg_signed_wide with function sample (bit signed [64:0] value, bit side);
option.auto_bin_max = 4;
cp: coverpoint value {
ignore_bins ignored = {[$ : 65'sh1_7fffffffffffffff]};
}
other: coverpoint side;
cx: cross cp, other{
bins negative = binsof (cp) intersect {65'sh1_ffffffffffffffff};
bins positive = binsof (cp) intersect {65'sh0_8000000000000000} && binsof (other) intersect {
1
};
}
endgroup
covergroup cg_pattern_carry with function sample (
bit [3:0] unsigned_value, bit signed [3:0] signed_value, bit side
);
cp_unsigned: coverpoint unsigned_value {
wildcard bins patterned = {4'b?0?0};
ignore_bins ignored = {15};
}
cp_signed: coverpoint signed_value {
wildcard bins patterned = {4'sb?0?0};
ignore_bins ignored = {4'shf};
}
other: coverpoint side;
unsigned_cross: cross cp_unsigned, other{
bins carry = binsof (cp_unsigned.patterned) intersect {[3 : 9]};
bins empty = binsof (cp_unsigned.patterned) intersect {[11 : 15]};
}
signed_cross: cross cp_signed, other{
bins carry = binsof (cp_signed.patterned) intersect {[-5 : 1]};
}
endgroup
// Bin values resolve to the coverpoint's type (IEEE 1800-2023 19.5.7) and bins without
// values do not count (19.11.1), whether or not the coverpoint has exclusions. The two
// covergroups differ only by an ignore_bins that is never hit.
// verilog_format: off
`define RESOLUTION_BINS \
bins outside = {[8 : 9]}; \
bins unknown = {3'bx01}; \
bins wide = {15}; \
bins reversed = {[3 : 1]}; \
bins kept = {[2 : 3]}; \
bins clipped = {[6 : 10]}; \
bins values[] = {4, 3'bx11}; \
bins rest = default;
`define RESOLUTION_CROSS \
other: coverpoint side; \
cx: cross cp, other { \
bins unknown_values = binsof (cp) intersect {3'bx01}; \
bins kept_values = binsof (cp.kept); \
}
// verilog_format: on
covergroup cg_resolution with function sample (logic [2:0] value, bit side);
cp: coverpoint value {`RESOLUTION_BINS}
`RESOLUTION_CROSS
endgroup
covergroup cg_resolution_excl with function sample (logic [2:0] value, bit side);
cp: coverpoint value {
`RESOLUTION_BINS
ignore_bins unused = {5};
}
`RESOLUTION_CROSS
endgroup
// Real bin values keep the integral values they contain (IEEE 1800-2023 19.5.7).
covergroup cg_real_values with function sample (bit [2:0] value);
cp: coverpoint value {
bins two = {2.0};
bins fraction = {2.5};
bins far = {100.0};
bins middle = {[3.5 : 5.5]};
bins high = {[6.5 : 100.0]};
bins low = {[-5.0 : 0.5]};
ignore_bins ignored = {1.0};
}
plain: coverpoint value {
bins two = {2.0};
bins middle = {[3.5 : 5.5]};
}
endgroup
covergroup cg_real_point with function sample (real value);
cp: coverpoint value {
bins low = {[0 : 1]};
bins middle = {[1.5 : 2.5]};
bins three = {3.0};
}
endgroup
// More values than one constructor call describes
covergroup cg_many_values with function sample (bit [9:0] value);
cp: coverpoint value {
bins odd = {
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29,
31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59,
61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89,
91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119,
121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149,
151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179,
181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209,
211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239,
241, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263, 265, 267, 269,
271, 273, 275, 277, 279, 281, 283, 285, 287, 289, 291, 293, 295, 297, 299,
301, 303, 305, 307, 309, 311, 313, 315, 317, 319, 321, 323, 325, 327, 329,
331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351, 353, 355, 357, 359,
361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383, 385, 387, 389,
391, 393, 395, 397, 399, 401, 403, 405, 407, 409, 411, 413, 415, 417, 419,
421, 423, 425, 427, 429, 431, 433, 435, 437, 439, 441, 443, 445, 447, 449,
451, 453, 455, 457, 459, 461, 463, 465, 467, 469, 471, 473, 475, 477, 479,
481, 483, 485, 487, 489, 491, 493, 495, 497, 499, 501, 503, 505, 507, 509,
511, 513, 515, 517, 519, 521, 523, 525, 527, 529, 531, 533, 535, 537, 539,
541, 543, 545, 547, 549, 551, 553, 555, 557, 559, 561, 563, 565, 567, 569,
571, 573, 575, 577, 579, 581, 583, 585, 587, 589, 591, 593, 595, 597, 599
};
ignore_bins ignored = {0};
}
endgroup
cg_partition partition_cov = new;
cg_explicit explicit_cov = new;
cg_cross cross_cov = new;
cg_wide_cross wide_cross_cov = new;
cg_empty empty_cov = new;
cg_signed signed_cov = new;
cg_wide wide_cov = new;
cg_wild wild_cov = new;
cg_wild_empty wild_empty_cov = new;
cg_projection projection_cov = new;
cg_source_width source_width_cov = new;
cg_unsigned_pattern unsigned_pattern_cov = new;
cg_zero_limit zero_limit_cov = new;
cg_signed_wide signed_wide_cov = new;
cg_pattern_carry pattern_carry_cov = new;
cg_resolution resolution_cov = new;
cg_resolution_excl resolution_excl_cov = new;
cg_real_values real_values_cov = new;
cg_real_point real_point_cov = new;
cg_many_values many_values_cov = new;
always @(posedge clk) begin
if (cyc == 0) begin
partition_cov.sample(2);
partition_cov.sample(4);
partition_cov.sample(7);
`checkr(partition_cov.get_inst_coverage(), 100.0);
partition_cov.sample(3);
explicit_cov.sample(2);
explicit_cov.sample(3);
explicit_cov.sample(4);
`checkr(explicit_cov.get_inst_coverage(), 100.0);
empty_cov.sample(0, 1);
empty_cov.sample(3, 2);
`checkr(empty_cov.get_inst_coverage(), 50.0);
signed_cov.sample(-22);
signed_cov.sample(63);
`checkr(signed_cov.get_inst_coverage(), 100.0);
signed_cov.sample(-64);
wide_cov.sample(65'h0_8000000000000000);
wide_cov.sample(65'h1_0000000000000000);
wide_cov.sample(65'h1_ffffffffffffffff);
`checkr(wide_cov.get_inst_coverage(), 100.0);
wide_cov.sample(0);
wild_cov.sample(1);
wild_cov.sample(65'h1_ffffffffffffffff);
wild_cov.sample(0);
`checkr(wild_cov.get_inst_coverage(), 100.0);
wild_empty_cov.sample(0);
wild_empty_cov.sample(1);
wild_empty_cov.sample(65'h1_fffffffffffffffe);
wild_empty_cov.sample(65'h1_ffffffffffffffff);
projection_cov.sample(-1);
`checkr(projection_cov.get_inst_coverage(), 50.0);
projection_cov.sample(1);
`checkr(projection_cov.get_inst_coverage(), 100.0);
source_width_cov.sample(-4);
source_width_cov.sample(2);
`checkr(source_width_cov.get_inst_coverage(), 0.0);
source_width_cov.sample(-2);
source_width_cov.sample(0);
`checkr(source_width_cov.get_inst_coverage(), 100.0);
unsigned_pattern_cov.sample(-1);
`checkr(unsigned_pattern_cov.get_inst_coverage(), 100.0);
zero_limit_cov.sample(0);
zero_limit_cov.sample(1);
signed_wide_cov.sample(-1, 0);
signed_wide_cov.sample(-1, 1);
signed_wide_cov.sample(0, 0);
signed_wide_cov.sample(65'sh0_ffffffffffffffff, 1);
signed_wide_cov.sample(65'sh1_0000000000000000, 0);
for (int value = 0; value < 8; ++value) begin
if (value != 5) begin
resolution_cov.sample(3'(value), 0);
resolution_cov.sample(3'(value), 1);
resolution_excl_cov.sample(3'(value), 0);
resolution_excl_cov.sample(3'(value), 1);
end
real_values_cov.sample(3'(value));
end
`checkr(resolution_cov.get_inst_coverage(), 100.0);
`checkr(resolution_excl_cov.get_inst_coverage(), 100.0);
`checkr(real_values_cov.get_inst_coverage(), 100.0);
real_point_cov.sample(1.25);
`checkr(real_point_cov.get_inst_coverage(), 0.0);
real_point_cov.sample(0.5);
real_point_cov.sample(2.0);
real_point_cov.sample(3.0);
`checkr(real_point_cov.get_inst_coverage(), 100.0);
many_values_cov.sample(2);
`checkr(many_values_cov.get_inst_coverage(), 0.0);
many_values_cov.sample(599);
many_values_cov.sample(1);
`checkr(many_values_cov.get_inst_coverage(), 100.0);
end
if (cyc < 8) begin
if (cyc == 0 || cyc == 1 || cyc == 5 || cyc == 6) partition_cov.sample(3'(cyc));
if (cyc == 0 || cyc == 1 || cyc >= 5) explicit_cov.sample(3'(cyc));
cross_cov.sample(cyc < 4 ? 4'd4 : 4'd8, 2'(cyc), 1'(cyc));
end
if (cyc == 8) cross_cov.sample(8, 2, 1);
if (cyc < 16) pattern_carry_cov.sample(4'(cyc), 4'(cyc), 1'(cyc / 2));
if (cyc < 65) begin
wide_cross_cov.sample(7'(cyc), 0, 1);
wide_cross_cov.sample(7'(cyc), 1, 1);
end
else begin
wide_cross_cov.sample(1, 0, 0);
$write("*-* All Finished *-*\n");
$finish;
end
++cyc;
end
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
import coverage_covergroup_common
test.scenarios('vlt')
test.top_filename = 't/t_covergroup_auto_exclusions.v'
test.golden_filename = 't/t_covergroup_auto_exclusions.out'
if test.tsan:
test.skip("ThreadSanitizer not compatible with AddressSanitizer\n")
# Construction-time value analysis and dynamic cross layouts under AddressSanitizer
coverage_covergroup_common.run(
test, verilator_flags2=['--timing', '-CFLAGS -fsanitize=address -LDFLAGS -fsanitize=address'])
test.passes()
+20
View File
@@ -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_auto_exclusions.v'
test.compile(verilator_flags2=['--timing'], threads=(2 if test.vltmt else 1))
test.execute()
test.passes()
+14 -26
View File
@@ -11,14 +11,6 @@
: ... note: In instance 't'
32 | bins auto[1001];
| ^~~~
%Error: t/t_covergroup_autobins_bad.v:44:26: Non-constant expression in bin value list; values must be constants (IEEE 1800-2023 19.5)
: ... note: In instance 't'
44 | ignore_bins ign = {size_var};
| ^~~~~~~~
%Error: t/t_covergroup_autobins_bad.v:45:32: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5)
: ... note: In instance 't'
45 | ignore_bins ign_range = {[0:size_var]};
| ^
%Error: t/t_covergroup_autobins_bad.v:39:12: Non-constant expression in array bins range; range bounds must be constants (IEEE 1800-2023 19.5)
: ... note: In instance 't'
39 | bins b[] = {[size_var:size_var]};
@@ -43,16 +35,16 @@
: ... note: In instance 't'
44 | ignore_bins ign = {size_var};
| ^~~~~~~~
%Error: t/t_covergroup_autobins_bad.v:45:32: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5)
: ... note: In instance 't'
45 | ignore_bins ign_range = {[0:size_var]};
| ^
%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:52:25: Ignoring unsupported: non-constant 'option.at_least'; using default value
: ... note: In instance 't'
52 | option.at_least = size_var;
| ^~~~~~~~
... For warning description see https://verilator.org/warn/COVERIGN?v=latest
... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message.
%Error: t/t_covergroup_autobins_bad.v:62:31: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5)
: ... note: In instance 't'
62 | ignore_bins ign_nclo = {[size_var:4]};
| ^
%Error: t/t_covergroup_autobins_bad.v:59:20: Four-state (x/z) value in bin range bound; range bounds must be two-state constants
: ... note: In instance 't'
59 | bins b_xz = {[4'bxxxx:4'hF]};
@@ -65,6 +57,10 @@
: ... note: In instance 't'
61 | ignore_bins ign_xz_hi = {[4'h0:4'bzzzz]};
| ^
%Error: t/t_covergroup_autobins_bad.v:62:31: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5)
: ... note: In instance 't'
62 | ignore_bins ign_nclo = {[size_var:4]};
| ^
%Error: t/t_covergroup_autobins_bad.v:63:23: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5)
: ... note: In instance 't'
63 | bins b_nc_ub = {[size_var:$]};
@@ -185,20 +181,12 @@
: ... note: In instance 't'
161 | auto_only: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f;
| ^~~~~
%Error: t/t_covergroup_autobins_bad.v:167:34: Non-constant expression in bin value list; values must be constants (IEEE 1800-2023 19.5)
%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:172:9: Unsupported: cross coverage with more than 2^32-1 tuples.
: ... note: In instance 't'
172 | xc: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f;
| ^~~~~
%Error: t/t_covergroup_autobins_bad.v:178:34: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5)
: ... note: In instance 't'
167 | ignore_bins nonconstant = {size_var};
178 | ignore_bins nonconstant = {size_var};
| ^~~~~~~~
%Error: t/t_covergroup_autobins_bad.v:167:34: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5)
: ... note: In instance 't'
167 | ignore_bins nonconstant = {size_var};
| ^~~~~~~~
%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:171:23: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound.
: ... note: In instance 't'
171 | bins selected = binsof(cp_a) intersect {0};
| ^~~~~~
%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:210:23: Unsupported: 'intersect' exclusion matching exceeds the selection work limit.
: ... note: In instance 't'
210 | bins selected = binsof(cp_a.whole) intersect {[0:30'h3fffffff]} && binsof(cp_b);
| ^~~~~~
%Error: Exiting due to
+1 -1
View File
@@ -9,7 +9,7 @@
import vltest_bootstrap
test.scenarios('vlt')
test.scenarios('linter')
test.lint(expect_filename=test.golden_filename, fails=True)
+14 -42
View File
@@ -42,7 +42,7 @@ module t;
bins b_range2 = {[0:size_var]}; // non-constant regular bin range (rhs non-const)
bins b2 = {size_var}; // non-constant simple bin value
ignore_bins ign = {size_var}; // non-constant ignore_bins value
ignore_bins ign_range = {[0:size_var]}; // non-constant ignore_bins range (rhs non-const)
ignore_bins ign_range = {[0:size_var]};
}
endgroup
@@ -59,7 +59,7 @@ module t;
bins b_xz = {[4'bxxxx:4'hF]}; // four-state lower bound (match-code path)
ignore_bins ign_xz_lo = {[4'bxxxx:4'hF]}; // four-state lower bound (range-enum path)
ignore_bins ign_xz_hi = {[4'h0:4'bzzzz]}; // four-state upper bound (range-enum path)
ignore_bins ign_nclo = {[size_var:4]}; // non-constant lower bound
ignore_bins ign_nclo = {[size_var:4]};
bins b_nc_ub = {[size_var:$]}; // non-constant lower bound, open-ended '$' upper
bins b_xz_ub = {[4'bxxxx:$]}; // four-state lower bound, open-ended '$' upper
bins b_xz_arr[] = {[4'bxxxx:4'hF]}; // four-state lower bound (array-bins path)
@@ -161,6 +161,17 @@ module t;
auto_only: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f;
endgroup
// Live bins only shrink a runtime cross, so its declared product is checked the same way.
covergroup cgx_dynamic_large;
cp_a: coverpoint cp_wide {ignore_bins removed = {0};}
cp_b: coverpoint cp_wide;
cp_c: coverpoint cp_wide;
cp_d: coverpoint cp_wide;
cp_e: coverpoint cp_wide;
cp_f: coverpoint cp_wide;
xc: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f;
endgroup
covergroup cgx_binsof_excluded;
cp_a: coverpoint cp_expr {
bins normal = {0};
@@ -172,45 +183,6 @@ module t;
}
endgroup
logic [29:0] complex_value;
localparam logic [29:0] ANY = 30'bx;
covergroup cgx_binsof_complex;
cp_a: coverpoint complex_value {
bins whole = {[0:30'h3fffffff]};
// Six pigeons in five holes: a deliberately hard union of excluded assignments.
wildcard ignore_bins no_hole = {
(ANY & ~30'h0000001f), (ANY & ~30'h000003e0), (ANY & ~30'h00007c00),
(ANY & ~30'h000f8000), (ANY & ~30'h01f00000), (ANY & ~30'h3e000000)
};
wildcard ignore_bins shared_hole = {
(ANY | 30'h00000021), (ANY | 30'h00000401), (ANY | 30'h00008001), (ANY | 30'h00100001),
(ANY | 30'h02000001), (ANY | 30'h00000420), (ANY | 30'h00008020), (ANY | 30'h00100020),
(ANY | 30'h02000020), (ANY | 30'h00008400), (ANY | 30'h00100400), (ANY | 30'h02000400),
(ANY | 30'h00108000), (ANY | 30'h02008000), (ANY | 30'h02100000), (ANY | 30'h00000042),
(ANY | 30'h00000802), (ANY | 30'h00010002), (ANY | 30'h00200002), (ANY | 30'h04000002),
(ANY | 30'h00000840), (ANY | 30'h00010040), (ANY | 30'h00200040), (ANY | 30'h04000040),
(ANY | 30'h00010800), (ANY | 30'h00200800), (ANY | 30'h04000800), (ANY | 30'h00210000),
(ANY | 30'h04010000), (ANY | 30'h04200000), (ANY | 30'h00000084), (ANY | 30'h00001004),
(ANY | 30'h00020004), (ANY | 30'h00400004), (ANY | 30'h08000004), (ANY | 30'h00001080),
(ANY | 30'h00020080), (ANY | 30'h00400080), (ANY | 30'h08000080), (ANY | 30'h00021000),
(ANY | 30'h00401000), (ANY | 30'h08001000), (ANY | 30'h00420000), (ANY | 30'h08020000),
(ANY | 30'h08400000), (ANY | 30'h00000108), (ANY | 30'h00002008), (ANY | 30'h00040008),
(ANY | 30'h00800008), (ANY | 30'h10000008), (ANY | 30'h00002100), (ANY | 30'h00040100),
(ANY | 30'h00800100), (ANY | 30'h10000100), (ANY | 30'h00042000), (ANY | 30'h00802000),
(ANY | 30'h10002000), (ANY | 30'h00840000), (ANY | 30'h10040000), (ANY | 30'h10800000),
(ANY | 30'h00000210), (ANY | 30'h00004010), (ANY | 30'h00080010), (ANY | 30'h01000010),
(ANY | 30'h20000010), (ANY | 30'h00004200), (ANY | 30'h00080200), (ANY | 30'h01000200),
(ANY | 30'h20000200), (ANY | 30'h00084000), (ANY | 30'h01004000), (ANY | 30'h20004000),
(ANY | 30'h01080000), (ANY | 30'h20080000), (ANY | 30'h21000000)
};
}
cp_b: coverpoint cp_expr;
xc: cross cp_a, cp_b {
bins selected = binsof(cp_a.whole) intersect {[0:30'h3fffffff]} && binsof(cp_b);
}
endgroup
covergroup cgx_binsof_many_values;
cp_a: coverpoint cp_wide {
bins whole[] = {[0:767]};
@@ -255,8 +227,8 @@ module t;
cgx_arr_open cgx_arr_open_inst = new;
cgx_binsof cgx_binsof_inst = new;
cgx_binsof_large cgx_binsof_large_inst = new;
cgx_dynamic_large cgx_dynamic_large_inst = new;
cgx_binsof_excluded cgx_binsof_excluded_inst = new;
cgx_binsof_complex cgx_binsof_complex_inst = new;
cgx_binsof_many_values cgx_binsof_many_values_inst = new;
initial $finish;
+1 -1
View File
@@ -16,7 +16,7 @@ cg_binsof.cp_a.arrayed[1]: 2
cg_binsof.cp_a.high: 2
cg_binsof.cp_a.ignored [ignore]: 2
cg_binsof.cp_a.low: 4
cg_binsof.cp_a.rest [default]: 8
cg_binsof.cp_a.rest [default]: 6
cg_binsof.cp_b.either: 18
cg_binsof.cp_b.high: 9
cg_binsof.cp_b.low: 9
@@ -11,7 +11,6 @@ cg_excluded_four_state.cp_a.states: 0
cg_excluded_four_state.cp_a.xstate [ignore]: 0
cg_excluded_four_state.cp_b.auto_0: 0
cg_excluded_four_state.cp_b.auto_1: 0
cg_excluded_four_state.selected.kept [cross]: 0
cg_excluded_four_state.selected.numeric [cross]: 0
cg_excluded_many.cp_a.whole: 2
cg_excluded_many.cp_a.zero_bit [ignore]: 0
@@ -103,12 +102,8 @@ cg_fixed_words.selected.all_values [cross]: 16
cg_fixed_words.sparse.all_values [cross]: 16
cg_fixed_words.sparse.subset [cross]: 4
cg_four_state.cp_a.known: 0
cg_four_state.cp_a.xstate: 0
cg_four_state.cp_a.zstate: 0
cg_four_state.cp_b.auto_0: 0
cg_four_state.cp_b.auto_1: 0
cg_four_state.selected.exact_x [cross]: 0
cg_four_state.selected.exact_z [cross]: 0
cg_four_state.selected.not_x [cross]: 0
cg_guards.cp_a.zero: 8
cg_guards.cp_b.zero: 8
+2 -1
View File
@@ -303,7 +303,8 @@ module t;
}
endgroup
// Check four-state bin identities without relying on four-state sampling.
// Values with x or z bits do not participate in bins or selections (IEEE 1800-2023 19.5.7),
// so the x/z bins and the selections of x/z values have no values and are not reported.
covergroup cg_four_state with function sample (logic [2:0] a, bit b);
cp_a: coverpoint a {
bins known = {3'b001};
-6
View File
@@ -57,9 +57,6 @@ cg5.cp_addr.addr0: 1
cg5.cp_addr.addr1: 1
cg5.cp_cmd.read: 1
cg5.cp_cmd.write: 1
cg_arr_4state.a4.av[0]_x_read [cross]: 1
cg_arr_4state.a4.av[0]_x_write [cross]: 1
cg_arr_4state.cp_addr.av[0]: 2
cg_arr_4state.cp_cmd.read: 1
cg_arr_4state.cp_cmd.write: 1
cg_arr_range.ar.av[0]_x_read [cross]: 1
@@ -128,11 +125,8 @@ cg_ignore.cross_ab.a0_x_read [cross]: 1
cg_ignore.cross_ab.a0_x_write [cross]: 1
cg_ignore.cross_ab.a1_x_read [cross]: 1
cg_ignore.cross_ab.a1_x_write [cross]: 1
cg_inv.cp_addr.inv: 0
cg_inv.cp_cmd.read: 1
cg_inv.cp_cmd.write: 1
cg_inv.iv.inv_x_read [cross]: 0
cg_inv.iv.inv_x_write [cross]: 0
cg_mixed.ab.addr0_x_read [cross]: 1
cg_mixed.ab.addr0_x_write [cross]: 1
cg_mixed.ab.addr1_x_read [cross]: 1
+12 -10
View File
@@ -171,7 +171,8 @@ module t;
// Crossed coverpoint with a four-state literal in a non-wildcard array bin
// (bins av[] = {2'b0x}): LRM 1800-2023 19.5.4 permits 4-state values in a bin definition.
// The hit-list sizing cannot statically analyze a 4-state value, so it falls back to the
// safe slot count. Under Verilator's 2-state simulation the value matches addr=0.
// safe slot count. A value with x or z bits does not participate (IEEE 1800-2023 19.5.7),
// so av[0] and its cross bins have no values and leave the coverage computation.
covergroup cg_arr_4state;
cp_addr: coverpoint addr {bins av[] = {2'b0x};}
cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};}
@@ -204,9 +205,10 @@ module t;
orc: cross cp_addr, cp_cmd;
endgroup
// Crossed coverpoint with an inverted range bin (lo bound > hi bound): the bin matches no
// value, so the hit-list sizing rejects it (lo > hi) and falls back to the safe slot count.
// The 'inv' bin and its cross bins are therefore never hit (coverage stays at 40%).
// Crossed coverpoint with an inverted range bin (lo bound > hi bound): the range is empty
// (IEEE 1800-2023 11.4.13), so the hit-list sizing rejects it (lo > hi) and falls back to the
// safe slot count. A bin without values, and its cross bins, leave the coverage computation
// (IEEE 1800-2023 19.11.1).
covergroup cg_inv;
cp_addr: coverpoint addr {bins inv = {[3 : 0]};} // inverted -> never matches
cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};}
@@ -486,11 +488,11 @@ module t;
addr = 0; cmd = 1; cg_wild_solo_inst.sample();
`checkr(cg_wild_solo_inst.get_inst_coverage(), 100.0); // 5/5
// Sample cg_arr_4state: 4-state literal bin {2'b0x} matches addr=0 (2-state sim); cross 1x2
// cg_arr_4state: 1+2+2=5 bins; sample both cmd values -> 100%
// Sample cg_arr_4state: 4-state literal bin {2'b0x} has no value, so addr=0 hits no bin
// cg_arr_4state: 0+2+0=2 bins; sample both cmd values -> 100%
addr = 0; cmd = 0; cg_arr_4state_inst.sample();
addr = 0; cmd = 1; cg_arr_4state_inst.sample();
`checkr(cg_arr_4state_inst.get_inst_coverage(), 100.0); // 5/5
`checkr(cg_arr_4state_inst.get_inst_coverage(), 100.0); // 2/2
// Sample cg_overlap: overlapping range bins lo={0,1}, hi={1,2}; cross 2x2
// cg_overlap: 2+2+4=8 bins; cover lo/hi via addr 0 and 2, plus addr=1 double-hits both
@@ -517,11 +519,11 @@ module t;
addr = 2; cmd = 1; cg_openrange_inst.sample(); // hi x write
`checkr(cg_openrange_inst.get_inst_coverage(), 100.0); // 8/8
// Sample cg_inv: inverted range bin never matches; only cmd bins are hittable
// cg_inv: 1+2+2=5 bins; inv and its 2 cross bins never hit -> 2/5=40%
// Sample cg_inv: the empty inverted range bin and its cross bins are not counted
// cg_inv: 0+2+0=2 bins -> read + write give 2/2=100%
addr = 0; cmd = 0; cg_inv_inst.sample(); // read
addr = 1; cmd = 1; cg_inv_inst.sample(); // write
`checkr(cg_inv_inst.get_inst_coverage(), 40.0); // 2/5: read + write only
`checkr(cg_inv_inst.get_inst_coverage(), 100.0); // 2/2: read + write only
// Sample cg_noNormal: coverpoint has no Normal bins; cross product is empty
// cg_noNormal: 0+2+0=2 bins (cmd only); both hit -> 100%
@@ -19,4 +19,34 @@
: ... note: In instance 't'
19 | bins invalid_right = cx || other;
| ^~~~~
%Error: t/t_covergroup_cross_ref_bad.v:33:26: Cross selection 'other' may only name its enclosing cross 'cx' (IEEE 1800-2023 19.6.1.2).
: ... note: In instance 't'
33 | bins wrong_cross = other;
| ^~~~~
%Error: t/t_covergroup_cross_ref_bad.v:34:27: Cross selection 'missing' may only name its enclosing cross 'cx' (IEEE 1800-2023 19.6.1.2).
: ... note: In instance 't'
34 | bins invalid_left = missing && cx;
| ^~~~~~~
%Error: t/t_covergroup_cross_ref_bad.v:35:34: Cross selection 'missing' may only name its enclosing cross 'cx' (IEEE 1800-2023 19.6.1.2).
: ... note: In instance 't'
35 | bins invalid_right = cx || missing;
| ^~~~~~~
%Error: t/t_covergroup_cross_ref_bad.v:36:26: binsof coverpoint 'cp_other' is not an item of cross 'cx' (IEEE 1800-2023 19.6.1).
: ... note: In instance 't'
36 | bins wrong_point = binsof (cp_other);
| ^~~~~~
%Error: t/t_covergroup_cross_ref_bad.v:37:24: Cannot find bin 'missing' in coverpoint 'cp_a' (IEEE 1800-2023 19.6.1).
: ... note: In instance 't'
37 | bins wrong_bin = binsof (cp_a.missing);
| ^~~~~~
%Error: t/t_covergroup_cross_ref_bad.v:39:7: Duplicate cross bin 'duplicate' (IEEE 1800-2023 19.6.1).
: ... note: In instance 't'
39 | bins duplicate = binsof (cp_b);
| ^~~~
%Warning-COVERIGN: t/t_covergroup_cross_ref_bad.v:40:26: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound.
: ... note: In instance 't'
40 | bins nonconstant = binsof (cp_a) intersect {limit_value};
| ^~~~~~
... For warning description see https://verilator.org/warn/COVERIGN?v=latest
... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message.
%Error: Exiting due to
+2 -2
View File
@@ -9,8 +9,8 @@
import vltest_bootstrap
test.scenarios('vlt')
test.scenarios('linter')
test.compile(verilator_flags2=['--coverage'], fails=True, expect_filename=test.golden_filename)
test.lint(verilator_flags2=['--coverage'], fails=True, expect_filename=test.golden_filename)
test.passes()
@@ -20,7 +20,29 @@ module t;
}
endgroup
int limit_value;
covergroup cg_dynamic;
cp_a: coverpoint value {
ignore_bins ignored = {0};
}
cp_b: coverpoint value;
cp_other: coverpoint value;
other: cross cp_a, cp_b;
cx: cross cp_a, cp_b{
bins wrong_cross = other;
bins invalid_left = missing && cx;
bins invalid_right = cx || missing;
bins wrong_point = binsof (cp_other);
bins wrong_bin = binsof (cp_a.missing);
bins duplicate = cx;
bins duplicate = binsof (cp_b);
bins nonconstant = binsof (cp_a) intersect {limit_value};
}
endgroup
cg cov = new;
cg_dynamic dynamic_cov = new;
initial $finish;
endmodule
+2 -2
View File
@@ -5,10 +5,10 @@ cg2.cp_only_default.all [default]: 4
cg3.data.bad [ignore]: 1
cg3.data.err [illegal]: 0
cg3.data.normal: 2
cg3.data.other [default]: 2
cg3.data.other [default]: 1
cg4.cp_idx.auto_0: 1
cg4.cp_idx.auto_1: 1
cg4.cp_idx.auto_2: 1
cg4.cp_idx.auto_3: 1
cg4.cp_idx.skip [ignore]: 0
cg5.cp_data64.auto[0]: 2
cg5.cp_data64.auto[1]: 0
+1 -1
View File
@@ -59,7 +59,7 @@ module t;
endgroup
// Auto-bins on a small range with one value excluded by ignore_bins -
// when the range is small enough, one auto-bin per valid value is created; the excluded value is skipped.
// the empty auto_2 bin is omitted without renumbering the remaining bins.
covergroup cg4;
cp_idx: coverpoint idx {
ignore_bins skip = {2}; // value 2 excluded; auto-bins created for 0,1,3
+28
View File
@@ -0,0 +1,28 @@
#!/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_auto_exclusions.v'
test.compile(verilator_flags2=[
'--coverage-user', '--protect-ids', '--protect-key AUTO_EXCLUSION_KEY', '-Wno-INSECURE'
],
threads=(2 if test.vltmt else 1))
test.execute()
test.file_grep_not(
test.coverage_filename,
r'cg_partition|cg_explicit|cg_projection|cg_wide_cross|t_covergroup_auto_exclusions|enabled_bin'
)
test.passes()
@@ -0,0 +1,24 @@
%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:14:20: Unsupported: non-integral value in a coverage bin of an integral coverpoint.
: ... note: In instance 't'
14 | bins text = {TEXT};
| ^~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:21:29: Unsupported: non-integral value in a coverage bin of an integral coverpoint.
: ... note: In instance 't'
21 | wildcard bins text = {TEXT};
| ^~~~
%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:27:25: Unsupported: non-integral value in a transition bin of a coverpoint with exclusions.
: ... note: In instance 't'
27 | bins text = (1 => TEXT);
| ^~~~
%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:40:59: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound.
: ... note: In instance 't'
40 | static_cross: cross cp_real, cp_plain{bins selected = binsof (cp_real) intersect {1};}
| ^~~~~~
... For warning description see https://verilator.org/warn/COVERIGN?v=latest
... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message.
%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:41:62: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound.
: ... note: In instance 't'
41 | dynamic_cross: cross cp_real, cp_dynamic{bins selected = binsof (cp_real) intersect {1};}
| ^~~~~~
%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.compile(verilator_flags2=['--timing'], fails=True, expect_filename=test.golden_filename)
test.passes()
@@ -0,0 +1,50 @@
// 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
module t;
bit [3:0] value;
real real_value;
localparam string TEXT = "a";
covergroup cg_values;
cp: coverpoint value {
bins text = {TEXT};
ignore_bins ignored = {0};
}
endgroup
covergroup cg_wild;
cp: coverpoint value {
wildcard bins text = {TEXT};
}
endgroup
covergroup cg_transition;
cp: coverpoint value {
bins text = (1 => TEXT);
ignore_bins ignored = {0};
}
endgroup
covergroup cg_cross;
cp_real: coverpoint real_value {
bins one = {1.0};
}
cp_plain: coverpoint value;
cp_dynamic: coverpoint value {
ignore_bins ignored = {0};
}
static_cross: cross cp_real, cp_plain{bins selected = binsof (cp_real) intersect {1};}
dynamic_cross: cross cp_real, cp_dynamic{bins selected = binsof (cp_real) intersect {1};}
endgroup
cg_values values_cov = new;
cg_wild wild_cov = new;
cg_transition transition_cov = new;
cg_cross cross_cov = new;
initial $finish;
endmodule
+2 -2
View File
@@ -17,6 +17,6 @@ cg2.cp_auto_ub.ub [ignore]: 2
cg2.cp_bounds.hi: 2
cg2.cp_bounds.lo: 2
cg2.cp_full.all: 4
cg3.cp_auto_lb.auto_0: 1
cg3.cp_auto_lb.auto_1: 1
cg3.cp_auto_lb.auto_2: 1
cg3.cp_auto_lb.auto_3: 1
cg3.cp_auto_lb.lb [ignore]: 1
+3 -3
View File
@@ -27,7 +27,7 @@ module t;
}
endgroup
// cg2: ignore_bins using a range - auto-bins are created only for values not in the range.
// cg2: ignore_bins using a range - empty auto-bins are omitted after partitioning.
// Also tests range-boundary conditions: when lo==0 or hi==maxVal, the range check simplifies.
// Also tests ignore_bins with a transition list.
covergroup cg2;
@@ -87,10 +87,10 @@ module t;
data2 = 0;
cg3_inst.sample(); // lb (ignored)
data2 = 2;
cg3_inst.sample(); // auto_0
cg3_inst.sample(); // auto_2
`checkr(cg3_inst.get_inst_coverage(), 50.0);
data2 = 3;
cg3_inst.sample(); // auto_1
cg3_inst.sample(); // auto_3
`checkr(cg3_inst.get_inst_coverage(), 100.0);
$write("*-* All Finished *-*\n");
+2
View File
@@ -0,0 +1,2 @@
%Warning: t/t_covergroup_limits.v:42: Coverage bin exclusions exceed the decision-graph work limit; bin retained
*-* 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_all')
test.compile(verilator_flags2=['--timing'], threads=(2 if test.vltmt else 1))
test.execute(expect_filename=test.golden_filename)
test.passes()
+84
View File
@@ -0,0 +1,84 @@
// 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
// 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
// A bin whose exclusions exceed a search limit is retained, and a cross bin whose selection
// exceeds one is ignored, each with a runtime warning; sampling still applies the exclusions.
module t (
input clk
);
int cyc = 0;
`ifdef LIMIT_DEPTH
localparam bit [1024:0] VALUE = 1;
localparam real EXCLUDED_COVERAGE = 100.0 * 1.0 / 3.0;
covergroup cg with function sample (bit [1024:0] value, bit side);
cp: coverpoint value {
bins whole = {[$ : $]};
ignore_bins endpoints = {1025'b0, {1025{1'b1}}};
}
other: coverpoint side {
bins zero = {0};
}
cx: cross cp, other{bins selected = binsof (cp) intersect {[0 : $]};}
endgroup
`else
localparam logic [63:0] ANY = 64'bx;
localparam bit [63:0] VALUE = 64'h0000_0000_00ff_ffff;
`ifdef LIMIT_QUERY
localparam real EXCLUDED_COVERAGE = 100.0 * 1.0 / 3.0;
`else
localparam real EXCLUDED_COVERAGE = 50.0;
`endif
covergroup cg with function sample (bit [63:0] value, bit side);
cp: coverpoint value {
bins whole = {[$ : $]};
wildcard ignore_bins pairs = {
ANY & ~64'h0000000100000001, ANY & ~64'h0000000200000002,
ANY & ~64'h0000000400000004, ANY & ~64'h0000000800000008,
ANY & ~64'h0000001000000010, ANY & ~64'h0000002000000020,
ANY & ~64'h0000004000000040, ANY & ~64'h0000008000000080,
ANY & ~64'h0000010000000100, ANY & ~64'h0000020000000200,
ANY & ~64'h0000040000000400, ANY & ~64'h0000080000000800,
ANY & ~64'h0000100000001000, ANY & ~64'h0000200000002000,
ANY & ~64'h0000400000004000, ANY & ~64'h0000800000008000,
ANY & ~64'h0001000000010000, ANY & ~64'h0002000000020000,
ANY & ~64'h0004000000040000, ANY & ~64'h0008000000080000,
ANY & ~64'h0010000000100000, ANY & ~64'h0020000000200000,
ANY & ~64'h0040000000400000, ANY & ~64'h0080000000800000
};
`ifndef LIMIT_QUERY
ignore_bins endpoint = {64'hffffffffffffffff};
`endif
}
other: coverpoint side {
bins zero = {0};
}
`ifdef LIMIT_QUERY
cx: cross cp, other{bins selected = binsof (cp) intersect {[0 : 64'hfffffffefffffffe]};}
`endif
endgroup
`endif
cg cov;
always @(posedge clk) begin
++cyc;
if (cyc == 3) cov = new;
if (cyc == 4) begin
cov.sample(0, 0); // Excluded
`checkr(cov.get_inst_coverage(), EXCLUDED_COVERAGE);
cov.sample(VALUE, 0);
`checkr(cov.get_inst_coverage(), 100.0);
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule
@@ -0,0 +1,3 @@
%Warning: t/t_covergroup_limits.v:24: Coverage bin exclusions exceed the decision-graph depth limit; bin retained
%Warning: t/t_covergroup_limits.v:30: Cross bin selection exceeds the decision-graph depth limit; bin ignored
*-* All Finished *-*
+21
View File
@@ -0,0 +1,21 @@
#!/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_limits.v'
test.compile(verilator_flags2=['--timing', '+define+LIMIT_DEPTH'],
threads=(2 if test.vltmt else 1))
test.execute(expect_filename=test.golden_filename)
test.passes()
@@ -0,0 +1,2 @@
%Warning: t/t_covergroup_limits.v:65: Cross bin selection exceeds the decision-graph work limit; bin ignored
*-* All Finished *-*
+21
View File
@@ -0,0 +1,21 @@
#!/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_limits.v'
test.compile(verilator_flags2=['--timing', '+define+LIMIT_QUERY'],
threads=(2 if test.vltmt else 1))
test.execute(expect_filename=test.golden_filename)
test.passes()
@@ -0,0 +1,23 @@
%Error: t/t_covergroup_wild_real_bad.v:11:9: Cannot use a wildcard bin on a coverpoint of type 'real' (IEEE 1800-2023 19.5.4).
: ... note: In instance 't'
11 | cp: coverpoint value {
| ^~~~~~~~~~
t/t_covergroup_wild_real_bad.v:12:21: ... Location of wildcard bin
12 | wildcard bins one = {1};
| ^~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_covergroup_wild_real_bad.v:14:15: Cannot use a wildcard bin on a coverpoint of type 'real' (IEEE 1800-2023 19.5.4).
: ... note: In instance 't'
14 | cp_range: coverpoint value {
| ^~~~~~~~~~
t/t_covergroup_wild_real_bad.v:15:21: ... Location of wildcard bin
15 | wildcard bins range = {[1 : 2]};
| ^~~~~
%Error: t/t_covergroup_wild_real_bad.v:17:14: Cannot use a wildcard bin on a coverpoint of type 'real' (IEEE 1800-2023 19.5.4).
: ... note: In instance 't'
17 | cp_open: coverpoint value {
| ^~~~~~~~~~
t/t_covergroup_wild_real_bad.v:18:21: ... Location of wildcard bin
18 | wildcard bins open = {[1 : $]};
| ^~~~
%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('linter')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()
@@ -0,0 +1,25 @@
// 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
module t;
real value;
covergroup cg;
cp: coverpoint value {
wildcard bins one = {1};
}
cp_range: coverpoint value {
wildcard bins range = {[1 : 2]};
}
cp_open: coverpoint value {
wildcard bins open = {[1 : $]};
}
endgroup
cg cov = new;
initial $finish;
endmodule
+55
View File
@@ -1023,6 +1023,59 @@ module Vt_debug_emitv_t;
input string name;
endfunction
endcovergroup
covergroup Vt_debug_emitv_cg_live_cross;
function new;
cp_x: coverpoint cg_sig {
ignore_bins removed = {'sh0};
};
cp_y: coverpoint cg_sig2;
cx: cross cp_x, cp_y {
bins all = cx;
}
endfunction
int signed __Vint;
struct {
string name;
int signed weight;
int signed goal;
string comment;
int signed at_least;
int signed auto_bin_max;
int signed cross_num_print_missing;
bit cross_retain_auto_bins;
bit detect_overlap;
bit per_instance;
bit get_inst_coverage;
} option;
struct {
int signed weight;
int signed goal;
string comment;
bit strobe;
bit merge_instances;
bit distribute_first;
real real_interval;
} type_option;
function sample;
endfunction
function start;
endfunction
function stop;
endfunction
function get_coverage;
get_coverage = /*CRESET*/;
input string covered_bins;
input string total_bins;
endfunction
function get_inst_coverage;
get_inst_coverage = /*CRESET*/;
input string covered_bins;
input string total_bins;
endfunction
function set_inst_name;
input string name;
endfunction
endcovergroup
Vt_debug_emitv_cg_basic cg_basic_instVt_debug_emitv_cg_basic;
cg_basic_inst = new();
Vt_debug_emitv_cg_clocked cg_clocked_instVt_debug_emitv_cg_clocked;
@@ -1031,6 +1084,8 @@ module Vt_debug_emitv_t;
cg_trans_inst = new();
Vt_debug_emitv_cg_cross cg_cross_instVt_debug_emitv_cg_cross;
cg_cross_inst = new();
Vt_debug_emitv_cg_live_cross cg_live_cross_instVt_debug_emitv_cg_live_cross;
cg_live_cross_inst = new();
enum logic [2:0] {
ZERO = 3'h0,
ONE = 3'h1
+12
View File
@@ -452,10 +452,22 @@ module t (/*AUTOARG*/
}
endgroup
covergroup cg_live_cross;
option.auto_bin_max = 4;
cp_x: coverpoint cg_sig {
ignore_bins removed = {0};
}
cp_y: coverpoint cg_sig2;
cx: cross cp_x, cp_y{
bins all = cx;
}
endgroup
cg_basic cg_basic_inst = new;
cg_clocked cg_clocked_inst = new;
cg_trans cg_trans_inst = new;
cg_cross cg_cross_inst = new;
cg_live_cross cg_live_cross_inst = new;
endmodule
module sub(input logic clk);
+16 -24
View File
@@ -570,18 +570,14 @@
// Crossed coverpoint with a four-state literal in a non-wildcard array bin
// (bins av[] = {2'b0x}): LRM 1800-2023 19.5.4 permits 4-state values in a bin definition.
// The hit-list sizing cannot statically analyze a 4-state value, so it falls back to the
// safe slot count. Under Verilator's 2-state simulation the value matches addr=0.
// safe slot count. A value with x or z bits does not participate (IEEE 1800-2023 19.5.7),
// so av[0] and its cross bins have no values and leave the coverage computation.
covergroup cg_arr_4state;
%000002 cp_addr: coverpoint addr {bins av[] = {2'b0x};}
-000002 point: type=covergroup comment= hier=cg_arr_4state.cp_addr.av[0]
cp_addr: coverpoint addr {bins av[] = {2'b0x};}
%000001 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};}
-000001 point: type=covergroup comment= hier=cg_arr_4state.cp_cmd.read
-000001 point: type=covergroup comment= hier=cg_arr_4state.cp_cmd.write
%000001 a4: cross cp_addr, cp_cmd;
-000001 point: type=covergroup comment= hier=cg_arr_4state.a4.av[0]_x_read
// cross: [av[0], read]
-000001 point: type=covergroup comment= hier=cg_arr_4state.a4.av[0]_x_write
// cross: [av[0], write]
a4: cross cp_addr, cp_cmd;
endgroup
// Crossed coverpoint with two *overlapping* Normal range bins (addr=1 is in both lo and
@@ -646,20 +642,16 @@
// cross: [lo, write]
endgroup
// Crossed coverpoint with an inverted range bin (lo bound > hi bound): the bin matches no
// value, so the hit-list sizing rejects it (lo > hi) and falls back to the safe slot count.
// The 'inv' bin and its cross bins are therefore never hit (coverage stays at 40%).
// Crossed coverpoint with an inverted range bin (lo bound > hi bound): the range is empty
// (IEEE 1800-2023 11.4.13), so the hit-list sizing rejects it (lo > hi) and falls back to the
// safe slot count. A bin without values, and its cross bins, leave the coverage computation
// (IEEE 1800-2023 19.11.1).
covergroup cg_inv;
%000000 cp_addr: coverpoint addr {bins inv = {[3 : 0]};} // inverted -> never matches
-000000 point: type=covergroup comment= hier=cg_inv.cp_addr.inv
cp_addr: coverpoint addr {bins inv = {[3 : 0]};} // inverted -> never matches
%000001 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};}
-000001 point: type=covergroup comment= hier=cg_inv.cp_cmd.read
-000001 point: type=covergroup comment= hier=cg_inv.cp_cmd.write
%000000 iv: cross cp_addr, cp_cmd;
-000000 point: type=covergroup comment= hier=cg_inv.iv.inv_x_read
// cross: [inv, read]
-000000 point: type=covergroup comment= hier=cg_inv.iv.inv_x_write
// cross: [inv, write]
iv: cross cp_addr, cp_cmd;
endgroup
// Crossed coverpoint with *no* Normal bins (only ignore_bins): the cross has an empty bin
@@ -1214,13 +1206,13 @@
-000000 point: type=line comment=block hier=top.t
-000001 point: type=line comment=else hier=top.t
// Sample cg_arr_4state: 4-state literal bin {2'b0x} matches addr=0 (2-state sim); cross 1x2
// cg_arr_4state: 1+2+2=5 bins; sample both cmd values -> 100%
// Sample cg_arr_4state: 4-state literal bin {2'b0x} has no value, so addr=0 hits no bin
// cg_arr_4state: 0+2+0=2 bins; sample both cmd values -> 100%
%000001 addr = 0; cmd = 0; cg_arr_4state_inst.sample();
-000001 point: type=line comment=block hier=top.t
%000001 addr = 0; cmd = 1; cg_arr_4state_inst.sample();
-000001 point: type=line comment=block hier=top.t
%000001 `checkr(cg_arr_4state_inst.get_inst_coverage(), 100.0); // 5/5
%000001 `checkr(cg_arr_4state_inst.get_inst_coverage(), 100.0); // 2/2
-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
@@ -1272,13 +1264,13 @@
-000000 point: type=line comment=block hier=top.t
-000001 point: type=line comment=else hier=top.t
// Sample cg_inv: inverted range bin never matches; only cmd bins are hittable
// cg_inv: 1+2+2=5 bins; inv and its 2 cross bins never hit -> 2/5=40%
// Sample cg_inv: the empty inverted range bin and its cross bins are not counted
// cg_inv: 0+2+0=2 bins -> read + write give 2/2=100%
%000001 addr = 0; cmd = 0; cg_inv_inst.sample(); // read
-000001 point: type=line comment=block hier=top.t
%000001 addr = 1; cmd = 1; cg_inv_inst.sample(); // write
-000001 point: type=line comment=block hier=top.t
%000001 `checkr(cg_inv_inst.get_inst_coverage(), 40.0); // 2/5: read + write only
%000001 `checkr(cg_inv_inst.get_inst_coverage(), 100.0); // 2/2: read + write only
-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