verilator/src/V3Covergroup.cpp

1857 lines
90 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Functional coverage implementation
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// FUNCTIONAL COVERAGE TRANSFORMATIONS:
// For each covergroup (AstClass with isCovergroup()):
// For each coverpoint (AstCoverpoint):
// Generate member variable for VerilatedCoverpoint
// Generate initialization in constructor
// Generate sample code in sample() method
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Covergroup.h"
#include "V3Const.h"
#include "V3MemberMap.h"
#include <set>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Functional coverage visitor
class FunctionalCoverageVisitor final : public VNVisitor {
// NODE STATE
// Entire netlist:
// AstCoverpoint::user1p() -> AstVar*. Previous-value variable for transition bins
const VNUser1InUse m_inuser1;
// STATE
AstClass* m_covergroupp = nullptr; // Current covergroup being processed
AstFunc* m_sampleFuncp = nullptr; // Current sample() function
AstFunc* m_constructorp = nullptr; // Current constructor
std::vector<AstCoverpoint*> m_coverpoints; // Coverpoints in current covergroup
std::map<std::string, AstCoverpoint*> m_coverpointMap; // Name -> coverpoint for fast lookup
std::vector<AstCoverCross*> m_coverCrosses; // Cross coverage items in current covergroup
// Structure to track bins with their variables and options
struct BinInfo final {
AstCoverBin* binp;
AstVar* varp;
int atLeast; // Minimum hits required for coverage (from option.at_least)
AstCoverpoint* coverpointp; // Associated coverpoint (or nullptr for cross bins)
AstCoverCross* crossp; // Associated cross (or nullptr for coverpoint bins)
string crossBins; // For cross bins: comma-separated individual bin names, in order
BinInfo(AstCoverBin* b, AstVar* v, int al = 1, AstCoverpoint* cp = nullptr,
AstCoverCross* cr = nullptr, const string& cb = "")
: binp{b}
, varp{v}
, atLeast{al}
, coverpointp{cp}
, crossp{cr}
, crossBins{cb} {}
};
std::vector<BinInfo> m_binInfos; // All bins in current covergroup
std::set<std::string> m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy)
std::vector<AstVar*> m_convCpVars; // VlCoverpoint members of converted coverpoints
AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type
VMemberMap m_memberMap; // Member names cached for fast lookup
// METHODS
void clearBinInfos() {
// Delete pseudo-bins created for cross coverage (they're never inserted into the AST)
for (const BinInfo& bi : m_binInfos) {
if (!bi.coverpointp) pushDeletep(bi.binp);
}
m_binInfos.clear();
}
void processCovergroup() {
UINFO(4, "Processing covergroup: " << m_covergroupp->name() << " with "
<< m_coverpoints.size() << " coverpoints and "
<< m_coverCrosses.size() << " crosses");
// Clear bin info for this covergroup (deleting any orphaned cross pseudo-bins)
clearBinInfos();
// Coverpoints referenced by a cross keep the legacy per-bin-member path (the cross
// reads those members); collect their names before they are consumed by the cross.
m_crossedCpNames.clear();
m_convCpVars.clear();
for (AstCoverCross* crossp : m_coverCrosses) {
for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) {
if (const AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef))
m_crossedCpNames.insert(refp->name());
}
}
// For each coverpoint, generate sampling code
for (AstCoverpoint* cpp : m_coverpoints) generateCoverpointCode(cpp);
// For each cross, generate sampling code
for (AstCoverCross* crossp : m_coverCrosses) generateCrossCode(crossp);
// Generate coverage computation code (even for empty covergroups)
generateCoverageComputationCode();
// TODO: Generate instance registry infrastructure for static get_coverage()
// This requires:
// - Static registry members (t_instances, s_mutex)
// - registerInstance() / unregisterInstance() methods
// - Proper C++ emission in EmitC backend
// For now, get_coverage() returns 0.0 (placeholder)
// Generate coverage database registration if coverage is enabled
if (v3Global.opt.coverage()) generateCoverageRegistration();
// Clean up orphaned cross pseudo-bins now that we're done with them
clearBinInfos();
}
static constexpr int COVER_BINS_LIMIT
= 1000; // Sanity limit to avoid hangs from e.g. signed underflow
void expandAutomaticBins(AstCoverpoint* coverpointp, AstNodeExpr* exprp) {
// Find and expand any automatic bins
AstNode* prevBinp = nullptr;
for (AstNode* binp = coverpointp->binsp(); binp;) {
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
AstNode* const nextBinp = binp->nextp();
if (cbinp->binsType() == VCoverBinsType::BINS_AUTO) {
UINFO(4, " Expanding automatic bin: " << cbinp->name());
// Get array size - must be a constant
AstNodeExpr* const sizep = cbinp->arraySizep();
// Evaluate as constant
const AstConst* constp = VN_CAST(sizep, Const);
if (!constp) {
cbinp->v3error("Automatic bins array size must be a constant");
binp = nextBinp;
continue;
}
const int numBins = constp->toSInt();
if (numBins <= 0) {
cbinp->v3error("Automatic bins array size must be >= 1, got " << numBins);
binp = nextBinp;
continue;
}
if (numBins > COVER_BINS_LIMIT) {
cbinp->v3error("Automatic bins array size of "
<< numBins << " exceeds limit of " << COVER_BINS_LIMIT);
binp = nextBinp;
continue;
}
// Calculate range division
const int width = exprp->width();
const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1);
// For width >= 64: (maxVal+1) would overflow; compute binSize without overflow
const uint64_t binSize
= (width < 64) ? ((maxVal + 1) / numBins) : (UINT64_MAX / numBins + 1);
UINFO(4, " Width=" << width << " maxVal=" << maxVal << " numBins=" << numBins
<< " binSize=" << binSize);
// Create expanded bins
for (int i = 0; i < numBins; i++) {
const uint64_t lo = static_cast<uint64_t>(i) * binSize;
const uint64_t hi = (i == numBins - 1) ? maxVal : ((i + 1) * binSize - 1);
// Create constants for range (use setQuad to handle values > 32-bit)
V3Number loNum{cbinp->fileline(), width, 0};
loNum.setQuad(lo);
AstConst* const loConstp = new AstConst{cbinp->fileline(), loNum};
V3Number hiNum{cbinp->fileline(), width, 0};
hiNum.setQuad(hi);
AstConst* const hiConstp = new AstConst{cbinp->fileline(), hiNum};
// Create InsideRange [lo:hi]
AstInsideRange* const rangep
= new AstInsideRange{cbinp->fileline(), loConstp, hiConstp};
rangep->dtypeFrom(exprp); // Set dtype from coverpoint expression
// Create new bin
const string binName = cbinp->name() + "[" + std::to_string(i) + "]";
AstCoverBin* const newBinp
= new AstCoverBin{cbinp->fileline(), binName, rangep, false, false};
// Insert after previous bin
if (prevBinp) {
prevBinp->addNext(newBinp);
} else {
coverpointp->addBinsp(newBinp);
}
prevBinp = newBinp;
}
// Remove the AUTO bin from the list
VL_DO_DANGLING(pushDeletep(binp->unlinkFrBack()), binp);
} else {
prevBinp = binp;
}
binp = nextBinp;
}
}
// Extract all coverpoint option values in a single pass.
// atLeastOut: option.at_least (default 1)
// autoBinMaxOut: option.auto_bin_max (coverpoint overrides covergroup, default 64)
void extractCoverpointOptions(AstCoverpoint* coverpointp, int& atLeastOut,
int& autoBinMaxOut) {
atLeastOut = 1;
autoBinMaxOut = -1; // -1 = not set at coverpoint level
for (AstNode* optionp = coverpointp->optionsp(); optionp; optionp = optionp->nextp()) {
AstCoverOption* const optp = VN_AS(optionp, CoverOption);
AstConst* const constp = VN_CAST(optp->valuep(), Const);
if (!constp) {
optp->valuep()->v3warn(COVERIGN, "Ignoring unsupported: non-constant 'option."
<< optp->optionType().ascii()
<< "'; using default value");
continue;
}
if (optp->optionType() == VCoverOptionType::AT_LEAST) {
atLeastOut = constp->toSInt();
} else {
// V3LinkParse only converts at_least/auto_bin_max coverpoint options into
// AstCoverOption (others are dropped there), so this is the only alternative.
UASSERT_OBJ(optp->optionType() == VCoverOptionType::AUTO_BIN_MAX, optp,
"Unexpected coverpoint option type reaching V3Covergroup");
autoBinMaxOut = constp->toSInt();
}
}
// Fall back to covergroup-level auto_bin_max if not set at coverpoint level
if (autoBinMaxOut < 0) {
if (m_covergroupp->cgAutoBinMax() >= 0) {
autoBinMaxOut = m_covergroupp->cgAutoBinMax();
} else {
autoBinMaxOut = 64; // Default per IEEE 1800-2023 Table 19-1
}
}
}
// Extract individual values from a range expression list, used only to carve values
// out of implicit auto-bins. Iterates over all siblings (nextp) in the list, handling
// AstConst (single value) and AstInsideRange ([lo:hi]); an open-ended bound ('$',
// AstUnbounded) resolves to the coverpoint domain min (lower) or max (upper, == maxVal).
void extractValuesFromRange(AstNode* nodep, std::set<uint64_t>& values, uint64_t maxVal) {
// Cap enumeration so a '$'-bounded or otherwise huge range cannot blow up memory;
// auto-bins are per-value only for small domains, so a partial set is harmless here.
constexpr size_t maxEnumerate = 1ULL << 16;
for (AstNode* np = nodep; np; np = np->nextp()) {
if (AstConst* constp = VN_CAST(np, Const)) {
if (constp->num().isFourState())
continue; // wildcard patterns can't be enumerated
values.insert(constp->toUQuad());
} else if (AstInsideRange* rangep = VN_CAST(np, InsideRange)) {
AstNodeExpr* const lhsp = V3Const::constifyEdit(rangep->lhsp());
AstNodeExpr* const rhsp = V3Const::constifyEdit(rangep->rhsp());
const bool loUnbounded = VN_IS(lhsp, Unbounded);
const bool hiUnbounded = VN_IS(rhsp, Unbounded);
AstConst* const loConstp = VN_CAST(lhsp, Const);
AstConst* const hiConstp = VN_CAST(rhsp, Const);
if ((!loConstp && !loUnbounded) || (!hiConstp && !hiUnbounded)) {
rangep->v3error("Non-constant expression in bin range; "
"range bounds must be constants");
continue;
}
if ((loConstp && loConstp->num().isFourState())
|| (hiConstp && hiConstp->num().isFourState()))
continue;
const uint64_t lo = loUnbounded ? 0 : loConstp->toUQuad();
const uint64_t hi = hiUnbounded ? maxVal : hiConstp->toUQuad();
for (uint64_t v = lo; v <= hi; v++) {
if (values.size() >= maxEnumerate) break;
values.insert(v);
}
} else {
np->v3error("Non-constant expression in bin value list; values must be constants");
}
}
}
// Single-pass categorization: determine whether any regular (non-ignore/illegal) bins exist
// and collect the set of excluded values from ignore/illegal bins.
void categorizeBins(AstCoverpoint* coverpointp, bool& hasRegularOut,
std::set<uint64_t>& excludedOut, uint64_t maxVal) {
hasRegularOut = false;
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
const VCoverBinsType btype = cbinp->binsType();
if (btype == VCoverBinsType::BINS_IGNORE || btype == VCoverBinsType::BINS_ILLEGAL) {
if (AstNode* rangep = cbinp->rangesp()) {
extractValuesFromRange(rangep, excludedOut, maxVal);
}
} else {
hasRegularOut = true;
}
}
}
// Create implicit automatic bins when coverpoint has no explicit regular bins
void createImplicitAutoBins(AstCoverpoint* coverpointp, AstNodeExpr* exprp, int autoBinMax) {
const int width = exprp->width();
const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1);
// Single pass: check for regular bins and collect excluded values simultaneously.
// maxVal resolves any '$' (open-ended) bound in ignore_bins/illegal_bins ranges.
bool hasRegular = false;
std::set<uint64_t> excluded;
categorizeBins(coverpointp, hasRegular, excluded, maxVal);
// If already has regular bins, nothing to do
if (hasRegular) return;
UINFO(4, " Creating implicit automatic bins for coverpoint: " << coverpointp->name());
const uint64_t numTotalValues = (width >= 64) ? UINT64_MAX : (1ULL << width);
const uint64_t numValidValues = numTotalValues - excluded.size();
// Determine number of bins to create (based on non-excluded values)
int numBins;
if (numValidValues <= static_cast<uint64_t>(autoBinMax)) {
// Create one bin per valid value
numBins = numValidValues;
} else {
// Create autoBinMax bins, dividing range
numBins = autoBinMax;
}
UINFO(4, " Width=" << width << " numTotalValues=" << numTotalValues
<< " numValidValues=" << numValidValues << " autoBinMax="
<< autoBinMax << " creating " << numBins << " bins");
// Strategy: Create bins for each value (if numValidValues <= autoBinMax)
// or create range bins that avoid excluded values
if (numValidValues <= static_cast<uint64_t>(autoBinMax)) {
// Create one bin per valid value
int binCount = 0;
for (uint64_t v = 0; v <= maxVal && binCount < numBins; v++) {
// Skip excluded values
if (excluded.find(v) != excluded.end()) continue;
// Create single-value bin
AstConst* const valConstp = new AstConst{
coverpointp->fileline(), V3Number(coverpointp->fileline(), width, v)};
AstConst* const valConstp2 = new AstConst{
coverpointp->fileline(), V3Number(coverpointp->fileline(), width, v)};
AstInsideRange* const rangep
= new AstInsideRange{coverpointp->fileline(), valConstp, valConstp2};
rangep->dtypeFrom(exprp);
const string binName = "auto_" + std::to_string(binCount);
AstCoverBin* const newBinp
= new AstCoverBin{coverpointp->fileline(), binName, rangep, false, false};
coverpointp->addBinsp(newBinp);
binCount++;
}
UINFO(4, " Created " << binCount << " single-value automatic bins");
} else {
// Create range bins (more complex - need to handle excluded values in ranges)
// For simplicity, create bins and let excluded values not match any bin
const uint64_t binSize = (maxVal + 1) / numBins;
for (int i = 0; i < numBins; i++) {
const uint64_t lo = i * binSize;
const uint64_t hi = (i == numBins - 1) ? maxVal : ((i + 1) * binSize - 1);
// Create constants for range
AstConst* const loConstp = new AstConst{
coverpointp->fileline(), V3Number(coverpointp->fileline(), width, lo)};
AstConst* const hiConstp = new AstConst{
coverpointp->fileline(), V3Number(coverpointp->fileline(), width, hi)};
// Create InsideRange [lo:hi]
AstInsideRange* const rangep
= new AstInsideRange{coverpointp->fileline(), loConstp, hiConstp};
rangep->dtypeFrom(exprp);
// Create bin name
const string binName = "auto_" + std::to_string(i);
AstCoverBin* const newBinp
= new AstCoverBin{coverpointp->fileline(), binName, rangep, false, false};
// Add to coverpoint
coverpointp->addBinsp(newBinp);
}
UINFO(4, " Created range-based automatic bins");
}
}
// Sanitize generated names to be valid C++ identifiers
static string sanitizeGeneratedName(string name) {
std::replace(name.begin(), name.end(), '[', '_');
std::replace(name.begin(), name.end(), ']', '_');
return name;
}
AstVar* createCoverageCounterVar(FileLine* fl, const string& varName, AstNodeDType* dtypep) {
AstVar* const varp = new AstVar{fl, VVarType::MEMBER, varName, dtypep};
varp->isStatic(false);
varp->valuep(new AstConst{fl, AstConst::WidthedValue{}, 32, 0});
m_covergroupp->addMembersp(varp);
return varp;
}
AstVar* createTrackedCoverpointBinCounter(AstCoverpoint* coverpointp, AstCoverBin* binp,
const string& generatedBinName, int atLeastValue,
const string& logPrefix,
const string& logSuffix = "") {
const string varName = "__Vcov_" + coverpointp->name() + "_" + generatedBinName;
AstVar* const varp
= createCoverageCounterVar(binp->fileline(), varName, binp->findUInt32DType());
UINFO(4, " " << logPrefix << ": " << varName << logSuffix);
m_binInfos.push_back(BinInfo(binp, varp, atLeastValue, coverpointp));
return varp;
}
AstNodeExpr* applyCoverpointIffCondition(AstCoverpoint* coverpointp, FileLine* fl,
AstNodeExpr* condp) {
if (AstNodeExpr* const iffp = coverpointp->iffp()) {
UINFO(6, " Adding iff condition");
condp = new AstAnd{fl, iffp->cloneTree(false), condp};
}
return condp;
}
void addCoverpointBinHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* hitVarp,
AstNodeExpr* condp, const string& illegalErrMsg,
const char* assertMsg) {
AstNode* stmtp = makeBinHitIncrement(binp->fileline(), hitVarp);
if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
stmtp = stmtp->addNext(makeIllegalBinAction(binp->fileline(), illegalErrMsg));
}
AstIf* const ifp = new AstIf{
binp->fileline(), applyCoverpointIffCondition(coverpointp, binp->fileline(), condp),
stmtp, nullptr};
UASSERT_OBJ(m_sampleFuncp, binp, assertMsg);
m_sampleFuncp->addStmtsp(ifp);
}
// Create previous value variable for transition tracking
AstVar* createPrevValueVar(AstCoverpoint* coverpointp, AstNodeExpr* exprp) {
// Check if already created
if (AstVar* const prevVarp = VN_CAST(coverpointp->user1p(), Var)) return prevVarp;
// Create variable to store previous sampled value
const string varName = "__Vprev_" + coverpointp->name();
AstVar* prevVarp
= new AstVar{coverpointp->fileline(), VVarType::MEMBER, varName, exprp->dtypep()};
prevVarp->isStatic(false);
m_covergroupp->addMembersp(prevVarp);
UINFO(4, " Created previous value variable: " << varName);
// Initialize to zero in constructor
AstNodeExpr* const initExprp
= new AstConst{prevVarp->fileline(), AstConst::WidthedValue{}, prevVarp->width(), 0};
AstNodeStmt* const initStmtp = new AstAssign{
prevVarp->fileline(), new AstVarRef{prevVarp->fileline(), prevVarp, VAccess::WRITE},
initExprp};
m_constructorp->addStmtsp(initStmtp);
coverpointp->user1p(prevVarp);
return prevVarp;
}
// Create state position variable for multi-value transition bins
// Tracks position in sequence: 0=not started, 1=seen first item, etc.
AstVar* createSequenceStateVar(AstCoverpoint* coverpointp, AstCoverBin* binp) {
// Create variable to track sequence position
const string varName = "__Vseqpos_" + coverpointp->name() + "_" + binp->name();
// Use 8-bit integer for state position (sequences rarely > 255 items)
AstVar* stateVarp
= new AstVar{binp->fileline(), VVarType::MEMBER, varName, VFlagLogicPacked{}, 8};
stateVarp->isStatic(false);
m_covergroupp->addMembersp(stateVarp);
UINFO(4, " Created sequence state variable: " << varName);
// Initialize to 0 (not started) in constructor
AstNodeStmt* const initStmtp = new AstAssign{
stateVarp->fileline(), new AstVarRef{stateVarp->fileline(), stateVarp, VAccess::WRITE},
new AstConst{stateVarp->fileline(), AstConst::WidthedValue{}, 8, 0}};
m_constructorp->addStmtsp(initStmtp);
return stateVarp;
}
void generateCoverpointCode(AstCoverpoint* coverpointp) {
UINFO(4, " Generating code for coverpoint: " << coverpointp->name());
// Get the coverpoint expression
AstNodeExpr* const exprp = coverpointp->exprp();
// Expand automatic bins before processing
expandAutomaticBins(coverpointp, exprp);
// Extract all coverpoint options in a single pass
int atLeastValue;
int autoBinMax;
extractCoverpointOptions(coverpointp, atLeastValue, autoBinMax);
UINFO(6, " Coverpoint at_least = " << atLeastValue << " auto_bin_max = " << autoBinMax);
// Create implicit automatic bins if no regular bins exist
createImplicitAutoBins(coverpointp, exprp, autoBinMax);
// Eligible coverpoints route through the VlCoverpoint runtime; the rest (cross-fed or
// transition-bearing) keep the legacy per-bin-member path below.
if (coverpointConvertible(coverpointp)) {
generateConvertedCoverpoint(coverpointp, exprp, atLeastValue);
return;
}
// Generate member variables and matching code for each bin
// Process in two passes: first non-default bins, then default bins
std::vector<AstCoverBin*> defaultBins;
bool hasTransition = false;
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
// Defer default bins to second pass
if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT) {
defaultBins.push_back(cbinp);
continue;
}
// Handle array bins: create separate bin for each value/transition
if (cbinp->isArray()) {
if (cbinp->transp()) { // transition bin (includes illegal_bins with transitions)
hasTransition = true;
generateTransitionArrayBins(coverpointp, cbinp, exprp, atLeastValue);
} else {
generateArrayBins(coverpointp, cbinp, exprp, atLeastValue);
}
continue;
}
// Create a member variable to track hits for this bin
// Sanitize bin name to make it a valid C++ identifier
const string binName = sanitizeGeneratedName(cbinp->name());
AstVar* const varp = createTrackedCoverpointBinCounter(
coverpointp, cbinp, binName, atLeastValue, "Created member variable",
" type=" + string{cbinp->binsType().ascii()});
// Note: Coverage database registration happens later via VL_COVER_INSERT
// (see generateCoverageDeclarations() method around line 1164)
// Classes use "v_covergroup/" hier prefix vs modules
// Generate bin matching code in sample()
// Handle transition bins specially (includes illegal_bins with transition syntax)
if (cbinp->transp()) {
hasTransition = true;
generateTransitionBinMatchCode(coverpointp, cbinp, exprp, varp);
} else {
generateBinMatchCode(coverpointp, cbinp, exprp, varp);
}
}
// Second pass: Handle default bins
// Default bin matches when value doesn't match any other explicit bin
for (AstCoverBin* defBinp : defaultBins) {
// Create member variable for default bin
const string binName = sanitizeGeneratedName(defBinp->name());
AstVar* const varp = createTrackedCoverpointBinCounter(
coverpointp, defBinp, binName, atLeastValue, "Created default bin variable");
// Generate matching code: if (NOT (bin1 OR bin2 OR ... OR binN))
generateDefaultBinMatchCode(coverpointp, defBinp, exprp, varp);
}
// After all bins processed, if coverpoint has transition bins, update previous value
if (hasTransition) {
AstVar* const prevVarp = VN_AS(coverpointp->user1p(), Var);
// Generate: __Vprev_cpname = current_value;
AstNodeStmt* updateStmtp
= new AstAssign{coverpointp->fileline(),
new AstVarRef{prevVarp->fileline(), prevVarp, VAccess::WRITE},
exprp->cloneTree(false)};
m_sampleFuncp->addStmtsp(updateStmtp);
UINFO(4, " Added previous value update at end of sample()");
}
}
void generateBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* binp, AstNodeExpr* exprp,
AstVar* hitVarp) {
UINFO(4, " Generating bin match for: " << binp->name());
// Build the bin matching condition using the shared function
AstNodeExpr* fullCondp = buildBinCondition(binp, exprp);
if (!fullCondp) {
// Reachable: e.g. 'ignore_bins ib = default' creates a BINS_IGNORE bin
// with null rangesp. Skipping match code generation is correct in that case.
return;
}
UINFO(4, " Adding bin match if statement to sample function");
addCoverpointBinHitIf(coverpointp, binp, hitVarp, fullCondp,
"Illegal bin " + binp->prettyNameQ() + " hit in coverpoint "
+ coverpointp->prettyNameQ(),
"sample() CFunc not set when generating bin match code");
UINFO(4, " Successfully added if statement for bin: " << binp->name());
}
// Build the condition under which a default bin matches: NOT(OR of all normal bins).
AstNodeExpr* buildDefaultCondition(AstCoverpoint* coverpointp, AstNodeExpr* exprp,
FileLine* fl) {
AstNodeExpr* anyBinMatchp = nullptr;
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT
|| cbinp->binsType() == VCoverBinsType::BINS_IGNORE
|| cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL)
continue;
AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp);
UASSERT_OBJ(binCondp, cbinp,
"buildBinCondition returned nullptr for non-ignore/non-illegal bin");
anyBinMatchp = anyBinMatchp ? new AstOr{fl, anyBinMatchp, binCondp} : binCondp;
}
return anyBinMatchp ? static_cast<AstNodeExpr*>(new AstNot{fl, anyBinMatchp})
: static_cast<AstNodeExpr*>(new AstConst{fl, AstConst::BitTrue{}});
}
//====================================================================
// VlCoverpoint conversion (eligible coverpoints)
// True if a coverpoint routes through the VlCoverpoint runtime. Cross-fed coverpoints
// (the cross reads their per-bin members) and transition-bearing ones stay legacy.
bool coverpointConvertible(AstCoverpoint* coverpointp) {
if (m_crossedCpNames.count(coverpointp->name())) return false;
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
if (VN_AS(binp, CoverBin)->transp()) return false;
}
return true;
}
// A 'this->m_member' reference for embedding in an AstCStmt
AstVarRef* memberRef(FileLine* fl, AstVar* varp) {
AstVarRef* const refp = new AstVarRef{fl, varp, VAccess::READ};
refp->selfPointer(VSelfPointerText{VSelfPointerText::This{}});
return refp;
}
// Individual equality targets of an array bin (bins b[] = {values/ranges}), in order.
// An open-ended bound ('$', AstUnbounded) resolves to the coverpoint domain: '[lo:$]'
// covers [lo:maxVal] and '[$:hi]' covers [0:hi]. One target is produced per value; a
// range whose resolved size would exceed COVER_BINS_LIMIT (e.g. an open '[lo:$]' over a
// wide coverpoint) is unsupported -- emits COVERIGN, sets unsupportedOut, yields nothing.
std::vector<AstNodeExpr*> extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp,
bool& unsupportedOut) {
unsupportedOut = false;
const int width = exprp->width();
const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1);
std::vector<AstNodeExpr*> values;
for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) {
if (AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) {
AstNodeExpr* const lhsp = V3Const::constifyEdit(irp->lhsp());
AstNodeExpr* const rhsp = V3Const::constifyEdit(irp->rhsp());
const bool loUnb = VN_IS(lhsp, Unbounded);
const bool hiUnb = VN_IS(rhsp, Unbounded);
AstConst* const minp = VN_CAST(lhsp, Const);
AstConst* const maxp = VN_CAST(rhsp, Const);
if ((!minp && !loUnb) || (!maxp && !hiUnb)) {
arrayBinp->v3error("Non-constant expression in array bins range; "
"range bounds must be constants");
return values;
}
if ((minp && minp->num().isFourState()) || (maxp && maxp->num().isFourState())) {
arrayBinp->v3error("Four-state (x/z) value in array bins range bound; "
"range bounds must be two-state constants");
return values;
}
const uint64_t lo = loUnb ? 0 : minp->toUQuad();
const uint64_t hi = hiUnb ? maxVal : maxp->toUQuad();
if (hi < lo) continue; // empty range contributes no bins
// Guard against a '$'-bounded or otherwise huge range exploding the bin count.
const uint64_t span = hi - lo; // == valueCount - 1 (no overflow: hi >= lo)
if (span >= static_cast<uint64_t>(COVER_BINS_LIMIT)
|| values.size() + span + 1 > static_cast<uint64_t>(COVER_BINS_LIMIT)) {
arrayBinp->v3warn(COVERIGN, "Unsupported: array 'bins' covering more than "
<< COVER_BINS_LIMIT
<< " values (e.g. an open '[lo:$]' range over "
"a wide coverpoint); bin ignored");
unsupportedOut = true;
for (AstNodeExpr* const vp : values) VL_DO_DANGLING(pushDeletep(vp), vp);
values.clear();
return values;
}
for (uint64_t v = lo; v <= hi; ++v)
values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{}, width,
static_cast<uint32_t>(v)});
} else {
values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr));
}
}
return values;
}
// Emit a 'this->m_cp.addSingleNamer/addArrayNamer(...)' statement for one bin
AstCStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count) {
FileLine* const fl = binp->fileline();
AstCStmt* const cs = new AstCStmt{fl};
cs->add(memberRef(fl, cpVarp));
const std::string loc = "\"" + std::string{fl->filename()} + "\", "
+ std::to_string(fl->lineno()) + ", "
+ std::to_string(fl->firstColumn()) + ");";
if (count < 0) { // single bin
cs->add(".addSingleNamer(" + std::string{binp->binsType().binSetEnum()} + ", \""
+ binp->name() + "\", " + loc);
} else { // value array bin
cs->add(".addArrayNamer(" + std::string{binp->binsType().binSetEnum()} + ", "
+ std::to_string(count) + ", \"" + binp->name() + "\", " + loc);
}
return cs;
}
// Emit 'if (iff && cond) m_cp.incrementBin(idx);' (or recordHit, + illegal action) in sample()
void emitConvHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, int idx,
AstNodeExpr* condp) {
FileLine* const fl = binp->fileline();
AstCStmt* const hitp = new AstCStmt{fl};
hitp->add(memberRef(fl, cpVarp));
hitp->add((binp->binsType().binIsNormal() ? ".incrementBin(" : ".recordHit(")
+ std::to_string(idx) + ");");
AstNode* actionp = hitp;
if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
actionp->addNext(makeIllegalBinAction(fl, "Illegal bin " + binp->prettyNameQ()
+ " hit in coverpoint "
+ coverpointp->prettyNameQ()));
}
AstNodeExpr* const guardedp = applyCoverpointIffCondition(coverpointp, fl, condp);
UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set in converted coverpoint");
m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr});
}
// Route an eligible coverpoint through a VlCoverpoint member: emit the member, its
// sample() increments, the constructor configuration (init + namers), and registration.
void generateConvertedCoverpoint(AstCoverpoint* coverpointp, AstNodeExpr* exprp,
int atLeastValue) {
FileLine* const fl = coverpointp->fileline();
UINFO(4, " Converting coverpoint to VlCoverpoint: " << coverpointp->name());
if (!m_vlCoverpointDTypep) {
m_vlCoverpointDTypep = new AstCDType{fl, "VlCoverpoint"};
v3Global.rootp()->typeTablep()->addTypesp(m_vlCoverpointDTypep);
}
AstVar* const cpVarp = new AstVar{fl, VVarType::MEMBER, "__Vcp_" + coverpointp->name(),
m_vlCoverpointDTypep};
cpVarp->isStatic(false);
m_covergroupp->addMembersp(cpVarp);
m_convCpVars.push_back(cpVarp);
// Walk bins (non-default, then default), assigning sequential indices that match the
// namer append order; emit sample increments and collect namer statements.
std::vector<AstCStmt*> namerStmts;
std::vector<AstCoverBin*> defaultBins;
int idx = 0;
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT) {
defaultBins.push_back(cbinp);
continue;
}
if (cbinp->isArray()) { // value array: bins b[N] = {...} -> b[0]..b[N-1]
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())));
for (AstNodeExpr* valuep : values) {
emitConvHitIf(coverpointp, cbinp, cpVarp, idx++,
new AstEq{cbinp->fileline(), exprp->cloneTree(false), valuep});
}
} else {
namerStmts.push_back(makeNamer(cpVarp, cbinp, -1));
// buildBinCondition is null for 'ignore_bins = default' (no ranges); the bin
// still gets a reserved slot (recorded, never incremented).
if (AstNodeExpr* const condp = buildBinCondition(cbinp, exprp))
emitConvHitIf(coverpointp, cbinp, cpVarp, idx, condp);
++idx;
}
}
for (AstCoverBin* const defBinp : defaultBins) {
namerStmts.push_back(makeNamer(cpVarp, defBinp, -1));
emitConvHitIf(coverpointp, defBinp, cpVarp, idx++,
buildDefaultCondition(coverpointp, exprp, defBinp->fileline()));
}
// Constructor: init (allocates), namers, then registration (under --coverage)
const std::string hier = m_covergroupp->name() + "." + coverpointp->name();
AstCStmt* const initp = new AstCStmt{fl};
initp->add(memberRef(fl, cpVarp));
initp->add(".init(\"" + hier + "\", " + std::to_string(atLeastValue) + ", "
+ std::to_string(idx) + ");");
m_constructorp->addStmtsp(initp);
for (AstCStmt* const ns : namerStmts) m_constructorp->addStmtsp(ns);
if (v3Global.opt.coverage()) {
AstCStmt* const regp = new AstCStmt{fl};
regp->add(memberRef(fl, cpVarp));
regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"v_covergroup/"
+ m_covergroupp->name() + "\");");
m_constructorp->addStmtsp(regp);
}
}
// Generate matching code for default bins
// Default bins match when value doesn't match any other explicit bin
void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp,
AstNodeExpr* exprp, AstVar* hitVarp) {
UINFO(4, " Generating default bin match for: " << defBinp->name());
AstNodeExpr* defaultCondp = buildDefaultCondition(coverpointp, exprp, defBinp->fileline());
// Apply iff condition if present
if (AstNodeExpr* iffp = coverpointp->iffp()) {
defaultCondp = new AstAnd{defBinp->fileline(), iffp->cloneTree(false), defaultCondp};
}
// Create increment statement
AstNode* const stmtp = makeBinHitIncrement(defBinp->fileline(), hitVarp);
// Create if statement
AstIf* const ifp = new AstIf{defBinp->fileline(), defaultCondp, stmtp, nullptr};
UASSERT_OBJ(m_sampleFuncp, defBinp,
"sample() CFunc not set when generating default bin code");
m_sampleFuncp->addStmtsp(ifp);
UINFO(4, " Successfully added default bin if statement");
}
// Generate matching code for transition bins
// Transition bins match sequences like: (val1 => val2 => val3)
void generateTransitionBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* binp,
AstNodeExpr* exprp, AstVar* hitVarp) {
UINFO(4, " Generating transition bin match for: " << binp->name());
// Get the (single) transition set
AstCoverTransSet* const transSetp = binp->transp();
// Use the helper function to generate code for this transition
generateSingleTransitionCode(coverpointp, binp, exprp, hitVarp, transSetp);
}
// Generate state machine code for multi-value transition sequences
// Handles transitions like (1 => 2 => 3 => 4)
void generateMultiValueTransitionCode(AstCoverpoint* coverpointp, AstCoverBin* binp,
AstNodeExpr* exprp, AstVar* hitVarp,
const std::vector<AstCoverTransItem*>& items) {
UINFO(4, " Generating multi-value transition state machine for: " << binp->name());
UINFO(4, " Sequence length: " << items.size() << " items");
// Create state position variable
AstVar* const stateVarp = createSequenceStateVar(coverpointp, binp);
// Build case statement with N cases (one for each state 0 to N-1)
// State 0: Not started, looking for first item
// State 1 to N-1: In progress, looking for next item
AstCase* const casep
= new AstCase{binp->fileline(), VCaseType::CT_CASE,
new AstVarRef{stateVarp->fileline(), stateVarp, VAccess::READ}, nullptr};
// Generate each case item in the switch statement
for (size_t state = 0; state < items.size(); ++state) {
AstCaseItem* caseItemp = generateTransitionStateCase(coverpointp, binp, exprp, hitVarp,
stateVarp, items, state);
casep->addItemsp(caseItemp);
}
// Add default case (reset to state 0) to prevent CASEINCOMPLETE warnings,
// since the state variable is wider than the number of valid states.
AstCaseItem* const defaultItemp = new AstCaseItem{
binp->fileline(), nullptr,
new AstAssign{binp->fileline(),
new AstVarRef{binp->fileline(), stateVarp, VAccess::WRITE},
new AstConst{binp->fileline(), AstConst::WidthedValue{}, 8, 0}}};
casep->addItemsp(defaultItemp);
m_sampleFuncp->addStmtsp(casep);
UINFO(4, " Successfully added multi-value transition state machine");
}
// Generate code for a single state in the transition state machine
// Returns the case item for this state
AstCaseItem* generateTransitionStateCase(AstCoverpoint* coverpointp, AstCoverBin* binp,
AstNodeExpr* exprp, AstVar* hitVarp,
AstVar* stateVarp,
const std::vector<AstCoverTransItem*>& items,
size_t state) {
FileLine* const fl = binp->fileline();
// Build condition for current value matching expected item at this state
AstNodeExpr* matchCondp = buildTransitionItemCondition(items[state], exprp);
// Apply iff condition if present
if (AstNodeExpr* iffp = coverpointp->iffp()) {
matchCondp = new AstAnd{fl, iffp->cloneTree(false), matchCondp};
}
AstNodeStmt* matchActionp = nullptr;
if (state == items.size() - 1) {
// Last state: sequence complete!
// Increment bin counter
matchActionp = makeBinHitIncrement(fl, hitVarp);
// For illegal_bins, add error message
if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
const string errMsg = "Illegal transition bin " + binp->prettyNameQ()
+ " hit in coverpoint " + coverpointp->prettyNameQ();
matchActionp = matchActionp->addNext(makeIllegalBinAction(fl, errMsg));
}
// Reset state to 0
matchActionp = matchActionp->addNext(
new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 8, 0}});
} else {
// Intermediate state: advance to next state
matchActionp = new AstAssign{
fl, new AstVarRef{fl, stateVarp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 8, static_cast<uint32_t>(state + 1)}};
}
// Build restart logic: check if current value matches first item
// If so, restart sequence from state 1 (even if we're in middle of sequence)
AstNodeStmt* noMatchActionp = nullptr;
if (state > 0) {
// Check if current value matches first item (restart condition)
AstNodeExpr* restartCondp = buildTransitionItemCondition(items[0], exprp);
UASSERT_OBJ(restartCondp, items[0],
"buildTransitionItemCondition returned nullptr for restart");
// Apply iff condition
if (AstNodeExpr* iffp = coverpointp->iffp()) {
restartCondp = new AstAnd{fl, iffp->cloneTree(false), restartCondp};
}
// Restart to state 1
AstNodeStmt* restartActionp
= new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 8, 1}};
// Reset to state 0 (else branch)
AstNodeStmt* resetActionp
= new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 8, 0}};
noMatchActionp = new AstIf{fl, restartCondp, restartActionp, resetActionp};
}
// For state 0, no action needed if no match (stay in state 0)
// Combine into if-else
AstNodeStmt* const stmtp = new AstIf{fl, matchCondp, matchActionp, noMatchActionp};
// Create case item for this state value
AstCaseItem* const caseItemp = new AstCaseItem{
fl, new AstConst{fl, AstConst::WidthedValue{}, 8, static_cast<uint32_t>(state)},
stmtp};
return caseItemp;
}
// Create: $error(msg); $stop; Used when an illegal bin is hit.
AstNodeStmt* makeIllegalBinAction(FileLine* fl, const string& errMsg) {
AstDisplay* const errorp
= new AstDisplay{fl, VDisplayType::DT_ERROR, errMsg, nullptr, nullptr};
errorp->fmtp()->timeunit(m_covergroupp->timeunit());
static_cast<AstNode*>(errorp)->addNext(new AstStop{fl, true});
return errorp;
}
// Create: hitVarp = hitVarp + 1
AstAssign* makeBinHitIncrement(FileLine* fl, AstVar* hitVarp) {
return new AstAssign{fl, new AstVarRef{fl, hitVarp, VAccess::WRITE},
new AstAdd{fl, new AstVarRef{fl, hitVarp, VAccess::READ},
new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}};
}
// Clone a constant node, widening to targetWidth if needed (zero-extend).
// Used to ensure comparisons use matching widths after V3Width has run.
static AstConst* widenConst(FileLine* fl, AstConst* constp, int targetWidth) {
if (constp->width() == targetWidth) return constp->cloneTree(false);
V3Number num{fl, targetWidth, 0};
num.opAssign(constp->num());
return new AstConst{fl, num};
}
// Build a range condition: minp <= exprp <= maxp.
// Uses signed comparisons if exprp is signed; omits trivially-true bounds for unsigned.
// All arguments are non-owning; clones exprp/minp/maxp as needed.
AstNodeExpr* makeRangeCondition(FileLine* fl, AstNodeExpr* exprp, AstNodeExpr* minp,
AstNodeExpr* maxp) {
const int exprWidth = exprp->widthMin();
AstConst* const minConstp = VN_AS(minp, Const);
AstConst* const maxConstp = VN_AS(maxp, Const);
// Widen constants to match expression width so post-V3Width nodes use correct macros
AstConst* const minWidep = widenConst(fl, minConstp, exprWidth);
AstConst* const maxWidep = widenConst(fl, maxConstp, exprWidth);
if (exprp->isSigned()) {
return new AstAnd{fl, new AstGteS{fl, exprp->cloneTree(false), minWidep},
new AstLteS{fl, exprp->cloneTree(false), maxWidep}};
}
// Unsigned: skip bounds that are trivially satisfied for the expression width
const bool skipLowerCheck = (minConstp->toUQuad() == 0);
bool skipUpperCheck = false;
if (exprWidth <= 64) {
const uint64_t maxVal
= (exprWidth == 64) ? ~static_cast<uint64_t>(0) : ((1ULL << exprWidth) - 1ULL);
skipUpperCheck = (maxConstp->toUQuad() == maxVal);
}
if (skipLowerCheck && skipUpperCheck) {
VL_DO_DANGLING(pushDeletep(minWidep), minWidep);
VL_DO_DANGLING(pushDeletep(maxWidep), maxWidep);
return new AstConst{fl, AstConst::BitTrue{}};
} else if (skipLowerCheck) {
VL_DO_DANGLING(pushDeletep(minWidep), minWidep);
return new AstLte{fl, exprp->cloneTree(false), maxWidep};
} else if (skipUpperCheck) {
VL_DO_DANGLING(pushDeletep(maxWidep), maxWidep);
return new AstGte{fl, exprp->cloneTree(false), minWidep};
} else {
return new AstAnd{fl, new AstGte{fl, exprp->cloneTree(false), minWidep},
new AstLte{fl, exprp->cloneTree(false), maxWidep}};
}
}
// Build a one-sided comparison for an open-ended bin range whose other bound is '$'.
// '$' denotes the coverpoint domain extreme, so {[lo:$]} == (expr >= lo) and
// {[$:hi]} == (expr <= hi).
AstNodeExpr* makeOpenRangeCondition(FileLine* fl, AstNodeExpr* exprp, AstConst* boundp,
bool isLowerBound) {
AstConst* const widep = widenConst(fl, boundp, exprp->widthMin());
if (isLowerBound) {
if (exprp->isSigned()) return new AstGteS{fl, exprp->cloneTree(false), widep};
return new AstGte{fl, exprp->cloneTree(false), widep};
}
if (exprp->isSigned()) return new AstLteS{fl, exprp->cloneTree(false), widep};
return new AstLte{fl, exprp->cloneTree(false), widep};
}
// Build condition for a single transition item.
// Returns expression that checks if exprp matches the item's value/range list.
// Overload for when the expression is a variable read -- creates and manages the VarRef
// internally, so callers don't need to construct a temporary node.
AstNodeExpr* buildTransitionItemCondition(AstCoverTransItem* itemp, AstVar* varp) {
AstNodeExpr* varRefp = new AstVarRef{varp->fileline(), varp, VAccess::READ};
AstNodeExpr* const condp = buildTransitionItemCondition(itemp, varRefp);
VL_DO_DANGLING(pushDeletep(varRefp), varRefp);
return condp;
}
// Non-owning: exprp is cloned internally; caller retains ownership of exprp.
AstNodeExpr* buildTransitionItemCondition(AstCoverTransItem* itemp, AstNodeExpr* exprp) {
AstNodeExpr* condp = nullptr;
for (AstNode* valp = itemp->valuesp(); valp; valp = valp->nextp()) {
AstNodeExpr* singleCondp = nullptr;
AstConst* const constp = VN_AS(valp, Const);
singleCondp
= new AstEq{constp->fileline(), exprp->cloneTree(false), constp->cloneTree(false)};
if (condp) {
condp = new AstOr{itemp->fileline(), condp, singleCondp};
} else {
condp = singleCondp;
}
}
return condp;
}
// Generate multiple bins for array bins
// Array bins create one bin per value in the range list
void generateArrayBins(AstCoverpoint* coverpointp, AstCoverBin* arrayBinp, AstNodeExpr* exprp,
int atLeastValue) {
UINFO(4, " Generating array bins for: " << arrayBinp->name());
// Extract all values from the range list (resolves '$', caps/ignores huge ranges)
bool unsupported = false;
std::vector<AstNodeExpr*> values = extractArrayValues(arrayBinp, exprp, unsupported);
if (unsupported) return; // bin ignored (COVERIGN emitted)
// Create a separate bin for each value
int index = 0;
for (AstNodeExpr* valuep : values) {
const string sanitizedName = arrayBinp->name() + "_" + std::to_string(index);
AstVar* const varp = createTrackedCoverpointBinCounter(
coverpointp, arrayBinp, sanitizedName, atLeastValue,
"Created array bin [" + std::to_string(index) + "]");
// Generate matching code for this specific value
generateArrayBinMatchCode(coverpointp, arrayBinp, exprp, varp, valuep);
++index;
}
UINFO(4, " Generated " << index << " array bins");
}
// Generate matching code for a single array bin element
void generateArrayBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* binp,
AstNodeExpr* exprp, AstVar* hitVarp, AstNodeExpr* valuep) {
// Create condition: expr == value
AstNodeExpr* condp = new AstEq{binp->fileline(), exprp->cloneTree(false), valuep};
addCoverpointBinHitIf(coverpointp, binp, hitVarp, condp,
"Illegal bin " + binp->prettyNameQ() + " hit in coverpoint "
+ coverpointp->prettyNameQ(),
"sample() CFunc not set when generating array bin code");
}
// Generate multiple bins for transition array bins
// Array bins with transitions create one bin per transition sequence
void generateTransitionArrayBins(AstCoverpoint* coverpointp, AstCoverBin* arrayBinp,
AstNodeExpr* exprp, int atLeastValue) {
UINFO(4, " Generating transition array bins for: " << arrayBinp->name());
// Extract all transition sets
std::vector<AstCoverTransSet*> transSets;
for (AstNode* transSetp = arrayBinp->transp(); transSetp; transSetp = transSetp->nextp())
transSets.push_back(VN_AS(transSetp, CoverTransSet));
UINFO(4, " Found " << transSets.size() << " transition sets");
int index = 0;
for (AstCoverTransSet* transSetp : transSets) {
const string sanitizedName = arrayBinp->name() + "_" + std::to_string(index);
AstVar* const varp = createTrackedCoverpointBinCounter(
coverpointp, arrayBinp, sanitizedName, atLeastValue,
"Created transition array bin [" + std::to_string(index) + "]");
// Generate matching code for this specific transition
generateSingleTransitionCode(coverpointp, arrayBinp, exprp, varp, transSetp);
++index;
}
UINFO(4, " Generated " << index << " transition array bins");
}
// Generate code for a single transition sequence (used by both regular and array bins)
void generateSingleTransitionCode(AstCoverpoint* coverpointp, AstCoverBin* binp,
AstNodeExpr* exprp, AstVar* hitVarp,
AstCoverTransSet* transSetp) {
UINFO(4, " Generating code for transition sequence");
// Get or create previous value variable
AstVar* const prevVarp = createPrevValueVar(coverpointp, exprp);
UASSERT_OBJ(
transSetp, binp,
"Transition bin has no transition set (transp() was checked before calling this)");
// Get transition items (the sequence: item1 => item2 => item3)
std::vector<AstCoverTransItem*> items;
for (AstNode* itemp = transSetp->itemsp(); itemp; itemp = itemp->nextp())
items.push_back(VN_AS(itemp, CoverTransItem));
if (items.empty()) {
binp->v3error("Transition set without items");
return;
}
if (items.size() == 1) {
// Single item transition not valid (need at least 2 values for =>)
binp->v3error("Transition requires at least two values");
return;
} else if (items.size() == 2) {
// Simple two-value transition: (val1 => val2)
// Use optimized direct comparison (no state machine needed)
AstNodeExpr* const cond1p = buildTransitionItemCondition(items[0], prevVarp);
AstNodeExpr* const cond2p = buildTransitionItemCondition(items[1], exprp);
// Combine: prev matches val1 AND current matches val2
AstNodeExpr* fullCondp = new AstAnd{binp->fileline(), cond1p, cond2p};
addCoverpointBinHitIf(coverpointp, binp, hitVarp, fullCondp,
"Illegal transition bin " + binp->prettyNameQ()
+ " hit in coverpoint " + coverpointp->prettyNameQ(),
"sample() CFunc not set when generating transition bin code");
UINFO(4, " Successfully added 2-value transition if statement");
} else {
// Multi-value sequence (a => b => c => ...)
// Use state machine to track position in sequence
generateMultiValueTransitionCode(coverpointp, binp, exprp, hitVarp, items);
}
}
// Recursive helper to generate Cartesian product of cross bins
void generateCrossBinsRecursive(AstCoverCross* crossp,
const std::vector<AstCoverpoint*>& coverpointRefs,
const std::vector<std::vector<AstCoverBin*>>& allCpBins,
std::vector<AstCoverBin*> currentCombination,
size_t dimension) {
if (dimension == allCpBins.size()) {
// Base case: we have a complete combination, generate the cross bin
generateOneCrossBin(crossp, coverpointRefs, currentCombination);
return;
}
// Recursive case: iterate through bins at current dimension
for (AstCoverBin* binp : allCpBins[dimension]) {
currentCombination.push_back(binp);
generateCrossBinsRecursive(crossp, coverpointRefs, allCpBins, currentCombination,
dimension + 1);
currentCombination.pop_back();
}
}
// Generate a single cross bin for a specific combination of bins
void generateOneCrossBin(AstCoverCross* crossp,
const std::vector<AstCoverpoint*>& coverpointRefs,
const std::vector<AstCoverBin*>& bins) {
// Build sanitized name from all bins
string binName;
string varName = "__Vcov_" + crossp->name();
string crossBins; // Comma-separated individual bin names (one per coverpoint dimension)
for (size_t i = 0; i < bins.size(); ++i) {
const string sanitized = sanitizeGeneratedName(bins[i]->name());
if (i > 0) {
binName += "_x_";
varName += "_x_";
crossBins += ",";
}
binName += sanitized;
varName += "_" + sanitized;
crossBins += sanitized;
}
// Create member variable for this cross bin
AstVar* const varp
= createCoverageCounterVar(crossp->fileline(), varName, bins[0]->findUInt32DType());
UINFO(4, " Created cross bin variable: " << varName);
// Track this for coverage computation
AstCoverBin* const pseudoBinp = new AstCoverBin{
crossp->fileline(), binName, static_cast<AstNode*>(nullptr), false, false};
m_binInfos.push_back(BinInfo(pseudoBinp, varp, 1, nullptr, crossp, crossBins));
// Generate matching code: if (bin1 && bin2 && ... && binN) varName++;
generateNWayCrossBinMatchCode(crossp, coverpointRefs, bins, varp);
}
// Generate matching code for N-way cross bin
void generateNWayCrossBinMatchCode(AstCoverCross* crossp,
const std::vector<AstCoverpoint*>& coverpointRefs,
const std::vector<AstCoverBin*>& bins, AstVar* hitVarp) {
UINFO(4, " Generating " << bins.size() << "-way cross bin match");
// Build combined condition by ANDing all bin conditions
AstNodeExpr* fullCondp = nullptr;
for (size_t i = 0; i < bins.size(); ++i) {
AstNodeExpr* const exprp = coverpointRefs[i]->exprp();
AstNodeExpr* const condp = buildBinCondition(bins[i], exprp);
if (fullCondp) {
fullCondp = new AstAnd{crossp->fileline(), fullCondp, condp};
} else {
fullCondp = condp;
}
}
// Generate: if (cond1 && cond2 && ... && condN) { ++varName; }
AstNodeStmt* const incrp = makeBinHitIncrement(crossp->fileline(), hitVarp);
AstIf* const ifp = new AstIf{crossp->fileline(), fullCondp, incrp};
m_sampleFuncp->addStmtsp(ifp);
}
void generateCrossCode(AstCoverCross* crossp) {
UINFO(4, " Generating code for cross: " << crossp->name());
// Resolve coverpoint references and build list
std::vector<AstCoverpoint*> coverpointRefs;
AstNode* itemp = crossp->itemsp();
while (itemp) {
AstNode* const nextp = itemp->nextp();
AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef);
// Find the referenced coverpoint via name map (O(log n) vs O(n) linear scan)
const auto it = m_coverpointMap.find(refp->name());
AstCoverpoint* const foundCpp = (it != m_coverpointMap.end()) ? it->second : nullptr;
if (!foundCpp) {
// Name not found as an explicit coverpoint - it's a direct variable
// reference (implicit coverpoint), which Verilator does not support.
// Warn and drop the whole cross.
refp->v3warn(COVERIGN, "Unsupported: cross of '"
<< refp->prettyName()
<< "' which is not a coverpoint (implicit coverpoint)");
return;
}
coverpointRefs.push_back(foundCpp);
// Delete the reference node - it's no longer needed
VL_DO_DANGLING(pushDeletep(refp->unlinkFrBack()), refp);
itemp = nextp;
} // LCOV_EXCL_BR_LINE
UINFO(4, " Generating " << coverpointRefs.size() << "-way cross");
// Collect bins from all coverpoints (excluding ignore/illegal bins)
std::vector<std::vector<AstCoverBin*>> allCpBins;
for (AstCoverpoint* cpp : coverpointRefs) {
std::vector<AstCoverBin*> cpBins;
for (AstNode* binp = cpp->binsp(); binp; binp = binp->nextp()) {
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
if (cbinp->binsType() == VCoverBinsType::BINS_USER) { cpBins.push_back(cbinp); }
}
UINFO(4, " Found " << cpBins.size() << " bins in " << cpp->name());
allCpBins.push_back(cpBins);
}
// Generate cross bins using Cartesian product
generateCrossBinsRecursive(crossp, coverpointRefs, allCpBins, {}, 0);
}
AstNodeExpr* buildBinCondition(AstCoverBin* binp, AstNodeExpr* exprp) {
// Get the range list from the bin
AstNode* const rangep = binp->rangesp();
if (!rangep) return nullptr;
// Check if this is a wildcard bin
const bool isWildcard = binp->isWildcard();
// Build condition by OR-ing all ranges together
AstNodeExpr* fullCondp = nullptr;
for (AstNode* currRangep = rangep; currRangep; currRangep = currRangep->nextp()) {
AstNodeExpr* rangeCondp = nullptr;
if (AstInsideRange* irp = VN_CAST(currRangep, InsideRange)) {
AstNodeExpr* const minExprp = irp->lhsp();
AstNodeExpr* const maxExprp = irp->rhsp();
AstConst* const minConstp = VN_CAST(minExprp, Const);
AstConst* const maxConstp = VN_CAST(maxExprp, Const);
const bool loUnbounded = VN_IS(minExprp, Unbounded);
const bool hiUnbounded = VN_IS(maxExprp, Unbounded);
if (loUnbounded || hiUnbounded) {
// Open-ended range: '$' is the coverpoint domain min/max, so the
// range reduces to a single inequality (e.g. {[10:$]} -> expr >= 10).
AstConst* const boundp = hiUnbounded ? minConstp : maxConstp;
if (loUnbounded && hiUnbounded) {
rangeCondp = new AstConst{irp->fileline(), AstConst::BitTrue{}};
} else if (!boundp) {
irp->v3error("Non-constant expression in bin range; "
"range bounds must be constants");
return nullptr;
} else if (boundp->num().isFourState()) {
irp->v3error("Four-state (x/z) value in bin range bound; "
"range bounds must be two-state constants");
return nullptr;
} else {
rangeCondp = makeOpenRangeCondition(irp->fileline(), exprp, boundp,
/*isLowerBound=*/hiUnbounded);
}
} else if (!minConstp || !maxConstp) {
irp->v3error("Non-constant expression in bin range; "
"range bounds must be constants");
return nullptr;
} else if (minConstp->num().isFourState() || maxConstp->num().isFourState()) {
irp->v3error("Four-state (x/z) value in bin range bound; "
"range bounds must be two-state constants");
return nullptr;
} else if (minConstp->toUQuad() == maxConstp->toUQuad()) {
// Single value
if (isWildcard) {
rangeCondp = buildWildcardCondition(binp, exprp, minConstp);
} else {
rangeCondp = new AstEq{binp->fileline(), exprp->cloneTree(false),
minExprp->cloneTree(false)};
}
} else {
rangeCondp = makeRangeCondition(irp->fileline(), exprp, minExprp, maxExprp);
}
} else if (AstConst* constp = VN_CAST(currRangep, Const)) {
if (isWildcard) {
rangeCondp = buildWildcardCondition(binp, exprp, constp);
} else {
rangeCondp = new AstEq{binp->fileline(), exprp->cloneTree(false),
constp->cloneTree(false)};
}
} else {
currRangep->v3error(
"Non-constant expression in bin range; values must be constants");
return nullptr;
}
UASSERT_OBJ(rangeCondp, binp, "rangeCondp is null after building range condition");
fullCondp
= fullCondp ? new AstOr{binp->fileline(), fullCondp, rangeCondp} : rangeCondp;
}
return fullCondp;
}
// Build a wildcard condition: (expr & mask) == (value & mask)
// where mask has 1s for defined bits and 0s for wildcard bits
// Non-owning: exprp is cloned internally; caller retains ownership.
AstNodeExpr* buildWildcardCondition(AstCoverBin* binp, AstNodeExpr* exprp, AstConst* constp) {
FileLine* const fl = binp->fileline();
// Extract mask from constant (bits that are not X/Z)
V3Number mask{constp, constp->width()};
V3Number value{constp, constp->width()};
for (int bit = 0; bit < constp->width(); ++bit) {
if (constp->num().bitIs0(bit) || constp->num().bitIs1(bit)) {
mask.setBit(bit, 1);
value.setBit(bit, constp->num().bitIs1(bit) ? 1 : 0);
} else {
mask.setBit(bit, 0);
value.setBit(bit, 0);
}
}
// Generate: (expr & mask) == (value & mask)
AstConst* const maskConstp = new AstConst{fl, mask};
AstConst* const valueConstp = new AstConst{fl, value};
AstNodeExpr* const exprMasked = new AstAnd{fl, exprp->cloneTree(false), maskConstp};
AstNodeExpr* const valueMasked = new AstAnd{fl, valueConstp, maskConstp->cloneTree(false)};
return new AstEq{fl, exprMasked, valueMasked};
}
void generateCoverageComputationCode() {
UINFO(4, " Generating coverage computation code");
// Invalidate cache: addMembersp() calls in generateCoverpointCode/generateCrossCode
// have added new members since the last scan, so clear before re-querying.
m_memberMap.clear();
// Find get_coverage() and get_inst_coverage() methods
AstFunc* const getCoveragep
= VN_CAST(m_memberMap.findMember(m_covergroupp, "get_coverage"), Func);
AstFunc* const getInstCoveragep
= VN_CAST(m_memberMap.findMember(m_covergroupp, "get_inst_coverage"), Func);
// Even if there are no bins, we still need to generate the coverage methods
// Empty covergroups should return 100% coverage
if (m_binInfos.empty()) {
UINFO(4, " No bins found, will generate method to return 100%");
} else {
UINFO(6, " Found " << m_binInfos.size() << " bins for coverage");
}
// Generate code for get_inst_coverage()
generateCoverageMethodBody(getInstCoveragep);
// Generate code for get_coverage() (type-level)
// NOTE: Full type-level coverage requires instance tracking infrastructure
// For now, return 0.0 as a placeholder
AstVar* const coverageReturnVarp = VN_AS(getCoveragep->fvarp(), Var);
// TODO: Implement proper type-level coverage aggregation
// This requires tracking all instances and averaging their coverage
// For now, return 0.0
getCoveragep->addStmtsp(new AstAssign{
getCoveragep->fileline(),
new AstVarRef{getCoveragep->fileline(), coverageReturnVarp, VAccess::WRITE},
new AstConst{getCoveragep->fileline(), AstConst::RealDouble{}, 0.0}});
UINFO(4, " Added placeholder get_coverage() (returns 0.0)");
}
void generateCoverageMethodBody(AstFunc* funcp) {
FileLine* const fl = funcp->fileline();
AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var);
// Converted coverpoints hold their bins in VlCoverpoint. Combine their contributions
// (via coverageParts) with any remaining legacy cross/cross-fed bins as the same flat
// covered/total ratio the all-legacy path below computes. Normal bins only: ignore,
// illegal, and default are excluded (LRM 19.5).
if (!m_convCpVars.empty()) {
AstCStmt* const headp = new AstCStmt{fl};
headp->add("double __Vcov = 0.0; double __Vtot = 0.0;");
funcp->addStmtsp(headp);
for (AstVar* const cpVarp : m_convCpVars) {
AstCStmt* const cs = new AstCStmt{fl};
cs->add("{ double __Vc = 0.0; double __Vt = 0.0; ");
cs->add(memberRef(fl, cpVarp));
cs->add(".coverageParts(__Vc, __Vt); __Vcov += __Vc; __Vtot += __Vt; }");
funcp->addStmtsp(cs);
}
int legacyRegular = 0;
for (const BinInfo& bi : m_binInfos) {
if (!bi.binp->binsType().binIsNormal()) continue;
++legacyRegular;
AstCStmt* const cs = new AstCStmt{fl};
cs->add("if (");
cs->add(memberRef(fl, bi.varp));
cs->add(" >= " + std::to_string(bi.atLeast) + ") __Vcov += 1.0;");
funcp->addStmtsp(cs);
}
if (legacyRegular) {
AstCStmt* const cs = new AstCStmt{fl};
cs->add("__Vtot += " + std::to_string(legacyRegular) + ".0;");
funcp->addStmtsp(cs);
}
AstCStmt* const retp = new AstCStmt{fl};
retp->add(new AstVarRef{fl, returnVarp, VAccess::WRITE});
retp->add(" = (__Vtot != 0.0) ? (100.0 * __Vcov / __Vtot) : 100.0;");
funcp->addStmtsp(retp);
return;
}
// Count total bins (Normal only: excludes ignore/illegal/default)
int totalBins = 0;
for (const BinInfo& bi : m_binInfos) {
UINFO(6, " Bin: " << bi.binp->name() << " type=" << bi.binp->binsType().ascii());
if (bi.binp->binsType().binIsNormal()) totalBins++;
}
UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size());
if (totalBins == 0) {
// No coverage to compute - return 100%.
// Any parser-generated initialization of returnVar is overridden by our assignment.
UINFO(4, " Empty covergroup, returning 100.0");
funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE},
new AstConst{fl, AstConst::RealDouble{}, 100.0}});
UINFO(4, " Added assignment to return 100.0");
return;
}
// Create local variable to count covered bins
AstVar* const coveredCountp
= new AstVar{fl, VVarType::BLOCKTEMP, "__Vcovered_count", funcp->findUInt32DType()};
coveredCountp->funcLocal(true);
funcp->addStmtsp(coveredCountp);
// Initialize: covered_count = 0
funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, coveredCountp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 32, 0}});
// For each regular bin, if count > 0, increment covered_count
for (const BinInfo& bi : m_binInfos) {
// Skip ignore/illegal/default bins in coverage calculation
if (!bi.binp->binsType().binIsNormal()) continue;
// if (bin_count >= at_least) covered_count++;
AstIf* ifp = new AstIf{
fl,
new AstGte{fl, new AstVarRef{fl, bi.varp, VAccess::READ},
new AstConst{fl, AstConst::WidthedValue{}, 32,
static_cast<uint32_t>(bi.atLeast)}},
new AstAssign{fl, new AstVarRef{fl, coveredCountp, VAccess::WRITE},
new AstAdd{fl, new AstVarRef{fl, coveredCountp, VAccess::READ},
new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}},
nullptr};
funcp->addStmtsp(ifp);
}
// Calculate coverage: (covered_count / total_bins) * 100.0
// return_var = (double)covered_count / (double)total_bins * 100.0
// Cast covered_count to real/double
AstNodeExpr* const coveredReal
= new AstIToRD{fl, new AstVarRef{fl, coveredCountp, VAccess::READ}};
// Create total bins as a double constant
AstNodeExpr* const totalReal
= new AstConst{fl, AstConst::RealDouble{}, static_cast<double>(totalBins)};
// Divide using AstDivD (double division that emits native /)
AstNodeExpr* const divExpr = new AstDivD{fl, coveredReal, totalReal};
// Multiply by 100 using AstMulD (double multiplication that emits native *)
AstNodeExpr* const hundredConst = new AstConst{fl, AstConst::RealDouble{}, 100.0};
AstNodeExpr* const coverageExpr = new AstMulD{fl, hundredConst, divExpr};
// Assign to return variable
funcp->addStmtsp(
new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, coverageExpr});
UINFO(6, " Added coverage computation to " << funcp->name() << " with " << totalBins
<< " bins (excluding ignore/illegal)");
}
void generateCoverageRegistration() {
// Generate VL_COVER_INSERT calls for each bin in the covergroup
// This registers the bins with the coverage database so they can be reported
UINFO(4,
" Generating coverage database registration for " << m_binInfos.size() << " bins");
if (m_binInfos.empty()) return;
// For each bin, generate a VL_COVER_INSERT call
// The calls use CCall nodes to invoke VL_COVER_INSERT macro
for (const BinInfo& binInfo : m_binInfos) {
AstVar* const varp = binInfo.varp;
AstCoverBin* const binp = binInfo.binp;
AstCoverpoint* const coverpointp = binInfo.coverpointp;
AstCoverCross* const crossp = binInfo.crossp;
FileLine* const fl = binp->fileline();
// Build hierarchical name: covergroup.coverpoint.bin or covergroup.cross.bin
std::string hierName = m_covergroupp->name();
const std::string binName = binp->name();
if (coverpointp) {
// Coverpoint bin: V3LinkParse guarantees a non-empty name for every
// coverpoint (user label, single-variable name, or synthesized __Vcoverpoint<N>).
UASSERT_OBJ(!coverpointp->name().empty(), coverpointp,
"Coverpoint without a name (should be set in V3LinkParse)");
hierName += "." + coverpointp->name();
} else {
// Cross bin: grammar always provides a name (user label or auto "__crossN")
hierName += "." + crossp->name();
}
hierName += "." + binName;
// Generate: VL_COVER_INSERT(contextp, hier, &binVar, "page", "v_covergroup/...", ...)
UINFO(6, " Registering bin: " << hierName << " -> " << varp->name());
// Build the coverage insert as a C statement mixing literal text with a proper
// AstVarRef for the bin variable. Using AstVarRef (with selfPointer=This) lets
// V3Name apply __PVT__ mangling and the emitter apply nameProtect(), which also
// handles --protect-ids correctly. The vlSymsp->_vm_contextp__ path is the
// established convention used by the existing __vlCoverInsert helper.
// Use "page" field with v_covergroup prefix so the coverage type is identified
// correctly (consistent with code coverage).
const std::string pageName = "v_covergroup/" + m_covergroupp->name();
AstCStmt* const cstmtp = new AstCStmt{fl};
cstmtp->add("VL_COVER_INSERT(vlSymsp->_vm_contextp__->coveragep(), "
"\""
+ hierName + "\", &(");
AstVarRef* const binVarRefp = new AstVarRef{fl, varp, VAccess::READ};
binVarRefp->selfPointer(VSelfPointerText{VSelfPointerText::This{}});
cstmtp->add(binVarRefp);
cstmtp->add("), \"page\", \"" + pageName
+ "\", "
"\"filename\", \""
+ fl->filename()
+ "\", "
"\"lineno\", \""
+ std::to_string(fl->lineno())
+ "\", "
"\"column\", \""
+ std::to_string(fl->firstColumn()) + "\", ");
const std::string crossSuffix
= crossp ? (", \"cross\", \"1\", \"cross_bins\", \"" + binInfo.crossBins + "\"")
: "";
if (binp->binsType() == VCoverBinsType::BINS_IGNORE) {
cstmtp->add("\"bin\", \"" + binName + "\", \"bin_type\", \"ignore\"" + crossSuffix
+ ");");
} else if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
cstmtp->add("\"bin\", \"" + binName + "\", \"bin_type\", \"illegal\"" + crossSuffix
+ ");");
} else {
cstmtp->add("\"bin\", \"" + binName + "\"" + crossSuffix + ");");
}
// Add to constructor
m_constructorp->addStmtsp(cstmtp);
UINFO(6, " Added VL_COVER_INSERT call to constructor");
}
}
// VISITORS
AstNode* findEnclosingMemberRef(AstClass* cgClassp) {
// An embedded covergroup is lowered into a sibling AstClass that has no handle to
// the enclosing object. A coverpoint/iff/cross expression that references a
// (non-static) member of the enclosing class therefore emits C++ that accesses the
// member as if it were static ("invalid use of non-static data member"). Detect
// such references so the caller can skip lowering with a clean warning instead of
// producing uncompilable code. Returns the first offending node, or nullptr.
// Collect the covergroup class's own member variables (sample/constructor args);
// references to those are legitimate.
std::set<const AstVar*> ownVars;
for (AstNode* itemp = cgClassp->membersp(); itemp; itemp = itemp->nextp()) {
if (const AstVar* const varp = VN_CAST(itemp, Var)) ownVars.insert(varp);
}
AstNode* offenderp = nullptr;
const auto scan = [&](AstNode* rootp) {
rootp->foreach([&](AstVarRef* refp) {
if (offenderp) return;
const AstVar* const varp = refp->varp(); // Always set post-LinkDot
// A member of another class (the enclosing class) reached with no handle.
// Members of the covergroup class itself (sample/constructor args) are
// legitimate and excluded via ownVars.
if (varp->isClassMember() && !ownVars.count(varp)) offenderp = refp;
});
};
for (AstCoverpoint* cpp : m_coverpoints) scan(cpp);
for (AstCoverCross* crossp : m_coverCrosses) scan(crossp);
return offenderp;
}
void visit(AstClass* nodep) override {
UINFO(9, "Visiting class: " << nodep->name() << " isCovergroup=" << nodep->isCovergroup());
if (nodep->isCovergroup()) {
VL_RESTORER(m_covergroupp);
VL_RESTORER(m_sampleFuncp);
VL_RESTORER(m_constructorp);
VL_RESTORER(m_coverpoints);
VL_RESTORER(m_coverpointMap);
VL_RESTORER(m_coverCrosses);
m_covergroupp = nodep;
m_sampleFuncp = nullptr;
m_constructorp = nullptr;
m_coverpoints.clear();
m_coverpointMap.clear();
m_coverCrosses.clear();
// Extract and store the clocking event from AstCovergroup node
// The parser creates this node to preserve the event information
bool hasUnsupportedEvent = false;
for (AstNode* itemp = nodep->membersp(); itemp;) {
AstNode* const nextp = itemp->nextp();
if (AstCovergroup* const cgp = VN_CAST(itemp, Covergroup)) {
// Store the event in the global map for V3Active to retrieve later
// V3LinkParse only creates this sentinel AstCovergroup node when a clocking
// event exists, so cgp->eventp() is always non-null here.
UASSERT_OBJ(cgp->eventp(), cgp,
"Sentinel AstCovergroup in class must have non-null eventp");
// Check if the clocking event references a member variable (unsupported)
// Clocking events should be on signals/nets, not class members
bool eventUnsupported = false;
for (AstNode* senp = cgp->eventp()->sensesp(); senp; senp = senp->nextp()) {
AstSenItem* const senItemp = VN_AS(senp, SenItem);
if (AstVarRef* const varrefp // LCOV_EXCL_BR_LINE
= VN_CAST(senItemp->sensp(), VarRef)) {
if (varrefp->varp()->isClassMember()) {
cgp->v3warn(COVERIGN, "Unsupported: 'covergroup' clocking event "
"on member variable");
eventUnsupported = true;
hasUnsupportedEvent = true;
break;
}
}
}
if (!eventUnsupported) {
// Leave cgp in the class membersp so the SenTree stays
// linked in the AST. V3Active will find it via membersp,
// use the event, then delete the AstCovergroup itself.
UINFO(4, "Keeping covergroup event node for V3Active: " << nodep->name());
itemp = nextp;
continue;
}
// Remove the AstCovergroup node - either unsupported event or no event
VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp);
}
itemp = nextp;
}
// If covergroup has unsupported clocking event, skip processing it
// but still clean up coverpoints so they don't reach downstream passes
if (hasUnsupportedEvent) {
iterateChildren(nodep);
for (AstCoverpoint* cpp : m_coverpoints) {
VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp);
}
for (AstCoverCross* crossp : m_coverCrosses) {
VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp);
}
return;
}
// Find the sample() method and constructor
m_sampleFuncp = VN_CAST(m_memberMap.findMember(nodep, "sample"), Func);
// V3LinkParse always synthesizes a sample() method for every covergroup, and the
// sampling-code generation below dereferences m_sampleFuncp unconditionally.
UASSERT_OBJ(m_sampleFuncp, nodep, "Covergroup missing synthesized sample() method");
m_sampleFuncp->isCovergroupSample(true);
m_constructorp = VN_CAST(m_memberMap.findMember(nodep, "new"), Func);
UINFO(9, "Found sample() method: " << (m_sampleFuncp ? "yes" : "no"));
UINFO(9, "Found constructor: " << (m_constructorp ? "yes" : "no"));
iterateChildren(nodep);
// Option B safety net for embedded covergroups: if a coverpoint/iff/cross
// references a member of the enclosing class, lowering would emit uncompilable
// C++ (no handle to the enclosing instance). Skip this covergroup with a clean
// warning rather than crashing the C++ compile. (Full support - an enclosing
// back-pointer - is the planned follow-up.)
if (AstNode* const offenderp = findEnclosingMemberRef(nodep)) {
offenderp->v3warn(COVERIGN,
"Unsupported: 'covergroup' coverpoint referencing enclosing "
"class member; ignoring covergroup "
<< nodep->prettyNameQ());
for (AstCoverpoint* cpp : m_coverpoints) {
VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp);
}
for (AstCoverCross* crossp : m_coverCrosses) {
VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp);
}
return;
}
processCovergroup();
// Remove lowered coverpoints/crosses from the class - they have been
// fully translated into C++ code and must not reach downstream passes
for (AstCoverpoint* cpp : m_coverpoints) {
VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp);
}
for (AstCoverCross* crossp : m_coverCrosses) {
VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp);
}
} else {
iterateChildren(nodep);
}
}
void visit(AstCoverpoint* nodep) override {
UINFO(9, "Found coverpoint: " << nodep->name());
m_coverpoints.push_back(nodep);
m_coverpointMap.emplace(nodep->name(), nodep);
iterateChildren(nodep);
}
void visit(AstCoverCross* nodep) override {
UINFO(9, "Found cross: " << nodep->name());
m_coverCrosses.push_back(nodep);
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit FunctionalCoverageVisitor(AstNetlist* nodep) { iterate(nodep); }
~FunctionalCoverageVisitor() override = default;
};
//######################################################################
// Functional coverage class functions
void V3Covergroup::covergroup(AstNetlist* nodep) {
UINFO(4, __FUNCTION__ << ": ");
{ FunctionalCoverageVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("coveragefunc", 0, dumpTreeEitherLevel() >= 3);
}