Chapter 5 of 15
Code Coverage: FSM
State coverage and transition coverage, given precise vocabulary and reframed directly onto axil_regfile's aw_have/w_have latching logic -- including one state this design makes structurally impossible to reach, and the same real gap ch4's toggle report found, now visible a third way.
Ch1's mocked summary line read FSM Coverage: 50.0% (2/4 states, 3/6 transitions). This chapter gives that pair of numbers precise meaning by reframing logic this site already knows well — axil_regfile's aw_have/w_have latching (uvm-advanced ch1, and the exact signals ch4 found had never toggled) — explicitly as a small finite state machine, with named states and named arcs between them.
Naming the states
aw_have and w_have are two independent bits, but together they define one state: which side of a write, if either, has already arrived and is waiting for the other. Four combinations exist on paper:
IDLE(aw_have=0, w_have=0) — nothing latched; waiting for AW, W, or both.AW_ONLY(aw_have=1, w_have=0) — AW arrived and got latched; still waiting for W.W_ONLY(aw_have=0, w_have=1) — W arrived and got latched; still waiting for AW.BOTH_LATCHED(aw_have=1, w_have=1) — both sides latched at once.
Five transitions matter: IDLE→AW_ONLY (AW arrives alone), IDLE→W_ONLY (W arrives alone), IDLE→IDLE (both arrive together, write_fire fires directly, nothing gets latched at all), AW_ONLY→IDLE (the pending W finally arrives), and W_ONLY→IDLE (the pending AW finally arrives).
The state that can't exist
BOTH_LATCHED isn't on that transition list, and that's not an oversight — the RTL makes it structurally unreachable, provably, from its own logic (uvm-advanced ch1):
assign write_fire = (aw_have || aw_fire) && (w_have || w_fire) && !s_axi_bvalid;
...
if (aw_fire && !write_fire) begin aw_addr_q <= s_axi_awaddr; aw_have <= 1'b1; end
if (w_fire && !write_fire) begin w_data_q <= s_axi_wdata; w_have <= 1'b1; endSuppose w_have were already 1 (from W_ONLY) and aw_fire then arrived. write_fire's second term, (w_have || w_fire), is already true — so write_fire goes high that same cycle, purely combinationally. That makes aw_fire && !write_fire false, so aw_have <= 1'b1 never executes. The symmetric case (aw_have already 1, then w_fire arrives) blocks the same way. There is no sequence of events that ever sets both bits at once — the moment the second side of a write shows up, write_fire fires immediately and the whole thing resolves in that cycle rather than ever latching both sides.
That's the FSM-coverage equivalent of ch2's illegal_bins: a state a real coverage tool would exclude from the denominator entirely, or flag as an error if it were ever somehow observed — not a gap to close, because closing it would mean something is actually broken.
State coverage
FSM State Coverage Report -- axil_regfile.sv (aw_have/w_have)
================================================================
State (aw_have, w_have) Hits Status
-------------- ------------------ ---- --------------------------
IDLE (0, 0) 7 covered
AW_ONLY (1, 0) 0 NOT COVERED
W_ONLY (0, 1) 0 NOT COVERED
BOTH_LATCHED (1, 1) - excluded -- structurally unreachable
IDLE is visited at reset and again after every one of chapter 1's six writes resolves. AW_ONLY and W_ONLY sit at zero — for the same reason ch4 found aw_have/w_have never toggled: nothing in this site's test history has ever sent AW and W on different cycles.
Transition coverage
State coverage alone can't tell the difference between "visited IDLE seven times, always the same way" and "visited IDLE seven times, by several different routes." Transition coverage can:
FSM Transition Coverage Report -- axil_regfile.sv (aw_have/w_have)
======================================================================
Transition Hits Status
--------------------- ---- ---------------------------------------
IDLE -> IDLE 6 covered (AW and W arrive together)
IDLE -> AW_ONLY 0 NOT COVERED
IDLE -> W_ONLY 0 NOT COVERED
AW_ONLY -> IDLE 0 NOT COVERED
W_ONLY -> IDLE 0 NOT COVERED
Every one of this site's six writes takes the exact same arc: IDLE→IDLE, a self-loop where write_fire fires the instant both aw_fire and w_fire land on the same cycle and neither bit ever needs to latch. AW_ONLY and W_ONLY reporting zero states already said as much, but the transition report is more specific: it's not just that those states are unvisited, it's that all four arcs actually involving them — entering either one, and leaving either one — are unvisited too. A design could, in principle, visit every state at least once while still never taking a particular arc between two of them; this DUT's report happens to show both failures for the same reason, but they're answering different questions.
The same gap, found three different ways
aw_have/w_have at 0% toggle (ch4), AW_ONLY/W_ONLY at 0 state hits, and four of five transitions unvisited are three separate coverage reports converging on one underlying fact: every write this site has ever issued sends AW and W together, so the logic built specifically to handle them arriving apart has never once run. That convergence isn't redundancy — toggle coverage asks whether a bit's value moved, state coverage asks whether a named condition was ever true, transition coverage asks whether a specific change between two named conditions ever happened. Three different questions, all answered "no" by the same real gap, from three angles a single metric alone wouldn't have made this clear from.
A verification-feasibility reminder
Same limitation as this module's other chapters: Icarus Verilog has no FSM coverage collection (a commercial-tool feature, per ch1), so the reports above are illustrative — derived by tracing write_fire's actual logic and this site's real write history, not produced by a tool. A commercial simulator on EDA Playground would infer this state machine directly from aw_have/w_have's usage and report against it automatically.
Summary
- FSM coverage reframes a design's control state as named states and named transitions between them — here,
aw_have/w_havebecome a 4-state machine (IDLE,AW_ONLY,W_ONLY,BOTH_LATCHED) with 5 real transitions. - Some states are unreachable by construction, not merely untested.
BOTH_LATCHEDis provably impossible givenwrite_fire's own definition — the FSM-coverage analog ofillegal_bins(ch2): excluded from the goal, not a gap to close. - State coverage (was this state ever entered) and transition coverage (was this specific arc ever taken) ask different questions — a design can visit every state without ever taking a particular arc between two of them.
AW_ONLY/W_ONLYand the four transitions touching them all report zero, for the exact reason ch4's toggle report already found: every write this site has ever issued sends AW and W on the same cycle.- The same real gap showing up in toggle, state, and transition coverage alike isn't redundant — each metric asks a genuinely different question, and getting the same "no" from all three is stronger evidence than any one of them alone.
A design visits every state in an FSM at least once during a test. Does that guarantee 100% transition coverage?
Why does BOTH_LATCHED (aw_have=1, w_have=1) get excluded from the state coverage goal instead of showing up as a gap to close?
AW_ONLY and W_ONLY both report 0 state hits, and all four transitions touching them report 0 as well. What's the underlying cause?
Toggle coverage (ch4), state coverage, and transition coverage all report the same underlying gap on aw_have/w_have. Does that make two of the three metrics redundant?