assignは永続的な真理を記述する
wire宣言はsignalを作ります。assignは 何がそれを駆動するか を記述します。関係は連続的で、右辺が何であれ、左辺はそれに等しく、シミュレートされる時間の全時点でそうです:
wire y;
assign y = a & b;
この2行が暗示する2つのこと:
yは回路内のwireとして存在する。yは常にaとbのビット単位ANDである。どちらかの入力を変えれば、yは追従する。
クロックも更新トリガとなるイベントもありません。シミュレータはaまたはbの変化を検知し、yを汚れたものとマークし、式を再評価します。ハードウェアでは、これがAND gateにマップされます。組み合わせ、stateless、瞬時です。
暗黙的な形式
宣言と代入を1行にまとめられます:
wire y = a & b;
これは上の2行と同じです。このスコープ外では誰も気にしないインラインwireに便利です。多くのスタイルガイドは、宣言を式のすぐ隣に置くため、暗黙的な形式を好みます。
assignが駆動できるもの
assignのターゲットは ネット型 でなければなりません。プレーンVerilogではwire(または稀なtri、wand、wor)です。regはだめです。誤ってターゲットをregとして宣言した場合:
reg y;
assign y = a & b; // エラー: assignでregを駆動できない
コンパイラが教えてくれます。ターゲットをwireに変えるか、regが正当なターゲットであるalways @(*)ブロックにロジックを移動します。
右辺は値に評価される何でも構いません。リテラル、signal、parameter、演算子の式、関数呼び出し。複数の入力幅を混在させることもでき、標準の幅拡張ルールが適用されます。
assignとalwaysの使い分け
両方とも組み合わせ論理を生成できます。選択は主にコードの読みやすさです:
assignは関係が単一の式の場合に最適。 加算器、?:で構築する単純なmux、マスク、パリティビット、1行で書けるあらゆるもの。always @(*)は手続き的な文が必要な場合に最適。 多分岐のcase文、ネストしたif/else if、名前付き中間regの恩恵を受けるもの。Always Blockで扱います。
同じ4-to-1 muxを両方の書き方で:
両moduleはほぼ同じmultiplexerに合成されます。assign版は1行、always版は6行。4ケースなら近いですが、16ケースになるとcaseブロックの方が明らかに読みやすいです。
よくあるパターン
単純な組み合わせ論理
assign sum = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);
1つの式、1つのwire。assignの主役です。
2-to-1 mux
assign out = sel ? a : b;
単一の条件式。合成ツールは単一の2-to-1 muxに変換します。「aとbから選ぶ」の最もきれいな書き方です。
ビット詰め込み
assign status = {error, overflow, ready, busy, 4'b0};
assignの右辺での連結は、フラグをステータスバイトに詰め込む方法です。結果は連続的に計算され駆動されます。
Tri-state出力
assign data_pin = output_enable ? data_out : 1'bz;
output_enableが1のとき、ピンを駆動します。0のとき、ハイインピーダンスに解放します。複数のドライバがwireを共有する可能性のあるチップピンでの定番パターンです。
並行性:複数のassignは順序ではない
何度も関係するリマインダーです。同じmodule内の複数のassign文は すべて並列に動作します 。順序ではありません:
assign y = a & b; // 常に存在
assign z = a | b; // こちらも独立に常に存在
ファイル内の順序は無関係です。両方の式が同時に真です。合成ツールはAND gateをORの左に置くか右に置くかもしれませんが、関係ありません。両方のgateは連続的に発火します。
順序的な見た目の挙動が欲しいなら、alwaysブロック(おそらくクロック付き)に手が伸びます。それは別の章です。
複数ドライバ:バスのパターン
wireは複数のassignでターゲットにできますが、tri-stateバス以外ではほぼ常にこれを避けたいでしょう。wireを争う2つのドライバは未定義動作を生みます:
assign y = a;
assign y = b; // 悪い - 2つのドライバ、シミュレータは片方を選ぶかxにする
正当なパターン:各ドライバが非アクティブ時にzに解放し、任意の時点でアクティブなのは多くとも1つだけ。
assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;
任意の時点で、2つの三項演算子のうち多くとも1つだけが非z値を生成するので動きます。wireの実際の値は、解放していないドライバの値です。
内部ロジック(チップピンやチップ内共有バス以外のどこでも)では、1つのwireに1つのドライバ。多ドライバのバグはデバッグが厄介です。
assignにできないこと
assignが間違ったツールである例:
- ストレージ。
assignは組み合わせの関係を記述します。flip-flopを導入できません。値をクロックサイクル間で記憶する必要があれば、always @(posedge clk)ブロックの出番です。 - 多段階の手続きロジック。
assign内でif/elseやcaseを書けません。最も近いのはチェーン化した?:ですが、3分岐を超えると醜くなります。 - 手続きブロック内からregisterを駆動する。
regターゲットはassignではなく手続き的代入が必要です。
限界を知ることが、いつalwaysに切り替えるかを判断する方法です。
次に読むもの
これでVerilogの構造的な側面を完全に見ました。moduleの宣言、インスタンス化、組み合わせ論理をassignで配線する。次の章は手続きブロックに入ります。時間と順序が重要になり始めるinitialとalways構文です。
よくある質問
Verilogの連続代入とは?
assign target = expression;は永続的で連続的な関係を宣言します。targetは常にexpressionに等しい、ということです。式中のsignalが変わるたびに、シミュレータは右辺を再評価し、targetを更新します。クロックもイベントもなく、関係はあらゆる時点で真です。
Verilogでassignのターゲットにできるものは?
assignはwireを駆動できますが、regは駆動できません。ターゲットはネット型でなければなりません。代わりにalwaysブロック内で何かに代入したい場合は、それをregとして宣言してください。xがregなら、コンパイラはassign x = ...を拒否し、xがwireならalways内のx = ...を拒否します。
assignとalways blockのどちらを使うべき?
シンプルな組み合わせ論理(1つの式が入り、1つのsignalが出るだけで、if/elseが不要)にはassignを使います。ロジックが手続き的な文(case、if/else ifチェーン、forループ)を必要とする場合はalways @(*)を使います。両者とも組み合わせハードウェアを生成します。選択は可読性の問題です。
同じwireに複数のassignを行えますか?
各ドライバが非アクティブ時にwireをzに解放するtri-stateバスをモデル化している場合のみです。2つのassignが同時にwireを確定値に駆動しようとすると競合します。シミュレータは片方を選ぶかもしれないし、signalをxにするかもしれず、ツール次第です。通常の組み合わせ論理では、1つのwireに1つのドライバです。