Chapter 4 of 15
Code Coverage: Toggle
Bit-level 0->1/1->0 activity, entirely orthogonal to control flow -- and a real finding in axil_regfile's own test history: the AW/W independence logic ch1 called out as genuinely hard has literally never toggled, and s_axi_wstrb has been driven to one constant value in every test this site has ever written.
Ch1's mocked summary line read Toggle Coverage: 65.0% (130/200 bits) and moved on. This chapter opens it up — and unlike ch3's line/branch gap, which was at least visible in the source as an untaken branch, this chapter's headline finding is invisible to every metric covered so far: a feature uvm-advanced ch1 explicitly built, named, and called "genuinely harder... than anything mux2 ever required" has never once been exercised, anywhere in this site's test history — not because a branch was skipped, but because a signal never moved.
What counts as a "toggle"
Toggle coverage instruments every net and register in the design — ports, internal wires, internal flops, everything with a value — and counts two things per bit, separately: how many times it transitioned 0→1, and how many times it transitioned 1→0. A bit only counts as fully covered once both directions have happened at least once; a bit that only ever rises, or only ever falls, is half covered no matter how many times that one direction occurred.
This is a genuinely different question from line or branch coverage, and it's worth being precise about how: line and branch both ask whether a piece of control flow executed. Toggle asks nothing about control flow at all — it asks whether a value actually moved. A signal can sit inside a branch that runs on every single clock cycle and still show 0% toggle coverage, if that branch always drives it to the same constant.
A real, checkable report
Four signals from axil_regfile's write logic (uvm-advanced ch1), reported against the actual sequence of writes this site's own test code has ever issued — chapter 1's directed test and every later chapter's reuse of the same axi_driver (uvm-advanced ch2):
Toggle Coverage Report -- axil_regfile.sv
====================================================
Signal Bits 0->1 1->0 Status
-------------------- ---- ---- ---- --------------------------
s_axi_bvalid 1 6 6 covered
ctrl_enable 1 1 0 PARTIAL -- 0->1 only
aw_have 1 0 0 NOT COVERED -- never toggled
w_have 1 0 0 NOT COVERED -- never toggled
s_axi_wstrb[3:0] 4 0 0 NOT COVERED -- held constant
s_axi_bvalid is what fully covered looks like: write_fire sets it once per completed write, bready (held high by every driver this site has written) clears it the next cycle, six times each direction across chapter 1's six writes. That's the baseline the other three rows are missing.
ctrl_enable: covered in one direction only
ctrl_enable goes 0→1 exactly once — chapter 1's first CTRL write (ENABLE=1) — and never 1→0. Every later CTRL write across this entire site (including the second write in chapter 1's own directed test, ENABLE=1, IRQ_CLR=1) also sets bit 0 to 1; nothing has ever written CTRL with ENABLE=0. Line and branch coverage can't see this at all — the ctrl_enable <= w_data_eff[0]; assignment (uvm-advanced ch1) executes every time CTRL is written, so that line reads fully covered. Toggle coverage is the only metric of the three that notices the bit itself has only ever moved one way. Closing this gap is small and concrete: a test just needs to write CTRL with bit 0 clear at some point.
aw_have / w_have: a real feature, never once exercised
This is the finding worth sitting with. uvm-advanced ch1 introduced aw_have/w_have as the mechanism implementing a real AXI4-Lite requirement: "a master is free to send the address and the data in either order, or at the same time, and a slave must be ready to latch whichever arrives first while it waits for the other... a genuinely harder piece of handshake timing than anything mux2 ever required." That's not an incidental signal — it's the one piece of this DUT's logic that exists specifically to handle AW and W arriving on different cycles.
Every write this site has ever issued — chapter 1's hand-written directed test and axi_driver's drive() task (uvm-advanced ch2), reused unchanged by every chapter since — asserts awvalid and wvalid in the same clock cycle, every time. Since s_axi_awready/s_axi_wready are both already high before a new write starts, aw_fire and w_fire land on the identical edge, write_fire goes high directly through the aw_fire/w_fire terms in its expression, and the aw_have <= 1'b1 / w_have <= 1'b1 assignments — guarded by if (aw_fire && !write_fire) / if (w_fire && !write_fire), conditions that are never true when both arrive together — simply never execute. Both bits sit at their reset value of 0 for the entire simulation, in every test this site has ever run. (aw_addr_q/w_data_q, the registers that would hold the early-arriving side's value, fare even worse: nothing initializes them, and the only assignments that could ever touch them are inside those same never-taken branches — they've never been anything but unknown.)
Toggle coverage is what surfaces this cleanly. Branch coverage could have shown the same gap — the guarding if statements are themselves untaken branches — but nothing in this module has looked at that specific if yet, and toggle coverage catches it a different way regardless: by noticing the bit itself never moved, without needing anyone to have already gone looking at that particular line. A feature this DUT was specifically designed and documented to handle has been sitting untested since the chapter that introduced it.
s_axi_wstrb: a gap that's real, but closing it wouldn't mean much
s_axi_wstrb reports 0% for a related but different reason. Every driver this site has written drives it to a constant 4'hF (uvm-advanced ch1's directed test, axi_driver's drive(), ch2) — so, like aw_have/w_have, it's never toggled. But look at what axil_regfile's actual write logic does with it: nothing. s_axi_wstrb is declared as a port, connected all the way through the interface, and never once referenced in the always_ff block that performs the write. Every write this DUT ever does is a full 32-bit write, regardless of what s_axi_wstrb says — a deliberate simplification of this tutorial's DUT, being named explicitly for the first time here, the same way earlier simplifications (the combinational-only driver, the single-outstanding-transaction restriction) eventually got named once they became directly relevant.
That distinction matters for what closing this particular gap would actually buy. A future test that varies s_axi_wstrb across its writes would raise the toggle number — the bits would genuinely move. But it wouldn't strengthen this DUT's verification at all, because there's no byte-strobe behavior in the design for a test to check. Doing it properly would need a design decision first (implement real per-byte write masking) and only then a test that both varies s_axi_wstrb and checks that the right bytes actually changed. Toggling the bits alone, on the design as it stands today, would be coverage that looks closed without verifying anything — a version of ch1 and ch3's classic trap that's specific to toggle coverage: 100% toggle only proves bits moved, never that the design does anything meaningful in response. Telling aw_have/w_have's gap apart from s_axi_wstrb's — a real feature nobody tested, versus a signal nobody's logic even consumes — is exactly the judgment call ch6 comes back to.
A verification-feasibility reminder
Same limitation as this module's other chapters: Icarus Verilog has no toggle coverage collection (a commercial-tool feature, per ch1), so the report above is illustrative — built by tracing every write this site's own example code has ever actually issued against the real RTL, not by running a tool. A commercial simulator on EDA Playground is where an actual toggle report, shaped like this one, would come from.
Summary
- Toggle coverage counts per-bit
0→1and1→0transitions on every net and register — completely independent of control flow. A signal can sit inside a branch that runs constantly and still show 0% toggle if it's always driven to the same value. - A bit needs both directions to count as fully covered; a bit that only ever rises (or only ever falls) is partially covered no matter how many times that one direction happened —
ctrl_enable'sENABLEbit is a real example, set once and never cleared by any test this site has written. aw_have/w_have— the exact mechanism implementing AXI4-Lite's AW/W independence rule, called out inuvm-advancedch1 as genuinely hard — have never toggled at all, because every test ever written drivesawvalidandwvalidtogether. Toggle coverage surfaces this without needing anyone to already be looking at the specificifstatement that guards it.s_axi_wstrbalso never toggles, but for a different reason: the RTL never reads it at all. Closing that gap would move bits without verifying anything, since there's no byte-strobe behavior implemented to check — a toggle-specific version of the "covered but meaningless" trap.- Telling a real, valuable gap (
aw_have/w_have) apart from a technically-closeable but hollow one (s_axi_wstrb) is a judgment call, not something the coverage number alone answers — ch6's territory.
A signal sits inside a branch that executes on every single clock cycle, but the branch always drives it to the same constant value. What does toggle coverage report for that signal?
uvm-advanced ch1 specifically built aw_have/w_have to handle AW and W arriving on different clock cycles, calling it genuinely harder than anything mux2 required. Why does the toggle report above show both signals at 0%?
ctrl_enable shows 1 hit on the 0->1 transition and 0 hits on 1->0. What would close this specific gap?
A future test varies s_axi_wstrb across several writes, closing its toggle coverage gap to 100%. Does this meaningfully strengthen axil_regfile's verification?