What a Proof Covers

Draw the cone of influence of every property, find the logic no property reads, measure the proof core by cutting, and read cover depths as bounded coverage.

Lesson
F7.1
Time
20 min
Runs on
SymbiYosys
Check
5 questions

A counter block was signed off as "all properties proven". Months later its debug register returned a stale value after every wrap. No property read that register, so no proof had ever looked at it.

The Cone of One Property

The Cone of `count <= 9` on ctr_dbgTrace back from the assertion through every gate and register that can change it. What you reach is its cone of influence; the rest is invisible to the proof.
The cone of `count <= 9` on ctr_dbgTrace back from the assertion through every gate and register that can change it. What you reach is its cone of influence; the rest is invisible to the proof. input en, fast_in free inputs in cone fast speed mode register:step by 2 in cone count wraps 9 to 0 (8 to 0when fast) no cone dbg debug mirror ofcount, one cycle late property a_cnt count <= 9 feeds, but nothingreads dbg
Read the diagram as text
  • en, fast_in: free inputs
  • fast: speed mode register: step by 2
  • count: wraps 9 to 0 (8 to 0 when fast)
  • dbg: debug mirror of count, one cycle late
  • a_cnt: count <= 9
  • en, fast_in → fast
  • fast → count
  • count → a_cnt
  • count → dbg: feeds, but nothing reads dbg

Measured by Yosys: the cone of a_cnt holds count, fast. The debug register is in no cone, so no run of this property can say anything about it.

Where the map comes from. %ci* walks the input cone of a check to a fixed point; intersecting with registers and memories keeps the state. count_q and past are bench registers.
read_verilog -formal -sv design.sv
prep -top ctr_dbg
flatten
opt_clean
write_json net.json
cd ctr_dbg
select -write cone_a_cnt.txt a_cnt %ci* t:$dff t:$mem_v2 %u %i
select -write cone_a_dbg.txt a_dbg %ci* t:$dff t:$mem_v2 %u %i

# cone_a_cnt.txt   count fast
# cone_a_dbg.txt   count count_q dbg fast past

A property reads a few signals. Those depend on registers, which depend on inputs and other registers. Everything reachable backwards from the property is its cone of influence. Logic outside the cone cannot change the verdict, so tools drop it before solving.

One More Property, a Bigger Map

ctr_dbg with the mirror bugRegisters in the conesMeasured result
count <= 9 onlycount, fastProve PASS (k = 10)
plus dbg == $past(count)count, count_q, dbg, fast, pastFAIL at reported step 8
both, mirror fixedcount, count_q, dbg, fast, pastProve PASS (k = 10)

Prove mode, smtbmc yices, depth 10, reset assumed in step 0. One property changed; the map grew over dbg and caught the bug.

The mirror misses the wrap
012345678clkfreeenfastcount0135790dbg013570count_q0135790count = 9, en = 1: the wrapdbg kept 7reported step 8

Measured: FAIL at reported step 8 (smtbmc yices, prove depth 10). In the wrap cycle the faulty mirror skips its update, so dbg is still 7 when it should be 9.

The first run was a true proof of a true property. It said nothing about dbg, because dbg was not in its cone. Coverage starts by asking which logic your properties read at all.

The Map of the Module Spine

Check on the 4 × 8 FIFORegisters in its coneBits
a_emptycount, rd_ptr, wr_ptr7
a_fullcount, rd_ptr, wr_ptr7
a_countcount3
a_levelcount, rd_ptr, wr_ptr7
a_e2ecount, dut.mem, rd_ptr, sb_n, sb0, sb1, sb2, sb3, wr_ptr74

The four glue checks of F3.4 together reach count, rd_ptr, wr_ptr: 7 of the FIFO's 39 state bits. The 32-bit memory is white until the end-to-end check arrives.

White logic is unverified, not wrong. Here the white part is the data, the one thing a FIFO exists to keep. COI coverage is cheap and runs early, so run it before you believe a green dashboard. The next lesson measures what that white memory hides.

Inside the Cone: The Proof Core

Cut on fixed ctr_dbg, count <= 9 onlyMeasured result
count cut: any value, every cycleFAIL at reported step 2
fast cutProve PASS (k = 10)
dbg cut (outside the cone)Prove PASS (k = 10)

A cut turns a register's output into a free input ($anyseq). Prove mode, smtbmc yices, depth 10.

fast is in the cone, yet the proof survives its cut: the counter stays at or below 9 at either speed. The part of the cone a proof needs is its proof core. The open tools do not print it; one cut per register measures it.

Logic in the cone but outside the core is a review question. It may be dead, redundant, or simply checked by no property. Here the speed mode needs its own property.

Bounded Coverage

Cover depths against the bounded run

Four covers on the spine, reached at reported steps 3, 6, 6, 8. A bounded run must reach past the deepest one.

Linear scale. 0 to 20 steps.

Checked bound
10
Required bound
9
Claim
Bounded check only

Measured: one entry at step 3; full at step 6; full, push and pop together at step 6; full again after two reads at step 8. The spine passes bmc at depth 10.

