---
title: Verilog中Latch的生成原因与避免策略：设计指南与实施手册
url: "https://www.fpgalearn.com/50310.html"
kind: article
id: 50310
excerpt: Quick Start（快速上手）本指南旨在帮助FPGA设计者快速识别并消除Verilog代码中意外生成的锁存器（Latch）。以下步骤可在30分钟内完成一次完…
published_at: 2026-06-10
author: FPGA小白
tags: fpga, Latch, Verilog
word_count: 5307
source: "https://www.fpgalearn.com"
---

# Verilog中Latch的生成原因与避免策略：设计指南与实施手册

> 标签：fpga、Latch、Verilog
> Quick Start（快速上手）本指南旨在帮助FPGA设计者快速识别并消除Verilog代码中意外生成的锁存器（Latch）。以下步骤可在30分钟内完成一次完…

> Quick Start（快速上手）本指南旨在帮助FPGA设计者快速识别并消除Verilog代码中意外生成的锁存器（Latch）。以下步骤可在30分钟内完成一次完…

## Quick Start（快速上手）
本指南旨在帮助FPGA设计者快速识别并消除Verilog代码中意外生成的锁存器（Latch）。以下步骤可在30分钟内完成一次完整的Latch检测与修复流程。
- **步骤一**：打开Vivado 2025.2（或更高版本），新建一个RTL工程，目标器件选择Xilinx Artix-7 XC7A35T（或任意FPGA）。
- **步骤二**：创建一个Verilog模块，包含一个always块，敏感列表为posedge clk或negedge rst_n（同步设计），确保所有分支覆盖完整。
- **步骤三**：在always块中，使用if-else if-else结构或case-default结构，确保每个条件分支都有赋值语句，避免遗漏。
- **步骤四**：运行综合（Synthesis），打开综合后的原理图（Schematic），检查是否有Latch原语（如LD、LDPE等）出现。
- **步骤五**：在Vivado的Report Utilization中查看Latch数量，若为0则说明无意外Latch；若大于0，则定位到具体模块。
- **步骤六**：修复所有未覆盖完整的分支：为每个if补充else，为每个case添加default，确保每个信号在每个分支下都被赋值。
- **步骤七**：重新综合，确认Latch数量归零，且功能仿真通过。
- **步骤八**：在Vivado中运行Implementation，检查时序报告（Setup/Hold）是否满足，确保无意外Latch引入的时序问题。
## 前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
| --- | --- | --- | --- |
| FPGA器件 | Xilinx Artix-7 XC7A35T | 通用低端器件，Latch行为典型 | 任意Xilinx/Altera/国产FPGA |
| EDA工具 | Vivado 2025.2 | 支持Latch检测与报告 | Quartus Prime 24.3+、Gowin EDA |
| 仿真器 | Vivado Simulator | 集成在Vivado中，无需额外安装 | ModelSim、Questa、Verilator |
| 时钟与复位 | 50MHz时钟，异步低有效复位 | 常用同步设计基础 | 任意频率，建议<100MHz |
## 目标与验收标准
完成本指南后，读者应能够：
- 理解Verilog中Latch生成的底层机制（组合逻辑中的不完全分支）。
- 在Vivado中快速检测并定位Latch。
- 通过代码修改消除所有意外Latch，且不影响功能。
- 验证修复后时序收敛，无新增风险。
**验收标准**：综合后Report Utilization中Latch数量为0；功能仿真结果与预期一致；时序分析无违规。
## 实施步骤
### 1. 创建测试工程与模块
打开Vivado，新建RTL工程，选择Artix-7 XC7A35T器件。创建一个Verilog模块，包含以下代码（该代码故意包含Latch生成风险）：
```
module latch_example (
    input wire a,
    input wire b,
    input wire sel,
    output reg y
);
always @(*) begin
    if (sel)
        y = a;
    // 缺少else分支，当sel=0时y保持原值，综合出Latch
end
endmodule
```
### 逐行说明
- **第1行**：定义模块名latch_example，端口列表包含a、b、sel（输入）和y（输出）。
- **第2行**：声明a为1位输入线网。
- **第3行**：声明b为1位输入线网（本示例中未使用，仅作演示）。
- **第4行**：声明sel为1位输入线网。
- **第5行**：声明y为1位输出寄存器类型（reg），用于always块内赋值。
- **第6行**：always块开始，敏感列表为@(*)，表示组合逻辑（所有输入信号变化触发）。
- **第7行**：if语句开始，条件为sel为真（高电平）。
- **第8行**：当sel为1时，将a的值赋给y。
- **第9行**：if语句结束，但缺少else分支——这是Latch生成的关键原因：当sel=0时，y没有被赋值，综合工具推断y需要保持原值，因此插入透明锁存器。
- **第10行**：endmodule，模块结束。
### 2. 综合并检测Latch
运行综合（Synthesis）。综合完成后，打开Schematic视图，搜索“LD”或“LDPE”原语。同时，在Report Utilization中查看Latch计数。预期结果：Latch数量为1（对应y信号）。
### 3. 修复Latch：补全分支
修改代码，为if语句添加else分支，确保所有条件下y都有明确赋值：
```
module latch_fixed (
    input wire a,
    input wire b,
    input wire sel,
    output reg y
);
always @(*) begin
    if (sel)
        y = a;
    else
        y = b;  // 补全分支，避免Latch
end
endmodule
```
### 逐行说明
- **第1行**：定义修复后的模块latch_fixed。
- **第2-5行**：端口声明与之前相同。
- **第6行**：always @(*) 组合逻辑块。
- **第7行**：if (sel) 条件判断。
- **第8行**：sel为1时，y = a。
- **第9行**：else分支开始。
- **第10行**：sel为0时，y = b。现在所有条件分支都已覆盖，综合工具不会推断Latch。
- **第11行**：endmodule。
### 4. 重新综合并验证
重新运行综合，检查Schematic和Report Utilization。预期：Latch数量为0。运行功能仿真，验证y在sel=0时输出b，sel=1时输出a。
### 5. 处理case语句中的Latch
case语句中缺少default分支也会生成Latch。以下代码演示问题与修复：
```
module case_latch (
    input wire [1:0] sel,
    input wire [3:0] data_in,
    output reg [3:0] data_out
);
always @(*) begin
    case (sel)
        2'b00: data_out = data_in;
        2'b01: data_out = data_in + 1;
        // 缺少2'b10、2'b11和default分支，产生Latch
    endcase
end
endmodule
```
### 逐行说明
- **第1行**：定义模块case_latch。
- **第2行**：声明2位输入sel。
- **第3行**：声明4位输入data_in。
- **第4行**：声明4位输出寄存器data_out。
- **第5行**：always @(*) 组合逻辑。
- **第6行**：case (sel) 开始。
- **第7行**：当sel=00时，data_out = data_in。
- **第8行**：当sel=01时，data_out = data_in + 1。
- **第9行**：缺少sel=10、sel=11及default分支——当sel为10或11时，data_out无赋值，综合出Latch。
- **第10行**：endcase。
- **第11行**：endmodule。
修复方式：添加default分支，给data_out一个默认值（如0或data_in）：
```
module case_fixed (
    input wire [1:0] sel,
    input wire [3:0] data_in,
    output reg [3:0] data_out
);
always @(*) begin
    case (sel)
        2'b00: data_out = data_in;
        2'b01: data_out = data_in + 1;
        default: data_out = 4'b0;  // 补全所有未覆盖情况
    endcase
end
endmodule
```
### 逐行说明
- **第1-5行**：模块声明与端口定义同前。
- **第6行**：always @(*) 组合逻辑。
- **第7行**：case (sel)。
- **第8行**：sel=00时赋值。
- **第9行**：sel=01时赋值。
- **第10行**：default分支，为所有未列出的sel值（10、11）赋值为0，消除Latch。
- **第11行**：endcase。
- **第12行**：endmodule。
### 6. 综合后检查与实现
对修复后的两个模块（latch_fixed和case_fixed）分别综合，确认Latch数量为0。然后运行Implementation，检查时序报告（Setup/Hold）是否满足。若时序违规，可能源于Latch引入的组合路径延迟，需进一步优化。
## 验证结果
修复前：
- latch_example模块综合后产生1个Latch（LD原语）。
- case_latch模块综合后产生1个Latch（对应data_out信号）。
修复后：
- latch_fixed模块综合后Latch数量为0，功能仿真通过。
- case_fixed模块综合后Latch数量为0，功能仿真通过。
时序报告显示，修复后所有路径的Setup Slack和Hold Slack均为正值，无违规。
## 排障指南
- **问题：综合后Latch数量不为0，但代码中所有分支似乎已覆盖。** 可能原因：在always块中对同一个信号在多个分支中赋值，但某些路径下未赋值。检查是否所有信号在所有分支中都被赋值。另外，注意组合逻辑always块中不要使用非阻塞赋值（<=），否则可能产生意外行为。
- **问题：Latch出现在同步always块（posedge clk）中。** 同步always块中，如果if缺少else，综合工具通常不会生成Latch，而是推断为寄存器（flip-flop）。但如果敏感列表包含电平触发信号（如always @(a or b)），则可能生成Latch。确保同步设计使用posedge/negedge敏感列表。
- **问题：Vivado Report Utilization中Latch计数为0，但Schematic中仍有类似Latch的结构。** 可能是综合工具将某些组合逻辑优化为透明锁存器，但未标记为Latch。此时应检查Schematic中是否有反馈路径（输出回馈到输入），这是Latch的典型特征。
## 扩展：Latch的合理使用场景
虽然意外Latch应避免，但在某些低功耗设计或异步接口中，Latch可被有意使用。例如，在时钟门控（Clock Gating）中，Latch用于在时钟低电平时保持使能信号，减少动态功耗。此时需在代码中显式实例化Latch原语（如Vivado中的LDCE），并添加注释说明意图。注意：有意使用的Latch仍需满足时序约束，且需通过静态时序分析验证。
## 参考
- Xilinx UG901: Vivado Design Suite User Guide – Synthesis
- IEEE Std 1364-2005: Verilog Hardware Description Language
- Clifford E. Cummings, “Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!”
## 附录：完整测试代码
以下为包含所有示例的完整测试模块，可用于仿真验证：
```
module tb_latch;
    reg a, b, sel;
    wire y_bad, y_good;

    latch_example u_bad (.a(a), .b(b), .sel(sel), .y(y_bad));
    latch_fixed  u_good(.a(a), .b(b), .sel(sel), .y(y_good));

    initial begin
        a = 0; b = 1; sel = 0;
        #10 sel = 1;
        #10 sel = 0;
        #10 $finish;
    end
endmodule
```
### 逐行说明
- **第1行**：定义测试模块tb_latch。
- **第2行**：声明reg类型变量a、b、sel，用于驱动输入。
- **第3行**：声明wire类型y_bad和y_good，用于观察输出。
- **第5行**：实例化latch_example（含Latch的模块），输出连接到y_bad。
- **第6行**：实例化latch_fixed（修复后的模块），输出连接到y_good。
- **第8行**：initial块开始，定义测试序列。
- **第9行**：初始化a=0, b=1, sel=0。
- **第10行**：延迟10个时间单位后，sel置为1。
- **第11行**：再延迟10个单位，sel置回0。
- **第12行**：再延迟10个单位，结束仿真。
- **第13行**：endmodule。
仿真时，观察y_bad在sel=0时保持上一次值（Latch行为），而y_good在sel=0时立即输出b=1，符合预期。
