[System Verilog] Overview - 3 process, communication

System Verilog Process

In System Verilog, a process is an independently executing piece of code. A process, also known as a thread, can be executed within the begin~end statement in Verilog in three ways:

  • fork – join
  • fork – join_any
  • fork – join_none
글 설명 이미지, fork 종류
Fork types

fork – join

The fork – join statement starts all processes simultaneously, waits for all processes to terminate, and then proceeds with the process following the join. Let's understand this with an example.

module top;
  
  initial begin
    #1 $display("[%0t ns] Start fork", $time);
    
    //Process Start
    fork
      //Tread 1
      #5 $display("[%0t ns] Thread1", $time);
      
      //Thread 2
      begin
        #2 $display("[%0t ns] Thread2_step1", $time);
        #4 $display("[%0t ns] Thread2_step2", $time);
      end
      
      //Thread 3
      #10 $display("[%0t ns] Thread3", $time);
    join
    
    //Process end
    $display("[%0t ns] After fork", $time);
  end
  
endmodule

The simulation results are as follows.

Simulation result
Simulation result

Fork-join statements can be nested.

module tb;
  
  initial begin
    $display("[%0t] Main Thread: fork start", $time);
    
    fork
      
      fork
        print(20,"Thread1_step1");
        print(30,"Thread1_step2");
      join
      
      print(10,"Thread2");
      
    join
    
    $display("[%0t] Main Thread: fork finished", $time);
    
  end
  
  task automatic print (int _time, string t_name);
    #(_time) $display("[%0t] %s", $time, t_name);
  endtask
endmodule

The simulation results are as follows.

Simulation result
Simulation result

fork – join_any

The fork – join_any statement also starts all processes simultaneously, and the next process is processed after the first process to complete. Let's look at an example.

module tb;
  
  initial begin
    $display("[%0t] Main Thread: fork start", $time);
    
    fork
      print(20, "Thread1_0");
      print(30, "Thread1_1");
      print(10, "Thread2");
    join_any
    
    $display("[%0t] Main Thread: fork finished", $time);
    
  end
  
  task automatic print (int _time, string t_name);
    #(_time) $display("[%0t] %s", $time, t_name);
  endtask
  
endmodule

The simulation results are as follows.

Simulation result
Simulation result

The example code below produces the same result.

module tb;
  
  initial begin
    $display("[%0t] Main Thread: fork start", $time);
    
    fork
      fork
        print(20, "Thread1_0");
        print(30, "Thread1_1");
      join_any
      print(10, "Thread2");
    join_any
    
    $display("[%0t] Main Thread: fork finished", $time);
    
  end
  
  task automatic print (int _time, string t_name);
    #(_time) $display("[%0t] %s", $time, t_name);
  endtask
  
endmodule

fork – join_none

The fork – join_none statement starts processes simultaneously, but unlike fork – join or fork – join_any , it doesn't wait for any process to complete. Let's understand with an example.

module tb;
  
  initial begin
    $display("[%0t] Main Thread: fork start", $time);
    
    fork
      print(20, "Thread1_0");
      print(30, "Thread1_1");
      print(10, "Thread2");
    join_none
    
    $display("[%0t] Main Thread: fork finished", $time);
    
  end
  
  task automatic print (int _time, string t_name);
    #(_time) $display("[%0t] %s", $time, t_name);
  endtask
  
endmodule

The simulation results are as follows.

Simulation 결과
Simulation result

fork – You can see that the display statement following join_none is executed without waiting for the process.

Disable fork

The disable fork statement allows you to terminate a running process. Let's first look at an example without using disable .

module tb;
  
  initial begin
    fork
      
      //Thread1
      #40 $display("[%0t ns] show #40", $time);
      
      //Thread2
      begin
        #20 $display("[%0t ns] show #20", $time);
        #50 $display("[%0t ns] show #50", $time);
      end
      
      //Thread3
      #60 $display("[%0t ns] TIMEOUT", $time);
    join_any
    
    //fork done
    $display("[%0t ns] fork - join_any done", $time);
  end
  
endmodule

After Thread1, the first process to finish, finishes, display, which follows join_any, is executed.

Simulation result
Simulation result

Now, let's look at an example using disable fork.

