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
    • Event Scheduling Puzzles
    • Race Condition Puzzles
    • Clocking Block Puzzles
    • Semaphore & Mailbox Puzzles
      • Lock Held Across Blocking Response Wait
      • Bounded Mailbox put Deadlock
      • try_get Busy Spin at Time 0
      • Leaked Semaphore Token on Error Path
      • Two Consumers Waiting on One Mailbox
      • Deadlock-Free Request Loop with try_put/try_get
    • Concurrency & Timing Puzzles: Tricky Q&A
  • Randomization & Constraint Puzzles25
  • SVA Assertion Puzzles25
  • Functional Coverage Puzzles25
  • Logic & Aptitude Puzzles25
  • Scenario & Debug Puzzles25
  • UVM Testbench Puzzles25
  • Clock, Reset & CDC Puzzles25
  • Bus and Interface Protocol Puzzles25
  • Interview Prep: Puzzle Rounds7
  • DV Puzzle Cheatsheet
Loading lesson

←Semaphore & Mailbox Puzzles
  1. Topics
  2. DV Interview Puzzles
  3. Concurrency & Timing Puzzles
  4. Semaphore & Mailbox Puzzles
  5. Bounded Mailbox put Deadlock
On this page

    VLSI DV Interview PuzzlesAll levels

    Bounded Mailbox put Deadlock

    The start order is fixed explicitly in this version. Explain exactly why depth matters, where progress stops, and what mb.num() is when the simulation ends.

    Puzzle

    Difficulty: Medium · Puzzle 2 of 6 · Topic: Semaphore & Mailbox Puzzles

    The start order is fixed explicitly in this version. Explain exactly why depth matters, where progress stops, and what mb.num() is when the simulation ends.

    Code

    tb.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    module tb;
      semaphore lock = new(1);
      mailbox #(int) mb = new(1);
    
      task producer();
        lock.get(1);
        $display("[%0t] P holds lock, putting 100", $time);
        mb.put(100);                 // blocks: mailbox is full
        lock.put(1);
        $display("[%0t] P done", $time);
      endtask
    
      task consumer();
        int v;
        lock.get(1);                 // waits for lock
        mb.get(v);
        lock.put(1);
        $display("[%0t] C got %0d", $time, v);
      endtask
    
      initial begin
        mb.put(99);                  // fills the 1-deep mailbox
        fork producer(); join_none
        #1;                          // producer is now blocked inside put
        fork consumer(); join_none
        #50;
        $display("[%0t] end: mb.num()=%0d", $time, mb.num());
        $finish;
      end
    endmodule

    Hint

    A bounded put blocks when the mailbox is full, exactly like get on empty.

    Step-by-step solution

    Diagram
    1) mb.put(99) fills the depth-1 mailbox.
    2) At t=0 the producer takes the lock and blocks in mb.put(100) because the mailbox is full.
    3) At t=1 the consumer starts and blocks on lock.get: the producer owns the lock.
    4) Only the consumer could free space (by mb.get), but it needs the lock the producer holds: circular wait.
    5) Output: '[0] P holds lock, putting 100' and '[51] end: mb.num()=1'. No 'P done' and no 'C got'. With the explicit #1 the order is deterministic (verified in Verilator).

    Answer

    Answer: It hangs: P holds the lock blocked on put, C blocked on the lock. The output is '[0] P holds lock, putting 100' and '[51] end: mb.num()=1'.

    How this was checked

    Checked by running the code in Verilator 5; the printed output supports the answer above. The producer holds the lock while blocked on a full mailbox, so the consumer never runs and mb.num() stays 1.

    Why candidates get it wrong

    The original form of this puzzle depended on undefined start order of initial blocks and fork children; make the order explicit before claiming a deadlock. And bounded put is as blocking as get.

    Interviewer follow-up

    Would increasing mailbox depth to 2 truly fix correctness or only mask the bug? Model answer: it only masks it: with room for 100 the producer finishes, but any burst larger than the new depth recreates the same lock-held-while-blocked cycle. The real fix is not holding the lock across the put.

    Say it out loud

    It hangs, and the depth is the reason. The producer fills the depth-one mailbox, takes the lock and blocks on the second put, and the consumer, which is the only one that could free space, is stuck on the lock. So nothing progresses, the end line shows one item still in the mailbox, and the circular wait is the lock plus the full mailbox. The trap is thinking a put cannot block, and I would make the start order explicit before claiming the deadlock. I would check the depth and who holds what when the timeout fires.

    Related topics

    • Mailboxes: Typed Pipes with Backpressure

    • Semaphores: One Key and Many

    • Back to Semaphore & Mailbox Puzzles

    • Concurrency & Timing Puzzles section

    Previous

    80 / 293

    Next: try_get Busy Spin at Time 0

    Up next

    Up nexttry_get Busy Spin at Time 0

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Leaked Semaphore Token on Error Path
    • Two Consumers Waiting on One Mailbox
    • Deadlock-Free Request Loop with try_put/try_get