Cover mode prints the step each cover was reached at. The deepest one, step 8, is the shortest trace that shows that behaviour. A bounded proof below it never saw that behaviour; F7.3 turns this into a required depth.

Asked in Interviews

QuestionAnswer
What does formal coverage mean?Structural: which logic the properties read (the cone) and which logic the proofs needed (the core). Bounded: how deep covers and bounded runs reached. Behavioural: whether the properties notice injected faults (mutation, F7.2).
How does the proof core differ from the cone of influence?The cone is everything the property could depend on. The core is the part the proof actually used, found from the invariant or by cutting registers one at a time.
Can a block be "proven" with white logic on the map?Its properties are proven. The white logic is not verified, and the sign-off must name it and say why it is acceptable.

Try It

The lab is ctr_dbg with the mirror bug and only the count property: Prove PASS (k = 10) in 0.5 s. Both rules in concurrent SVA; the design carries their immediate forms.

The second rule runs as count_q (a registered copy) and a past guard.
assert property (@(posedge clk) disable iff (rst) count <= 4'd9);
assert property (@(posedge clk) disable iff (rst) dbg == $past(count));
  1. Read the claim

    Run as it is. PASS: but is dbg verified? Check the cone listing above.

  2. Grow the map

    Add if (past) a_dbg: assert(dbg == count_q); to the check block and run. Read the step where dbg falls behind.

  3. Cut inside the cone

    Fix the mirror, delete the new check, and turn fast into $anyseq. Then cut count instead. Which register is in the core?

design.sv

props.sv

Use assert property (@(posedge clk) ...);. Leave blank for immediate assertions in the design.

Inputs are free unless constrained. Run to find a counterexample or check a proof.

Model: one positive-edge clock, binary data. Sampled history requires declaration-initialized variables; primary-input history is unsupported.

SBY summary and log

Check Yourself

Predict the outputctr_dbg with the mirror bug and only count <= 9, mode prove, smtbmc yices, depth 10, reset assumed in step 0. The verdict is:
module ctr_dbg(input clk, rst, en, fast_in, output [3:0] count_o, dbg_o);
  reg [3:0] count = 0;   // the counter
  reg fast = 0;          // speed mode, loaded by software
  reg [3:0] dbg = 0;     // debug mirror: count, one cycle late
  assign count_o = count;
  assign dbg_o = dbg;
  always @(posedge clk)
    if (rst) begin count <= 0; fast <= 0; dbg <= 0; end
    else begin
      fast <= fast_in;
      if (en) count <= (count == 4'd9 || fast && count == 4'd8) ? 4'd0 : count + (fast ? 4'd2 : 4'd1);
      if (!(en && count == 4'd9)) dbg <= count;   // bug: skips the wrap cycle
    end
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  reg past = 0;
  reg [3:0] count_q = 0;           // registered copy for the mirror check
  always @(posedge clk) begin past <= !rst; count_q <= count; end
  always @(posedge clk) if (!rst) begin
    a_cnt: assert(count <= 4'd9);
  end
endmodule
Predict the outputThe same design with dbg == $past(count) added, mode prove, smtbmc yices, depth 10, reset assumed in step 0. At which step does SBY report the failure?
module ctr_dbg(input clk, rst, en, fast_in, output [3:0] count_o, dbg_o);
  reg [3:0] count = 0;   // the counter
  reg fast = 0;          // speed mode, loaded by software
  reg [3:0] dbg = 0;     // debug mirror: count, one cycle late
  assign count_o = count;
  assign dbg_o = dbg;
  always @(posedge clk)
    if (rst) begin count <= 0; fast <= 0; dbg <= 0; end
    else begin
      fast <= fast_in;
      if (en) count <= (count == 4'd9 || fast && count == 4'd8) ? 4'd0 : count + (fast ? 4'd2 : 4'd1);
      if (!(en && count == 4'd9)) dbg <= count;   // bug: skips the wrap cycle
    end
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  reg past = 0;
  reg [3:0] count_q = 0;           // registered copy for the mirror check
  always @(posedge clk) begin past <= !rst; count_q <= count; end
  always @(posedge clk) if (!rst) begin
    a_cnt: assert(count <= 4'd9);
    if (past) a_dbg: assert(dbg == count_q);
  end
endmodule
ConceptWhat separates the cone of influence from the proof core?
ConceptWhich claim about the cone listing is wrong?
Read the waveformIn the measured trace, at which step does dbg first disagree with count_q, the copy of the last count?
The mirror misses the wrap
012345678clkfreeenfastcount0135790dbg013570count_q0135790count = 9, en = 1: the wrapdbg kept 7reported step 8

Measured: FAIL at reported step 8 (smtbmc yices, prove depth 10). In the wrap cycle the faulty mirror skips its update, so dbg is still 7 when it should be 9.

Remember

  • A proof covers its cone of influence and nothing else; draw the union of the cones before trusting a sign-off.
  • White logic on the cone map is unverified, not wrong: name it in the review.
  • The proof core is the part of the cone the proof used; cutting one register at a time measures it.
  • Cover depths are bounded coverage: a bounded run below the deepest cover never saw that behaviour.