Part 2 · OOP for Verification · Intermediate
Events, Semaphores, Mailboxes & Processes
Hub — events, semaphores, mailboxes, fork variants, process control, and a generator-driver handshake lab.
Overview
A testbench is a collection of concurrent processes — a generator producing transactions, a driver wiggling pins, a monitor watching them, a scoreboard comparing. None of these run in isolation; they must hand data to each other, share resources, and signal completion. SystemVerilog provides three built-in synchronization primitives — event, semaphore, and mailbox — plus the fork...join family and std::process for spawning and controlling threads.
Choosing the right primitive matters: an event carries no data and signals a moment in time; a semaphore arbitrates access to a shared resource; a mailbox moves data between threads with optional backpressure. Interviewers love this trio because the failure modes — the trigger-before-wait race, semaphore deadlock, unbounded mailbox memory growth — separate engineers who have debugged real testbenches from those who memorized syntax.
Sub-topics
Events & event Triggering — -> vs @, .triggered, handle passing, wait_order.
Semaphores — keys, get/put/try_get, bus arbitration, deadlock.
Mailboxes — bounded vs unbounded, parameterized mailbox, backpressure.
fork...join, join_any, join_none — semantics and the loop-variable bug.
disable fork, wait fork & process — scoping, isolation, fine-grained control.
Lab: Generator → Driver Handshake — complete runnable mini-environment.
IPC TOPIC MAP
WHO TALKS TO WHOM, AND HOW
generator ──── mailbox #(txn) ────► driver (data transfer)
│ │
│ ▼
│ virtual interface ──► DUT pins
│
└──── event done ◄──────────── driver (moment-in-time signal)
driver A ──┐
├──► semaphore bus_sem (1 key) ──► shared bus (mutual exclusion)
driver B ──┘
test ── fork
├── generator.run() \
├── driver.run() ├─ join_none / wait fork (thread lifecycle)
└── watchdog timeout /
Primitive Carries data? Blocking? Typical use
───────── ───────────── ─────────────── ─────────────────────────
event no @ / wait blocks done flags, phase sync
semaphore no (keys) get blocks resource arbitration
mailbox yes put/get block producer-consumer pipesKey takeaways
Events signal moments, semaphores arbitrate resources, mailboxes move data — pick by job, not habit.
Every fork variant has a distinct blocking semantic; misusing join_none causes most thread bugs.
The classic races (trigger-before-wait, loop-variable capture, disable fork killing siblings) are standard interview questions.