← Formal Verification Interviews

Interview Question · Code Reading

End-to-End vs Glue Properties

L2 Mid-Levelpropertiesfifo

The Question

The interviewer asks

Your FIFO bench proves the four properties below. Is the FIFO verified? What would you write first?

systemverilog
always @(posedge clk) if (!rst) begin
  if (empty) assert(wr_ptr == rd_ptr);
  if (full)  assert(wr_ptr == rd_ptr);
  assert(count <= 3'd4);
  assert(count == f_ptr_diff || count == 3'd4 && f_ptr_diff == 2'd0);
end
Reveal the Model Answer

Not yet: those are glue properties about the mechanism, and they miss bugs that leave the pointers and count intact. Write an end-to-end check at the ports first: every accepted word leaves once, in order and unchanged. In the lesson, this glue set passes depth 10 on a FIFO that reads the wrong slot. The end-to-end check fails at step 3. Keep the glue to localise failures and feed induction.

Where Each Kind of Property AttachesGlue properties read internal signals and check one piece of mechanism. The end-to-end check reads only ports and the bench's shadow copy. Arrows mean "data flows to".
data: where the transaction goes
Where each kind of property attachesGlue properties read internal signals and check one piece of mechanism. The end-to-end check reads only ports and the bench's shadow copy. Arrows mean "data flows to". component push, wdata port component wr_ptr, rd_ptr,count glue: pointers equalwhen empty or full,count <= 4, countmatches the pointers component mem[0:3] four bytes component pop, rdata port component Scoreboardsb0..sb3 modelling code in thebench: everyaccepted byte, oldestfirst component End-to-end check pop && !empty |->rdata == sb0 writereadrecordhead
Read the diagram as text
  • push, wdata: port
  • wr_ptr, rd_ptr, count: glue: pointers equal when empty or full, count <= 4, count matches the pointers
  • mem[0:3]: four bytes
  • pop, rdata: port
  • Scoreboard sb0..sb3: modelling code in the bench: every accepted byte, oldest first
  • End-to-end check: pop && !empty |-> rdata == sb0
  • push, wdata → wr_ptr, rd_ptr, count
  • push, wdata → mem[0:3]: write
  • mem[0:3] → pop, rdata: read
  • push, wdata → Scoreboard sb0..sb3: record
  • Scoreboard sb0..sb3 → End-to-end check: head
  • pop, rdata → End-to-end check

Glue attaches inside the box. End-to-end attaches across it: what goes in comes out, in order, once, unchanged.

The data-order bug: glue green, end-to-end red
0123clkfreepushfreepopfreewdata00countrdata80sb000push 00pop: rdata 80, sb0 00SBY reports FAIL

Measured at depth 10 with smtbmc yices. The solver needed no second push: the slot after the head was never written, so its power-up value (80) is the solver's choice.

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Mid-Level)

A good mid-level answer starts with end-to-end checks and explains what glue properties miss. It uses glue properties to find where a bug is and to help induction.

Cross-Questions

Where the interviewer goes next. Open a node to practise that follow-up.

