Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Puzzle Bank
  • SystemVerilog Language Puzzles25
  • Difficulty Guide
  • Full Topic Map
  • Interview-Day Tips
  • Study Route: One Week Before the Interview
  • Study Route: Two Weeks
  • Study Route: Four Weeks (Full Course)4
  • OOP & Testbench Puzzles25
  • Concurrency & Timing Puzzles25
  • Randomization & Constraint Puzzles25
  • SVA Assertion Puzzles25
  • Functional Coverage Puzzles25
  • Logic & Aptitude Puzzles25
  • Scenario & Debug Puzzles25
    • Testbench Debug Puzzles: Why Did the Test Hang?
    • Deadlock Scenario Puzzles: Objection and Phase Stalls
    • Scoreboard Puzzles: Mismatch or Ordering Bug?
    • Real Interview Rounds: End-to-End Debug Walkthrough
      • Round: Mid-Test Reset Hangs the Regression
      • Round Walkthrough: Objection Deadlock with Clean Waves
      • Round Walkthrough: APB Reads Return Previous Data
      • Round: Sequencer Grants Nothing After lock()
      • Round: Scoreboard Deadlock Through Bounded Mailboxes
      • Round: Drain Timeout That Never Fires
    • Scenario & Debug Puzzles: Tricky Q&A
  • UVM Testbench Puzzles25
  • Clock, Reset & CDC Puzzles25
  • Bus and Interface Protocol Puzzles25
  • Interview Prep: Puzzle Rounds7
  • DV Puzzle Cheatsheet
Loading lesson

←Real Interview Rounds: End-to-End Debug Walkthrough
  1. Topics
  2. DV Interview Puzzles
  3. Scenario & Debug Puzzles
  4. Real Interview Rounds: End-to-End Debug Walkthrough
  5. Round: Drain Timeout That Never Fires
On this page

    VLSI DV Interview PuzzlesAll levels

    Round: Drain Timeout That Never Fires

    Interviewer: 'The scoreboard has a bounded drain, but when a response is genuinely lost the run does not fail at the end of the test with the residual ids; it sits until the global timeout.' Log tail: UVM_INFO @ 120000ns [SB] draining, 3 expected outstanding ... nothing ... UVM_FATAL @ 10000000ns [TIMEOUT] The simulation timed out Explain why the scoreboard's own timeout never reports, and fix it.

    Puzzle

    Difficulty: Hard · Puzzle 6 of 6 · Topic: Real Interview Rounds: End-to-End Debug Walkthrough

    Interviewer: 'The scoreboard has a bounded drain, but when a response is genuinely lost the run does not fail at the end of the test with the residual ids; it sits until the global timeout.' Log tail: UVM_INFO @ 120000ns [SB] draining, 3 expected outstanding ... nothing ... UVM_FATAL @ 10000000ns [TIMEOUT] The simulation timed out Explain why the scoreboard's own timeout never reports, and fix it.

    Code

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    localparam time DRAIN_TIMEOUT = 2000ns;
    
    function void phase_ready_to_end(uvm_phase phase);
      if (phase.get_name() != "run" || exp_q.size() == 0) return;
      phase.raise_objection(this, "scoreboard drain");
      \`uvm_info("SB", $sformatf("draining, %0d expected outstanding", exp_q.size()), UVM_LOW)
      fork begin
        wait (exp_q.size() == 0);
        #(DRAIN_TIMEOUT);                       // bug: sequential, after the wait
        if (exp_q.size() != 0)
          \`uvm_error("SB", $sformatf("drain timeout, %0d left", exp_q.size()))
        phase.drop_objection(this, "scoreboard drain");
      end join_none
    endfunction

    Hint

    In which order do the wait and the delay execute?

    Step-by-step solution

    Diagram
    1) Symptom: lost response leads to a hang until the global timeout instead of a scoreboard error with residual ids.
    2) Hypotheses: a leaked objection from the drain; the objection raised in the wrong phase (excluded: the log shows 'draining'); the timeout never being reached.
    3) Confirm: +UVM_OBJECTION_TRACE shows the 'scoreboard drain' objection held by the SB; a log line placed after the wait never prints in the lost-response run.
    4) Root cause: wait(exp_q.size()==0) and #DRAIN_TIMEOUT are written one after the other, so the delay starts only after the queue empties. If a response is lost, the wait blocks forever and the timeout code is unreachable (verified in simulation: the post-wait statement never executes). Even in the healthy case it adds a pointless 2us.
    5) Fix: race them, in an isolated block: fork begin fork wait (exp_q.size() == 0); #(DRAIN_TIMEOUT); join_any disable fork; end join; then report any residual ids (list them from exp_q) and drop the objection on every path. Better, restart the timer whenever exp_q changes size so a slow but moving drain is not cut off.
    6) Make it re-entry safe: phase_ready_to_end is called again after the objection drops (UVM 1.2 bounds this with max_ready_to_end_iter, 20 by default, as remembered). On the timeout path exp_q is still non-empty, so the fixed code would re-raise and report the timeout again. Add a bit drain_failed, set on timeout, and start the function with if (drain_failed) return; so the error is reported once and the run ends.

    Answer

    Answer: Root cause: the timeout is written after the wait instead of in parallel with it, so when a response is lost the wait never returns and the timeout and the drop are never reached. Fix: race the wait against the timeout (fork..join_any inside an isolating fork begin..end join), then report residual ids and drop the objection on every path.

    How this was checked

    Checked by running the code in Verilator 5; the printed output supports the answer above. Plain-SV model of the drain: a wait followed by a delay never reaches its timeout, the join_any race reports once and the guard stops a repeat.

    Why candidates get it wrong

    A timeout written sequentially after the thing it is meant to bound is no timeout at all. Test the timeout path explicitly by deleting a response in a directed test.

    Interviewer follow-up

    How would you prove in a test that the drain timeout itself works? Model answer: Add a negative test that deliberately swallows one response (a knob in the monitor or a DUT error injection), expects the scoreboard's 'drain timeout, N left' error with the residual ids exactly at the configured bound, and checks that the run then ends normally rather than at the global timeout. That last claim relies on the drain_failed re-entry guard, because UVM re-invokes phase_ready_to_end after the objection drops (bounded by max_ready_to_end_iter, default 20; medium confidence from memory of UVM 1.2).

    Say it out loud

    The drain timeout never fires, so I look at where the timeout is written relative to the wait. The root cause is that the timeout is written after the wait instead of in parallel with it, so when a response is lost the wait never returns and neither the timeout nor the drop is reached. I confirm it by deleting a response in a directed test. The fix is to race the wait against the timeout with fork and join_any inside an isolating fork begin end join, then report residual ids and drop the objection on every path.

    Related topics

    • Objection Triage and Hang Forensics

    • Watchdogs and Hang Detectors

    • Back to Real Interview Rounds: End-to-End Debug Walkthrough

    • Scenario & Debug Puzzles section

    Previous

    209 / 293

    Next: Scenario & Debug Puzzles: Tricky Q&A

    Up next

    Up nextScenario & Debug Puzzles: Tricky Q&A

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Round: Mid-Test Reset Hangs the Regression
    • Round Walkthrough: Objection Deadlock with Clean Waves
    • Round Walkthrough: APB Reads Return Previous Data