SystemVerilog Semaphores
SystemVerilog semaphores are used to control the access of shared resources. It is a built-in class in SystemVerilog used for synchronization which is a container that contains a fixed number of keys.
For example, the same memory location is accessed by two different cores. To avoid unexpected results when cores try to write or read from the same memory location, a semaphore can be used.
Methods in semaphore
Method name |
Description |
new() |
To create a semaphore with a specified number of keys |
get() |
To obtain or get a specified number of keys |
put() |
To put or return the number of keys |
try_get() |
Try to obtain or get a specified number of keys without blocking the execution |
new()
The new() method is used to create the semaphore with a specified number of keys. By default, no keys are created. The new() method returns semaphore handle or null if it is not created.
Syntax:
<semaphore> = new(<number_of_keys>); // <number_of_keys> is integer value
get()
The get() method in semaphore is used to obtain a specified number of keys. By default, one key is returned if no value is specified. The get() method is a blocking method and execution continues after successful key or keys are obtained.
Syntax:
<semaphore>.get(<number_of_keys>);
put()
The put() method in semaphore is used to return a specified number of keys to the semaphore container or bucket.
Syntax:
<semaphore>.put(<number_of_keys>);
try_get()
The try_get() method in semaphore tries to obtain a specified number of keys. The get() method is blocking whereas try_get() is a non-blocking method. The execution is not blocked even if the number of keys is not available. The try_get() function returns 1 if keys are available otherwise, it returns 0 if no keys are available,
Syntax:
<semaphore>.try_get(<number_of_keys>);
Semaphore Examples
Semaphore having a single key
To understand how semaphore provides controlled access to the shared resource (memory in the below example), compare the output of the below two codes.
In the below examples, the write_mem task takes 5ns to write into memory and the read_mem takes 4ns to read data from memory. Two different process wants to access memory which is not aware that another one is accessing at the same time, in such a cases semaphore is useful
A. Without using semaphore
module semaphore_example();
task write_mem();
$display("Before writing into memory");
#5ns; // Assume 5ns is required to write into mem
$display("Write completed into memory");
endtask
task read_mem();
$display("Before reading from memory");
#4ns; // Assume 4ns is required to read from mem
$display("Read completed from memory");
endtask
initial begin
fork
write_mem();
read_mem();
join
end
endmodule
Output:
Before writing into memory
Before reading from memory
Read completed from memory
Write completed into memory
As you can see, before the write_mem task is completed, the read_mem task has already started. Due to this, the unexpected outcome is observed. Let’s see how semaphore solves this problem in the below example.
B. Using semaphore
module semaphore_example();
semaphore sem = new(1);
task write_mem();
sem.get();
$display("Before writing into memory");
#5ns // Assume 5ns is required to write into mem
$display("Write completed into memory");
sem.put();
endtask
task read_mem();
sem.get();
$display("Before reading from memory");
#4ns // Assume 4ns is required to read from mem
$display("Read completed from memory");
sem.put();
endtask
initial begin
fork
write_mem();
read_mem();
join
end
endmodule
Output:
Before writing into memory
Write completed into memory
Before reading from memory
Read completed from memory
Semaphore having multiple keys
The semaphore is created with 3 keys as shown in the below example.
process_A requires all 3 keys whereas process_B requires 2 keys to access the resource.
module semaphore_example();
semaphore sem = new(3);
task process_A();
sem.get(3);
$display("process_A started");
#5ns;
$display("process_A completed");
sem.put(3);
endtask
task process_B();
sem.get(2);
$display("process_B started");
#4ns;
$display("process_B completed");
sem.put(2);
endtask
initial begin
fork
process_A();
process_B();
join
end
endmodule
Output:
process_A started
process_A completed
process_B started
process_B completed
try_get() example
try_get() is a non-blocking semaphore method where execution is not blocked if keys are not available. In the below example, process_A obtained a key at first and even if a key is not available, process_B execution is not blocked.
module semaphore_example();
semaphore sem = new(1);
task process_A();
if(sem.try_get())
$display("process_A: Key received");
else
$display("process_A: Key is not available");
$display("process_A started");
#5ns;
$display("process_A completed");
sem.put();
endtask
task process_B();
if(sem.try_get())
$display("process_B: Key received");
else
$display("process_B: Key is not available");
$display("process_B started");
#4ns;
$display("process_B completed");
sem.put();
endtask
initial begin
fork
process_A();
process_B();
join
end
endmodule
Output:
process_A: Key received
process_A started
process_B: Key is not available
process_B started
process_B completed
process_A completed
Put more keys back
In the below example, 3 keys are obtained using the get method whereas 5 keys are put back. It is one of the ways to manage resources. Note that no error is observed in the example since keys allocation is initialized, it is not like fixed keys are assigned.
Let’s try to access more keys than initialized keys when process_A puts back extra keys i.e. 5 keys and same numbers keys are accessed with get() call in process_B. Thus, process_B will get executed. But process_C requires 6 keys that are no longer available which will block process_C execution.
module semaphore_example();
semaphore sem = new(3);
task process_A();
sem.get(3);
$display("process_A is started");
#5ns;
$display("process_A is completed");
sem.put(5);
endtask
task process_B();
sem.get(5); // Accessing more keys than initialized
$display("process_B is started");
#5ns;
$display("process_B is completed");
sem.put(5);
endtask
task process_C();
sem.get(6); // Accessing more keys than available in the bucket
$display("process_C is started");
#5ns;
$display("process_C is completed");
sem.put(5);
endtask
initial begin
fork
process_A();
process_B();
process_C();
join
end
endmodule
Output:
process_A is started
process_A is completed
process_B is started
process_B is completed
System Verilog Tutorials