What coverage measures

Tell apart "the tests passed" from "the tests exercised what matters", and describe what a functional coverage model must count.

Lesson
C1
Time
20 min
Runs on
Verilator
Check
5 questions

A packet link passed 200 random tests every night for a quarter, and its first long write in the lab corrupted memory. A constraint left over from bring-up, kind == WRITE -> len <= 4, had quietly kept every write short. Every test passed because no test ever sent the packet that failed.

Every packet passes; one kind of packet never appears
012345kindREADWRITEREADCONFIGWRITEREADlen736128scoreboardpasslong write?-no-no-

One column per packet. Nothing fails, and nothing ever tests a long write.

Passing and exercising are different claims

A passing test says that whatever happened was correct. It says nothing about what did not happen. A scoreboard can only find bugs in traffic that reaches the design, so a regression is only as good as the situations its stimulus produces.

Functional coverage measures those situations directly. You list the cases the design must be tested in, such as every packet kind at every length, and the testbench counts how often each one occurred. A case that never occurred is a hole: a part of the design that the regression has not tested, however green it looks.

The gap is easy to miss because a passing regression produces nothing worth reading. Failures demand attention, but a test that never sends a long write prints nothing at all, which looks exactly like success.

Code coverage and functional coverage

Simulators can also measure code coverage: which lines, branches and toggles of the design ran. It needs no extra work and finds dead code, but it only knows the code that exists. It cannot tell you that a combination of inputs never arrived, or that a feature the specification requires was never written at all.

Code coverageFunctional coverage
Measureslines, branches and toggles of the code that ransituations from the specification that occurred
Written bythe simulator, automaticallyyou, as a coverage model
Findscode no test reachedcombinations and features no test reached
Cannot seemissing features and untested combinationscode nobody listed in the model

The two kinds of coverage answer different questions, so a project uses both.

In the opening story, code coverage could well have been 100%. The write path and the long-packet path each ran; only their combination, a long write, never did. Functional coverage names that combination as a case, so its absence shows up as a hole.

A coverage model comes from the specification

The list of cases is called a coverage model. Build it from the specification and the test plan, never from the design's code. The reason is the same as for a scoreboard's reference model: a model copied from the code would only ask about cases the code already handles.

For the packet link, the specification says there are three kinds of packet, lengths from 1 to 8 beats, and CONFIG packets of exactly one beat. Lengths fall into three classes that matter to the design: a single beat, a short burst of 2 to 4, and a long burst of 5 to 8. The model asks for every kind in every legal length class.

Kindone beat2 to 4 beats5 to 8 beats
READrequiredrequiredrequired
WRITErequiredrequiredrequired
CONFIGrequiredimpossibleimpossible

The coverage model for the packet link: 7 required cells and 2 impossible ones.

Review the model with the people who wrote the specification. They know which combinations matter, such as long writes, and which can never happen, such as long CONFIG packets.

Writing the model down before running anything also catches gaps in the specification. If nobody can say whether a CONFIG packet may be long, the model forces that question early.

Counting by hand, then with covergroups

The example counts the model's cells by hand, with an array, for 200 random packets. Every packet passes, and the table shows a zero in the WRITE row under 5 to 8 beats: the hole from the opening story.

A hole is information, not a failure. Here it points straight at the leftover constraint, the very bug the regression had been hiding for a quarter.

Counting by hand works for one table, but it grows quickly and every testbench would invent its own format. SystemVerilog has a language feature for this job, the covergroup, which counts, groups values into bins and reports a percentage. Lesson C2 replaces the array with one.

A coverage number is only meaningful next to the model it measures. A model that leaves out long writes can reach 100% and say nothing about them, so review the model itself as carefully as the tests. The rest of this track builds such models with the language's own tools, and ends by testing a model itself.

Try it

All 200 packets pass, and the table makes the hole obvious: no WRITE packet ever had 5 to 8 beats. The inline constraint left from bring-up removed them, and no check could notice, because nothing was wrong with the packets that were sent.

Delete the with { ... } part of the randomize() call and predict which cells change. The counts per cell depend on the simulator and seed; whether a cell is zero does not.

RUNVerilator ยท simulator service
Edit the code, then run it
Expected: 200 packets sent, 200 passed len 1 len 2-4 len 5-8 READ 2 27 134 WRITE 7 22 0 CONFIG 8 0 0

Check yourself

Predict the outputThe function below has three branches, and the test calls it three times. Predict the exact output.
typedef enum bit [1:0] {READ, WRITE, CONFIG} kind_e;

// One packet on the link. CONFIG packets carry a single beat.
class packet;
  rand kind_e    kind;
  rand bit [3:0] len;              // payload beats, 1 to 8
  constraint legal {
    kind inside {READ, WRITE, CONFIG};
    len inside {[1:8]};
    kind == CONFIG -> len == 1;
  }
endclass

module tb;
  bit took [3];
  bit seen [3][3];
  function automatic void handle(kind_e k, bit [3:0] len);
    if (k == CONFIG) took[0] = 1;
    else if (len > 4) took[1] = 1;
    else took[2] = 1;
    seen[k][len == 1 ? 0 : len <= 4 ? 1 : 2] = 1;
  endfunction
  initial begin
    int branches, cells;
    handle(READ, 6);
    handle(WRITE, 2);
    handle(CONFIG, 1);
    foreach (took[i]) branches += took[i];
    foreach (seen[k, c]) cells += seen[k][c];
    $display("branches %0d of 3, model cells %0d of 7", branches, cells);
  end
endmodule
Spot the bugEach option is the body of function bit model_complete(), which must return 1 when every required cell of the packet model has been seen at least once. seen[k][c] counts packets per kind and length class. Which option can never return 1?
ConceptLine and branch coverage of the packet link are both 100%. What does that tell you about long writes?
ConceptWhere should the cases in a functional coverage model come from?
ConceptA regression shows 100% functional coverage and no failures. What can still be wrong?

Remember

  • A passing test shows that what happened was correct; coverage shows what happened at all.
  • Code coverage records the code that ran; functional coverage records situations from the specification.
  • Build the coverage model from the specification, and leave out cases that cannot occur.
  • A hole is a required case that never occurred: a part of the design no test has checked.
  • A coverage percentage means nothing without the model behind it.