module tb;
  
  initial begin
    fork
      
      //Thread1
      #40 $display("[%0t ns] show #40", $time);
      
      //Thread2
      begin
        #20 $display("[%0t ns] show #20", $time);
        #50 $display("[%0t ns] show #50", $time);
      end
      
      //Thread3
      #60 $display("[%0t ns] TIMEOUT", $time);
    join_any
    
    //fork done
    $display("[%0t ns] fork - join_any done, disable fork", $time);
    
    disable fork;
  end
  
endmodule

The process indicated by Disable fork will not be executed and the fork statement will exit.

Simulation result
Simulation result

Wait fork

Wait fork waits for all processes in the fork-join statement to finish. Let's look at an example.

module tb;
  
  initial begin
    fork
      
      //Thread1
      #40 $display("[%0t ns] show #40, Thread1", $time);
      
      //Thread2
      begin
        #20 $display("[%0t ns] show #20, Thread2", $time);
        #50 $display("[%0t ns] show #50, Thread2", $time);
      end
      
      //Thread3
      #60 $display("[%0t ns] TIMEOUT, Thread3", $time);
    join_any
    
    //join_any done
    $display("[%0t ns] fork - join_any done, wait fork", $time);
    
    wait fork;
    
    //fork done
    $display("[%0t ns] fork - join done", $time);
  end
  
endmodule

The "join_any done" message is displayed after Thread1, which is the first process to finish within the fork statement. Then, "fork done" is displayed after all processes have finished due to wait fork.

Simulation result
Simulation result

So, does wait fork wait until all processes are finished? You can understand by looking at the example below.

module tb;
  
  initial begin
    //First fork
    fork
      
      //Thread1
      #40 $display("[%0t ns] show #40, Thread1", $time);
      
      //Thread2
      begin
        #20 $display("[%0t ns] show #20, Thread2", $time);
        #50 $display("[%0t ns] show #50, Thread2", $time);
      end
      
      //Thread3
      #60 $display("[%0t ns] TIMEOUT, Thread3", $time);
    join_any
    
    //join_any done
    $display("[%0t ns] fork - join_any done", $time);
    
    //Second fork
    fork
      #10 $display("[%0t ns] wait for 10ns", $time);
      #20 $display("[%0t ns] wait for 20ns", $time);
    join_any
    
    wait fork;
    
    //fork done
    $display("[%0t ns] fork - join done", $time);
  end
  
endmodule

I added a second fork to the original code. However, the first fork finishes more slowly, and all processes in the first fork are waiting due to the wait fork.

Simulation result
Simulation result

We have looked at the types and examples of the fork statement, a process used in System Verilog.

System Verilog Communication

When conducting verification, you may need to exchange data between components in the testbench or check output values. System Verilog provides several mechanisms to control the flow of control between threads or components.

  • Events: Synchronization between threads using each event handle within the testbench.
  • Semaphores: Use semaphores to allow alternate access to shared resources between threads.
  • Mailbox: Allows data exchange between threads and components using a mailbox.

Semaphore

Semaphore is a class built into System Verilog that controls access to shared resources. A semaphore bucket has a fixed number of keys, so only processes with a key can access the resource. If a process lacks a key, it waits until another process releases a key.

The semaphore methods supported in System Verilog are as follows:

NameDescription
function new();Specifies the number of keys initially assigned to the Semaphore bucket.
function void put();Specifies the number of keys in the semaphore to be returned.
task get();Specifies the number of keys to obtain from the semaphore.
If you are in a situation where you can receive a semaphore key, continue waiting.
function int try_get();Specifies the number of keys to retrieve from the semaphore. Does not wait if no keys are found.
Semaphore method

Let's check an example.

module tb;

  semaphore key;

  initial begin
    key = new(1);
    fork
      personA();
      personB();
      #25 personA();
    join_none
  end

  task getRoom (bit [1:0] id);
    $display("[%0t] Trying to get a room for id [%0d] ...", $time, id);
    key.get(1);
    $display("[%0t] Room key retrieved id [%0d] ...", $time, id);
  endtask

  task putRoom (bit [1:0] id);
    $display("[%0t] Leaving room id [%0d] ...", $time, id);
    key.put(1);
    $display("[%0t] Room key put back id [%0d] ...", $time, id);
  endtask

  task personA ();
    getRoom(1);
    #20 putRoom(1);
  endtask

  task personB ();
    #5  getRoom(2);
    #10 putRoom(2);
  endtask

endmodule

The simulation results are as follows.

Simulation result
Simulation result

References: chip verify

Similar Posts