Add hierarchy-aware reporting to `verilator_coverage` (#7657)

This commit is contained in:
Yogish Sekhar 2026-06-04 13:32:19 +00:00 committed by GitHub
parent 14cf611c72
commit 947a08965e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 912 additions and 38 deletions

View File

@ -157,6 +157,10 @@ verilator_coverage - Verilator coverage analyzer
verilator_coverage --annotate <obj>
verilator_coverage --report summary,hier <datafiles>...
verilator_coverage --report hier <datafiles>...
verilator_coverage -write merged.dat <datafiles>...
verilator_coverage -write-info merged.info <datafiles>...
@ -176,7 +180,9 @@ L<https://verilator.org/guide/latest/exe_verilator_coverage.html>.
--filter-type <regex> Keep only records of given coverage type.
--help Displays this message and version and exits.
--include-reset-arcs Include reset arcs in FSM arc summaries.
--levels <depth> Limit displayed hierarchy report depth.
--rank Compute relative importance of tests.
--report <kind>[,<kind>...] Generate reports: summary, hier, or hierarchy.
--unlink With --write, unlink all inputs
--version Displays program version and exits.
--write <filename> Write aggregate coverage results.

View File

@ -38,6 +38,10 @@ verilator_coverage Example Usage
verilator_coverage --annotate obj_dir coverage.dat
verilator_coverage --report summary coverage.dat
verilator_coverage --report hier --levels 3 coverage.dat
verilator_coverage --write merged.dat coverage.dat ...
verilator_coverage --write-info merged.info coverage.dat ...
@ -143,6 +147,13 @@ verilator_coverage Arguments
By default, reset arcs are tracked but summarized separately from the
non-reset FSM arcs.
.. option:: --levels <depth>
With :option:`--report hierarchy`, limits displayed hierarchy depth.
Deeper descendants still roll up into the visible parent totals. A depth
of 0 shows the top-level hierarchy rollup. If negative or omitted, all
depths are shown.
.. option:: --rank
Prints an experimental report listing the relative importance of each
@ -155,6 +166,18 @@ verilator_coverage Arguments
contribute to overall coverage if all tests are run in the order of
highest to the lowest rank.
.. option:: --report <kind>[,<kind>...]
Generates a human-consumable report. Supported report kinds are
``summary``, ``hier``, and ``hierarchy``. Multiple report kinds may be
comma-separated, for example ``summary,hier``. With no explicit
:option:`--report`, the legacy flat summary is printed. ``summary`` is
equivalent to the flat type summary. ``hier`` and ``hierarchy`` build a
deterministic hierarchy rollup from ``hier`` fields. Collapsed wildcard
hierarchy can be reported, but it is not precise per-instance coverage. If
no hierarchy fields are present, a warning is printed and the flat summary
is shown instead.
.. option:: --unlink
With :option:`--write`, unlink all input files after the output has been

View File

@ -57,6 +57,24 @@ string VlcOptions::version() {
return ver;
}
void VlcOptions::parseReportOption() {
if (m_report.empty()) return;
string::size_type start = 0;
while (true) {
const string::size_type comma = m_report.find(',', start);
const string kind = m_report.substr(start, comma - start);
if (kind == "summary") {
m_reportSummary = true;
} else if (kind == "hier" || kind == "hierarchy") {
m_reportHierarchy = true;
} else {
v3fatal("Invalid --report option: " << m_report);
}
if (comma == string::npos) break;
start = comma + 1;
}
}
void VlcOptions::parseOptsList(int argc, char** argv) {
V3OptionParser parser;
V3OptionParser::AppendHelper DECL_OPTION{parser};
@ -70,7 +88,9 @@ void VlcOptions::parseOptsList(int argc, char** argv) {
DECL_OPTION("-debugi", CbVal, [](int v) { V3Error::debugDefault(v); });
DECL_OPTION("-filter-type", Set, &m_filterType);
DECL_OPTION("-include-reset-arcs", OnOff, &m_includeResetArcs);
DECL_OPTION("-levels", Set, &m_reportLevels);
DECL_OPTION("-rank", OnOff, &m_rank);
DECL_OPTION("-report", Set, &m_report);
DECL_OPTION("-unlink", OnOff, &m_unlink);
DECL_OPTION("-V", CbCall, []() {
showVersion(true);
@ -101,6 +121,7 @@ void VlcOptions::parseOptsList(int argc, char** argv) {
++i;
}
}
parseReportOption();
}
void VlcOptions::showVersion(bool verbose) {
@ -141,7 +162,12 @@ int main(int argc, char** argv) {
top.points().dump();
}
if (!top.opt.rank() && top.opt.writeFile().empty() && top.opt.writeInfoFile().empty()) {
const bool defaultReport = !top.opt.reportSpecified() && !top.opt.rank()
&& top.opt.writeFile().empty() && top.opt.writeInfoFile().empty();
if (top.opt.reportSpecified()) {
if (top.opt.reportSummary()) top.printTypeSummary();
if (top.opt.reportHierarchy()) top.printHierarchyReport();
} else if (defaultReport) {
top.printTypeSummary();
}

View File

@ -42,6 +42,10 @@ class VlcOptions final {
bool m_includeResetArcs = false; // main switch: --include-reset-arcs
string m_filterType = "*"; // main switch: --filter-type
VlStringSet m_readFiles; // main switch: --read
int m_reportLevels = -1; // main switch: --levels, negative means all depths
string m_report; // main switch: --report
bool m_reportSummary = false; // main switch: --report summary
bool m_reportHierarchy = false; // main switch: --report hier or hierarchy
bool m_rank = false; // main switch: --rank
bool m_unlink = false; // main switch: --unlink
string m_writeFile; // main switch: --write
@ -50,6 +54,7 @@ class VlcOptions final {
private:
// METHODS
void parseReportOption() VL_MT_DISABLED;
static void showVersion(bool verbose) VL_MT_DISABLED;
public:
@ -69,6 +74,10 @@ public:
bool countOk(uint64_t count) const { return count >= static_cast<uint64_t>(m_annotateMin); }
bool annotatePoints() const { return m_annotatePoints; }
bool includeResetArcs() const { return m_includeResetArcs; }
int reportLevels() const { return m_reportLevels; }
bool reportSpecified() const { return !m_report.empty(); }
bool reportSummary() const { return m_reportSummary; }
bool reportHierarchy() const { return m_reportHierarchy; }
bool rank() const { return m_rank; }
bool unlink() const { return m_unlink; }
string writeFile() const { return m_writeFile; }

View File

@ -64,6 +64,7 @@ public:
string filename() const { return keyExtract(VL_CIK_FILENAME, m_name.c_str()); }
string comment() const { return keyExtract(VL_CIK_COMMENT, m_name.c_str()); }
string hier() const { return keyExtract(VL_CIK_HIER, m_name.c_str()); }
string page() const { return keyExtract("page", m_name.c_str()); }
string type() const { return typeExtract(m_name.c_str()); }
string thresh() const {
// string as maybe ""

View File

@ -32,6 +32,139 @@
//######################################################################
namespace {
// Report helpers only. They keep the flat summary and hierarchy report using
// the same hit/total accounting and output formatting. These helpers read the
// fields already exposed by VlcPoint; they do not affect coverage point
// identity, merging, or .dat writing.
// Map coverage type to (covered points, total points).
using TypeTally = std::map<std::string, std::pair<uint64_t, uint64_t>>;
static const char* const s_orderedTypes[]
= {"line", "toggle", "branch", "expr", "fsm_state", "fsm_arc"};
static const size_t s_summaryIndent = 2;
static const size_t s_reportRowIndent = 4;
string displayType(const VlcPoint& point) {
const string type = point.type();
return type.empty() ? "point" : type;
}
bool isCollapsedHier(const string& hier) {
return hier.find('*') != string::npos || hier.find('?') != string::npos;
}
bool isOrderedType(const string& type) {
for (const char* const typep : s_orderedTypes) {
if (type == typep) return true;
}
return false;
}
string reportHier(const VlcPoint& point) {
// FSM records currently have useful instance scope in Fv.
if (point.isFsmState() || point.isFsmArc()) {
const string fsmVar = point.fsmVarName();
return fsmVar.substr(0, fsmVar.rfind('.'));
}
return point.hier();
}
std::vector<string> splitHier(const string& hier) {
// Verilator emits dot-separated non-empty hierarchy components.
std::vector<string> parts;
string::size_type start = 0;
while (true) {
const string::size_type dot = hier.find('.', start);
if (dot == string::npos) break;
parts.push_back(hier.substr(start, dot - start));
start = dot + 1;
}
parts.push_back(hier.substr(start));
return parts;
}
string duName(const VlcPoint& point) {
// Pages are emitted as v_<type>/<design-unit> for RTL coverage. Use the
// suffix as the design-unit summary key.
const string page = point.page();
return page.substr(page.find('/') + 1);
}
void tallyPoint(TypeTally& tally, const string& type, uint64_t count) {
std::pair<uint64_t, uint64_t>& entry = tally[type];
if (count > 0) ++entry.first;
++entry.second;
}
// Keep the percentage calculation in one place so flat summaries and hierarchy
// reports cannot drift in formatting or zero-total handling.
double pct(uint64_t hit, uint64_t total) {
return total ? (100.0 * static_cast<double>(hit) / static_cast<double>(total)) : 0.0;
}
// Shared row formatter. The callers choose which rows to print; this only keeps
// the text layout identical between the flat and hierarchy reports.
void printIndent(size_t indent) {
for (size_t i = 0; i < indent; ++i) std::cout << ' ';
}
void printTallyRow(const string& type, uint64_t hit, uint64_t total, size_t indent,
size_t typeWidth, size_t countWidth) {
printIndent(indent);
std::cout << std::left << std::setw(typeWidth) << type << " : " << std::right << std::fixed
<< std::setprecision(1) << pct(hit, total) << "% (" << std::setw(countWidth) << hit
<< "/" << std::setw(countWidth) << total << ")\n";
}
size_t countWidth(const TypeTally& tally) {
size_t width = cvtToStr(0).size();
for (TypeTally::const_iterator it = tally.begin(); it != tally.end(); ++it) {
width = std::max(width, cvtToStr(it->second.first).size());
width = std::max(width, cvtToStr(it->second.second).size());
}
return width;
}
size_t typeWidth(const TypeTally& tally) {
size_t typeWidth = 0;
for (const char* const typep : s_orderedTypes) {
const string type = typep;
typeWidth = std::max(typeWidth, type.size());
}
for (TypeTally::const_iterator it = tally.begin(); it != tally.end(); ++it) {
typeWidth = std::max(typeWidth, it->first.size());
}
return typeWidth;
}
void printTypeTally(const TypeTally& tally, size_t indent, bool includeMissingOrdered) {
// Print standard coverage types first for stable output. When requested,
// missing standard rows are printed with zero counts for compatibility with
// the historical flat summary output.
const size_t typWidth = typeWidth(tally);
const size_t cntWidth = countWidth(tally);
for (const char* const typep : s_orderedTypes) {
const string type = typep;
const TypeTally::const_iterator it = tally.find(type);
if (it != tally.end()) {
printTallyRow(type, it->second.first, it->second.second, indent, typWidth, cntWidth);
} else if (includeMissingOrdered) {
printTallyRow(type, 0, 0, indent, typWidth, cntWidth);
}
}
for (TypeTally::const_iterator it = tally.begin(); it != tally.end(); ++it) {
if (!isOrderedType(it->first)) {
printTallyRow(it->first, it->second.first, it->second.second, indent, typWidth,
cntWidth);
}
}
}
} // namespace
void VlcTop::readCoverage(const string& filename, bool nonfatal) {
UINFO(2, "readCoverage " << filename);
@ -372,47 +505,69 @@ void VlcTop::annotate(const string& dirname) {
}
void VlcTop::printTypeSummary() {
static const std::vector<std::string> orderedTypes
= {"line", "toggle", "branch", "expr", "fsm_state", "fsm_arc"};
std::map<std::string, std::pair<uint64_t, uint64_t>> tally;
for (const auto& i : m_points) {
TypeTally tally;
for (VlcPoints::ByName::value_type& i : m_points) {
const VlcPoint& pt = m_points.pointNumber(i.second);
const string type = pt.type().empty() ? "point" : pt.type();
auto& entry = tally[type];
if (pt.count() > 0) ++entry.first;
++entry.second;
tallyPoint(tally, displayType(pt), pt.count());
}
if (tally.empty()) return;
std::set<std::string> printed;
size_t typeWidth = 0;
size_t countWidth = 0;
for (const string& type : orderedTypes) typeWidth = std::max(typeWidth, type.size());
countWidth = std::max(countWidth, cvtToStr(0).size());
for (const auto& it : tally) {
typeWidth = std::max(typeWidth, it.first.size());
countWidth = std::max(countWidth, cvtToStr(it.second.first).size());
countWidth = std::max(countWidth, cvtToStr(it.second.second).size());
}
std::cout << "Coverage Summary:\n";
for (const string& type : orderedTypes) {
const auto it = tally.find(type);
printed.insert(type);
const uint64_t hit = (it == tally.end()) ? 0 : it->second.first;
const uint64_t total = (it == tally.end()) ? 0 : it->second.second;
const double pct
= total ? (100.0 * static_cast<double>(hit) / static_cast<double>(total)) : 0.0;
std::cout << " " << std::left << std::setw(typeWidth) << type << " : " << std::right
<< std::fixed << std::setprecision(1) << pct << "% (" << std::setw(countWidth)
<< hit << "/" << std::setw(countWidth) << total << ")\n";
// Keep the legacy summary behavior of showing standard coverage types even
// when the input has no points of that type.
printTypeTally(tally, s_summaryIndent, true);
}
void VlcTop::printHierarchyReport() {
std::map<string, TypeTally> hierTallies;
std::map<string, TypeTally> duTallies;
bool hasHier = false;
bool hasCollapsedHier = false;
for (VlcPoints::ByName::value_type& i : m_points) {
const VlcPoint& pt = m_points.pointNumber(i.second);
const string hier = reportHier(pt);
if (hier.empty()) continue;
hasHier = true;
if (isCollapsedHier(hier)) hasCollapsedHier = true;
const string type = displayType(pt);
const std::vector<string> parts = splitHier(hier);
string path;
for (std::vector<string>::const_iterator it = parts.begin(); it != parts.end(); ++it) {
path = path.empty() ? *it : path + "." + *it;
tallyPoint(hierTallies[path], type, pt.count());
}
tallyPoint(duTallies[duName(pt)], type, pt.count());
}
for (const auto& it : tally) {
if (printed.count(it.first)) continue;
const uint64_t hit = it.second.first;
const uint64_t total = it.second.second;
const double pct
= total ? (100.0 * static_cast<double>(hit) / static_cast<double>(total)) : 0.0;
std::cout << " " << std::left << std::setw(typeWidth) << it.first << " : " << std::right
<< std::fixed << std::setprecision(1) << pct << "% (" << std::setw(countWidth)
<< hit << "/" << std::setw(countWidth) << total << ")\n";
if (!hasHier) {
std::cout << "%Warning: --report hierarchy input has no hierarchy fields; "
<< "printing flat summary instead.\n";
printTypeSummary();
return;
}
const int levels = opt.reportLevels();
if (hasCollapsedHier) {
std::cout << "Note: hierarchy report contains collapsed hierarchy paths; "
<< "it is not precise per-instance coverage.\n";
}
std::cout << "Hierarchy Coverage Summary:\n";
for (std::map<string, TypeTally>::const_iterator it = hierTallies.begin();
it != hierTallies.end(); ++it) {
const std::vector<string> parts = splitHier(it->first);
if (levels >= 0 && static_cast<int>(parts.size()) > levels + 1) continue;
printIndent(s_summaryIndent);
std::cout << it->first << "\n";
// Hierarchy nodes can be numerous, so only print coverage types present
// under this node instead of repeating absent zero-count rows.
printTypeTally(it->second, s_reportRowIndent, false);
}
std::cout << "Design Unit Coverage Summary:\n";
for (std::map<string, TypeTally>::const_iterator it = duTallies.begin(); it != duTallies.end();
++it) {
printIndent(s_summaryIndent);
std::cout << it->first << "\n";
// Design-unit summaries follow the hierarchy report style: present
// types only, but in the same stable order as the flat summary.
printTypeTally(it->second, s_reportRowIndent, false);
}
}

View File

@ -55,6 +55,7 @@ public:
// METHODS
void annotate(const string& dirname);
void printHierarchyReport();
void printTypeSummary();
void readCoverage(const string& filename, bool nonfatal = false);
void writeCoverage(const string& filename);

View File

@ -0,0 +1,40 @@
# DESCRIPTION: Verilator: coverage test helpers
#
# 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 os
import re
def vlcov_bin():
return os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage"
def vlcov_run_context(test, log, tmp_log):
return test, log, tmp_log
def init_log(log):
with open(log, "w", encoding="utf-8"):
pass
def run_vlcov(context, label, args, fails=False, normalize_errors=False):
test, log, tmp_log = context
with open(log, "a", encoding="utf-8") as log_fh:
if log_fh.tell():
log_fh.write("\n")
log_fh.write("$ " + label + "\n")
test.run(cmd=[vlcov_bin(), *args], logfile=tmp_log, fails=fails, tee=False, verilator_run=True)
with open(tmp_log, encoding="utf-8") as in_fh:
text = in_fh.read()
if normalize_errors:
text = re.sub(r"verilator_doc[.]html[?]v=[^ ]+", "verilator_doc.html?v=latest", text)
with open(log, "a", encoding="utf-8") as log_fh:
log_fh.write(text)

View File

@ -0,0 +1,251 @@
$ verilator_coverage --report summary t/t_vlcov_data_e.dat
Coverage Summary:
line : 88.6% ( 39/ 44)
toggle : 33.3% ( 35/105)
branch : 78.1% ( 50/ 64)
expr : 66.7% ( 8/ 12)
fsm_state : 0.0% ( 0/ 0)
fsm_arc : 0.0% ( 0/ 0)
$ verilator_coverage t/t_cover_hier.out
Coverage Summary:
line : 100.0% (4/4)
toggle : 0.0% (0/0)
branch : 100.0% (4/4)
expr : 0.0% (0/0)
fsm_state : 0.0% (0/0)
fsm_arc : 0.0% (0/0)
user : 100.0% (2/2)
$ verilator_coverage --report summary,hier t/t_cover_hier.out
Coverage Summary:
line : 100.0% (4/4)
toggle : 0.0% (0/0)
branch : 100.0% (4/4)
expr : 0.0% (0/0)
fsm_state : 0.0% (0/0)
fsm_arc : 0.0% (0/0)
user : 100.0% (2/2)
Hierarchy Coverage Summary:
top
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t.u_a
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
top.t.u_a.same_stmt
user : 100.0% (1/1)
top.t.u_b
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
top.t.u_b.same_stmt
user : 100.0% (1/1)
Design Unit Coverage Summary:
child
line : 100.0% (2/2)
branch : 100.0% (4/4)
user : 100.0% (2/2)
t
line : 100.0% (2/2)
$ verilator_coverage --report hier,summary --levels 0 t/t_cover_hier.out
Coverage Summary:
line : 100.0% (4/4)
toggle : 0.0% (0/0)
branch : 100.0% (4/4)
expr : 0.0% (0/0)
fsm_state : 0.0% (0/0)
fsm_arc : 0.0% (0/0)
user : 100.0% (2/2)
Hierarchy Coverage Summary:
top
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
Design Unit Coverage Summary:
child
line : 100.0% (2/2)
branch : 100.0% (4/4)
user : 100.0% (2/2)
t
line : 100.0% (2/2)
$ verilator_coverage --report hier,summary --levels -4 t/t_cover_hier.out
Coverage Summary:
line : 100.0% (4/4)
toggle : 0.0% (0/0)
branch : 100.0% (4/4)
expr : 0.0% (0/0)
fsm_state : 0.0% (0/0)
fsm_arc : 0.0% (0/0)
user : 100.0% (2/2)
Hierarchy Coverage Summary:
top
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t.u_a
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
top.t.u_a.same_stmt
user : 100.0% (1/1)
top.t.u_b
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
top.t.u_b.same_stmt
user : 100.0% (1/1)
Design Unit Coverage Summary:
child
line : 100.0% (2/2)
branch : 100.0% (4/4)
user : 100.0% (2/2)
t
line : 100.0% (2/2)
$ verilator_coverage --report summary empty.dat
$ verilator_coverage --report hierarchy t/t_cover_hier.out
Hierarchy Coverage Summary:
top
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t.u_a
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
top.t.u_a.same_stmt
user : 100.0% (1/1)
top.t.u_b
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
top.t.u_b.same_stmt
user : 100.0% (1/1)
Design Unit Coverage Summary:
child
line : 100.0% (2/2)
branch : 100.0% (4/4)
user : 100.0% (2/2)
t
line : 100.0% (2/2)
$ verilator_coverage --report hierarchy --levels 2 t/t_cover_hier.out
Hierarchy Coverage Summary:
top
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t.u_a
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
top.t.u_b
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
Design Unit Coverage Summary:
child
line : 100.0% (2/2)
branch : 100.0% (4/4)
user : 100.0% (2/2)
t
line : 100.0% (2/2)
$ verilator_coverage --report hierarchy --levels -1 t/t_cover_hier.out
Hierarchy Coverage Summary:
top
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t.u_a
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
top.t.u_a.same_stmt
user : 100.0% (1/1)
top.t.u_b
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
top.t.u_b.same_stmt
user : 100.0% (1/1)
Design Unit Coverage Summary:
child
line : 100.0% (2/2)
branch : 100.0% (4/4)
user : 100.0% (2/2)
t
line : 100.0% (2/2)
$ verilator_coverage --report hier --levels 2 t/t_cover_hier.out
Hierarchy Coverage Summary:
top
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t
line : 100.0% (4/4)
branch : 100.0% (4/4)
user : 100.0% (2/2)
top.t.u_a
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
top.t.u_b
line : 100.0% (1/1)
branch : 100.0% (2/2)
user : 100.0% (1/1)
Design Unit Coverage Summary:
child
line : 100.0% (2/2)
branch : 100.0% (4/4)
user : 100.0% (2/2)
t
line : 100.0% (2/2)
$ verilator_coverage --report hierarchy collapsed.dat
Note: hierarchy report contains collapsed hierarchy paths; it is not precise per-instance coverage.
Hierarchy Coverage Summary:
tb
line : 50.0% (1/2)
branch : 50.0% (1/2)
tb.cluster*
line : 50.0% (1/2)
branch : 100.0% (1/1)
tb.cluster*.u_core
line : 50.0% (1/2)
branch : 100.0% (1/1)
tb.cluster?
branch : 0.0% (0/1)
tb.cluster?.u_core
branch : 0.0% (0/1)
Design Unit Coverage Summary:
core
line : 50.0% (1/2)
branch : 50.0% (1/2)

View File

@ -0,0 +1,72 @@
#!/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
from coverage_common import init_log, run_vlcov, vlcov_run_context
test.scenarios('dist')
log = test.obj_dir + "/vlcov.log"
tmp_log = test.obj_dir + "/vlcov.tmp"
init_log(log)
vlcov = vlcov_run_context(test, log, tmp_log)
collapsed_cov = test.obj_dir + "/collapsed.dat"
with open(collapsed_cov, "w", encoding="utf-8") as fh:
fh.write("# SystemC::Coverage-3\n")
fh.write("C '\001f\002t/soc.v\001l\00210\001t\002line\001page\002v_line/core"
"\001h\002tb.cluster*.u_core' 4\n")
fh.write("C '\001f\002t/soc.v\001l\00211\001t\002line\001page\002v_line/core"
"\001h\002tb.cluster*.u_core' 0\n")
fh.write("C '\001f\002t/soc.v\001l\00212\001t\002branch\001page\002v_branch/core"
"\001h\002tb.cluster*.u_core' 9\n")
fh.write("C '\001f\002t/soc.v\001l\00213\001t\002branch\001page\002v_branch/core"
"\001h\002tb.cluster?.u_core' 0\n")
empty_cov = test.obj_dir + "/empty.dat"
with open(empty_cov, "w", encoding="utf-8") as fh:
fh.write("# SystemC::Coverage-3\n")
run_vlcov(vlcov,
"verilator_coverage --report summary t/t_vlcov_data_e.dat",
args=["--report", "summary", "t/t_vlcov_data_e.dat"])
run_vlcov(vlcov, "verilator_coverage t/t_cover_hier.out", args=["t/t_cover_hier.out"])
run_vlcov(vlcov,
"verilator_coverage --report summary,hier t/t_cover_hier.out",
args=["--report", "summary,hier", "t/t_cover_hier.out"])
run_vlcov(vlcov,
"verilator_coverage --report hier,summary --levels 0 t/t_cover_hier.out",
args=["--report", "hier,summary", "--levels", "0", "t/t_cover_hier.out"])
run_vlcov(vlcov,
"verilator_coverage --report hier,summary --levels -4 t/t_cover_hier.out",
args=["--report", "hier,summary", "--levels", "-4", "t/t_cover_hier.out"])
run_vlcov(vlcov,
"verilator_coverage --report summary empty.dat",
args=["--report", "summary", empty_cov])
run_vlcov(vlcov,
"verilator_coverage --report hierarchy t/t_cover_hier.out",
args=["--report", "hierarchy", "t/t_cover_hier.out"])
run_vlcov(vlcov,
"verilator_coverage --report hierarchy --levels 2 t/t_cover_hier.out",
args=["--report", "hierarchy", "--levels", "2", "t/t_cover_hier.out"])
run_vlcov(vlcov,
"verilator_coverage --report hierarchy --levels -1 t/t_cover_hier.out",
args=["--report", "hierarchy", "--levels", "-1", "t/t_cover_hier.out"])
run_vlcov(vlcov,
"verilator_coverage --report hier --levels 2 t/t_cover_hier.out",
args=["--report", "hier", "--levels", "2", "t/t_cover_hier.out"])
run_vlcov(vlcov,
"verilator_coverage --report hierarchy collapsed.dat",
args=["--report", "hierarchy", collapsed_cov])
test.files_identical(log, test.golden_filename)
test.passes()

View File

@ -0,0 +1,17 @@
$ verilator_coverage --report unknown t/t_cover_hier.out
%Error: Invalid --report option: unknown
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
$ verilator_coverage --report summary,unknown t/t_cover_hier.out
%Error: Invalid --report option: summary,unknown
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
$ verilator_coverage --report hierarchy missing.dat
%Warning: --report hierarchy input has no hierarchy fields; printing flat summary instead.
Coverage Summary:
line : 100.0% (1/1)
toggle : 0.0% (0/0)
branch : 0.0% (0/0)
expr : 0.0% (0/0)
fsm_state : 0.0% (0/0)
fsm_arc : 0.0% (0/0)

View File

@ -0,0 +1,43 @@
#!/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
from coverage_common import init_log, run_vlcov, vlcov_run_context
test.scenarios('dist')
log = test.obj_dir + "/vlcov.log"
tmp_log = test.obj_dir + "/vlcov.tmp"
init_log(log)
vlcov = vlcov_run_context(test, log, tmp_log)
missing_cov = test.obj_dir + "/missing.dat"
with open(missing_cov, "w", encoding="utf-8") as fh:
fh.write("# SystemC::Coverage-3\n")
fh.write("C '\001f\002t/missing.v\001l\0021\001t\002line\001page\002v_line/missing' 1\n")
run_vlcov(vlcov,
"verilator_coverage --report unknown t/t_cover_hier.out",
args=["--report", "unknown", "t/t_cover_hier.out"],
fails=True,
normalize_errors=True)
run_vlcov(vlcov,
"verilator_coverage --report summary,unknown t/t_cover_hier.out",
args=["--report", "summary,unknown", "t/t_cover_hier.out"],
fails=True,
normalize_errors=True)
run_vlcov(vlcov,
"verilator_coverage --report hierarchy missing.dat",
args=["--report", "hierarchy", missing_cov])
test.files_identical(log, test.golden_filename)
test.passes()

View File

@ -0,0 +1,67 @@
$ verilator_coverage --report summary coverage.dat
Coverage Summary:
line : 82.9% (29/35)
toggle : 85.4% (82/96)
branch : 78.6% (22/28)
expr : 57.1% ( 8/14)
fsm_state : 91.7% (11/12)
fsm_arc : 71.4% (10/14)
$ verilator_coverage --report hierarchy --levels 3 coverage.dat
Hierarchy Coverage Summary:
tb
line : 82.9% (29/35)
toggle : 85.4% (82/96)
branch : 78.6% (22/28)
expr : 57.1% ( 8/14)
fsm_state : 91.7% (11/12)
fsm_arc : 71.4% (10/14)
tb.cluster_a
line : 73.3% (11/15)
toggle : 76.1% (35/46)
branch : 66.7% ( 8/12)
expr : 100.0% ( 6/ 6)
fsm_state : 83.3% ( 5/ 6)
fsm_arc : 57.1% ( 4/ 7)
tb.cluster_a.u_core
line : 66.7% ( 8/12)
toggle : 83.3% (20/24)
branch : 62.5% ( 5/ 8)
fsm_state : 83.3% ( 5/ 6)
fsm_arc : 57.1% ( 4/ 7)
tb.cluster_b
line : 86.7% (13/15)
toggle : 93.5% (43/46)
branch : 83.3% (10/12)
expr : 0.0% ( 0/ 6)
fsm_state : 100.0% ( 6/ 6)
fsm_arc : 85.7% ( 6/ 7)
tb.cluster_b.u_core
line : 83.3% (10/12)
toggle : 100.0% (24/24)
branch : 87.5% ( 7/ 8)
fsm_state : 100.0% ( 6/ 6)
fsm_arc : 85.7% ( 6/ 7)
Design Unit Coverage Summary:
$root
fsm_state : 91.7% (11/12)
fsm_arc : 71.4% (10/14)
cluster
line : 100.0% ( 3/ 3)
toggle : 68.2% (15/22)
branch : 75.0% ( 3/ 4)
expr : 100.0% ( 6/ 6)
cluster__M1
line : 100.0% ( 3/ 3)
toggle : 86.4% (19/22)
branch : 75.0% ( 3/ 4)
expr : 0.0% ( 0/ 6)
fsm_core
line : 75.0% (18/24)
toggle : 91.7% (44/48)
branch : 75.0% (12/16)
tb
line : 100.0% (5/5)
toggle : 100.0% (4/4)
branch : 100.0% (4/4)
expr : 100.0% (2/2)

View File

@ -0,0 +1,46 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Runtime hierarchy report with per-instance coverage data
#
# 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
from coverage_common import init_log, run_vlcov, vlcov_run_context
test.scenarios('simulator')
if not test.have_coroutines:
test.skip("Test requires Coroutines; ignore error since not available")
test.compile(top_filename="t/t_vlcov_hier_report_runtime.v",
verilator_flags2=[
"--binary",
"--coverage",
"--coverage-fsm",
"--coverage-per-instance",
"--top-module",
"tb",
"--timing",
])
test.execute(all_run_flags=[" +verilator+coverage+file+" + test.obj_dir + "/coverage.dat"])
summary_log = test.obj_dir + "/summary.log"
hier_log = test.obj_dir + "/hierarchy.log"
combined_log = test.obj_dir + "/vlcov.log"
init_log(combined_log)
run_vlcov(vlcov_run_context(test, combined_log, summary_log),
"verilator_coverage --report summary coverage.dat",
args=["--report", "summary", test.obj_dir + "/coverage.dat"])
run_vlcov(vlcov_run_context(test, combined_log, hier_log),
"verilator_coverage --report hierarchy --levels 3 coverage.dat",
args=["--report", "hierarchy", "--levels", "3", test.obj_dir + "/coverage.dat"])
test.files_identical(combined_log, test.golden_filename)
test.passes()

View File

@ -0,0 +1,117 @@
// DESCRIPTION: Verilator: Runtime hierarchy coverage data for report tests
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module fsm_core (
input logic clk,
input logic rst,
input logic start,
input logic finish,
input logic fail,
input logic retry
);
typedef enum logic [2:0] {
S_IDLE = 3'd0,
S_LOAD = 3'd1,
S_RUN = 3'd2,
S_WAIT = 3'd3,
S_DONE = 3'd4,
S_ERR = 3'd5
} state_t;
state_t state_q /*verilator fsm_reset_arc*/;
state_t state_d;
always_comb begin
state_d = state_q;
case (state_q)
S_IDLE: if (start) state_d = S_LOAD;
S_LOAD: state_d = S_RUN;
S_RUN: begin
if (fail) state_d = S_ERR;
else if (finish) state_d = S_DONE;
else state_d = S_WAIT;
end
S_WAIT: begin
if (finish) state_d = S_DONE;
else state_d = S_RUN;
end
S_DONE: state_d = S_IDLE;
S_ERR: begin
if (retry) state_d = S_LOAD;
else state_d = S_ERR;
end
default: state_d = S_ERR;
endcase
end
always_ff @(posedge clk) begin
if (rst) state_q <= S_IDLE;
else state_q <= state_d;
end
endmodule
module cluster #(
parameter int MODE = 0
) (
input logic clk,
input logic rst
);
logic [4:0] cyc = 0;
logic start;
logic finish;
logic fail;
logic retry;
always_ff @(posedge clk) begin
if (rst) cyc <= 0;
else cyc <= cyc + 1'b1;
end
always_comb begin
start = 1'b0;
finish = 1'b0;
fail = 1'b0;
retry = 1'b0;
if (MODE == 0) begin
start = (cyc == 1) || (cyc == 7);
finish = (cyc == 4) || (cyc == 10);
end else begin
start = (cyc == 1);
fail = (cyc == 3);
retry = (cyc == 6);
finish = (cyc == 9);
end
end
fsm_core u_core (
.clk(clk),
.rst(rst),
.start(start),
.finish(finish),
.fail(fail),
.retry(retry)
);
endmodule
module tb;
logic clk = 0;
logic rst = 1;
int cyc = 0;
always #1 clk = !clk;
always_ff @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 0;
if (cyc == 14) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
cluster #(.MODE(0)) cluster_a (.clk(clk), .rst(rst));
cluster #(.MODE(1)) cluster_b (.clk(clk), .rst(rst));
endmodule