From 947a08965e25eaf60b9702238f8f7c1c815a272d Mon Sep 17 00:00:00 2001 From: Yogish Sekhar <160029258+ysekhar@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:32:19 +0000 Subject: [PATCH] Add hierarchy-aware reporting to `verilator_coverage` (#7657) --- bin/verilator_coverage | 6 + docs/guide/exe_verilator_coverage.rst | 23 ++ src/VlcMain.cpp | 28 +- src/VlcOptions.h | 9 + src/VlcPoint.h | 1 + src/VlcTop.cpp | 229 +++++++++++++--- src/VlcTop.h | 1 + test_regress/t/coverage_common.py | 40 +++ test_regress/t/t_vlcov_hier_report.out | 251 ++++++++++++++++++ test_regress/t/t_vlcov_hier_report.py | 72 +++++ test_regress/t/t_vlcov_hier_report_bad.out | 17 ++ test_regress/t/t_vlcov_hier_report_bad.py | 43 +++ .../t/t_vlcov_hier_report_runtime.out | 67 +++++ test_regress/t/t_vlcov_hier_report_runtime.py | 46 ++++ test_regress/t/t_vlcov_hier_report_runtime.v | 117 ++++++++ 15 files changed, 912 insertions(+), 38 deletions(-) create mode 100644 test_regress/t/coverage_common.py create mode 100644 test_regress/t/t_vlcov_hier_report.out create mode 100755 test_regress/t/t_vlcov_hier_report.py create mode 100644 test_regress/t/t_vlcov_hier_report_bad.out create mode 100755 test_regress/t/t_vlcov_hier_report_bad.py create mode 100644 test_regress/t/t_vlcov_hier_report_runtime.out create mode 100755 test_regress/t/t_vlcov_hier_report_runtime.py create mode 100644 test_regress/t/t_vlcov_hier_report_runtime.v diff --git a/bin/verilator_coverage b/bin/verilator_coverage index be48ff433..4854f5117 100755 --- a/bin/verilator_coverage +++ b/bin/verilator_coverage @@ -157,6 +157,10 @@ verilator_coverage - Verilator coverage analyzer verilator_coverage --annotate + verilator_coverage --report summary,hier ... + + verilator_coverage --report hier ... + verilator_coverage -write merged.dat ... verilator_coverage -write-info merged.info ... @@ -176,7 +180,9 @@ L. --filter-type 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 Limit displayed hierarchy report depth. --rank Compute relative importance of tests. + --report [,...] Generate reports: summary, hier, or hierarchy. --unlink With --write, unlink all inputs --version Displays program version and exits. --write Write aggregate coverage results. diff --git a/docs/guide/exe_verilator_coverage.rst b/docs/guide/exe_verilator_coverage.rst index 2eac8e5fc..01c9ee782 100644 --- a/docs/guide/exe_verilator_coverage.rst +++ b/docs/guide/exe_verilator_coverage.rst @@ -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 + + 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 [,...] + + 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 diff --git a/src/VlcMain.cpp b/src/VlcMain.cpp index fb485c500..3f4b1b2a9 100644 --- a/src/VlcMain.cpp +++ b/src/VlcMain.cpp @@ -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(); } diff --git a/src/VlcOptions.h b/src/VlcOptions.h index 3e46db033..8011e6e90 100644 --- a/src/VlcOptions.h +++ b/src/VlcOptions.h @@ -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(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; } diff --git a/src/VlcPoint.h b/src/VlcPoint.h index 94fba6028..f8758cd20 100644 --- a/src/VlcPoint.h +++ b/src/VlcPoint.h @@ -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 "" diff --git a/src/VlcTop.cpp b/src/VlcTop.cpp index 4eb479e00..e46f33571 100644 --- a/src/VlcTop.cpp +++ b/src/VlcTop.cpp @@ -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>; + +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 splitHier(const string& hier) { + // Verilator emits dot-separated non-empty hierarchy components. + std::vector 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_/ 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& 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(hit) / static_cast(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 orderedTypes - = {"line", "toggle", "branch", "expr", "fsm_state", "fsm_arc"}; - std::map> 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 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(hit) / static_cast(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 hierTallies; + std::map 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 parts = splitHier(hier); + string path; + for (std::vector::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(hit) / static_cast(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::const_iterator it = hierTallies.begin(); + it != hierTallies.end(); ++it) { + const std::vector parts = splitHier(it->first); + if (levels >= 0 && static_cast(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::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); } } diff --git a/src/VlcTop.h b/src/VlcTop.h index a4dd731ee..e64f1feda 100644 --- a/src/VlcTop.h +++ b/src/VlcTop.h @@ -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); diff --git a/test_regress/t/coverage_common.py b/test_regress/t/coverage_common.py new file mode 100644 index 000000000..2028ad793 --- /dev/null +++ b/test_regress/t/coverage_common.py @@ -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) diff --git a/test_regress/t/t_vlcov_hier_report.out b/test_regress/t/t_vlcov_hier_report.out new file mode 100644 index 000000000..bc70aa8a1 --- /dev/null +++ b/test_regress/t/t_vlcov_hier_report.out @@ -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) diff --git a/test_regress/t/t_vlcov_hier_report.py b/test_regress/t/t_vlcov_hier_report.py new file mode 100755 index 000000000..f8c20fb25 --- /dev/null +++ b/test_regress/t/t_vlcov_hier_report.py @@ -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() diff --git a/test_regress/t/t_vlcov_hier_report_bad.out b/test_regress/t/t_vlcov_hier_report_bad.out new file mode 100644 index 000000000..64c71c728 --- /dev/null +++ b/test_regress/t/t_vlcov_hier_report_bad.out @@ -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) diff --git a/test_regress/t/t_vlcov_hier_report_bad.py b/test_regress/t/t_vlcov_hier_report_bad.py new file mode 100755 index 000000000..9eb631f45 --- /dev/null +++ b/test_regress/t/t_vlcov_hier_report_bad.py @@ -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() diff --git a/test_regress/t/t_vlcov_hier_report_runtime.out b/test_regress/t/t_vlcov_hier_report_runtime.out new file mode 100644 index 000000000..7f5a47177 --- /dev/null +++ b/test_regress/t/t_vlcov_hier_report_runtime.out @@ -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) diff --git a/test_regress/t/t_vlcov_hier_report_runtime.py b/test_regress/t/t_vlcov_hier_report_runtime.py new file mode 100755 index 000000000..eba1c5dec --- /dev/null +++ b/test_regress/t/t_vlcov_hier_report_runtime.py @@ -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() diff --git a/test_regress/t/t_vlcov_hier_report_runtime.v b/test_regress/t/t_vlcov_hier_report_runtime.v new file mode 100644 index 000000000..555c8ed84 --- /dev/null +++ b/test_regress/t/t_vlcov_hier_report_runtime.v @@ -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