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 – 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
endmoduleThe simulation results are as follows.
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
endmoduleThe simulation results are as follows.
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
endmoduleThe simulation results are as follows.
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
endmodulefork – 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
endmoduleThe simulation results are as follows.
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
endmoduleAfter Thread1, the first process to finish, finishes, display, which follows join_any, is executed.
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
endmoduleThe process indicated by Disable fork will not be executed and the fork statement will exit.
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
endmoduleThe "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.
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
endmoduleI 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.
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:
| Name | Description |
|---|---|
| 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. |
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
endmoduleThe simulation results are as follows.
References: chip verify