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

  • Start Here5
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive39
  • SystemVerilog Assertions — Interview-Critical Deep Dive41
  • Functional Coverage in SystemVerilog25
  • Testbench Architecture in SystemVerilog24
  • Advanced Language & Integration26
  • Senior & Interview Preparation23
    • Senior Debug Playbook
    • Reviewing Verification Code
    • Verification Strategy: Spec to Sign-Off
    • SystemVerilog Interview Q&A Bank
      • Q&A: Language Core
      • Q&A: Testbench Plumbing
      • Q&A: Classes and Interfaces
      • Q&A: Constraint and Assertion Fire-Drill
      • Q&A: Scenario and Design Questions
    • Capstone: Verify a Parameterized FIFO
  • Principal-Level Verification34
Loading lesson

←SystemVerilog Interview Q&A Bank
  1. Topics
  2. SystemVerilog
  3. Senior & Interview Preparation
  4. SystemVerilog Interview Q&A Bank
  5. Q&A: Testbench Plumbing
On this page

    Part 8 of 9, lesson 14 of 23Part 8 of 9/Senior & Interview Prep/Lesson 14 of 23Intermediate

    Q&A: Testbench Plumbing

    Revision: containers, array methods, fork discipline, mailboxes, semaphores and events, as short answers with links to the lessons.

    Plumbing moves transactions between testbench parts without races or leaks. Containers hold them, threads drive them, and channels synchronize them. Each answer below is the short version with a link to the lesson.

    Choose the container by access pattern

    Q: Dynamic array, associative array or queue, how do you choose

    Pattern

    Container

    Example

    Known size, dense indices

    Dynamic array

    A packet payload allocated once

    Sparse or non-integer keys

    Associative array

    A memory model keyed by address

    Ordered, grows and shrinks

    Queue

    Scoreboards, generators, monitors

    Reading a missing associative entry returns the type default without allocating, so guard reads with exists().

    Taught in: Dynamic Arrays and Queues, Associative Arrays.

    Q: What are the memory implications of each array type

    Fixed and dynamic arrays allocate every element. Associative arrays allocate per written element, the only sane way to model a large address space. An unbounded queue that is pushed but never popped is the classic testbench leak, so check queue-empty at end of test and print watermarks on long runs.

    Q: Find errors, sum a field, unique values, sort by field

    Array locator and ordering methods with with replace loops. Locator methods return a queue, possibly empty, never a scalar, so guard max, min and unique results with size() and cast inside sum so a narrow field cannot wrap.

    Taught in: Array Methods and Selection.

    Threads that outlive their welcome

    Q: The three fork variants, when is each used

    join waits for all branches, join_any resumes at the first, and join_none resumes immediately for background services. With join_any and join_none the other branches keep running. Children of join_none start only when the parent yields.

    Q: What is wrong with forking on the loop variable, and what is dangerous about disable fork

    Every branch captures the same loop variable, and join_none branches start after the loop ends, so all branches see the exit value. Copy the variable into an automatic local inside the fork. Separately, disable fork kills every child of the current thread, including monitors launched earlier, so wrap the timeout in an isolating fork...join.

    The bench proves both fixes: per-branch capture gives 0 1 2 3, and join_any plus disable fork ends the slow branch. (Verilator 5.048 already copies the loop variable per iteration, so the capture half passes there even without the fix; Icarus shows the trap.)

    design.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
    module tb_fork_catch;
      timeunit 1ns; timeprecision 1ps;
      int errors = 0;
      int hit[4];
      bit slow_done = 0;
      initial begin
        for (int i = 0; i < 4; i++) hit[i] = -1;
        for (int i = 0; i < 4; i++) begin : gen
          automatic int idx = i;
          fork begin : worker
            #10;
            hit[idx] = idx;
          end join_none
        end
        #11;
        for (int i = 0; i < 4; i++) if (hit[i] !== i) errors++;
        fork
          begin #5; end
          begin #100; slow_done = 1; end
        join_any
        disable fork;
        #110;
        if (slow_done !== 1'b0) errors++;
        if (errors == 0) $display("PASS: loop capture gave 0 1 2 3 and disable ended the slow branch");
        else $fatal(1, "FAIL: fork behavior wrong");
        $finish;
      end
    endmodule

    Taught in: Threads and Control: Fork, Disable, Wait, Process.

    Mailboxes, semaphores and events

    Q: Mailbox versus queue plus event, why the mailbox

    A mailbox is a thread-safe FIFO with blocking built in: get suspends while empty and a bounded put suspends while full. A hand-rolled queue plus event races when the trigger lands before the wait. Bound the mailbox so generation stays coupled to consumption.

    Taught in: Mailboxes: Typed Pipes with Backpressure.

    Q: When do you use a semaphore

    A semaphore manages keys for a shared resource: one key is a mutex, N keys model limited outstanding transactions. Keys leak when a holder is killed before put, which deadlocks everyone behind it.

    Taught in: Semaphores: One Key and Many.

    Q: What does event triggered solve that at-event does not

    @(ev) only sees a trigger that arrives while already waiting. ev.triggered stays true for the rest of the time step, so wait(ev.triggered) passes whether the process arrived before or after the trigger. Cross-timestep handoffs still belong in a mailbox.

    Taught in: Events: Edge Waits and Level Fixes.

    disable fork after join_any kills background monitors launched earlier by the same thread. Isolate timeout forks, or passing tests may be checking nothing.

    Likely whiteboard: fork four drivers in a loop and predict the wrong value. The answer names both halves: deferred branch start plus the shared variable, fixed by an automatic copy.

    Check yourself Why does a bounded mailbox beat an unbounded one between generator and driver?

    The bound adds backpressure: a fast generator cannot race thousands of transactions ahead. Memory stays flat and stimulus keeps responding to DUT state.

    Check yourself A branch holds a semaphore key when disable fork kills it. What does the rest of the test see?

    A hang. Everyone behind get blocks forever on a key that never returns, which looks like a DUT deadlock.

    Related topics

    • OOP: interprocess communication

    • Testbench: anatomy

    • Testbench: monitors and scoreboards

    Previous

    232 / 275

    Next: Q&A: Classes and Interfaces

    Up next

    Up nextQ&A: Classes and Interfaces

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Q&A: Constraint and Assertion Fire-Drill
    • Q&A: Scenario and Design Questions