Block coverage is a type of code coverage that measures whether each basic block of RTL code has been executed during simulation. It is one of the foundational coverage metrics collected by EDA simulators and is often the first number reported in a coverage summary — because if entire blocks of code were never reached, no other metric can tell you what happened inside them.

What You'll Learn

  • What a basic block is and how simulators identify them in RTL
  • How block coverage is measured and what it reports
  • How block coverage relates to statement and branch coverage
  • How to interpret and close block coverage holes

What Is a Basic Block?

A basic block is a maximal sequence of consecutive statements with exactly one entry point and one exit point. Once execution enters a basic block at the top, every statement in it runs — there are no conditional branches in the middle that could skip ahead. Execution only leaves at the bottom, either by falling through to the next block or by jumping to another block at a decision point.

The simulator automatically partitions your RTL into basic blocks at compile time. Decision points — if, else, case arms, loop boundaries — are where one block ends and new blocks begin.

Consider this sequential block:


always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        // Basic Block 1: reset path
        count <= '0;
        valid <= 1'b0;
    end else if (enable) begin
        // Basic Block 2: enable=1 path
        count <= count + 1;
        valid <= 1'b1;
    end else begin
        // Basic Block 3: enable=0 path
        valid <= 1'b0;
    end
end

The simulator sees three basic blocks here. Each has a single entry (the condition that selects it) and a single exit (the end of the begin/end group). Block coverage reports which of these three were executed at least once across the entire regression.

Within a basic block, all statements are treated as a unit. If the block was entered, every statement in it is considered covered. This is what distinguishes block coverage from statement coverage, which tracks each line individually.

How Block Coverage Is Measured

During compilation with coverage instrumentation enabled, the simulator inserts a counter or flag at the entry point of each basic block. During simulation, whenever execution reaches that entry point, the block is marked as hit. After the regression completes and databases are merged, the coverage report shows:

  • Total blocks in the design
  • Covered blocks — entered at least once
  • Uncovered blocks — never entered (coverage holes)
  • Block coverage % = covered / total × 100

A typical simulator report for the example above might look like:


Module: counter
  Block 1 (line 3): COVERED   -- reset path hit
  Block 2 (line 6): COVERED   -- enable=1 path hit
  Block 3 (line 9): NOT HIT   -- enable=0 path never taken

Block Coverage: 66.7% (2/3 blocks)

The hole on Block 3 immediately tells you that the testbench never drove enable=0 while out of reset — an entire scenario class was never tested.

Block Coverage in Regression Triage

In large verification environments with hundreds of tests running overnight, block coverage is typically the first metric reviewed in the morning. Uncovered blocks stand out immediately in the coverage report and directly point to missing test scenarios, making it an efficient starting point for regression triage before drilling into finer-grained metrics like condition or expression coverage.

Block Coverage vs Statement Coverage

These two metrics look similar but have a meaningful difference in granularity.

Statement coverage tracks each individual line. If three statements are inside an else block and only the first two were reached (for example, due to an early disable or a simulation anomaly), statement coverage shows partial coverage within that block.

Block coverage treats the entire else block as one unit. It only tells you whether execution entered the block at all — not which lines inside it ran. This makes it coarser than statement coverage but also faster to analyze: a block coverage hole means nothing in that block was ever tested, which is a clear and actionable finding.


