diff --git a/include/verilated_cov_model.h b/include/verilated_cov_model.h new file mode 100644 index 000000000..519fd214c --- /dev/null +++ b/include/verilated_cov_model.h @@ -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 +#include + +// 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 +// 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 diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp new file mode 100644 index 000000000..6dc7006f1 --- /dev/null +++ b/include/verilated_covergroup.cpp @@ -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); + } + } +} diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h new file mode 100644 index 000000000..1ffc98ff7 --- /dev/null +++ b/include/verilated_covergroup.h @@ -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 +#include +#include + +class VerilatedCovContext; + +// How a namer builds the names of the bins it covers. +enum class VlCovBinNaming : uint8_t { + Single, // "" one bin + Array, // "[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 m_counts; // [m_total], one per bin + std::vector 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 diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index c12627886..dd1754a9d 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -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; diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 727b5689c..0d43e9f60 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -68,6 +68,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { , crossBins{cb} {} }; std::vector m_binInfos; // All bins in current covergroup + std::set m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy) + std::vector 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 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(new AstNot{fl, anyBinMatchp}) + : static_cast(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 extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp) { + std::vector 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(exprp->width()), + static_cast(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 namerStmts; + std::vector 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 values = extractArrayValues(cbinp, exprp); + namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast(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 diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 1746139b8..524a39222 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -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"); diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 211eea99f..6ddd53e77 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -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 diff --git a/src/V3Global.cpp b/src/V3Global.cpp index e0b6f42ea..2e7c25fc7 100644 --- a/src/V3Global.cpp +++ b/src/V3Global.cpp @@ -245,6 +245,7 @@ std::vector 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"); diff --git a/test_regress/t/t_covergroup_array_bins.out b/test_regress/t/t_covergroup_array_bins.out index 4c0767563..ccbbbc297 100644 --- a/test_regress/t/t_covergroup_array_bins.out +++ b/test_regress/t/t_covergroup_array_bins.out @@ -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 diff --git a/test_regress/t/t_covergroup_cross.out b/test_regress/t/t_covergroup_cross.out index 84d9104d6..a7311da80 100644 --- a/test_regress/t/t_covergroup_cross.out +++ b/test_regress/t/t_covergroup_cross.out @@ -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 diff --git a/test_regress/t/t_covergroup_cross.v b/test_regress/t/t_covergroup_cross.v index 6245baa35..e86f179a4 100644 --- a/test_regress/t/t_covergroup_cross.v +++ b/test_regress/t/t_covergroup_cross.v @@ -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 diff --git a/test_regress/t/t_covergroup_default_bins.out b/test_regress/t/t_covergroup_default_bins.out index 9e4e7a1c6..e034545b8 100644 --- a/test_regress/t/t_covergroup_default_bins.out +++ b/test_regress/t/t_covergroup_default_bins.out @@ -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 diff --git a/test_regress/t/t_covergroup_default_bins.v b/test_regress/t/t_covergroup_default_bins.v index f4ba5148c..c97cf8f66 100644 --- a/test_regress/t/t_covergroup_default_bins.v +++ b/test_regress/t/t_covergroup_default_bins.v @@ -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) diff --git a/test_regress/t/t_covergroup_iff.out b/test_regress/t/t_covergroup_iff.out index 0e28a68cf..69a7711dd 100644 --- a/test_regress/t/t_covergroup_iff.out +++ b/test_regress/t/t_covergroup_iff.out @@ -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 diff --git a/test_regress/t/t_covergroup_iff.v b/test_regress/t/t_covergroup_iff.v index 1816bd327..8bf6e0016 100644 --- a/test_regress/t/t_covergroup_iff.v +++ b/test_regress/t/t_covergroup_iff.v @@ -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); diff --git a/test_regress/t/t_covergroup_ignore_bins.out b/test_regress/t/t_covergroup_ignore_bins.out index bcd0b72e0..6a1cba188 100644 --- a/test_regress/t/t_covergroup_ignore_bins.out +++ b/test_regress/t/t_covergroup_ignore_bins.out @@ -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 diff --git a/test_regress/t/t_covergroup_illegal_bins.out b/test_regress/t/t_covergroup_illegal_bins.out index d2de75a65..798247e10 100644 --- a/test_regress/t/t_covergroup_illegal_bins.out +++ b/test_regress/t/t_covergroup_illegal_bins.out @@ -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 diff --git a/test_regress/t/t_covergroup_trans.out b/test_regress/t/t_covergroup_trans.out index f7621e74a..17e059ede 100644 --- a/test_regress/t/t_covergroup_trans.out +++ b/test_regress/t/t_covergroup_trans.out @@ -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 diff --git a/test_regress/t/t_covergroup_trans.v b/test_regress/t/t_covergroup_trans.v index dea93e6c5..f95561ed2 100644 --- a/test_regress/t/t_covergroup_trans.v +++ b/test_regress/t/t_covergroup_trans.v @@ -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 diff --git a/test_regress/t/t_vlcov_covergroup.annotate.out b/test_regress/t/t_vlcov_covergroup.annotate.out index 436973cb7..b8616ad2d 100644 --- a/test_regress/t/t_vlcov_covergroup.annotate.out +++ b/test_regress/t/t_vlcov_covergroup.annotate.out @@ -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;