← Formal Verification Interviews
Interview Question · Code Writing
The Formal Scoreboard with One Symbolic Word
The Question
The interviewer asks
Write a formal check of data integrity and order through a FIFO, without modelling the whole queue.
You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
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.
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.
The 8-deep FIFO from lesson F4.4 with one symbolic tracked word and its helpers, proved by k-induction.
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.
What Interviewers Listen For (Senior)
A good senior answer states the data-independence condition, and adds helper invariants that tie the tracked slot to memory. It narrows the data width only when no control logic reads the data.
Cross-Questions
Where the interviewer goes next. Open a node to practise that follow-up.
WhyWhat IfProve ItCompareScale It
- The Formal Scoreboard with One Symbolic Word
-
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?
2:30You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
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 endmodulePrints:
prove, depth 8 (smtbmc yices): UNKNOWNSelf-Check
Tick each key point you covered in your answer.
-
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.
-
What IfTwo Tracked Words for OrderingL2 Mid-Level
What if you also want to check that two different words never swap order?
2:30You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
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. 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.
-
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.
-
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?
2:30You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
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"
-
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.
2:30You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
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.
-
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?
2:30You have two and a half minutes. Speak or type, then press Get Feedback under your answer. No credits needed.
Speech-to-text runs in your browser. Chrome and Edge may send audio to their own speech service.
You can edit your answer before you send it. Feedback uses AI and checks your answer only against our written answer. No credits are used and your answer is not saved.
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. 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.
tidnames the input a packet came from.Self-Check
Tick each key point you covered in your answer.
-
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.
What People Get Wrong
Common wrong answers and why they fail. Run It shows the wrong belief failing on a real simulator.
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