Skip to practice questions

Part 1 · Communication and synchronization

Mailboxes, semaphores, and events

Select communication and synchronization primitives from their persistence, capacity, ownership, and wakeup guarantees.

Reading path
1 of 3
Concept chapters
2
Practice links
11

Question-first preparation

Practice the mechanisms on this page.

Each question maps directly to one of the chapters below, so you can test the contract before reading the explanation.

Recommended start

One representative prompt from each major mechanism. Open any card in the live question bank.

  1. Q161Synchronize a one-outstanding producer and consumerTestbench Concurrency · MediumPractice
  2. Q642EventsVerification Utilities · EasyPremium
  3. Q230Make the allocator thread-safeMemory Systems · MediumPractice
  4. Q730MailboxesVerification Utilities · EasyPremium
  5. Q626SemaphoresVerification Utilities · EasyPremium
  6. Q1055Bounded mailboxesVerification Utilities · EasyPremium
Complete question directoryFilter every stable practice link by mechanism.11 questions

01 · Message transfer

A mailbox transfers a value or handle; ownership remains a contract.

Mailbox put/get operations provide atomic communication and optional capacity. Correctness still depends on producer count, consumer count, termination, type safety, and whether sent objects are copied or shared.

Rule to say firstUse typed, bounded mailboxes when capacity is architectural, pair every blocking put with eventual space and every get with eventual data, and explicitly define whether a transferred handle is shared, frozen, or cloned.

Bounded channel

Producer → capacity → consumer

  1. 01Producerput(txn)

    Blocks when the bounded mailbox is full.

  2. 02Mailbox0…N entries

    Atomic ordering and capacity, but no automatic end-of-stream.

  3. 03Consumerget(txn)

    Blocks when empty and removes the oldest message.

  4. 04Shutdownexplicit token

    A sentinel, count, or phase protocol terminates both sides.

Producerput(txn)

Blocks when the bounded mailbox is full.

new(0)
Unbounded
It removes backpressure and can hide runaway production.
peek
Non-consuming
The item remains, so another consumer can change subsequent behavior.
Class item
Handle transfer
Clone for independent ownership or freeze after send.

A mailbox is a synchronization and communication boundary. Capacity, end-of-stream behavior, and any separate ownership handoff belong in its public contract.

01

Observe

Count producers, consumers, maximum outstanding messages, blocking operations, termination tokens, and shared class handles.

02

Decide

Choose blocking put/get for backpressure, try_put/try_get for explicit polling, and peek only when consuming later is safe.

03

Failure

A final put can deadlock after consumers stop, and sending a class handle can create a race if the producer mutates the same object afterward.

04

Prove

Assert bounded occupancy, count sends and receives, run empty/full schedules, and poison or clone transferred objects according to ownership policy.

Blocking operations create a liveness graph

put waits for capacity, get waits for an item, and peek waits for an item without removing it. A system is live only when another active process can satisfy each wait. Count operations along every shutdown path, not just the steady-state loop.

Typed channel with explicit completionsystemverilog
typedef struct {
  bit last;
  int value;
} item_t;

mailbox #(item_t) channel = new(4);

