System Verilog Process
System Verilog에서 process는 독립적으로 실행되는 code입니다. Thread라고도 하는 process는 Verilog에서 운용되던 begin~end 문 안에서 아래 세 가지 방법이 추가되어 진행될 수 있습니다.
- fork – join
- fork – join_any
- fork – join_none
fork – join
fork – join 문에서는 모든 process가 동시에 시작되고, 모든 process가 종료될 때까지 기다렸다가 이어서 join 뒤의 process를 진행합니다. 예시로 이해해 볼까요?
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 문은 중첩이 가능합니다.
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
fork – join_any 문도 모든 process가 동시에 시작되고, 가장 먼저 완료된 process 이후에 다음 process를 진행합니다. 예시를 확인해 봅시다.
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.
아래 예시 코드도 동일한 결과를 보입니다.
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
fork – join_none 문은 process가 동시에 시작되지만, fork – join이나 fork – join_any와 달리 어떤 process도 완료되기를 기다리지 않습니다. 예시로 이해해 봅시다.
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 – join_none 뒤에 오는 display 문이 process를 기다리지 않고 실행된 것을 확인할 수 있습니다.
Disable fork
Disable fork 문을 사용하면 진행 중인 process를 종료할 수 있습니다. 우선 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가장 먼저 끝나는 process인 Thread1이 마친 뒤 join_any 뒤에 오는 display가 실행됩니다.
그러면 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
endmoduleDisable fork로 인해 표시된 process는 실행되지 않고 fork 문이 종료됩니다.
Wait fork
Wait fork는 fork – join 문에서 모든 process가 끝날 때까지 기다립니다. 예시를 확인해 볼까요?
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
endmodulefork 문 안에 있는 process 중 가장 먼저 끝나는 Thread1 뒤에 join_any done이 display 됩니다. 그리고 wait fork로 인해 모든 process가 종료된 후 fork done이 display 됩니다.
그렇다면 wait fork는 모든 process를 다 끝날 때까지 기다릴까요?? 아래 예시를 보면 이해할 수 있습니다.
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원래 코드에서 Second fork를 추가하였습니다. 그래도 First fork가 더 늦게 끝나는데요, wait fork로 인해 First fork의 모든 process를 기다립니다.
이렇게 System Verilog에서 사용하는 process인 fork 문의 종류와 예시를 살펴봤습니다.
System Verilog Communication
검증을 진행할 때 testbench에 있는 component끼리 data를 주고받거나 output value를 check 해야 할 경우가 있습니다. System Verilog에서는 여러 mechanism을 통해 Thread나 component 간의 control flow를 제어할 수 있습니다.
- Events: Testbench내에 각각의 event handle을 이용하여 Thread 간 동기화
- Semaphores: Semaphore를 이용하여 Thread 간 필요한 공유 자원을 교대로 접근할 수 있게 함
- Mailbox: Thread와 components 사이에 mailbox를 이용해 데이터의 교환을 할 수 있게 함
Semaphore
Semaphore는 System Verilog에 내장된 class로, shared resources에 access를 제어하는 데 사용됩니다. Semaphore bucket에 key의 개수가 정해져 있어서 key를 가지고 있는 process만 resource에 접근할 수 있고, key가 없으면 다른 process가 key를 반환할 때까지 기다립니다.
System Verilog에서 지원하는 semaphore method는 다음과 같습니다.
| Name | Description |
|---|---|
| function new(); | 초기에 Semaphore bucket에 할당한 키의 개수를 지정함. |
| function void put(); | 반환될 semaphore의 키 개수를 지정함. |
| task get(); | Semaphore에서 얻을 키 개수를 지정함. Semaphore key를 받을 수 있는 상황일 시, 계속 대기. |
| function int try_get(); | Semaphore에서 필요한 키를 얻기 위해 키 개수를 지정함. 키가 없어도 대기하지 않음 |
예시를 확인해 볼까요?
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