Add initial support for solo binsof (#8298)

This commit is contained in:
Marco Bartoli
2026-09-06 19:51:31 -04:00
committed by GitHub
parent bbbb9c3e7c
commit 9da900a5e2
17 changed files with 663 additions and 293 deletions
+32 -2
View File
@@ -1097,6 +1097,36 @@ public:
bool isArray() const { return m_isArray; }
void isArray(bool flag) { m_isArray = flag; }
};
class AstCoverBinsof final : public AstNode {
// A binsof selection of a coverpoint or one of its named bins
// @astgen op1 := pointp : AstCoverpointRef
string m_name; // Selected bin name, or empty for all bins of the coverpoint
public:
AstCoverBinsof(FileLine* fl, AstCoverpointRef* pointp)
: ASTGEN_SUPER_CoverBinsof(fl) {
this->pointp(pointp);
}
ASTGEN_MEMBERS_AstCoverBinsof;
string name() const override VL_MT_STABLE { return m_name; }
void name(const string& name) override { m_name = name; }
};
class AstCoverCrossBin final : public AstNode {
// A named cross bin and its selection expression
// @astgen op1 := selectp : Optional[AstNode] // Null for unsupported selections
// @astgen op2 := iffp : Optional[AstNodeExpr]
const string m_name; // Declared cross bin name
public:
AstCoverCrossBin(FileLine* fl, const string& name, AstNode* selectp, AstNodeExpr* iffp)
: ASTGEN_SUPER_CoverCrossBin(fl)
, m_name{name} {
this->selectp(selectp);
this->iffp(iffp);
}
ASTGEN_MEMBERS_AstCoverCrossBin;
string name() const override VL_MT_STABLE { return m_name; }
};
class AstCoverOption final : public AstNode {
// Coverage-option assignment
// @astgen op1 := valuep : AstNodeExpr
@@ -2807,8 +2837,8 @@ public:
class AstCoverCross final : public AstNodeFuncCovItem {
// @astgen op1 := itemsp : List[AstCoverpointRef]
// @astgen op2 := optionsp : List[AstCoverOption] // post-LinkParse only
// @astgen op3 := rawBodyp : List[AstNode] // Parse: raw cross_body items;
// // post-LinkParse: empty
// @astgen op3 := binsp : List[AstNode] // Parse: mixed cross bins/options;
// // post-LinkParse: AstCoverCrossBin only
// @astgen op4 := iffp : Optional[AstNodeExpr] // Conditional sampling guard
public:
AstCoverCross(FileLine* fl, const string& name, AstCoverpointRef* itemsp,
+111 -8
View File
@@ -31,6 +31,7 @@
#include "V3MemberMap.h"
#include <set>
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
@@ -66,8 +67,13 @@ class CovergroupExprValidVisitor final : public VNVisitor {
}
void visit(AstCoverCross* nodep) override {
iterateAndNextNull(nodep->itemsp());
scanSampleExpression(nodep->iffp());
iterateAndNextNull(nodep->optionsp());
iterateAndNextNull(nodep->rawBodyp());
iterateAndNextNull(nodep->binsp());
}
void visit(AstCoverCrossBin* nodep) override {
iterateAndNextNull(nodep->selectp());
scanSampleExpression(nodep->iffp());
}
void visit(AstCoverBin* nodep) override {
scanCoverageExpression(nodep->rangesp());
@@ -156,6 +162,12 @@ 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 CoverpointBins final {
uint32_t total = 0; // Number of Normal bins
std::unordered_map<std::string, std::pair<uint32_t, uint32_t>>
spans; // Declared bin name -> first Normal index and number of bins
};
std::map<AstVar*, CoverpointBins> m_cpBins; // Runtime coverpoint -> binsof index ranges
std::set<AstCoverCross*>
m_droppedCrosses; // Crosses with a bare-variable item: drop (COVERIGN)
std::map<int, AstCDType*> m_vlCoverpointTypes; // hit-list bound K -> "VlCoverpointT<K>" type
@@ -173,6 +185,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
m_cpVars.clear();
m_crossVars.clear();
m_cpVarMap.clear();
m_cpBins.clear();
m_droppedCrosses.clear();
// Scan every cross item to record the coverpoints it references (the cross dimensions)
@@ -845,6 +858,11 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Emit a 'this->m_cp.addSingleNamer/addArrayNamer(...)' statement for one bin
AstCStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count) {
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;
AstCStmt* const cs = new AstCStmt{fl};
cs->add(memberRef(fl, cpVarp));
// Under --protect-ids the filename and bin name flow into the coverage database
@@ -936,6 +954,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
m_covergroupp->addMembersp(cpVarp);
m_cpVars.push_back(cpVarp);
m_cpVarMap[coverpointp->name()] = cpVarp;
m_cpBins.emplace(cpVarp, CoverpointBins{});
// A cross reads this coverpoint's hit list, so clear it at the start of the
// coverpoint's sample() contribution (before any incrementBin appends to it).
@@ -1304,12 +1323,27 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Append a "{ VlCoverpoint* __Vcx_cps[] = {&cp0, &cp1, ...}; <member>.<call> }" statement.
AstCStmt* makeCrossCpsCall(FileLine* fl, const std::vector<AstVar*>& cpVars, AstVar* cxVarp,
const std::string& callText) {
const std::string& callText,
const std::vector<AstCoverCrossBin*>& bins = {}) {
AstCStmt* const cs = new AstCStmt{fl};
cs->add("{ VlCoverpoint* __Vcx_cps[] = {");
for (size_t d = 0; d < cpVars.size(); ++d) {
cs->add(d == 0 ? "&" : ", &");
cs->add(memberRef(fl, cpVars[d]));
cs->add("{ ");
if (!bins.empty()) {
cs->add("const bool __Vcx_iffs[] = {");
bool first = true;
for (const AstCoverCrossBin* const binp : bins) {
if (!first) cs->add(", ");
first = false;
cs->add(binp->iffp() ? binp->iffp()->cloneTree(false)
: new AstConst{fl, AstConst::BitTrue{}});
}
cs->add("}; ");
}
cs->add("VlCoverpoint* __Vcx_cps[] = {");
bool first = true;
for (AstVar* const cpVarp : cpVars) {
cs->add(first ? "&" : ", &");
first = false;
cs->add(memberRef(fl, cpVarp));
}
cs->add("}; ");
cs->add(memberRef(fl, cxVarp));
@@ -1318,9 +1352,72 @@ class FunctionalCoverageVisitor final : public VNVisitor {
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;
std::set<std::string> names;
for (AstNode* itemp = crossp->binsp(); itemp; itemp = itemp->nextp()) {
AstCoverCrossBin* const binp = VN_AS(itemp, CoverCrossBin);
if (!names.emplace(binp->name()).second) {
binp->v3error("Duplicate cross bin " << binp->prettyNameQ()
<< " (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).");
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;
}
// 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;
FileLine* const fl = binp->fileline();
const bool prot = v3Global.opt.protectIds();
const std::string name = V3OutFormatter::quoteNameControls(
VIdProtect::protectWordsIf(binp->name(), prot));
const std::string file
= V3OutFormatter::quoteNameControls(VIdProtect::protectIf(fl->filename(), prot));
AstCStmt* const addp = new AstCStmt{fl};
addp->add(memberRef(fl, cxVarp));
addp->add(".addBin(" + std::to_string(dim) + ", " + std::to_string(first) + ", "
+ std::to_string(count) + ", \"" + name + "\", \"" + file + "\", "
+ std::to_string(fl->lineno()) + ", " + std::to_string(fl->firstColumn())
+ ");");
m_constructorp->addStmtsp(addp);
bins.push_back(binp);
}
if (!bins.empty()) {
AstCStmt* const finishp = new AstCStmt{crossp->fileline()};
finishp->add(memberRef(crossp->fileline(), cxVarp));
finishp->add(".finalizeBins();");
m_constructorp->addStmtsp(finishp);
}
return bins;
}
// Route a cross through a VlCoverCross member: emit the member, its constructor init +
// registration, and the sample() call. The feeding coverpoints are already generated
// (their hit lists drive the cross), so only O(1) generated code is needed here.
// (their hit lists drive the cross). Each explicit bin adds one configuration call.
void generateCross(AstCoverCross* crossp) {
FileLine* const fl = crossp->fileline();
UINFO(4, " Generating VlCoverCross member: " << crossp->name());
@@ -1333,11 +1430,13 @@ class FunctionalCoverageVisitor final : public VNVisitor {
// Resolve and unlink the coverpoint refs, in dimension order. Every ref resolves to a
// known coverpoint (a cross with an unresolvable item was dropped earlier).
std::vector<AstVar*> cpVars;
std::map<std::string, uint32_t> dimensions;
for (AstNode* itemp = crossp->itemsp(); itemp;) {
AstNode* const nextp = itemp->nextp();
AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef);
const auto it = m_cpVarMap.find(refp->name());
UASSERT_OBJ(it != m_cpVarMap.end(), crossp, "Cross references an unknown coverpoint");
dimensions.emplace(refp->name(), static_cast<uint32_t>(cpVars.size()));
cpVars.push_back(it->second);
VL_DO_DANGLING(pushDeletep(refp->unlinkFrBack()), refp);
itemp = nextp;
@@ -1364,6 +1463,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
+ VIdProtect::protectIf(fl->filename(), prot) + "\", " + std::to_string(fl->lineno())
+ ", " + std::to_string(fl->firstColumn()) + ");";
m_constructorp->addStmtsp(makeCrossCpsCall(fl, cpVars, cxVarp, initCall));
const std::vector<AstCoverCrossBin*> bins
= generateCrossBins(crossp, cxVarp, cpVars, dimensions);
if (v3Global.opt.coverage()) {
const std::string page
= VIdProtect::protectIf("v_covergroup/" + m_covergroupp->name(), prot);
@@ -1375,7 +1476,9 @@ 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");
AstNodeStmt* const samplep = makeCrossCpsCall(fl, cpVars, cxVarp, ".sample(__Vcx_cps);");
AstNodeStmt* const samplep = makeCrossCpsCall(
fl, cpVars, cxVarp,
bins.empty() ? ".sample(__Vcx_cps);" : ".sample(__Vcx_cps, __Vcx_iffs);", bins);
if (AstNodeExpr* const iffp = crossp->iffp()) {
m_sampleFuncp->addStmtsp(new AstIf{fl, iffp->cloneTree(false), samplep});
} else {
+34 -5
View File
@@ -1415,14 +1415,43 @@ class LinkParseVisitor final : public VNVisitor {
iterateChildren(nodep);
}
void visit(AstCoverBinsof* nodep) override {
cleanFileline(nodep);
AstCoverpointRef* const refp = nodep->pointp();
const AstParseRef* pointp = VN_CAST(refp->exprp(), ParseRef);
const AstParseRef* binp = nullptr;
const AstDot* const dotp = VN_CAST(refp->exprp(), Dot);
if (dotp) {
pointp = VN_CAST(dotp->lhsp(), ParseRef);
binp = VN_CAST(dotp->rhsp(), ParseRef);
}
if (!pointp || (dotp && !binp)) {
nodep->v3warn(COVERIGN, "Unsupported: 'binsof' in coverage select expression");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
// These names belong to the coverage namespace, not the sampled variables.
if (binp) nodep->name(binp->name());
refp->replaceWith(new AstCoverpointRef{pointp->fileline(), pointp->name()});
VL_DO_DANGLING(pushDeletep(refp), refp);
iterateChildren(nodep);
}
void visit(AstCoverCrossBin* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
if (!nodep->selectp()) {
nodep->v3warn(COVERIGN, "Unsupported: explicit coverage cross bins");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
}
void visit(AstCoverCross* nodep) override {
cleanFileline(nodep);
// Distribute the parse-time raw cross_body list (rawBodyp, op3) into the
// typed optionsp slot. The grammar produces AstCgOptionAssign nodes for
// option.* items; convert them to AstCoverOption exactly as visit(AstCoverpoint*)
// does. Other items (functions, unsupported bin selectors) are discarded.
for (AstNode *itemp = nodep->rawBodyp(), *nextp; itemp; itemp = nextp) {
// Move options out of the mixed parse-time body, leaving only cross bins.
for (AstNode *itemp = nodep->binsp(), *nextp; itemp; itemp = nextp) {
nextp = itemp->nextp();
if (VN_IS(itemp, CoverCrossBin)) continue;
itemp->unlinkFrBack();
AstCgOptionAssign* const optp = VN_AS(itemp, CgOptionAssign);
const VCoverOptionType optType = optp->optType();
+5 -1
View File
@@ -2066,7 +2066,11 @@ class WidthVisitor final : public VNVisitor {
userIterateAndNext(nodep->itemsp(), nullptr);
if (nodep->iffp()) iterateCheckBool(nodep, "iff condition", nodep->iffp(), BOTH);
userIterateAndNext(nodep->optionsp(), nullptr);
userIterateAndNext(nodep->rawBodyp(), nullptr);
userIterateAndNext(nodep->binsp(), nullptr);
}
void visit(AstCoverCrossBin* nodep) override {
userIterateAndNext(nodep->selectp(), nullptr);
if (nodep->iffp()) iterateCheckBool(nodep, "iff condition", nodep->iffp(), BOTH);
}
void visit(AstCoverpoint* nodep) override {
// The coverpoint expression is self-determined (IEEE 1800-2023 19.5). Width it
+8 -8
View File
@@ -7323,7 +7323,7 @@ cover_cross<nodep>: // ==IEEE: cover_cross
{
AstCoverCross* const nodep = new AstCoverCross{$<fl>3, *$1,
VN_AS($4, CoverpointRef), $5};
if ($6) nodep->addRawBodyp($6);
if ($6) nodep->addBinsp($6);
$$ = nodep;
}
| yCROSS list_of_cross_items iffE cross_body
@@ -7331,7 +7331,7 @@ cover_cross<nodep>: // ==IEEE: cover_cross
AstCoverCross* const nodep = new AstCoverCross{$<fl>1,
"__cross" + cvtToStr(GRAMMARP->s_typeImpNum++),
VN_AS($2, CoverpointRef), $3};
if ($4) nodep->addRawBodyp($4);
if ($4) nodep->addBinsp($4);
$$ = nodep;
}
;
@@ -7393,9 +7393,9 @@ cross_body_item<nodep>: // ==IEEE: cross_body_item
{ $$ = nullptr; BBCOVERIGN($1->fileline(), "Unsupported: 'function' in coverage cross body"); DEL($1); }
// // IEEE: bins_selection_or_option
| coverage_option ';' { $$ = $1; }
// // IEEE: bins_selection - for now, we ignore explicit cross bins
// // IEEE: bins_selection
| yBINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';'
{ $$ = nullptr; BBCOVERIGN($1, "Unsupported: explicit coverage cross bins"); DEL($4, $5); }
{ $$ = new AstCoverCrossBin{$1, *$2, $4, $5}; }
| yIGNORE_BINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';'
{ $$ = nullptr; BBCOVERIGN($1, "Unsupported: explicit coverage cross bins"); DEL($4, $5); }
| yILLEGAL_BINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';'
@@ -7416,11 +7416,11 @@ select_expression<nodep>: // ==IEEE: select_expression
select_expression_r<nodep>:
// // IEEE: select_condition expanded here
yBINSOF '(' bins_expression ')'
{ $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'binsof' in coverage select expression"); DEL($3); }
{ $$ = 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($7); }
{ $$ = 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); }
| yWITH__PAREN '(' cgexpr ')'
@@ -7450,7 +7450,7 @@ select_expression_r<nodep>:
//UNSUP // Above are all removed, replace with:
;
bins_expression<nodep>: // ==IEEE: bins_expression
bins_expression<nodeExprp>: // ==IEEE: bins_expression
// // "cover_point_identifier" and "variable_identifier" look identical
// IEEE specifies:
// bins_expression ::=
@@ -7459,7 +7459,7 @@ bins_expression<nodep>: // ==IEEE: bins_expression
// Verilator supports hierarchical reference in a place of variable identifier.
// This is an extension based on other simulators.
idDotted
{ $$ = nullptr; /*UNSUP*/ DEL($1); }
{ $$ = $1; }
;
coverage_eventE<nodep>: // IEEE: [ coverage_event ]