🙄

Verilogは本来RTL記述言語ではない

に公開

Verilog HDLは、今日ではRTL(Register Transfer Level)記述言語として広く認識されています。しかし、その本来の設計思想を辿ると、VerilogはRTL専用の言語ではなく、むしろシミュレーションを目的としたビヘイビアモデル記述言語であり、RTL設計に使用するためにはいろいろと注意が必要です。

Verilogの起源:シミュレーション言語としての設計

Verilog HDLは、電子システムの設計・検証・テストを支援するために開発された形式的な記述言語です。特にその文法は、シミュレーション用途を中心に定義されており、動作モデルの記述に重点が置かれています。つまり、Verilogの本質は「動作を模擬するための言語」であり、もともとは合成を前提とした構文ではありません。

RTL記述への拡張:抽象レベルの混在

とはいえ、Verilogには論理ゲートやユーザー定義プリミティブ、スイッチ、ワイヤードロジックなど、構造的な記述を可能にする豊富なプリミティブが組み込まれています。さらに、ネット型と変数型という2種類のデータ型セマンティクスにより、構造的(連続代入)とビヘイビア的(プロシージャル代入)な記述が混在可能となっています。

このような設計思想は、ゲートレベルからレジスタ転送レベルまで、複数の抽象度でロジックを記述できる柔軟性を持っています。結果として、Verilogの一部構文がRTL記述に適しているため、合成ツールによってRTL設計に活用されるようになりました。

RTL実装に使う記述はBehavior記述の一部

しかしながら、verilogのsimulatorはbehavior記述としてコードを解釈して動作しているだけで、これを回路として認識して動作しているわけではありません。

FFの非同期リセットの不思議

例えば、以下のような記述は一般的に合成時にflipflopとして解釈されます。
しかしこれは記述通り「RST_Xのnegedgeでdata_ffを0に更新する、RST_Xが1の場合はCLKのrise edgeでdata_ffをdata_inに更新する」という記述にすぎず、simulatorはこれがFFであることを認識しているわけではありません。

reg data_ff;

always @(posedge CLK or negedge RST_X)
begin
	if(~RST_X) begin
		data_ff	<= 1'b0;
	end else begin
		data_ff	<= data_in;
	end
end

ここで、「RST_Xが0である間はdata_ffが0になる」ではなく「RST_Xのnegedgeでdata_ffを0にする」であることがちょっと不思議に感じられると思います。なぜこれがFFと同じと認識できるのでしょうか。

以下のように時間0でRST_Xが0から開始した場合にdata_ffは不定(x)になるように感じられないでしょうか。

reg RST_X = 1'b0;
initial begin
    #1;
    RST_X<=1'b1
end

実際にはこれは必ず時間0でdata_ffは0になります。
これはRST_Xの初期値はあくまでxであり、時間0での処理の中でx->0への遷移が必ず発生する。そして、negedgeイベントは下記で発生すると規定されているからです。

  • 1 からx、z、または0への遷移
  • x またはzから0への遷移

すなわちdata_ffは時間0の時点でxから0への遷移が必ず発生します。つまり非同期リセットを持ったFFと同じ動作になるといえます。
(ただし時間0の時点で最初から0であるわけではないです。このあたりがverilog simulationで時間0時点の動作に不定要素があるといわれる原因になっています。)

FFの信号伝搬の不思議

では下記のようなFFを二段重ねた回路を考えてみます。CLK posedgeでdata_ff0が取り込んだデータを次のCLK posedgeでdata_ff1が取り込む回路と解釈できると思います。でもこれは何故そういう挙動になるのでしょうか。

reg data_ff0;
always @(posedge CLK or negedge RST_X)
begin
	if(~RST_X) begin
		data_ff0	<= 1'b0;
	end else begin
		data_ff0 <= data_in;
	end
end

reg data_ff1;
always @(posedge CLK or negedge RST_X)
begin
	if(~RST_X) begin
		data_ff1	<= 1'b0;
	end else begin
		data_ff1 <= data_ff0;
	end
end

回路記述である、という見方をやめてbehavior記述としてみたとき、1つのCLK posedgeでdata_inがdata_ff1に出力されてもおかしくないように感じられないでしょうか。でもこれは文法上必ず2CLK posedgeでデータ伝搬する記述になっています。

このあたりはverilogの仕様上での各データ変化の処理順規約に規定されています。verilogではいろんな記述が並列に動作しますが、それらは処理イベントごとに実行キューに保存され、一定のルールに従って順番に実行されます。このあたりの実行キューの処理仕様を理解するとverilogの挙動の理解が一気に深まります。

