「Latch inferred 地獄から脱出した3日間」もふねこの実験ログ

もふねこ

「Warning: Latch inferred for signal 'xxx'」──この警告を見た瞬間の絶望感、もふねこも知ってるよ🐾
このページは、実習で丸3日間ラッチ地獄にハマった学生と一緒に解決した実験ログ。「直したはずなのに消えない!」を3段階で追体験しながら、最終的にはラッチの正体まで理解できる構成にしたよ!

⚠️
この実験ログについて 以下の内容は、もふねこが大学の実習指導で実際に遭遇したケースを元に、学習用に再構成したものです。ラッチ生成の3つの原因を「Day 1 → Day 2 → Day 3」の時系列で追体験できるようにまとめています。

1. はじめに:Warning地獄の3日間

実習で、ある学生がデコーダ回路を書いていました。シミュレーションは完璧に通る。波形も綺麗。なのに、論理合成をかけるとWarningの嵐が出る──。

Warning: Latch inferred for signal 'Z'
Warning: Latch inferred for signal 'DATA2'

「Warningだから動くんですよね?」と学生は言いました。もふねこは首を横に振りました。

もふねこ

🐾 もふねこ:
「Latch inferred」のWarningは絶対に無視しちゃダメ。意図しないラッチは、意図しない値の保持で誤動作したり、タイミング解析をFFとは別扱いにしたり、実機で追いにくいバグを生み出す元凶なんだ。このWarningが消えるまで、一緒にデバッグしよう。

ここから、3日間の格闘が始まりました。

2. Day 1:「elseを書いたのに消えない」

🔴 症状

学生が書いたANDゲート相当の組合せ回路に「Latch inferred」のWarningが出る。

🔍 原因:if文のelseが抜けていた

// ❌ Day 1 のコード:else不足
always @(A, B) begin
    if (A == 1'b1 && B == 1'b1)
        Z = 1'b1;
    // A=0 や B=0 のとき Z はどうなる?→ 抜けている!
end
⚠️ なぜラッチが生まれるのか

A=1, B=0 のとき、Z の値が定義されていません。合成ツールは「値が定義されていないなら、前の値を保持(記憶)しなければならない」と解釈し、記憶素子であるラッチを作ってしまいます。

✅ Day 1 の修正

// ⭕ 修正後:elseで残りの条件をすべてカバー
always @(A, B) begin
    if (A == 1'b1 && B == 1'b1)
        Z = 1'b1;
    else
        Z = 1'b0;   // ← これでラッチは消える!
end
もふねこ

🐾 もふねこ:
「よし、これでWarning消えた!」と学生は喜んだ。でもまだ終わりじゃなかったんだ……。

📌 Day 1 の教訓
  • if 文は、すべての経路で出力に値が決まるようにする(単独の else を書くか、分岐の前に既定値を代入する)
  • case 文も同じ。default を書くか、case の前に既定値を代入しておく
  • 条件分岐に「抜け」があると、合成ツールは自動的にラッチを推論する

3. Day 2:「全部の分岐を網羅したのにまだ出る」

🔴 症状

Day 1 の修正で一部のWarningは消えたが、別の信号で「Latch inferred」が再発。学生は「elseもdefaultも全部書いたのに!」と混乱。

🔍 原因:複数出力時の代入漏れ

// ❌ Day 2 のコード:DATA2 の代入漏れ
always @(A) begin
    case (A)
        1'b0: DATA1 = 1'b1;   // ← DATA2 がない!
        1'b1: begin
            DATA1 = 1'b0;
            DATA2 = 1'b1;
        end
        default: begin
            DATA1 = 1'b0;
            DATA2 = 1'b0;
        end
    endcase
end

A=0 のとき、DATA2 はどうなるでしょうか? DATA1 には 1 が入りますが、DATA2 の記述がないため、「前の値を保持」→ ラッチが生成されます。

⚠️ 見落としやすいポイント

条件分岐の網羅(else/default)は完璧でも、すべての分岐ですべての出力信号に値を代入しているかを確認しないと、ラッチが生まれます。出力信号が増えるほどこのミスは起きやすくなります。

✅ Day 2 の修正:先頭初期化テクニック

// ⭕ 修正後:always文の先頭で全出力を初期化
always @(A) begin
    DATA1 = 1'b0;   // ← まず全出力をデフォルト値で初期化!
    DATA2 = 1'b0;   // ← これが最強のラッチ防止策

    case (A)
        1'b0: DATA1 = 1'b1;   // DATA2 は初期化済みなので記述不要!
        1'b1: begin
            DATA1 = 1'b0;
            DATA2 = 1'b1;
        end
        // default すら不要になる(全出力が初期化済みのため)
    endcase
end
もふねこ

🐾 もふねこ:
「先頭初期化」を覚えた瞬間、学生の顔がパッと明るくなったんだ。「これなら分岐の中で『DATA2書いたっけ?』って悩む必要がない!」って。そう、この「先頭で全出力をデフォルト代入する」テクニックは、ラッチ防止の最強パターンだよ🐾

