SystemVerilogにおけるレースコンディション(Design and Verification LANDSCAPE 2024-Vol1)

はじめに
IEEE 1800 - SystemVerilogではさまざまなコーディングが可能だが、記述において注意を要する項目がある。それはレースコンディション、またはレーシングと呼ばれる。基本的には変数が複数のスレッドによって同時に書き込みや読み出しがされることで生じる非決定性のことを指す。
設計エンジニアにとっては、合成可能なRTLの要件がレースコンディションの発生可能性を減らすため、それほど問題とはならないかも知れない。しかし検証エンジニアにとってはレースコンディションをテストベンチに記述してしまう可能性は非常に高く、シミュレーションにおいて何が起きているかを解析、理解、修正するのに数時間から数日間を要することもある。
ここでは、レースコンディションを引き起こすさまざまなケースを簡単なコードによって紹介するとともに、避けるべき記述についての注意を喚起したい。
アサインメントの基本
レースコンディションの解説に入る前に、アサインメントの基本について確認したい。アサインメントにはコンティニュアスとプロシージャルの二種類がある。
コンティニュアスアサインメントはその名のとおり、変数やネットに対して継続的にアサインする記述であり、ちょうどゲートの出力がネットをドライブする様子が表現される。
プロシージャルアサインメントはalways、initial、task、functionなどのプロシージャで使われ、変数に対してトリガーをかけるようなアサインを行う。つまりアサインメントの右辺が評価されると一度だけ左辺を更新し、左辺はその値を保持する。さらにプロシージャルアサインメントは、ブロッキングとノンブロッキングに分かれる。
ブロッキングアサインメントは1ステップのプロセスであり、右辺を評価し遅滞なく左辺を更新する。
これに対してノンブロッキングアサインメントは2ステップのプロセスであり、タイムステップの開始時に右辺を評価し、タイムステップの終了時に左辺を更新する。
クロックのたびにノンブロッキングアサインメントが実行されるような記述、例えば図1.に示すようなコードでは、複数のノンブロッキングステートメントがハードウェアとして同時に実行されるため、論理合成によりシフトレジスタが生成される。
ノンブロッキングアサインメントの4つの行が同時に実行されるという意味は、clkのタイムステップの開始時にnonblocking-assignment-1においてdが評価されるが、まだその時点ではq[0]は更新されない。しかし同時にnonblocking-assignment-2も実行されてq[0]が評価されるが、この時点ではq[1]は更新されない。
このようにすべてのノンブロッキングアサインメントの右辺は評価されるが、左辺が更新されるのはclkのタイムステップを抜ける直前となる。この4つの行がすべて同時に評価され、そして同時に更新されることから、論理合成ツールでは図2.のようなシフトレジスタが生成される。
この4つのノンブロッキングアサインメント "<=" の代わりにブロッキングアサインメント "=" を使用すると、clkの立ち上がりのタイムステップにおいて、すべての右辺が評価され、かつ更新されるため、q[0]〜q[3]の値はすべてdの値によって更新される。論理合成ツールでは図3.のような回路が生成される。
それではここから、さまざまなレースコンディションが作られる記述例を紹介する。
レースコンディションのパターン
Race1: ブロッキング/ノンブロッキング アサインメントの混在
図4.はSystemVerilogで最も一般的なレースコンディションである。複数のスレッドやプロセスが並列に実行され、それらがすべて同じイベント(クロックエッジ)に同期している場合、ブロッキング代入から古い値を読み取るか、更新された値を読み取るかの間で競合が発生する。
そのため、あるプロセスが書き込みを行い、別のプロセスが同じクロックに同期して同じ変数を読み取る場合は、常にノンブロッキング代入を使用すれば、一貫して前の値を読むことが保証される。
Race2: 初期化における不定値
これは基本的にRace1と同じである。このコーディングでは、初期化されていないにも関わらず、0であるかのごとく記述されている。このソースからはコーディング意図としては ping = !pong; となるが、結果としてはすべてが不定値のままになってしまう。
Race3: プロシージャル/コンティニュアスのアサインメント
コンティニュアスアサインメントは独立したプロセスとして振る舞い、どのプロセス間でも決定論的な実行順序はない。コンティニュアスアサインメントでは右辺のオペランドの値が変更されるたびに、左辺への代入が行われる。
しかし、他のプロセスがこれらのオペランドに変更を加えている場合、beach_ballへのプロシージャルアサインメントの前でも後でも、左辺がいつ更新されるかが保証されない。
"+=" や "++" のような累加代入演算子はすべて、ブロッキングアサインメントのためのショートカットに過ぎない。ブロッキングではない演算を意図する場合、同等のショートカットは存在しないため、以下のように展開する必要がある。
always @(posedge clk) stripes <= stripes + 1;
Race4: 複数スレッドにおけるアサインメント
まずコード中に#0や#1が散りばめられているが、あまり感心できるコーディングではない。SystemVerilogのスケジューリング・セマンティクスを十分に理解していない人に見られる。
この場合、レースはクロックエッジから1タイムユニット(#1)後である。cycleへの代入は、alwaysブロックでの読み出しと同時に行われる。
Race5: エッジセンシティブのイベント
これもRace1の一種である。あるプロセスが書き込みを行い、別のプロセスが同じ変数を読み込んでいる。しかしここでは、読み込みは式(goal==2)が変化するのを待つイベント制御である。
そのため、initialブロックとalwaysブロックの間における評価順序によって、最初のクロック・サイクルでfalse(1'b0)からtrue(1'b1)への立ち上がりをキャッチするか、次のサイクルでtrue(1'b1)からfalse(1'b0)への立ち下がりをキャッチするかのどちらかになる。
Race1で説明したように、同期プロセス間では常にノンブロッキングの代入を使用するべきである。また、エッジセンシティブのイベントで式を使用することは非常に稀であるため、以下のような記述にすべきである。
(posedge clk iff (goal==2));
Race6: ネームドイベント
ネームドイベントは同期オブジェクトであり、上記Race5と同じような問題に直面する可能性がある。つまりイベントをトリガーする前に、イベントを待たなければならない。
もし最初のforeverブロックの実行開始前に2番目のforeverブロックがbatsmanをトリガーすると、最初のforeverブロックが既にトリガーされたイベントを待つことになるデッドロックに陥ってしまう。
これを回避するにはブロッキングしないトリガーである->>演算子をbatsmanとwicketに使うことで、この特定のレースコンディションを抑制できる。ネームドイベントを使用する際には、SystemVerilogのイベント・スケジューリング・アルゴリズムを理解していることが求められる。
Race7: NBAファンクション
このレースコンディションは多くの人が作り出してしまう。それはファンクションの戻り値や、ファンクションやタスクの出力としての引数にノンブロッキング代入を使うと、NBA(ノンブロッキングアサインメント)が値を更新する前に、現在の値がコピーされてしまう。このような場合にはブロッキングアサインメントを使わなければならない。
Race8: プロシージャルなforce/release
これはRace3と同じ種類のものである。すべてのコンティニュアスアサインメントは、それぞれが独立して並行に存在するプロセスである。つまり同じ時間領域内での実行順序に期待するべきではない。
まとめ
本稿ではIEEE 1800 – SystemVerilogの記述において、誰もが陥りやすいレースコンディションを作ってしまうコーディング例を取り上げて解説した。ここにあるコーディング例で学べることを実プロジェクトでも活かすことで、多くのレースコンディションの問題を回避することができる。
とは言え、コーディングのスキルや経験値はエンジニアによっても異なるため、均質化することは難しい。そこで設計コードであれば、一般的に取られている手法はLINTツールの導入である。
LINTツールでは記述されたコードに対して、例えばブロッキングアサインメントとノンブロッキングアサインメントが混在してはいけないというルールを適用すれば、そのルールに反するコードに対してワーニングメッセージを出力してくれる。
レースコンディションの問題は一度発生すると、そのデバッグが非常に困難で分かりにくいため、なるべくLINTツールで露払いをしておくことが望ましい。
参考文献
[1] Dave Rich, "SystemVerilog Race Condition Challenge Responses" – Siemens EDA Thought Leaders Blog
[2] Clifford E. Cummings, "SystemVerilog Event Regions, Race Avoidance & Guidelines"
このブログのシリーズ一覧は下記になります。
是非あわせてお読みください。