Line, branch, toggle, assertion and covergroup coverage: collect it, merge runs, render a report.
Coverage is compiled into the simulation model. You choose the coverage
types on the ryusim compile line. The model counts hits while it runs and writes
them to an LCOV-format file when it exits.
The design is a 4-bit counter. Save it as counter.sv:
module counter (
input logic clk,
input logic rst,
input logic en,
output logic [3:0] count
);
always_ff @(posedge clk) begin
if (rst)
count <= '0;
else if (en)
count <= count + 1;
end
endmodule
The testbench enables the counter only when the +enable plusarg is given, so two
runs of the same model exercise different code. Save it as tb.sv:
module tb;
logic clk = 0, rst = 1, en = 0;
logic [3:0] count;
counter dut (.clk, .rst, .en, .count);
always #5 clk = ~clk;
initial begin
@(negedge clk) rst = 0;
en = $test$plusargs("enable");
repeat (3) @(negedge clk);
$display("count=%0d", count);
$finish;
end
endmodule
Compile with line, branch and toggle coverage:
ryusim compile --top tb --coverage --coverage-branch --coverage-toggle tb.sv counter.sv
The compile log confirms each type and counts the instrumented points:
[3/4] Generating C++ simulation code...
Coverage collection enabled
Branch coverage enabled
Toggle coverage enabled
[coverage] census: 29 point(s) registered; excluded: line=0 branch=0 toggle=0 fsm=0 functional=0 assertion=0
Run the model twice. Each run writes coverage.info in the current directory,
so rename it after each run:
obj_dir/build/tb_sim
mv coverage.info idle.info
obj_dir/build/tb_sim +enable
mv coverage.info enabled.info
Compare the summary lines for counter.sv. The idle run never reaches the
increment, so it hits 3 of 4 lines, 2 of 3 branches and none of the 8 counter bit
transitions:
sed -n '/^SF:counter.sv/,/^end_of_record/p' idle.info | grep -E '^(LF|LH|BRF|BRH|TGF|TGH):'
LF:4
LH:3
BRF:3
BRH:2
TGF:8
TGH:0
Merge the two runs:
ryusim coverage merge -o merged.info idle.info enabled.info
RyuSim - Merging 2 coverage file(s)
Output: merged.info
Merge complete: merged.info
The merged file has every line and branch of the counter covered. Hit counts are summed across the inputs:
sed -n '/^SF:counter.sv/,/^end_of_record/p' merged.info | grep -E '^(DA|LF|LH|BRF|BRH):'
DA:8,8
DA:9,2
DA:10,6
DA:11,3
LF:4
LH:4
BRF:3
BRH:3
Add --verbose to ryusim coverage merge to print per-file parsing and
line and branch totals. Its --strict option is accepted but has no effect in
2.1.16. The CLI reference lists the
merge options.
ryusim coverage html runs genhtml from the
lcov package, which you install
separately (sudo apt install lcov on Ubuntu and Debian). Without it the command
stops with Error: genhtml command not found. (see
Troubleshooting).
ryusim coverage html -o coverage_html merged.info
RyuSim - Generating HTML coverage report
Input: merged.info
Output: coverage_html
Report generated: coverage_html
Open coverage_html/index.html in a browser to view
This block is not run by the site's CI, which has no lcov installed; the output above is
from lcov 2.0. The report shows line coverage per source file. Each covergroup gets its
own page, linked from a tile on the index page. genhtml
does not render toggle, FSM or assertion records. With genhtml 2.x, RyuSim adds
--ignore-errors format,source --synthesize-missing so those records do not
stop it. --title sets the report title (default
RyuSim Coverage Report).
You can also run genhtml on the file yourself. genhtml 2.x rejects the
RyuSim record types unless you pass the same --ignore-errors format.
Any of these options turns coverage on. Line coverage is always collected once coverage is on, and covergroups are recorded with any of them.
| Option | Records | Records in the file |
|---|---|---|
--coverage | Line coverage | DA |
--coverage-line | Line coverage. The output is the same as with --coverage. | DA |
--coverage-branch | Taken counts for if/else and case branches | BRDA |
--coverage-toggle | 0→1 and 1→0 transitions of each signal bit | TGDA |
--coverage-fsm | States and transitions of detected state machines. Partial: the records are written but no hits are counted in 2.1.16 (see Known limits). | FSMDA, FSMTDA |
--coverage-assert | Outcome counts of each assert property: PASS, FAIL, VACUOUS, DISABLED | ASSERTA |
--coverage-functional | Covergroups | CGDA and the other CG records |
--coverage on its own collects line coverage only. BRDA records
need --coverage-branch and TGDA records need
--coverage-toggle; neither is implied by --coverage.
The model writes coverage.info in the directory the simulation runs in.
--coverage-file sets another name or path at compile time. At startup the
model writes the file with every count at zero, and at exit it overwrites it with the
counts. A run that dies before exit leaves the zeroed file.
The file is LCOV tracefile text. Per source file (SF: …
end_of_record) it carries the standard LCOV line and branch records plus
RyuSim's own records. A count of - means the point was never reached.
ryusim coverage merge writes 0 in its place.
| Record | Fields |
|---|---|
DA | DA:<line>,<hits>. Summary: LF (lines found), LH (lines hit). |
BRDA | BRDA:<line>,<block>,<branch>,<taken>. Summary: BRF, BRH. |
TGDA | TGDA:<line>,<signal>,<bit>,<0to1|1to0>,<hits>. Summary: TGF, TGH. |
FSMDA | FSMDA:<line>,<state variable>,<state>,<hits>. Summary: FSMF, FSMH. |
FSMTDA | FSMTDA:<line>,<state variable>,<from>,<to>,<hits>. Summary: FSMTF, FSMTH. |
ASSERTA | ASSERTA:<line>,<label>,<outcome>,<hits>. Summary: ASSERTF, ASSERTH. |
EXCL | EXCL:<first line>,<last line>,<types>, one per excluded region. Summary: EXCLF (regions), EXCLL (lines). |
CGDA | CGDA:<covergroup>,<instance>,<coverpoint or cross>,<bin>,<hits>,<at_least>. These come before the first SF:. CGSUM and CGISUM carry the type and instance percentages; CGTMD, CGIMD and CGXB carry options and excluded bins. |
The CG record layout may still change. The merger matches covergroup bins by
name and writes * as the instance.
Covergroups (IEEE 1800-2023 clause 19) run natively and write their bins to the same
coverage file. get_coverage() and get_inst_coverage() return the
percentages during the run. The clause 19.9 system tasks work too:
$get_coverage returns the overall percentage,
$set_coverage_db_name changes the file the run writes at exit, and
$load_coverage_db reads the bins of an earlier run into this one. The
compliance matrix lists which parts of clause 19 are
supported.
This covergroup has explicit bins, an illegal bin and a cross with a
binsof ignore:
covergroup cg_mode @(posedge clk);
cp_mode: coverpoint mode {
bins idle = {0};
bins active[] = {[1:2]};
illegal_bins bad = {3};
}
cp_err: coverpoint err;
x: cross cp_mode, cp_err {
ignore_bins no_err_idle = binsof(cp_mode.idle) && binsof(cp_err) intersect {1};
}
endgroup
A run that visits modes 0, 1 and 2 with err held at 0 writes these bin
records:
CGDA:cg_mode,cg_mode,cp_mode,idle,1,1
CGDA:cg_mode,cg_mode,cp_mode,active[1],1,1
CGDA:cg_mode,cg_mode,cp_mode,active[2],1,1
CGDA:cg_mode,cg_mode,cp_mode,bad,0,1
CGDA:cg_mode,cg_mode,cp_err,auto[0],3,1
CGDA:cg_mode,cg_mode,cp_err,auto[1],0,1
CGDA:cg_mode,cg_mode,x,<idle,auto[0]>,1,1
CGDA:cg_mode,cg_mode,x,<active[1],auto[0]>,1,1
CGDA:cg_mode,cg_mode,x,<active[2],auto[0]>,1,1
Comments in the source exclude regions from coverage:
// coverage off … // coverage on (or
coverage_off/coverage_on) excludes every type.coverage line off/on, coverage branch off/on
and coverage toggle off/on exclude one type. The underscore
spellings (coverage_line_off) work too.// coverage exclude excludes the line it is on.
// verilator coverage_block_off and // verilator lint_off UNUSED do
the same.off with no matching on excludes to the end of the file.The compile's [coverage] census line counts excluded points by type, and the
file records each region as EXCL.
Put the coverage options in the compile options of your cocotb flow; the
cocotb guide shows where they go for the Makefile,
the Python Runner and pytest. The model writes coverage.info in the directory
the simulation runs in, which that guide gives for each flow. Coverage options are compiled
in, so delete the build directory after you change them.
BRDA records carry line 1, not the line of the branch. For the
same reason ryusim coverage html --branch-coverage fails under genhtml 2.x with
line 1 of counter.sv has branchcov but no linecov data.TGDA records carry line 0, and the signal name ends with an
underscore (count_ for count).--coverage-exclude-pragmas/--coverage-no-exclude,
--coverage-warn-nested/--no-coverage-warn-nested and
--coverage-warn-unclosed/--no-coverage-warn-unclosed are accepted
but have no effect: exclusion comments
are always honored, and no warning is printed for a nested or unclosed region.DA records, and LF counts each of them. ryusim coverage
merge combines them into one record per line.rst starts at 1 and drops to 0, and its
1to0 record stays at -.