Support covergroup runtime model Phase A1 (#7728)
This commit is contained in:
parent
5831cc8d46
commit
e03fa6c783
|
|
@ -0,0 +1,63 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//=============================================================================
|
||||
//
|
||||
// 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: 2024-2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//=============================================================================
|
||||
///
|
||||
/// \file
|
||||
/// \brief Verilated functional-coverage model interfaces
|
||||
///
|
||||
/// Defines interface classes to runtime covergroup coverage-collection classes.
|
||||
/// These are used to query coverage achievement at runtime, and (future)
|
||||
/// when writing coverage to the coverage database.
|
||||
///
|
||||
//=============================================================================
|
||||
|
||||
#ifndef VERILATOR_VERILATED_COV_MODEL_H_
|
||||
#define VERILATOR_VERILATED_COV_MODEL_H_
|
||||
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
// Per-bin classification. A bin's kind is which set it lives in (structural),
|
||||
// not a per-bin field. Only Normal feeds coverage(); the rest are recorded.
|
||||
// Enumerators are 'KIND_'-prefixed because the bare LRM terms collide with
|
||||
// <windows.h> macros (e.g. IGNORE), which the preprocessor would expand.
|
||||
enum class VlCovBinKind : uint8_t {
|
||||
KIND_NORMAL = 0, // Base coverage-collecting bin
|
||||
KIND_DEFAULT = 1, // Bin declared with 'default' range (which is excluded per LRM)
|
||||
KIND_IGNORE = 2, // Ignore bin
|
||||
KIND_ILLEGAL = 3 // Illegal bin
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VlCoverpointIf
|
||||
/// Read-side view of a coverpoint. The writer queries bins by index; the
|
||||
/// implementor computes names/kinds on demand. Bounded bin count, so random
|
||||
/// access by index is the primary usage.
|
||||
|
||||
class VlCoverpointIf VL_NOT_FINAL {
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
virtual ~VlCoverpointIf() = default;
|
||||
|
||||
// METHODS
|
||||
// All bins, across every set; index range [0, binCount())
|
||||
virtual int binCount() const = 0;
|
||||
// Bin name in declaration order (e.g. "myBin" or "b[3]")
|
||||
virtual std::string binName(int i) const = 0;
|
||||
virtual VlCovBinKind binKind(int i) const = 0;
|
||||
// Bins covered / effective total (Normal set only) for the coverage calc
|
||||
virtual void coverageParts(double& covered, double& total) const = 0;
|
||||
};
|
||||
|
||||
#endif // Guard
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//=============================================================================
|
||||
//
|
||||
// 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: 2024-2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//=============================================================================
|
||||
///
|
||||
/// \file
|
||||
/// \brief Verilated functional-coverage collection runtime implementation
|
||||
///
|
||||
/// Compiled and linked when "verilator --coverage" is used with covergroups.
|
||||
///
|
||||
//=============================================================================
|
||||
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "verilated_covergroup.h"
|
||||
|
||||
#include "verilated_cov.h"
|
||||
|
||||
void VlCoverpoint::init(const char* hier, uint32_t atLeast, int nBins) {
|
||||
m_hier = hier;
|
||||
m_atLeast = atLeast;
|
||||
m_total = nBins;
|
||||
m_counts.assign(nBins, 0);
|
||||
}
|
||||
|
||||
void VlCoverpoint::addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name,
|
||||
const char* file, int line, int col) {
|
||||
m_namers.emplace_back(set, count, m_nextBase, naming, name, file, line, col);
|
||||
m_nextBase += count;
|
||||
if (set == VlCovBinKind::KIND_NORMAL) m_normal += count;
|
||||
}
|
||||
|
||||
const VlCovNamer& VlCoverpoint::namerFor(int i) const {
|
||||
// Namers are appended in ascending, contiguous index order covering [0, m_total),
|
||||
// and i is always a valid bin index, so the matching namer always exists.
|
||||
for (const VlCovNamer& nm : m_namers) {
|
||||
if (i < nm.base() + nm.count()) return nm;
|
||||
}
|
||||
VL_UNREACHABLE;
|
||||
}
|
||||
|
||||
std::string VlCoverpoint::binName(int i) const {
|
||||
const VlCovNamer& nm = namerFor(i);
|
||||
std::string name = nm.name();
|
||||
if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(i - nm.base()) + ']';
|
||||
return name;
|
||||
}
|
||||
|
||||
void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) {
|
||||
for (int i = 0; i < binCount(); ++i) {
|
||||
const VlCovNamer& nm = namerFor(i);
|
||||
const VlCovBinKind kind = binKind(i);
|
||||
const std::string binp = binName(i);
|
||||
const std::string full = m_hier + "." + binp;
|
||||
const std::string lineStr = std::to_string(nm.line());
|
||||
const std::string colStr = std::to_string(nm.col());
|
||||
if (kind == VlCovBinKind::KIND_NORMAL) {
|
||||
VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename",
|
||||
nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin",
|
||||
binp.c_str());
|
||||
} else {
|
||||
const char* const binType = kind == VlCovBinKind::KIND_IGNORE ? "ignore"
|
||||
: kind == VlCovBinKind::KIND_ILLEGAL ? "illegal"
|
||||
: "default";
|
||||
VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename",
|
||||
nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin",
|
||||
binp.c_str(), "bin_type", binType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//=============================================================================
|
||||
//
|
||||
// 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: 2024-2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//=============================================================================
|
||||
///
|
||||
/// \file
|
||||
/// \brief Verilated functional-coverage collection runtime
|
||||
///
|
||||
/// VlCoverpoint owns per-instance bin-count storage for one coverpoint,
|
||||
/// computes coverage, builds bin names on demand, and registers bins with the
|
||||
/// coverage database. It implements the VlCoverpointIf read interface.
|
||||
///
|
||||
/// Generated covergroup code holds one VlCoverpoint per coverpoint, configures
|
||||
/// it in the constructor (init + add*Namer), increments bins from sample(),
|
||||
/// and registers via registerBins().
|
||||
///
|
||||
//=============================================================================
|
||||
|
||||
#ifndef VERILATOR_VERILATED_COVERGROUP_H_
|
||||
#define VERILATOR_VERILATED_COVERGROUP_H_
|
||||
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "verilated_cov_model.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class VerilatedCovContext;
|
||||
|
||||
// How a namer builds the names of the bins it covers.
|
||||
enum class VlCovBinNaming : uint8_t {
|
||||
Single, // "<name>" one bin
|
||||
Array, // "<name>[i]" bins b[N] value array
|
||||
};
|
||||
|
||||
// Specifies the naming scheme for a range of bins, allowing the
|
||||
// specific name to be computed on-demand.
|
||||
// All name strings are borrowed literals from the generated code.
|
||||
class VlCovNamer final {
|
||||
// MEMBERS
|
||||
VlCovBinKind m_set; // which set the bins belong to
|
||||
int m_count; // bins this namer covers (1 for Single)
|
||||
int m_base; // first bin index (declaration order), assigned on append
|
||||
VlCovBinNaming m_naming; // how bin names are built
|
||||
const char* m_name; // bin name (Single) or array base name (Array)
|
||||
const char* m_file; // declaration file
|
||||
int m_line; // declaration line
|
||||
int m_col; // declaration column
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
VlCovNamer(VlCovBinKind set, int count, int base, VlCovBinNaming naming, const char* name,
|
||||
const char* file, int line, int col)
|
||||
: m_set{set}
|
||||
, m_count{count}
|
||||
, m_base{base}
|
||||
, m_naming{naming}
|
||||
, m_name{name}
|
||||
, m_file{file}
|
||||
, m_line{line}
|
||||
, m_col{col} {}
|
||||
|
||||
// METHODS
|
||||
VlCovBinKind set() const { return m_set; }
|
||||
int count() const { return m_count; }
|
||||
int base() const { return m_base; }
|
||||
VlCovBinNaming naming() const { return m_naming; }
|
||||
const char* name() const { return m_name; }
|
||||
const char* file() const { return m_file; }
|
||||
int line() const { return m_line; }
|
||||
int col() const { return m_col; }
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VlCoverpoint
|
||||
/// Per-instance coverpoint runtime. Bins are stored in declaration order; a
|
||||
/// bin's set/name come from the owning namer. coverage() is computed on demand
|
||||
/// by scanning bin counts, keeping the sample() hot path a plain counter bump.
|
||||
|
||||
class VlCoverpoint final : public VlCoverpointIf {
|
||||
// MEMBERS
|
||||
std::string m_hier; // "covergroup.coverpoint"
|
||||
uint32_t m_atLeast = 1; // option.at_least (coverpoint-wide)
|
||||
int m_total = 0; // bins across all sets
|
||||
int m_normal = 0; // Normal bins (coverage denominator)
|
||||
int m_nextBase = 0; // running append cursor
|
||||
std::vector<uint32_t> m_counts; // [m_total], one per bin
|
||||
std::vector<VlCovNamer> m_namers; // appended in declaration order
|
||||
|
||||
// PRIVATE METHODS
|
||||
const VlCovNamer& namerFor(int i) const; // obtain the bin-specific name producer
|
||||
void addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name,
|
||||
const char* file, int line, int col);
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
VlCoverpoint() = default;
|
||||
|
||||
// METHODS
|
||||
// ---- configuration (from generated constructor) ----
|
||||
void init(const char* hier, uint32_t atLeast, int nBins);
|
||||
void addSingleNamer(VlCovBinKind set, const char* name, const char* file, int line, int col) {
|
||||
addNamer(set, 1, VlCovBinNaming::Single, name, file, line, col);
|
||||
}
|
||||
void addArrayNamer(VlCovBinKind set, int count, const char* name, const char* file, int line,
|
||||
int col) {
|
||||
addNamer(set, count, VlCovBinNaming::Array, name, file, line, col);
|
||||
}
|
||||
void registerBins(VerilatedCovContext* covcontextp, const char* page);
|
||||
|
||||
// ---- hot path (from generated sample()) ----
|
||||
void incrementBin(int i) { ++m_counts[i]; } // Normal bin: count only
|
||||
void recordHit(int i) { ++m_counts[i]; } // Ignore/Illegal/Default: count only
|
||||
|
||||
// ---- VlCoverpointIf ----
|
||||
int binCount() const override { return m_total; }
|
||||
std::string binName(int i) const override;
|
||||
VlCovBinKind binKind(int i) const override { return namerFor(i).set(); }
|
||||
void coverageParts(double& covered, double& total) const override {
|
||||
// Count Normal bins that reached option.at_least on demand, so the hot
|
||||
// path (incrementBin) stays a plain counter bump.
|
||||
int numCovered = 0;
|
||||
for (const VlCovNamer& nm : m_namers) {
|
||||
if (nm.set() != VlCovBinKind::KIND_NORMAL) continue;
|
||||
for (int i = nm.base(); i < nm.base() + nm.count(); ++i) {
|
||||
if (m_counts[i] >= m_atLeast) ++numCovered;
|
||||
}
|
||||
}
|
||||
covered = numCovered;
|
||||
total = m_normal;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // Guard
|
||||
|
|
@ -1191,6 +1191,19 @@ public:
|
|||
= {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"};
|
||||
return names[m_e];
|
||||
}
|
||||
// VlCovBinKind enumerator naming the bin's set
|
||||
const char* binSetEnum() const {
|
||||
switch (m_e) {
|
||||
case BINS_IGNORE: return "VlCovBinKind::KIND_IGNORE";
|
||||
case BINS_ILLEGAL: return "VlCovBinKind::KIND_ILLEGAL";
|
||||
case BINS_DEFAULT: return "VlCovBinKind::KIND_DEFAULT";
|
||||
default: return "VlCovBinKind::KIND_NORMAL";
|
||||
}
|
||||
}
|
||||
// Normal bins (feed coverage) are anything but ignore/illegal/default
|
||||
bool binIsNormal() const {
|
||||
return m_e != BINS_IGNORE && m_e != BINS_ILLEGAL && m_e != BINS_DEFAULT;
|
||||
}
|
||||
};
|
||||
constexpr bool operator==(const VCoverBinsType& lhs, VCoverBinsType::en rhs) {
|
||||
return lhs.m_e == rhs;
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
|||
, 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
|
||||
|
||||
|
|
@ -88,6 +91,17 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
|||
// 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);
|
||||
|
||||
|
|
@ -504,6 +518,13 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
|||
// 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;
|
||||
|
|
@ -595,47 +616,179 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
|||
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[N] = {values/ranges}), in order.
|
||||
std::vector<AstNodeExpr*> extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp) {
|
||||
std::vector<AstNodeExpr*> values;
|
||||
for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) {
|
||||
if (AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) {
|
||||
AstConst* const minp = VN_CAST(V3Const::constifyEdit(irp->lhsp()), Const);
|
||||
AstConst* const maxp = VN_CAST(V3Const::constifyEdit(irp->rhsp()), Const);
|
||||
if (!minp || !maxp) {
|
||||
arrayBinp->v3error("Non-constant expression in array bins range; "
|
||||
"range bounds must be constants");
|
||||
return values;
|
||||
}
|
||||
for (int val = minp->toSInt(); val <= maxp->toSInt(); ++val)
|
||||
values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{},
|
||||
static_cast<int>(exprp->width()),
|
||||
static_cast<uint32_t>(val)});
|
||||
} 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]
|
||||
std::vector<AstNodeExpr*> values = extractArrayValues(cbinp, exprp);
|
||||
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());
|
||||
|
||||
// Build OR of all non-default, non-ignore bins
|
||||
AstNodeExpr* anyBinMatchp = nullptr;
|
||||
|
||||
for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) {
|
||||
AstCoverBin* const cbinp = VN_AS(binp, CoverBin);
|
||||
|
||||
// Skip default, ignore, and illegal bins
|
||||
if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT
|
||||
|| cbinp->binsType() == VCoverBinsType::BINS_IGNORE
|
||||
|| cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build condition for this bin
|
||||
AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp);
|
||||
UASSERT_OBJ(binCondp, cbinp,
|
||||
"buildBinCondition returned nullptr for non-ignore/non-illegal bin");
|
||||
|
||||
// OR with previous conditions
|
||||
if (anyBinMatchp) {
|
||||
anyBinMatchp = new AstOr{defBinp->fileline(), anyBinMatchp, binCondp};
|
||||
} else {
|
||||
anyBinMatchp = binCondp;
|
||||
}
|
||||
}
|
||||
|
||||
// Default matches when NO explicit bin matches
|
||||
AstNodeExpr* defaultCondp = nullptr;
|
||||
if (anyBinMatchp) {
|
||||
// NOT (bin1 OR bin2 OR ... OR binN)
|
||||
defaultCondp = new AstNot{defBinp->fileline(), anyBinMatchp};
|
||||
} else {
|
||||
// No other bins - default always matches (shouldn't happen in practice)
|
||||
defaultCondp = new AstConst{defBinp->fileline(), AstConst::BitTrue{}};
|
||||
}
|
||||
AstNodeExpr* defaultCondp = buildDefaultCondition(coverpointp, exprp, defBinp->fileline());
|
||||
|
||||
// Apply iff condition if present
|
||||
if (AstNodeExpr* iffp = coverpointp->iffp()) {
|
||||
|
|
@ -1320,15 +1473,50 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
|||
|
||||
void generateCoverageMethodBody(AstFunc* funcp) {
|
||||
FileLine* const fl = funcp->fileline();
|
||||
AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var);
|
||||
|
||||
// Count total bins (excluding ignore_bins and illegal_bins)
|
||||
// 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() != VCoverBinsType::BINS_IGNORE
|
||||
&& bi.binp->binsType() != VCoverBinsType::BINS_ILLEGAL) {
|
||||
totalBins++;
|
||||
}
|
||||
if (bi.binp->binsType().binIsNormal()) totalBins++;
|
||||
}
|
||||
|
||||
UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size());
|
||||
|
|
@ -1337,7 +1525,6 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
|||
// No coverage to compute - return 100%.
|
||||
// Any parser-generated initialization of returnVar is overridden by our assignment.
|
||||
UINFO(4, " Empty covergroup, returning 100.0");
|
||||
AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var);
|
||||
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");
|
||||
|
|
@ -1356,11 +1543,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
|||
|
||||
// For each regular bin, if count > 0, increment covered_count
|
||||
for (const BinInfo& bi : m_binInfos) {
|
||||
// Skip ignore_bins and illegal_bins in coverage calculation
|
||||
if (bi.binp->binsType() == VCoverBinsType::BINS_IGNORE
|
||||
|| bi.binp->binsType() == VCoverBinsType::BINS_ILLEGAL) {
|
||||
continue;
|
||||
}
|
||||
// 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{
|
||||
|
|
@ -1375,9 +1559,6 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
|||
funcp->addStmtsp(ifp);
|
||||
}
|
||||
|
||||
// Find the return variable
|
||||
AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var);
|
||||
|
||||
// Calculate coverage: (covered_count / total_bins) * 100.0
|
||||
// return_var = (double)covered_count / (double)total_bins * 100.0
|
||||
|
||||
|
|
|
|||
|
|
@ -689,6 +689,7 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n");
|
||||
if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n");
|
||||
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
|
||||
if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n");
|
||||
if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n");
|
||||
if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n");
|
||||
if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n");
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ class EmitCModel final : public EmitCFunc {
|
|||
if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n");
|
||||
if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n");
|
||||
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
|
||||
if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n");
|
||||
if (v3Global.dpi()) puts("#include \"svdpi.h\"\n");
|
||||
|
||||
// Declare foreign instances up front to make C++ happy
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ std::vector<std::string> V3Global::verilatedCppFiles() {
|
|||
if (v3Global.opt.vpi()) result.emplace_back("verilated_vpi.cpp");
|
||||
if (v3Global.opt.savable()) result.emplace_back("verilated_save.cpp");
|
||||
if (v3Global.opt.coverage()) result.emplace_back("verilated_cov.cpp");
|
||||
if (v3Global.opt.coverage()) result.emplace_back("verilated_covergroup.cpp");
|
||||
for (const string& base : v3Global.opt.traceSourceBases())
|
||||
result.emplace_back(base + "_c.cpp");
|
||||
if (v3Global.usesProbDist()) result.emplace_back("verilated_probdist.cpp");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
cg.data.grouped: 2
|
||||
cg.data.values: 3
|
||||
cg2.cp.range_arr: 3
|
||||
cg3.cp.range_sized: 3
|
||||
cg.data.values[0]: 1
|
||||
cg.data.values[1]: 1
|
||||
cg.data.values[2]: 1
|
||||
cg2.cp.range_arr[0]: 1
|
||||
cg2.cp.range_arr[1]: 1
|
||||
cg2.cp.range_arr[2]: 1
|
||||
cg2.cp.range_arr[3]: 0
|
||||
cg3.cp.range_sized[0]: 1
|
||||
cg3.cp.range_sized[1]: 1
|
||||
cg3.cp.range_sized[2]: 1
|
||||
cg3.cp.range_sized[3]: 0
|
||||
|
|
|
|||
|
|
@ -65,6 +65,15 @@ cg_at_least.cp_addr.addr0: 1
|
|||
cg_at_least.cp_addr.addr1: 1
|
||||
cg_at_least.cp_cmd.read: 1
|
||||
cg_at_least.cp_cmd.write: 1
|
||||
cg_def_cross.axc.a0_x_read [cross]: 1
|
||||
cg_def_cross.axc.a0_x_write [cross]: 0
|
||||
cg_def_cross.axc.a1_x_read [cross]: 0
|
||||
cg_def_cross.axc.a1_x_write [cross]: 0
|
||||
cg_def_cross.cp_a.a0: 1
|
||||
cg_def_cross.cp_a.a1: 0
|
||||
cg_def_cross.cp_a.ad: 1
|
||||
cg_def_cross.cp_c.read: 1
|
||||
cg_def_cross.cp_c.write: 1
|
||||
cg_goal.addr_cmd_goal.addr0_x_read [cross]: 1
|
||||
cg_goal.addr_cmd_goal.addr0_x_write [cross]: 0
|
||||
cg_goal.addr_cmd_goal.addr1_x_read [cross]: 0
|
||||
|
|
@ -82,6 +91,16 @@ cg_ignore.cross_ab.a0_x_read [cross]: 1
|
|||
cg_ignore.cross_ab.a0_x_write [cross]: 1
|
||||
cg_ignore.cross_ab.a1_x_read [cross]: 1
|
||||
cg_ignore.cross_ab.a1_x_write [cross]: 1
|
||||
cg_mixed.ab.addr0_x_read [cross]: 1
|
||||
cg_mixed.ab.addr0_x_write [cross]: 1
|
||||
cg_mixed.ab.addr1_x_read [cross]: 1
|
||||
cg_mixed.ab.addr1_x_write [cross]: 1
|
||||
cg_mixed.cp_addr.addr0: 2
|
||||
cg_mixed.cp_addr.addr1: 2
|
||||
cg_mixed.cp_cmd.read: 2
|
||||
cg_mixed.cp_cmd.write: 2
|
||||
cg_mixed.cp_solo.debug: 2
|
||||
cg_mixed.cp_solo.normal: 2
|
||||
cg_range.addr_cmd_range.hi_range_x_read [cross]: 1
|
||||
cg_range.addr_cmd_range.hi_range_x_write [cross]: 1
|
||||
cg_range.addr_cmd_range.lo_range_x_read [cross]: 1
|
||||
|
|
|
|||
|
|
@ -99,6 +99,23 @@ module t;
|
|||
cross cp_a, cp_c; // no label: reported under the default cross name
|
||||
endgroup
|
||||
|
||||
// Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted
|
||||
// (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins.
|
||||
covergroup cg_mixed;
|
||||
cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};}
|
||||
cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};}
|
||||
cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed
|
||||
ab: cross cp_addr, cp_cmd;
|
||||
endgroup
|
||||
|
||||
// Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the
|
||||
// legacy default-bin codegen path that converted coverpoints bypass.
|
||||
covergroup cg_def_cross;
|
||||
cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;}
|
||||
cp_c: coverpoint cmd {bins read = {0}; bins write = {1};}
|
||||
axc: cross cp_a, cp_c;
|
||||
endgroup
|
||||
|
||||
cg2 cg2_inst = new;
|
||||
cg_ignore cg_ignore_inst = new;
|
||||
cg_range cg_range_inst = new;
|
||||
|
|
@ -109,6 +126,8 @@ module t;
|
|||
cg_goal cg_goal_inst = new;
|
||||
cg_unsup_cross_opt cg_unsup_cross_opt_inst = new;
|
||||
cg_unnamed_cross cg_unnamed_cross_inst = new;
|
||||
cg_mixed cg_mixed_inst = new;
|
||||
cg_def_cross cg_def_cross_inst = new;
|
||||
|
||||
initial begin
|
||||
// Sample 2-way: hit all 4 combinations
|
||||
|
|
@ -275,6 +294,23 @@ module t;
|
|||
cg_unnamed_cross_inst.sample(); // a1 x write
|
||||
`checkr(cg_unnamed_cross_inst.get_inst_coverage(), 75.0);
|
||||
|
||||
// Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4)
|
||||
addr = 0; cmd = 0; mode = 0;
|
||||
cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read)
|
||||
`checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10
|
||||
addr = 0; cmd = 1; mode = 1;
|
||||
cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write)
|
||||
addr = 1; cmd = 0; mode = 0;
|
||||
cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read)
|
||||
addr = 1; cmd = 1; mode = 1;
|
||||
cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write)
|
||||
`checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10
|
||||
|
||||
// Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff)
|
||||
mode = 1;
|
||||
addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read
|
||||
addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
cg.data.high: 1
|
||||
cg.data.low: 1
|
||||
cg.data.other: 2
|
||||
cg2.cp_only_default.all: 4
|
||||
cg.data.other [default]: 2
|
||||
cg2.cp_only_default.all [default]: 4
|
||||
cg3.data.bad [ignore]: 1
|
||||
cg3.data.err [illegal]: 0
|
||||
cg3.data.normal: 2
|
||||
cg3.data.other: 2
|
||||
cg3.data.other [default]: 2
|
||||
cg4.cp_idx.auto_0: 1
|
||||
cg4.cp_idx.auto_1: 1
|
||||
cg4.cp_idx.auto_2: 1
|
||||
|
|
|
|||
|
|
@ -157,17 +157,17 @@ module t;
|
|||
// Sample cg3: verify ignore/illegal bins do not contribute to coverage
|
||||
data = 2;
|
||||
cg3_inst.sample(); // hits normal bin
|
||||
`checkr(cg3_inst.get_inst_coverage(), 50.0); // 1/2 bins hit (normal)
|
||||
`checkr(cg3_inst.get_inst_coverage(), 100.0); // 1/1: only 'normal' counts (default/ignore/illegal excluded, LRM 19.5)
|
||||
data = 7;
|
||||
cg3_inst.sample(); // hits normal bin again
|
||||
`checkr(cg3_inst.get_inst_coverage(), 50.0); // no new bins
|
||||
`checkr(cg3_inst.get_inst_coverage(), 100.0); // no new normal bins
|
||||
data = 255;
|
||||
cg3_inst.sample(); // ignore_bins value; Verilator counts it toward default bin
|
||||
`checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2: Verilator hits 'other' (default) even for ignore_bins
|
||||
cg3_inst.sample(); // ignore_bins value; recorded but excluded from coverage
|
||||
`checkr(cg3_inst.get_inst_coverage(), 100.0);
|
||||
// note: do not sample 254 (illegal_bins would cause runtime assertion)
|
||||
data = 100;
|
||||
cg3_inst.sample(); // hits default (other) bin
|
||||
`checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2 bins hit
|
||||
cg3_inst.sample(); // hits default (other) bin; recorded but excluded from coverage
|
||||
`checkr(cg3_inst.get_inst_coverage(), 100.0);
|
||||
|
||||
// Sample cg4: auto-bins with one excluded value
|
||||
// idx=2 is in ignore_bins, so auto-bins cover 0, 1, 3 only (3 bins total)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ cg_and.cp_concat.b00: 0
|
|||
cg_and.cp_concat.b01: 1
|
||||
cg_and.cp_concat.b10: 0
|
||||
cg_and.cp_concat.b11: 0
|
||||
cg_array_iff.cp.arr: 1
|
||||
cg_array_iff.cp.arr[0]: 1
|
||||
cg_array_iff.cp.arr[1]: 0
|
||||
cg_array_iff.cp.arr[2]: 0
|
||||
cg_bitw.cp.seven: 1
|
||||
cg_default_iff.cp.def: 1
|
||||
cg_default_iff.cp.def [default]: 1
|
||||
cg_default_iff.cp.known: 1
|
||||
cg_iff.cp_value.disabled_hi: 0
|
||||
cg_iff.cp_value.disabled_lo: 0
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ module t;
|
|||
enable = 1;
|
||||
value = 10;
|
||||
cg2.sample(); // hits 'known'
|
||||
`checkr(cg2.get_inst_coverage(), 50.0);
|
||||
`checkr(cg2.get_inst_coverage(), 100.0); // 1/1: only 'known' counts (default excluded, LRM 19.5)
|
||||
value = 99;
|
||||
cg2.sample(); // hits 'def' (default)
|
||||
`checkr(cg2.get_inst_coverage(), 100.0);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
cg.data.arr [ignore]: 0
|
||||
cg.data.bad [illegal]: 0
|
||||
cg.data.arr[0] [ignore]: 0
|
||||
cg.data.arr[1] [ignore]: 0
|
||||
cg.data.bad[0] [illegal]: 0
|
||||
cg.data.bad[1] [illegal]: 0
|
||||
cg.data.catch_all [ignore]: 0
|
||||
cg.data.high: 1
|
||||
cg.data.low: 1
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ cg.data.forbidden [illegal]: 0
|
|||
cg.data.high: 1
|
||||
cg.data.low: 1
|
||||
cg.data.mid: 1
|
||||
cg2.cp_arr.bad_arr [illegal]: 0
|
||||
cg2.cp_arr.bad_arr[0] [illegal]: 0
|
||||
cg2.cp_arr.bad_arr[1] [illegal]: 0
|
||||
cg2.cp_arr.bad_arr[2] [illegal]: 0
|
||||
cg2.cp_arr.ok: 1
|
||||
cg2.cp_arr.wlib [illegal]: 0
|
||||
cg2.cp_trans.bad_2step [illegal]: 0
|
||||
|
|
|
|||
|
|
@ -7,3 +7,5 @@ cg.cp_trans2.trans2: 1
|
|||
cg.cp_trans2.trans3: 1
|
||||
cg.cp_trans3.seq_a: 1
|
||||
cg.cp_trans3.seq_b: 1
|
||||
cg_legacy.cp_mix.tr: 1
|
||||
cg_legacy.cp_mix.vals: 1
|
||||
|
|
|
|||
|
|
@ -39,7 +39,17 @@ module t;
|
|||
}
|
||||
endgroup
|
||||
|
||||
// Non-convertible coverpoint (it has a transition bin) that also carries a value-array bin,
|
||||
// so the legacy array codegen path (which converted coverpoints bypass) is still exercised.
|
||||
covergroup cg_legacy;
|
||||
cp_mix: coverpoint state {
|
||||
bins tr = (0 => 1);
|
||||
bins vals[] = {2, [4:5]}; // discrete + range elements (both legacy array paths)
|
||||
}
|
||||
endgroup
|
||||
|
||||
cg cg_inst = new;
|
||||
cg_legacy cg_legacy_inst = new;
|
||||
|
||||
initial begin
|
||||
// Drive sequence 0->1->2->3->4 which hits all bins
|
||||
|
|
@ -56,6 +66,12 @@ module t;
|
|||
cg_inst.sample(); // 3=>4: seq_b done
|
||||
`checkr(cg_inst.get_inst_coverage(), 100.0);
|
||||
|
||||
// cg_legacy: exercise legacy array codegen (non-convertible coverpoint)
|
||||
state = 0; cg_legacy_inst.sample();
|
||||
state = 1; cg_legacy_inst.sample(); // 0=>1: tr
|
||||
state = 2; cg_legacy_inst.sample(); // vals
|
||||
state = 3; cg_legacy_inst.sample(); // vals
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
|
||||
module t;
|
||||
%000001 logic [1:0] addr;
|
||||
-000001 point: type=toggle comment=addr[0]:0->1 hier=top.t
|
||||
-000000 point: type=toggle comment=addr[0]:0->1 hier=top.t
|
||||
-000000 point: type=toggle comment=addr[0]:1->0 hier=top.t
|
||||
-000000 point: type=toggle comment=addr[1]:0->1 hier=top.t
|
||||
-000001 point: type=toggle comment=addr[1]:0->1 hier=top.t
|
||||
-000000 point: type=toggle comment=addr[1]:1->0 hier=top.t
|
||||
%000001 logic cmd;
|
||||
-000001 point: type=toggle comment=cmd:0->1 hier=top.t
|
||||
|
|
@ -278,6 +278,50 @@
|
|||
// cross: [a1, write]
|
||||
endgroup
|
||||
|
||||
// Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted
|
||||
// (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins.
|
||||
covergroup cg_mixed;
|
||||
%000002 cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};}
|
||||
-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr0
|
||||
-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr1
|
||||
%000002 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};}
|
||||
-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.read
|
||||
-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.write
|
||||
%000002 cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed
|
||||
-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.normal
|
||||
-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.debug
|
||||
%000001 ab: cross cp_addr, cp_cmd;
|
||||
-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_read
|
||||
// cross: [addr0, read]
|
||||
-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_write
|
||||
// cross: [addr0, write]
|
||||
-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_read
|
||||
// cross: [addr1, read]
|
||||
-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_write
|
||||
// cross: [addr1, write]
|
||||
endgroup
|
||||
|
||||
// Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the
|
||||
// legacy default-bin codegen path that converted coverpoints bypass.
|
||||
covergroup cg_def_cross;
|
||||
%000001 cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;}
|
||||
-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.a0
|
||||
-000000 point: type=covergroup comment= hier=cg_def_cross.cp_a.a1
|
||||
-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.ad
|
||||
%000001 cp_c: coverpoint cmd {bins read = {0}; bins write = {1};}
|
||||
-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.read
|
||||
-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.write
|
||||
%000001 axc: cross cp_a, cp_c;
|
||||
-000001 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_read
|
||||
// cross: [a0, read]
|
||||
-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_write
|
||||
// cross: [a0, write]
|
||||
-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_read
|
||||
// cross: [a1, read]
|
||||
-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_write
|
||||
// cross: [a1, write]
|
||||
endgroup
|
||||
|
||||
%000001 cg2 cg2_inst = new;
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 cg_ignore cg_ignore_inst = new;
|
||||
|
|
@ -298,6 +342,10 @@
|
|||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 cg_unnamed_cross cg_unnamed_cross_inst = new;
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 cg_mixed cg_mixed_inst = new;
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 cg_def_cross cg_def_cross_inst = new;
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
|
||||
%000001 initial begin
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
|
|
@ -641,6 +689,40 @@
|
|||
-000000 point: type=line comment=block hier=top.t
|
||||
-000001 point: type=line comment=else hier=top.t
|
||||
|
||||
// Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4)
|
||||
%000001 addr = 0; cmd = 0; mode = 0;
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read)
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
-000000 point: type=line comment=block hier=top.t
|
||||
-000001 point: type=line comment=else hier=top.t
|
||||
%000001 addr = 0; cmd = 1; mode = 1;
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write)
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 addr = 1; cmd = 0; mode = 0;
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read)
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 addr = 1; cmd = 1; mode = 1;
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write)
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
-000000 point: type=line comment=block hier=top.t
|
||||
-000001 point: type=line comment=else hier=top.t
|
||||
|
||||
// Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff)
|
||||
%000001 mode = 1;
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
|
||||
%000001 $write("*-* All Finished *-*\n");
|
||||
-000001 point: type=line comment=block hier=top.t
|
||||
%000001 $finish;
|
||||
|
|
|
|||
Loading…
Reference in New Issue