Threads & IPC Interview Questions
Master SystemVerilog concurrency: fork-join variants, mailboxes, semaphores, events, and deadlock scenarios.
Beginner Level
Fundamentals- Mailbox: FIFO-like message queue for transferring transactions between processes (e.g., monitor to scoreboard).
- Semaphore: Token-based mechanism for controlling access to shared resources (e.g., limiting concurrent bus accesses).
- Event: Signal mechanism for process synchronization (e.g., notifying driver that reset is complete).
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 Standardget(), 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.
-> 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
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
Expertmodule 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.
@(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.triggeredproperty, 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
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.
program block code execute? Can a module block's get() receive an
item put by a program block in the same timestep?Event Regions:
- Active: Module code, blocking assignments.
- Inactive: #0 delays.
- NBA: Non-blocking assignment updates.
- Observed: Assertion evaluation.
- Reactive:
programblock code. - 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.