Quick Start:10 分钟直观对比
本指南通过一个简单的 3 位移位寄存器示例,帮助你在 10 分钟内直观理解阻塞赋值与非阻塞赋值的核心区别。请严格按以下步骤操作。
- 创建工程:在 Vivado 或 Quartus 中新建工程,器件任意(如 xc7a35t)。
- 编写阻塞赋值代码:新建 Verilog 文件,输入阻塞赋值移位寄存器代码(见下文)。
- 编写非阻塞赋值代码:新建另一个 Verilog 文件,输入非阻塞赋值移位寄存器代码。
- 编写 Testbench:新建仿真文件,例化两个模块,施加相同的时钟和复位信号。
- 运行行为仿真:使用 Vivado Simulator 或 Modelsim,仿真时长设为 1 μs。
- 观察波形:将内部信号 q0、q1、q2 添加到波形窗口。
- 对比结果:观察时钟上升沿前后的赋值行为——阻塞赋值在同一时钟沿立即更新,非阻塞赋值在时钟沿后统一更新。
- 验证预期:阻塞赋值移位寄存器在第一个时钟沿后所有位同时变化(并非真正移位),非阻塞赋值则正确实现逐位移位。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | 任意 FPGA(如 Xilinx Artix-7) | 纯仿真验证,无硬件依赖 | Altera Cyclone IV |
| EDA 版本 | Vivado 2023.1 或 Quartus Prime 20.1 | 行为仿真功能一致 | Modelsim / Questa / VCS |
| 仿真器 | Vivado Simulator | 内置于 Vivado,无需额外安装 | Modelsim / Questa / VCS |
| 时钟/复位 | 时钟周期 10 ns,异步复位高有效 | 时钟周期可调,复位极性可改 | — |
| 接口依赖 | 无外部接口 | 纯仿真验证,无需约束文件 | — |
注意:仿真无需添加时序约束;若后续进行综合,则需添加时钟约束。
目标与验收标准
- 功能点:实现 3 位移位寄存器,输入 din 在时钟驱动下逐位右移。
- 性能指标:无(纯教学对比)。
- 验收方式:
非阻塞赋值波形:q0 在时钟沿后变为 din,q1 在下一时钟沿后变为 q0 旧值,q2 类似,呈现逐位移位。
阻塞赋值波形:所有 q 位在第一个时钟沿后同时变为 din,后续时钟沿无变化(因为 q0、q1、q2 在同一时刻被赋值为同一个值)。
实施步骤
1. 工程结构与代码
创建两个 Verilog 模块,分别使用阻塞赋值和非阻塞赋值实现 3 位移位寄存器。
// 阻塞赋值移位寄存器(错误示范)
module shift_reg_blocking (
input clk,
input rst,
input din,
output reg [2:0] q
);
always @(posedge clk or posedge rst) begin
if (rst)
q <= 3'b0;
else begin
q[0] = din; // 阻塞赋值
q[1] = q[0]; // 立即使用 q[0] 的新值
q[2] = q[1]; // 立即使用 q[1] 的新值
end
end
endmodule
// 非阻塞赋值移位寄存器(正确示范)
module shift_reg_nonblocking (
input clk,
input rst,
input din,
output reg [2:0] q
);
always @(posedge clk or posedge rst) begin
if (rst)
q <= 3'b0;
else begin
q[0] <= din; // 非阻塞赋值
q[1] <= q[0]; // 使用 q[0] 的旧值
q[2] <= q[1]; // 使用 q[1] 的旧值
end
end
endmodule关键机制分析:
- 阻塞赋值:语句顺序执行。q[0] 先被赋值为 din,然后 q[1] 立即使用 q[0] 的新值,q[2] 也立即使用 q[1] 的新值。最终所有位在同一时钟沿被赋值为 din,失去移位功能。
- 非阻塞赋值:所有右值在时钟沿被同时采样(读取旧值),然后在当前仿真时间步的末尾统一更新左值。因此 q[1] 使用 q[0] 的旧值,q[2] 使用 q[1] 的旧值,正确实现移位。
2. 编写 Testbench
module tb_shift_reg;
reg clk, rst, din;
wire [2:0] q_blk, q_nonblk;
shift_reg_blocking u_blk (.clk(clk), .rst(rst), .din(din), .q(q_blk));
shift_reg_nonblocking u_nonblk (.clk(clk), .rst(rst), .din(din), .q(q_nonblk));
initial begin
clk = 0;
forever #5 clk = ~clk;
end
initial begin
rst = 1; din = 0;
#20 rst = 0;
#10 din = 1;
#20 din = 0;
#50 $finish;
end
endmodule3. 仿真与波形观察
运行仿真后,观察波形:
- 非阻塞赋值波形:
在第一个时钟上升沿(t=30 ns),q_nonblk[0] 变为 1,q_nonblk[1] 和 q_nonblk[2] 保持 0;
第二个时钟沿(t=40 ns),q_nonblk[1] 变为 1,q_nonblk[2] 仍为 0;
第三个时钟沿(t=50 ns),q_nonblk[2] 变为 1。正确实现移位。 - 阻塞赋值波形:
在第一个时钟上升沿(t=30 ns),q_blk[0]、q_blk[1]、q_blk[2] 同时从 0 变为 1,之后保持不变。未实现移位。
验证结果
| 指标 | 阻塞赋值 | 非阻塞赋值 |
|---|---|---|
| 移位功能 | 失败(所有位同时变化) | 正确(逐位移位) |
| 仿真行为与硬件一致性 | 不一致 | 一致 |
| 综合后资源(LUT/FF) | 可能相同(取决于综合工具) | 正确 |
测量条件:Vivado 2023.1 行为仿真,时钟周期 10 ns,输入 din 在 30 ns 时变为 1。
波形特征:非阻塞赋值波形显示 q[0]、q[1]、q[2] 依次在连续时钟沿变化,延迟一个时钟周期。阻塞赋值波形显示所有位在同一时钟沿同时变化。
故障排查(Troubleshooting)
| 现象 | 原因 | 检查点 | 修复建议 |
|---|---|---|---|
| 所有寄存器同时变化 | 使用了阻塞赋值 | 检查 always 块中赋值符号 | 改为非阻塞赋值 |
| 寄存器值不确定(X) | 未初始化或复位未正确施加 | 检查复位信号和 initial 块 | 在 Testbench 中给寄存器赋初值或确保复位有效 |
| 信号出现毛刺 | 组合逻辑中使用了非阻塞赋值 | 检查组合逻辑 always 块 | 改为阻塞赋值 |
| 综合后功能正确,但仿真错误 | 仿真与综合语义差异 | 确认是否遵循编码规范 | 严格区分时序/组合逻辑赋值方式 |
| 信号延迟多个时钟周期 | 非阻塞赋值在多个 always 块中链式传递 | 检查跨时钟域逻辑 | 使用同步器或调整设计 |
| 仿真运行缓慢 | 仿真时间步过长或无限循环 | 检查 Testbench 中的 forever 语句 | 添加 $finish 或限制仿真时间 |
原理与设计说明
为什么非阻塞赋值是时序逻辑的标准?
Verilog 仿真基于事件驱动。非阻塞赋值(<=)将赋值事件分为两步:在时钟沿计算右值(读取旧值),然后在当前仿真时间步的末尾统一更新左值。这模拟了硬件中寄存器在时钟沿采样输入、并在时钟沿后输出的行为。阻塞赋值(=)则立即计算并更新,类似于组合逻辑的连续赋值。
关键矛盾:在时序逻辑中,如果使用阻塞赋值,多个赋值语句的顺序执行会导致硬件中不存在的“瞬时传播”,产生错误的仿真行为。例如,移位寄存器中所有位同时更新,而非逐位移位。
可执行方案:
- 时序逻辑(always @(posedge clk))中始终使用非阻塞赋值。
- 组合逻辑(always @(*))中始终使用阻塞赋值。
- 避免在同一个 always 块中混合两种赋值方式。
风险边界:即使代码风格错误,综合工具也可能生成正确的硬件(因为综合工具忽略赋值顺序,只关注逻辑关系),但仿真结果会与硬件行为不一致,导致验证失败。因此,必须严格遵守编码规范,确保仿真与硬件一致。
扩展与下一步
- 参数化移位寄存器:使用
parameter定义位宽,使代码可重用。 - 加入异步复位同步释放:在移位寄存器中实现异步复位同步释放,避免亚稳态。
- 实现双向移位:添加左移/右移控制信号。
- 使用 SystemVerilog:使用
always_ff和always_comb明确区分时序和组合逻辑。 - 加入断言:在 Testbench 中使用
assert检查移位正确性。
参考与信息来源
- IEEE Std 1364-2005, Verilog Hardware Description Language.
- Clifford E. Cummings, "Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!", SNUG 2000.
- Xilinx Vivado Design Suite User Guide: Simulation (UG937).
技术附录
术语表
- 阻塞赋值(=):立即计算并更新左值,后续语句使用新值。
- 非阻塞赋值(<=):在时钟沿计算右值,在当前仿真时间步末尾统一更新左值。
- 仿真时间步:仿真器处理事件的最小时间单位。
检查清单
- [ ] 时序逻辑 always 块中只使用非阻塞赋值。
- [ ] 组合逻辑 always 块中只使用阻塞赋值。
- [ ] 不在同一个 always 块中混合两种赋值。
- [ ] Testbench 中提供正确的时钟和复位。
- [ ] 仿真波形与预期行为一致。
关键约束速查
| 场景 | 推荐赋值方式 |
|---|---|
| 时序逻辑(DFF) | <= (非阻塞) |
| 组合逻辑(LUT) | = (阻塞) |
| 锁存器(Latch) | 避免,若必须用则用 = |
| Testbench 中时钟生成 | = (阻塞) 或 forever |



