ソフトウェアの時間 vs ハードウェアの時間
プログラムでは、時間は1命令ずつ進みます。CPUが1行目を終え、次に2行目を実行します。2つのことを同時に行いたければ、thread、async、もう1台のマシンに頼ります。
ハードウェアでは、 すべてが同時に起こります 。回路は順番待ちをしません。adderは常に加算し、multiplexerは常に選択し、flip-flopは常にクロックを見ています。プログラムカウンタはありません。「現在の行」もありません。
Verilogはそうした世界をテキストで記述しなければなりません。その表現の仕方が、この章のすべての混乱の元になっています。
2層の並行性
Verilogのmoduleを読むときには、同時に2つの異なるものを見ています:
- 回路の静的な記述。 wire、register、gateのinstance、サブmoduleのinstance、連続代入。これらはすべて同時に存在します。ファイル内の順序は意味を持ちません。
- 手続きブロック(
initialとalways)。シミュレータがステップ実行する小さなプログラムのように見えます。これらの中では、文は ある種の 順序で実行されます。しかし複数のalwaysブロックが同時にアクティブになることがあり、それぞれが独自の小さなシミュレーション時間のスレッドで動きます。
module example(input wire a, input wire b, output wire y, output wire z);
assign y = a & b; // 常に存在
assign z = a | b; // こちらも並列に常に存在
always @(posedge a) begin
// `a`の立ち上がりエッジで起動する、もう1つの
// 「常時オン」のリアクタ
end
endmodule
2つのassign行は順列ではありません。合成ツールが横並びにレイアウトできる2つの組み合わせ論理を記述しています。alwaysブロックは並列に動く3つ目のものです。
「Signal」とは何を意味するか
ソフトウェアでは、変数は変更するまで値を保持します。Verilogでは signal は連続して値を保持し、どの瞬間も駆動しているものに依存します。
wireは外側から駆動されます(assign、サブmoduleの出力ポート、inout接続)。regは手続きブロックの内側から駆動されます。どちらも常に値を持っています。Cが意味する「未初期化」はありません。signalは確定した値、特殊な未知のx、ハイインピーダンスのzのいずれかで駆動されています。後者2つはXとZの値で扱います。
クロックがすべてを変える
クロックを持ち込むと、時間が意味を持ち始めます。flip-flopは、入力値をクロックの立ち上がり(または立ち下がり)エッジで捕捉し、次のエッジまで保持する小さなハードウェアです。カウンタ、状態機械、パイプライン、つまり 記憶 を持つあらゆるものを構築できるのは、クロックのおかげです。
q = dではなくq <= dであることに注目してください。これは 非blocking代入 で、クロック同期論理の主役です。「次のクロックエッジで、qを今のdの値にスケジュールする」という意味です。ルールはBlocking vs Non-blockingで掘り下げますが、今はこの代入がソフトウェア文のふりをしていないことを認識してください。
RTL:Register Transferの考え方
合成可能なVerilogのほとんどは Register Transfer Level(RTL)と呼ばれるスタイルで書かれます。考え方はシンプルです:
- 回路に必要なstate(register)を決める。
- 各registerについて、何が reset するか、組み合わせ論理が 次の値 をどう計算するか、の2つを記述する。
- 組み合わせ論理の出力をregisterの入力に配線すれば、動作する回路ができる。
always @(posedge clk) begin
if (reset) state <= IDLE;
else state <= next_state;
end
always @(*) begin
case (state)
IDLE: next_state = start ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = IDLE;
endcase
end
これが2状態の状態機械です。最初のalwaysブロックはクロック同期で、flip-flopになります。2つ目は純粋な組み合わせ論理で、ただの式です。書くことになる状態機械、カウンタ、パイプラインのほとんどがこの形に従います。
詰まりやすい習慣
ソフトウェアから来た人なら、捨てるべき習慣の短いリストはこちらです:
- 「変数は代入したときに更新される」。 クロックエッジ上では違います。非blocking代入は、time stepの終わりに更新をスケジュールします。
- 「文は上から下へ実行される」。 手続きブロックの外では違います。クロック同期ブロックの中ではある意味そうですが、blocking/非blockingの違いで「順番」の意味が変わります。
- 「必要なときに割り当てればいい」。 ハードウェアは割り当てません。すべてのregisterとgateは合成時に存在しなければなりません。すべてのvectorのサイズは固定です。
- 「このループは速い、たった1演算だから」。 合成可能なVerilogの
forループは並列ハードウェアに 展開(unroll) されます。64回の反復ループは、64回実行されるCPU命令ではなく、本体の64コピーになります。
あなたが学んでいるのはプログラムの書き方ではありません。回路の記述の仕方です。上から下へ読む本能はまさに間違った本能で、再訓練に時間がかかります。
次に読むもの
次のドキュメントでは、ローカルツールチェーンの導入(任意、ブラウザエディタで十分)、そして最初のmoduleをゼロから書きます。何かが奇妙に見えるたびに、ハードウェアとソフトウェアの対比に戻ってきます。「奇妙な」部分のほとんどは同じ根本原因(これはソフトウェアではない)に由来するからです。
よくある質問
Verilogの観点でハードウェアとソフトウェアの違いは?
ソフトウェアはCPUが順番に実行する命令の列です。ハードウェア(Verilogが記述するもの)は、すべてが同時に信号を運ぶgateとwireのネットワークです。Verilogファイルはそのネットワークを記述します。シミュレータが並列挙動を模倣し、合成ツールがそれを実際のシリコンに変換します。
VerilogのコードはCのように上から下へ実行されますか?
いいえ。そう扱うことが初心者の最も一般的な間違いです。トップレベルの文(assign、moduleのinstance、always block)はすべて同時に「存在」します。手続き的なブロック(initialとalways)の中でだけ、順次実行に似たことが起きますが、そこでも非blocking代入がその錯覚を壊します。
Verilogにおける「並行(concurrent)」とは?
複数の文が、同時に動作する回路の各部分を記述するという意味です。同じmodule内の2つのassign文は「1行目、次に2行目」ではなく、両方が並列で動作するハードウェアの2つの部品で、入力に対して常に反応しています。
RTL設計とは?
RTLはRegister Transfer Levelの略です。回路をregister(flip-flop)と、その次の値を計算する組み合わせ論理として記述するVerilogの書き方です。合成可能なVerilogのほとんどがRTLです。その上のレベルがbehavioral、下がgate-levelです。