Support logic and set operations on binsof (#8306)

This commit is contained in:
Marco Bartoli
2026-09-13 12:59:10 -04:00
committed by GitHub
parent 298922d887
commit 47ead4e89d
26 changed files with 2455 additions and 312 deletions
+621 -52
View File
@@ -30,7 +30,10 @@
#include "V3File.h"
#include "V3MemberMap.h"
#include <array>
#include <bitset>
#include <set>
#include <tuple>
#include <unordered_map>
#include <vector>
@@ -75,6 +78,7 @@ class CovergroupExprValidVisitor final : public VNVisitor {
iterateAndNextNull(nodep->selectp());
scanSampleExpression(nodep->iffp());
}
void visit(AstCoverBinsof* nodep) override { scanCoverageExpression(nodep->rangesp()); }
void visit(AstCoverBin* nodep) override {
scanCoverageExpression(nodep->rangesp());
scanSampleExpression(nodep->iffp());
@@ -162,8 +166,15 @@ class FunctionalCoverageVisitor final : public VNVisitor {
std::vector<AstVar*> m_cpVars; // VlCoverpoint member, one per coverpoint
std::vector<AstVar*> m_crossVars; // VlCoverCross member, one per cross
std::map<std::string, AstVar*> m_cpVarMap; // Coverpoint name -> its VlCoverpoint member
struct CrossBinValues final {
AstCoverBin* binp; // Declaration owning this Normal bin
AstNodeExpr* valuep; // Individual array-bin value, or nullptr for a scalar bin
};
struct CoverpointBins final {
uint32_t total = 0; // Number of Normal bins
AstNodeExpr* exprp = nullptr; // Sampled expression, for the value domain
std::vector<CrossBinValues> values; // Values in runtime Normal-bin index order
std::vector<AstCoverBin*> excluded; // State ignore/illegal bins removing values
std::unordered_map<std::string, std::pair<uint32_t, uint32_t>>
spans; // Declared bin name -> first Normal index and number of bins
};
@@ -171,6 +182,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
std::set<AstCoverCross*>
m_droppedCrosses; // Crosses with a bare-variable item: drop (COVERIGN)
std::map<uint32_t, AstCoverpointDType*> m_cpDTypes; // Hit-list bound -> interned dtype
using CrossShape = std::tuple<uint32_t, uint32_t, uint32_t, uint32_t, uint64_t>;
std::map<CrossShape, AstCoverCrossDType*> m_cxDTypes;
AstVar* m_cgInstVarp = nullptr; // __Vcg_inst handle member of the current covergroup
VMemberMap m_memberMap; // Member names cached for fast lookup
@@ -745,6 +758,10 @@ class FunctionalCoverageVisitor final : public VNVisitor {
for (AstNode* rp = cbinp->rangesp(); rp; rp = rp->nextp()) {
RangeBounds rb;
if (!constRangeBounds(rp, rb)) return false;
if ((rb.loConstp() && rb.loConstp()->width() > 64)
|| (rb.hiConstp() && rb.hiConstp()->width() > 64)) {
return false; // Use the safe slot-count bound for wide values.
}
const uint64_t lo = rb.loUnbounded() ? 0 : rb.loConstp()->toUQuad();
const uint64_t hi = rb.hiUnbounded() ? maxVal : rb.hiConstp()->toUQuad();
if (lo > hi) return false;
@@ -806,7 +823,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// One entry per Normal bin (cross slot): its covered intervals.
std::vector<std::vector<std::pair<uint64_t, uint64_t>>> bins;
int slotCount = 0; // == runtime m_normal; the safe fallback bound
bool exact = true;
// Unsigned intervals cannot establish overlap between differently sized signed values.
bool exact = !exprp->isSigned();
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
if (!cbinp->binsType().binIsNormal())
@@ -862,7 +880,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// A literal C++ argument with no AST equivalent: a 'const char*' string literal (an SV
// string AstConst emits '"..."s', a std::string temporary the runtime cannot borrow), a
// VlCovBinKind enum token, or a '__V' temporary declared by the enclosing AstCStmt.
// VlCovBinKind enum token, a constant selection-word initializer list, or a '__V' temporary
// declared by the enclosing AstCStmt.
static AstCExpr* ctext(FileLine* fl, const std::string& text) {
return new AstCExpr{fl, text};
}
@@ -934,13 +953,22 @@ class FunctionalCoverageVisitor final : public VNVisitor {
}
// Emit a 'this->m_cp->addSingleNamer/addArrayNamer(...)' statement for one bin
AstNodeStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count) {
AstNodeStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count,
const std::vector<AstNodeExpr*>& values = {}) {
FileLine* const fl = binp->fileline();
CoverpointBins& bins = m_cpBins.at(cpVarp);
const uint32_t normalCount
= binp->binsType().binIsNormal() ? static_cast<uint32_t>(count < 0 ? 1 : count) : 0;
bins.spans.emplace(binp->name(), std::make_pair(bins.total, normalCount));
bins.total += normalCount;
for (uint32_t i = 0; i < normalCount; ++i) {
bins.values.push_back({binp, values.empty() ? nullptr : values[i]});
}
if (!binp->transp()
&& (binp->binsType() == VCoverBinsType::BINS_IGNORE
|| binp->binsType() == VCoverBinsType::BINS_ILLEGAL)) {
bins.excluded.push_back(binp);
}
// Under --protect-ids the filename and bin name flow into the coverage database
// verbatim, so obfuscate them exactly as line/toggle coverage points are (whole-
// unit filename, per-word bin name). A no-op when --protect-ids is off.
@@ -1030,6 +1058,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
m_cpVars.push_back(cpVarp);
m_cpVarMap[coverpointp->name()] = cpVarp;
m_cpBins.emplace(cpVarp, CoverpointBins{});
m_cpBins.at(cpVarp).exprp = exprp;
// Create the runtime in the instance node first; everything below configures it.
m_constructorp->addStmtsp(makeItemCreate(fl, cpVarp, VCMethod::COVERGROUP_ADD_COVERPOINT));
@@ -1071,7 +1100,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
bool unsupported = false;
std::vector<AstNodeExpr*> values = extractArrayValues(cbinp, exprp, unsupported);
if (unsupported) continue; // bin ignored (COVERIGN emitted); reserve no slot
namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast<int>(values.size())));
namerStmts.push_back(
makeNamer(cpVarp, cbinp, static_cast<int>(values.size()), values));
for (AstNodeExpr* valuep : values) {
// TODO: A 4-state bin value (e.g. bins b[] = {2'b0x}) must match with ===
// (AstEqCase) per IEEE 1800-2023 19.5.4. == is equivalent under 2-state sim
@@ -1426,30 +1456,542 @@ class FunctionalCoverageVisitor final : public VNVisitor {
return cs;
}
// Append a "{ const bool __Vcx_iffs[] = {<iff>, ...}; <call> }" statement, one entry per
// explicit cross bin in declaration order (true where the bin has no iff). As above, the
// temporary array is literal text because a CMethodHard is one call, not a block.
// Assign the per-bin flags individually: one-bit SV results have integer C++ storage types,
// which may narrow in a bool initializer list but convert implicitly in assignments.
AstCStmt* makeCrossIffsCall(FileLine* fl, const std::vector<AstCoverCrossBin*>& bins,
AstCMethodHard* callp) {
AstCStmt* const cs = new AstCStmt{fl};
cs->add("{ const bool __Vcx_iffs[] = {");
bool first = true;
for (const AstCoverCrossBin* const binp : bins) {
if (!first) cs->add(", ");
first = false;
cs->add("{ bool __Vcx_iffs[" + cvtToStr(bins.size()) + "]; ");
for (size_t i = 0; i < bins.size(); ++i) {
const AstCoverCrossBin* const binp = bins[i];
cs->add("__Vcx_iffs[" + cvtToStr(i) + "] = ");
cs->add(binp->iffp() ? binp->iffp()->cloneTree(false)
: new AstConst{fl, AstConst::BitTrue{}});
cs->add("; ");
}
cs->add("}; ");
cs->add(callp);
cs->add("; }");
return cs;
}
std::vector<AstCoverCrossBin*>
generateCrossBins(AstCoverCross* crossp, AstVar* cxVarp, const std::vector<AstVar*>& cpVars,
const std::map<std::string, uint32_t>& dimensions) {
std::vector<AstCoverCrossBin*> bins;
using CrossSelection = std::vector<uint64_t>;
struct ResolvedCrossBin final {
AstCoverCrossBin* binp;
CrossSelection selection;
};
struct CrossLayout final {
uint32_t tuples = 0;
uint32_t autoBins = 0;
uint64_t binWords = 0;
bool valid = true;
std::vector<ResolvedCrossBin> bins;
};
struct CrossSelectionContext final {
AstCoverCross* crossp; // Cross whose tuple space is being selected
const std::vector<AstVar*>& cpVars; // Feeding coverpoints in dimension order
const std::map<std::string, uint32_t>& dimensions; // Coverpoint name -> dimension
uint32_t tuples; // Size of the Cartesian product
std::vector<uint32_t> strides; // Flat-index stride per dimension
bool valid = true; // False if this explicit bin cannot be implemented
};
struct CrossValueRange final {
V3Number lo; // Inclusive lower bound, sign-extended to the comparison width
V3Number hi; // Inclusive upper bound
V3Number pattern; // Allowed bit values; all X for an ordinary interval
bool singleton = false; // A single value, possibly a wildcard pattern
bool wildcard = false; // A wildcard singleton rather than an exact four-state value
CrossValueRange(AstNode* nodep, int width)
: lo{nodep, width}
, hi{nodep, width}
, pattern{nodep, width} {
pattern.setAllBitsX();
}
};
static std::vector<AstNode*> crossBinValues(const CrossBinValues& bin) {
if (bin.valuep) return {bin.valuep};
std::vector<AstNode*> values;
if (bin.binp->transp()) {
// IEEE 1800-2023 19.6.1: binsof uses the last value of each transition.
for (AstNode* setp = bin.binp->transp(); setp; setp = setp->nextp()) {
AstCoverTransItem* lastp = VN_AS(setp, CoverTransSet)->itemsp();
while (lastp->nextp()) lastp = VN_AS(lastp->nextp(), CoverTransItem);
for (AstNode* valuep = lastp->valuesp(); valuep; valuep = valuep->nextp()) {
values.push_back(valuep);
}
}
} else {
for (AstNode* valuep = bin.binp->rangesp(); valuep; valuep = valuep->nextp()) {
values.push_back(valuep);
}
}
return values;
}
static int crossRangeWidth(AstNode* nodep) {
if (const AstInsideRange* const rangep = VN_CAST(nodep, InsideRange)) {
return std::max(rangep->lhsp()->width(), rangep->rhsp()->width());
}
return nodep->width();
}
static bool crossValueLess(const V3Number& lhs, const V3Number& rhs) {
V3Number result{&lhs};
return !result.opLtS(lhs, rhs).isEqZero();
}
static bool crossRangeBound(AstNode* nodep, AstNodeExpr* exprp, bool upper, bool binValue,
V3Number& result) {
if (VN_IS(nodep, Unbounded)) {
V3Number limit{nodep, exprp->width()};
if (upper) limit.setAllBits1();
if (exprp->isSigned()) {
limit.setBit(exprp->width() - 1, !upper);
result.opExtendS(limit, limit.width());
} else {
result.opAssign(limit);
}
return true;
}
const AstConst* const constp = VN_CAST(nodep, Const);
if (!constp || constp->num().isOpaque()) return false;
if (binValue && exprp->isSigned() && constp->width() <= exprp->width()) {
// Bin bit patterns use the coverpoint's effective type (IEEE 1800-2023 19.5.7).
// Wider values and intersect filters retain their values for domain clipping.
V3Number value{nodep, exprp->width()};
if (constp->isSigned()) {
value.opExtendS(constp->num(), constp->width());
} else {
value.opAssign(constp->num());
}
result.opExtendS(value, value.width());
} else if (constp->isSigned()) {
result.opExtendS(constp->num(), constp->width());
} else {
result.opAssign(constp->num());
}
return true;
}
static CrossValueRange crossValueDomain(AstNode* nodep, int valueWidth, bool isSigned,
int width) {
CrossValueRange domain{nodep, width};
V3Number lo{nodep, valueWidth};
V3Number hi{nodep, valueWidth};
hi.setAllBits1();
if (isSigned) {
lo.setBit(valueWidth - 1, 1);
hi.setBit(valueWidth - 1, 0);
domain.lo.opExtendS(lo, valueWidth);
domain.hi.opExtendS(hi, valueWidth);
} else {
domain.lo.opAssign(lo);
domain.hi.opAssign(hi);
}
return domain;
}
static void intersectCrossRange(CrossValueRange& range, const CrossValueRange& other) {
if (crossValueLess(range.lo, other.lo)) range.lo = other.lo;
if (crossValueLess(other.hi, range.hi)) range.hi = other.hi;
}
static bool crossValueRange(AstNode* nodep, AstNodeExpr* exprp, bool binValue, bool wildcard,
const CrossValueRange& domain, CrossValueRange& range) {
if (const AstInsideRange* const rangep = VN_CAST(nodep, InsideRange)) {
if (!crossRangeBound(rangep->lhsp(), exprp, false, binValue, range.lo)
|| !crossRangeBound(rangep->rhsp(), exprp, true, binValue, range.hi)
|| range.lo.isFourState() || range.hi.isFourState()) {
return false;
}
} else {
range.singleton = true;
if (!crossRangeBound(nodep, exprp, false, binValue, range.lo)) return false;
range.hi = range.lo;
range.wildcard = wildcard;
if (wildcard) {
range.pattern = range.lo;
range.lo = domain.lo;
range.hi = domain.hi;
if (const AstConst* const constp = VN_CAST(nodep, Const)) {
if (constp->isSigned()) {
// Replicated X sign bits are correlated, not independent wildcards.
// The source domain preserves expansion-before-casting (19.5.7).
intersectCrossRange(range, crossValueDomain(nodep, constp->width(), true,
domain.lo.width()));
}
}
}
}
if (!range.lo.isFourState()) intersectCrossRange(range, domain);
return true;
}
enum class CrossMatchResult : uint8_t { MATCH, NO_MATCH, WORK_LIMIT };
enum CrossRangeState : uint8_t {
CROSS_INSIDE_BOUNDS = 0, // Prefix is strictly inside the interval
CROSS_AT_LOWER = 1,
CROSS_AT_UPPER = 2,
CROSS_AT_BOUNDS = CROSS_AT_LOWER | CROSS_AT_UPPER,
CROSS_NO_MATCH = 4 // Prefix cannot match the interval/pattern
};
static constexpr size_t CROSS_MATCH_LINEAR_ALLOWANCE = 4; // Minimum linear traversals
static constexpr size_t CROSS_MATCH_WORK_LIMIT = 1U << 20; // Base bit-step budget per search
static CrossRangeState crossRangeStep(const CrossValueRange& range, CrossRangeState state,
int bit, int value) {
if (state == CROSS_NO_MATCH) return CROSS_NO_MATCH;
// Flipping the sign bit makes signed order lexicographic.
const bool sign = bit == range.pattern.width() - 1;
if (!range.pattern.bitIsXZ(bit) && value != (range.pattern.bitIs1(bit) ^ sign)) {
return CROSS_NO_MATCH;
}
const int low = range.lo.bitIs1(bit) ^ sign;
const int high = range.hi.bitIs1(bit) ^ sign;
if (((state & CROSS_AT_LOWER) && value < low)
|| ((state & CROSS_AT_UPPER) && value > high)) {
return CROSS_NO_MATCH;
}
return static_cast<CrossRangeState>(
((state & CROSS_AT_LOWER) && value == low ? CROSS_AT_LOWER : CROSS_INSIDE_BOUNDS)
| ((state & CROSS_AT_UPPER) && value == high ? CROSS_AT_UPPER : CROSS_INSIDE_BOUNDS));
}
static bool crossWildcardIntersects(const CrossValueRange& range) {
unsigned states = 1U << CROSS_AT_BOUNDS;
for (int bit = range.pattern.width() - 1; bit >= 0 && states; --bit) {
unsigned next = 0;
for (const CrossRangeState state :
{CROSS_INSIDE_BOUNDS, CROSS_AT_LOWER, CROSS_AT_UPPER, CROSS_AT_BOUNDS}) {
if (!(states & (1U << state))) continue;
for (int value = 0; value < 2; ++value) {
const CrossRangeState equal = crossRangeStep(range, state, bit, value);
if (equal != CROSS_NO_MATCH) next |= 1U << equal;
}
}
states = next;
}
return states != 0;
}
static std::array<int, CROSS_NO_MATCH> crossFreeBelow(const CrossValueRange& range) {
const int width = range.pattern.width();
int fixed = width;
int lower = width;
int upper = width;
for (int bit = 0; bit < width; ++bit) {
if (fixed == width && !range.pattern.bitIsXZ(bit)) fixed = bit;
if (lower == width && range.lo.bitIs1(bit)) lower = bit;
if (upper == width && !range.hi.bitIs1(bit)) upper = bit;
}
return {fixed, std::min(fixed, lower), std::min(fixed, upper),
std::min({fixed, lower, upper})};
}
static bool crossRangeContains(const CrossValueRange& range, const V3Number& value) {
if (range.lo.isFourState() || crossValueLess(value, range.lo)
|| crossValueLess(range.hi, value)) {
return false;
}
V3Number result{&value};
return !result.opWildEq(value, range.pattern).isEqZero();
}
static CrossMatchResult crossOutsideExcluded(const CrossValueRange& range,
const std::vector<CrossValueRange>& excluded) {
// Array-bin elements and singleton filters need no prefix search.
if (range.lo.isCaseEq(range.hi)) {
if (!crossRangeContains(range, range.lo)) return CrossMatchResult::NO_MATCH;
return std::none_of(excluded.begin(), excluded.end(),
[&](const CrossValueRange& exclusion) {
return crossRangeContains(exclusion, range.lo);
})
? CrossMatchResult::MATCH
: CrossMatchResult::NO_MATCH;
}
std::vector<const CrossValueRange*> blockers;
std::vector<std::array<int, CROSS_NO_MATCH>> freeBelow;
for (const CrossValueRange& exclusion : excluded) {
if (exclusion.lo.isFourState() || crossValueLess(exclusion.hi, exclusion.lo)
|| crossValueLess(exclusion.hi, range.lo)
|| crossValueLess(range.hi, exclusion.lo)) {
continue;
}
blockers.push_back(&exclusion);
freeBelow.push_back(crossFreeBelow(exclusion));
}
if (blockers.empty()) {
return !range.wildcard || crossWildcardIntersects(range) ? CrossMatchResult::MATCH
: CrossMatchResult::NO_MATCH;
}
struct Frame final {
int bit; // Next bit to assign
std::vector<CrossRangeState> state; // Bound states for candidate and exclusions
int nextValue = 0; // Next bit value to try
};
std::vector<Frame> stack{
{range.pattern.width() - 1,
std::vector<CrossRangeState>(blockers.size() + 1, CROSS_AT_BOUNDS), 0}};
std::set<std::pair<int, std::vector<CrossRangeState>>> failed;
size_t work = 0;
const size_t stepCost = blockers.size() + 1;
const size_t workLimit
= std::max(CROSS_MATCH_WORK_LIMIT, static_cast<size_t>(range.pattern.width())
* stepCost * CROSS_MATCH_LINEAR_ALLOWANCE);
// Seek one witness, pruning prefixes wholly covered by an exclusion. Memoizing
// failed prefixes avoids repeated work; a budget bounds hard wildcard unions.
while (!stack.empty()) {
Frame& frame = stack.back();
if (frame.nextValue == 2) {
failed.emplace(frame.bit, std::move(frame.state));
stack.pop_back();
continue;
}
if (stepCost > workLimit - work) return CrossMatchResult::WORK_LIMIT;
work += stepCost;
const int value = frame.nextValue++;
const CrossRangeState candidate
= crossRangeStep(range, frame.state[0], frame.bit, value);
if (candidate == CROSS_NO_MATCH) continue;
std::vector<CrossRangeState> successor = frame.state;
successor[0] = candidate;
bool covered = false;
for (size_t i = 0; i < blockers.size(); ++i) {
const CrossRangeState match
= crossRangeStep(*blockers[i], frame.state[i + 1], frame.bit, value);
successor[i + 1] = match;
if (match != CROSS_NO_MATCH && freeBelow[i][match] >= frame.bit) {
covered = true;
break;
}
}
if (covered) continue;
if (frame.bit == 0) return CrossMatchResult::MATCH;
const int bit = frame.bit - 1;
if (failed.find({bit, successor}) == failed.end()) {
stack.push_back({bit, std::move(successor), 0});
}
}
return CrossMatchResult::NO_MATCH;
}
static CrossMatchResult crossRangesIntersect(const CrossValueRange& bin,
const CrossValueRange& filter,
const std::vector<CrossValueRange>& excluded,
bool excludeValues) {
if (filter.lo.isFourState() || bin.lo.isFourState()) {
if (!bin.singleton || !filter.singleton || !bin.lo.isCaseEq(filter.lo)) {
return CrossMatchResult::NO_MATCH;
}
return (!excludeValues
|| std::none_of(excluded.begin(), excluded.end(),
[&](const CrossValueRange& range) {
return !range.wildcard && range.singleton
&& bin.lo.isCaseEq(range.lo);
}))
? CrossMatchResult::MATCH
: CrossMatchResult::NO_MATCH;
}
CrossValueRange match = bin;
intersectCrossRange(match, filter);
if (crossValueLess(match.hi, match.lo)) return CrossMatchResult::NO_MATCH;
if (!excludeValues || excluded.empty()) {
return !bin.wildcard || crossWildcardIntersects(match) ? CrossMatchResult::MATCH
: CrossMatchResult::NO_MATCH;
}
return crossOutsideExcluded(match, excluded);
}
static void unsupportedCrossRange(AstCoverBinsof* selectp, bool& valid) {
selectp->v3warn(COVERIGN, "Unsupported: non-constant or non-integral 'intersect' value, "
"or four-state range bound.");
valid = false;
}
static bool crossValueMatchesFilters(AstCoverBinsof* selectp, AstNode* valuep,
AstNodeExpr* exprp, const AstCoverBin* binp,
const CrossValueRange& domain,
const std::vector<CrossValueRange>& filters,
const std::vector<CrossValueRange>& excluded,
bool& valid) {
CrossValueRange range{valuep, domain.lo.width()};
if (!crossValueRange(valuep, exprp, true, binp->isWildcard(), domain, range)) {
unsupportedCrossRange(selectp, valid);
return false;
}
for (const CrossValueRange& filter : filters) {
// State exclusions do not remove values from transition sequences.
const CrossMatchResult result
= crossRangesIntersect(range, filter, excluded, !binp->transp());
if (result == CrossMatchResult::WORK_LIMIT) {
selectp->v3warn(COVERIGN, "Unsupported: 'intersect' exclusion matching exceeds "
"the selection work limit.");
valid = false;
return false;
}
if (result == CrossMatchResult::MATCH) return true;
}
return false;
}
std::vector<bool> selectCoverpointBins(AstCoverBinsof* selectp, const CoverpointBins& bins,
uint32_t first, uint32_t count, bool& valid) {
std::vector<bool> selected(bins.total, false);
std::vector<std::vector<AstNode*>> values;
int width = bins.exprp->width();
for (AstNode* rangep = selectp->rangesp(); rangep; rangep = rangep->nextp()) {
width = std::max(width, crossRangeWidth(rangep));
}
if (selectp->rangesp()) {
for (AstCoverBin* const binp : bins.excluded) {
for (AstNode* rangep = binp->rangesp(); rangep; rangep = rangep->nextp()) {
width = std::max(width, crossRangeWidth(rangep));
}
}
values.reserve(count);
for (uint32_t i = first; i < first + count; ++i) {
values.push_back(crossBinValues(bins.values[i]));
for (AstNode* const valuep : values.back()) {
width = std::max(width, crossRangeWidth(valuep));
}
}
}
// One extra bit preserves both unsigned maxima and negative signed bounds.
++width;
const CrossValueRange domain
= crossValueDomain(selectp, bins.exprp->width(), bins.exprp->isSigned(), width);
std::vector<CrossValueRange> filters;
for (AstNode* rangep = selectp->rangesp(); rangep; rangep = rangep->nextp()) {
CrossValueRange filter{rangep, width};
if (!crossValueRange(rangep, bins.exprp, false, false, domain, filter)) {
unsupportedCrossRange(selectp, valid);
return {};
}
filters.push_back(std::move(filter));
}
std::vector<CrossValueRange> excluded;
if (selectp->rangesp()) {
for (AstCoverBin* const binp : bins.excluded) {
for (AstNode* rangep = binp->rangesp(); rangep; rangep = rangep->nextp()) {
CrossValueRange range{rangep, width};
if (!crossValueRange(rangep, bins.exprp, true, binp->isWildcard(), domain,
range)) {
unsupportedCrossRange(selectp, valid);
return {};
}
excluded.push_back(std::move(range));
}
}
}
for (uint32_t i = first; i < first + count; ++i) {
if (!selectp->rangesp()) {
selected[i] = true;
continue;
}
for (AstNode* const valuep : values[i - first]) {
selected[i]
= crossValueMatchesFilters(selectp, valuep, bins.exprp, bins.values[i].binp,
domain, filters, excluded, valid);
if (!valid) return {};
if (selected[i]) break;
}
}
if (selectp->isNegated()) {
for (uint32_t i = 0; i < bins.total; ++i) selected[i] = !selected[i];
}
return selected;
}
static void setCrossSelectionRange(CrossSelection& selection, uint64_t first, uint64_t end) {
while (first < end) {
const unsigned bit = first % 64;
const unsigned bits = std::min<uint64_t>(64 - bit, end - first);
selection[first / 64] |= (bits == 64 ? ~uint64_t{0} : (uint64_t{1} << bits) - 1)
<< bit;
first += bits;
}
}
CrossSelection crossSelection(AstNode* nodep, CrossSelectionContext& ctx) {
if (AstCoverCrossSelect* const opp = VN_CAST(nodep, CoverCrossSelect)) {
CrossSelection lhs = crossSelection(opp->lhsp(), ctx);
const CrossSelection rhs = crossSelection(opp->rhsp(), ctx);
if (!ctx.valid) return {};
for (size_t i = 0; i < lhs.size(); ++i) {
lhs[i] = opp->isOr() ? lhs[i] | rhs[i] : lhs[i] & rhs[i];
}
return lhs;
}
AstCoverBinsof* const selectp = VN_AS(nodep, CoverBinsof);
const auto dimIt = ctx.dimensions.find(selectp->pointp()->name());
if (dimIt == ctx.dimensions.end()) {
selectp->v3error("binsof coverpoint "
<< selectp->pointp()->prettyNameQ() << " is not an item of cross "
<< ctx.crossp->prettyNameQ() << " (IEEE 1800-2012 19.6.1).");
ctx.valid = false;
return {};
}
const uint32_t dim = dimIt->second;
const CoverpointBins& bins = m_cpBins.at(ctx.cpVars[dim]);
uint32_t first = 0;
uint32_t count = bins.total;
if (!selectp->name().empty()) {
const auto binIt = bins.spans.find(selectp->name());
if (binIt == bins.spans.end()) {
selectp->v3error("Cannot find bin " << selectp->prettyNameQ() << " in coverpoint "
<< selectp->pointp()->prettyNameQ()
<< " (IEEE 1800-2012 19.6.1).");
ctx.valid = false;
return {};
}
first = binIt->second.first;
count = binIt->second.second;
}
const std::vector<bool> selected
= selectCoverpointBins(selectp, bins, first, count, ctx.valid);
if (!ctx.valid) return {};
CrossSelection result((static_cast<uint64_t>(ctx.tuples) + 63) / 64, 0);
const uint64_t stride = ctx.strides[dim];
const uint64_t period = stride * bins.total;
for (uint64_t base = 0; base < ctx.tuples; base += period) {
for (uint32_t i = 0; i < bins.total;) {
if (!selected[i]) {
++i;
continue;
}
const uint32_t begin = i++;
while (i < bins.total && selected[i]) ++i;
setCrossSelectionRange(result, base + begin * stride, base + i * stride);
}
}
return result;
}
CrossLayout resolveCrossLayout(AstCoverCross* crossp, const std::vector<AstVar*>& cpVars,
const std::map<std::string, uint32_t>& dimensions) {
CrossLayout layout;
CrossSelectionContext ctx{crossp, cpVars, dimensions, 0, {}};
uint64_t tuples = std::any_of(cpVars.begin(), cpVars.end(),
[this](AstVar* varp) { return !m_cpBins.at(varp).total; })
? 0
: 1;
ctx.strides.resize(cpVars.size());
for (size_t d = cpVars.size(); d > 0; --d) {
ctx.strides[d - 1] = tuples;
tuples *= m_cpBins.at(cpVars[d - 1]).total;
if (tuples > UINT32_MAX) {
crossp->v3warn(COVERIGN,
"Unsupported: cross coverage with more than 2^32-1 tuples.");
layout.valid = false;
return layout;
}
}
ctx.tuples = tuples;
layout.tuples = tuples;
CrossSelection excluded;
std::set<std::string> names;
for (AstNode* itemp = crossp->binsp(); itemp; itemp = itemp->nextp()) {
AstCoverCrossBin* const binp = VN_AS(itemp, CoverCrossBin);
@@ -1458,38 +2000,58 @@ class FunctionalCoverageVisitor final : public VNVisitor {
<< " (IEEE 1800-2012 19.6.1).");
continue;
}
const AstCoverBinsof* const selectp = VN_AS(binp->selectp(), CoverBinsof);
const auto dimIt = dimensions.find(selectp->pointp()->name());
if (dimIt == dimensions.end()) {
selectp->v3error("binsof coverpoint "
<< selectp->pointp()->prettyNameQ() << " is not an item of cross "
<< crossp->prettyNameQ() << " (IEEE 1800-2012 19.6.1).");
ctx.valid = true;
CrossSelection selection = crossSelection(binp->selectp(), ctx);
if (!ctx.valid || std::all_of(selection.begin(), selection.end(), [](uint64_t word) {
return word == 0;
})) {
continue;
}
const uint32_t dim = dimIt->second;
const CoverpointBins& cpBins = m_cpBins.at(cpVars[dim]);
uint32_t first = 0;
uint32_t count = cpBins.total;
if (!selectp->name().empty()) {
const auto binIt = cpBins.spans.find(selectp->name());
if (binIt == cpBins.spans.end()) {
selectp->v3error("Cannot find bin " << selectp->prettyNameQ()
<< " in coverpoint "
<< selectp->pointp()->prettyNameQ()
<< " (IEEE 1800-2012 19.6.1).");
continue;
}
first = binIt->second.first;
count = binIt->second.second;
if (excluded.empty()) excluded.resize(selection.size(), 0);
for (size_t i = 0; i < selection.size(); ++i) {
excluded[i] |= selection[i];
if (selection[i]) ++layout.binWords;
}
// IEEE 1800-2012 19.6 excludes default, ignored and illegal coverpoint bins
// from cross products. An empty selection is valid, not an unsupported construct.
if (!count) continue;
layout.bins.push_back({binp, std::move(selection)});
}
if (!layout.bins.empty()) {
layout.autoBins = layout.tuples;
for (const uint64_t word : excluded) {
layout.autoBins -= static_cast<uint32_t>(std::bitset<VL_QUADSIZE>{word}.count());
}
}
return layout;
}
AstCoverCrossDType* crossDType(FileLine* fl, uint32_t dimensions, const CrossLayout& layout) {
const uint32_t bins = static_cast<uint32_t>(layout.bins.size());
const CrossShape shape{dimensions, layout.tuples, bins, layout.autoBins, layout.binWords};
AstCoverCrossDType*& typep = m_cxDTypes[shape];
if (!typep) {
typep = new AstCoverCrossDType{fl, dimensions, layout.tuples,
bins, layout.autoBins, layout.binWords};
v3Global.rootp()->typeTablep()->addTypesp(typep);
}
return typep;
}
std::vector<AstCoverCrossBin*> generateCrossBins(AstCoverCross* crossp, AstVar* cxVarp,
const CrossLayout& layout) {
std::vector<AstCoverCrossBin*> bins;
for (const ResolvedCrossBin& resolved : layout.bins) {
AstCoverCrossBin* const binp = resolved.binp;
const CrossSelection& selection = resolved.selection;
FileLine* const fl = binp->fileline();
const bool prot = v3Global.opt.protectIds();
std::string mask = "{";
for (size_t i = 0; i < selection.size(); ++i) {
if (i) mask += ", ";
mask += std::to_string(selection[i]) + "ULL";
}
mask += "}";
m_constructorp->addStmtsp(
itemCall(fl, cxVarp, VCMethod::COVERGROUP_ADD_BIN,
{cnum(fl, dim), cnum(fl, first), cnum(fl, count),
{ctext(fl, mask),
ctext(fl, quoted(VIdProtect::protectWordsIf(binp->name(), prot))),
ctext(fl, quoted(VIdProtect::protectIf(fl->filename(), prot))),
cnum(fl, static_cast<uint32_t>(fl->lineno())),
@@ -1532,9 +2094,11 @@ class FunctionalCoverageVisitor final : public VNVisitor {
itemp = nextp;
}
const int dims = static_cast<int>(cpVars.size());
const CrossLayout layout = resolveCrossLayout(crossp, cpVars, dimensions);
if (!layout.valid) return;
AstVar* const cxVarp = new AstVar{fl, VVarType::MEMBER, "__Vcx_" + crossp->name(),
basicDType(fl, VBasicDTypeKwd::COVERGROUP_CROSS)};
crossDType(fl, static_cast<uint32_t>(dims), layout)};
m_covergroupp->addMembersp(cxVarp);
m_crossVars.push_back(cxVarp);
m_constructorp->addStmtsp(makeItemCreate(fl, cxVarp, VCMethod::COVERGROUP_ADD_CROSS));
@@ -1552,8 +2116,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
ctext(fl, quoted(VIdProtect::protectIf(fl->filename(), prot))),
cnum(fl, static_cast<uint32_t>(fl->lineno())),
cnum(fl, static_cast<uint32_t>(fl->firstColumn()))})));
const std::vector<AstCoverCrossBin*> bins
= generateCrossBins(crossp, cxVarp, cpVars, dimensions);
const std::vector<AstCoverCrossBin*> bins = generateCrossBins(crossp, cxVarp, layout);
if (v3Global.opt.coverage()) {
const std::string page
= VIdProtect::protectIf("v_covergroup/" + m_covergroupp->name(), prot);
@@ -1565,15 +2128,18 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// sample(): after all coverpoints have sampled (cross loop runs after coverpoint loop).
UASSERT_OBJ(m_sampleFuncp, crossp, "sample() CFunc not set for cross");
// The cross reads its coverpoints from its own m_cps, so sample() needs no cps array;
// The cross remembers its feeding coverpoints, so sample() needs no cps array;
// per-bin iff guards still need a temporary array, hence the block form.
const bool hasIffs
= std::any_of(bins.begin(), bins.end(),
[](const AstCoverCrossBin* binp) { return binp->iffp() != nullptr; });
AstNodeStmt* const samplep
= bins.empty() ? static_cast<AstNodeStmt*>(
itemCall(fl, cxVarp, VCMethod::COVERGROUP_SAMPLE)->makeStmt())
: static_cast<AstNodeStmt*>(makeCrossIffsCall(
fl, bins,
itemCall(fl, cxVarp, VCMethod::COVERGROUP_SAMPLE_IFFS,
{ctext(fl, "__Vcx_iffs")})));
= !hasIffs ? static_cast<AstNodeStmt*>(
itemCall(fl, cxVarp, VCMethod::COVERGROUP_SAMPLE)->makeStmt())
: static_cast<AstNodeStmt*>(makeCrossIffsCall(
fl, bins,
itemCall(fl, cxVarp, VCMethod::COVERGROUP_SAMPLE_IFFS,
{ctext(fl, "__Vcx_iffs")})));
if (AstNodeExpr* const iffp = crossp->iffp()) {
m_sampleFuncp->addStmtsp(new AstIf{fl, iffp->cloneTree(false), samplep});
} else {
@@ -2355,6 +2921,9 @@ public:
~FunctionalCoverageVisitor() override = default;
};
// C++14 requires definitions for constexpr members passed by reference.
constexpr size_t FunctionalCoverageVisitor::CROSS_MATCH_WORK_LIMIT;
//######################################################################
// Functional coverage class functions