Support coverpoint bin 'with' filters (#8518)

This commit is contained in:
Marco Bartoli
2026-09-28 18:29:54 -04:00
committed by GitHub
parent a949674a6b
commit bde1b98ee0
48 changed files with 2366 additions and 293 deletions
+447 -123
View File
@@ -30,6 +30,7 @@
#include "V3Error.h"
#include "V3File.h"
#include "V3MemberMap.h"
#include "V3UniqueNames.h"
#include <bitset>
#include <cmath>
@@ -295,6 +296,11 @@ class FunctionalCoverageVisitor final : public VNVisitor {
std::deque<BinRun> runs; // Runs 'values' refers to
};
std::map<AstVar*, CoverpointBins> m_cpBins; // Runtime coverpoint -> binsof index ranges
// Names of the bins declarations each coverpoint ignored, which binsof selects as no bins
std::map<const AstCoverpoint*, std::vector<std::string>> m_droppedBins;
// Prefixes of the constructor temporaries of constructed bins, as coverpoint and bin names
// alone may repeat: coverpoint 'a_' bins 'b', and coverpoint 'a' bins '_b'
V3UniqueNames m_sizedNames{"__Vsized"};
std::vector<AstNodeExpr*> m_detachedValues; // Array-bin values m_cpBins refers to
std::set<AstCoverCross*>
m_droppedCrosses; // Crosses with a bare-variable item: drop (COVERIGN)
@@ -372,6 +378,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
m_crossedCpNames.clear();
m_cpVarMap.clear();
m_cpBins.clear();
m_droppedBins.clear();
m_sizedNames.reset();
m_runtimePoints.clear();
m_runtimeCrosses.clear();
m_excludedVars.clear();
@@ -400,12 +408,13 @@ class FunctionalCoverageVisitor final : public VNVisitor {
std::map<AstCoverpoint*, std::vector<AstCoverCross*>> consumers;
std::map<AstCoverCross*, std::vector<AstCoverpoint*>> inputs;
for (AstCoverpoint* const cpp : m_coverpoints) {
checkSizedArrays(cpp);
checkBinNames(cpp);
checkConstructedBins(cpp);
if (!cpp->exprp()->dtypep()->skipRefp()->isIntegralOrPacked()) continue;
// Bins without values leave the report (IEEE 1800-2023 19.11.1), exclusions or not.
// A sized array of bins gets its values when the covergroup is constructed.
// Constructed bins get their values when the covergroup is constructed.
if (!coverpointHasStateExclusions(cpp) && !coverpointHasEmptyBins(cpp)
&& !coverpointHasSizedArrays(cpp)) {
&& !coverpointHasConstructedBins(cpp)) {
continue;
}
m_runtimePoints.insert(cpp);
@@ -495,62 +504,176 @@ class FunctionalCoverageVisitor final : public VNVisitor {
return binp->arraySizep() && !isAutoBins(binp);
}
// Check the sized arrays of bins of a coverpoint, dropping invalid ones. A real
// coverpoint's are unsupported, and treated as arrays of a bin per value. A wildcard array
// of too many ranges of values is ignored, or if ignore or illegal, treated as one bin.
void checkSizedArrays(AstCoverpoint* coverpointp) {
const bool integral = coverpointp->exprp()->dtypep()->skipRefp()->isIntegralOrPacked();
// The 'with' filter of a bin, if any (IEEE 1800-2023 19.5.1.1)
static AstCoverWith* binWith(const AstCoverBin* binp) {
return VN_CAST(binp->rangesp(), CoverWith);
}
// True for bins whose values the covergroup constructor computes: a sized array, or bins of
// the values a 'with' filter keeps
static bool isConstructedBins(const AstCoverBin* binp) {
return isSizedArray(binp) || binWith(binp);
}
// The range list of a bin's values, or of the candidates of its 'with' filter; null for all
// of the coverpoint's values, which a filter of the coverpoint's name has
static AstNode* binRangesp(const AstCoverBin* binp) {
const AstCoverWith* const withp = binWith(binp);
if (!withp) return binp->rangesp();
return VN_IS(withp->subp(), CoverpointRef) ? nullptr : withp->subp();
}
// Report and delete a bins declaration of a name that another of its coverpoint has
void checkBinNames(AstCoverpoint* coverpointp) {
std::set<std::string> names;
for (AstNode* nodep = coverpointp->binsp(); nodep;) {
AstCoverBin* const binp = VN_AS(nodep, CoverBin);
nodep = nodep->nextp();
if (!isSizedArray(binp)) continue;
AstNodeExpr* const sizep = binp->arraySizep();
const AstConst* const constp = VN_CAST(sizep, Const);
if (VN_IS(sizep, Unbounded)) { // A parameter of '$'; see bins_orBraE
binp->v3error("Bins array size must be integral, not '$' (IEEE 1800-2023 19.5.1)");
} else if (!sizep->dtypep()->skipRefp()->isIntegralOrPacked()) {
sizep->v3error("Bins array size must be integral (IEEE 1800-2023 19.5.1)");
} else if (constp && (constp->num().isFourState() || binsCount(constp) < 1)) {
sizep->v3error("Bins array size must be >= 1, got "
<< (constp->num().isFourState() ? constp->num().ascii(false)
: constp->num().toDecimalS())
<< " (IEEE 1800-2023 19.5.1)");
} else if (!integral) {
binp->v3warn(COVERIGN, "Unsupported: 'bins' explicit array size of a real "
"coverpoint (treated as '[]')");
VL_DO_DANGLING(pushDeletep(sizep->unlinkFrBack()), sizep);
continue;
} else if (binp->isWildcard()
&& sizedWildcardRuns(binp, coverpointp->exprp())
> v3Global.opt.coverageMaxBins()) {
// An ignore or illegal array still excludes or checks its values, as one bin
const bool single = !binp->binsType().binIsNormal();
binp->v3warn(COVERIGN,
"Unsupported: sized wildcard array '"
<< binp->binsType().verilogKwd()
<< "' of more than --coverage-max-bins of "
<< v3Global.opt.coverageMaxBins() << " ranges of values; "
<< (single ? "treated as one bin" : "bin ignored") << "\n"
<< binp->warnMore()
<< "... Suggest a larger --coverage-max-bins");
if (single) {
VL_DO_DANGLING(pushDeletep(sizep->unlinkFrBack()), sizep);
binp->isArray(false);
continue;
}
coverpointp->user2(true);
} else {
continue;
}
if (names.emplace(binp->name()).second) continue;
binp->v3error("Duplicate bin " << binp->prettyNameQ() << " in coverpoint "
<< coverpointp->prettyNameQ()
<< " (IEEE 1800-2023 3.13)");
VL_DO_DANGLING(pushDeletep(binp->unlinkFrBack()), binp);
}
}
// The ranges of values the wildcard patterns of a sized wildcard array give, counted up to
// more than --coverage-max-bins
// Delete an ignored bins declaration, which binsof then selects as no bins
void dropBins(const AstCoverpoint* coverpointp, AstCoverBin* binp) {
m_droppedBins[coverpointp].push_back(binp->name());
VL_DO_DANGLING(pushDeletep(binp->unlinkFrBack()), binp);
}
// Check the size of a sized array of bins, which drops an invalid array. A real coverpoint's
// are unsupported, and treated as arrays of a bin per value. False unless it stays sized.
bool checkBinsArraySize(const AstCoverpoint* coverpointp, AstCoverBin* binp, bool integral) {
AstNodeExpr* const sizep = binp->arraySizep();
const AstConst* const constp = VN_CAST(sizep, Const);
if (VN_IS(sizep, Unbounded)) { // A parameter of '$'; see bins_orBraE
binp->v3error("Bins array size must be integral, not '$' (IEEE 1800-2023 19.5.1)");
} else if (!sizep->dtypep()->skipRefp()->isIntegralOrPacked()) {
sizep->v3error("Bins array size must be integral (IEEE 1800-2023 19.5.1)");
} else if (constp && (constp->num().isFourState() || binsCount(constp) < 1)) {
sizep->v3error("Bins array size must be >= 1, got "
<< (constp->num().isFourState() ? constp->num().ascii(false)
: constp->num().toDecimalS())
<< " (IEEE 1800-2023 19.5.1)");
} else if (!integral) {
binp->v3warn(COVERIGN, "Unsupported: 'bins' explicit array size of a real "
"coverpoint (treated as '[]')");
VL_DO_DANGLING(pushDeletep(sizep->unlinkFrBack()), sizep);
return false;
} else {
return true;
}
dropBins(coverpointp, binp);
return false;
}
// Check the bins of a coverpoint whose values the constructor computes, dropping invalid
// ones. A wildcard array of too many ranges of values is ignored, or if ignore or illegal,
// treated as one bin; filtering too many ranges of values ignores the bins.
void checkConstructedBins(AstCoverpoint* coverpointp) {
const bool integral = coverpointp->exprp()->dtypep()->skipRefp()->isIntegralOrPacked();
for (AstNode* nodep = coverpointp->binsp(); nodep;) {
AstCoverBin* const binp = VN_AS(nodep, CoverBin);
nodep = nodep->nextp();
if (!isConstructedBins(binp)) continue;
if (isSizedArray(binp) && !checkBinsArraySize(coverpointp, binp, integral)) continue;
if (!binp->isWildcard()
|| sizedWildcardRuns(binp, coverpointp->exprp())
<= v3Global.opt.coverageMaxBins()) {
if (binWith(binp) && withCandidatesOver(binp, coverpointp->exprp())) {
binp->v3warn(COVERIGN, "Unsupported: 'with' filter of more than 2**32 "
"candidate values; bin "
<< binp->prettyNameQ() << " ignored");
if (binp->binsType().binIsNormal()) coverpointp->user2(true);
dropBins(coverpointp, binp);
}
continue;
}
// An ignore or illegal array still excludes or checks its values, as one bin
const bool single = !binp->binsType().binIsNormal() && !binWith(binp);
binp->v3warn(COVERIGN,
"Unsupported: " << (binWith(binp) ? "'with' filter of wildcard '"
: "sized wildcard array '")
<< binp->binsType().verilogKwd()
<< "' of more than --coverage-max-bins of "
<< v3Global.opt.coverageMaxBins()
<< " ranges of values; bin " << binp->prettyNameQ()
<< (single ? " treated as one bin" : " ignored") << "\n"
<< binp->warnMore()
<< "... Suggest a larger --coverage-max-bins");
if (single) {
AstNodeExpr* const sizep = binp->arraySizep();
VL_DO_DANGLING(pushDeletep(sizep->unlinkFrBack()), sizep);
binp->isArray(false);
continue;
}
if (binp->binsType().binIsNormal()) coverpointp->user2(true);
dropBins(coverpointp, binp);
}
}
// True if a 'with' filter would be evaluated for more than 2**32 candidate values, known
// now for the coverpoint's name, or for a range list of constants: each is evaluated once
// but for an array 'b[N]', which keeps their order and duplicates (see withBegin())
static bool withCandidatesOver(AstCoverBin* binp, AstNodeExpr* exprp) {
const int width = runWidth(exprp);
std::vector<std::pair<V3Number, V3Number>> runs;
if (!binRangesp(binp)) runs = coverpointValues(binp, exprp);
const auto constant
= [](const AstNode* nodep) { return VN_IS(nodep, Const) || VN_IS(nodep, Unbounded); };
for (AstNode* rangep = binRangesp(binp); rangep; rangep = rangep->nextp()) {
const AstInsideRange* const irp = VN_CAST(rangep, InsideRange);
// Else known when constructed
if (irp ? !constant(irp->lhsp()) || !constant(irp->rhsp()) : !VN_IS(rangep, Const)) {
return false;
}
CrossValueRange range{rangep, resolveWidth(rangep, exprp)};
if (!resolveValue(rangep, exprp, true, binp->isWildcard(), range)
|| crossRangeEmpty(range)) {
continue;
}
std::vector<std::pair<V3Number, V3Number>> found{{range.lo, range.hi}};
if (range.wildcard) { // checkConstructedBins bounded the runs
found.clear();
crossRangeRuns(range, v3Global.opt.coverageMaxBins(), found);
}
for (const std::pair<V3Number, V3Number>& run : found) {
runs.emplace_back(V3Number{rangep, width, run.first},
V3Number{rangep, width, run.second});
}
}
if (!isSizedArray(binp)) { // The union of the values
std::sort(runs.begin(), runs.end(), [](const auto& lhs, const auto& rhs) {
return crossValueLess(lhs.first, rhs.first);
});
std::vector<std::pair<V3Number, V3Number>> merged;
for (const std::pair<V3Number, V3Number>& run : runs) {
if (merged.empty() || crossValueLess(merged.back().second, run.first)) {
merged.push_back(run);
} else if (crossValueLess(merged.back().second, run.second)) {
merged.back().second = run.second;
}
}
runs = std::move(merged);
}
uint64_t count = 0;
for (const std::pair<V3Number, V3Number>& run : runs) {
V3Number span{exprp, width};
span.opSub(run.second, run.first);
if (span.mostSetBitP1() > 32) return true; // More than 2**32 values
count += span.toUQuad() + 1;
if (count > (uint64_t{1} << 32)) return true;
}
return false;
}
// The ranges of values the wildcard patterns of a sized wildcard array, or of a 'with'
// filter's candidates, give, counted up to more than --coverage-max-bins
static size_t sizedWildcardRuns(const AstCoverBin* binp, AstNodeExpr* exprp) {
std::vector<std::pair<V3Number, V3Number>> runs;
for (AstNode* rangep = binp->rangesp(); rangep; rangep = rangep->nextp()) {
for (AstNode* rangep = binRangesp(binp); rangep; rangep = rangep->nextp()) {
if (!VN_IS(rangep, Const)) continue; // A range, or a value known at construction
CrossValueRange range{rangep, resolveWidth(rangep, exprp)};
if (resolveValue(rangep, exprp, true, true, range) && !crossRangeEmpty(range)) {
@@ -797,8 +920,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|| cbinp->binsType() == VCoverBinsType::BINS_IGNORE
|| cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL)
continue;
// The values of a sized array are known at construction; see emitSizedSample
if (isSizedArray(cbinp)) continue;
// The values of constructed bins are known at construction; see emitSizedSample
if (isConstructedBins(cbinp)) continue;
if (isAutoBins(cbinp)) {
// Automatic bins partition the whole domain, leaving no default value
if (anyBinMatchp) VL_DO_DANGLING(pushDeletep(anyBinMatchp), anyBinMatchp);
@@ -828,9 +951,9 @@ class FunctionalCoverageVisitor final : public VNVisitor {
return false;
}
static bool coverpointHasSizedArrays(const AstCoverpoint* coverpointp) {
static bool coverpointHasConstructedBins(const AstCoverpoint* coverpointp) {
for (const AstNode* nodep = coverpointp->binsp(); nodep; nodep = nodep->nextp()) {
if (isSizedArray(VN_AS(nodep, CoverBin))) return true;
if (isConstructedBins(VN_AS(nodep, CoverBin))) return true;
}
return false;
}
@@ -842,8 +965,9 @@ class FunctionalCoverageVisitor final : public VNVisitor {
for (AstNode* nodep = coverpointp->binsp(); nodep; nodep = nodep->nextp()) {
const AstCoverBin* const binp = VN_AS(nodep, CoverBin);
if (!binp->binsType().binIsNormal() || binp->transp() || !binp->rangesp()) continue;
// A wildcard array has a bin for each value its elements match, so none empty
if (binp->isArray() && binp->isWildcard()) continue;
// A wildcard array has a bin for each value its elements match, so none empty, and
// the constructor creates no bin without values of a 'with' filter
if ((binp->isArray() && binp->isWildcard()) || binWith(binp)) continue;
bool empty = true;
for (AstNode* valuep = binp->rangesp(); valuep; valuep = valuep->nextp()) {
if (binp->isArray() && VN_IS(valuep, InsideRange)) continue;
@@ -1001,6 +1125,17 @@ class FunctionalCoverageVisitor final : public VNVisitor {
bins.push_back({{0, maxVal}});
return exprp->width() <= 64;
}
if (binWith(cbinp)) {
// A value is in one bin of a filter's, but of a sized array, in one for each range
// list element holding it; the coverpoint's name is one element
int elements = 0;
for (const AstNode* rp = binRangesp(cbinp); rp && isSizedArray(cbinp);
rp = rp->nextp()) {
++elements;
}
slotCount += std::max(1, elements);
return false;
}
if (cbinp->isArray() && cbinp->isWildcard() && !cbinp->arraySizep()) {
// A value is in at most one bin of a wildcard array: one slot covering its values.
// Signed values are sign-extended, not unsigned intervals (see computeHitListBound)
@@ -1203,8 +1338,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
arrayBinp->v3warn(COVERIGN,
"Unsupported: array 'bins' of a real coverpoint "
"covering more than "
<< v3Global.opt.coverageMaxRealBins()
<< " values; bin ignored.\n"
<< v3Global.opt.coverageMaxRealBins() << " values; bin "
<< arrayBinp->prettyNameQ() << " ignored.\n"
<< arrayBinp->warnMore()
<< "... Suggest a larger --coverage-max-real-bins");
unsupportedOut = true;
@@ -1285,7 +1420,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
arrayBinp->v3warn(COVERIGN, "Unsupported: array 'bins' covering more than "
<< v3Global.opt.coverageMaxBins()
<< " values (e.g. an open '[lo:$]' range over "
"a wide coverpoint); bin ignored\n"
"a wide coverpoint); bin "
<< arrayBinp->prettyNameQ() << " ignored\n"
<< arrayBinp->warnMore()
<< "... Suggest a larger --coverage-max-bins");
out.runs.clear();
@@ -1361,15 +1497,15 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// An ignore or illegal array still excludes or checks its values, as one bin
out.single = !arrayBinp->binsType().binIsNormal();
if (report) {
arrayBinp->v3warn(COVERIGN,
"Unsupported: wildcard array '"
<< arrayBinp->binsType().verilogKwd()
<< "' of more than --coverage-max-bins of "
<< v3Global.opt.coverageMaxBins() << " values; "
<< (out.single ? "treated as one bin" : "bin ignored")
<< "\n"
<< arrayBinp->warnMore()
<< "... Suggest a larger --coverage-max-bins");
arrayBinp->v3warn(
COVERIGN, "Unsupported: wildcard array '"
<< arrayBinp->binsType().verilogKwd()
<< "' of more than --coverage-max-bins of "
<< v3Global.opt.coverageMaxBins() << " values; bin "
<< arrayBinp->prettyNameQ()
<< (out.single ? " treated as one bin" : " ignored") << "\n"
<< arrayBinp->warnMore()
<< "... Suggest a larger --coverage-max-bins");
}
out.unsupported = true;
return out;
@@ -1539,16 +1675,31 @@ class FunctionalCoverageVisitor final : public VNVisitor {
void emitSizedSample(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp,
AstNodeExpr* exprp, uint32_t sized, AstVar* matchedp) {
FileLine* const fl = binp->fileline();
AstNodeExpr* const enabledp
AstNodeExpr* enabledp
= binCondition(coverpointp, binp, cpVarp, new AstConst{fl, AstConst::BitTrue{}});
UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set for coverpoint");
const bool illegal = binp->binsType() == VCoverBinsType::BINS_ILLEGAL;
if (illegal) {
// The illegal action reads the condition too, which is evaluated once, as the guard
// may have side effects
AstVar* const varp
= new AstVar{fl, VVarType::BLOCKTEMP,
"__VcpEnabled_" + sanitizeGeneratedName(coverpointp->name()) + "_"
+ cvtToStr(sized),
binp->findBitDType()};
varp->funcLocal(true);
m_sampleFuncp->addStmtsp(varp);
m_sampleFuncp->addStmtsp(
new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, enabledp});
enabledp = new AstVarRef{fl, varp, VAccess::READ};
}
AstCMethodHard* const callp
= itemCall(fl, cpVarp,
exprp->isWide() ? VCMethod::COVERGROUP_SIZED_SAMPLE_W
: VCMethod::COVERGROUP_SIZED_SAMPLE,
{cnum(fl, sized), exprp->cloneTree(false), enabledp});
callp->dtypeSetBit();
UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set for coverpoint");
if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
if (illegal) {
m_sampleFuncp->addStmtsp(
new AstIf{fl, new AstLogAnd{fl, callp, enabledp->cloneTree(false)},
makeIllegalBinAction(fl, "Illegal bin " + binp->prettyNameQ()
@@ -1585,61 +1736,223 @@ class FunctionalCoverageVisitor final : public VNVisitor {
return valuep;
}
// Emit the constructor code building the sized array of bins 'binp': the values of each
// element that are coverpoint values (IEEE 1800-2023 19.5.7), then its bins
void generateSizedArray(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp,
AstNodeExpr* exprp) {
// The values of a coverpoint, which its name denotes (IEEE 1800-2023 19.5.1.1), as runs in
// value order: an enumerated type's values (6.19), else all values of its type. Bounds
// are at runWidth(), sign-extended like a CrossValueRange's.
static std::vector<std::pair<V3Number, V3Number>> coverpointValues(AstNode* nodep,
AstNodeExpr* exprp) {
const int width = runWidth(exprp);
std::vector<std::pair<V3Number, V3Number>> runs;
const AstEnumDType* const enump = VN_CAST(exprp->dtypep()->skipRefToEnump(), EnumDType);
if (!enump) {
const CrossValueRange domain
= crossValueDomain(nodep, exprp->width(), exprp->isSigned(), width);
runs.emplace_back(domain.lo, domain.hi);
return runs;
}
std::vector<V3Number> values;
for (const AstEnumItem* itemp = enump->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), EnumItem)) {
const V3Number& num = VN_AS(itemp->valuep(), Const)->num();
if (num.isFourState()) continue; // Not a coverpoint value (19.5.7)
values.emplace_back(nodep, width);
if (exprp->isSigned()) {
values.back().opExtendS(num, num.width());
} else {
values.back().opAssign(num);
}
}
std::sort(values.begin(), values.end(), crossValueLess);
const V3Number one{nodep, width, 1};
for (const V3Number& value : values) {
V3Number next{nodep, width};
if (!runs.empty() && next.opAdd(runs.back().second, one).isCaseEq(value)) {
runs.back().second = value;
} else {
runs.emplace_back(value, value);
}
}
return runs;
}
// Emit the constructor code building the bins 'binp' whose values it computes: the values of
// each element that are coverpoint values (IEEE 1800-2023 19.5.7), those a 'with' filter
// keeps (19.5.1.1), then its bins
void generateConstructedBins(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp,
AstNodeExpr* exprp) {
FileLine* const fl = binp->fileline();
const string prefix
= "__Vsized_" + sanitizeGeneratedName(coverpointp->name() + "__" + binp->name());
AstNodeExpr* const sizep = binp->arraySizep();
AstVar* const countp = constructorTemp(fl, prefix + "_count", sizep->dtypep());
m_constructorp->addStmtsp(
new AstAssign{fl, new AstVarRef{fl, countp, VAccess::WRITE}, sizep->cloneTree(false)});
= m_sizedNames.get(sanitizeGeneratedName(coverpointp->name() + "__" + binp->name()));
AstVar* countp = nullptr;
if (AstNodeExpr* const sizep = binp->arraySizep()) {
countp = constructorTemp(fl, prefix + "_count", sizep->dtypep());
m_constructorp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, countp, VAccess::WRITE},
sizep->cloneTree(false)});
}
AstCoverWith* const withp = binWith(binp);
if (withp && !binRangesp(binp)) { // The coverpoint's name: all of its values
for (const std::pair<V3Number, V3Number>& run : coverpointValues(binp, exprp)) {
m_constructorp->addStmtsp(itemCall(fl, cpVarp,
exprp->isWide()
? VCMethod::COVERGROUP_SIZED_RANGE_W
: VCMethod::COVERGROUP_SIZED_RANGE,
{newValueConst(fl, run.first, exprp),
newValueConst(fl, run.second, exprp)})
->makeStmt());
}
}
uint32_t element = 0;
for (AstNode* rangep = binp->rangesp(); rangep; rangep = rangep->nextp()) {
for (AstNode* rangep = binRangesp(binp); rangep; rangep = rangep->nextp()) {
if (VN_IS(rangep, Unbounded)) { // A parameter of '$'
binp->v3error("Bins value may not be '$', which may only bound a range "
"(IEEE 1800-2023 6.20.7)");
continue;
}
generateSizedElement(cpVarp, rangep, exprp, binp->isWildcard(),
prefix + "_" + cvtToStr(element++));
generateSizedElement(cpVarp, binp, rangep, exprp, prefix + "_" + cvtToStr(element++));
}
const auto countRef = [&]() { return new AstVarRef{fl, countp, VAccess::READ}; };
AstConst* const zerop = new AstConst{fl, AstConst::DTyped{}, countp->dtypep()};
AstNodeExpr* const positivep
= countp->isSigned() ? static_cast<AstNodeExpr*>(new AstGtS{fl, countRef(), zerop})
: new AstNeq{fl, countRef(), zerop};
// Saturate a count wider than 64 bits: min(N, T) is unchanged, or over any bins limit
AstNodeExpr* countValuep = resizeValue(countRef(), countp->findUInt64DType());
if (countp->width() > VL_QUADSIZE) {
countValuep = new AstCond{
fl,
new AstRedOr{
fl, new AstSel{fl, countRef(), VL_QUADSIZE, countp->width() - VL_QUADSIZE}},
new AstConst{fl, AstConst::Unsized64{}, std::numeric_limits<uint64_t>::max()},
countValuep};
countValuep->dtypeSetUInt64();
if (withp) generateWithFilter(binp, withp, cpVarp, exprp, prefix);
AstNodeExpr* countValuep;
AstNodeExpr* positivep;
if (countp) {
const auto countRef = [&]() { return new AstVarRef{fl, countp, VAccess::READ}; };
AstConst* const zerop = new AstConst{fl, AstConst::DTyped{}, countp->dtypep()};
positivep = countp->isSigned()
? static_cast<AstNodeExpr*>(new AstGtS{fl, countRef(), zerop})
: new AstNeq{fl, countRef(), zerop};
// Saturate a count wider than 64 bits: min(N, T) is unchanged, or over any limit
countValuep = resizeValue(countRef(), countp->findUInt64DType());
if (countp->width() > VL_QUADSIZE) {
countValuep = new AstCond{
fl,
new AstRedOr{fl, new AstSel{fl, countRef(), VL_QUADSIZE,
countp->width() - VL_QUADSIZE}},
new AstConst{fl, AstConst::Unsized64{}, std::numeric_limits<uint64_t>::max()},
countValuep};
countValuep->dtypeSetUInt64();
}
} else { // Of a filter's scalar bin, or bin per value
countValuep = new AstConst{fl, AstConst::Unsized64{}, 1};
positivep = new AstConst{fl, AstConst::BitTrue{}};
}
std::vector<AstNodeExpr*> args{ctext(fl, binp->binsType().binSetEnum()), countValuep,
positivep};
// A filter's bins have the limit it began with
if (!withp) args.push_back(cnum(fl, v3Global.opt.coverageMaxBins()));
const bool prot = v3Global.opt.protectIds();
args.push_back(ctext(fl, quoted(VIdProtect::protectWordsIf(binp->name(), prot))));
args.push_back(ctext(fl, quoted(VIdProtect::protectIf(fl->filename(), prot))));
args.push_back(cnum(fl, static_cast<uint32_t>(fl->lineno())));
args.push_back(cnum(fl, static_cast<uint32_t>(fl->firstColumn())));
m_constructorp->addStmtsp(
itemCall(fl, cpVarp, VCMethod::COVERGROUP_SIZED_FINISH,
{ctext(fl, binp->binsType().binSetEnum()), countValuep, positivep,
cnum(fl, v3Global.opt.coverageMaxBins()),
ctext(fl, quoted(VIdProtect::protectWordsIf(binp->name(), prot))),
ctext(fl, quoted(VIdProtect::protectIf(fl->filename(), prot))),
cnum(fl, static_cast<uint32_t>(fl->lineno())),
cnum(fl, static_cast<uint32_t>(fl->firstColumn()))})
itemCall(fl, cpVarp,
withp ? VCMethod::COVERGROUP_WITH_FINISH : VCMethod::COVERGROUP_SIZED_FINISH,
args)
->makeStmt());
}
// Emit 'sizedRange(lo, hi)' for the coverpoint values of an element of a sized array of
// bins: resolved now if constant, else when constructed by clipping to the coverpoint's
// values. A 'wildcard' pattern's values give a range for each run of them. 'prefix' names
// its temporaries.
void generateSizedElement(AstVar* cpVarp, AstNode* rangep, AstNodeExpr* exprp, bool wildcard,
const string& prefix) {
// Emit the constructor code evaluating the 'with' filter of 'binp' for each of its candidate
// values, which sizedRange() added, passing the runs of values it keeps (IEEE 1800-2023
// 19.5.1.1). The filter is evaluated in a loop, whose code does not grow with the elements:
// withBegin(grouping, limit);
// more = 1;
// while (withNext()) {
// value = withLo(); last = withHi(); run = 0;
// while (true) {
// item = value;
// if (filter) { if (!run) { first = value; run = 1; } }
// else if (run) { more = withRun(first, value - 1); run = 0; }
// if (!more || value == last) break;
// ++value;
// }
// if (run) more = withRun(first, last);
// }
void generateWithFilter(AstCoverBin* binp, AstCoverWith* withp, AstVar* cpVarp,
AstNodeExpr* exprp, const string& prefix) {
FileLine* const fl = withp->fileline();
const bool wide = exprp->isWide();
const string grouping = !binp->isArray() ? "Single"
: binp->arraySizep() ? "Fixed"
: "Values";
m_constructorp->addStmtsp(itemCall(fl, cpVarp, VCMethod::COVERGROUP_WITH_BEGIN,
{ctext(fl, "VlCovBinGrouping::" + grouping),
cnum(fl, v3Global.opt.coverageMaxBins())})
->makeStmt());
// The candidates count in the coverpoint's width, and the filter reads each as 'item',
// of the coverpoint's type, so that a filter changing 'item' cannot change the loop
AstNodeDType* const valueDTypep
= exprp->findLogicDType(exprp->width(), exprp->width(),
exprp->isSigned() ? VSigning::SIGNED : VSigning::UNSIGNED);
AstVar* const valuep = constructorTemp(fl, prefix + "_value", valueDTypep);
AstVar* const lastp = constructorTemp(fl, prefix + "_last", valueDTypep);
AstVar* const firstp = constructorTemp(fl, prefix + "_first", valueDTypep);
AstVar* const runp = constructorTemp(fl, prefix + "_run", binp->findBitDType());
AstVar* const morep = constructorTemp(fl, prefix + "_more", binp->findBitDType());
AstVar* const itemp = withp->itemp()->unlinkFrBack();
itemp->name(prefix + "_item");
m_constructorp->addStmtsp(itemp);
const auto ref = [&](AstVar* varp) { return new AstVarRef{fl, varp, VAccess::READ}; };
const auto assign = [&](AstVar* varp, AstNodeExpr* rhsp) -> AstNode* {
return new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, rhsp};
};
const auto flag = [&](AstVar* varp, bool value) {
return assign(varp, value ? new AstConst{fl, AstConst::BitTrue{}}
: new AstConst{fl, AstConst::BitFalse{}});
};
const auto bound = [&](VCMethod narrow, VCMethod wideMethod, AstVar* varp) -> AstNode* {
if (wide) {
return itemCall(fl, cpVarp, wideMethod, {new AstVarRef{fl, varp, VAccess::WRITE}})
->makeStmt();
}
AstCMethodHard* const callp = itemCall(fl, cpVarp, narrow);
callp->dtypeSetUInt64();
return assign(varp, resizeValue(callp, valueDTypep));
};
const auto keep = [&](AstNodeExpr* lop, AstNodeExpr* hip) {
AstCMethodHard* const callp = itemCall(
fl, cpVarp, wide ? VCMethod::COVERGROUP_WITH_RUN_W : VCMethod::COVERGROUP_WITH_RUN,
{lop, hip});
callp->dtypeSetBit();
return assign(morep, callp);
};
const auto step = [&](bool up) {
AstConst* const onep = new AstConst{fl, AstConst::WidthedValue{}, exprp->width(), 1};
AstNodeExpr* const stepp
= up ? static_cast<AstNodeExpr*>(new AstAdd{fl, ref(valuep), onep})
: new AstSub{fl, ref(valuep), onep};
stepp->dtypep(valueDTypep);
return stepp;
};
AstLoop* const innerp = new AstLoop{fl};
innerp->addStmtsp(assign(itemp, ref(valuep)));
innerp->addStmtsp(new AstIf{
fl, withp->filterp()->unlinkFrBack(),
new AstIf{fl, new AstNot{fl, ref(runp)},
assign(firstp, ref(valuep))->addNext(flag(runp, true))},
new AstIf{fl, ref(runp), keep(ref(firstp), step(false))->addNext(flag(runp, false))}});
innerp->addStmtsp(new AstLoopTest{
fl, innerp, new AstLogAnd{fl, ref(morep), new AstNeq{fl, ref(valuep), ref(lastp)}}});
innerp->addStmtsp(assign(valuep, step(true)));
AstLoop* const outerp = new AstLoop{fl};
AstCMethodHard* const nextp = itemCall(fl, cpVarp, VCMethod::COVERGROUP_WITH_NEXT);
nextp->dtypeSetBit();
outerp->addStmtsp(new AstLoopTest{fl, outerp, nextp});
outerp->addStmtsp(
bound(VCMethod::COVERGROUP_WITH_LO, VCMethod::COVERGROUP_WITH_LO_W, valuep));
outerp->addStmtsp(
bound(VCMethod::COVERGROUP_WITH_HI, VCMethod::COVERGROUP_WITH_HI_W, lastp));
outerp->addStmtsp(flag(runp, false));
outerp->addStmtsp(innerp);
outerp->addStmtsp(new AstIf{fl, ref(runp), keep(ref(firstp), ref(lastp))});
m_constructorp->addStmtsp(flag(morep, true));
m_constructorp->addStmtsp(outerp);
}
// Emit 'sizedRange(lo, hi)' for the coverpoint values of an element of bins 'binp', whose
// values the constructor computes: resolved now if constant, else when constructed by
// clipping to the coverpoint's values. A wildcard pattern's values give a range for each
// run of them. 'prefix' names its temporaries.
void generateSizedElement(AstVar* cpVarp, const AstCoverBin* binp, AstNode* rangep,
AstNodeExpr* exprp, const string& prefix) {
FileLine* const fl = rangep->fileline();
const VCMethod method = exprp->isWide() ? VCMethod::COVERGROUP_SIZED_RANGE_W
: VCMethod::COVERGROUP_SIZED_RANGE;
@@ -1659,17 +1972,18 @@ class FunctionalCoverageVisitor final : public VNVisitor {
return constp && constp->num().isFourState();
};
if (irp && (fourState(lowp) || fourState(highp))) {
rangep->v3error("Four-state (x/z) value in array bins range bound; range bounds "
"must be two-state constants");
rangep->v3error("Four-state (x/z) value in "
<< (binp->isArray() ? "array bins" : "bin")
<< " range bound; range bounds must be two-state constants");
return;
}
CrossValueRange range{rangep, resolveWidth(rangep, exprp)};
if (!resolveValue(rangep, exprp, true, wildcard, range)) {
if (!resolveValue(rangep, exprp, true, binp->isWildcard(), range)) {
rangep->v3warn(E_UNSUPPORTED, "Unsupported: non-integral value in a coverage bin "
"of an integral coverpoint.");
} else if (!crossRangeEmpty(range)) {
std::vector<std::pair<V3Number, V3Number>> runs{{range.lo, range.hi}};
if (range.wildcard) { // checkSizedArrays bounded the runs
if (range.wildcard) { // checkConstructedBins bounded the runs
runs.clear();
crossRangeRuns(range, v3Global.opt.coverageMaxBins(), runs);
}
@@ -1864,8 +2178,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
}
// Walk bins (non-default, then default), assigning sequential indices that match the
// namer append order; emit sample increments and collect namer statements. A sized
// array's bins follow them all, placed when the coverpoint is constructed.
// namer append order; emit sample increments and collect namer statements. Constructed
// bins follow them all, placed when the coverpoint is constructed.
std::vector<AstNodeStmt*> namerStmts;
std::vector<AstCoverBin*> defaultBins;
std::vector<AstCoverBin*> sizedBins;
@@ -1879,8 +2193,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
defaultBins.push_back(cbinp);
continue;
}
if (isSizedArray(cbinp)) {
UASSERT_OBJ(dynamic, cbinp, "Sized bin array without value metadata");
if (isConstructedBins(cbinp)) {
UASSERT_OBJ(dynamic, cbinp, "Constructed bins without value metadata");
BinSpan span;
span.sized = static_cast<int32_t>(sizedBins.size());
m_cpBins.at(cpVarp).spans.emplace(cbinp->name(), span);
@@ -1910,7 +2224,10 @@ class FunctionalCoverageVisitor final : public VNVisitor {
if (binRunsFor(cbinp, exprp, plan)) {
// Array elements and automatic bins generate as runs, so neither sample() nor
// the constructor grows with their number of bins.
if (plan.unsupported) continue; // bin ignored or invalid; reserve no slot
if (plan.unsupported) { // bin ignored or invalid; reserve no slot
m_droppedBins[coverpointp].push_back(cbinp->name());
continue;
}
CoverpointBins& bins = m_cpBins.at(cpVarp);
const uint32_t firstValue = bins.total;
const uint32_t firstDeclared = static_cast<uint32_t>(idx);
@@ -1942,7 +2259,10 @@ class FunctionalCoverageVisitor final : public VNVisitor {
UASSERT_OBJ(!dynamic, cbinp, "Runtime value metadata for a real coverpoint");
bool unsupported = false;
std::vector<AstNodeExpr*> values = extractArrayValues(cbinp, exprp, unsupported);
if (unsupported) continue; // bin ignored (COVERIGN emitted); reserve no slot
if (unsupported) { // bin ignored (COVERIGN emitted); reserve no slot
m_droppedBins[coverpointp].push_back(cbinp->name());
continue;
}
namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast<int64_t>(values.size()),
static_cast<uint32_t>(idx), values));
for (AstNodeExpr* valuep : values) {
@@ -1966,7 +2286,11 @@ class FunctionalCoverageVisitor final : public VNVisitor {
++idx;
}
}
// A value a sized array's bin holds is no default bin's; only sampling tells which do
// A cross selecting an ignored bins declaration selects no bins
for (const std::string& name : m_droppedBins[coverpointp]) {
m_cpBins.at(cpVarp).spans.emplace(name, BinSpan{});
}
// A value a constructed bin holds is no default bin's; only sampling tells which do
AstVar* sizedMatchedp = nullptr;
if (!defaultBins.empty()
&& std::any_of(sizedBins.begin(), sizedBins.end(), [](const AstCoverBin* binp) {
@@ -2043,7 +2367,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
emitValueList(fl, cpVarp, VCMethod::COVERGROUP_VALUE_PATTERNS, lists.m_patterns);
emitValueList(fl, cpVarp, VCMethod::COVERGROUP_VALUE_TRANSITIONS, lists.m_transitions);
for (AstCoverBin* const binp : sizedBins) {
generateSizedArray(coverpointp, binp, cpVarp, exprp);
generateConstructedBins(coverpointp, binp, cpVarp, exprp);
}
m_constructorp->addStmtsp(
itemCall(fl, cpVarp, VCMethod::COVERGROUP_VALUE_FINALIZE)->makeStmt());