📌 Day 2 の教訓
  • always文の先頭で、すべての出力信号にデフォルト値を代入する
  • その後のcase/if文では、変更が必要な信号だけ上書きすればよい
  • この方法なら、出力信号が何本あってもラッチが生まれない

4. Day 3:「そもそもラッチって何だったの?」

Warningは全て消えました。しかし学生は、ふと疑問を感じます。

「ラッチを作っちゃダメ!って言われるけど、そもそもラッチって何?フリップフロップと何が違うの?」

もふねこ

🐾 もふねこ:
いい質問!「なぜダメなのか」を理解せずにルールだけ覚えても、応用が効かないよね。ここが一番大事なところだから、しっかり説明するね。

D-FFとラッチの決定的な違い

項目D-FF(フリップフロップ)ラッチ(Dラッチ)
動作タイミングクロックのエッジ(瞬間)でデータを読み込むイネーブル信号がHighの間ずっとデータを素通しにする
イメージ「せーの!」の合図で全員一斉に動く優等生合図を待たずに勝手に動く自由人
値を取り込む瞬間エッジの前後にデバイスが決めたセットアップ/ホールドの時間があり、その外での変化は取り込まれないイネーブルがHighの間は入力の変化がそのまま出口へ伝わる
タイミング解析STA(静的タイミング解析)が容易タイムボローイングなど、FFとは別の扱いが必要
FPGA適性FPGA内部はD-FF前提で設計されている → 最適FFを前提にした資源と解析の流れから外れる → 扱いにくい

なぜ「意図しないラッチ」が危険なのか

⚠️
意図しないラッチの3つの危険
  1. いつ値が決まるのかが読めない:ゲートが開いている間は入力の変化がそのまま出口へ伝わり、ゲートが閉じた瞬間の値がそのあと保持される。開いている間に入った変化が元へ戻れば出力も戻るので「一瞬のヒゲを必ず記憶する」わけではないよ(EN=1 のまま D を 0→1→0 と動かすと Q も 0→1→0 と追従し、EN=0 のあとは 0 を保持するのを確かめた)。こわいのは、どの瞬間に閉じるかで残る値が変わることなんだ
  2. タイミング解析の手当てが増える:ラッチに対応した解析のしかたはちゃんとあって、イネーブルをクロックとして扱い、透過している期間やタイムボローイングを考えて見ていくよ(Quartus Prime Timing Analyzer のガイドに説明がある)。問題は、意図せずできたラッチだと開閉のタイミングも制約も想定していない状態になること。対応する資源や解析の設定も確認が要るよ。ついでに言うと、D-FFどうしの間でも「次のクロックまでに間に合えばOK」ではなく、セットアップとホールドの両方を満たす必要があるんだ
  3. FPGAの作りから外れる:FPGAは「LUT+D-FF」のセットが敷き詰められた構造だから、ラッチはその前提から外れて、資源の使い方もタイミング解析も扱いにくくなる
もふねこ

🐾 もふねこ:
「論理合成ツールが『Latch inferred』と怒るのには、こういう深い理由があったんだね!」と学生は納得してくれた。3日間の苦労が、ここで一気に報われた瞬間だったよ🐾

5. 二度とハマらないための3つの鉄則

3日間の実験ログを踏まえて、もふねこが実習で教えている「ラッチ0件にするための鉄則」をまとめます。

#鉄則対応するDay
1 組合せ回路は、すべての経路で全出力に値が決まるようにする
基本は if に else、case に default。鉄則2の先頭初期化をしてあれば、それで経路は埋まっている
Day 1
2 always文の先頭で全出力をデフォルト値に初期化する
複数出力の代入漏れを根本的に防止する最強パターン
Day 2
3 「Latch inferred」の警告は必ず理由をつけて処理する
意図したラッチ以外は残さない。警告は種類ごとに扱いを決めよう(実験ログ3)
Day 3

6. まとめ

📝 Latch地獄 脱出マップ

Day問題原因解決策
1else不足でラッチ生成条件分岐の網羅漏れすべての経路で全出力に値を決める(else / default で埋める)
2複数出力で代入漏れ一部の分岐で一部の出力が未定義always文の先頭で全出力を初期化
3なぜラッチがダメなのか不明D-FFとラッチの違いを知らなかった「エッジ vs レベル」の違いと、タイミング解析での扱いを理解
📌 このページで一番覚えてほしいこと

「先頭初期化」を癖にすれば、ラッチ地獄はほぼ確実に回避できます。always文を書くときは、まず全出力にデフォルト値を入れてから、その後のif/case文で上書きする──この順序を体に叩き込んでください。

関連するページ: 応用実験5: ラッチを生む3つの原因と対策 | 実験ログ1: Sim/Synthミスマッチを解剖する | 実験ログ3: Warning放置で溶かした話 ベスト5