← Design Verification Interviews

Interview Question · Debug

Two Requests, One Response, No Failure

L2 Mid-Levelsvalocal variablestags

The Question

The interviewer asks

The scoreboard says a response is missing, but a_resp: assert property (req |-> ##[1:4] ack) reports no failure. Here is the log. How did the assertion miss it, and how do you fix it?

log
t=15 req (id 0)
t=25 req (id 1)
t=45 ack
-- end of test: a_resp 0 failures, scoreboard: 1 response missing
Reveal the Model Answer

Both windows contain the single ack at 45: the request at 15 looks at 25 to 55 and the request at 25 looks at 35 to 65. One ack satisfies both attempts, because nothing ties a response to a particular request. Tag each request: with in-order responses a ticket counter works, (req, t = issued) |-> ##4 (served + ack > t), so each request needs its own response by its deadline.

One ack inside two windows
012345edge (ns)152535455565reqackissued012servedone ack: both range attempts passticket 1 deadline: served + ack = 1, fail

The ticket turns "an ack happened" into "this request's ack happened".

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Mid-Level)

A good mid-level answer reads both windows off the log and shows that they overlap at 45. It writes the ticket rule with a local variable.

Cross-Questions

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

WhyWhat IfProve ItCompareScale It

  1. Two Requests, One Response, No Failure
  2. WhyWhy One Response Satisfies Every Open WindowL1 JuniorL2 Mid-Level

    Why can one ack pass several overlapping attempts of req |-> ##[1:4] ack?

    Reveal the Model Answer

    Each attempt only asks whether ack is true at some edge of its own window. The attempts are evaluated independently, and none of them uses up the ack. So that one edge satisfies every window that contains it. Counting responses needs state outside the property, a ticket or a queue.

    One Ack Passes Three AttemptsOne lane per request for req |-> ##[1:4] ack. Each attempt forks a thread per delay of the range.req |-> ##[1:4] ackMeasured on Verilator 5.052: one copy of the assertion per lane
    attempt startspassthread dies; the attempt goes onthread dropped: the attempt has already passed
    One ack passes three attemptsreq @15, from edge 2 (t=15): thread 1 (to edge 3 (t=25)) dies at edge 3 (t=25); thread 2 (to edge 4 (t=35)) dies at edge 4 (t=35); thread 3 (to edge 5 (t=45)) passes at edge 5 (t=45); thread 4 (to edge 6 (t=55)) is dropped at edge 5 (t=45), because the attempt has already passed. req @25, from edge 3 (t=25): thread 1 (to edge 4 (t=35)) dies at edge 4 (t=35); thread 2 (to edge 5 (t=45)) passes at edge 5 (t=45); thread 3 (to edge 6 (t=55)) is dropped at edge 5 (t=45), because the attempt has already passed; thread 4 (to edge 7 (t=65)) is dropped at edge 5 (t=45), because the attempt has already passed. req @35, from edge 4 (t=35): thread 1 (to edge 5 (t=45)) passes at edge 5 (t=45); thread 2 (to edge 6 (t=55)) is dropped at edge 5 (t=45), because the attempt has already passed; thread 3 (to edge 7 (t=65)) is dropped at edge 5 (t=45), because the attempt has already passed; thread 4 (to edge 8 (t=75)) is dropped at edge 5 (t=45), because the attempt has already passed.edgetime15215325435545655765875clkreqackattemptsreq @15req @15: dies at edge 3 (t=25)req @15: dies at edge 4 (t=35)req @15: passes at edge 5 (t=45)req @25req @25: dies at edge 4 (t=35)req @25: passes at edge 5 (t=45)req @35req @35: passes at edge 5 (t=45)
    1. Edge 2 · t=15

      The first request starts an attempt with four threads, one for each edge from 25 to 55.

    2. Edge 4 · t=35

      Two more requests have started their own attempts. No ack yet, so the earliest threads die, but every attempt still has threads looking.

    3. Edge 5 · t=45

      One ack at 45. Every attempt has a thread looking at this edge, so all three pass here, together.

      More context

      Nothing in ack says which request it answers. The two lost responses are never missed.

    4. Edge 8 · t=75

      The windows close with no further ack, and nothing fails.

    Read the lanes as text
    • req @15, from edge 2 (t=15): thread 1 (to edge 3 (t=25)) dies at edge 3 (t=25); thread 2 (to edge 4 (t=35)) dies at edge 4 (t=35); thread 3 (to edge 5 (t=45)) passes at edge 5 (t=45); thread 4 (to edge 6 (t=55)) is dropped at edge 5 (t=45), because the attempt has already passed.
    • req @25, from edge 3 (t=25): thread 1 (to edge 4 (t=35)) dies at edge 4 (t=35); thread 2 (to edge 5 (t=45)) passes at edge 5 (t=45); thread 3 (to edge 6 (t=55)) is dropped at edge 5 (t=45), because the attempt has already passed; thread 4 (to edge 7 (t=65)) is dropped at edge 5 (t=45), because the attempt has already passed.
    • req @35, from edge 4 (t=35): thread 1 (to edge 5 (t=45)) passes at edge 5 (t=45); thread 2 (to edge 6 (t=55)) is dropped at edge 5 (t=45), because the attempt has already passed; thread 3 (to edge 7 (t=65)) is dropped at edge 5 (t=45), because the attempt has already passed; thread 4 (to edge 8 (t=75)) is dropped at edge 5 (t=45), because the attempt has already passed.

    Sampled values, edge 1 to 8: req 0 1 1 1 0 0 0 0; ack 0 0 0 0 1 0 0 0.

    The program behind the lanes

    The lanes come from running this program on Verilator 5.052. Each lane is a separate copy of acked that can start only at that lane's edge, so every verdict and its edge are read from that copy's own output.

    module tb;
      logic clk = 0;
      always #5 clk = ~clk;                 // rising edges at 5, 15, 25, ...
      logic req = 0, ack = 0;
    
      // Ticket counters: a request takes ticket number issued, the next ack serves ticket served.
      int unsigned issued = 0, served = 0;
      always @(posedge clk) begin
        if (req) issued <= issued + 1;
        if (ack) served <= served + 1;
      end
    
      acked: assert property (@(posedge clk) req |-> ##[1:4] ack);
    
      initial begin
        @(negedge clk); req = 1;            // requests at 15, 25, 35
        repeat (2) @(negedge clk);
        @(negedge clk); req = 0; ack = 1;   // one ack, at 45
        @(negedge clk); ack = 0;
        repeat (3) @(negedge clk);          // edges at 55 ... 75
        $finish;
      end
    endmodule
    

    The lanes converge: one response, three passes.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "The first attempt uses up the ack, so the second one fails."

    Attempts share no state; an ack is a value at an edge, not a token that one attempt can take.

    Source IEEE 1800-2017 §16.12.7

  3. CompareA Ticket in the Checker vs an ID on the BusL2 Mid-LevelL3 Senior

    Compare tagging requests with a ticket counter inside the checker and using an ID field the protocol already carries.

    Reveal the Model Answer

    A ticket counter needs no design support, but it assumes responses return in order. It credits the n-th ack to the n-th request. A bus ID, such as an AXI ID, lets the checker match a response to its request even when responses reorder. Capture the ID in a local variable or key a table on it. Use the ticket only when the spec guarantees ordering.

    Three Requests with Tickets, One AckOne lane per request for the ticket rule. The capsule is the ticket the attempt captured.(req, t = issued) |-> ##4 (served + ack > t)Measured on Verilator 5.052: one copy of the assertion per lane
    attempt startspassfail
    Three requests with tickets, one ackreq @15, from edge 2 (t=15), captures t=0: passes at edge 6 (t=55). req @25, from edge 3 (t=25), captures t=1: fails at edge 7 (t=65). req @35, from edge 4 (t=35), captures t=2: fails at edge 8 (t=75).edgetime15215325435545655765875clkreqackissued0123servedattemptsreq @15t=0req @15: passes at edge 6 (t=55)req @25t=1req @25: fails at edge 7 (t=65)req @35t=2req @35: fails at edge 8 (t=75)
    1. Edge 2 · t=15

      The first request takes ticket 0.

    2. Edge 4 · t=35

      The next two take tickets 1 and 2. issued has moved on; each attempt kept its own ticket.

    3. Edge 5 · t=45

      The ack serves ticket 0. served becomes 1 after this edge.

    4. Edge 6 · t=55

      Deadline of ticket 0: one response so far, more than 0. It passes.

    5. Edge 7 · t=65

      Deadline of ticket 1: still one response, not more than 1. It fails.

    6. Edge 8 · t=75

      Ticket 2 fails the same way. The lanes now separate: one pass, two failures.

    Read the lanes as text
    • req @15, from edge 2 (t=15), captures t=0: passes at edge 6 (t=55).
    • req @25, from edge 3 (t=25), captures t=1: fails at edge 7 (t=65).
    • req @35, from edge 4 (t=35), captures t=2: fails at edge 8 (t=75).

    Sampled values, edge 1 to 8: req 0 1 1 1 0 0 0 0; ack 0 0 0 0 1 0 0 0; issued 0 0 1 2 3 3 3 3; served 0 0 0 0 0 1 1 1.

    The program behind the lanes

    The lanes come from running this program on Verilator 5.052. Each lane is a separate copy of ticket_served that can start only at that lane's edge, so every verdict and its edge are read from that copy's own output.

    module tb;
      logic clk = 0;
      always #5 clk = ~clk;                 // rising edges at 5, 15, 25, ...
      logic req = 0, ack = 0;
    
      // Ticket counters: a request takes ticket number issued, the next ack serves ticket served.
      int unsigned issued = 0, served = 0;
      always @(posedge clk) begin
        if (req) issued <= issued + 1;
        if (ack) served <= served + 1;
      end
    
      ticket_served: assert property (@(posedge clk) (req, t = issued) |-> ##4 (served + ack > t));
    
      initial begin
        @(negedge clk); req = 1;            // requests at 15, 25, 35
        repeat (2) @(negedge clk);
        @(negedge clk); req = 0; ack = 1;   // one ack, at 45
        @(negedge clk); ack = 0;
        repeat (3) @(negedge clk);          // edges at 55 ... 75
        $finish;
      end
    endmodule
    

    Each attempt waits for its own ticket, so the lost responses fail at their deadlines. A ticket assumes responses come back in order; a bus ID does not.

    Self-Check

    Tick each key point you covered in your answer.

    Missed Corner

    "The ticket works for any protocol."

    If responses can reorder, a late response is credited to the wrong ticket and both verdicts can be wrong.

    Source Lesson A4.2 (tickets assume in-order responses)

  4. Scale ItOrdering Checks: Assertions or the Scoreboard?L3 Senior

    For a block with outstanding, tagged transactions, which ordering and completion rules do you put in assertions and which in the scoreboard?

    Reveal the Model Answer

    Assertions take the local, cycle-level rules: an ID is not reused while outstanding, every response matches an outstanding ID, deadlines are met. They report the exact cycle and also run in formal. The scoreboard takes end-to-end data correctness and anything needing a reference model. Overlap is deliberate: a lost response should be caught by both, at different distances from the bug.

    Self-Check

    Tick each key point you covered in your answer.

    What Interviewers Listen For (Senior)

    A good senior answer splits the rules by how local they are and by reuse in formal. It wants both the assertions and the scoreboard to catch a lost response, on purpose.

    Wrong Altitude

    "The scoreboard already checks everything, so assertions are redundant."

    A scoreboard reports a missing response at the end of the test, far from the cause. An assertion names the cycle, and only properties carry over to formal.

  5. What IfTags When Responses ReorderL2 Mid-LevelL3 Senior

    Asked if responses can come back out of order

    What if responses can return out of order, each carrying the request's ID?

    Reveal the Model Answer

    Keep a small table in auxiliary code indexed by ID that records an outstanding request and its deadline or expected data. Assert on each response that its ID was outstanding, and on each cycle that no outstanding entry has passed its deadline. That is a per-ID scoreboard in miniature, and it keeps the properties one-edge checks that every tool runs.

    Self-Check

    Tick each key point you covered in your answer.

    Missed Corner

    "Capture the ID in a local variable and wait with ##[1:$] for a response with that ID."

    It is legal, but an unbounded weak wait never fails for a lost response. Long-lived attempts are also costly. The table makes the deadline explicit.

    Source Mehta, SystemVerilog Assertions and Functional Coverage (3rd ed.), §20.1 (PDF p309)

  6. Prove ItSolve 6.6.12: Tickets for a Lost ResponseSimulator-graded

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

    Open Problem 6.6.12

What People Get Wrong

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

Misconception

"Every request gets its own ack because every request starts its own attempt."

Separate attempts do not mean separate responses. In the twin two requests and one ack give zero failures; the ticket version fails the second request.

Run It

Two requests at 15 and 25, a single ack at 45.

RUNVerilator · simulator service
Edit the code, then run it
Expected: failures=0 FAIL: one ack sat inside both windows and satisfied both requests
See the Fix
module tb;
  logic clk = 0, req = 0, ack = 0;
  int fails = 0;
  always #5 clk = ~clk;
  // The fix: give each request a ticket; its ack must be served by the deadline.
  int unsigned issued = 0, served = 0;
  always @(posedge clk) begin
    if (req) issued <= issued + 1;
    if (ack) served <= served + 1;
  end
  property p_resp;
    int unsigned t;
    @(posedge clk) (req, t = issued) |-> ##4 (served + ack > t);
  endproperty
  a_resp: assert property (p_resp) else begin fails++; $display("t=%0t ticket failed", $time); end
  initial begin
    @(negedge clk) req = 1;             // two requests, at 15 and 25
    @(negedge clk) req = 1;
    @(negedge clk) req = 0;
    @(negedge clk) ack = 1;             // only one ack, at 45
    @(negedge clk) ack = 0;
    repeat (5) @(negedge clk);
    $display("failures=%0d", fails);
    if (fails == 1) $display("PASS: the second ticket was never served and its deadline failed");
    $finish;
  end
endmodule

Prints: t=65 ticket failed failures=1 PASS: the second ticket was never served and its deadline failed

Half-Truth

"Use $rose(req) so overlapping requests cannot happen."

Back-to-back requests that stay high give one rise for two transactions, so the check loses a request instead of gaining one.

Source IEEE 1800-2017 §16.9.3

Tool as Truth

"Write the ticket across the window: (req, t = issued) |-> ##[1:4] (ack && served == t)."

The standard allows it, but Verilator 5.052 rejects a local variable carried across a range. So the measured form checks at a fixed deadline.

Source ASIC Point engine notes (Verilator 5.052 SVA probes), row "local variable across a ranged delay"

Learn It, Then Prove It