本記事は以下の3部構成の一部です。
verilogには複数のプロセスを同時に実行する機能としてfork joinがありますが、System Verilogでは拡張され、fork join_anyとfork join_noneが仲間に加わりました。どちらも便利な機能で検証で頻繁に使用されますので紹介したいと思います。
全て複数のプロセスを同時に実行するというforkの部分は同じなのですが、どのタイミングで次のステートメントの実行に移るかのjoinの部分の振る舞いが異なります。
fork join
最初にverilog時代からあったfork joinの復習です。fork joinの中のプロセス(begin endで囲ったブロック)が並列に実行されます。全てのプロセスの実行が終了するとjoinの次のステートメントに実行が移ります。
以下がサンプルプログラムと実行結果です。forkしたプロセスが全て終了した20nsでjoinの次のステートメントが実行されています。
`timescale 1ns/1ns
program top();
initial begin
$timeformat(-9, 0, " ns", 6);
fork
begin
#10ns $display("Process A Finished at %t", $realtime());
end
begin
#20ns $display("Process B Finished at %t", $realtime());
end
join
$display("Current time is %t", $realtime());
end
endprogram
# KERNEL: Process A Finished at 10 ns
# KERNEL: Process B Finished at 20 ns
# KERNEL: Current time is 20 ns
# RUNTIME: Info: RUNTIME_0068 $finish called.
用途としてはLRMにもあるように、順番の不定な複数のイベントの終了を待つのに使用できます。以下の例ではevent_aとevent_b両方の発生後に次に進みます (本例のようにプロセスの単位が1ステートメントの場合はbegin endで囲う必要はありません)。
fork
@event_a;
@event_b;
join
$display("Both evant_a and event_b have occurred.");
fork join_any
join_anyでは同時実行されたプロセスのうちどれか一つでも終了したら次のステートメントの実行に移ります。終了していないプロセスはそのまま実行が継続されます。
以下がサンプルプログラムと実行結果です。Process Aの終了時間である10nsでjoin_anyの次のステートメントが実行されていることがわかります。
`timescale 1ns/1ns
program top();
initial begin
$timeformat(-9, 0, " ns", 6);
fork
begin
#10ns $display("Process A Finished at %t", $realtime());
end
begin
#20ns $display("Process B Finished at %t", $realtime());
end
join_any
$display("Current time is %t", $realtime());
#15ns;
$display("Current time is %t", $realtime());
end
endprogram
# KERNEL: Process A Finished at 10 ns
# KERNEL: Current time is 10 ns
# KERNEL: Process B Finished at 20 ns
# KERNEL: Current time is 25 ns
# RUNTIME: Info: RUNTIME_0068 $finish called.
用途としてはステータスポーリングのタイムアウトが挙げられます。下記例では1 usをタイムアウトとし、REGから非0の値が読めるまでデータを読み出し続けます。例えREGS.REG.read()がハングして値を返さなくても、確実にタイムアウトが可能です。
bit is_timeout = 1'b0;
fork
begin
int v;
// Keep reading until non-zero is read from the register
do (REGS.REG.read(v))
;
while (!v);
end
#1us is_timeout = 1'b1;
join_any
disable fork;
if (is_timeout) begin
`uvm_error("Timeout during reading REG");
end
fork join_none
join_noneではプロセスをforkしたら即座に次のステートメントの実行に移ります。
以下がサンプルプログラムと実行結果です。fork直後にjoin_noneの次のステートメントが実行されていることがわかります。
`timescale 1ns/1ns
program top();
initial begin
$timeformat(-9, 0, " ns", 6);
fork
begin
#10ns $display("Process A Finished at %t", $realtime());
end
begin
#20ns $display("Process B Finished at %t", $realtime());
end
join_none
$display("Current time is %t", $realtime());
#35ns;
$display("Current time is %t", $realtime());
end
endprogram
# KERNEL: Current time is 0 ns
# KERNEL: Process A Finished at 10 ns
# KERNEL: Process B Finished at 20 ns
# KERNEL: Current time is 35 ns
# RUNTIME: Info: RUNTIME_0068 $finish called.
本機能は検証環境内でDUTと信号レベルでやりとりをする部分に使用することが多いです。
例えばDUTの信号のモニタなどに使用できます。下記のようにfork join_noneで独立プロセスとしてモニタを実行し、後はDUTからのイベントドリブンでステートメントを実行します。
fork
forever begin
@vif.signal_a;
$display("signal_a value changed %x",@vif.signal_a);
end
join_none
参考文献
"9.3.2 Parallel blocks". 1800-2017 - IEEE Standard for SystemVerilog--Unified Hardware Design, Specification, and Verification Language. pp.210-212.