Improve code coverage and add coverage report production to more tests

Signed-off-by: Matthew Ballance <matt.ballance@gmail.com>
This commit is contained in:
Matthew Ballance 2026-04-05 16:23:49 +00:00
parent f1389497c1
commit e710f1b6b3
23 changed files with 562 additions and 240 deletions

View File

@ -702,11 +702,11 @@ class CovergroupInjectVisitor final : public VNVisitor {
void visit(AstVarScope* nodep) override {
// Get the underlying var
AstVar* const varp = nodep->varp();
if (!varp) return; // LCOV_EXCL_BR_LINE -- AstVarScope always has non-null varp
UASSERT_OBJ(varp, nodep, "AstVarScope must have non-null varp");
// Check if the variable is of covergroup class type
const AstNodeDType* const dtypep = varp->dtypep();
if (!dtypep) return; // LCOV_EXCL_BR_LINE -- typed vars always have non-null dtypep
UASSERT_OBJ(dtypep, nodep, "AstVar must have non-null dtypep after V3Width");
const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType);
if (!classRefp) return;
@ -754,7 +754,7 @@ class CovergroupInjectVisitor final : public VNVisitor {
activep->addStmtsp(
new AstAlways{fl, VAlwaysKwd::ALWAYS_FF, nullptr, cmethodCallp->makeStmt()});
UINFO(4, " Added automatic sample() call for covergroup " << varp->name());
UINFO(4, " Added automatic sample() call for covergroup " << varp->name()); // LCOV_EXCL_BR_LINE
}
void visit(AstActive*) override {} // Don't iterate into actives

View File

@ -1080,7 +1080,8 @@ public:
, m_name{name}
, m_type{type}
, m_isArray{isArrayBin} {
if (transp) addTransp(transp);
UASSERT(transp, "AstCoverBin transition constructor requires non-null transp");
addTransp(transp);
}
ASTGEN_MEMBERS_AstCoverBin;
void dump(std::ostream& str) const override;
@ -1122,7 +1123,6 @@ public:
ASTGEN_MEMBERS_AstCoverTransItem;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
VTransRepType repType() const { return m_repType; }
};
class AstCoverTransSet final : public AstNode {
// Represents a transition set: value1 => value2 => value3
@ -1163,7 +1163,6 @@ public:
};
class AstCoverpointRef final : public AstNode {
// Reference to a coverpoint used in a cross
// @astgen ptr := m_coverpointp : Optional[AstCoverpoint]
const string m_name; // coverpoint name
public:
@ -1174,8 +1173,6 @@ public:
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return m_name; }
AstCoverpoint* coverpointp() const { return m_coverpointp; }
void coverpointp(AstCoverpoint* nodep) { m_coverpointp = nodep; }
};
class AstDefParam final : public AstNode {
// A defparam assignment
@ -2700,7 +2697,8 @@ class AstCoverCross final : public AstNodeFuncCovItem {
public:
AstCoverCross(FileLine* fl, const string& name, AstCoverpointRef* itemsp)
: ASTGEN_SUPER_CoverCross(fl, name) {
if (itemsp) addItemsp(itemsp);
UASSERT(itemsp, "AstCoverCross requires at least one coverpoint reference");
addItemsp(itemsp);
}
ASTGEN_MEMBERS_AstCoverCross;
void dump(std::ostream& str) const override;

View File

@ -69,7 +69,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
void clearBinInfos() {
// Delete pseudo-bins created for cross coverage (they're never inserted into the AST)
for (const BinInfo& bi : m_binInfos) {
if (!bi.coverpointp && bi.crossp && bi.binp) pushDeletep(bi.binp);
if (!bi.coverpointp) pushDeletep(bi.binp);
}
m_binInfos.clear();
}
@ -137,21 +137,25 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Calculate range division
const int width = exprp->width();
const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1);
const uint64_t binSize = (maxVal + 1) / numBins;
// For width >= 64: (maxVal+1) would overflow; compute binSize without overflow
const uint64_t binSize
= (width < 64) ? ((maxVal + 1) / numBins) : (UINT64_MAX / numBins + 1);
UINFO(4, " Width=" << width << " maxVal=" << maxVal << " numBins=" << numBins
<< " binSize=" << binSize);
// Create expanded bins
for (int i = 0; i < numBins; i++) {
const uint64_t lo = i * binSize;
const uint64_t lo = static_cast<uint64_t>(i) * binSize;
const uint64_t hi = (i == numBins - 1) ? maxVal : ((i + 1) * binSize - 1);
// Create constants for range
AstConst* const loConstp
= new AstConst{cbinp->fileline(), V3Number(cbinp->fileline(), width, lo)};
AstConst* const hiConstp
= new AstConst{cbinp->fileline(), V3Number(cbinp->fileline(), width, hi)};
// Create constants for range (use setQuad to handle values > 32-bit)
V3Number loNum{cbinp->fileline(), width, 0};
loNum.setQuad(lo);
AstConst* const loConstp = new AstConst{cbinp->fileline(), loNum};
V3Number hiNum{cbinp->fileline(), width, 0};
hiNum.setQuad(hi);
AstConst* const hiConstp = new AstConst{cbinp->fileline(), hiNum};
// Create InsideRange [lo:hi]
AstInsideRange* const rangep
@ -190,14 +194,12 @@ class FunctionalCoverageVisitor final : public VNVisitor {
atLeastOut = 1;
autoBinMaxOut = -1; // -1 = not set at coverpoint level
for (AstNode* optionp = coverpointp->optionsp(); optionp; optionp = optionp->nextp()) {
if (AstCoverOption* optp = VN_CAST(optionp, CoverOption)) {
if (AstConst* constp = VN_CAST(optp->valuep(), Const)) {
if (optp->optionType() == VCoverOptionType::AT_LEAST) {
atLeastOut = constp->toSInt();
} else if (optp->optionType() == VCoverOptionType::AUTO_BIN_MAX) {
autoBinMaxOut = constp->toSInt();
}
}
AstCoverOption* const optp = VN_AS(optionp, CoverOption);
AstConst* const constp = VN_AS(optp->valuep(), Const);
if (optp->optionType() == VCoverOptionType::AT_LEAST) {
atLeastOut = constp->toSInt();
} else if (optp->optionType() == VCoverOptionType::AUTO_BIN_MAX) {
autoBinMaxOut = constp->toSInt();
}
}
// Fall back to covergroup-level auto_bin_max if not set at coverpoint level
@ -220,13 +222,13 @@ class FunctionalCoverageVisitor final : public VNVisitor {
} else if (AstInsideRange* rangep = VN_CAST(np, InsideRange)) {
AstNodeExpr* const lhsp = V3Const::constifyEdit(rangep->lhsp());
AstNodeExpr* const rhsp = V3Const::constifyEdit(rangep->rhsp());
AstConst* const loConstp = VN_CAST(lhsp, Const);
AstConst* const hiConstp = VN_CAST(rhsp, Const);
if (loConstp && hiConstp) {
const uint64_t lo = loConstp->toUQuad();
const uint64_t hi = hiConstp->toUQuad();
for (uint64_t v = lo; v <= hi; v++) values.insert(v);
}
AstConst* const loConstp = VN_AS(lhsp, Const);
AstConst* const hiConstp = VN_AS(rhsp, Const);
const uint64_t lo = loConstp->toUQuad();
const uint64_t hi = hiConstp->toUQuad();
for (uint64_t v = lo; v <= hi; v++) values.insert(v);
} else {
np->v3error("Non-constant expression in bin value list; values must be constants");
}
}
}
@ -536,8 +538,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
AstIf* const ifp = new AstIf{binp->fileline(), fullCondp, stmtp, nullptr};
UINFO(4, " Adding bin match if statement to sample function");
if (!m_sampleFuncp) // LCOV_EXCL_BR_LINE
binp->v3fatalSrc("m_sampleFuncp is null when trying to add bin match code"); // LCOV_EXCL_LINE
UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set when generating bin match code");
m_sampleFuncp->addStmtsp(ifp);
UINFO(4, " Successfully added if statement for bin: " << binp->name());
}
@ -563,7 +564,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Build condition for this bin
AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp);
if (!binCondp) continue;
UASSERT_OBJ(binCondp, cbinp, "buildBinCondition returned nullptr for non-ignore/non-illegal bin");
// OR with previous conditions
if (anyBinMatchp) {
@ -594,7 +595,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Create if statement
AstIf* const ifp = new AstIf{defBinp->fileline(), defaultCondp, stmtp, nullptr};
if (!m_sampleFuncp) defBinp->v3fatalSrc("m_sampleFuncp is null for default bin");
UASSERT_OBJ(m_sampleFuncp, defBinp, "sample() CFunc not set when generating default bin code");
m_sampleFuncp->addStmtsp(ifp);
UINFO(4, " Successfully added default bin if statement");
}
@ -701,28 +702,24 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Check if current value matches first item (restart condition)
AstNodeExpr* restartCondp = buildTransitionItemCondition(items[0], exprp);
if (restartCondp) {
// Apply iff condition
if (AstNodeExpr* iffp = coverpointp->iffp()) {
restartCondp = new AstAnd{fl, iffp->cloneTree(false), restartCondp};
}
// Restart to state 1
AstNodeStmt* restartActionp
= new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 8, 1}};
// Reset to state 0 (else branch)
AstNodeStmt* resetActionp
= new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 8, 0}};
noMatchActionp = new AstIf{fl, restartCondp, restartActionp, resetActionp};
} else {
// Can't build restart condition, just reset
noMatchActionp = new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 8, 0}};
UASSERT_OBJ(restartCondp, items[0],
"buildTransitionItemCondition returned nullptr for restart");
// Apply iff condition
if (AstNodeExpr* iffp = coverpointp->iffp()) {
restartCondp = new AstAnd{fl, iffp->cloneTree(false), restartCondp};
}
// Restart to state 1
AstNodeStmt* restartActionp
= new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 8, 1}};
// Reset to state 0 (else branch)
AstNodeStmt* resetActionp
= new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 8, 0}};
noMatchActionp = new AstIf{fl, restartCondp, restartActionp, resetActionp};
}
// For state 0, no action needed if no match (stay in state 0)
@ -753,35 +750,52 @@ class FunctionalCoverageVisitor final : public VNVisitor {
new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}};
}
// Clone a constant node, widening to targetWidth if needed (zero-extend).
// Used to ensure comparisons use matching widths after V3Width has run.
static AstConst* widenConst(FileLine* fl, AstConst* constp, int targetWidth) {
if (constp->width() == targetWidth) return constp->cloneTree(false);
V3Number num{fl, targetWidth, 0};
num.opAssign(constp->num());
return new AstConst{fl, num};
}
// Build a range condition: minp <= exprp <= maxp.
// Uses signed comparisons if exprp is signed; omits trivially-true bounds for unsigned.
// All arguments are non-owning; clones exprp/minp/maxp as needed.
AstNodeExpr* makeRangeCondition(FileLine* fl, AstNodeExpr* exprp, AstNodeExpr* minp,
AstNodeExpr* maxp) {
const int exprWidth = exprp->widthMin();
AstConst* const minConstp = VN_AS(minp, Const);
AstConst* const maxConstp = VN_AS(maxp, Const);
// Widen constants to match expression width so post-V3Width nodes use correct macros
AstConst* const minWidep = widenConst(fl, minConstp, exprWidth);
AstConst* const maxWidep = widenConst(fl, maxConstp, exprWidth);
if (exprp->isSigned()) {
return new AstAnd{fl, new AstGteS{fl, exprp->cloneTree(false), minp->cloneTree(false)},
new AstLteS{fl, exprp->cloneTree(false), maxp->cloneTree(false)}};
return new AstAnd{fl,
new AstGteS{fl, exprp->cloneTree(false), minWidep},
new AstLteS{fl, exprp->cloneTree(false), maxWidep}};
}
// Unsigned: skip bounds that are trivially satisfied for the expression width
AstConst* const minConstp = VN_CAST(minp, Const);
AstConst* const maxConstp = VN_CAST(maxp, Const);
const int exprWidth = exprp->widthMin();
bool skipLowerCheck = (minConstp && minConstp->toUQuad() == 0);
const bool skipLowerCheck = (minConstp->toUQuad() == 0);
bool skipUpperCheck = false;
if (maxConstp && exprWidth > 0 && exprWidth <= 64) {
if (exprWidth <= 64) {
const uint64_t maxVal
= (exprWidth == 64) ? ~static_cast<uint64_t>(0) : ((1ULL << exprWidth) - 1ULL);
skipUpperCheck = (maxConstp->toUQuad() == maxVal);
}
if (skipLowerCheck && skipUpperCheck) {
VL_DO_DANGLING(pushDeletep(minWidep), minWidep);
VL_DO_DANGLING(pushDeletep(maxWidep), maxWidep);
return new AstConst{fl, AstConst::BitTrue{}};
} else if (skipLowerCheck) {
return new AstLte{fl, exprp->cloneTree(false), maxp->cloneTree(false)};
VL_DO_DANGLING(pushDeletep(minWidep), minWidep);
return new AstLte{fl, exprp->cloneTree(false), maxWidep};
} else if (skipUpperCheck) {
return new AstGte{fl, exprp->cloneTree(false), minp->cloneTree(false)};
VL_DO_DANGLING(pushDeletep(maxWidep), maxWidep);
return new AstGte{fl, exprp->cloneTree(false), minWidep};
} else {
return new AstAnd{fl, new AstGte{fl, exprp->cloneTree(false), minp->cloneTree(false)},
new AstLte{fl, exprp->cloneTree(false), maxp->cloneTree(false)}};
return new AstAnd{fl, new AstGte{fl, exprp->cloneTree(false), minWidep},
new AstLte{fl, exprp->cloneTree(false), maxWidep}};
}
}
@ -803,12 +817,9 @@ class FunctionalCoverageVisitor final : public VNVisitor {
for (AstNode* valp = itemp->valuesp(); valp; valp = valp->nextp()) {
AstNodeExpr* singleCondp = nullptr;
if (AstConst* constp = VN_CAST(valp, Const)) {
singleCondp = new AstEq{constp->fileline(), exprp->cloneTree(false),
constp->cloneTree(false)};
} else {
valp->v3fatalSrc("Unexpected node type in transition item: " << valp->typeName());
}
AstConst* const constp = VN_AS(valp, Const);
singleCondp = new AstEq{constp->fileline(), exprp->cloneTree(false),
constp->cloneTree(false)};
if (condp) {
condp = new AstOr{itemp->fileline(), condp, singleCondp};
@ -835,7 +846,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
AstNodeExpr* const maxp = V3Const::constifyEdit(insideRangep->rhsp());
AstConst* const minConstp = VN_CAST(minp, Const);
AstConst* const maxConstp = VN_CAST(maxp, Const);
if (minConstp && maxConstp) {
if (minConstp && maxConstp) { // LCOV_EXCL_BR_LINE
const int minVal = minConstp->toSInt();
const int maxVal = maxConstp->toSInt();
UINFO(6, " Expanding InsideRange [" << minVal << ":" << maxVal << "]"
@ -907,7 +918,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Create if statement
AstIf* const ifp = new AstIf{binp->fileline(), condp, stmtp, nullptr};
if (!m_sampleFuncp) binp->v3fatalSrc("m_sampleFuncp is null for array bin");
UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set when generating array bin code");
m_sampleFuncp->addStmtsp(ifp);
}
@ -919,11 +930,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Extract all transition sets
std::vector<AstCoverTransSet*> transSets;
for (AstNode* transSetp = arrayBinp->transp(); transSetp; transSetp = transSetp->nextp()) {
if (AstCoverTransSet* ts = VN_CAST(transSetp, CoverTransSet)) {
transSets.push_back(ts);
}
}
for (AstNode* transSetp = arrayBinp->transp(); transSetp; transSetp = transSetp->nextp())
transSets.push_back(VN_AS(transSetp, CoverTransSet));
UINFO(4, " Found " << transSets.size() << " transition sets");
int index = 0;
@ -962,36 +970,18 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Get or create previous value variable
AstVar* const prevVarp = createPrevValueVar(coverpointp, exprp);
if (!transSetp) { // LCOV_EXCL_BR_LINE
binp->v3error("Transition bin without transition set"); // LCOV_EXCL_LINE
return; // LCOV_EXCL_LINE
}
UASSERT_OBJ(transSetp, binp, "Transition bin has no transition set (transp() was checked before calling this)");
// Get transition items (the sequence: item1 => item2 => item3)
std::vector<AstCoverTransItem*> items;
for (AstNode* itemp = transSetp->itemsp(); itemp; itemp = itemp->nextp()) {
if (AstCoverTransItem* transp = VN_CAST(itemp, CoverTransItem)) {
items.push_back(transp);
}
}
for (AstNode* itemp = transSetp->itemsp(); itemp; itemp = itemp->nextp())
items.push_back(VN_AS(itemp, CoverTransItem));
if (items.empty()) {
binp->v3error("Transition set without items");
return;
}
// Check for unsupported repetition operators
// Note: the grammar handles [*], [->], [=] at parse time via COVERIGN warning,
// resulting in null AstCoverTransItem nodes which are filtered out above.
// This check is therefore unreachable from normal SV parsing.
for (AstCoverTransItem* item : items) { // LCOV_EXCL_START
if (item->repType() != VTransRepType::NONE) {
binp->v3warn(E_UNSUPPORTED,
"Transition repetition operators ([*], [->], [=]) not yet supported");
return;
}
} // LCOV_EXCL_STOP
if (items.size() == 1) {
// Single item transition not valid (need at least 2 values for =>)
binp->v3error("Transition requires at least two values");
@ -1127,28 +1117,26 @@ class FunctionalCoverageVisitor final : public VNVisitor {
AstNode* itemp = crossp->itemsp();
while (itemp) {
AstNode* const nextp = itemp->nextp();
AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef);
if (refp) {
// Find the referenced coverpoint via name map (O(log n) vs O(n) linear scan)
const auto it = m_coverpointMap.find(refp->name());
AstCoverpoint* const foundCpp
= (it != m_coverpointMap.end()) ? it->second : nullptr;
AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef);
// Find the referenced coverpoint via name map (O(log n) vs O(n) linear scan)
const auto it = m_coverpointMap.find(refp->name());
AstCoverpoint* const foundCpp
= (it != m_coverpointMap.end()) ? it->second : nullptr;
if (!foundCpp) {
// Name not found as an explicit coverpoint - it's likely a direct variable
// reference (implicit coverpoint). Silently ignore; cross is dropped.
UINFO(4, " Ignoring cross with implicit variable reference: " << refp->name()
);
return;
}
coverpointRefs.push_back(foundCpp);
// Delete the reference node - it's no longer needed
VL_DO_DANGLING(pushDeletep(refp->unlinkFrBack()), refp);
if (!foundCpp) {
// Name not found as an explicit coverpoint - it's likely a direct variable
// reference (implicit coverpoint). Silently ignore; cross is dropped.
UINFO(4, " Ignoring cross with implicit variable reference: " << refp->name()
);
return;
}
coverpointRefs.push_back(foundCpp);
// Delete the reference node - it's no longer needed
VL_DO_DANGLING(pushDeletep(refp->unlinkFrBack()), refp);
itemp = nextp;
}
} // LCOV_EXCL_BR_LINE
UINFO(4, " Generating " << coverpointRefs.size() << "-way cross");
@ -1187,9 +1175,9 @@ class FunctionalCoverageVisitor final : public VNVisitor {
if (AstInsideRange* irp = VN_CAST(currRangep, InsideRange)) {
AstNodeExpr* const minExprp = irp->lhsp();
AstNodeExpr* const maxExprp = irp->rhsp();
AstConst* const minConstp = VN_CAST(minExprp, Const);
AstConst* const maxConstp = VN_CAST(maxExprp, Const);
if (minConstp && maxConstp && minConstp->toSInt() == maxConstp->toSInt()) {
AstConst* const minConstp = VN_AS(minExprp, Const);
AstConst* const maxConstp = VN_AS(maxExprp, Const);
if (minConstp->toUQuad() == maxConstp->toUQuad()) {
// Single value
if (isWildcard) {
rangeCondp = buildWildcardCondition(binp, exprp, minConstp);
@ -1207,12 +1195,14 @@ class FunctionalCoverageVisitor final : public VNVisitor {
rangeCondp = new AstEq{binp->fileline(), exprp->cloneTree(false),
constp->cloneTree(false)};
}
} else {
currRangep->v3error("Non-constant expression in bin range; values must be constants");
return nullptr;
}
if (rangeCondp) {
fullCondp
= fullCondp ? new AstOr{binp->fileline(), fullCondp, rangeCondp} : rangeCondp;
}
UASSERT_OBJ(rangeCondp, binp, "rangeCondp is null after building range condition");
fullCondp
= fullCondp ? new AstOr{binp->fileline(), fullCondp, rangeCondp} : rangeCondp;
}
return fullCondp;
@ -1400,20 +1390,17 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Coverpoint bin: use coverpoint name or generate from expression
std::string cpName = coverpointp->name();
if (cpName.empty()) {
// Generate name from expression
if (coverpointp->exprp()) {
cpName = coverpointp->exprp()->name();
if (cpName.empty()) cpName = "cp";
} else {
cpName = "cp";
}
// Unlabeled coverpoint: name comes from its expression (always a VarRef)
UASSERT_OBJ(coverpointp->exprp(), coverpointp,
"Coverpoint without expression and without name");
cpName = coverpointp->exprp()->name();
UASSERT_OBJ(!cpName.empty(), coverpointp,
"Coverpoint expression has empty name");
}
hierName += "." + cpName;
} else if (crossp) {
// Cross bin: use cross name
std::string crossName = crossp->name();
if (crossName.empty()) crossName = "cross";
hierName += "." + crossName;
} else {
// Cross bin: grammar always provides a name (user label or auto "__crossN")
hierName += "." + crossp->name();
}
hierName += "." + binName;
@ -1486,36 +1473,35 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Store the event in the global map for V3Active to retrieve later
// V3LinkParse only creates this sentinel AstCovergroup node when a clocking
// event exists, so cgp->eventp() is always non-null here.
if (cgp->eventp()) { // LCOV_EXCL_BR_LINE
// Check if the clocking event references a member variable (unsupported)
// Clocking events should be on signals/nets, not class members
bool eventUnsupported = false;
for (AstNode* senp = cgp->eventp()->sensesp(); senp;
senp = senp->nextp()) {
if (AstSenItem* const senItemp = VN_CAST(senp, SenItem)) { // LCOV_EXCL_BR_LINE
if (AstVarRef* const varrefp // LCOV_EXCL_BR_LINE
= VN_CAST(senItemp->sensp(), VarRef)) {
if (varrefp->varp()->isClassMember()) {
cgp->v3warn(COVERIGN,
"Unsupported: 'covergroup' clocking event "
"on member variable");
eventUnsupported = true;
hasUnsupportedEvent = true;
break;
}
}
UASSERT_OBJ(cgp->eventp(), cgp,
"Sentinel AstCovergroup in class must have non-null eventp");
// Check if the clocking event references a member variable (unsupported)
// Clocking events should be on signals/nets, not class members
bool eventUnsupported = false;
for (AstNode* senp = cgp->eventp()->sensesp(); senp;
senp = senp->nextp()) {
AstSenItem* const senItemp = VN_AS(senp, SenItem);
if (AstVarRef* const varrefp // LCOV_EXCL_BR_LINE
= VN_CAST(senItemp->sensp(), VarRef)) {
if (varrefp->varp()->isClassMember()) {
cgp->v3warn(COVERIGN,
"Unsupported: 'covergroup' clocking event "
"on member variable");
eventUnsupported = true;
hasUnsupportedEvent = true;
break;
}
}
}
if (!eventUnsupported) {
// Leave cgp in the class membersp so the SenTree stays
// linked in the AST. V3Active will find it via membersp,
// use the event, then delete the AstCovergroup itself.
UINFO(4, "Keeping covergroup event node for V3Active: "
<< nodep->name());
itemp = nextp;
continue;
}
if (!eventUnsupported) {
// Leave cgp in the class membersp so the SenTree stays
// linked in the AST. V3Active will find it via membersp,
// use the event, then delete the AstCovergroup itself.
UINFO(4, "Keeping covergroup event node for V3Active: "
<< nodep->name());
itemp = nextp;
continue;
}
// Remove the AstCovergroup node - either unsupported event or no event
VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp);

View File

@ -342,7 +342,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
if (setp != nodep->transp()) puts(", ");
iterateConst(setp);
}
} else if (nodep->rangesp()) {
} else if (nodep->rangesp()) { // LCOV_EXCL_BR_LINE - false: CoverBin always has transp/rangesp/default
puts(" = {");
for (AstNode* rangep = nodep->rangesp(); rangep; rangep = rangep->nextp()) {
if (rangep != nodep->rangesp()) puts(", ");
@ -1014,7 +1014,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
}
}
void visit(AstClassRefDType* nodep) override {
putfs(nodep, nodep->classp() ? EmitCUtil::prefixNameProtect(nodep->classp())
putfs(nodep, nodep->classp() ? EmitCUtil::prefixNameProtect(nodep->classp()) // LCOV_EXCL_BR_LINE - false: classp always set after linking
: nodep->prettyDTypeName(false));
}
void visit(AstRequireDType* nodep) override { iterateConst(nodep->lhsp()); }

View File

@ -1130,36 +1130,36 @@ class LinkParseVisitor final : public VNVisitor {
// Handle constructor arguments - add function parameters and assignments
if (argsp) {
// Find the 'new' function to add parameters to
// Find the 'new' function to add parameters to.
// At this point the only AstFunc in the class is the "new" constructor
// created just above; other members are AstVar or AstCovergroup sentinel.
AstFunc* newFuncp = nullptr;
for (AstNode* memberp = nodep->membersp(); memberp; memberp = memberp->nextp()) {
if (AstFunc* const funcp = VN_CAST(memberp, Func)) {
if (funcp->name() == "new") {
newFuncp = funcp;
break;
}
UASSERT_OBJ(funcp->name() == "new", funcp,
"Unexpected non-new function in covergroup class during arg setup");
newFuncp = funcp;
break;
}
}
if (newFuncp) {
// Save the existing body statements and unlink them
AstNode* const existingBodyp = newFuncp->stmtsp();
if (existingBodyp) existingBodyp->unlinkFrBackWithNext();
// Add function parameters and assignments
for (AstNode* argp = argsp; argp; argp = argp->nextp()) {
if (AstVar* const origVarp = VN_CAST(argp, Var)) {
AstVar* const paramp = origVarp->cloneTree(false);
paramp->funcLocal(true);
paramp->direction(VDirection::INPUT);
newFuncp->addStmtsp(paramp);
AstNodeExpr* const lhsp
= new AstParseRef{origVarp->fileline(), origVarp->name()};
AstNodeExpr* const rhsp
= new AstParseRef{paramp->fileline(), paramp->name()};
newFuncp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp});
}
}
if (existingBodyp) newFuncp->addStmtsp(existingBodyp);
UASSERT_OBJ(newFuncp, nodep, "Covergroup class must have a 'new' constructor function");
// Save the existing body statements and unlink them
AstNode* const existingBodyp = newFuncp->stmtsp();
if (existingBodyp) existingBodyp->unlinkFrBackWithNext();
// Add function parameters and assignments
for (AstNode* argp = argsp; argp; argp = argp->nextp()) {
AstVar* const origVarp = VN_AS(argp, Var);
AstVar* const paramp = origVarp->cloneTree(false);
paramp->funcLocal(true);
paramp->direction(VDirection::INPUT);
newFuncp->addStmtsp(paramp);
AstNodeExpr* const lhsp
= new AstParseRef{origVarp->fileline(), origVarp->name()};
AstNodeExpr* const rhsp
= new AstParseRef{paramp->fileline(), paramp->name()};
newFuncp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp});
}
if (existingBodyp) newFuncp->addStmtsp(existingBodyp);
}
// IEEE: option / type_option members allow external access (cg_inst.option.X)
@ -1189,17 +1189,16 @@ class LinkParseVisitor final : public VNVisitor {
AstFunc* const funcp = new AstFunc{nodep->fileline(), "sample", nullptr, nullptr};
if (sampleArgsp) {
for (AstNode* argp = sampleArgsp; argp; argp = argp->nextp()) {
if (AstVar* const origVarp = VN_CAST(argp, Var)) {
AstVar* const paramp = origVarp->cloneTree(false);
paramp->funcLocal(true);
paramp->direction(VDirection::INPUT);
funcp->addStmtsp(paramp);
AstNodeExpr* const lhsp
= new AstParseRef{origVarp->fileline(), origVarp->name()};
AstNodeExpr* const rhsp
= new AstParseRef{paramp->fileline(), paramp->name()};
funcp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp});
}
AstVar* const origVarp = VN_AS(argp, Var);
AstVar* const paramp = origVarp->cloneTree(false);
paramp->funcLocal(true);
paramp->direction(VDirection::INPUT);
funcp->addStmtsp(paramp);
AstNodeExpr* const lhsp
= new AstParseRef{origVarp->fileline(), origVarp->name()};
AstNodeExpr* const rhsp
= new AstParseRef{paramp->fileline(), paramp->name()};
funcp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp});
}
}
funcp->classMethod(true);
@ -1283,24 +1282,22 @@ class LinkParseVisitor final : public VNVisitor {
// Convert constructor args to member variables
for (AstNode* argp = nodep->argsp(); argp; argp = argp->nextp()) {
if (AstVar* const origVarp = VN_CAST(argp, Var)) {
AstVar* const memberp = origVarp->cloneTree(false);
memberp->varType(VVarType::MEMBER);
memberp->funcLocal(false);
memberp->direction(VDirection::NONE);
cgClassp->addMembersp(memberp);
}
AstVar* const origVarp = VN_AS(argp, Var);
AstVar* const memberp = origVarp->cloneTree(false);
memberp->varType(VVarType::MEMBER);
memberp->funcLocal(false);
memberp->direction(VDirection::NONE);
cgClassp->addMembersp(memberp);
}
// Convert sample args to member variables
for (AstNode* argp = nodep->sampleArgsp(); argp; argp = argp->nextp()) {
if (AstVar* const origVarp = VN_CAST(argp, Var)) {
AstVar* const memberp = origVarp->cloneTree(false);
memberp->varType(VVarType::MEMBER);
memberp->funcLocal(false);
memberp->direction(VDirection::NONE);
cgClassp->addMembersp(memberp);
}
AstVar* const origVarp = VN_AS(argp, Var);
AstVar* const memberp = origVarp->cloneTree(false);
memberp->varType(VVarType::MEMBER);
memberp->funcLocal(false);
memberp->direction(VDirection::NONE);
cgClassp->addMembersp(memberp);
}
// Create the constructor; detach membersp (coverage body) and use as its body

View File

@ -343,18 +343,9 @@ class TimingSuspendableVisitor final : public VNVisitor {
}
}
void visit(AstNodeCCall* nodep) override {
AstCFunc* funcp = nodep->funcp();
if (!funcp) { // LCOV_EXCL_BR_LINE -- AstNodeCCall always has non-null funcp
iterateChildren(nodep);
return;
}
// Skip if we're not inside a function/procedure (m_procp would be null)
// This can happen for calls in Active nodes at module scope
if (!m_procp) { // LCOV_EXCL_BR_LINE -- m_procp is always set when CCall is inside a function
iterateChildren(nodep);
return;
}
AstCFunc* const funcp = nodep->funcp();
UASSERT_OBJ(funcp, nodep, "AstNodeCCall must have non-null funcp post-link");
UASSERT_OBJ(m_procp, nodep, "AstNodeCCall must be inside a procedure/CFunc/Begin");
UINFO(9, "V3Timing: Processing CCall to " << funcp->name() << " in dependency graph\n");
new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(funcp), getSuspendDepVtx(m_procp), P_CALL};

View File

@ -4,16 +4,23 @@
// SPDX-FileCopyrightText: 2025 Antmicro
// SPDX-License-Identifier: CC0-1.0
// A plain (non-covergroup) class — exercises the non-covergroup class scope/varscope paths
class PlainClass;
int x;
endclass
// verilator lint_off COVERIGN
module t;
int i, j;
covergroup cg(int var1, int var2 = 42);
cp1: coverpoint i; // Non-empty body with args: exercises constructor-body path
endgroup
cg cov1 = new(69, 77);
cg cov2 = new(69);
int i, j;
PlainClass plain_inst = new; // Non-covergroup class instance: exercises early-return paths
function void x();
cov1.set_inst_name("the_inst_name");

View File

@ -7,4 +7,24 @@
: ... note: In instance 't'
24 | bins auto[0];
| ^~~~
%Error: t/t_covergroup_autobins_bad.v:34:26: Non-constant expression in bin value list; values must be constants
: ... note: In instance 't'
34 | ignore_bins ign = {size_var};
| ^~~~~~~~
%Error: t/t_covergroup_autobins_bad.v:31:12: Non-constant expression in array bins range; range bounds must be constants
: ... note: In instance 't'
31 | bins b[] = {[size_var:size_var]};
| ^
%Error: t/t_covergroup_autobins_bad.v:32:12: Non-constant expression in array bins range; range bounds must be constants
: ... note: In instance 't'
32 | bins b_mixed[] = {[0:size_var]};
| ^~~~~~~
%Error: t/t_covergroup_autobins_bad.v:33:18: Non-constant expression in bin range; values must be constants
: ... note: In instance 't'
33 | bins b2 = {size_var};
| ^~~~~~~~
%Error: t/t_covergroup_autobins_bad.v:34:26: Non-constant expression in bin range; values must be constants
: ... note: In instance 't'
34 | ignore_bins ign = {size_var};
| ^~~~~~~~
%Error: Exiting due to

View File

@ -25,8 +25,19 @@ module t;
}
endgroup
// Error: non-constant value in bin ranges
covergroup cg3;
cp1: coverpoint cp_expr {
bins b[] = {[size_var:size_var]}; // non-constant array bins range (both bounds non-const)
bins b_mixed[] = {[0:size_var]}; // non-constant array bins range (max bound non-const)
bins b2 = {size_var}; // non-constant simple bin value
ignore_bins ign = {size_var}; // non-constant ignore_bins value
}
endgroup
cg1 cg1_inst = new;
cg2 cg2_inst = new;
cg3 cg3_inst = new;
initial $finish;
endmodule

View File

@ -57,6 +57,15 @@ cg5.cp_addr.addr0: 1
cg5.cp_addr.addr1: 1
cg5.cp_cmd.read: 1
cg5.cp_cmd.write: 1
cg_ignore.cp_addr.a0: 2
cg_ignore.cp_addr.a1: 2
cg_ignore.cp_addr.ign [ignore]: 1
cg_ignore.cp_cmd.read: 3
cg_ignore.cp_cmd.write: 2
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_range.addr_cmd_range.hi_range_x_read [cross]: 1
cg_range.addr_cmd_range.hi_range_x_write [cross]: 1
cg_range.addr_cmd_range.lo_range_x_read [cross]: 1
@ -65,3 +74,11 @@ cg_range.cp_addr.hi_range: 2
cg_range.cp_addr.lo_range: 2
cg_range.cp_cmd.read: 2
cg_range.cp_cmd.write: 2
cg_unnamed_cross.__cross7.a0_x_read [cross]: 1
cg_unnamed_cross.__cross7.a0_x_write [cross]: 0
cg_unnamed_cross.__cross7.a1_x_read [cross]: 0
cg_unnamed_cross.__cross7.a1_x_write [cross]: 1
cg_unnamed_cross.cp_a.a0: 1
cg_unnamed_cross.cp_a.a1: 1
cg_unnamed_cross.cp_c.read: 1
cg_unnamed_cross.cp_c.write: 1

View File

@ -92,11 +92,35 @@ module t;
addr_cmd_range: cross cp_addr, cp_cmd;
endgroup
// Cross where one coverpoint has ignore_bins: exercises BINS_USER FALSE branch
// in collectBins during cross code generation (L1139)
covergroup cg_ignore;
cp_addr: coverpoint addr {
ignore_bins ign = {3}; // BINS_IGNORE: not BINS_USER, exercises L1139 FALSE path
bins a0 = {0};
bins a1 = {1};
}
cp_cmd: coverpoint cmd {
bins read = {0};
bins write = {1};
}
cross_ab: cross cp_addr, cp_cmd;
endgroup
// Covergroup with unnamed cross: exercises crossName.empty() fallback to "cross" (L1395)
covergroup cg_unnamed_cross;
cp_a: coverpoint addr { bins a0 = {0}; bins a1 = {1}; }
cp_c: coverpoint cmd { bins read = {0}; bins write = {1}; }
cross cp_a, cp_c; // no label -> crossName is empty
endgroup
cg2 cg2_inst = new;
cg_ignore cg_ignore_inst = new;
cg_range cg_range_inst = new;
cg3 cg3_inst = new;
cg4 cg4_inst = new;
cg5 cg5_inst = new;
cg_unnamed_cross cg_unnamed_cross_inst = new;
initial begin
// Sample 2-way: hit all 4 combinations
@ -121,12 +145,23 @@ module t;
addr = 0; cmd = 0; cg5_inst.sample();
addr = 1; cmd = 1; cg5_inst.sample();
// Sample cg_ignore: addr=3 is in ignore_bins so no cross bins for it
addr = 0; cmd = 0; cg_ignore_inst.sample(); // a0 x read
addr = 1; cmd = 1; cg_ignore_inst.sample(); // a1 x write
addr = 0; cmd = 1; cg_ignore_inst.sample(); // a0 x write
addr = 1; cmd = 0; cg_ignore_inst.sample(); // a1 x read
addr = 3; cmd = 0; cg_ignore_inst.sample(); // ignored (addr=3 in ignore_bins)
// Sample range-bin cross
addr = 0; cmd = 0; cg_range_inst.sample(); // lo_range x read
addr = 2; cmd = 1; cg_range_inst.sample(); // hi_range x write
addr = 1; cmd = 1; cg_range_inst.sample(); // lo_range x write
addr = 3; cmd = 0; cg_range_inst.sample(); // hi_range x read
// Sample cg_unnamed_cross: exercises unnamed cross (crossName fallback to "cross")
addr = 0; cmd = 0; cg_unnamed_cross_inst.sample(); // a0 x read
addr = 1; cmd = 1; cg_unnamed_cross_inst.sample(); // a1 x write
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -2,3 +2,19 @@ cg.data.high: 1
cg.data.low: 1
cg.data.other: 2
cg2.cp_only_default.all: 4
cg3.data.bad [ignore]: 1
cg3.data.err [illegal]: 0
cg3.data.normal: 2
cg3.data.other: 2
cg4.cp_idx.auto_0: 1
cg4.cp_idx.auto_1: 1
cg4.cp_idx.auto_2: 1
cg4.cp_idx.skip [ignore]: 0
cg5.cp_data64.auto[0]: 2
cg5.cp_data64.auto[1]: 0
cg5.cp_data64.auto[2]: 0
cg5.cp_data64.auto[3]: 0
cg6.cp_data65.hi: 1
cg6.cp_data65.lo: 1
cg7.data.hi: 1
cg7.data.lo: 1

View File

@ -6,8 +6,18 @@
// Test default bins - catch-all for values not in other bins
// Non-covergroup class: exercises V3Active isCovergroup()=false branch
class DataHelper;
bit [7:0] val;
function new(bit [7:0] v); val = v; endfunction
endclass
module t;
bit [7:0] data;
logic [1:0] idx;
logic [63:0] data64; // 64-bit: exercises width>=64 auto-bin path (L139)
logic [64:0] data65; // 65-bit: exercises exprWidth>64 in makeRangeCondition
DataHelper helper; // Module-level class var: exercises V3Active isCovergroup()=false
covergroup cg;
coverpoint data {
@ -24,12 +34,59 @@ module t;
}
endgroup
// Covergroup with default + ignore + illegal bins: exercises BINS_IGNORE/BINS_ILLEGAL
// skip paths in generateDefaultBinMatchCode (L558-L559)
covergroup cg3;
coverpoint data {
ignore_bins bad = {255}; // BINS_IGNORE skip path
illegal_bins err = {254}; // BINS_ILLEGAL skip path
bins normal = {[1:10]};
bins other = default;
}
endgroup
// Covergroup with auto-bins + ignore_bins on small range: exercises L295 excluded-value continue
// When numValidValues <= auto_bin_max, single-value auto-bins are created per value; the
// excluded.find() check at L295 fires for the ignore_bins value (idx=2).
covergroup cg4;
cp_idx: coverpoint idx {
ignore_bins skip = {2}; // value 2 excluded; auto-bins created for 0,1,3
}
endgroup
// 64-bit signal with 4 auto-bins: exercises width>=64 branch in auto-bin range calculation
covergroup cg5;
cp_data64: coverpoint data64 { bins auto[4]; }
endgroup
// 65-bit signal with range bins: exercises exprWidth>64 path in makeRangeCondition
covergroup cg6;
cp_data65: coverpoint data65 { bins lo = {[0:15]}; bins hi = {[100:200]}; }
endgroup
// Unlabeled coverpoint: exercises cpName fallback via exprp()->name() (L1394-1398)
covergroup cg7;
coverpoint data { bins lo = {[0:7]}; bins hi = {[8:15]}; }
endgroup
initial begin
cg cg_inst;
cg2 cg2_inst;
cg3 cg3_inst;
cg4 cg4_inst;
cg5 cg5_inst;
cg6 cg6_inst;
cg7 cg7_inst;
cg_inst = new();
cg2_inst = new();
cg3_inst = new();
cg4_inst = new();
cg5_inst = new();
cg6_inst = new();
cg7_inst = new();
helper = new(8'h42);
data = helper.val; // Use helper to avoid optimization
// Hit low bin
data = 2;
@ -51,6 +108,31 @@ module t;
cg_inst.sample();
cg2_inst.sample();
// Sample cg3: exercises BINS_IGNORE/BINS_ILLEGAL skip in default-bin detection loop
data = 2; cg3_inst.sample(); // hits normal bin
data = 7; cg3_inst.sample(); // hits normal bin again
data = 255; cg3_inst.sample(); // ignore_bins (not counted)
// note: do not sample 254 (illegal_bins would cause runtime assertion)
data = 100; cg3_inst.sample(); // hits default (other) bin
// Sample cg4: exercises auto-bin generation with excluded value (L295)
// idx=2 is in ignore_bins, so auto-bins cover 0,1,3 only
idx = 0; cg4_inst.sample();
idx = 1; cg4_inst.sample();
idx = 3; cg4_inst.sample();
// Sample cg5: 64-bit signal, sample into bin b[0] (value 0 is in first quarter)
data64 = 0; cg5_inst.sample();
data64 = 5; cg5_inst.sample();
// Sample cg6: 65-bit signal with range bins
data65 = 5; cg6_inst.sample(); // hits bin lo=[0:15]
data65 = 150; cg6_inst.sample(); // hits bin hi=[100:200]
// Sample cg7: unlabeled coverpoint (exercises exprp()->name() path)
data = 3; cg7_inst.sample(); // hits bin lo
data = 10; cg7_inst.sample(); // hits bin hi
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -0,0 +1,2 @@
cg.cp.ign [ignore]: 2
cg.cp.ill [illegal]: 0

View File

@ -0,0 +1,17 @@
# DESCRIPTION: Verilator: Verilog Test driver for Verilog simulator
#
# Copyright 2026 by Wilson Snyder. 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=['--coverage'])
test.execute()
test.covergroup_coverage_report()
test.files_identical(test.obj_dir + "/covergroup_report.txt", test.golden_filename)
test.passes()

View File

@ -0,0 +1,33 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// Test covergroup where all bins are ignore_bins or illegal_bins (no regular
// bins). This exercises the totalBins==0 path in generateCoverageMethodBody()
// which returns 100.0 immediately.
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2026 by Wilson Snyder.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t;
logic [1:0] data;
covergroup cg;
cp: coverpoint data {
ignore_bins ign = {0, 1};
illegal_bins ill = {2, 3};
// No regular bins -> totalBins == 0 -> get_coverage returns 100.0
}
endgroup
initial begin
automatic cg cg_inst = new;
// Only sample values that fall in ignore_bins, never illegal_bins
data = 0; cg_inst.sample();
data = 1; cg_inst.sample();
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -237,8 +237,8 @@
%Warning-COVERIGN: t/t_covergroup_unsup.v:198:7: Unsupported: explicit coverage cross bins
198 | illegal_bins lib_cross = binsof(a);
| ^~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_covergroup_unsup.v:220:5: Unsupported: covergroup inheritance (extends)
220 | covergroup extends cg_empty;
%Error-UNSUPPORTED: t/t_covergroup_unsup.v:223:5: Unsupported: covergroup inheritance (extends)
223 | covergroup extends cg_empty;
| ^~~~~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -207,8 +207,11 @@ module t (
int m_y;
int m_z;
covergroup cov1 @m_z;
coverpoint m_x;
coverpoint m_y;
cp_x: coverpoint m_x;
cp_y: coverpoint m_y;
`ifdef T_COVERGROUP_UNSUP_IGN
xy_cross: cross cp_x, cp_y; // exercises cross cleanup in hasUnsupportedEvent path
`endif
endgroup
`ifndef T_COVERGROUP_UNSUP_IGN
function new(); cov1 = new; endfunction

View File

@ -676,10 +676,15 @@ module Vt_debug_emitv_t;
cp_sig: coverpoint cg_sig {
bins low = {['sh0:'sh3]};
bins high = {['sh4:'sh6]};
bins multi = {'sh0, 'sh1, 'sh2};
bins dflt = default;
ignore_bins ign = {'sh7};
illegal_bins ill = {'sh5};
};
cp_options: coverpoint cg_sig2 {
???? // COVEROPTION
'sh2};
endfunction
int signed __Vint;
struct {
@ -784,6 +789,7 @@ module Vt_debug_emitv_t;
cp_t: coverpoint cg_sig {
bins t01 = (3'h0 => 3'h1);
bins t12 = (3'h1 => 3'h2);
bins talt = (3'h2 => 3'h3), (3'h4 => 3'h5);
};
endfunction
int signed __Vint;

View File

@ -358,10 +358,15 @@ module t (/*AUTOARG*/
cp_sig: coverpoint cg_sig {
bins low = {[0:3]};
bins high = {[4:6]};
bins multi = {0, 1, 2}; // multiple values in one bins (exercises EmitV range loop)
bins dflt = default;
ignore_bins ign = {7};
illegal_bins ill = {5};
}
// Coverpoint with per-coverpoint option but no explicit bins
cp_options: coverpoint cg_sig2 {
option.at_least = 2;
}
endgroup
// Covergroup with clocking event
@ -372,8 +377,9 @@ module t (/*AUTOARG*/
// Covergroup with transition bins
covergroup cg_trans;
cp_t: coverpoint cg_sig {
bins t01 = (3'b000 => 3'b001);
bins t12 = (3'b001 => 3'b010);
bins t01 = (3'b000 => 3'b001);
bins t12 = (3'b001 => 3'b010);
bins talt = (3'b010 => 3'b011), (3'b100 => 3'b101); // multiple transition sets
}
endgroup

View File

@ -151,4 +151,14 @@ module Test(/*AUTOARG*/
endcase
end
logic [1:0] cg_v1;
logic [1:0] cg_v2;
covergroup cg @(posedge clk);
option.at_least = 2;
cp1: coverpoint cg_v1 { option.weight = 1; bins lo = {0}; }
cp2: coverpoint cg_v2;
cx: cross cp1, cp2;
endgroup
cg cg_inst = new;
endmodule

View File

@ -9,7 +9,7 @@
module t;
%000001 logic [1:0] addr;
%000000 logic cmd;
%000001 logic cmd;
%000001 logic mode;
%000001 logic parity;
@ -93,11 +93,35 @@
%000001 addr_cmd_range: cross cp_addr, cp_cmd;
endgroup
// Cross where one coverpoint has ignore_bins: exercises BINS_USER FALSE branch
// in collectBins during cross code generation (L1139)
%000005 covergroup cg_ignore;
%000001 cp_addr: coverpoint addr {
%000001 ignore_bins ign = {3}; // BINS_IGNORE: not BINS_USER, exercises L1139 FALSE path
%000002 bins a0 = {0};
%000002 bins a1 = {1};
}
%000001 cp_cmd: coverpoint cmd {
%000003 bins read = {0};
%000002 bins write = {1};
}
%000001 cross_ab: cross cp_addr, cp_cmd;
endgroup
// Covergroup with unnamed cross: exercises crossName.empty() fallback to "cross" (L1395)
%000002 covergroup cg_unnamed_cross;
%000001 cp_a: coverpoint addr { bins a0 = {0}; bins a1 = {1}; }
%000001 cp_c: coverpoint cmd { bins read = {0}; bins write = {1}; }
%000001 cross cp_a, cp_c; // no label -> crossName is empty
endgroup
%000001 cg2 cg2_inst = new;
%000001 cg_ignore cg_ignore_inst = new;
%000001 cg_range cg_range_inst = new;
%000001 cg3 cg3_inst = new;
%000001 cg4 cg4_inst = new;
%000001 cg5 cg5_inst = new;
%000001 cg_unnamed_cross cg_unnamed_cross_inst = new;
%000001 initial begin
// Sample 2-way: hit all 4 combinations
@ -122,12 +146,23 @@
%000001 addr = 0; cmd = 0; cg5_inst.sample();
%000001 addr = 1; cmd = 1; cg5_inst.sample();
// Sample cg_ignore: addr=3 is in ignore_bins so no cross bins for it
%000001 addr = 0; cmd = 0; cg_ignore_inst.sample(); // a0 x read
%000001 addr = 1; cmd = 1; cg_ignore_inst.sample(); // a1 x write
%000001 addr = 0; cmd = 1; cg_ignore_inst.sample(); // a0 x write
%000001 addr = 1; cmd = 0; cg_ignore_inst.sample(); // a1 x read
%000001 addr = 3; cmd = 0; cg_ignore_inst.sample(); // ignored (addr=3 in ignore_bins)
// Sample range-bin cross
%000001 addr = 0; cmd = 0; cg_range_inst.sample(); // lo_range x read
%000001 addr = 2; cmd = 1; cg_range_inst.sample(); // hi_range x write
%000001 addr = 1; cmd = 1; cg_range_inst.sample(); // lo_range x write
%000001 addr = 3; cmd = 0; cg_range_inst.sample(); // hi_range x read
// Sample cg_unnamed_cross: exercises unnamed cross (crossName fallback to "cross")
%000001 addr = 0; cmd = 0; cg_unnamed_cross_inst.sample(); // a0 x read
%000001 addr = 1; cmd = 1; cg_unnamed_cross_inst.sample(); // a1 x write
%000001 $write("*-* All Finished *-*\n");
%000001 $finish;
end

View File

@ -1,7 +1,8 @@
COVERGROUP COVERAGE REPORT
==========================
TOTAL: 45/67 bins covered (67.16%)
TOTAL: 59/83 bins covered (71.08%)
(1 ignored, 0 illegal)
------------------------------------------------------------------------------
Covergroup Type: cg2 [t/t_covergroup_cross.v:18]
@ -138,6 +139,31 @@ Covergroup Type: cg5 [t/t_covergroup_cross.v:70]
ZERO addr1_x_read 0 hits
COVERED addr1_x_write 1 hits
------------------------------------------------------------------------------
Covergroup Type: cg_ignore [t/t_covergroup_cross.v:100]
Type Coverage: 8/8 bins (100.00%)
Coverpoint: cp_addr
Coverage: 2/2 bins (100.00%)
Bins:
COVERED a0 2 hits
COVERED a1 2 hits
IGNORE ign 1 hits
Coverpoint: cp_cmd
Coverage: 2/2 bins (100.00%)
Bins:
COVERED read 3 hits
COVERED write 2 hits
Cross: cross_ab
Coverage: 4/4 bins (100.00%)
Bins:
COVERED a0_x_read 1 hits
COVERED a0_x_write 1 hits
COVERED a1_x_read 1 hits
COVERED a1_x_write 1 hits
------------------------------------------------------------------------------
Covergroup Type: cg_range [t/t_covergroup_cross.v:85]
Type Coverage: 8/8 bins (100.00%)
@ -163,3 +189,27 @@ Covergroup Type: cg_range [t/t_covergroup_cross.v:85]
COVERED lo_range_x_write 1 hits
------------------------------------------------------------------------------
Covergroup Type: cg_unnamed_cross [t/t_covergroup_cross.v:112]
Type Coverage: 6/8 bins (75.00%)
Coverpoint: cp_a
Coverage: 2/2 bins (100.00%)
Bins:
COVERED a0 1 hits
COVERED a1 1 hits
Coverpoint: cp_c
Coverage: 2/2 bins (100.00%)
Bins:
COVERED read 1 hits
COVERED write 1 hits
Cross: __cross7
Coverage: 2/4 bins (50.00%)
Bins:
COVERED a0_x_read 1 hits
ZERO a0_x_write 0 hits
ZERO a1_x_read 0 hits
COVERED a1_x_write 1 hits
------------------------------------------------------------------------------