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
+1 -7
View File
@@ -510,7 +510,6 @@ public:
RANDOM_GENERATOR,
RANDOM_STDGENERATOR,
COVERGROUP_INSTHANDLE,
COVERGROUP_CROSS,
// Unsigned and two state; fundamental types
UINT32,
UINT64,
@@ -547,7 +546,6 @@ public:
"VlRandomizer",
"VlStdRandomizer",
"VlCovInstHandle",
"VlCoverCross*",
"IData",
"QData",
"LOGIC_IMPLICIT",
@@ -581,7 +579,6 @@ public:
"%E-rand-gen",
"%E-stdrand-gen",
"%E-cg-insthandle",
"%E-cover-cross",
"IData",
"QData",
"%E-logic-implct",
@@ -627,7 +624,6 @@ public:
case RANDOM_GENERATOR: return 0; // opaque
case RANDOM_STDGENERATOR: return 0; // opaque
case COVERGROUP_INSTHANDLE: return 0; // opaque
case COVERGROUP_CROSS: return 0; // opaque
case UINT32: return 32;
case UINT64: return 64;
default: return 0;
@@ -669,8 +665,7 @@ public:
|| m_e == MTASKSTATE || m_e == DELAY_SCHEDULER || m_e == TRIGGER_SCHEDULER
|| m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC || m_e == PROCESS_REFERENCE
|| m_e == RANDOM_GENERATOR || m_e == RANDOM_STDGENERATOR
|| m_e == COVERGROUP_INSTHANDLE || m_e == COVERGROUP_CROSS || m_e == DOUBLE
|| m_e == UNTYPED);
|| m_e == COVERGROUP_INSTHANDLE || m_e == DOUBLE || m_e == UNTYPED);
}
bool isCHandle() const VL_MT_SAFE { return m_e == CHANDLE; }
bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; }
@@ -726,7 +721,6 @@ public:
/* RANDOM_GENERATOR: */ "", // Should not be traced
/* RANDOM_STD_GENERATOR: */ "", // Should not be traced
/* COVERGROUP_INSTHANDLE: */ "", // Should not be traced
/* COVERGROUP_CROSS: */ "", // Should not be traced
/* UINT32: */ "BIT",
/* UINT64: */ "BIT",
/* LOGIC_IMPLICIT: */ "", // Should not be traced
+44 -3
View File
@@ -501,9 +501,6 @@ public:
bool isCovergroupInstHandle() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::COVERGROUP_INSTHANDLE;
}
bool isCovergroupCross() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::COVERGROUP_CROSS;
}
bool isOpaque() const VL_MT_SAFE { return keyword().isOpaque(); }
bool isString() const VL_MT_STABLE { return keyword().isString(); }
bool isZeroInit() const { return keyword().isZeroInit(); }
@@ -693,6 +690,50 @@ public:
int widthTotalBytes() const override { return 1; }
bool isCompound() const override { return false; }
};
class AstCoverCrossDType final : public AstNodeDType {
// Borrowed pointer to VlCoverCrossT<dimensions, tuples, bins, autoBins, binWords>.
const uint32_t m_dimensions;
const uint32_t m_tuples;
const uint32_t m_bins;
const uint32_t m_autoBins;
const uint64_t m_binWords;
public:
AstCoverCrossDType(FileLine* fl, uint32_t dimensions, uint32_t tuples, uint32_t bins,
uint32_t autoBins, uint64_t binWords)
: ASTGEN_SUPER_CoverCrossDType(fl)
, m_dimensions{dimensions}
, m_tuples{tuples}
, m_bins{bins}
, m_autoBins{autoBins}
, m_binWords{binWords} {
dtypep(this);
}
ASTGEN_MEMBERS_AstCoverCrossDType;
const char* broken() const override {
BROKEN_RTN(m_dimensions == 0);
return nullptr;
}
bool sameNode(const AstNode* samep) const override {
const AstCoverCrossDType* const sp = VN_DBG_AS(samep, CoverCrossDType);
return dimensions() == sp->dimensions() && tuples() == sp->tuples() && bins() == sp->bins()
&& autoBins() == sp->autoBins() && binWords() == sp->binWords();
}
bool similarDTypeNode(const AstNodeDType* samep) const override { return this == samep; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
void dumpSmall(std::ostream& str) const override;
uint32_t dimensions() const { return m_dimensions; }
uint32_t tuples() const { return m_tuples; }
uint32_t bins() const { return m_bins; }
uint32_t autoBins() const { return m_autoBins; }
uint64_t binWords() const { return m_binWords; }
string cppTemplateArgs() const;
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
int widthAlignBytes() const override { return sizeof(void*); }
int widthTotalBytes() const override { return sizeof(void*); }
bool isCompound() const override { return true; }
};
class AstCoverpointDType final : public AstNodeDType {
// Borrowed pointer to a covergroup coverpoint runtime, 'VlCoverpointT<hitBound>*'.
// Follows pattern of AstQueueDType in capturing the compile-time max bin overlap
+36 -2
View File
@@ -1108,16 +1108,28 @@ public:
class AstCoverBinsof final : public AstNode {
// A binsof selection of a coverpoint or one of its named bins
// @astgen op1 := pointp : AstCoverpointRef
// @astgen op2 := rangesp : List[AstNode] // Optional intersect value ranges
string m_name; // Selected bin name, or empty for all bins of the coverpoint
const bool m_isNegated; // Complement the selection within the cross product
public:
AstCoverBinsof(FileLine* fl, AstCoverpointRef* pointp)
: ASTGEN_SUPER_CoverBinsof(fl) {
AstCoverBinsof(FileLine* fl, AstCoverpointRef* pointp, bool isNegated = false,
AstNode* rangesp = nullptr)
: ASTGEN_SUPER_CoverBinsof(fl)
, m_isNegated{isNegated} {
this->pointp(pointp);
addRangesp(rangesp);
}
ASTGEN_MEMBERS_AstCoverBinsof;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return m_name; }
void name(const string& name) override { m_name = name; }
bool isNegated() const { return m_isNegated; }
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
const AstCoverBinsof* const asamep = VN_DBG_AS(samep, CoverBinsof);
return m_name == asamep->m_name && m_isNegated == asamep->m_isNegated;
} // LCOV_EXCL_STOP
};
class AstCoverCrossBin final : public AstNode {
// A named cross bin and its selection expression
@@ -1135,6 +1147,28 @@ public:
ASTGEN_MEMBERS_AstCoverCrossBin;
string name() const override VL_MT_STABLE { return m_name; }
};
class AstCoverCrossSelect final : public AstNode {
// Intersection or union of two cross-bin selections
// @astgen op1 := lhsp : Optional[AstNode] // Null for an unsupported selection
// @astgen op2 := rhsp : Optional[AstNode] // Null for an unsupported selection
const bool m_isOr; // Union (||), rather than intersection (&&)
public:
AstCoverCrossSelect(FileLine* fl, AstNode* lhsp, AstNode* rhsp, bool isOr)
: ASTGEN_SUPER_CoverCrossSelect(fl)
, m_isOr{isOr} {
this->lhsp(lhsp);
this->rhsp(rhsp);
}
ASTGEN_MEMBERS_AstCoverCrossSelect;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool isOr() const { return m_isOr; }
string verilogKwd() const override { return isOr() ? "||" : "&&"; }
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
return m_isOr == VN_DBG_AS(samep, CoverCrossSelect)->m_isOr;
} // LCOV_EXCL_STOP
};
class AstCoverOption final : public AstNode {
// Coverage-option assignment
// @astgen op1 := valuep : AstNodeExpr
+39 -4
View File
@@ -1209,8 +1209,44 @@ void AstCoverBin::dumpJson(std::ostream& str) const {
dumpJsonBoolIf(str, "isWildcard", isWildcard());
str << ", \"binsType\": \"" << binsType().ascii() << "\"";
}
void AstCoverBinsof::dump(std::ostream& str) const {
Super::dump(str);
if (isNegated()) str << " [NEGATED]";
}
void AstCoverBinsof::dumpJson(std::ostream& str) const {
Super::dumpJson(str);
dumpJsonBoolIf(str, "isNegated", isNegated());
}
void AstCoverCross::dump(std::ostream& str) const { Super::dump(str); }
void AstCoverCross::dumpJson(std::ostream& str) const { Super::dumpJson(str); }
string AstCoverCrossDType::cppTemplateArgs() const {
return cvtToStr(dimensions()) + ", " + cvtToStr(tuples()) + ", " + cvtToStr(bins()) + ", "
+ cvtToStr(autoBins()) + ", " + cvtToStr(binWords());
}
void AstCoverCrossDType::dump(std::ostream& str) const {
Super::dump(str);
str << " [" << cppTemplateArgs() << "]";
}
void AstCoverCrossDType::dumpJson(std::ostream& str) const {
dumpJsonNumFunc(str, dimensions);
dumpJsonNumFunc(str, tuples);
dumpJsonNumFunc(str, bins);
dumpJsonNumFunc(str, autoBins);
dumpJsonNumFunc(str, binWords);
dumpJsonGen(str);
}
void AstCoverCrossDType::dumpSmall(std::ostream& str) const {
Super::dumpSmall(str);
str << "covercross[" << cppTemplateArgs() << "]";
}
void AstCoverCrossSelect::dump(std::ostream& str) const {
Super::dump(str);
str << (isOr() ? " [OR]" : " [AND]");
}
void AstCoverCrossSelect::dumpJson(std::ostream& str) const {
Super::dumpJson(str);
dumpJsonBoolIf(str, "isOr", isOr());
}
void AstCoverInc::dump(std::ostream& str) const {
Super::dump(str);
str << " -> ";
@@ -2148,6 +2184,9 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe
// + 1 below as VlQueue uses 0 to mean unlimited, 1 to mean size() max is 1
if (adtypep->boundp()) info.m_type += ", " + cvtToStr(adtypep->boundConst() + 1);
info.m_type += ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, CoverCrossDType)) {
UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union");
info.m_type = "VlCoverCrossT<" + adtypep->cppTemplateArgs() + ">*";
} else if (const auto* const adtypep = VN_CAST(dtypep, CoverpointDType)) {
UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union");
info.m_type = "VlCoverpointT<" + cvtToStr(adtypep->hitBound()) + ">*";
@@ -2228,10 +2267,6 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe
info.m_type = "VlStdRandomizer";
} else if (bdtypep->isCovergroupInstHandle()) {
info.m_type = "VlCovInstHandle";
} else if (bdtypep->isCovergroupCross()) {
// Borrowed pointer: VlCovergroupInst owns the cross runtime, so its bins outlive the
// SV covergroup object (the coverage DB holds raw count pointers read at write() time)
info.m_type = "VlCoverCross*";
} else if (bdtypep->isEvent()) {
info.m_type = v3Global.assignsEvents() ? "VlAssignableEvent" : "VlEvent";
} else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width
+12 -11
View File
@@ -93,6 +93,7 @@ class CleanVisitor final : public VNVisitor {
|| VN_IS(nodep->dtypep()->skipRefp(), QueueDType)
|| VN_IS(nodep->dtypep()->skipRefp(), StreamDType)
|| VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)
|| VN_IS(nodep->dtypep()->skipRefp(), CoverCrossDType)
|| VN_IS(nodep->dtypep()->skipRefp(), CoverpointDType)
|| VN_IS(nodep->dtypep()->skipRefp(), VoidDType)) {
} else {
@@ -141,12 +142,12 @@ class CleanVisitor final : public VNVisitor {
computeCppWidth(nodep);
if (!isClean(nodep)) insertClean(nodep);
}
void ensureCleanAndNext(AstNodeExpr* nodep) {
void ensureCleanAndNext(AstNode* nodep) {
// Editing list, careful looping!
for (AstNodeExpr* exprp = nodep; exprp;) {
AstNodeExpr* const nextp = VN_AS(exprp->nextp(), NodeExpr);
ensureClean(exprp);
exprp = nextp;
for (AstNode* argp = nodep; argp;) {
AstNode* const nextp = argp->nextp();
if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp);
argp = nextp;
}
}
@@ -250,9 +251,7 @@ class CleanVisitor final : public VNVisitor {
setClean(nodep, false);
// We always clean, as we don't trust those pesky users.
if (!VN_IS(nodep->backp(), And)) insertClean(nodep);
for (AstNode* argp = nodep->nodesp(); argp; argp = argp->nextp()) {
if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp);
}
ensureCleanAndNext(nodep->nodesp());
}
void visit(AstTraceDecl* nodep) override {} // Nothing to do here
void visit(AstTraceInc* nodep) override {
@@ -288,11 +287,13 @@ class CleanVisitor final : public VNVisitor {
ensureCleanAndNext(nodep->exprsp());
setClean(nodep, true); // generates a string, so not relevant
}
void visit(AstCStmt* nodep) override {
iterateChildren(nodep);
ensureCleanAndNext(nodep->nodesp());
}
void visit(AstCStmtUser* nodep) override {
iterateChildren(nodep);
for (AstNode* argp = nodep->nodesp(); argp; argp = argp->nextp()) {
if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp);
}
ensureCleanAndNext(nodep->nodesp());
}
void visit(AstNodeCCall* nodep) override {
iterateChildren(nodep);
+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
+3 -4
View File
@@ -541,7 +541,7 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing,
depth + 1, suffix + ".atDefault()", nullptr);
} else if (VN_IS(dtypep, CDType)) {
return ""; // Constructor does it
} else if (VN_IS(dtypep, CoverpointDType)) {
} else if (VN_IS(dtypep, CoverCrossDType) || VN_IS(dtypep, CoverpointDType)) {
return ""; // Covergroup constructor creates the runtime and assigns the pointer
} else if (const AstClassRefDType* const adtypep = VN_CAST(dtypep, ClassRefDType)) {
return adtypep->rawPointer() ? varNameProtected + suffix + " = nullptr;\n" : "";
@@ -596,9 +596,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing,
return "";
} else if (basicp && (basicp->isRandomGenerator() || basicp->isStdRandomGenerator())) {
return "";
} else if (basicp && (basicp->isCovergroupInstHandle() || basicp->isCovergroupCross())) {
// The handle's own constructor deals with it; the cross is a borrowed pointer that
// the covergroup constructor assigns once it creates the runtime
} else if (basicp && basicp->isCovergroupInstHandle()) {
// The handle's own constructor deals with it.
return "";
} else if (basicp && (basicp->isEvent())) {
return "VlAssignableEvent{};\n";
+4
View File
@@ -819,6 +819,10 @@ public:
const AstCoverpointDType* const cpdtypep
= VN_AS(nodep->dtypep()->skipRefp(), CoverpointDType);
puts("<" + cvtToStr(cpdtypep->hitBound()) + ">");
} else if (nodep->method() == VCMethod::COVERGROUP_ADD_CROSS) {
const AstCoverCrossDType* const cxdtypep
= VN_AS(nodep->dtypep()->skipRefp(), CoverCrossDType);
puts("<" + cxdtypep->cppTemplateArgs() + ">");
}
puts("(");
bool comma = false;
+40 -2
View File
@@ -354,7 +354,38 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
}
puts(";\n");
}
void visit(AstCoverpointRef* nodep) override { putfs(nodep, nodep->name()); }
void visit(AstCoverBinsof* nodep) override {
putfs(nodep, nodep->isNegated() ? "!binsof(" : "binsof(");
iterateConst(nodep->pointp());
if (!nodep->name().empty()) puts("." + nodep->name());
puts(")");
if (nodep->rangesp()) {
puts(" intersect {");
iterateAndCommaConstNull(nodep->rangesp());
puts("}");
}
}
void visit(AstCoverCrossBin* nodep) override {
putfs(nodep, "bins " + nodep->name() + " = ");
iterateConstNull(nodep->selectp());
if (nodep->iffp()) {
puts(" iff (");
iterateConst(nodep->iffp());
puts(")");
}
puts(";\n");
}
void visit(AstCoverCrossSelect* nodep) override {
putfs(nodep, "(");
iterateConstNull(nodep->lhsp());
putbs(" " + nodep->verilogKwd() + " ");
iterateConstNull(nodep->rhsp());
puts(")");
}
void visit(AstCoverpointRef* nodep) override {
putfs(nodep, nodep->name());
iterateConstNull(nodep->exprp());
}
void visit(AstCoverCross* nodep) override {
putfs(nodep, nodep->name() + ": cross ");
for (AstNode* itemp = nodep->itemsp(); itemp; itemp = itemp->nextp()) {
@@ -366,7 +397,13 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
iterateConst(nodep->iffp());
puts(")");
}
puts(";\n");
if (nodep->binsp()) {
puts(" {\n");
iterateAndNextConstNull(nodep->binsp());
puts("}\n");
} else {
puts(";\n");
}
}
void visit(AstCoverTransSet* nodep) override {
puts("(");
@@ -1207,6 +1244,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
}
}
void visit(AstConst* nodep) override { putfs(nodep, nodep->num().ascii(m_prefixed, true)); }
void visit(AstUnbounded* nodep) override { emitVerilogFormat(nodep, nodep->emitVerilog()); }
// Just iterate
void visit(AstTopScope* nodep) override { iterateChildrenConst(nodep); }
+10
View File
@@ -1447,6 +1447,16 @@ class LinkParseVisitor final : public VNVisitor {
}
}
void visit(AstCoverCrossSelect* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
if (!nodep->lhsp()
|| !nodep->rhsp()) { // Due to earlier Unsupported errors dropping only one operand
// would silently change the selected set.
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
}
void visit(AstCoverCross* nodep) override {
cleanFileline(nodep);
// Move options out of the mixed parse-time body, leaving only cross bins.
+10 -3
View File
@@ -2080,13 +2080,13 @@ class WidthVisitor final : public VNVisitor {
if (nodep->iffp()) iterateCheckBool(nodep, "iff condition", nodep->iffp(), BOTH);
userIterateAndNext(nodep->optionsp(), nullptr);
}
void visit(AstCoverBin* nodep) override {
// Bin range/value entries are self-determined constant expressions (IEEE 1800-2023
void widthCovergroupRanges(AstNode* rangesp) {
// Bin range/value entries are self-determined expressions (IEEE 1800-2023
// 19.5). Width each plain single-value entry self-determined so a referenced
// parameter acquires a dtype, then constify so the reference folds to the AstConst
// value that V3Covergroup requires. AstInsideRange entries fold their own bounds in
// visit(AstInsideRange).
for (AstNode *nextp, *itemp = nodep->rangesp(); itemp; itemp = nextp) {
for (AstNode *nextp, *itemp = rangesp; itemp; itemp = nextp) {
nextp = itemp->nextp();
if (VN_IS(itemp, InsideRange)) {
userIterate(itemp, nullptr);
@@ -2095,6 +2095,13 @@ class WidthVisitor final : public VNVisitor {
V3Const::constifyEdit(itemp); // itemp may change
}
}
}
void visit(AstCoverBinsof* nodep) override {
userIterateAndNext(nodep->pointp(), nullptr);
widthCovergroupRanges(nodep->rangesp());
}
void visit(AstCoverBin* nodep) override {
widthCovergroupRanges(nodep->rangesp());
userIterateAndNext(nodep->iffp(), nullptr);
userIterateAndNext(nodep->arraySizep(), nullptr);
userIterateAndNext(nodep->transp(), nullptr);
+8 -7
View File
@@ -7407,9 +7407,9 @@ select_expression<nodep>: // ==IEEE: select_expression
select_expression_r
{ $$ = $1; }
| select_expression yP_ANDAND select_expression
{ $$ = nullptr; BBCOVERIGN($2, "Unsupported: '&&' in coverage select expression"); DEL($1, $3); }
{ $$ = new AstCoverCrossSelect{$2, $1, $3, false}; }
| select_expression yP_OROR select_expression
{ $$ = nullptr; BBCOVERIGN($2, "Unsupported: '||' in coverage select expression"); DEL($1, $3); }
{ $$ = new AstCoverCrossSelect{$2, $1, $3, true}; }
;
// This non-terminal exists to disambiguate select_expression and make "with" bind tighter
@@ -7418,11 +7418,12 @@ select_expression_r<nodep>:
yBINSOF '(' bins_expression ')'
{ $$ = new AstCoverBinsof{$1, new AstCoverpointRef{$3->fileline(), $3}}; }
| '!' yBINSOF '(' bins_expression ')'
{ $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'binsof' in coverage select expression"); DEL($4); }
| yBINSOF '(' bins_expression ')' yINTERSECT '{' covergroup_range_list '}'
{ $$ = nullptr; BBCOVERIGN($5, "Unsupported: 'intersect' in coverage select expression"); DEL($3, $7); }
| '!' yBINSOF '(' bins_expression ')' yINTERSECT '{' covergroup_range_list '}' { }
{ $$ = nullptr; BBCOVERIGN($5, "Unsupported: 'intersect' in coverage select expression"); DEL($4, $8); }
{ $$ = new AstCoverBinsof{$1, new AstCoverpointRef{$4->fileline(), $4}, true}; }
// // IEEE: covergroup_range_list has the same syntax as range_list
| yBINSOF '(' bins_expression ')' yINTERSECT '{' range_list '}'
{ $$ = new AstCoverBinsof{$1, new AstCoverpointRef{$3->fileline(), $3}, false, $7}; }
| '!' yBINSOF '(' bins_expression ')' yINTERSECT '{' range_list '}'
{ $$ = new AstCoverBinsof{$1, new AstCoverpointRef{$4->fileline(), $4}, true, $8}; }
| yWITH__PAREN '(' cgexpr ')'
{ $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'with' in coverage select expression"); DEL($3); }
| '!' yWITH__PAREN '(' cgexpr ')'