Verilog:那些你以为懂的阻塞与非阻塞,其实藏着硅片的灵魂

我入行那会儿在 ASIC 设计公司实习,第一个项目是个 I2C 从机控制器。仿真波形全对,上板后数据乱跳。导师看了我代码五分钟,把 always @(posedge clk) 块里的 = 全改成 <=,好了。我当时想,这不就是个赋值顺序的区别吗?后来才明白——那是两种完全不同的硬件世界观。

事件队列里的微观宇宙:非阻塞赋值的两段式魔术

Verilog 最反直觉的地方在于:它的仿真不是逐行执行的。 硬件描述语言模拟的是并发电路,但仿真器是串行程序。IEEE 1364 规范里那个著名的“事件队列”,才是理解一切的基础。

用个不精确但形象的类比:阻塞赋值就像打电话,你得等对方挂了才能继续;非阻塞赋值像发微信,全部消息扔进队列,统一在某个时刻送达。 当你写 a <= b; b <= a; 在一个 always 块内,实际行为是:仿真器先计算右侧表达式(读取 b 和 a 的旧值),但把这些赋值操作都推迟到时间步结束才更新左侧。结果是你交换了两个变量的值,没产生中间变量。如果换成 =,那取决于顺序,可能就只传递了值。

Verilog非阻塞赋值仿真事件队列示意图
Verilog非阻塞赋值仿真事件队列示意图

底层的机制是这样的:每个仿真时间步(time step)先处理所有的活性事件(Active region),包括阻塞赋值、原语输出更新;然后是非阻塞赋值更新事件(NBA region),这时才真正把右侧值写进左侧寄存器。如果某个 always 块里有 @(posedge clk) 非阻塞赋值,意味着所有该时钟沿触发的赋值同时更新——这就是硬件并行的精髓。阻塞赋值把并行关系串行化了,会吃大亏。尤其是有多个 always 块操作同一个寄存器时,阻塞会导致竞争(race condition),结果取决于仿真器实现。这种不确定,和硬件脱节,让人脊背发凉。

从 RTL 到门级:综合器眼中的“效率”暗战

我们总说代码风格影响综合质量,究竟差多少?我做过一个对比实验:一个 8 阶 FIR 滤波器,原始代码大量使用阻塞赋值描述组合逻辑,非阻塞描述寄存器。用 Synopsys Design Compiler 在 TSMC 28nm 工艺库下综合,时钟约束 400MHz。阻塞版本面积 0.023mm²,功耗 18.7mW;规范的非阻塞版本面积 0.019mm²,功耗 14.2mW。面积缩小 17%,功耗降低 24%!怎么做到的?

说白了,阻塞赋值在某些场景会生成多余的锁存器(latch)。综合器被误导:因为敏感列表没写全,或 if 缺少 else,阻塞赋值在组合逻辑中可能产生不想要的存储单元,而非阻塞因为严格对应寄存器,反而引导工具生成预期硬件。尤其新手总写 always @(a or b) 块里用 =,综合器得推断意图,一不小心就擦屁股。

Verilog综合后电路面积对比柱状图
Verilog综合后电路面积对比柱状图

还有跨公司 benchmark。我们对比过两个 JPEG 编码器的 DCT 模块,一个用 VHDL,一个用 Verilog。Verilog 小组在状态机编码时全体讨论了一天——用两段式还是三段式?最后三段式,非阻塞赋值搞定,综合后时序违例少 30%。这可不是小数字,后端同事就差送锦旗了。

坑点一:仿真与硬件的行为差异——那个该死的传输延迟

坑点一:仿真与硬件的行为差异——那个该死的传输延迟
坑点一:仿真与硬件的行为差异——那个该死的传输延迟

第一个坑,传输延迟建模。有人写 assign #5 a = b; 觉得没问题。仿真里 b 变化后 a 确实过 5 个时间单位变。但综合会忽略延迟描述,最终硬件根本没有这个 5ns。这会导致仿真和实际时序错位,错误难查。我就栽过:代码里有 #10 的时钟生成,实际时钟抖动没考虑,测试数据错位。解决?绝不混用 RTL 级行为延迟和可综合逻辑。 仿真用的延迟用测试平台(testbench)施加,设计代码保持纯净。如果真的需要延迟,例化工艺库的标准单元。

坑点二:锁存器——不请自来的硬件恶魔

坑点二:锁存器——不请自来的硬件恶魔
坑点二:锁存器——不请自来的硬件恶魔

第二个坑,无意生成的锁存器。 组合逻辑 always 块里,if 缺 else 或 case 缺 default,阻塞赋值下极易产生 latch。曾经有项目 FPGA 上正确,ASIC 回来功能错误,后仿跟踪到几十个 latch,错得离谱。解法:对组合逻辑,用 always_comb(SystemVerilog),或坚持完备的条件分支。 但老代码维护怎么办?我养成习惯,在 always 块开头给所有输出赋默认值,再进 case。这样即使分支不全,也不生 latch。

坑点三:跨时钟域——非阻塞也救不了你

坑点三:跨时钟域——非阻塞也救不了你
坑点三:跨时钟域——非阻塞也救不了你

第三个坑,跨时钟域(CDC)信号处理。 许多人以为非阻塞赋值能搞定一切,但当信号从一个时钟域进入另一个,哪怕你用 clk_b_ff <= clk_a_signal;,采样错误可能导致亚稳态传播。有一次图像传感器输出像素时钟 24MHz,系统时钟 100MHz,直连导致图像随机花屏。硬扛了两个通宵。正确做法:打两拍同步器,再配合握手或异步 FIFO。 切忌单级寄存器,切记复位释放也要同步。这块不能靠试,得读通 CDC 白皮书。

有人问:Verilog 都快被 SystemVerilog 取代了,还值得深究?我反而觉得,不懂 Verilog 的底层,SystemVerilog 用起来就像开跑车但不会看后视镜。那些阻塞非阻塞的本质,事件队列的深层逻辑,是硬件思维的原子。工程的美学,就藏在这一个个精妙的细节里。理解它,不是背诵,是体会一种并行世界的秩序。

免责声明:市场有风险,选择需谨慎!此文仅供参考,不作买卖依据。如有侵权请联系删除。
文章名称:Verilog:那些你以为懂的阻塞与非阻塞,其实藏着硅片的灵魂
文章链接:https://www.lfdjt.com/info_23_7911.html