Fix hierarchical coverage counts for duplicate no-inline module instances (#7649)

This commit is contained in:
Yogish Sekhar 2026-05-24 15:42:42 +00:00 committed by GitHub
parent 6c96ce4b40
commit 4fbb8bf43b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 324 additions and 63 deletions

View File

@ -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; }

View File

@ -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));

View File

@ -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:

View File

@ -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(", ");

View File

@ -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");
}

View File

@ -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,");

View File

@ -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 {

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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()