上記のFF動作を模擬する記述に使われているnon-blocking代入(<=)は右辺データの取り込みと左辺データへの反映を別の処理タイミングで行うための記述です。verilogの文法上の規定により、同時間での処理ではすべての右辺データの取り込みが終了してから左辺データへの反映を行います。これによって同時間に発生したイベントでもdata_inに入れたデータが1つのCLK posedgeでdata_ff1に抜けることはないようになっています。

このような原理で上記の記述はFFと同じ動作とみなせるわけです。simulatorはあくまでbehavior model記述の一部としてこれらの演算を行っているだけで別にFFとして特別扱いしているわけではありません。合成ツールがこれはFFと同等記述とみなせるので、合成時にFF回路として実装していることになります。

問題になるケース

ちょっとした記述の問題でBahaviorとしての動作が垣間見えることがあります。下記のようにdata_ffの出力にアクセスタイムを設定したとします。このような記述は同時遷移に関する問題を回避するために多くのデザインルールで採用されていることが多く、合成時には#t_accだけ無視されて問題なく合成されます。
ただし、以下の記述にはRST_X側の記述にdelayがはいっていません。このような記述はsimulationした際にFF実装回路と異なる挙動をすることがあります。

`timescale 1ns / 1ps

// CLK cycleは100nsあるとします。

localparam t_acc = 10; // FFのアクセスタイムが10ns

reg data_ff;
always @(posedge CLK or negedge RST_X)
begin
	if(~RST_X) begin
		data_ff	<= 1'b0;
	end else begin
		data_ff <= #t_acc data_in;
	end
end

CLK posedgeの1ns後にRST_Xが0になり、さらに1ns後にRST_Xが1になったとします。この場合にdata_ffはその後、次のCLK posegeが来るまでどうなるでしょうか?FFとしての期待動作は「0になる」だと思いますが、verilogの文法から解釈すると、

  • まずCLK posedgeでnon-blocking代入文がdata_inを取り込む
  • RST_Xが0になったタイミングでdata_ffは0になる
  • RST_Xが1になってもdata_ffは0のまま。
  • 前のCLK posedgeから10ns経った時点で以前に取り込んだdata_inがdata_ffに反映される。

という挙動になり、非同期リセットを入れたにも関わらずリセット前の値が一時出力される挙動になります。そしてsimulatorはこの通りの挙動になり、simulationと実回路動作の乖離の原因となります。

通常、これを防ぐために一つのregに対するnon-blocking代入の遅延設定あすべて同じ値にそろえておく必要があります。

`timescale 1ns / 1ps

localparam delay = 10;

reg data_ff;
always @(posedge CLK or negedge RST_X)
begin
	if(~RST_X) begin
		data_ff	<= #delay 1'b0;
	end else begin
		data_ff <= #delay data_in;
	end
end

どの記述がどう合成されるかは合成ツール次第

ここで重要なのは、「Verilogのどの記述が回路合成に使えるか」は、言語仕様そのものでは定義されていないという点です。Verilog HDLの標準は、言語の構文とセマンティクスを定義するものであり、合成可能なサブセットの範囲については明示していません。

このため、実際にどの構文が合成可能かは、使用するEDAツール(合成ツール)の設計と実装に依存します。ツールベンダーは、標準に準拠しつつも、独自の合成可能範囲を定義し、設計目標(面積、速度、消費電力など)に応じた最適化を行います。

合成可能な記述を守るために

しかし、合成ツールが対応している記述をツールごとに詳細確認するのは大変なので、一般的に対応していることが確実なverilog記述だけを使ってRTLを設計するのが一般的な使われ方だと思います。これらの一般的なガイドラインをまとめたものがSTARCのデザインガイドであったり、
https://www.amazon.co.jp/Verilog-SystemVerilog-Gotchas-Common-Coding/dp/0387717145
こういう書籍にまとめられています。よってRTLを設計する際にはこれらのガイドラインから外れないように設計するのが重要といえます。

まとめ:Verilogの本質と現代的な使われ方

Verilog HDLは、元々シミュレーションを目的としたビヘイビア記述言語であり、RTL記述はその応用の一部に過ぎません。現在では、RTL設計や回路合成に広く使われていますが、その合成可能性は言語仕様ではなく、ツールベンダーの裁量に委ねられています。

Discussion