Threads & IPC Interview Questions

Master SystemVerilog concurrency: fork-join variants, mailboxes, semaphores, events, and deadlock scenarios.

Beginner Level

Fundamentals
What are the three primary IPC mechanisms in SystemVerilog? Give a one-sentence use case for each.
  1. Mailbox: FIFO-like message queue for transferring transactions between processes (e.g., monitor to scoreboard).
  2. Semaphore: Token-based mechanism for controlling access to shared resources (e.g., limiting concurrent bus accesses).
  3. Event: Signal mechanism for process synchronization (e.g., notifying driver that reset is complete).
What is the difference between mailbox mbx; and mailbox #(Transaction) mbx;?
  • Unparameterized: Accepts any type. Type errors are caught at runtime (or not at all).
  • Parameterized: Type-safe. Compile-time error if you try to put/get the wrong type.
// Type-safe - recommended
mailbox #(Transaction) mbx = new();
Transaction t;
mbx.put(t);      // OK
mbx.put(123);    // COMPILE ERROR!

Intermediate Level

Industry Standard
(Scenario) Bounded mailbox with size=1. P1 calls get(), P2 and P3 call put(). Describe the blocking behavior.

Initial state: Mailbox is empty.

  • P1 calls get(): Blocks (mailbox empty).
  • P2 calls put(item): Succeeds! Mailbox now has 1 item.
  • P3 calls put(item): Blocks (mailbox full).
  • P1 unblocks: Gets the item. Mailbox empty.
  • P3 unblocks: Can now put its item.

With try_put(): P2 succeeds, P3 immediately returns 0 (failure) without blocking.

What is the difference between -> and ->> when triggering an event?
  • -> (Blocking trigger): Executes in the current time step (Active region).
  • ->> (Non-blocking trigger): Schedules the trigger for the NBA region.

When to use ->>: When triggering from a clocked block to avoid race conditions with sampling.

event data_ready;
// In a clocked process:
always @(posedge clk) begin
    data_out <= compute();
    ->> data_ready;  // Non-blocking - safe timing
end
A semaphore is initialized with semaphore sem = new(2);. Process A calls sem.get(1). Process B calls sem.get(2). Which blocks? Can Process C call sem.put(5) to add more keys?
  • Initially: 2 keys available.
  • Process A: Gets 1 key → 1 remaining. Does NOT block.
  • Process B: Wants 2 keys, only 1 available → BLOCKS.
  • Can put(5)? Yes! sem.put(n) adds n keys. Semaphores don't track "borrowed" keys - you can add more than originally allocated.
sem.put(5);  // Now 6 keys available (1 + 5)
// Process B unblocks, gets its 2 keys.

Advanced Level

Expert
(Deadlock Design) Write a minimal SystemVerilog testbench with two semaphores and two forked processes that creates a deadlock.
module deadlock_demo;
  semaphore s1 = new(1);
  semaphore s2 = new(1);
  initial begin
    fork
      begin  // Thread 1
        s1.get();      // Gets s1
        #1;            // Small delay
        s2.get();      // Waits for s2 (held by Thread 2)
      end
      begin  // Thread 2
        s2.get();      // Gets s2
        #1;            // Small delay
        s1.get();      // Waits for s1 (held by Thread 1)
      end
    join
    $display("Never reaches here!");
  end
endmodule

Deadlock condition satisfied: Circular wait - Thread 1 holds s1, waits for s2. Thread 2 holds s2, waits for s1. Neither can proceed.

Explain the difference between @(my_event) and wait(my_event.triggered). Which catches a same-timestep trigger?
  • @(my_event): Waits for a FUTURE trigger. If the event was triggered earlier in the same timestep, it will MISS it.
  • wait(my_event.triggered): Checks the .triggered property, which stays true for the entire timestep. Catches same-timestep triggers.
event e;
initial begin
  -> e;           // Trigger
  @(e);           // BLOCKS FOREVER! Already triggered.
end
// Correct:
initial begin
  -> e;
  wait(e.triggered);  // Works! Sees the trigger.
end
(Design Choice) A Scoreboard needs to receive transactions from a Monitor. Should you use a mailbox or a shared queue protected by a semaphore? Justify.

Recommendation: Mailbox

Aspect Mailbox Queue + Semaphore
Atomicity Built-in (put/get are atomic) Manual (must protect every access)
Blocking Built-in (get blocks if empty) Manual (must implement wait logic)
Error-prone Low High (easy to forget lock/unlock)

Use Queue when: You need random access (e.g., search for matching transaction), reordering, or peek without removal.

Explain the simulation event regions. In which region does program block code execute? Can a module block's get() receive an item put by a program block in the same timestep?

Event Regions:

  1. Active: Module code, blocking assignments.
  2. Inactive: #0 delays.
  3. NBA: Non-blocking assignment updates.
  4. Observed: Assertion evaluation.
  5. Reactive: program block code.
  6. Postponed: $strobe, $monitor.

Answer: Program blocks execute in Reactive. A module block runs in Active. If the module's get() is waiting, it will NOT receive a program's put() in the same timestep because Reactive runs AFTER Active. The get will succeed in the next timestep.