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.
A mailbox is a FIFO queue for passing data between parallel processes. It combines data transfer and synchronization in one primitive.
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
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.
A semaphore is a counter used for mutual exclusion (mutex) and resource allocation. get() decrements; put() increments. If count is zero, get() blocks.
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
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
// ---- 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| Primitive | Purpose | Data? | Use case |
|---|---|---|---|
| Mailbox | Pass data + sync | Yes | Driver sends TX, monitor receives TX |
| Semaphore | Mutual exclusion / counting | No | Mutex for shared resources |
| Event | Simple trigger | No | Scoreboard done, simulation complete |
put(item) sends, get(item) receives (blocking by default).get() waits/decrements, put() increments.->event fires, @(event) waits.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.
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.
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.