Quick Start
- 准备任意一款FPGA开发板(如Xilinx Artix-7)或仿真工具(Vivado Simulator / ModelSim)。
- 创建两个Verilog模块:一个用阻塞赋值(=)实现移位寄存器,另一个用非阻塞赋值(<=)实现相同功能。
- 编写测试平台,观察两种赋值方式在仿真波形上的差异。
- 对比综合后的RTL网表,确认硬件实现是否一致。
前置条件
- 熟悉Verilog基本语法,包括always块和敏感列表。
- 已安装并配置好FPGA仿真工具(如Vivado Simulator 2020.1以上版本或ModelSim SE-64 10.6c)。
- 具备基本数字电路知识,了解寄存器、组合逻辑和时序逻辑的区别。
目标与验收标准
- 理解阻塞赋值与非阻塞赋值的核心语义差异:阻塞赋值立即更新,非阻塞赋值在块结束时统一更新。
- 掌握两种赋值方式在组合逻辑与时序逻辑中的正确用法,避免常见仿真-综合不匹配问题。
- 能够通过仿真波形识别因赋值方式错误导致的竞争冒险或功能错误。
- 验收标准:在仿真中,阻塞赋值移位寄存器的输出会出现非预期的中间值,而非阻塞赋值版本则正确实现移位功能;综合后两者网表结构相同。
实施步骤
步骤1:编写阻塞赋值移位寄存器模块
module shift_reg_blocking (
input clk,
input rst_n,
input din,
output reg [3:0] dout
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
dout = 4'b0;
else begin
dout[0] = din;
dout[1] = dout[0]; // 阻塞赋值:立即使用新值
dout[2] = dout[1];
dout[3] = dout[2];
end
end
endmodule此模块在时钟上升沿触发,但阻塞赋值导致赋值语句按顺序执行:dout[0]先被更新为din,随后dout[1]读取已更新的dout[0],依此类推。仿真中,一个时钟周期内所有位同时完成移位,但实际硬件中寄存器更新需要时间,因此仿真行为与硬件行为不一致。
步骤2:编写非阻塞赋值移位寄存器模块
module shift_reg_nonblocking (
input clk,
input rst_n,
input din,
output reg [3:0] dout
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
dout <= 4'b0;
else begin
dout[0] <= din;
dout[1] <= dout[0]; // 非阻塞赋值:使用旧值
dout[2] <= dout[1];
dout[3] <= dout[2];
end
end
endmodule非阻塞赋值在always块结束时统一更新,因此所有右值都使用进入always块时的旧值。仿真行为正确模拟了硬件寄存器在时钟边沿同时采样的特性。
步骤3:编写测试平台并运行仿真
module tb_shift_reg;
reg clk, rst_n, din;
wire [3:0] dout_block, dout_nonblock;
shift_reg_blocking u_block (.clk(clk), .rst_n(rst_n), .din(din), .dout(dout_block));
shift_reg_nonblocking u_nonblock (.clk(clk), .rst_n(rst_n), .din(din), .dout(dout_nonblock));
initial begin
clk = 0;
forever #5 clk = ~clk;
end
initial begin
rst_n = 0; din = 0;
#20 rst_n = 1;
#10 din = 1;
#10 din = 0;
#40 $finish;
end
endmodule运行仿真后,观察波形:阻塞赋值版本中,dout_block在时钟上升沿后立即变为全1(因为顺序赋值导致所有位同时更新),而非阻塞赋值版本中,dout_nonblock逐位移位,符合预期。
验证结果
- 仿真结果:阻塞赋值模块在单个时钟周期内完成所有移位,产生“瞬移”现象;非阻塞赋值模块正确实现逐位移位。
- 综合结果:两个模块综合后的网表相同,均为4个D触发器级联。因为综合工具忽略赋值方式,仅根据逻辑功能推断硬件。
- 关键发现:仿真与综合的不匹配是阻塞赋值在时序逻辑中使用的典型陷阱。非阻塞赋值保证了仿真行为与硬件行为一致。
排障指南
- 问题:仿真波形出现毛刺或意外跳变。
原因:组合逻辑中使用了非阻塞赋值,导致赋值延迟。
解决:组合逻辑always块中统一使用阻塞赋值。 - 问题:仿真结果与硬件实测不一致。
原因:时序逻辑中使用了阻塞赋值,仿真行为与硬件行为不同。
解决:时序逻辑always块中统一使用非阻塞赋值。 - 问题:综合后功能正确但仿真失败。
原因:混合使用阻塞和非阻塞赋值导致仿真竞争。
解决:遵循“组合逻辑用阻塞,时序逻辑用非阻塞”的黄金法则。
扩展:深入理解赋值机制
阻塞赋值与非阻塞赋值的本质差异源于Verilog的仿真事件调度机制。在仿真中,每个时间槽(time slot)分为多个区域:活跃区(active)、非活跃区(inactive)、NBA区(non-blocking assignment update)等。阻塞赋值在活跃区立即执行,而非阻塞赋值的右值计算在活跃区完成,但左值更新推迟到NBA区。这种调度机制使得非阻塞赋值天然适用于模拟寄存器行为,而阻塞赋值适用于组合逻辑的连续赋值。
风险边界:在同一个always块中混合使用阻塞和非阻塞赋值可能导致不可预测的仿真行为。例如,在时序逻辑中,如果部分信号用阻塞赋值、部分用非阻塞赋值,仿真结果可能依赖于语句顺序,而综合结果则完全由逻辑功能决定,造成仿真-综合不一致。此外,在多个always块中访问同一变量时,必须确保赋值方式一致,否则会产生竞争条件。
参考资源
- IEEE Std 1364-2001, Verilog Hardware Description Language, Section 9.2: Blocking and non-blocking assignments.
- Clifford E. Cummings, "Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!", SNUG 2000.
- Xilinx UG901: Vivado Design Suite User Guide, Synthesis, Chapter 3: Coding Guidelines.
附录:常见错误模式与修正
- 错误模式1:在时序逻辑中使用阻塞赋值实现计数器。
修正:改用非阻塞赋值,或使用“dout <= dout + 1”形式。 - 错误模式2:在组合逻辑中使用非阻塞赋值实现多路选择器。
修正:改用阻塞赋值,或使用assign连续赋值语句。 - 错误模式3:在同一个always块中同时使用阻塞和非阻塞赋值更新同一变量。
修正:拆分为两个always块,或统一赋值方式。



