Your tests pass. How do you know what they never tried?
Your testbench is green. Every check you wrote agrees with the design. That feels like the end of the story, and part of you wants to move on.
But a green run tries only the inputs it contains. Tests That Can Fail showed a passing test on a broken design: the inputs that would have exposed the bug were never driven. This lesson counts what your tests actually reached, so the untried inputs stop hiding.
Functional coverage counts the situations your tests reached. For the course counter the situations are eight input combinations of reset, clear and event, plus four events from the spec: the count reaching 15, the wrap from 15 to 0, reset arriving while the count is nonzero, and holding a nonzero count through an idle cycle.
A hole is a situation no test reached. Holes matter because an untried situation can hide any bug. If reset never arrives while the count is nonzero, a broken reset priority passes every run, which is exactly the blind spot lesson 5.1 measured.
Code coverage is the other kind. It counts lines and branches the simulator executed, and it appears in this lesson only in words. It answers a weaker question: every line of a small counter runs within a few cycles, yet the wrap from 15 to 0 may never happen. The lines ran; the situation that matters did not.
The figure below puts all three runs of this lesson side by side, measured on macOS, Icarus Verilog 13.0. Columns are the runs: directed only, with 400 random cycles added, and with reset and clear made rarer. Rows are the twelve situations: the eight input combinations written as {reset, clear, valid}, then the four counter events. Each cell shows how many times the run reached that situation, and a cell marked none is a hole.
Two patterns already show. Random cycles fill the common combinations and leave the rare corners. The last two rows move only when reset stays away long enough for the count to climb.
The listing below is the complete experiment: the course counter followed by a testbench that counts reached situations instead of only checking outputs. The testbench drives seven directed cycles, then adds 400 random cycles when the run flag +random is present, and makes reset and clear rarer when +rare is present.
`timescale 1ns/1ps
// Counts accepted events, 4 bits, wraps 15 -> 0.
// Priority: reset > clear > count > hold. Reset is synchronous, active low.
module mini_controller (
input logic clk,
input logic rst_n,
input logic event_valid,
input logic clear_count,
output logic [3:0] count
);
always_ff @(posedge clk) begin
if (!rst_n)
count <= 4'd0;
else if (clear_count)
count <= 4'd0;
else if (event_valid)
count <= count + 4'd1;
end
endmodule
`timescale 1ns/1psThis listing is the design under test (DUT), the hardware, plus the testbench that drives it and counts. The counters sit beside the stimulus: one counter per input combination, and one flag per counter event.
# captured terminal output
$ iverilog -g2012 -s tb_coverage -o build/cov.vvp rtl/mini_controller.sv tb/tb_coverage.sv && vvp build/cov.vvp | grep -v -E 'VCD|finish'
input combination {rst_n,clear,valid}: hits
000 : 1
001 : 0 <-- never exercised
010 : 0 <-- never exercised
011 : 0 <-- never exercised
100 : 1
101 : 4
110 : 0 <-- never exercised
111 : 1
count reached 15: 0 <-- never exercised
wrapped 15 -> 0: 0 <-- never exercised
reset while count nonzero: 0 <-- never exercised
held a nonzero count: 1
PASS: coverage report printedThe first command compiles the pair and runs the directed sequence. Measured on macOS, Icarus Verilog 13.0, the seven directed cycles reach four of the eight combinations: 000, 100, 101 and 111. The combinations 001, 010, 011 and 110 never occur. The count never reaches 15, never wraps, and reset never arrives while the count is nonzero; only the nonzero hold is reached, once.
# captured terminal output
$ vvp build/cov.vvp +random | grep -v -E 'VCD|finish'
input combination {rst_n,clear,valid}: hits
000 : 20
001 : 20
010 : 4
011 : 1
100 : 155
101 : 159
110 : 22
111 : 26
count reached 15: 0 <-- never exercised
wrapped 15 -> 0: 0 <-- never exercised
reset while count nonzero: 29
held a nonzero count: 101
PASS: coverage report printedThe second command replays the same binary with +random, which adds 400 random cycles. Measured on the same platform and versions, every combination is now reached at least once, reset arrives while the count is nonzero 29 times, and nonzero holds reach 101. But the count still never reaches 15 and never wraps: reset is asserted in 45 of the 407 cycles, and each reset returns the count to 0, so long counting runs keep getting cut short.
010 and 011: reset and clear together, with and without a valid event. Each input is rare alone, and the testbench draws them independently, so the pair is rare squared. Random stimulus fills common holes and leaves the rare corners, which need directed cycles instead.
# captured terminal output
$ vvp build/cov.vvp +random +rare | grep -v -E 'VCD|finish'
input combination {rst_n,clear,valid}: hits
000 : 3
001 : 6
010 : 0 <-- never exercised
011 : 0 <-- never exercised
100 : 192
101 : 197
110 : 6
111 : 3
count reached 15: 6
wrapped 15 -> 0: 4
reset while count nonzero: 6
held a nonzero count: 176
PASS: coverage report printedThe third command adds +rare, which makes reset and clear rarer. Measured on the same platform and versions, the count reaches 15 six times and wraps four times, and holds climb to 176. But the combinations 010 and 011 are still never exercised: reset and clear together, with and without a valid event. Two rare inputs coincide so seldom that random cycles keep missing the pair, so a directed case is needed. The strong testbench of Self-Checking and Golden Model carries exactly such a case: reset together with clear and valid.
Random stimulus left 010 and 011 empty: reset and clear together, without and with a valid event. A directed cycle fills a hole that chance cannot. In tb/tb_coverage.sv, add two cycles just before the line that starts if ($test$plusargs("random")), then run the third command again.
// snippet: two new directed cycles, in the order (rst_n, clear, valid)
cycle(0, 1, 0); // 010: reset and clear together, no event
cycle(0, 1, 1); // 011: reset and clear together, with an eventRun it and check the grid: the two rows that said never exercised should now show 1.
# captured terminal output
$ iverilog -g2012 -s tb_coverage -o build/cov.vvp rtl/mini_controller.sv tb/tb_coverage.sv && vvp build/cov.vvp +random +rare | grep -v -E 'VCD|finish'
input combination {rst_n,clear,valid}: hits
000 : 3
001 : 6
010 : 1
011 : 1
100 : 192
101 : 197
110 : 6
111 : 3
count reached 15: 6
wrapped 15 -> 0: 4
reset while count nonzero: 6
held a nonzero count: 176
PASS: coverage report printedAll eight combinations are now reached. The other numbers moved a little, because the new cycles change the count the random cycles start from; coverage numbers depend on the whole stimulus. Remember what the report does and does not say: this testbench only counts what it reaches and checks no answers. Reaching a situation is not the same as verifying it, which is why the strong testbench from lesson 5.2 checks every cycle against the model.
Treating 100 percent as proof of correctness. A full report says every situation you listed was reached. It says nothing about whether the checks were strong: lesson 5.1 showed green tests on broken designs. Pair the report with tests that can fail.
Chasing the number instead of the situations. Adding random cycles raises most counts while the rare corners sit at none. Read the named rows, especially 010 and 011 here, and close each hole with directed stimulus.
Writing covergroups before knowing which situations matter. List the situations from the spec first, as this lesson does with its eight combinations and four events, then encode them. SystemVerilog covergroups are the language feature that automates this counting; Covergroup Basics teaches the syntax.
Rule: coverage tells you what your tests reached, never whether the design is right.
010 and 011: reset asserted with clear high, without and with a valid event. Add directed cycles that drive those exact combinations, for example edges with rst_n low and clear high in both valid states, then check the count is 0. Random runs keep missing the pair because two rare inputs coincide so seldom; a directed case costs two cycles and closes the hole.
Hold reset and clear away while the event input stays high for sixteen edges: after the opening reset, repeat the counting cycle sixteen times and the count walks from 0 to 15 and wraps. The +random run misses it because reset arrives in about one cycle in eight and returns the count to 0, so long uninterrupted counting runs almost never survive.
That line coverage answers a weaker question. The increment line runs on every counting edge, so a line report looks complete while the one situation that separates wrapping from sticking at 15, the 15 to 0 step, stays untried. Count situations from the spec, not lines from the code.
Coverage counts the situations your tests reached; a hole is a situation no test reached.
Random stimulus fills common holes and leaves rare corners; directed cases close the corners.
A full report with weak checks still passes broken code, so pair coverage with tests that can fail.