「Latch inferred 地獄から脱出した3日間」もふねこの実験ログ
「Warning: Latch inferred for signal 'xxx'」──この警告を見た瞬間の絶望感、もふねこも知ってるよ🐾
このページは、実習で丸3日間ラッチ地獄にハマった学生と一緒に解決した実験ログ。「直したはずなのに消えない!」を3段階で追体験しながら、最終的にはラッチの正体まで理解できる構成にしたよ!
1. はじめに:Warning地獄の3日間
実習で、ある学生がデコーダ回路を書いていました。シミュレーションは完璧に通る。波形も綺麗。なのに、論理合成をかけるとWarningの嵐が出る──。
Warning: Latch inferred for signal 'Z'
Warning: Latch inferred for signal 'DATA2'
「Warningだから動くんですよね?」と学生は言いました。もふねこは首を横に振りました。
🐾 もふねこ:
「Latch inferred」のWarningは絶対に無視しちゃダメ。意図しないラッチは、ノイズで誤動作したり、タイミング解析を破綻させたり、実機で再現性のないバグを生み出す元凶なんだ。Warningが0になるまで、一緒にデバッグしよう。
ここから、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消えた!」と学生は喜んだ。でもまだ終わりじゃなかったんだ……。
if文の最後には必ず 単独のelseを書くcase文の最後には必ずdefaultを書く- 条件分岐に「抜け」があると、合成ツールは自動的にラッチを推論する
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書いたっけ?』って悩む必要がない!」って。そう、この「先頭で全出力をデフォルト代入する」テクニックは、ラッチ防止の最強パターンだよ🐾
- always文の先頭で、すべての出力信号にデフォルト値を代入する
- その後のcase/if文では、変更が必要な信号だけ上書きすればよい
- この方法なら、出力信号が何本あってもラッチが生まれない
4. Day 3:「そもそもラッチって何だったの?」
Warningは全て消えました。しかし学生は、ふと疑問を感じます。
「ラッチを作っちゃダメ!って言われるけど、そもそもラッチって何?フリップフロップと何が違うの?」
🐾 もふねこ:
いい質問!「なぜダメなのか」を理解せずにルールだけ覚えても、応用が効かないよね。ここが一番大事なところだから、しっかり説明するね。
D-FFとラッチの決定的な違い
| 項目 | D-FF(フリップフロップ) | ラッチ(Dラッチ) |
|---|---|---|
| 動作タイミング | クロックのエッジ(瞬間)でデータを読み込む | イネーブル信号がHighの間ずっとデータを素通しにする |
| イメージ | 「せーの!」の合図で全員一斉に動く優等生 | 合図を待たずに勝手に動く自由人 |
| ノイズ耐性 | エッジの瞬間以外のノイズは無視 | ゲートが開いている間のノイズを全部拾ってしまう |
| タイミング解析 | STA(静的タイミング解析)が容易 | タイムボローイング等の特殊解析が必要で極めて複雑 |
| FPGA適性 | FPGA内部はD-FF前提で設計されている → 最適 | 不自然な回路が割り当てられ、リソース無駄遣い → 不適 |
なぜ「意図しないラッチ」が危険なのか
- ノイズで誤動作:ゲートが開いている間、一瞬のヒゲ信号(グリッチ)もラッチが拾って記憶してしまう
- タイミング解析が破綻:D-FFだけなら「次のクロックまでに間に合えばOK」と簡単に計算できるが、ラッチが混ざると計算が非常に複雑化する
- FPGAのリソースを無駄遣い:FPGAは「LUT+D-FF」のセットが敷き詰められた構造。ラッチを無理やり作ると非効率な回路になる
🐾 もふねこ:
「論理合成ツールが『Latch inferred』と怒るのには、こういう深い理由があったんだね!」と学生は納得してくれた。3日間の苦労が、ここで一気に報われた瞬間だったよ🐾
5. 二度とハマらないための3つの鉄則
3日間の実験ログを踏まえて、もふねこが実習で教えている「ラッチ0件にするための鉄則」をまとめます。
| # | 鉄則 | 対応するDay |
|---|---|---|
| 1 | if文の最後に必ずelseを書く。case文の最後に必ずdefaultを書く 条件分岐に「抜け」があると即ラッチ生成 |
Day 1 |
| 2 | always文の先頭で全出力をデフォルト値に初期化する 複数出力の代入漏れを根本的に防止する最強パターン |
Day 2 |
| 3 | Warningが0件になるまで絶対に実機に書き込まない 「Latch inferred」は「動いているように見えるバグ」の温床 |
Day 3 |
6. まとめ
📝 Latch地獄 脱出マップ
| Day | 問題 | 原因 | 解決策 |
|---|---|---|---|
| 1 | else不足でラッチ生成 | 条件分岐の網羅漏れ | if → else / case → default を必ず書く |
| 2 | 複数出力で代入漏れ | 一部の分岐で一部の出力が未定義 | always文の先頭で全出力を初期化 |
| 3 | なぜラッチがダメなのか不明 | D-FFとラッチの違いを知らなかった | 「エッジ vs レベル」「ノイズ耐性」を理解 |
「先頭初期化」を癖にすれば、ラッチ地獄はほぼ確実に回避できます。always文を書くときは、まず全出力にデフォルト値を入れてから、その後のif/case文で上書きする──この順序を体に叩き込んでください。
関連するページ: 応用実験5: ラッチを生む3つの原因と対策 | 実験ログ1: Sim/Synthミスマッチを解剖する | 実験ログ3: Warning放置で溶かした話 ベスト5