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
      • Early Return Leaves Objection Raised
      • Background Poller Keeps Running After Traffic Ends
      • Semaphore Token Leak on Error Path
      • join_any Ends the Task with the Mailbox Full
      • Lock Inversion with Two Semaphores
      • disable fork Kills the Thread That Owned the Drop
    • Scoreboard Puzzles: Mismatch or Ordering Bug?
    • Real Interview Rounds: End-to-End Debug Walkthrough
    • 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

←Deadlock Scenario Puzzles: Objection and Phase Stalls
  1. Topics
  2. DV Interview Puzzles
  3. Scenario & Debug Puzzles
  4. Deadlock Scenario Puzzles: Objection and Phase Stalls
  5. disable fork Kills the Thread That Owned the Drop
On this page

    VLSI DV Interview PuzzlesAll levels

    disable fork Kills the Thread That Owned the Drop

    run_phase never ends. +UVM_OBJECTION_TRACE shows one outstanding count on this component with the description 'drain watch', held by a thread that no longer exists. The bring-up timeout helper looks harmless. Find why the drop never runs.

    Puzzle

    Difficulty: Hard · Puzzle 6 of 6 · Topic: Deadlock Scenario Puzzles: Objection and Phase Stalls

    run_phase never ends. +UVM_OBJECTION_TRACE shows one outstanding count on this component with the description 'drain watch', held by a thread that no longer exists. The bring-up timeout helper looks harmless. Find why the drop never runs.

    Code

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    task bring_up();   // called inline from run_phase
      fork
        wait_for_link_up();
        #1000ns;
      join_any
      disable fork;    // intent: kill whichever of the two lost the race
    endtask
    
    task run_phase(uvm_phase phase);
      phase.raise_objection(this, "main");
      fork
        begin                               // drain watcher
          phase.raise_objection(this, "drain watch");
          wait (pending_cnt == 0);
          phase.drop_objection(this, "drain watch");
        end
      join_none
      bring_up();
      run_traffic();
      phase.drop_objection(this, "main");
    endtask

    Hint

    disable fork kills every child of the calling thread, not just the ones forked inside the task.

    Step-by-step solution

    Diagram
    1) +UVM_OBJECTION_TRACE shows 'drain watch' raised and never dropped, and the owner thread is gone.
    2) disable fork terminates all child processes of the calling thread. bring_up() runs in run_phase's thread, so the drain watcher (also a child of that thread) is killed along with the intended loser.
    3) The watcher was killed between its raise and its drop, so the count stays at 1 (observed in Verilator: the bare disable fork kills the sibling thread, so the count is leaked, the 'main' drop alone cannot end the phase).
    4) Fix: isolate the race with an enclosing fork begin ... end join so disable fork only sees the helper's own children: fork begin fork wait_for_link_up(); #1000ns; join_any disable fork; end join. Alternatively keep process handles or use a named block.
    5) Make the cleanup independent of any thread that can be killed: drop in one place that always runs, and add a regression where the timeout path is hit.
    Note: the sibling-kill was observed on Verilator with a task model; UVM objections themselves were not run. Confirm on a commercial simulator (Icarus crashed on this model, so it is no evidence either way).

    Answer

    Answer: Bug: a bare disable fork in a helper kills all children of the calling thread, including the unrelated drain-watch thread that owned a drop_objection, so that objection is never dropped. Fix: wrap the race in fork begin ... end join (so only its own children are killed), or kill specific process handles.

    How this was checked

    Checked by running the code in Verilator 5; the printed output supports the answer above. Plain-SV model of a bare disable fork killing the sibling drain watcher (UVM objections are modelled by a counter); the isolated fork keeps it alive.

    Why candidates get it wrong

    Everyone reads disable fork as 'stop the losers of this fork', but it kills every child of the calling process. It does not skip the next statement, so the 'exits scope without drop' story is wrong; the damage is to unrelated threads.

    Interviewer follow-up

    How would you report child failure to parent while still preserving objection symmetry? Model answer: Keep the objection owned by one structure (the test or a base-class body wrapper) that always drops, and report failure through a status field or return value (and a uvm_error) that the parent reads after the child returns; never use non-local exits to signal failure.

    Say it out loud

    First I use the objection trace to see which objection is never dropped and which thread owned it. The root cause is a bare disable fork in a helper, which kills all children of the calling thread, including the unrelated drain-watch thread that owned a drop_objection. So that objection is never dropped. I confirm it by checking that the drain-watch thread dies when the helper runs, and the fix is to wrap the race in fork begin end join, or to kill specific process handles.

    Related topics

    • Objection Triage and Hang Forensics

    • Objection Counting Rules: Raises, Drops, and Edge Cases

    • Back to Deadlock Scenario Puzzles: Objection and Phase Stalls

    • Scenario & Debug Puzzles section

    Previous

    197 / 293

    Next: Scoreboard Puzzles: Mismatch or Ordering Bug?

    Up next

    Up nextScoreboard Puzzles: Mismatch or Ordering Bug?

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Early Return Leaves Objection Raised
    • Background Poller Keeps Running After Traffic Ends
    • Semaphore Token Leak on Error Path