Semaphore in System Verilog

Semaphore

A semaphore in SystemVerilog is a synchronization mechanism used to control access to shared resources among multiple processes.

It is particularly useful in testbenches to prevent race conditions when multiple threads try to access the same resource.

A semaphore consists of a fixed number of keys (or tokens), which can be acquired and released by processes. If a process wants to use a resource, it must acquire a key; once it is done, it releases the key, allowing other processes to access the resource.

Real-Life Analogy

Consider a public restroom with three stalls. If three people are using the stalls, others must wait until a stall is free. This scenario can be modeled using a semaphore with three keys:

  • Each person (process) acquires one key before using a stall.
  • If no keys are available, they must wait.
  • Once done, they release the key, making a stall available for the next person.

Semaphore Methods

SystemVerilog provides a built-in semaphore class with the following key methods:

  • new() – Creates a semaphore with a defined number of keys.
  • get(n) – Acquires n keys from the semaphore before accessing a shared resource.
  • put(n) – Releases n keys back to the semaphore after resource usage.
  • try_get(n) – Attempts to acquire n keys without blocking the process if keys are unavailable.

Important things to consider before writing semaphore code in System Verilog:

  1. Understand the Need for Synchronization
    Identify the shared resources in your testbench that multiple processes will access.

Determine if resource contention could cause race conditions.

  1. Decide the Number of Keys
    Set the number of semaphore keys based on the number of available resources.

Example: If you have three memory blocks that can be accessed concurrently, initialize the semaphore with three keys.

  1. Proper Use of get() and put() Methods
    Use get(n) to acquire n keys before accessing a shared resource.

Use put(n) to release n keys after completing the resource usage.

Ensure that every get(n) has a corresponding put(n) to avoid deadlocks.

  1. Avoid Deadlocks
    Deadlocks occur when multiple processes are waiting indefinitely for keys that are never released.

Ensure that resources are always released after use.

  1. Use try_get() for Non-blocking Access
    If a process should proceed even if it fails to acquire a key, use try_get(n) instead of get(n).

Example: If try_get(1) fails, the process can take an alternative action instead of waiting.

  1. Consider the Impact of fork-join
    When using fork-join, be aware of concurrent processes trying to access the semaphore.

Use fork-join_none or fork-join_any if required to avoid unnecessary blocking.

  1. Debugging with num()
    Use the num() method to check the current number of available keys for debugging purposes.

Example: $display(“Available keys: %0d”, sem.num());

Example:- Simple Semaphore Example



module semaphore_ex;
  semaphore sema;

  initial begin
    sema = new(2); // create semaphore with 2 keys
    fork
      task1(); // process-1
      task2(); // process-2
      task3(); // process-3 with timed check
    join
  end

  task automatic task1();
    sema.get(1);
    $display($time, "\tTask 1 got key");
    #10;
    sema.put(1);
  endtask

  task automatic task2();
    sema.get(1);
    $display($time, "\tTask 2 got key");
    #10;
    sema.put(1);
  endtask

  task automatic task3();
    #5; // wait for 5ns before checking
    if (sema.try_get(1)) begin
      $display($time, "\tTask 3 got key after waiting");
      #10;
      sema.put(1);
    end else begin
      $display($time, "\tTask 3 could not get key");
    end
  endtask
endmodule



Output:
xcelium> run
0 Task 1 got key
0 Task 2 got key
5 Task 3 could not get key
xmsim: *W,RNQUIE: Simulation is complete.

At time 0, Task 1 and Task 2 both successfully acquire the two available keys. Since the semaphore was initialized with 2 keys, both tasks can immediately proceed without any delay.

Meanwhile, Task 3 waits for 5 nanoseconds before it tries to acquire a key. It uses the try_get() method, which does not block — it simply checks if a key is available at that exact moment.

At time 5, Task 3 checks and finds that both keys are still in use (since Task 1 and Task 2 hold them for 10 ns), so it fails to get a key and exits gracefully with the message:
"5 Task 3 could not get key"

Explanation of the code:

This SystemVerilog example demonstrates the use of a semaphore with 2 keys to control access among 3 tasks. The first two tasks (task1 and task2) immediately acquire the available keys and simulate some work by waiting for 10 nanoseconds. The third task (task3) waits for 5 nanoseconds before attempting to acquire a key using try_get, which does not block. If a key is available at that moment, it proceeds; otherwise, it exits gracefully. This simple simulation effectively illustrates how semaphores can manage limited shared resources and how non-blocking attempts (try_get) can be used for conditional access based on availability.

Key Takeaways

  • Semaphore controls access to limited resources using “keys” — only as many tasks can proceed as there are available keys.
  • get() is blocking — a task will wait until the required number of keys are available.
  • try_get() is non-blocking — it checks if keys are available at that moment and proceeds only if they are.
  • Resource prioritization can be controlled with timing — in this case, the third task waits for 5 ns before trying.
  • Graceful fallback is possible — if a key isn’t available, the task doesn’t hang or crash; it exits with a message.
  • This pattern is useful in real-world systems like managing access to shared buses, memory, or I/O ports in hardware design and verification.

