Tutorials

Learn More

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.

module semaphore_example();
  semaphore sem = new(3);
  
  task process();
    sem.get(3);
    $display("process is started");
    #5ns;
    $display("process is completed");
    sem.put(5);
  endtask

  initial begin
    process();
  end
endmodule

Output:

process is started
process is completed

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