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:
parent
f1389497c1
commit
e710f1b6b3
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()); }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
cg.cp.ign [ignore]: 2
|
||||
cg.cp.ill [illegal]: 0
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in New Issue