mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 01:54:01 +02:00
Support logic and set operations on binsof (#8306)
This commit is contained in:
+1
-7
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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";
|
||||
|
||||
@@ -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
@@ -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); }
|
||||
|
||||
@@ -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
@@ -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
@@ -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 ')'
|
||||
|
||||
Reference in New Issue
Block a user