task automatic produce();
  for (int i = 0; i < 10; i++)
    channel.put('{last: 0, value: i});
  channel.put('{last: 1, value: 0});
endtask

task automatic consume();
  item_t item;
  forever begin
    channel.get(item);
    if (item.last) break;
    check(item.value);
  end
endtask

Operation counts expose deadlock

With a bounded mailbox, two gets do not guarantee that three later puts can complete. If the consumer stops after its second get, the final put may wait forever once capacity is exhausted. Build a balance table that includes the channel's initial and final occupancy.

Mailbox operation semantics
MethodBlocks?Removes?Use
putIf fullAddsBackpressured send
try_putNoAdds on successExplicit retry or drop policy
getIf emptyYesReturns a value or handle; no automatic clone or ownership transfer
try_getNoOn successNonblocking receive
peekIf emptyNoInspect next item
try_peekNoNoConditional inspection
numNoNoDiagnostic snapshot, not atomic admission

Explain it out loud

Interview reasoning checkpoints

Strong answer

A mailbox provides atomic put/get operations and blocking synchronization. A shared queue still needs locking, notification, capacity, and shutdown logic around every access.

Reason it through
  1. Identify the shared-state race around size and pop.
  2. Choose mailbox blocking or nonblocking operations deliberately.
  3. Define capacity and object ownership after transfer.
Interviewer lens

Good answers connect communication with synchronization, not only container syntax.

Common trap

Assuming mailbox transfer deep-copies a class object.

Strong answer

Balance all puts and gets, include initial capacity and shutdown messages, and show that every blocking operation has a concurrently runnable process capable of satisfying it.

Reason it through
  1. Draw a wait-for graph for full and empty states.
  2. Check the terminal path as carefully as the steady state.
  3. Use watchdogs and occupancy assertions to expose violations.
Interviewer lens

The key concept is liveness across all schedules, not merely matching source-code counts.

Common trap

Counting two gets and two puts while ignoring a final sentinel put.

02 · Resources and notifications

A semaphore protects capacity; an event announces a moment.

Semaphores count permits and events publish transient notifications. Neither stores arbitrary state, so late arrivals, fairness, reset, and cancellation need separate design.

Rule to say firstUse semaphores for quantified resource ownership, events for same-slot notification, and persistent state or a mailbox when a late waiter must not miss completion.

Synchronization selector

Permit, pulse, or persistent condition

  1. 01Semaphorecounted keys

    Acquire N units; release exactly N units.

  2. 02Event @enext trigger

    Waiter must already be listening.

  3. 03e.triggeredcurrent time slot

    True only for the remainder of the slot in which e fired.

  4. 04Persistent statebit / count / mailbox

    Late observers can discover that completion already occurred.

Semaphorecounted keys

Acquire N units; release exactly N units.

Fairness
Not guaranteed
Do not depend on a semaphore waking waiters in arrival order.
->>
Nonblocking trigger
Schedules an event trigger without blocking the caller.
Reset
Explicit
Neither an event nor leaked permit repairs itself between tests.

Choose a primitive whose memory matches the protocol: count, transient notification, or persistent completion.

01

Observe

For each wait, ask whether the condition is a resource count, a transient occurrence, or a state that must remain true for late observers.

02

Decide

Use get/put for permits, @event for the next trigger, wait(event.triggered) for current-slot observation, and a bit/counter/mailbox for persistence.

03

Failure

A late @event waiter misses an earlier trigger, and a missing semaphore put leaks capacity until all later users block.

04

Prove

Track permits with assertions, randomize waiter arrival, test trigger-before-wait and wait-before-trigger, and verify reset and cancellation paths.

Semaphores model divisible resources

new(K) creates K keys. get(N) blocks until N are available and then atomically removes them; try_get(N) returns immediately; put(N) returns capacity. The owner must release permits on every normal, error, timeout, and cancellation path.

Release permits on every pathsystemverilog
semaphore ports = new(2);

task automatic use_port(input int id);
  bit acquired = 0;
  ports.get(1);
  acquired = 1;
  begin
    automatic int status = transact(id);
    if (status != 0) $error("transaction %0d failed", id);
  end
  if (acquired) ports.put(1);
endtask

Event observation is deliberately ephemeral

@done waits for a future trigger and misses one that already happened. wait(done.triggered) can observe a trigger anywhere in the current time slot, but the property is not sticky across time slots. Use a persistent done bit, counter, barrier, or message when completion must be remembered.

Notification choices
NeedPrimitiveLate waiter behavior
Next occurrence@eventMisses earlier trigger
Occurrence in current slotwait(event.triggered)Sees same-slot trigger only
Remembered completionbit/counter plus eventReads state immediately
One payload per notificationmailboxPayload remains queued
N arrivals before releasebarrier/counter protocolState persists until phase advances

Explain it out loud

Interview reasoning checkpoints

Strong answer

No. It is true only for the remainder of the simulation time slot in which the event triggered. Use separate state when a waiter arriving later must still observe completion.

Reason it through
  1. Determine whether trigger and wait can occur in either order.
  2. If only same-slot order varies, triggered can remove that race.
  3. If time can advance, store completion in a bit, count, phase, or mailbox.
Interviewer lens

The expected phrase is “current time slot,” not “one clock cycle.”

Common trap

Treating triggered like a sticky boolean property.

Strong answer

No portable fairness guarantee should be assumed. A semaphore guarantees atomic key accounting, not an arrival-ordered arbiter.

Reason it through
  1. Separate mutual exclusion or capacity from scheduling policy.
  2. If fairness matters, place requests in an explicit ordered queue.
  3. Assert bounded wait and starvation requirements at that higher layer.
Interviewer lens

Strong answers distinguish resource safety from progress fairness.

Common trap

Building protocol correctness around the observed wakeup order of one simulator.