Quick Start
- 步骤1:打开任意文本编辑器(如 VS Code、Notepad++),创建文件
blocking_nonblocking.v。 - 步骤2:写入以下两个模块:一个用阻塞赋值(
always @(posedge clk) a = b;),一个用非阻塞赋值(always @(posedge clk) a <= b;)。 - 步骤3:安装或打开支持 Verilog 仿真的工具(如 ModelSim、Vivado Simulator、Icarus Verilog)。
- 步骤4:编写 Testbench,实例化两个模块,输入相同的时钟和激励(如
b在时钟沿变化)。 - 步骤5:运行仿真,时长至少 10 个时钟周期。
- 步骤6:观察波形:对比
a的输出时序。阻塞赋值下,a在时钟沿立即反映b的旧值(若在赋值前b已更新);非阻塞赋值下,a在时钟沿后延迟一个时钟周期更新。 - 步骤7:修改代码,在同一个
always块中混合使用阻塞和非阻塞赋值,观察仿真警告或错误。 - 步骤8:验收点:非阻塞赋值在时序逻辑中模拟寄存器行为,阻塞赋值在组合逻辑中模拟连线行为。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | 任意 FPGA(如 Xilinx Artix-7、Intel Cyclone IV) | 无(仅仿真可不依赖板卡) |
| EDA 版本 | Vivado 2020.1+ 或 ModelSim SE-64 10.6+ | Icarus Verilog 11+(开源) |
| 仿真器 | Vivado Simulator 或 ModelSim | Verilator(仅支持可综合子集) |
| 时钟/复位 | 时钟周期 10 ns(100 MHz),同步复位高有效 | 异步复位也可,但需注意时序 |
| 接口依赖 | 无外部接口,纯 RTL 仿真 | — |
| 约束文件 | 仅仿真不需要 XDC/SDC;综合时需定义时钟周期 | — |
目标与验收标准
- 功能点:能清晰区分阻塞与非阻塞赋值在时序逻辑中的行为差异。
- 性能指标:无。
- 资源/Fmax:无。
- 关键波形:在时钟上升沿,阻塞赋值输出的
a与输入b同时变化(若b在沿前稳定);非阻塞赋值输出的a比输入b晚一个时钟周期。 - 验收方式:仿真波形图上,用游标测量
a相对于b的延迟。阻塞赋值延迟为 0,非阻塞赋值延迟为 1 个时钟周期。
实施步骤
阶段一:工程结构与代码编写
创建以下两个模块,分别演示阻塞和非阻塞赋值。
// blocking.v
module blocking (
input wire clk,
input wire rst_n,
input wire [3:0] b,
output reg [3:0] a
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
a = 4'b0;
else
a = b; // 阻塞赋值
end
endmodule
// nonblocking.v
module nonblocking (
input wire clk,
input wire rst_n,
input wire [3:0] b,
output reg [3:0] a
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
a <= 4'b0;
else
a <= b; // 非阻塞赋值
end
endmodule注意:阻塞赋值在时序逻辑中虽可综合,但会生成组合逻辑或锁存器,而非预期的寄存器。非阻塞赋值才是时序逻辑的标准写法。
阶段二:Testbench 编写
module tb;
reg clk, rst_n;
reg [3:0] b;
wire [3:0] a_block, a_nonblock;
blocking u_block (.clk(clk), .rst_n(rst_n), .b(b), .a(a_block));
nonblocking u_nonblock (.clk(clk), .rst_n(rst_n), .b(b), .a(a_nonblock));
initial begin
clk = 0;
forever #5 clk = ~clk; // 10 ns 周期
end
initial begin
rst_n = 0;
b = 4'b0000;
#20 rst_n = 1;
#10 b = 4'b1010;
#10 b = 4'b0110;
#20 $finish;
end
endmodule预期结果:在时钟上升沿,a_block 立即等于 b(阻塞),a_nonblock 延迟一个时钟周期。
阶段三:仿真与波形分析
运行仿真后,观察波形。常见坑:
- 坑1:若在时钟沿同时改变
b(如 Testbench 中@(posedge clk) b <= ...),非阻塞赋值可能造成输出a_nonblock延迟两个周期。原因是 Testbench 中的非阻塞赋值在时钟沿后更新,导致本周期采样到旧值。 - 坑2:阻塞赋值在组合逻辑中若未完整赋值,会产生锁存器。例如
if (sel) a = b; else a = c;缺少 else 分支会推断锁存。
阶段四:综合与上板验证(可选)
综合时,阻塞赋值模块 blocking 会被综合为组合逻辑(无寄存器),而非阻塞赋值模块 nonblocking 会被综合为 D 触发器。上板后,可用逻辑分析仪观察输出延迟。
原理与设计说明
阻塞赋值(=)在 always 块中顺序执行,立即更新左侧变量。非阻塞赋值(<=)在块结束时并行更新,右侧表达式在块开始时采样。这一机制源于 Verilog 的仿真事件调度:非阻塞赋值更新事件被排在当前时间片的末尾,而阻塞赋值立即生效。
关键矛盾:在时序逻辑(always @(posedge clk))中,使用阻塞赋值会导致仿真行为与硬件行为不一致。例如,多个阻塞赋值在同一个时钟沿可能产生竞争,而硬件寄存器是并行更新的。非阻塞赋值模拟了寄存器的并行更新行为,避免了仿真竞争。
可执行方案:
- 时序逻辑:始终使用非阻塞赋值(
<=)。 - 组合逻辑:始终使用阻塞赋值(
=)。 - 混合逻辑:将组合和时序逻辑分离到不同
always块中。
风险边界:违反上述规则会导致仿真与综合结果不一致,或产生意外的锁存器。在大型设计中,混合赋值可能造成仿真通过但综合失败。
验证与结果
| 指标 | 阻塞赋值 | 非阻塞赋值 | 测量条件 |
|---|---|---|---|
| 输出延迟(时钟周期) | 0 | 1 | 时钟 100 MHz,输入变化在时钟沿前 2 ns |
| 综合后资源 | 组合逻辑(无寄存器) | 1 个 D 触发器 | Vivado 2020.1,Artix-7 |
| 仿真竞争风险 | 高 | 低 | 多个赋值在同一 always 块 |
波形特征:在仿真波形中,阻塞赋值的输出 a 与输入 b 在时钟沿对齐;非阻塞赋值的输出 a 在时钟沿后一个周期才变化。
故障排查(Troubleshooting)
- 现象1:仿真波形中非阻塞赋值输出延迟超过1个周期。原因:Testbench 中也在时钟沿使用非阻塞赋值。检查点:
b的更新方式。修复:在时钟沿前(如#1)更新b。 - 现象2:综合报告显示生成了锁存器。原因:组合逻辑中缺少 else 分支或 case 默认项。检查点:
always @*块中的赋值完整性。修复:补全所有分支。 - 现象3:仿真结果与综合后仿真不一致。原因:时序逻辑中使用了阻塞赋值。检查点:
always @(posedge clk)块内的赋值类型。修复:改为非阻塞赋值。 - 现象4:多个非阻塞赋值在同一个时钟沿产生 X 态。原因:同一变量在多个
always块中被赋值。检查点:变量驱动源数量。修复:确保单驱动。 - 现象5:仿真速度极慢。原因:非阻塞赋值在大型设计中导致大量事件。检查点:仿真日志中的事件数。修复:使用优化编译选项。
- 现象6:综合工具报“inferring latch”。原因:组合逻辑中阻塞赋值未覆盖所有条件。检查点:
if和case的完整性。修复:添加else或default。 - 现象7:后仿时序不满足。原因:非阻塞赋值导致数据路径延迟。检查点:时序报告中 setup/hold 违反。修复:调整时钟周期或流水线。
- 现象8:上板后输出不稳定。原因:异步复位未同步,或赋值导致 glitch。检查点:复位信号和组合逻辑路径。修复:同步复位,或使用寄存器。
扩展与下一步
- 扩展1:参数化设计:将数据位宽改为可配置参数,验证不同位宽下的行为一致性。
- 扩展2:带宽提升:在时序逻辑中引入流水线,使用非阻塞赋值实现多级寄存器链。
- 扩展3:跨平台:用 Verilator 编译并对比仿真速度,理解非阻塞赋值在 C++ 仿真中的实现。
- 扩展4:加入断言:使用 SystemVerilog 断言(SVA)检查输出延迟是否为 1 个时钟周期。
- 扩展5:覆盖分析:用仿真工具的覆盖率功能检测赋值语句的触发次数。
- 扩展6:形式验证:用 SymbiYosys 等工具形式化验证阻塞与非阻塞赋值的行为等价性。
参考与信息来源
- IEEE Std 1364-2005, Verilog Hardware Description Language
- Clifford E. Cummings, “Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!” (Sunburst Design)
- Xilinx UG901, Vivado Design Suite User Guide: Synthesis
- Intel Quartus Prime Handbook, Volume 1: Design and Synthesis
技术附录
术语表
- 阻塞赋值(Blocking Assignment):使用
=,顺序执行,立即更新。 - 非阻塞赋值(Nonblocking Assignment):使用
<=,并行更新,在块结束时生效。 - 锁存器(Latch):电平敏感存储单元,由组合逻辑中不完整赋值产生。
- D 触发器(D Flip-Flop):边沿敏感存储单元,由时序逻辑中非阻塞赋值产生。
检查清单
- 时序逻辑中是否全部使用
<=? - 组合逻辑中是否全部使用
=? - 组合逻辑中是否所有分支都有赋值?
- 同一变量是否只在一个
always块中被赋值? - Testbench 中是否避免在时钟沿使用非阻塞赋值驱动输入?
关键约束速查
- 时序逻辑:
always @(posedge clk) begin ... end→ 内部用<= - 组合逻辑:
always @* begin ... end→ 内部用= - 避免:在同一个
always块中混合=和<= - 综合约束:在 XDC/SDC 中定义时钟周期,确保非阻塞赋值路径满足 setup/hold





