From ef3f541501851f52e5ff6e6296992eb20e5a2788 Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Thu, 26 Jun 2025 13:21:53 +0200 Subject: [PATCH] add linecoverage command to generate lcov report from selection --- passes/cmds/Makefile.inc | 1 + passes/cmds/linecoverage.cc | 148 ++++++++++++++++++++++++++++++++++++ tests/various/lcov.gold | 43 +++++++++++ tests/various/lcov.v | 55 ++++++++++++++ tests/various/lcov.ys | 4 + 5 files changed, 251 insertions(+) create mode 100644 passes/cmds/linecoverage.cc create mode 100644 tests/various/lcov.gold create mode 100644 tests/various/lcov.v create mode 100644 tests/various/lcov.ys diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 4ecaea7dd..9bf615a7e 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -56,3 +56,4 @@ OBJS += passes/cmds/setenv.o OBJS += passes/cmds/abstract.o OBJS += passes/cmds/test_select.o OBJS += passes/cmds/timeest.o +OBJS += passes/cmds/linecoverage.o diff --git a/passes/cmds/linecoverage.cc b/passes/cmds/linecoverage.cc new file mode 100644 index 000000000..01dc2973c --- /dev/null +++ b/passes/cmds/linecoverage.cc @@ -0,0 +1,148 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" + +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + + +static const std::regex src_re("(.*):(\\d+)\\.(\\d+)-(\\d+)\\.(\\d+)"); + +struct CoveragePass : public Pass { + CoveragePass() : Pass("linecoverage", "write coverage information to file") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" linecoverage [options] [selection]\n"); + log("\n"); + log("This command writes coverage information on the design based on the current\n"); + log("selection, where items in the selection are considered covered and items not in\n"); + log("the selection are considered uncovered. If the same source location is found\n"); + log("both on items inside and out of the selection, it is considered uncovered.\n"); + log("\n"); + log(" -lcov \n"); + log(" write coverage information in lcov format to this file\n"); + log("\n"); + } + + std::string extract_src_filename(std::string src) const + { + std::smatch m; + if (std::regex_match(src, m, src_re)) { + return m[1].str(); + }; + return ""; + } + + std::pair extract_src_lines(std::string src) const + { + std::smatch m; + if (std::regex_match(src, m, src_re)) { + return std::make_pair(stoi(m[2].str()), stoi(m[4].str())); + }; + return std::make_pair(0,0); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + std::string ofile; + + log_header(design, "Executing linecoverage pass.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-lcov" && argidx+1 < args.size()) { + ofile = args[++argidx]; + continue; + } + break; + } + extra_args(args, argidx, design); + + std::ofstream fout; + if (!ofile.empty()) { + fout.open(ofile, std::ios::out | std::ios::trunc); + if (!fout.is_open()) + log_error("Could not open file \"%s\" with write access.\n", ofile.c_str()); + } + + std::map> uncovered_lines; + std::map> all_lines; + + for (auto module : design->modules()) + { + log_debug("Module %s:\n", log_id(module)); + for (auto wire: module->wires()) { + log_debug("%s\t%s\t%s\n", module->selected(wire) ? "*" : " ", wire->get_src_attribute().c_str(), log_id(wire->name)); + for (auto src: wire->get_strpool_attribute(ID::src)) { + auto filename = extract_src_filename(src); + if (filename.empty()) continue; + auto [begin, end] = extract_src_lines(src); + for (int l = begin; l <=end; l++) { + if (l == 0) continue; + all_lines[filename].insert(l); + if (!module->selected(wire)) + uncovered_lines[filename].insert(l); + } + } + } + for (auto cell: module->cells()) { + log_debug("%s\t%s\t%s\n", module->selected(cell) ? "*" : " ", cell->get_src_attribute().c_str(), log_id(cell->name)); + for (auto src: cell->get_strpool_attribute(ID::src)) { + auto filename = extract_src_filename(src); + if (filename.empty()) continue; + auto [begin, end] = extract_src_lines(src); + for (int l = begin; l <=end; l++) { + if (l == 0) continue; + all_lines[filename].insert(l); + if (!module->selected(cell)) + uncovered_lines[filename].insert(l); + } + } + } + log_debug("\n"); + } + + if(!ofile.empty()) { + for (const auto& file_entry : all_lines) { + fout << "SF:" << file_entry.first << "\n"; + for (int l : file_entry.second) { + fout << "DA:" << l << ","; + if (uncovered_lines.count(file_entry.first) && uncovered_lines[file_entry.first].count(l)) + fout << "0"; + else + fout << "1"; + fout << "\n"; + } + fout << "LF:" << file_entry.second.size() << "\n"; + fout << "LH:" << (uncovered_lines.count(file_entry.first) ? uncovered_lines[file_entry.first].size() : 0) << "\n"; + fout << "end_of_record\n"; + } + } + + } +} CoveragePass; + +PRIVATE_NAMESPACE_END diff --git a/tests/various/lcov.gold b/tests/various/lcov.gold new file mode 100644 index 000000000..564783557 --- /dev/null +++ b/tests/various/lcov.gold @@ -0,0 +1,43 @@ +SF:lcov.v +DA:2,1 +DA:3,1 +DA:4,1 +DA:5,1 +DA:6,1 +DA:7,1 +DA:8,1 +DA:9,0 +DA:13,1 +DA:14,1 +DA:17,1 +DA:18,1 +DA:19,1 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,1 +DA:33,1 +DA:36,0 +DA:37,0 +DA:38,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:48,0 +DA:49,0 +DA:52,1 +DA:53,0 +LF:39 +LH:24 +end_of_record diff --git a/tests/various/lcov.v b/tests/various/lcov.v new file mode 100644 index 000000000..c481638cd --- /dev/null +++ b/tests/various/lcov.v @@ -0,0 +1,55 @@ +module top ( + input wire clk, + input wire rst, + input wire [7:0] a, + input wire [7:0] b, + input wire [3:0] c, + input wire en, + output wire [7:0] out1, + output wire [7:0] out2 +); + + // Shared intermediate signal + wire [7:0] ab_sum; + assign ab_sum = a + b; + + // Logic cone for out1 + wire [7:0] cone1_1, cone1_2; + assign cone1_1 = ab_sum ^ {4{c[1:0]}}; + assign cone1_2 = (a & b) | {4{c[3:2]}}; + + reg [7:0] reg1, reg2; // only reg1 feeds into out1, but both share a source location + always @(posedge clk or posedge rst) begin + if (rst) begin + reg1 <= 8'h00; + reg2 <= 8'hFF; + end else if (en) begin + reg1 <= cone1_1 + cone1_2; + reg2 <= cone1_2 - cone1_1; + end + end + + wire [7:0] cone1_3; + assign cone1_3 = reg1 & ~a[0]; + + // Logic cone for out2 + wire [7:0] cone2_1, cone2_2; + assign cone2_1 = (ab_sum << 1) | (a >> 2); + assign cone2_2 = (b ^ {4{c[2:0]}}) & 8'hAA; + + reg [7:0] reg3; + always @(posedge clk or posedge rst) begin + if (rst) + reg3 <= 8'h0F; + else + reg3 <= cone2_1 ^ cone2_2 ^ reg1[7:0]; + end + + wire [7:0] cone2_3; + assign cone2_3 = reg3 | (reg2 ^ 8'h55); + + // Outputs + assign out1 = cone1_3 | (reg1 ^ 8'hA5); + assign out2 = cone2_3 & (reg3 | 8'h5A); + +endmodule diff --git a/tests/various/lcov.ys b/tests/various/lcov.ys new file mode 100644 index 000000000..f1d233acc --- /dev/null +++ b/tests/various/lcov.ys @@ -0,0 +1,4 @@ +read_verilog lcov.v +prep -top top +linecoverage -lcov lcov.out o:\out1 %ci* +exec -expect-return 0 -- diff -q lcov.out lcov.gold