Quick Start
- 1. 准备环境:安装Vivado 2024.2(或更高版本)与ModelSim/QuestaSim 2025.1仿真器。
- 2. 创建工程:新建Vivado工程,选择xc7a35ticsg324-1L(Artix-7)作为目标器件。
- 3. 编写RTL代码:创建三个模块——
bad_comb(阻塞赋值组合逻辑)、good_seq(非阻塞赋值时序逻辑)、mixed(混合赋值演示)。 - 4. 编写Testbench:用时钟周期驱动输入,观察输出波形,重点检查
always @(posedge clk)与always @(*)中赋值行为。 - 5. 运行行为仿真:在QuestaSim中运行
vsim work.tb_top,添加clk、a、b、c、q1、q2信号到波形窗口。 - 6. 观察现象:对于时序逻辑,非阻塞赋值在时钟上升沿后同时更新;阻塞赋值则在时钟沿前立即更新,导致仿真与综合结果不一致。
- 7. 运行综合:在Vivado中运行
synth_design -rtl,检查综合后网表是否出现意外锁存器或组合反馈。 - 8. 验收:确保
good_seq综合为D触发器链,bad_comb综合为组合逻辑(无寄存器),mixed中无意外锁存器。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Artix-7 xc7a35ticsg324-1L | 典型FPGA,支持所有基本原语 | Kintex-7 / Zynq-7000 |
| EDA版本 | Vivado 2024.2 | 综合与实现工具 | Vivado 2023.1+ / Quartus Prime 23+ |
| 仿真器 | QuestaSim 2025.1 | 支持SystemVerilog与VHDL混合仿真 | Vivado Simulator / ModelSim SE |
| 时钟/复位 | 100 MHz系统时钟,异步复位高有效 | 用于时序逻辑验证 | 50 MHz / 200 MHz |
| 接口依赖 | 无外部IP,纯RTL | 仅需标准Verilog-2001语法 | — |
| 约束文件 | XDC约束:create_clock -period 10.0 [get_ports clk] | 定义时钟周期 | SDC格式 |
目标与验收标准
- 功能点:在时序逻辑中,非阻塞赋值实现寄存器传输;组合逻辑中,阻塞赋值实现立即更新。
- 性能指标:综合后无意外锁存器(latch)或组合反馈(combinatorial loop);Fmax ≥ 200 MHz(示例值,以实际时序报告为准)。
- 资源消耗:
good_seq使用约4个FDRE(D触发器);bad_comb使用0个FDRE;mixed使用2个FDRE + 少量LUT。 - 关键波形:在仿真中,非阻塞赋值在时钟上升沿同时更新;阻塞赋值在时钟沿前立即更新,导致仿真与综合结果不一致。
- 日志验收:Vivado综合日志中无“WARNING: [Synth 8-327] inferring latch”或“WARNING: [Synth 8-333] combinatorial loop”警告。
实施步骤
1. 工程结构与模块划分
- 创建顶层模块
top,实例化三个子模块:bad_comb、good_seq、mixed。 - 每个子模块使用独立的
always块,避免跨模块赋值干扰。 - 编写Testbench
tb_top,提供时钟、复位和输入激励。
2. 关键模块:阻塞赋值组合逻辑(bad_comb)
module bad_comb (
input wire [3:0] a,
input wire [3:0] b,
output reg [3:0] c
);
always @(*) begin
c = a & b; // 阻塞赋值,组合逻辑
end
endmodule逐行说明
- 第1行:声明模块名
bad_comb,端口列表。 - 第2-3行:输入
a和b为4位wire类型,输出c为4位reg类型(在always块中必须用reg)。 - 第5行:
always @(*)表示组合逻辑敏感列表,自动包含所有输入。 - 第6行:阻塞赋值
=,立即计算并更新c。综合为组合与门,无寄存器。 - 第8行:结束模块。注意:如果
always块中未覆盖所有分支,会综合出锁存器(此处无分支,安全)。
3. 关键模块:非阻塞赋值时序逻辑(good_seq)
module good_seq (
input wire clk,
input wire rst_n,
input wire [3:0] d,
output reg [3:0] q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 4'b0; // 异步复位
else
q <= d; // 非阻塞赋值,在时钟沿同时更新
end
endmodule逐行说明
- 第1行:模块名
good_seq。 - 第2-5行:时钟
clk、异步复位rst_n(低有效)、数据输入d、输出q。 - 第7行:敏感列表为时钟上升沿或复位下降沿,这是标准时序逻辑模板。
- 第8行:若复位有效,将
q清零(非阻塞赋值)。 - 第10行:否则,在时钟上升沿将
d赋值给q。非阻塞赋值<=保证所有赋值在时钟沿后同时生效,避免竞争。 - 第12行:综合为FDRE(D触发器)加异步复位。
4. 关键模块:混合赋值演示(mixed)
module mixed (
input wire clk,
input wire rst_n,
input wire [3:0] a,
input wire [3:0] b,
output reg [3:0] q1,
output reg [3:0] q2
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
q1 <= 4'b0;
q2 <= 4'b0;
end else begin
q1 <= a; // 非阻塞
q2 <= q1; // 非阻塞,q2得到的是q1旧值
end
end
endmodule逐行说明
- 第1-7行:模块声明,包含两个输出
q1和q2。 - 第9行:时序逻辑敏感列表。
- 第11-12行:复位时清零两个输出。
- 第14行:非阻塞赋值,
q1在时钟沿后变为a。 - 第15行:非阻塞赋值,
q2得到的是q1在时钟沿前的旧值(即上一个时钟周期的q1)。这模拟了移位寄存器的行为。 - 第17行:综合为两个D触发器链,
q2比q1延迟一个时钟周期。
5. 时序/CDC/约束
- 时钟约束:在XDC文件中添加
create_clock -period 10.0 [get_ports clk],对应100 MHz。 - 输入延迟:使用
set_input_delay约束输入端口,避免时序违规。 - CDC处理:本示例无跨时钟域,但若引入多时钟,必须使用双级同步器(非阻塞赋值实现)。
- 复位约束:异步复位需满足恢复/移除时间,使用
set_max_delay约束复位网络。
6. 验证
- 编写Testbench,驱动时钟周期为10 ns,复位持续5个时钟周期。
- 输入
a和b在复位释放后随机变化,观察q1和q2的波形。 - 检查
bad_comb中c是否立即响应输入变化(无延迟)。 - 检查
good_seq中q是否在时钟上升沿后延迟一个时钟周期更新。 - 检查
mixed中q2是否比q1延迟一个时钟周期。
7. 上板(可选)
- 将
q1和q2连接到LED,通过按键输入a和b。 - 观察LED变化:按下按键后,
q1立即变化,q2延迟一个时钟周期变化。 - 注意:上板前务必运行实现后仿真,确认时序满足要求。
原理与设计说明
阻塞赋值(=)和非阻塞赋值(<=)的核心区别在于更新时机与仿真语义。在Verilog仿真中,每个时间片(time step)分为多个区域:阻塞赋值在“阻塞赋值区域”立即执行,而非阻塞赋值在“非阻塞赋值区域”延迟更新(在时钟沿后同时生效)。这种机制模拟了硬件寄存器的行为:所有寄存器在时钟沿同时采样输入,并在时钟沿后同时更新输出。
关键矛盾:在时序逻辑中使用阻塞赋值会导致仿真与综合行为不一致。例如,在always @(posedge clk)中使用q = d,仿真中q会立即更新,可能影响同一时钟沿的其他赋值;而综合工具会将其视为组合逻辑或锁存器,导致硬件行为与仿真不符。这是最常见的综合陷阱。
最佳实践:
- 时序逻辑(
always @(posedge clk)):使用非阻塞赋值(<=)。 - 组合逻辑(
always @(*)):使用阻塞赋值(=)。 - 混合逻辑:将时序和组合逻辑分离到不同的
always块中。 - 锁存器:避免在组合逻辑中遗漏分支(如缺少
else),否则会综合出锁存器。
Trade-off分析:
- 资源 vs Fmax:使用非阻塞赋值不会引入额外资源,但错误的赋值风格可能导致组合反馈,降低Fmax。
- 吞吐 vs 延迟:非阻塞赋值在时钟沿同时更新,保证流水线级间延迟一致;阻塞赋值可能导致数据过早到达,破坏时序。
- 易用性 vs 可移植性:遵循此规则可确保代码在Vivado、Quartus、Synplify等工具中行为一致。
验证与结果
| 模块 | 仿真行为 | 综合结果 | 资源(LUT/FF) | Fmax(MHz,示例值) |
|---|---|---|---|---|
| bad_comb | c立即响应a,b | 组合与门 | 1 LUT / 0 FF | 无时序路径 |
| good_seq | q在时钟沿后延迟1周期 | D触发器 | 0 LUT / 4 FF | 350+ |
| mixed | q2比q1延迟1周期 | 2级D触发器链 | 0 LUT / 8 FF | 350+ |
测量条件:Vivado 2024.2,Artix-7速度等级-1L,时钟约束10 ns,未使用任何优化选项。Fmax值基于综合后时序报告,实际值以具体工程为准。
故障排查(Troubleshooting)
- 现象:仿真波形中q在时钟沿前变化。原因:在时序逻辑中使用了阻塞赋值。检查点:查看always块中赋值符号。修复建议:改为非阻塞赋值。
- 现象:综合后出现“inferring latch”警告。原因:组合逻辑中未覆盖所有分支(缺少else或case默认项)。检查点:检查always @(*)块是否完整。修复建议:添加else或default赋值。
- 现象:综合后出现“combinatorial loop”警告。原因:组合反馈,如输出赋值给自身。检查点:检查组合逻辑中是否有
c = c + 1。修复建议:使用时序逻辑实现反馈。 - 现象:上板后LED不按预期变化。原因:复位极性错误或时钟未连接。检查点:检查复位信号是否低有效,时钟是否约束正确。修复建议:核对原理图与约束。
- 现象:仿真中q2与q1同时变化。原因:在时序逻辑中使用了阻塞赋值。检查点:查看mixed模块的赋值符号。修复建议:改为非阻塞赋值。
- 现象:综合后资源消耗异常高。原因:意外生成了大量锁存器。检查点:查看综合日志中的latch inference。修复建议:重构组合逻辑,确保所有分支有赋值。
- 现象:时序违规严重。原因:组合逻辑路径过长。检查点:查看时序报告中的最差路径。修复建议:插入流水线寄存器(使用非阻塞赋值)。
- 现象:跨时钟域数据错误。原因:未使用同步器。检查点:检查CDC路径是否经过双级触发器。修复建议:添加两个非阻塞赋值触发器级联。
- 现象:仿真与综合后仿真结果不一致。原因:RTL中使用了阻塞赋值建模时序逻辑。检查点:对比行为仿真与门级仿真波形。修复建议:修正赋值风格。
- 现象:Vivado综合报错“unsupported conditional statement”。原因:在always @(*)中使用了非阻塞赋值。检查点:查看错误行。修复建议:组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值。
扩展与下一步
- 参数化:将数据位宽改为参数
WIDTH,使模块可复用。 - 带宽提升:使用流水线结构(非阻塞赋值实现级间寄存器)提高吞吐率。
- 跨平台:将代码移植到Quartus Prime,验证综合结果一致性。
- 加入断言:使用SystemVerilog断言(SVA)检查时序逻辑中非阻塞赋值的正确性。
- 覆盖分析:在仿真中收集代码覆盖率,确保所有分支被测试。
- 形式验证:使用Synopsys VC Formal或Cadence JasperGold验证赋值风格是否符合规则。
参考与信息来源
- IEEE Std 1364-2005, Verilog Hardware Description Language
- Clifford E. Cummings, “Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!” (SNUG 2000)
- Xilinx UG901, Vivado Design Suite User Guide: Synthesis
- Intel Quartus Prime Handbook, Volume 1: Design and Synthesis
- 成电国芯FPGA云课堂内部培训材料(2026版)
技术附录
术语表
- 阻塞赋值:使用
=,立即更新变量,用于组合逻辑建模。 - 非阻塞赋值:使用
<=,在时钟沿后同时更新,用于时序逻辑建模。 - 锁存器(Latch):电平敏感存储单元,由组合逻辑中未覆盖分支综合产生。
- 组合反馈(Combinatorial Loop):输出通过组合逻辑直接反馈到输入,导致时序不稳定。
- FDRE:Xilinx原语,带时钟使能和同步复位的D触发器。
检查清单
- 所有时序逻辑的always块使用非阻塞赋值。
- 所有组合逻辑的always块使用阻塞赋值。
- 组合逻辑中每个分支都有赋值(无锁存器)。
- 跨时钟域信号经过两级同步器(非阻塞赋值)。
- 复位信号在时序逻辑的敏感列表中。
- 综合后日志无latch或combinatorial loop警告。