always_comb begin
    result = '0;           // Line A
    if (mode == 2'b00) begin
        result = a + b;    // Block 1, Line B
        carry  = a[7] & b[7]; // Block 1, Line C
    end else begin
        result = a - b;    // Block 2, Line D
        borrow = (a < b);  // Block 2, Line E
    end
end

If mode is never 2'b00, block coverage flags Block 1 as uncovered. Statement coverage would flag both Line B and Line C as uncovered. Both identify the same hole, but statement coverage gives you the additional detail of which lines were missed within the block.

In practice, block and statement coverage are often reported together. Use block coverage to find which regions of the design were never reached, then use statement coverage to understand exactly which lines within those regions were missed.

Quick Check

Q: An else block contains five statements. The testbench never takes the else path. How many blocks are reported as uncovered, and how many statements?

Show Answer

One block is reported as uncovered (the entire else block is one basic block). Five statements are reported as uncovered (one per line inside it). Block coverage gives the coarser view; statement coverage gives the line-by-line detail.

Block Coverage vs Branch Coverage

Branch coverage and block coverage are closely related — uncovered branches and uncovered blocks often point to the same holes. The difference is in what they count and report.

Branch coverage focuses on decision points: for every if, it asks whether both the true and false outcomes were exercised. Block coverage focuses on the code regions those decisions lead to: for every basic block, it asks whether execution ever entered it.

For a simple if/else, the two metrics report the same hole in different terms. But for more complex structures — nested conditionals, multi-arm case statements — block coverage can reveal holes that are harder to spot in a branch coverage report, because it directly names the code region that was never reached rather than the decision that failed to fire.


case (opcode)
    ADD:  result = a + b;        // Block A
    SUB:  result = a - b;        // Block B
    AND:  result = a & b;        // Block C
    OR:   result = a | b;        // Block D
    default: result = '0;        // Block E
endcase

If the testbench never generates an OR opcode, block coverage directly reports Block D as uncovered. Branch coverage would report that the OR arm of the case was never taken. Both are correct — the framing differs, but the actionable outcome is the same: add a test that drives opcode = OR.

Identifying and Closing Block Coverage Holes

When the coverage report shows uncovered blocks, the process to close them follows a consistent pattern:

  1. Locate the block in the report. The tool gives you the module name, line range, and often the condition that guards entry to the block.
  2. Determine why it was never hit. Common reasons: the guarding condition was never true, a reset was never de-asserted, an error-injection path was never exercised, or a specific opcode or mode was never generated.
  3. Decide: add a test or add an exclusion. If the block is legitimately reachable, write a directed or constrained-random test that drives the missing condition. If the block is provably unreachable (e.g., a default on a fully enumerated case), document and formally exclude it.

Do not exclude a block simply because it is difficult to reach. Blocks guarding error-handling paths, overflow conditions, or protocol violation responses are often the most important to cover — they protect against exactly the scenarios most likely to cause silent failures in silicon.

Quick Check

Q: A coverage report shows the default arm of a fully enumerated case on a 2-bit signal (all four values explicitly handled) as uncovered. Should you write a test to cover it or exclude it?

Show Answer

Exclude it with documentation. If all four 2-bit values are handled by explicit arms, the default can only be reached by an X or Z value, which does not occur in normal simulation. This is a legitimate unreachable block. The exclusion should be noted in the verification plan with the justification.

Common Mistakes

Mistake #1: Confusing block coverage with full code coverage

100% block coverage means every basic block was entered at least once. It does not mean every branch direction, every condition combination, or every signal toggle was exercised. Block coverage is a necessary baseline, but it must be supplemented with branch, condition, expression, and toggle coverage for meaningful verification closure.

Mistake #2: Excluding uncovered blocks without investigation

It is tempting to exclude blocks that are hard to reach in order to hit a coverage threshold quickly. However, unreachable-looking blocks are often error-handling paths that should be tested. Every exclusion needs a documented justification explaining why the block is provably unreachable, not just inconvenient to test.

Mistake #3: Reporting block coverage per-test instead of merged

A single test may cover only 40% of blocks. Another test may cover a different 40%. Together, merged coverage may be 75%. Analyzing block coverage per-run rather than across the full merged regression gives a misleading picture and leads to unnecessary test development to cover blocks that were already hit by other tests.

Summary

In this tutorial, you learned:

  • A basic block is a maximal sequence of statements with one entry and one exit — decision points like if, else, and case arms each form separate blocks
  • Block coverage reports which blocks were entered at least once during simulation; uncovered blocks directly identify untested code regions
  • Block coverage is coarser than statement coverage (whole block vs individual lines) but finer than branch coverage in terms of naming the exact region missed
  • Closing block coverage holes involves either adding a test that drives the missing condition or formally excluding provably unreachable blocks with documented justification
Did you find what you were looking for?

Quiz

No quiz questions available for this article.