A mailbox is a thread-safe queue: put blocks when full, get blocks when empty. A semaphore is a bag of keys: get(1) waits, put(1) releases.
flowchart LR gen[generator] -->|put| mb[mailbox] mb -->|get| drv[driver] sem[semaphore] --> lock[shared_bus]
mailbox #(txn) mb = new();
mb.put(tr);
mb.get(tr2);
semaphore bus = new(1);
bus.get(1);
// ... exclusive access ...
bus.put(1);
Parameterized mailboxes (mailbox #(txn)) catch type errors at compile time. Mini-UVM and full UVM TLM ports wrap the same idea.
Wrong vs right
Wrong — two forever threads sharing an array without a mailbox or semaphore.
Right — one producer put, one consumer get; or a semaphore around a shared interface.