Semaphore in SystemVerilog:
In short: A semaphore in SystemVerilog controls access to a shared resource by handing out a fixed number of keys. A process calls get() to take one or more keys before using the resource and put() to return them when done. If no key is free, get() blocks until one is returned; try_get() checks without waiting. With a single key a semaphore acts as a mutex, letting only one process use the resource at a time.
In a testbench several processes may want the same resource, such as a shared bus, but the design only allows one driver at a time. A semaphore is the built in class that arbitrates this. It holds a bucket of keys, and a process must hold a key to use the resource, so access stays orderly.
A simple way to picture it
Think of a small office with two meeting rooms and a bowl of two room keys at reception. Anyone who wants a room takes a key from the bowl, holds their meeting, then drops the key back. If both keys are out, the next person waits at reception until someone returns one. The bowl is the semaphore, the keys are the count, and taking and returning keys are get() and put().
Semaphore methods
A semaphore is a built in class with a short set of methods. You create it with a starting key count, then take and return keys around the code that touches the shared resource.
| Method | Blocking? | What it does |
|---|---|---|
new(keyCount) | n/a | Creates a semaphore with a starting number of keys |
get(n) | Yes | Takes n keys; waits if fewer than n are free |
put(n) | No | Returns n keys to the bucket |
try_get(n) | No | Takes n keys if available and returns 1, else returns 0 and takes none |
When several processes block on get(), they are served in first in first out order, so no process is starved. The key count can be more than one, which lets you allow a fixed number of users at the same time rather than just one.
Example 1: a semaphore as a mutex
With a single key the semaphore behaves as a mutex: exactly one process may hold the resource. Here two processes both want the shared bus, but only one drives it at a time while the other waits for the key.
module mutex_demo;
semaphore bus_key = new(1); // one key => mutex
task use_bus(string name);
$display("[%0t] %s: waiting for key", $time, name);
bus_key.get(1);
$display("[%0t] %s: got key, driving bus", $time, name);
#10ns; // do the transfer
bus_key.put(1);
$display("[%0t] %s: returned key", $time, name);
endtask
initial begin
fork
use_bus("AGENT_A");
use_bus("AGENT_B");
join
end
endmoduleExample 2: allowing a fixed number of users with multiple keys
A semaphore is not limited to one key. If a resource can serve two clients at once, start it with two keys. A third process then waits until one of the first two finishes. This is useful for a model of a port that has two channels.
module pool_demo;
semaphore channels = new(2); // two channels available
task run(int id);
channels.get(1);
$display("[%0t] job %0d using a channel", $time, id);
#20ns;
channels.put(1);
$display("[%0t] job %0d freed a channel", $time, id);
endtask
initial begin
fork
run(1); run(2); run(3); // 3 jobs, only 2 channels
join
end
endmoduleJobs 1 and 2 start right away because two keys are free. Job 3 waits until job 1 or job 2 returns a key, then runs.
Example 3: non blocking access with try_get
Use try_get() when a process should not stall. It returns immediately with 1 if it took the key or 0 if none was free, so the process can take a different path instead of waiting.
module try_demo;
semaphore lock = new(1);
initial begin
lock.get(1); // main path takes the only key
fork
begin
if (lock.try_get(1))
$display("got the lock");
else
$display("busy, skipping this cycle"); // this runs
end
join
end
endmoduleExpected output, in plain words
In example 1 one agent takes the key and drives the bus while the other prints a waiting line; only after the first returns the key does the second get it. In example 2 two jobs run at once and the third starts only after a channel frees up. In example 3 the single key is already held, so try_get returns 0 and the skip branch prints.
Note: these outputs describe what the code is written to produce from a read of the IEEE 1800 LRM. Compile and run on your own simulator or EDA Playground to confirm exact timing.
Semaphore vs mailbox
People often mix these up because both are built in classes used with parallel processes. The difference is what they carry. A mailbox passes data between processes. A semaphore passes no data; it only grants permission to proceed. Use a mailbox to hand a transaction from a generator to a driver, and a semaphore to make sure only one process drives a shared resource at a time.
| Point | Semaphore | Mailbox |
|---|---|---|
| Purpose | Control access to a resource | Pass data between processes |
| Carries data? | No, only keys | Yes, items or objects |
| Blocks when | No key is free on get | Full on put, empty on get |
| Typical use | Mutex for a shared bus | Generator to driver hand off |
Common mistakes to avoid
- Forgetting to
put()the key: if a process takes a key and never returns it, other processes block forever. - Mismatched get and put counts: returning more keys than you took inflates the bucket and breaks the limit.
- Starting with the wrong count: the default key count is 0, so a semaphore created without an argument blocks the first
get(). - Using a semaphore to pass data: it only grants access; use a mailbox when you need to move data.
- Holding a key across a long wait: keep the held region short so other processes are not starved.
To pass data between the processes a semaphore guards, see our guide on the SystemVerilog mailbox. To run the processes in parallel, read about fork join. More tutorials are in our SystemVerilog section and interview questions.
Frequently asked questions
What is a semaphore in SystemVerilog?
A semaphore is a built in class that controls access to a shared resource using a bucket of keys. A process takes a key with get before using the resource and returns it with put when done.
What is the difference between get and try_get?
get is blocking: it waits until enough keys are free. try_get is non blocking: it takes the keys and returns 1 if they are available, or returns 0 immediately and takes none if they are not.
How is a semaphore used as a mutex?
Create it with a single key using new(1). Only one process can hold that key at a time, so only one process uses the resource while others wait. This is mutual exclusion, often called a mutex.
What is the difference between a semaphore and a mailbox?
A semaphore controls access and carries no data; it only grants permission with keys. A mailbox passes data between processes. Use a semaphore to guard a shared bus and a mailbox to move transactions.
What is the default key count of a semaphore?
The default key count is 0. A semaphore created without an argument has no keys, so the first get blocks until a put adds keys. Set a starting count such as new(1) for a mutex.
In what order are blocked processes served?
Processes that block on get are queued and served in first in first out order, so no process is starved as long as keys keep coming back through put.


