HomeSystemVerilog VerificationDay 10
DAY 10 · OOP PHASE

SystemVerilog Mailboxes, Semaphores & Events — Synchronization Primitives

By EcrioniX · Updated Jun 13, 2026

Days 7–9 showed you how to create flexible testbench components with classes. Now add inter-process communication (IPC) primitives: mailboxes for passing data between threads, semaphores for resource counting and mutual exclusion, and events for simple synchronization triggers.

Mailboxes: data + synchronization

A mailbox is a FIFO queue for passing data between parallel processes. It combines data transfer and synchronization in one primitive.

Mailbox operations

mailbox mb;

mb = new();                    // Create mailbox
mb.put(item);                  // Send item (may block if full)
mb.get(item);                  // Receive item (blocks if empty)
mb.try_get(item);              // Non-blocking: return 1 if got, 0 if empty
mb.try_put(item);              // Non-blocking: return 1 if sent, 0 if full
int count = mb.num();          // Number of items in mailbox

Blocking and non-blocking gets

get() is blocking: it waits until an item is available. This is useful for driver-monitor synchronization: the driver puts transactions, the monitor gets them, naturally blocking until each arrives. Use try_get() for non-blocking polling.

Semaphores: resource counting

A semaphore is a counter used for mutual exclusion (mutex) and resource allocation. get() decrements; put() increments. If count is zero, get() blocks.

Mutex example (semaphore initialized to 1)

semaphore sem = new(1);  // Initial count: 1 (available)

// Critical section: only one process at a time
sem.get(1);              // Wait if count is 0, then decrement
// ... exclusive access to shared resource
sem.put(1);              // Increment; allows next waiter to proceed

Events: simple triggers

An event is a one-way synchronization primitive: one process triggers it, others wait for it. No data is passed, just a signal.

event done;

// Process A: trigger
initial begin
  #100;
  -> done;  // Trigger the event
end

// Process B: wait
initial begin
  @(done);  // Wait for the event
  $display("Event received!");
end

Complete example: driver-monitor-scoreboard with mailboxes

tb_with_mailbox.sv
// ---- Transaction class ----
class transaction;
  logic [7:0] data;
  function new(logic [7:0] d = 0);
    data = d;
  endfunction
endclass

// ---- Driver: generates transactions ----
class driver;
  mailbox tx_mb;  // Mailbox to send transactions

  function new(mailbox m);
    tx_mb = m;
  endfunction

  task run();
    for (int i = 0; i < 5; i++) begin
      transaction tx = new(8'hA0 + i);
      tx_mb.put(tx);  // Send to mailbox
      $display("[%0t] Driver sent: %h", $time, tx.data);
      #10;
    end
  endtask
endclass

// ---- Monitor: receives transactions ----
class monitor;
  mailbox tx_mb;  // Mailbox to receive transactions
  mailbox sb_mb;  // Mailbox to send to scoreboard
  event mon_done; // Event to signal completion

  function new(mailbox t, mailbox s, event e);
    tx_mb = t;
    sb_mb = s;
    mon_done = e;
  endfunction

  task run();
    transaction tx;
    repeat (5) begin
      tx_mb.get(tx);  // Receive (blocks if empty)
      $display("[%0t] Monitor got: %h", $time, tx.data);
      sb_mb.put(tx);  // Forward to scoreboard
    end
    -> mon_done;  // Trigger completion event
  endtask
endclass

// ---- Scoreboard: verifies transactions ----
class scoreboard;
  mailbox sb_mb;
  event sb_done;

  function new(mailbox m, event e);
    sb_mb = m;
    sb_done = e;
  endfunction

  task run();
    transaction tx;
    repeat (5) begin
      sb_mb.get(tx);  // Receive from mailbox
      $display("[%0t] Scoreboard verified: %h", $time, tx.data);
    end
    -> sb_done;  // Trigger completion event
  endtask
endclass

// ---- Testbench ----
module tb_ipc;
  mailbox tx_mb;      // driver -> monitor
  mailbox sb_mb;      // monitor -> scoreboard
  event mon_done;     // monitor completion
  event sb_done;      // scoreboard completion

  driver drv;
  monitor mon;
  scoreboard sb;

  initial begin
    // Create mailboxes and events
    tx_mb = new();
    sb_mb = new();

    // Create components
    drv = new(tx_mb);
    mon = new(tx_mb, sb_mb, mon_done);
    sb = new(sb_mb, sb_done);

    // Run all in parallel
    fork
      drv.run();
      mon.run();
      sb.run();
    join

    // Wait for all to finish
    @(mon_done);
    @(sb_done);
    $display("All done!");
    $finish;
  end
endmodule

Comparison: mailbox vs semaphore vs event

PrimitivePurposeData?Use case
MailboxPass data + syncYesDriver sends TX, monitor receives TX
SemaphoreMutual exclusion / countingNoMutex for shared resources
EventSimple triggerNoScoreboard done, simulation complete

Day 10 takeaways

Frequently Asked Questions

What is a mailbox?

A mailbox is a FIFO queue for passing data between parallel processes. put(item) sends; get(item) receives. By default, get() blocks if empty, providing both data transfer and synchronization.

What does blocking mean in a mailbox?

Blocking: if you call get() on an empty mailbox, the process suspends until an item is available. This is useful for natural synchronization — the receiver waits for the sender. Use try_get() for non-blocking polling.

How do I use a semaphore for mutex?

Create with count=1: semaphore sem = new(1). Before critical section: sem.get(1) (waits if locked, then acquires). After: sem.put(1) (releases). Only one process can hold the semaphore at a time.

Previous
← Day 9: Factories

← Full course roadmap