// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: verilator_coverage: top implementation // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* #define VL_MT_DISABLED_CODE_UNIT 1 #include "VlcTop.h" #include "V3Error.h" #include "V3Os.h" #include "VlcOptions.h" #include #include #include #include //###################################################################### void VlcTop::readCoverage(const string& filename, bool nonfatal) { UINFO(2, "readCoverage " << filename); std::ifstream is{filename.c_str()}; if (!is) { if (!nonfatal) v3fatal("Can't read coverage file: " << filename); return; } // Testrun and computrons argument unsupported as yet VlcTest* const testp = tests().newTest(filename, 0, 0); while (!is.eof()) { const string line = V3Os::getline(is); // UINFO(9, " got " << line); if (line[0] == 'C') { string::size_type secspace = 3; for (; secspace < line.length(); secspace++) { if (line[secspace] == '\'' && line[secspace + 1] == ' ') break; } const string point = line.substr(3, secspace - 3); if (!opt.isTypeMatch(point.c_str())) continue; const uint64_t hits = std::atoll(line.c_str() + secspace + 1); // UINFO(9, " point '" << point << "'" << " " << hits); const uint64_t pointnum = points().findAddPoint(point, hits); if (opt.rank()) { // Only if ranking - uses a lot of memory if (hits >= VlcBuckets::sufficient()) { points().pointNumber(pointnum).testsCoveringInc(); testp->buckets().addData(pointnum, hits); } } } } } void VlcTop::writeCoverage(const string& filename) { UINFO(2, "writeCoverage " << filename); std::ofstream os{filename.c_str()}; if (!os) { v3fatal("Can't write file: " << filename); return; } os << "# SystemC::Coverage-3\n"; for (const auto& i : m_points) { const VlcPoint& point = m_points.pointNumber(i.second); os << "C '" << point.name() << "' " << point.count() << '\n'; } } void VlcTop::writeInfo(const string& filename) { UINFO(2, "writeInfo " << filename); std::ofstream os{filename.c_str()}; if (!os) { v3fatal("Can't write file: " << filename); return; } annotateCalc(); // See 'man lcov' for format details // TN: // Source file: // SF: // FN:, // FNDA:, // FNF: // FNH: // Branches: // BRDA:,,, // BRF: // BRH: // Line counts: // DA:, // LF: // LH: // Section ending: // end_of_record os << "TN:verilator_coverage\n"; for (auto& si : m_sources) { VlcSource& source = si.second; os << "SF:" << source.name() << '\n'; VlcSource::LinenoMap& lines = source.lines(); int branchesFound = 0; int branchesHit = 0; for (auto& li : lines) { VlcSourceCount& sc = li.second; os << "DA:" << sc.lineno() << "," << sc.maxCount() << "\n"; int num_branches = sc.points().size(); if (num_branches == 1) continue; branchesFound += num_branches; int point_num = 0; for (const auto& point : sc.points()) { os << "BRDA:" << sc.lineno() << ","; os << "0,"; os << point_num << ","; os << point->count() << "\n"; branchesHit += opt.countOk(point->count()); ++point_num; } } os << "BRF:" << branchesFound << '\n'; os << "BRH:" << branchesHit << '\n'; os << "end_of_record\n"; } } //******************************************************************** struct CmpComputrons final { bool operator()(const VlcTest* lhsp, const VlcTest* rhsp) const { if (lhsp->computrons() != rhsp->computrons()) { return lhsp->computrons() < rhsp->computrons(); } return lhsp->bucketsCovered() > rhsp->bucketsCovered(); } }; void VlcTop::rank() { UINFO(2, "rank..."); uint64_t nextrank = 1; // Sort by computrons, so fast tests get selected first std::vector bytime; for (const auto& testp : m_tests) { if (testp->bucketsCovered()) { // else no points, so can't help us bytime.push_back(testp); } } sort(bytime.begin(), bytime.end(), CmpComputrons()); // Sort the vector VlcBuckets remaining; for (const auto& i : m_points) { const VlcPoint* const pointp = &points().pointNumber(i.second); // If any tests hit this point, then we'll need to cover it. if (pointp->testsCovering()) remaining.addData(pointp->pointNum(), 1); } // Additional Greedy algorithm // O(n^2) Ouch. Probably the thing to do is randomize the order of data // then hierarchically solve a small subset of tests, and take resulting // solution and move up to larger subset of tests. (Aka quick sort.) while (true) { if (debug() >= 9) { UINFO_PREFIX("Left on iter" << nextrank << ": "); // LCOV_EXCL_LINE remaining.dump(); // LCOV_EXCL_LINE } VlcTest* bestTestp = nullptr; uint64_t bestRemain = 0; for (const auto& testp : bytime) { if (!testp->rank()) { uint64_t remain = testp->buckets().dataPopCount(remaining); if (remain > bestRemain) { bestTestp = testp; bestRemain = remain; } } } if (VlcTest* const testp = bestTestp) { testp->rank(nextrank++); testp->rankPoints(bestRemain); remaining.orData(bestTestp->buckets()); } else { break; // No test covering more stuff found } } } //###################################################################### void VlcTop::annotateCalc() { // Calculate per-line information into filedata structure for (const auto& i : m_points) { const VlcPoint& point = m_points.pointNumber(i.second); const string filename = point.filename(); const int lineno = point.lineno(); if (!filename.empty() && lineno != 0) { VlcSource& source = sources().findNewSource(filename); UINFO(9, "AnnoCalc count " << filename << ":" << lineno << ":" << point.column() << " " << point.count() << " " << point.linescov()); // Base coverage source.insertPoint(lineno, &point); // Additional lines covered by this statement bool range = false; int start = 0; int end = 0; const string linescov = point.linescov(); for (const char* covp = linescov.c_str(); true; ++covp) { if (!*covp || *covp == ',') { // Ending for (int lni = start; start && lni <= end; ++lni) { source.insertPoint(lni, &point); } if (!*covp) break; start = 0; // Prep for next end = 0; range = false; } else if (*covp == '-') { range = true; } else if (std::isdigit(*covp)) { const char* const digitsp = covp; while (std::isdigit(*covp)) ++covp; --covp; // Will inc in for loop if (!range) start = std::atoi(digitsp); end = std::atoi(digitsp); } } } } } void VlcTop::annotateCalcNeeded() { // Compute which files are needed. A file isn't needed if it has appropriate // coverage in all categories int totCases = 0; int totOk = 0; for (auto& si : m_sources) { VlcSource& source = si.second; // UINFO(1, "Source " << source.name()); if (opt.annotateAll()) source.needed(true); VlcSource::LinenoMap& lines = source.lines(); for (auto& li : lines) { VlcSourceCount& sc = li.second; // UINFO(0, "Source " << source.name() << ":" << sc.lineno() << ":" << sc.column()); ++totCases; if (opt.countOk(sc.minCount())) { ++totOk; } else { source.needed(true); } } } const float pct = totCases ? (100 * totOk / totCases) : 0; std::cout << "Total coverage (" << totOk << "/" << totCases << ") "; std::cout << std::fixed << std::setw(3) << std::setprecision(2) << pct << "%\n"; if (totOk != totCases) cout << "See lines with '%00' in " << opt.annotateOut() << '\n'; } void VlcTop::annotateOutputFiles(const string& dirname) { // Create if uncreated, ignore errors V3Os::createDir(dirname); for (auto& si : m_sources) { VlcSource& source = si.second; if (!source.needed()) continue; const string filename = source.name(); const string outfilename = dirname + "/" + V3Os::filenameNonDir(filename); UINFO(1, "annotateOutputFile " << filename << " -> " << outfilename); std::ifstream is{filename.c_str()}; if (!is) { v3error("Can't read annotation file: " << filename); return; } std::ofstream os{outfilename.c_str()}; if (!os) { v3error("Can't write file: " << outfilename); return; } os << "// // verilator_coverage annotation\n"; int lineno = 0; while (!is.eof()) { lineno++; string line = V3Os::getline(is); VlcSource::LinenoMap& lines = source.lines(); const auto lit = lines.find(lineno); if (lit == lines.end()) { os << " " << line << '\n'; } else { VlcSourceCount& sc = lit->second; // UINFO(0, "Source " << source.name() << ":" << sc.lineno() << ":" << // sc.column()); const bool minOk = opt.countOk(sc.minCount()); const bool maxOk = opt.countOk(sc.maxCount()); if (minOk) { os << " "; } else if (maxOk) { os << "~"; } else { os << "%"; } os << std::setfill('0') << std::setw(6) << sc.maxCount() << " " << line << '\n'; if (opt.annotatePoints()) { for (auto& pit : sc.points()) pit->dumpAnnotate(os, opt.annotateMin()); } } } } } void VlcTop::annotate(const string& dirname) { // Calculate per-line information into filedata structure annotateCalc(); annotateCalcNeeded(); annotateOutputFiles(dirname); }