Using a Class to Modify Semaphore Keys Dynamically

This SystemVerilog code models a real-life scenario where multiple people share a limited number of restroom stalls. The concept is implemented using a semaphore, which is a synchronization mechanism used to control access to a shared resource. The RestroomControl class creates a semaphore object named restroom, which is initialized with a certain number of “keys” representing the available stalls. When someone wants to use the washroom, they call the use_washroom task. This task tries to acquire one key from the semaphore, symbolizing entry into a stall. If a key is available, the person enters; otherwise, they wait until someone exits. Once inside, a message is displayed, a small delay simulates the time spent in the restroom, and then the person exits, releasing the key back to the semaphore.

The restroom_semaphore_class module creates an instance of the RestroomControl class and initially assigns 3 stalls to the restroom. After a delay, the number of stalls is increased to 5. Meanwhile, 8 people attempt to use the washroom simultaneously using fork...join, which means all requests are made at the same time. However, due to the limited number of stalls, only a few people can enter at once while the rest wait their turn. This simulation effectively demonstrates how semaphores manage resource sharing in both hardware and software systems.

In real-world scenarios, the number of restroom users may increase. Below is an updated version using a System Verilog class to allow dynamic key allocation.


class RestroomControl;
    semaphore restroom;
    
    function new(int num_stalls);
        restroom = new(num_stalls); // Initialize semaphore with dynamic keys
    endfunction
    
    task use_restroom(int person_id);

        restroom.get(1); // Acquire one stall
        $display("Person %0d entered the restroom at time %0t", person_id, $time);
        #5; // Simulate washroom usage
        $display("Person %0d exited the restroom at time %0t", person_id, $time);
        restroom.put(1); // Release the stall
    endtask
endclass

module restroom_semaphore_class;
    RestroomControl rc;
    
    initial begin
        rc = new(3); // Initially, only 3 stalls
        #10;
        rc = new(5); // Later, increase to 5 stalls
    end
    
    initial begin
        fork
            rc.use_restroom(1);

            rc.use_restroom(2);

            rc.use_restroom(3);

            rc.use_restroom(4);

            rc.use_restroom(5);

            rc.use_restroom(6);

            rc.use_restroom(7);

            rc.use_restroom(8);
        join
    end
endmodule

Output:-
xcelium> run
Person 1 entered the restroom at time 0
Person 2 entered the restroom at time 0
Person 3 entered the restroom at time 0
Person 1 exited the restroom at time 5
Person 2 exited the restroom at time 5
Person 3 exited the restroom at time 5
Person 4 entered the restroom at time 5
Person 5 entered the restroom at time 5
Person 6 entered the restroom at time 5
Person 4 exited the restroom at time 10
Person 5 exited the restroom at time 10
Person 6 exited the restroom at time 10
Person 7 entered the restroom at time 10
Person 8 entered the restroom at time 10
Person 7 exited the restroom at time 15
Person 8 exited the restroom at time 15
xmsim: *W,RNQUIE: Simulation is complete.
xcelium> exit

Explanation:

The output shows how people use a restroom with a limited number of stalls (3 in this case), and how access is managed over time using a semaphore.

Let’s walk through the output step-by-step:

  1. At time 0, Persons 1, 2, and 3 enter the washroom:
    • This is because the semaphore was initialized with 3 keys, meaning 3 stalls were available.
    • These three persons entered simultaneously as stalls were free.
  2. At time 5, Persons 1, 2, and 3 exit:
    • Each of them used the washroom for 5 time units.
    • As soon as they leave, they release the stalls back to the semaphore.
  3. Immediately at time 5, Persons 4, 5, and 6 enter:
    • They were waiting while the stalls were occupied.
    • Now that 3 stalls are free again, the next 3 persons enter.
  4. At time 10, Persons 4, 5, and 6 exit:
    • They also spent 5 time units in the washroom.
  5. At time 10, Persons 7 and 8 enter:
    • Only two persons are left waiting at this point, and all 3 stalls are free.
    • Even though 3 stalls are available, only 2 people are in the queue, so only they enter.
  6. At time 15, Persons 7 and 8 exit, ending the simulation.

Explanation of the Code

  1. The RestroomControl class encapsulates the semaphore functionality.
  2. The new(int num_stalls) constructor allows dynamic allocation of stalls.
  3. The use_restroom task ensures synchronized access using semaphore methods.
  4. The restroom_semaphore_class module initializes the class with 3 stalls and later increases it to 5 as demand grows.
  5. Eight people try to use the restroom, demonstrating controlled access as per the updated semaphore keys.

Conclusion

Semaphores in SystemVerilog are crucial for synchronizing multiple processes and preventing resource conflicts. The use of a class-based approach provides flexibility, allowing the semaphore size to be adjusted dynamically as the number of users increases.

Leave a Comment

Your email address will not be published. Required fields are marked *

The US Hits China With a Huge Microchip Bill FPGA Design Engineer Interview Questions Semiconductor Industry the huge break through