意図しないラッチを防ぐ:代入漏れと感度リスト不足の違い

組合せ回路を書いたつもりでも、値が更新されない場合があります。「値を保持する回路を記述した」のか、「シミュレーションで処理が起動しなかった」のかを分けて調べましょう。

1. ここで学ぶ内容

目標は、組合せ回路の代入漏れと、感度リストの不足を区別できることです。前者はラッチ推論につながる記述、後者はシミュレーションと合成後回路の動作が食い違う原因になります。最適化後のセル構成は実際の合成レポートで確認します。

2. 組合せ回路の確認事項

  • 出力ごとに、すべての入力条件で値が決まるか確認します。
  • 必要な入力変化で処理が起動するよう、通常は always @* を使います。
  • else や default の有無だけで判断せず、各経路の代入を確認します。

ブロックの先頭で出力に既定値を代入する方法なら、その後の分岐にelseがなくても値は決まります。一方、always @(posedge clk) のようなクロック付きの記述で代入しない経路は、フリップフロップの値保持を表すことがあります。このページの組合せ回路のルールと混同しないでください。

3. 条件によって代入されない場合

次の held はenが1の間だけdを取り込み、enが0の間は前の値を保持します。組合せ回路を意図しているなら、en=0の場合にも値を与える必要があります。比較用の complete は先頭で0を代入しています。

下のファイルには、このラッチ記述と、後で比較する感度リスト不足の記述を収録しています。

`timescale 1ns/1ps
`default_nettype none
// Educational comparison: held intentionally retains its value while en=0.
module latch_examples(input wire en, input wire d,
                      output reg held, output reg complete);
    always @* begin
        if (en) held = d;
    end
    always @* begin
        complete = 1'b0;
        if (en) complete = d;
    end
endmodule

// Educational bad example: d is intentionally missing from the sensitivity list.
module sensitivity_examples(input wire en, input wire d,
                            output reg missing, output reg complete);
    always @(en) missing = en & d;
    always @* complete = en & d;
endmodule
`default_nettype wire

ラッチは記憶素子の一種です。ラッチそのものが必ず誤りなのではなく、組合せ回路のつもりで意図せず記述してしまうことが問題です。意図した回路と合成結果を照合してください。

4. 分岐がそろっていても、出力の代入が足りない場合

// 問題例:sel=0のとき、out_bへ代入しない
always @* begin
    if (sel) begin
        out_a = 1'b0;
        out_b = data;
    end else begin
        out_a = 1'b1;
    end
end

elseがあっても、out_bの値はsel=0の経路では決まりません。出力が複数ある場合は、出力ごとに確認します。

// 組合せ回路にする例:両方の出力へ既定値を代入
always @* begin
    out_a = 1'b1;
    out_b = 1'b0;
    if (sel) begin
        out_a = 1'b0;
        out_b = data;
    end
end

先頭の代入は電源投入時の初期化ではありません。ブロックが実行されるたびに行う通常の代入です。ここに掲載した2つの断片は説明用で、単体のモジュールではありません。

5. 感度リストに入力が足りない場合

上の sensitivity_examples では、always @(en) の右辺にdも使っています。enを変えずにdだけを変えても、このブロックは起動しません。一方、always @* は右辺で使う信号の変化を取り込むため、出力を更新します。

これは代入漏れとは別の問題です。右辺は常にenとdのANDであり、感度リストの不足だけを理由にラッチが推論されると説明するのは適切ではありません。組合せ論理として扱う合成ツールとの間で、シミュレーション結果が食い違う可能性があります。

対策は always @* への修正と、片方の入力だけを変えるテストの追加です。SystemVerilogの always_comb にも関連する機能がありますが、追加の規則があるため、単なる表記の置換として扱わないでください。

6. 2種類の問題を実験で比べる

比較コードと自己判定テストベンチをダウンロード(ZIP)

ZIPを展開し、2つの.vファイルがあるフォルダで次を実行します。準備方法は最初の実験を参照してください。

iverilog -g2012 -Wall -s tb_latch_examples -o sim.vvp latch_examples.v tb_latch_examples.v
vvp sim.vvp

Windows / Icarus Verilog 13.0で、2026年10月25日に次のシミュレーション出力を確認しました(VCD作成・終了通知を省略し、主要な出力を抜粋)。

d-only change: held=1 fixed=1 missing=0 sensitivity_fixed=1
en=0: held=1 fixed=0
PASS: latch retention and sensitivity-list mismatch

1行目はdだけを変えたときの差、2行目はen=0に戻したときの保持動作の差です。この実験で確認したのはシミュレーションとVCD生成です。合成後のセルや警告メッセージを実測した結果ではありません。

実験に使うテストベンチを見る
`timescale 1ns/1ps
`default_nettype none
module tb_latch_examples;
    reg en, d;
    wire held, fixed_latch, missing, fixed_sensitivity;
    latch_examples a(.en(en), .d(d), .held(held), .complete(fixed_latch));
    sensitivity_examples b(.en(en), .d(d), .missing(missing),
                           .complete(fixed_sensitivity));
    initial begin
        $dumpfile("latch_examples.vcd");
        $dumpvars(0, tb_latch_examples);
        en = 0; d = 0; #1;
        if (fixed_latch !== 0 || missing !== 0 || fixed_sensitivity !== 0)
            $fatal(1, "FAIL: initial combinational outputs");
        en = 1; #1;
        if (held !== 0) $fatal(1, "FAIL: latch open");
        d = 1; #1;
        if (held !== 1 || fixed_latch !== 1 || missing !== 0 || fixed_sensitivity !== 1)
            $fatal(1, "FAIL: d-only change");
        $display("d-only change: held=%b fixed=%b missing=%b sensitivity_fixed=%b",
                 held, fixed_latch, missing, fixed_sensitivity);
        en = 0; #1;
        if (held !== 1 || fixed_latch !== 0 || missing !== 0 || fixed_sensitivity !== 0)
            $fatal(1, "FAIL: latch holds while complete assignment changes");
        $display("en=0: held=%b fixed=%b", held, fixed_latch);
        $display("PASS: latch retention and sensitivity-list mismatch");
        $finish;
    end
endmodule
`default_nettype wire

確認問題

  1. ブロックの先頭で全出力へ値を代入していれば、elseがなくても組合せ回路を記述できますか。
  2. always @(en) のままでdを変えたとき、missingが更新されない理由は何ですか。
  3. この実験のPASSだけで、FPGAでラッチが使われないと証明できますか。
解答
  1. はい。この例のように全経路で値が決まるなら、else自体は必須ではありません。
  2. dが感度リストになく、dだけの変化では処理が起動しないためです。
  3. いいえ。シミュレーションの確認と合成結果の確認は別です。

参考資料と次に読むページ