Fix hierarchical coverage counts for duplicate no-inline module instances (#7649)
This commit is contained in:
parent
6c96ce4b40
commit
4fbb8bf43b
|
|
@ -40,7 +40,10 @@ class AstNodeCoverDecl VL_NOT_FINAL : public AstNode {
|
|||
string m_page; // Coverage point's page tag
|
||||
string m_text; // Coverage point's text
|
||||
string m_hier; // Coverage point's hierarchy
|
||||
int m_binNum = 0; // Set by V3EmitCSyms to tell final V3Emit what to increment
|
||||
int m_binNum = 0; // Global coverage bin offset in the symbol table coverage array
|
||||
// Coverage counters are emitted in each module object, so duplicate
|
||||
// no-inline instances can keep independent counts for forcePerInstance.
|
||||
int m_localBinNum = 0; // Per-module coverage bin offset
|
||||
public:
|
||||
AstNodeCoverDecl(VNType t, FileLine* fl, const string& page, const string& comment)
|
||||
: AstNode(t, fl)
|
||||
|
|
@ -60,6 +63,8 @@ public:
|
|||
bool maybePointedTo() const override VL_MT_SAFE { return true; }
|
||||
int binNum() const { return m_binNum; }
|
||||
void binNum(int flag) { m_binNum = flag; }
|
||||
int localBinNum() const { return m_localBinNum; }
|
||||
void localBinNum(int flag) { m_localBinNum = flag; }
|
||||
virtual int size() const = 0;
|
||||
const string& comment() const { return m_text; } // text to insert in code
|
||||
const string& page() const { return m_page; }
|
||||
|
|
|
|||
|
|
@ -26,8 +26,11 @@
|
|||
EmitCParentModule::EmitCParentModule() {
|
||||
const auto setAll = [](AstNodeModule* modp) -> void {
|
||||
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||
if (VN_IS(nodep, CFunc) || VN_IS(nodep, Var)) nodep->user4p(modp);
|
||||
if (VN_IS(nodep, CFunc) || VN_IS(nodep, Var) || VN_IS(nodep, NodeCoverDecl)) {
|
||||
nodep->user4p(modp);
|
||||
}
|
||||
}
|
||||
modp->foreach([&](AstNodeCoverDecl* nodep) { nodep->user4p(modp); });
|
||||
};
|
||||
for (AstNode* modp = v3Global.rootp()->modulesp(); modp; modp = modp->nextp()) {
|
||||
setAll(VN_AS(modp, NodeModule));
|
||||
|
|
|
|||
|
|
@ -27,12 +27,14 @@
|
|||
#include <cstdarg>
|
||||
|
||||
//######################################################################
|
||||
// Set user4p in all CFunc and Var to point to the containing AstNodeModule
|
||||
// Set user4p in all CFunc, Var, and coverage declarations to point to the
|
||||
// containing AstNodeModule
|
||||
|
||||
class EmitCParentModule final {
|
||||
// NODE STATE
|
||||
// AstFunc::user4p() AstNodeModule* Parent module pointer
|
||||
// AstVar::user4p() AstNodeModule* Parent module pointer
|
||||
// AstCFunc::user4p() AstNodeModule* Parent module pointer
|
||||
// AstVar::user4p() AstNodeModule* Parent module pointer
|
||||
// AstNodeCoverDecl::user4p() AstNodeModule* Parent module pointer
|
||||
const VNUser4InUse user4InUse;
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -148,6 +148,72 @@ protected:
|
|||
return false;
|
||||
}
|
||||
|
||||
bool coverageUsesLocalCounter(AstNodeCoverDecl* const declp) const {
|
||||
// Only functions with an object receiver can address the module-local
|
||||
// coverage array. Static/package/class helper functions still use the
|
||||
// symbol-table array, as they may have vlSymsp but no vlSelf/this.
|
||||
// Also require the coverage declaration to belong to the current
|
||||
// module; cloned task bodies can increment declarations owned by a
|
||||
// different module, and those must keep using the global array.
|
||||
return m_cfuncp && !m_cfuncp->isStatic() && !VN_IS(m_modp, Class)
|
||||
&& !VN_IS(m_modp, ClassPackage)
|
||||
&& EmitCParentModule::get(declp->dataDeclThisp()) == m_modp;
|
||||
}
|
||||
int coverageBinNum(AstNodeCoverDecl* const declp, bool forceGlobal = false) const {
|
||||
return !forceGlobal && coverageUsesLocalCounter(declp)
|
||||
? declp->dataDeclThisp()->localBinNum()
|
||||
: declp->dataDeclThisp()->binNum();
|
||||
}
|
||||
void putCoverageArray(AstNodeCoverDecl* const declp, bool forceGlobal = false) {
|
||||
if (!forceGlobal && coverageUsesLocalCounter(declp)) {
|
||||
// Keep counters on the module object when possible, so every
|
||||
// no-inline instance has storage available if coverage output is
|
||||
// later written with forcePerInstance.
|
||||
puts(m_cfuncp->isLoose() ? "vlSelf->__Vcoverage" : "this->__Vcoverage");
|
||||
} else {
|
||||
// Static/package/class helper functions do not have vlSelf, so
|
||||
// keep using the shared symbol-table coverage array there.
|
||||
puts("vlSymsp->__Vcoverage");
|
||||
}
|
||||
}
|
||||
void emitCoverOtherDeclInsert(AstCoverOtherDecl* nodep, bool forceGlobal = false) {
|
||||
putns(nodep, "vlSelf->__vlCoverInsert("); // As Declared in emitCoverageDecl
|
||||
putCoverageArray(nodep, forceGlobal);
|
||||
puts(" + ");
|
||||
puts(cvtToStr(coverageBinNum(nodep, forceGlobal)));
|
||||
// first controls whether duplicate module instances are collapsed in
|
||||
// the default coverage view. The following flag says whether countp
|
||||
// points to an object-local counter; only those counters must be kept
|
||||
// real for later instances so forcePerInstance can split hierarchy.
|
||||
puts(", first"); // Enable, passed from __Vconfigure parameter
|
||||
puts(", ");
|
||||
puts(!forceGlobal && coverageUsesLocalCounter(nodep) ? "true" : "false");
|
||||
puts(", ");
|
||||
putsQuoted(protect(nodep->fileline()->filename()));
|
||||
puts(", ");
|
||||
puts(cvtToStr(nodep->fileline()->lineno()));
|
||||
puts(", ");
|
||||
puts(cvtToStr(nodep->offset() + nodep->fileline()->firstColumn()));
|
||||
puts(", ");
|
||||
putsQuoted((!nodep->hier().empty() ? "." : "")
|
||||
+ VIdProtect::protectWordsIf(nodep->hier(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->page(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(nodep->linescov());
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmVar(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmFrom(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTo(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTag(), nodep->protect()));
|
||||
puts(");\n");
|
||||
}
|
||||
|
||||
public:
|
||||
// METHODS
|
||||
bool displayEmitHeader(AstNode* nodep);
|
||||
|
|
@ -810,40 +876,13 @@ public:
|
|||
iterateChildrenConst(nodep);
|
||||
}
|
||||
void visit(AstCoverOtherDecl* nodep) override {
|
||||
putns(nodep, "vlSelf->__vlCoverInsert("); // As Declared in emitCoverageDecl
|
||||
puts("&(vlSymsp->__Vcoverage[");
|
||||
puts(cvtToStr(nodep->dataDeclThisp()->binNum()));
|
||||
puts("])");
|
||||
// If this isn't the first instantiation of this module under this
|
||||
// design, don't really count the bucket, and rely on verilator_cov to
|
||||
// aggregate counts. This is because Verilator combines all
|
||||
// hierarchies itself, and if verilator_cov also did it, you'd end up
|
||||
// with (number-of-instant) times too many counts in this bin.
|
||||
puts(", first"); // Enable, passed from __Vconfigure parameter
|
||||
puts(", ");
|
||||
putsQuoted(protect(nodep->fileline()->filename()));
|
||||
puts(", ");
|
||||
puts(cvtToStr(nodep->fileline()->lineno()));
|
||||
puts(", ");
|
||||
puts(cvtToStr(nodep->offset() + nodep->fileline()->firstColumn()));
|
||||
puts(", ");
|
||||
putsQuoted((!nodep->hier().empty() ? "." : "")
|
||||
+ VIdProtect::protectWordsIf(nodep->hier(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->page(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(nodep->linescov());
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmVar(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmFrom(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTo(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTag(), nodep->protect()));
|
||||
puts(");\n");
|
||||
emitCoverOtherDeclInsert(nodep);
|
||||
if (coverageUsesLocalCounter(nodep)) {
|
||||
// Some task bodies are cloned into a caller module where the
|
||||
// original instance-local counter is not addressable. Those clones
|
||||
// fall back to the global bin number, so register that slot too.
|
||||
emitCoverOtherDeclInsert(nodep, true);
|
||||
}
|
||||
}
|
||||
void visit(AstCoverToggleDecl* nodep) override {
|
||||
putns(nodep, "vlSelf->__vlCoverToggleInsert("); // As Declared in emitCoverageDecl
|
||||
|
|
@ -853,16 +892,17 @@ public:
|
|||
puts(", ");
|
||||
puts(cvtToStr(nodep->range().ranged()));
|
||||
puts(", ");
|
||||
puts("&(vlSymsp->__Vcoverage[");
|
||||
puts(cvtToStr(nodep->dataDeclThisp()->binNum()));
|
||||
puts("])");
|
||||
// If this isn't the first instantiation of this module under this
|
||||
// design, don't really count the bucket, and rely on verilator_cov to
|
||||
// aggregate counts. This is because Verilator combines all
|
||||
// hierarchies itself, and if verilator_cov also did it, you'd end up
|
||||
// with (number-of-instant) times too many counts in this bin.
|
||||
putCoverageArray(nodep);
|
||||
puts(" + ");
|
||||
puts(cvtToStr(coverageBinNum(nodep)));
|
||||
// first controls whether duplicate module instances are collapsed in
|
||||
// the default coverage view. The following flag says whether countp
|
||||
// points to an object-local counter; only those counters must be kept
|
||||
// real for later instances so forcePerInstance can split hierarchy.
|
||||
puts(", first"); // Enable, passed from __Vconfigure parameter
|
||||
puts(", ");
|
||||
puts(coverageUsesLocalCounter(nodep) ? "true" : "false");
|
||||
puts(", ");
|
||||
putsQuoted(protect(nodep->fileline()->filename()));
|
||||
puts(", ");
|
||||
puts(cvtToStr(nodep->fileline()->lineno()));
|
||||
|
|
@ -880,12 +920,16 @@ public:
|
|||
void visit(AstCoverInc* nodep) override {
|
||||
if (VN_IS(nodep->declp(), CoverOtherDecl)) {
|
||||
if (v3Global.opt.threads() > 1) {
|
||||
putns(nodep, "vlSymsp->__Vcoverage[");
|
||||
puts(cvtToStr(nodep->declp()->dataDeclThisp()->binNum()));
|
||||
putns(nodep, "");
|
||||
putCoverageArray(nodep->declp());
|
||||
puts("[");
|
||||
puts(cvtToStr(coverageBinNum(nodep->declp())));
|
||||
puts("].fetch_add(1, std::memory_order_relaxed);\n");
|
||||
} else {
|
||||
putns(nodep, "++(vlSymsp->__Vcoverage[");
|
||||
puts(cvtToStr(nodep->declp()->dataDeclThisp()->binNum()));
|
||||
putns(nodep, "++(");
|
||||
putCoverageArray(nodep->declp());
|
||||
puts("[");
|
||||
puts(cvtToStr(coverageBinNum(nodep->declp())));
|
||||
puts("]);\n");
|
||||
}
|
||||
} else {
|
||||
|
|
@ -901,8 +945,11 @@ public:
|
|||
// coverpoint
|
||||
puts(cvtToStr(nodep->declp()->size() / 2));
|
||||
puts(", ");
|
||||
puts("vlSymsp->__Vcoverage + ");
|
||||
puts(cvtToStr(nodep->declp()->dataDeclThisp()->binNum()));
|
||||
// Toggle update uses the same object-local counter array that
|
||||
// __vlCoverToggleInsert registered.
|
||||
putCoverageArray(nodep->declp());
|
||||
puts(" + ");
|
||||
puts(cvtToStr(coverageBinNum(nodep->declp())));
|
||||
puts(", ");
|
||||
iterateConst(nodep->toggleExprp());
|
||||
puts(", ");
|
||||
|
|
|
|||
|
|
@ -36,6 +36,24 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
V3UniqueNames m_names;
|
||||
// METHODS
|
||||
|
||||
class CoverCountVisitor final : public VNVisitorConst {
|
||||
int m_bins = 0;
|
||||
|
||||
void visit(AstNodeCoverDecl* nodep) override {
|
||||
// Each module class owns the counters for declarations it emits;
|
||||
// duplicate no-inline instances need separate arrays for
|
||||
// forcePerInstance reporting.
|
||||
if (!nodep->dataDeclNullp()) m_bins += nodep->size();
|
||||
}
|
||||
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
||||
|
||||
public:
|
||||
explicit CoverCountVisitor(const AstNodeModule* modp) {
|
||||
iterateConst(const_cast<AstNodeModule*>(modp));
|
||||
}
|
||||
int bins() const { return m_bins; }
|
||||
};
|
||||
|
||||
void decorateFirst(bool& first, const string& str) {
|
||||
if (first) {
|
||||
putsDecoration(nullptr, str);
|
||||
|
|
@ -132,6 +150,15 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
putsDecoration(nullptr, "\n// INTERNAL VARIABLES\n");
|
||||
puts(EmitCUtil::symClassName() + "* vlSymsp;\n");
|
||||
puts("const char* vlNamep;\n");
|
||||
// Allocate object-local coverage counters only on modules that
|
||||
// contain emitted coverage declarations.
|
||||
const int coverBins = CoverCountVisitor{modp}.bins();
|
||||
if (coverBins) {
|
||||
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
|
||||
puts(" __Vcoverage[");
|
||||
puts(std::to_string(coverBins));
|
||||
puts("]{};\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
void emitParamDecls(const AstNodeModule* modp) {
|
||||
|
|
@ -195,7 +222,8 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
decorateFirst(first, section);
|
||||
puts("void __vlCoverInsert(");
|
||||
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
|
||||
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
||||
puts("* countp, bool enable, bool localCounter, const char* filenamep, int lineno, "
|
||||
"int column,\n");
|
||||
puts("const char* hierp, const char* pagep, const char* commentp, const char* "
|
||||
"linescovp,\n");
|
||||
puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* "
|
||||
|
|
@ -206,7 +234,8 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
decorateFirst(first, section);
|
||||
puts("void __vlCoverToggleInsert(int begin, int end, bool ranged, ");
|
||||
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
|
||||
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
||||
puts("* countp, bool enable, bool localCounter, const char* filenamep, int lineno, "
|
||||
"int column,\n");
|
||||
puts("const char* hierp, const char* pagep, const char* commentp);\n");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -178,7 +178,8 @@ class EmitCImp final : public EmitCFunc {
|
|||
puts("\n// Coverage\n");
|
||||
puts("void " + EmitCUtil::prefixNameProtect(m_modp) + "::__vlCoverInsert(");
|
||||
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
|
||||
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
||||
puts("* countp, bool enable, bool localCounter, const char* filenamep, int lineno, "
|
||||
"int column,\n");
|
||||
puts("const char* hierp, const char* pagep, const char* commentp, const char* "
|
||||
"linescovp,\n");
|
||||
puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* "
|
||||
|
|
@ -193,8 +194,11 @@ class EmitCImp final : public EmitCFunc {
|
|||
puts("static uint32_t fake_zero_count = 0;\n");
|
||||
puts("std::string fullhier = std::string{vlNamep} + hierp;\n");
|
||||
puts("if (!fullhier.empty() && fullhier[0] == '.') fullhier = fullhier.substr(1);\n");
|
||||
// Used for second++ instantiation of identical bin
|
||||
puts("if (!enable) count32p = &fake_zero_count;\n");
|
||||
// Global-counter users still redirect later duplicate instances
|
||||
// to fake_zero_count for default collapsed coverage. Object-local
|
||||
// counters must keep the real pointer so forcePerInstance can
|
||||
// report each hierarchy independently.
|
||||
puts("if (!enable && !localCounter) count32p = &fake_zero_count;\n");
|
||||
puts("*count32p = 0;\n");
|
||||
puts("VL_COVER_INSERT(vlSymsp->_vm_contextp__->coveragep(), vlNamep, count32p,");
|
||||
puts(" \"filename\",filenamep,");
|
||||
|
|
@ -215,7 +219,8 @@ class EmitCImp final : public EmitCFunc {
|
|||
puts("void " + EmitCUtil::prefixNameProtect(m_modp) + "::__vlCoverToggleInsert(");
|
||||
puts("int begin, int end, bool ranged, ");
|
||||
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
|
||||
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
||||
puts("* countp, bool enable, bool localCounter, const char* filenamep, int lineno, "
|
||||
"int column,\n");
|
||||
puts("const char* hierp, const char* pagep, const char* commentp) {\n");
|
||||
if (v3Global.opt.threads() > 1) {
|
||||
puts("assert(sizeof(uint32_t) == sizeof(std::atomic<uint32_t>));\n");
|
||||
|
|
@ -236,8 +241,11 @@ class EmitCImp final : public EmitCFunc {
|
|||
puts("std::string commentWithIndex = commentp;\n");
|
||||
puts("if (ranged) commentWithIndex += '[' + std::to_string(i) + ']';\n");
|
||||
puts("commentWithIndex += j ? \":0->1\" : \":1->0\";\n");
|
||||
// Used for second++ instantiation of identical bin
|
||||
puts("if (!enable) count32p = &fake_zero_count;\n");
|
||||
// Global-counter users still redirect later duplicate instances
|
||||
// to fake_zero_count for default collapsed coverage. Object-local
|
||||
// counters must keep the real pointer so forcePerInstance can
|
||||
// report each hierarchy independently.
|
||||
puts("if (!enable && !localCounter) count32p = &fake_zero_count;\n");
|
||||
puts("*count32p = 0;\n");
|
||||
puts("VL_COVER_INSERT(vlSymsp->_vm_contextp__->coveragep(), vlNamep, count32p,");
|
||||
puts(" \"filename\",filenamep,");
|
||||
|
|
|
|||
|
|
@ -100,7 +100,11 @@ class EmitCSyms final : EmitCBaseVisitorConst {
|
|||
ScopeNames m_vpiScopeCandidates; // All scopes for VPI
|
||||
// The actual hierarchy of scopes
|
||||
std::map<const std::string, std::vector<std::string>> m_vpiScopeHierarchy;
|
||||
int m_coverBins = 0; // Coverage bin number
|
||||
int m_coverBins = 0; // Global coverage bin number for non-object helper functions
|
||||
// Counts bins within the current module. Coverage storage is also emitted
|
||||
// on each module object so no-inline instances keep independent counters
|
||||
// when forcePerInstance is used.
|
||||
int m_modCoverBins = 0; // Per-module coverage bin number
|
||||
const bool m_dpiHdrOnly; // Only emit the DPI header
|
||||
std::vector<std::string> m_splitFuncNames; // Split file names
|
||||
VDouble0 m_statVarScopeBytes; // Statistic tracking
|
||||
|
|
@ -567,7 +571,11 @@ class EmitCSyms final : EmitCBaseVisitorConst {
|
|||
void visit(AstNodeModule* nodep) override {
|
||||
nameCheck(nodep);
|
||||
VL_RESTORER(m_modp);
|
||||
VL_RESTORER(m_modCoverBins);
|
||||
m_modp = nodep;
|
||||
// Restart bin numbering for the object-local coverage array of this
|
||||
// module class.
|
||||
m_modCoverBins = 0;
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
void visit(AstCellInlineScope* nodep) override {
|
||||
|
|
@ -638,10 +646,14 @@ class EmitCSyms final : EmitCBaseVisitorConst {
|
|||
}
|
||||
}
|
||||
void visit(AstNodeCoverDecl* nodep) override {
|
||||
// Assign numbers to all bins, so we know how big of an array to use
|
||||
// Assign both global and module-local bin numbers. Most generated
|
||||
// coverage uses module-local counters, but static/package/class helper
|
||||
// functions may have no vlSelf and still need the global array.
|
||||
if (!nodep->dataDeclNullp()) { // else duplicate we don't need code for
|
||||
nodep->binNum(m_coverBins);
|
||||
m_coverBins += nodep->size();
|
||||
nodep->localBinNum(m_modCoverBins);
|
||||
m_modCoverBins += nodep->size();
|
||||
}
|
||||
}
|
||||
void visit(AstCFunc* nodep) override {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
// DESCRIPTION: Verilator: C++ test driver
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
#include <verilated.h>
|
||||
#include <verilated_cov.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include VM_PREFIX_INCLUDE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
VerilatedContext context;
|
||||
context.commandArgs(argc, argv);
|
||||
|
||||
const std::unique_ptr<VM_PREFIX> topp{new VM_PREFIX{&context, "top"}};
|
||||
|
||||
for (int cyc = 0; cyc < 9; ++cyc) {
|
||||
topp->clk = 0;
|
||||
topp->eval();
|
||||
context.timeInc(1);
|
||||
|
||||
topp->clk = 1;
|
||||
topp->eval();
|
||||
context.timeInc(1);
|
||||
}
|
||||
|
||||
// The paired inline/no-inline regressions check that duplicate hierarchy
|
||||
// instances keep real counters, so forcePerInstance can split the same
|
||||
// source coverage point by hierarchy at write time.
|
||||
context.coveragep()->forcePerInstance(true);
|
||||
context.coveragep()->write(VL_STRINGIFY(TEST_OBJ_DIR) "/coverage.dat");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# SystemC::Coverage-3
|
||||
C 'ft/t_cover_hier.vl19n5tuserpagev_user/childosame_stmtS19htop.t.u_a.same_stmt' 4
|
||||
C 'ft/t_cover_hier.vl19n5tuserpagev_user/childosame_stmtS19htop.t.u_b.same_stmt' 1
|
||||
C 'ft/t_cover_hier.vl21n3tlinepagev_line/childoblockS21htop.t.u_a' 9
|
||||
C 'ft/t_cover_hier.vl21n3tlinepagev_line/childoblockS21htop.t.u_b' 9
|
||||
C 'ft/t_cover_hier.vl22n5tbranchpagev_branch/childoifS22htop.t.u_a' 4
|
||||
C 'ft/t_cover_hier.vl22n5tbranchpagev_branch/childoifS22htop.t.u_b' 1
|
||||
C 'ft/t_cover_hier.vl22n6tbranchpagev_branch/childoelsehtop.t.u_a' 5
|
||||
C 'ft/t_cover_hier.vl22n6tbranchpagev_branch/childoelsehtop.t.u_b' 8
|
||||
C 'ft/t_cover_hier.vl29n19tlinepagev_line/toblockS29htop.t' 1
|
||||
C 'ft/t_cover_hier.vl50n3tlinepagev_line/toblockS50-51htop.t' 9
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// DESCRIPTION: Verilator: Hierarchical coverage in duplicate module instances
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module child (
|
||||
input clk,
|
||||
input en,
|
||||
output reg seen
|
||||
);
|
||||
`ifdef INLINE_CHILD //verilator inline_module
|
||||
`else //verilator no_inline_module
|
||||
`endif
|
||||
|
||||
// The user cover and generated line/branch coverage below share one
|
||||
// module body but must produce independent counters per instance.
|
||||
same_stmt:
|
||||
cover property (@(posedge clk) en);
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (en) seen <= 1'b1;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
reg [3:0] cyc = 0;
|
||||
wire seen_a;
|
||||
wire seen_b;
|
||||
|
||||
// Over 9 clock edges, u_a.en is true for cyc 0..3, so u_a should report
|
||||
// user/if coverage of 4, else coverage of 5, and line coverage of 9.
|
||||
child u_a (
|
||||
.clk(clk),
|
||||
.en(cyc < 4),
|
||||
.seen(seen_a)
|
||||
);
|
||||
|
||||
// u_b.en is true only for cyc 0, so u_b should report user/if coverage of
|
||||
// 1, else coverage of 8, and line coverage of 9. These different counts
|
||||
// prove the duplicate no-inline child instances did not collapse together.
|
||||
child u_b (
|
||||
.clk(clk),
|
||||
.en(cyc == 0),
|
||||
.seen(seen_b)
|
||||
);
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1'b1;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
# Keep this paired with t_cover_hier_noinline.py: both tests use the same
|
||||
# source and golden so inline and no-inline coverage are checked for parity.
|
||||
test.compile(top_filename="t/t_cover_hier.v",
|
||||
v_flags2=["+define+INLINE_CHILD --assert --coverage-line --coverage-user "
|
||||
"t/t_cover_hier.cpp"],
|
||||
verilator_flags2=["--exe --top-module t"],
|
||||
make_flags=['CPPFLAGS_ADD=-DTEST_OBJ_DIR="' + test.obj_dir + '"'],
|
||||
make_main=False)
|
||||
|
||||
test.execute()
|
||||
|
||||
test.files_identical_sorted(test.obj_dir + "/coverage.dat", "t/t_cover_hier.out")
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
# Exercise both user and generated coverage in duplicated hierarchy instances;
|
||||
# the shared golden verifies inline and no-inline modules report the same
|
||||
# per-hierarchy counts when forcePerInstance is used.
|
||||
test.compile(top_filename="t/t_cover_hier.v",
|
||||
v_flags2=["--assert --coverage-line --coverage-user t/t_cover_hier.cpp"],
|
||||
verilator_flags2=["--exe --top-module t"],
|
||||
make_flags=['CPPFLAGS_ADD=-DTEST_OBJ_DIR="' + test.obj_dir + '"'],
|
||||
make_main=False)
|
||||
|
||||
test.execute()
|
||||
|
||||
test.files_identical_sorted(test.obj_dir + "/coverage.dat", "t/t_cover_hier.out")
|
||||
|
||||
test.passes()
|
||||
Loading…
Reference in New Issue