WhyWhat IfProve ItBreak ItCompareScale It

  1. End-to-End vs Glue Properties
  2. WhyWhy Glue Properties Miss Data BugsL2 Mid-Level

    Why can every pointer and count property hold while the FIFO returns the wrong data?

    Reveal the Model Answer

    Glue properties check the mechanism: pointers stay consistent with the count and full and empty agree with them. A bug in what is read, such as the wrong slot or stale data, leaves all of that intact. Only a property that compares what leaves with what entered, the purpose of the block, can see it.

    Self-Check

    Tick each key point you covered in your answer.

  3. Prove ItSolve F3.4.1: Prove the Data, Not Just the PointersSimulator-graded

    Show it, don't just say it: this Silicon Lab problem is graded by the simulator.

    Open Problem F3.4.1

  4. CompareThe Formal Scoreboard with One Symbolic WordL2 Mid-LevelL3 Senior

    Write a formal check of data integrity and order through a FIFO, without modelling the whole queue.

    Reveal the Model Answer

    Track one word whose value comes from $anyconst, so the solver picks it once, and let the cycle it enters be free. Count the words ahead of it, then assert that it is never dropped and that it leaves first in line, unchanged. The value and entry time are arbitrary. So a proof for this one word covers every word, as long as the design never inspects the data.

    One Symbolic Packet Through the FIFOThe solver picks a value and an entry time. The tracker follows that one packet from its write to its read.
    One symbolic packet through the FIFOThe solver picks a value and an entry time. The tracker follows that one packet from its write to its read. symbolic f_val = $anyconst one value, fixed forthe run, any value symbolic f_pick = $anyseq free each cycle: whichwrite to follow event enters records its slot andthe words ahead state waits each read lowers thewords ahead property at the head assert: rdata ==f_val property while inside assert: the FIFO isnot empty wdata == f_valnone ahead
    Read the diagram as text
    • f_val = $anyconst: one value, fixed for the run, any value
    • f_pick = $anyseq: free each cycle: which write to follow
    • enters: records its slot and the words ahead
    • waits: each read lowers the words ahead
    • at the head: assert: rdata == f_val
    • while inside: assert: the FIFO is not empty
    • f_val = $anyconst → enters: wdata == f_val
    • f_pick = $anyseq → enters
    • enters → waits
    • waits → at the head: none ahead
    • waits → while inside

    A drop, duplicate, reorder or corruption breaks one of the two assertions for some choice of packet.

    module sfifo(input clk, rst, wr, rd, input [7:0] wdata, output [7:0] rdata, output empty, full);
      reg [7:0] mem [0:7];
      reg [3:0] wr_ptr = 0, rd_ptr = 0;
      wire [3:0] used = wr_ptr - rd_ptr;
      assign empty = used == 0;
      assign full = used == 8;
      assign rdata = mem[rd_ptr[2:0]];
      wire do_wr = wr && !full, do_rd = rd && !empty;
      always @(posedge clk)
        if (rst) begin wr_ptr <= 0; rd_ptr <= 0; end
        else begin
          if (do_wr) begin mem[wr_ptr[2:0]] <= wdata; wr_ptr <= wr_ptr + 1'b1; end
          if (do_rd) rd_ptr <= rd_ptr + 1'b1;
        end
      always @(posedge clk) begin
        if ($initstate) begin assume(rst); end else begin assume(!rst); end
      end
      // one symbolic packet: any value, entering at any accepted write the solver picks
      wire [7:0] f_val = $anyconst;
      wire f_pick = $anyseq;
      reg f_in = 0, f_done = 0;
      reg [3:0] f_addr = 0;       // the slot it was written to
      reg [3:0] f_ahead = 0;      // words in front of it
      wire f_enter = !f_in && !f_done && wr && !full && f_pick && wdata == f_val;
      always @(posedge clk)
        if (rst) begin f_in <= 0; f_done <= 0; end
        else if (f_enter) begin f_in <= 1; f_addr <= wr_ptr; f_ahead <= used - do_rd; end
        else if (f_in && do_rd) begin
          if (f_ahead == 0) begin f_in <= 0; f_done <= 1; end
          else f_ahead <= f_ahead - 1'b1;
        end
      always @(posedge clk) if (!rst && f_in) begin
        assert(!empty);                              // never dropped
        if (f_ahead == 0) assert(rdata == f_val);     // leaves first in line, unchanged
      end
      // helpers: where the tracked word sits, and that it is still there
      wire [3:0] f_dist = f_addr - rd_ptr;
      always @(posedge clk) if (!rst) begin
        assert(used <= 8);
        if (f_in) begin
          assert(f_dist == f_ahead);
          assert(f_ahead < used);
          assert(mem[f_addr[2:0]] == f_val);
        end
      end
    endmodule
    

    Prints: prove, depth 2 (smtbmc yices): PASS

    Self-Check

    Tick each key point you covered in your answer.

    What Interviewers Listen For (Mid-Level)

    A good mid-level answer describes the $anyconst value, the free entry time and the two assertions.

    Misconception

    "One tracked value proves the FIFO only for that value."

    The solver may choose any value and any entry time. If some word could be lost or reordered, the solver would pick that word, so the proof covers them all.

    Source Seligman, Schubert and Kiran Kumar, Formal Verification (2nd ed.) ch. 10 "Exploiting symmetry with rigid free variables"

    Missed Corner

    "Shrink the data to one bit; a FIFO does not care about data."

    Only true when no logic reads the data. This FIFO drops a second 8'hFF in a row. The tracker finds it with the value 255, which a one-bit path could never carry.

    Run It

    A FIFO with fill-word compression: a repeated 8'hFF is not stored.

    module sfifo(input clk, rst, wr, rd, input [7:0] wdata, output [7:0] rdata, output empty, full);
      reg [7:0] mem [0:7];
      reg [3:0] wr_ptr = 0, rd_ptr = 0;
      wire [3:0] used = wr_ptr - rd_ptr;
      assign empty = used == 0;
      assign full = used == 8;
      assign rdata = mem[rd_ptr[2:0]];
      reg last_ff = 0;   // fill-word compression: a second 8'hFF in a row is not stored
      always @(posedge clk) if (rst) last_ff <= 0; else if (wr && !full) last_ff <= wdata == 8'hFF;
      wire do_wr = wr && !full && !(wdata == 8'hFF && last_ff), do_rd = rd && !empty;
      always @(posedge clk)
        if (rst) begin wr_ptr <= 0; rd_ptr <= 0; end
        else begin
          if (do_wr) begin mem[wr_ptr[2:0]] <= wdata; wr_ptr <= wr_ptr + 1'b1; end
          if (do_rd) rd_ptr <= rd_ptr + 1'b1;
        end
      always @(posedge clk) begin
        if ($initstate) begin assume(rst); end else begin assume(!rst); end
      end
      // one symbolic packet: any value, entering at any accepted write the solver picks
      wire [7:0] f_val = $anyconst;
      wire f_pick = $anyseq;
      reg f_in = 0, f_done = 0;
      reg [3:0] f_addr = 0;       // the slot it was written to
      reg [3:0] f_ahead = 0;      // words in front of it
      wire f_enter = !f_in && !f_done && wr && !full && f_pick && wdata == f_val;
      always @(posedge clk)
        if (rst) begin f_in <= 0; f_done <= 0; end
        else if (f_enter) begin f_in <= 1; f_addr <= wr_ptr; f_ahead <= used - do_rd; end
        else if (f_in && do_rd) begin
          if (f_ahead == 0) begin f_in <= 0; f_done <= 1; end
          else f_ahead <= f_ahead - 1'b1;
        end
      always @(posedge clk) if (!rst && f_in) begin
        assert(!empty);                              // never dropped
        if (f_ahead == 0) assert(rdata == f_val);     // leaves first in line, unchanged
      end
      // helpers: where the tracked word sits, and that it is still there
      wire [3:0] f_dist = f_addr - rd_ptr;
      always @(posedge clk) if (!rst) begin
        assert(used <= 8);
        if (f_in) begin
          assert(f_dist == f_ahead);
          assert(f_ahead < used);
          assert(mem[f_addr[2:0]] == f_val);
        end
      end
    endmodule
    

    Prints: bmc, depth 12 (smtbmc yices): FAIL at step 4

    See the Fix
    module sfifo(input clk, rst, wr, rd, input [7:0] wdata, output [7:0] rdata, output empty, full);
      reg [7:0] mem [0:7];
      reg [3:0] wr_ptr = 0, rd_ptr = 0;
      wire [3:0] used = wr_ptr - rd_ptr;
      assign empty = used == 0;
      assign full = used == 8;
      assign rdata = mem[rd_ptr[2:0]];
      wire do_wr = wr && !full, do_rd = rd && !empty;
      always @(posedge clk)
        if (rst) begin wr_ptr <= 0; rd_ptr <= 0; end
        else begin
          if (do_wr) begin mem[wr_ptr[2:0]] <= wdata; wr_ptr <= wr_ptr + 1'b1; end
          if (do_rd) rd_ptr <= rd_ptr + 1'b1;
        end
      always @(posedge clk) begin
        if ($initstate) begin assume(rst); end else begin assume(!rst); end
      end
      // one symbolic packet: any value, entering at any accepted write the solver picks
      wire [7:0] f_val = $anyconst;
      wire f_pick = $anyseq;
      reg f_in = 0, f_done = 0;
      reg [3:0] f_addr = 0;       // the slot it was written to
      reg [3:0] f_ahead = 0;      // words in front of it
      wire f_enter = !f_in && !f_done && wr && !full && f_pick && wdata == f_val;
      always @(posedge clk)
        if (rst) begin f_in <= 0; f_done <= 0; end
        else if (f_enter) begin f_in <= 1; f_addr <= wr_ptr; f_ahead <= used - do_rd; end
        else if (f_in && do_rd) begin
          if (f_ahead == 0) begin f_in <= 0; f_done <= 1; end
          else f_ahead <= f_ahead - 1'b1;
        end
      always @(posedge clk) if (!rst && f_in) begin
        assert(!empty);                              // never dropped
        if (f_ahead == 0) assert(rdata == f_val);     // leaves first in line, unchanged
      end
      // helpers: where the tracked word sits, and that it is still there
      wire [3:0] f_dist = f_addr - rd_ptr;
      always @(posedge clk) if (!rst) begin
        assert(used <= 8);
        if (f_in) begin
          assert(f_dist == f_ahead);
          assert(f_ahead < used);
          assert(mem[f_addr[2:0]] == f_val);
        end
      end
    endmodule
    

    Prints: prove, depth 2 (smtbmc yices): PASS

  5. WhyWhy the Tracker Proof Needs HelpersL2 Mid-LevelL3 Senior

    Why does the symbolic tracker proof need helper assertions before k-induction (an induction proof over k steps) can close it?

    Reveal the Model Answer

    Without helpers the induction step may start with the tracker claiming a word is stored in a slot that holds something else. Helpers tie the tracker to the design. Its distance from the read pointer equals the count of words ahead. Memory at the tracked slot still holds the value. Without them the proof stays UNKNOWN even at k = 8; with them it closes at k = 2.

    module sfifo(input clk, rst, wr, rd, input [7:0] wdata, output [7:0] rdata, output empty, full);
      reg [7:0] mem [0:7];
      reg [3:0] wr_ptr = 0, rd_ptr = 0;
      wire [3:0] used = wr_ptr - rd_ptr;
      assign empty = used == 0;
      assign full = used == 8;
      assign rdata = mem[rd_ptr[2:0]];
      wire do_wr = wr && !full, do_rd = rd && !empty;
      always @(posedge clk)
        if (rst) begin wr_ptr <= 0; rd_ptr <= 0; end
        else begin
          if (do_wr) begin mem[wr_ptr[2:0]] <= wdata; wr_ptr <= wr_ptr + 1'b1; end
          if (do_rd) rd_ptr <= rd_ptr + 1'b1;
        end
      always @(posedge clk) begin
        if ($initstate) begin assume(rst); end else begin assume(!rst); end
      end
      // one symbolic packet: any value, entering at any accepted write the solver picks
      wire [7:0] f_val = $anyconst;
      wire f_pick = $anyseq;
      reg f_in = 0, f_done = 0;
      reg [3:0] f_addr = 0;       // the slot it was written to
      reg [3:0] f_ahead = 0;      // words in front of it
      wire f_enter = !f_in && !f_done && wr && !full && f_pick && wdata == f_val;
      always @(posedge clk)
        if (rst) begin f_in <= 0; f_done <= 0; end
        else if (f_enter) begin f_in <= 1; f_addr <= wr_ptr; f_ahead <= used - do_rd; end
        else if (f_in && do_rd) begin
          if (f_ahead == 0) begin f_in <= 0; f_done <= 1; end
          else f_ahead <= f_ahead - 1'b1;
        end
      always @(posedge clk) if (!rst && f_in) begin
        assert(!empty);                              // never dropped
        if (f_ahead == 0) assert(rdata == f_val);     // leaves first in line, unchanged
      end
    endmodule
    

    Prints: prove, depth 8 (smtbmc yices): UNKNOWN

    Self-Check

    Tick each key point you covered in your answer.

  6. Prove ItSolve F4.4.1: Implement the Symbolic Packet TrackerSimulator-graded

    Show it, don't just say it: this Silicon Lab problem is graded by the simulator.

    Open Problem F4.4.1

  7. What IfTwo Tracked Words for OrderingL2 Mid-Level

    What if you also want to check that two different words never swap order?

    Reveal the Model Answer

    Track two writes chosen freely by the solver, the first accepted before the second. Assert that the first leaves before the second and both arrive unchanged. Because both choices are free, the check covers every pair of words, which catches reordering that a single tracker cannot see.

    Two Tracked Writes Stand for Every PairThe solver picks any accepted write as the first; the next accepted write is the second. The bench copies their data and counts the entries in front of the first.
    Two tracked writes stand for every pairThe solver picks any accepted write as the first; the next accepted write is the second. The bench copies their data and counts the entries in front of the first. component write side write_en, write_data component first: f_d1 any accepted write,chosen by the freeinput f_pick; f_aheadentries in front component second: f_d2 the very nextaccepted write component read side read_valid, read_dataone cycle after anaccepted read component checks f_d1 leaves whenf_ahead reaches 0;f_d2 is the very nextread f_ahead reads later
    Read the diagram as text
    • write side: write_en, write_data
    • first: f_d1: any accepted write, chosen by the free input f_pick; f_ahead entries in front
    • second: f_d2: the very next accepted write
    • read side: read_valid, read_data one cycle after an accepted read
    • checks: f_d1 leaves when f_ahead reaches 0; f_d2 is the very next read
    • write side → first: f_d1
    • write side → second: f_d2
    • first: f_d1 → read side: f_ahead reads later
    • second: f_d2 → checks
    • read side → checks

    The solver may pick any accepted write as the first, so the proof covers every pair of entries in a row.

    Self-Check

    Tick each key point you covered in your answer.

  8. Prove ItSolve F8.1.1: Sign off a Four-Entry FIFOSimulator-graded

    Show it, don't just say it: this Silicon Lab problem is graded by the simulator.

    Open Problem F8.1.1

  9. WhyWhy Two Free Words Cover Every PairL2 Mid-LevelL3 Senior

    Why does choosing both words freely cover every pair, when the proof only ever tracks two?

    Reveal the Model Answer

    The solver may pick any two writes, at any times, as the tracked pair. A proof must hold for every choice it could make. So if any pair could swap, some choice of the two tracked words shows it. Two trackers then stand for all pairs, with only two sets of state.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Tracking two words only checks those two words."

    The words are free variables, so the proof covers every value the solver could choose. One run checks all pairs.

    Source Seligman, Schubert and Kiran Kumar, Formal Verification (2nd ed.) ch. 10 "Exploiting symmetry with rigid free variables"

  10. CompareSymbolic Tracker vs Reference QueueL2 Mid-LevelL3 Senior

    Asked if they propose a reference queue

    Compare a symbolic tracker with a reference-model queue as the scoreboard of a formal bench.

    Reveal the Model Answer

    A reference queue copies every word, so its state grows with depth times width. The solver must reason about all of it. The tracker holds one value, one slot and one count, yet covers every word through the free choice. The queue is closer to a simulation scoreboard and easier to read, so people pick it first. The tracker is what converges.

    Self-Check

    Tick each key point you covered in your answer.

  11. Scale ItScaling the Tracker to a Packet RouterL3 Senior

    How does the same idea scale to a two-by-two packet router with several flows?

    Reveal the Model Answer

    Track one symbolic packet, tagged at the moment of acceptance. Count how many packets of its flow are owed ahead of it at its output. Assert that it leaves at its turn, on the right port, unchanged. Also assert that no output delivers a packet it does not owe. Fairness is a separate question: a fixed-priority arbiter passes every one of these safety checks.

    rtr22 and the Tracked PacketTwo inputs, a grant per output, a two-entry queue per output, two outputs. The tracked packet is drawn as it moves: accepted, queued, at the head, gone.
    rtr22 and the tracked packetTwo inputs, a grant per output, a two-entry queue per output, two outputs. The tracked packet is drawn as it moves: accepted, queued, at the head, gone. input input 0 tvalid, tdata, tdest input input 1 tvalid, tdata, tdest logic grant per output round-robin whenboth want it state queue 0 two entries {tid,tdata} state queue 1 two entries {tid,tdata} output output 0 tvalid, tdata, tid output output 1 tvalid, tdata, tid 1 accepted2 queued3 its turn, 4 gone
    Read the diagram as text
    • input 0: tvalid, tdata, tdest
    • input 1: tvalid, tdata, tdest
    • grant per output: round-robin when both want it
    • queue 0: two entries {tid, tdata}
    • queue 1: two entries {tid, tdata}
    • output 0: tvalid, tdata, tid
    • output 1: tvalid, tdata, tid
    • input 0 → grant per output: 1 accepted
    • input 1 → grant per output
    • grant per output → queue 0: 2 queued
    • grant per output → queue 1
    • queue 0 → output 0: 3 its turn, 4 gone
    • queue 1 → output 1

    A two-by-two router with its tracked packet. tid names the input a packet came from.

    Self-Check

    Tick each key point you covered in your answer.

  12. Prove ItSolve F9.2.1: Prove Packet Routing End to EndSimulator-graded

    Show it, don't just say it: this Silicon Lab problem is graded by the simulator.

    Open Problem F9.2.1

  13. Break ItWhat a Bounded End-to-End Check MissesL2 Mid-LevelL3 Senior

    Your end-to-end FIFO check passes at depth 10. What bug could still be there?

    Reveal the Model Answer

    Anything that needs more than ten steps to reach a port. Examples are a pointer that wraps wrongly only after many writes, or a count that drifts and overflows later. The end-to-end check sees the effect only once the bad word is read. Glue invariants on the count and pointers catch such drift early and also help an induction proof close.

    The scoreboard's price in depth

    Correct FIFO, glue and end-to-end together. Depth 10 (samples 0 to 8) passed in 1.0 s. Depth 20 ended at the 20-second budget: time limit, no verdict.

    Linear scale. 0 to 20 steps.

    Checked bound
    10
    Required bound
    20
    Claim
    Bounded check only

    Glue and end-to-end checks together on a correct FIFO. Depth 10 passed in 1.0 s; depth 20 ran out of time.

    Self-Check

    Tick each key point you covered in your answer.

What People Get Wrong

Common wrong answers and why they fail. Run It shows the wrong belief failing on a real simulator.

Misconception

"If the pointers and the count are proven, the data must be right."

This FIFO reads mem[rd_ptr + 1]. Every pointer and count property holds, and only the check that the output equals the oldest stored word fails.

Run It

Glue properties only, on a FIFO that reads one slot ahead.

module fifo4sb(input clk, rst, push, pop, input [7:0] wdata,
  output [7:0] rdata, output full, empty);
  reg [7:0] mem [0:3];
  reg [1:0] wr_ptr=0, rd_ptr=0;
  reg [2:0] count=0;
  assign full  = count == 3'd4;
  assign empty = count == 3'd0;
  wire wr_en = push && !full, rd_en = pop && !empty;
  assign rdata = mem[rd_ptr + 2'd1];   // bug: the pointer moves before the read
  always @(posedge clk)
    if (rst) begin wr_ptr <= 0; rd_ptr <= 0; count <= 0; end
    else begin
      if (wr_en) begin mem[wr_ptr] <= wdata; wr_ptr <= wr_ptr + 2'd1; end
      if (rd_en) rd_ptr <= rd_ptr + 2'd1;
      count <= count + wr_en - rd_en;
    end
`ifdef FORMAL
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  wire [1:0] f_ptr_diff = wr_ptr - rd_ptr;   // entries between the pointers, modulo 4
  // Modelling code: a shadow copy of the FIFO's contents, in order, oldest in sb0.
  reg [7:0] sb0=0, sb1=0, sb2=0, sb3=0;
  reg [2:0] sb_n=0;
  wire [2:0] slot = sb_n - (rd_en ? 3'd1 : 3'd0);   // where an accepted push lands
  always @(posedge clk)
    if (rst) sb_n <= 0;
    else begin
      if (rd_en) begin sb0 <= sb1; sb1 <= sb2; sb2 <= sb3; end
      if (wr_en) case (slot)
        3'd0: sb0 <= wdata;  3'd1: sb1 <= wdata;  3'd2: sb2 <= wdata;  default: sb3 <= wdata;
      endcase
      sb_n <= sb_n + wr_en - rd_en;
    end
  always @(posedge clk) if (!rst) begin
    if (empty) assert(wr_ptr == rd_ptr);   // glue
    if (full)  assert(wr_ptr == rd_ptr);   // glue
    assert(count <= 3'd4);                 // glue
    assert(count == f_ptr_diff || count == 3'd4 && f_ptr_diff == 2'd0);   // glue
  end
`endif
endmodule

Prints: bmc, depth 10 (smtbmc yices): PASS

See the Fix
module fifo4sb(input clk, rst, push, pop, input [7:0] wdata,
  output [7:0] rdata, output full, empty);
  reg [7:0] mem [0:3];
  reg [1:0] wr_ptr=0, rd_ptr=0;
  reg [2:0] count=0;
  assign full  = count == 3'd4;
  assign empty = count == 3'd0;
  wire wr_en = push && !full, rd_en = pop && !empty;
  assign rdata = mem[rd_ptr + 2'd1];   // bug: the pointer moves before the read
  always @(posedge clk)
    if (rst) begin wr_ptr <= 0; rd_ptr <= 0; count <= 0; end
    else begin
      if (wr_en) begin mem[wr_ptr] <= wdata; wr_ptr <= wr_ptr + 2'd1; end
      if (rd_en) rd_ptr <= rd_ptr + 2'd1;
      count <= count + wr_en - rd_en;
    end
`ifdef FORMAL
  always @(posedge clk) begin
    if ($initstate) begin assume(rst); end else begin assume(!rst); end
  end
  wire [1:0] f_ptr_diff = wr_ptr - rd_ptr;   // entries between the pointers, modulo 4
  // Modelling code: a shadow copy of the FIFO's contents, in order, oldest in sb0.
  reg [7:0] sb0=0, sb1=0, sb2=0, sb3=0;
  reg [2:0] sb_n=0;
  wire [2:0] slot = sb_n - (rd_en ? 3'd1 : 3'd0);   // where an accepted push lands
  always @(posedge clk)
    if (rst) sb_n <= 0;
    else begin
      if (rd_en) begin sb0 <= sb1; sb1 <= sb2; sb2 <= sb3; end
      if (wr_en) case (slot)
        3'd0: sb0 <= wdata;  3'd1: sb1 <= wdata;  3'd2: sb2 <= wdata;  default: sb3 <= wdata;
      endcase
      sb_n <= sb_n + wr_en - rd_en;
    end
  always @(posedge clk) if (!rst) begin
    if (empty) assert(wr_ptr == rd_ptr);   // glue
    if (full)  assert(wr_ptr == rd_ptr);   // glue
    assert(count <= 3'd4);                 // glue
    assert(count == f_ptr_diff || count == 3'd4 && f_ptr_diff == 2'd0);   // glue
    if (pop && !empty) assert(rdata == sb0);   // end-to-end
  end
`endif
endmodule

Prints: bmc, depth 10 (smtbmc yices): FAIL at step 3

Half-Truth

"A bounded end-to-end check catches everything glue catches."

A bounded run can miss state that has gone wrong but not yet reached a port. In the lesson glue caught the count bug at step 4, end-to-end at step 5.

Wrong Altitude

Builds a full reference queue in the bench for a 16-deep FIFO.

It works, but it costs depth and state. One symbolic tracked word checks order and integrity for every value far more cheaply.

Learn It, Then Prove It