「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は絶対に無視しちゃダメ。意図しないラッチは、意図しない値の保持で誤動作したり、タイミング解析を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消えた!」と学生は喜んだ。でもまだ終わりじゃなかったんだ……。
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書いたっけ?』って悩む必要がない!」って。そう、この「先頭で全出力をデフォルト代入する」テクニックは、ラッチ防止の最強パターンだよ🐾
- always文の先頭で、すべての出力信号にデフォルト値を代入する
- その後のcase/if文では、変更が必要な信号だけ上書きすればよい
- この方法なら、出力信号が何本あってもラッチが生まれない
4. Day 3:「そもそもラッチって何だったの?」
Warningは全て消えました。しかし学生は、ふと疑問を感じます。
「ラッチを作っちゃダメ!って言われるけど、そもそもラッチって何?フリップフロップと何が違うの?」
🐾 もふねこ:
いい質問!「なぜダメなのか」を理解せずにルールだけ覚えても、応用が効かないよね。ここが一番大事なところだから、しっかり説明するね。
D-FFとラッチの決定的な違い
| 項目 | D-FF(フリップフロップ) | ラッチ(Dラッチ) |
|---|---|---|
| 動作タイミング | クロックのエッジ(瞬間)でデータを読み込む | イネーブル信号がHighの間ずっとデータを素通しにする |
| イメージ | 「せーの!」の合図で全員一斉に動く優等生 | 合図を待たずに勝手に動く自由人 |
| 値を取り込む瞬間 | エッジの前後にデバイスが決めたセットアップ/ホールドの時間があり、その外での変化は取り込まれない | イネーブルがHighの間は入力の変化がそのまま出口へ伝わる |
| タイミング解析 | STA(静的タイミング解析)が容易 | タイムボローイングなど、FFとは別の扱いが必要 |
| FPGA適性 | FPGA内部はD-FF前提で設計されている → 最適 | FFを前提にした資源と解析の流れから外れる → 扱いにくい |
なぜ「意図しないラッチ」が危険なのか
- いつ値が決まるのかが読めない:ゲートが開いている間は入力の変化がそのまま出口へ伝わり、ゲートが閉じた瞬間の値がそのあと保持される。開いている間に入った変化が元へ戻れば出力も戻るので「一瞬のヒゲを必ず記憶する」わけではないよ(EN=1 のまま D を 0→1→0 と動かすと Q も 0→1→0 と追従し、EN=0 のあとは 0 を保持するのを確かめた)。こわいのは、どの瞬間に閉じるかで残る値が変わることなんだ
- タイミング解析の手当てが増える:ラッチに対応した解析のしかたはちゃんとあって、イネーブルをクロックとして扱い、透過している期間やタイムボローイングを考えて見ていくよ(Quartus Prime Timing Analyzer のガイドに説明がある)。問題は、意図せずできたラッチだと開閉のタイミングも制約も想定していない状態になること。対応する資源や解析の設定も確認が要るよ。ついでに言うと、D-FFどうしの間でも「次のクロックまでに間に合えばOK」ではなく、セットアップとホールドの両方を満たす必要があるんだ
- 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 | 問題 | 原因 | 解決策 |
|---|---|---|---|
| 1 | else不足でラッチ生成 | 条件分岐の網羅漏れ | すべての経路で全出力に値を決める(else / default で埋める) |
| 2 | 複数出力で代入漏れ | 一部の分岐で一部の出力が未定義 | always文の先頭で全出力を初期化 |
| 3 | なぜラッチがダメなのか不明 | D-FFとラッチの違いを知らなかった | 「エッジ vs レベル」の違いと、タイミング解析での扱いを理解 |
「先頭初期化」を癖にすれば、ラッチ地獄はほぼ確実に回避できます。always文を書くときは、まず全出力にデフォルト値を入れてから、その後のif/case文で上書きする──この順序を体に叩き込んでください。
関連するページ: 応用実験5: ラッチを生む3つの原因と対策 | 実験ログ1: Sim/Synthミスマッチを解剖する | 実験ログ3: Warning放置で溶かした話 ベスト5