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
    • Classic Brain Teasers
    • Probability Puzzles
    • Digital Logic Puzzles
    • Debugging Riddles
      • Race-Region Printout: What Does Each Monitor Print?
      • Reset Release Leaves the FSM in an Illegal State
      • always_comb with NBA and Missing Else
      • UVM Analysis Port Handle Reuse
      • join_any Timeout Ghost Thread
      • Randomization Always Fails: Hidden Unsat
    • Logic & Aptitude Puzzles: Tricky Q&A
  • Scenario & Debug Puzzles25
  • UVM Testbench Puzzles25
  • Clock, Reset & CDC Puzzles25
  • Bus and Interface Protocol Puzzles25
  • Interview Prep: Puzzle Rounds7
  • DV Puzzle Cheatsheet
Loading lesson

←Debugging Riddles
  1. Topics
  2. DV Interview Puzzles
  3. Logic & Aptitude Puzzles
  4. Debugging Riddles
  5. join_any Timeout Ghost Thread
On this page

    VLSI DV Interview PuzzlesAll levels

    join_any Timeout Ghost Thread

    A test sometimes passes then later crashes with timeout fatal. Why can this happen?

    Puzzle

    Difficulty: Medium · Puzzle 5 of 6 · Topic: Debugging Riddles

    A test sometimes passes then later crashes with timeout fatal. Why can this happen?

    Code

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // broken: the losing branch is never cancelled
    fork
      begin wait(done); end
      begin #1000 `uvm_fatal("TIMEOUT", "No completion"); end
    join_any
    
    // fixed: wrap, then disable only this block's children
    fork begin
      fork
        begin wait(done); end
        begin #1000 `uvm_fatal("TIMEOUT", "No completion"); end
      join_any
      disable fork;
    end join

    Hint

    What happens to the losing forked branch after join_any?

    Step-by-step solution

    Diagram
    1) join_any returns when the first branch finishes (e.g. done becomes true).
    2) The other branch keeps running unless explicitly killed.
    3) The timeout branch later reaches #1000 and calls the fatal, even though the operation succeeded. It shows up only if the simulation is still running 1000 time units after the fork, which is why it is 'sometimes' (simulated: fatal at t=1010).
    4) Add disable fork after join_any, but wrap it in an outer fork begin ... end join. disable fork kills all child processes of the calling process, not just the two you just forked. Without the wrapper it also killed an unrelated earlier fork...join_none thread in the same task: the scoreboard thread that should have printed at t=50 never did. With the wrapper only the timeout was killed and the earlier thread finished normally.
    5) An alternative is a structured timeout utility, or process handles (follow-up).

    Answer

    Answer: Missing `disable fork` leaves timeout thread alive; it fires later as a false fatal.

    How this was checked

    Checked by running the code in Verilator 5; the printed output supports the answer above. Plain-SV model of the broken fork/join_any (no UVM library, `uvm_fatal replaced by $display): the done branch wins at t=20, yet the timeout branch still fires at 1010. A bare disable fork stops the timeout but also kills the unrelated earlier thread, which never prints at t=50. Wrapped in fork begin ... end join, disable fork kills only the timeout: no fatal and the earlier thread still prints at t=50.

    Why candidates get it wrong

    Adding a bare disable fork. It also kills unrelated background threads forked earlier by the same process, which then silently stop doing their job.

    Interviewer follow-up

    How would you write the same logic using `process` handles for explicit kill? Model answer: process t; fork begin t = process::self(); #1000 `uvm_fatal("TIMEOUT", "No completion"); end join_none wait(t != null); wait(done); t.kill(); The wait(t != null) avoids killing before the branch has started; only that thread is killed.

    Say it out loud

    The join_any returns as soon as the first branch finishes, but the other branch keeps running. So the timeout thread is still alive, and a thousand time units later it fires the fatal even though the operation succeeded; that is why it only fails sometimes. The fix is disable fork after the join_any, but wrapped in an outer fork begin end join, because disable fork kills all children of the calling process, not just these two. The trap is a bare disable fork, which silently kills unrelated background threads.

    Related topics

    • Threads and Control: Fork, Disable, Wait, Process

    • run_test, Timeouts and How a Simulation Ends

    • Back to Debugging Riddles

    • Logic & Aptitude Puzzles section

    Previous

    183 / 293

    Next: Randomization Always Fails: Hidden Unsat

    Up next

    Up nextRandomization Always Fails: Hidden Unsat

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Race-Region Printout: What Does Each Monitor Print?
    • Reset Release Leaves the FSM in an Illegal State
    • always_comb with NBA and Missing Else