资讯详情

Verilog基础:时序调度中的竞争(四)(描述时序逻辑时使用非阻塞赋值)

📅 2026/10/27 9:27:56 | 华诺云谱 👁 阅读
Verilog基础:时序调度中的竞争(四)(描述时序逻辑时使用非阻塞赋值)
相关阅读Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482作为一个硬件描述语言Verilog HDL常常需要使用语句描述并行执行的电路但其实在仿真器的底层这些并行执行的语句是有先后顺序的然而Verilog标准并没有将这些事件调度的顺序定死而是给予了仿真器厂商一定的自由去实现自己的产品这就导致了设计者如果不遵循一定的编程习惯会导致意想不到的仿真结果下面是一些相关的规则。描述时序逻辑时使用非阻塞赋值首先以一个三级触发器为例说明描述时序逻辑时使用非阻塞赋值其电路如图1所示。图1 一个触发器组例1如果使用例1所示的阻塞赋值顺序执行的阻塞赋值会导致d在时钟上升沿被直接传递到q3最后的仿真结果图2和综合结果图3都只有一级触发器。# 例1 module example1 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always (posedge clk) begin q1 d; q2 q1; q3 q2; end endmodule图2 例1的仿真结果图3 例1的综合结果例2例2将例1中的三个阻塞赋值重排序了以描述一个三级触发器的行为q3首先得到q2的值随后q2再得到q1的值最后才更新q1最后的仿真结果图4和综合结果图5都是三级触发器。# 例2 module example2 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always (posedge clk) begin q3 q2; q2 q1; q1 d; end endmodule图4 例2的仿真结果图5 例2的综合结果虽然看起来很完美但例2其实是有问题的如果在该模块后某个触发器采样了q3或与q3有关的组合逻辑则此时q3可能使用未更新的值正确也可能使用已更新的值错误下面的例3说明了这种情况。例3例3在三级触发器后又加了一个触发器在时钟上升沿时q3的值会更新同时q4的值也会谁先执行是一个取决于仿真器的未定义行为。如果q4先更新则q4得到的是q3旧值正确如果q3先更新则q4得到的是q3新值错误如图6的仿真结果所示。即使图7所示的综合结果是正确的但这会造成前仿和后仿的不一致。# 例3 module example3 (q4, d, clk); output [7:0] q4; input [7:0] d; input clk; reg [7:0] q4, q3, q2, q1; always (posedge clk) begin q3 q2; q2 q1; q1 d; end always (posedge clk) begin q4 q3; // q4 q3; 这两种赋值都会导致竞争 end endmodule图6 例3的仿真结果一种可能错误图7 例3的综合结果例4如果理解了上面的例3那么将例3拆成三个always块的例4毫无疑问是一种会导致前仿和后仿不一致的写法因为不同always块的执行顺序是不确定的。图8展示的仿真结果表示该仿真器选择从下到上执行这三个always块因此得到了和例1一样的结果。从图9所示的综合结果来看是正确的。# 例4 module example4 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always (posedge clk) begin q3 q2; end always (posedge clk) begin q2 q1; end always (posedge clk) begin q1 d; end endmodule图8 例4的仿真结果一种可能错误图9 例4的综合结果例5例5在例4的基础上将always块的顺序调换了图10展示的仿真结果表示该仿真器选择从下到上执行这三个always块因此得到了和例2一样的结果。从图11所示的综合结果来看是正确的。# 例5 module example5 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always (posedge clk) begin q1 d; end always (posedge clk) begin q2 q1; end always (posedge clk) begin q3 q2; end endmodule图10 例5的仿真结果一种可能正确图11 例5的综合结果上面四种使用阻塞赋值的方法中只有一种能保证仿真结果正确即使三种的综合结果是正确的。例6例6以非阻塞赋值重写了例1由于非阻塞赋值分两步执行首先是右侧表达式值的计算在当前仿真时间的最后才将右侧表达式值赋值给左值。因此q2得到的是q1的旧值而q3得到的也是q2的旧值如仿真结果图12所示这时的综合结果如图13所示也是正确的。# 例6 module example6 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always (posedge clk) begin q1 d; q2 q1; q3 q2; end endmodule图12 例6的仿真结果图13 例6的综合结果例7例7以非阻塞赋值重写了例2但仿真结果和综合结果依旧如图12和图13所示因为此时所有值的更新都是在最后进行的不会影响右侧表达式的计算结果。# 例7 module example7 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always (posedge clk) begin q3 q2; q2 q1; q1 d; end endmodule例8例8以非阻塞赋值重写了例4虽然不同always块的执行顺序是不确定的但这只表示右侧表达式值的计算顺序是不确定的右侧表达式值赋值给左值的顺序是不确定的这不会对结果有任何影响所有右侧表达式值赋值给左值还是发生在右侧表达式值的计算前。仿真结果和综合结果依旧如图12和图13所示。# 例8 module example8 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always (posedge clk) begin q3 q2; end always (posedge clk) begin q2 q1; end always (posedge clk) begin q1 d; end endmodule例9例9以非阻塞赋值重写了例5与例8同理仿真结果和综合结果依旧如图12和图13所示。# 例9 module example9 (q3, d, clk); output [7:0] q3; input [7:0] d; input clk; reg [7:0] q3, q2, q1; always (posedge clk) begin q1 d; end always (posedge clk) begin q2 q1; end always (posedge clk) begin q3 q2; end endmodule上面四种使用非阻塞赋值的方法中全部能保证仿真结果和综合结果正确。上面的九个例子说明了在描述时序逻辑时最好使用非阻塞赋值。例10例10展示了一个使用阻塞赋值实现的线性反馈移位寄存器(LFSR)关于这种结构的详细介绍可见数字IC前端学习笔记LFSR线性反馈移位寄存器。module example10 (q3, clk, pre_n); output q3; input clk, pre_n; reg q3, q2, q1; always (posedge clk or negedge pre_n) if (!pre_n) {q3,q2,q1} 3b111; else {q3,q2,q1} {q2,(q1^q3),q3}; endmodule例10将所有的赋值写在了一行保证了赋值的正确但这种风格是不建议的会让debug变得更加复杂。可以发现例10无法使用例2中将阻塞赋值重排序的方法实现因为其是有互相依赖即q3依赖q2而q2依赖于q3。而且例10仍然有例3所示的前仿和后仿不一致的问题。例11例11以非阻塞赋值重写了例10解决了例10存在的前仿和后仿不一致的问题。module example11 (q3, clk, pre_n); output q3; input clk, pre_n; reg q3, q2, q1; always (posedge clk or negedge pre_n) if (!pre_n) {q3,q2,q1} 3b111; else {q3,q2,q1} {q2,(q1^q3),q3}; endmodule例12例12将例11拆成了一个always块中的三个非阻塞赋值仿真结果和综合结果和例11一致。module example12 (q3, clk, pre_n); output q3; input clk, pre_n; reg q3, q2, q1; always (posedge clk or negedge pre_n) if (!pre_n) begin q1 1b1; q2 1b1; q3 1b1; end else begin q1 q3; q2 q1^q3; q3 q2; end endmodule例13例13将例12中的三个非阻塞赋值重排序了仿真结果和综合结果和例11一致。module example13 (q3, clk, pre_n); output q3; input clk, pre_n; reg q3, q2, q1; always (posedge clk or negedge pre_n) if (!pre_n) begin q3 1b1; q2 1b1; q1 1b1; end else begin q3 q2; q2 q1^q3; q1 q3; end endmodule例14例14将例11拆成了三always块仿真结果和综合结果和例11一致。module example14 (q3, clk, pre_n); output q3; input clk, pre_n; reg q3, q2, q1; always (posedge clk or negedge pre_n) if (!pre_n) begin q3 1b1; end else begin q3 q2; end always (posedge clk or negedge pre_n) if (!pre_n) begin q2 1b1; end else begin q2 q1^q3; end always (posedge clk or negedge pre_n) if (!pre_n) begin q1 1b1; end else begin q1 q3; end endmodule例15例15在例14的基础上将always块的顺序调换了仿真结果和综合结果和例11一致。module example15 (q3, clk, pre_n); output q3; input clk, pre_n; reg q3, q2, q1; always (posedge clk or negedge pre_n) if (!pre_n) begin q1 1b1; end else begin q1 q3; end always (posedge clk or negedge pre_n) if (!pre_n) begin q2 1b1; end else begin q2 q1^q3; end always (posedge clk or negedge pre_n) if (!pre_n) begin q3 1b1; end else begin q3 q2; end endmodule本文是基于《CUMMINGS, Clifford E., et al. Nonblocking assignments in verilog synthesis, coding styles that kill!.SNUG (Synopsys Users Group) 2000 User Papers, 2000. 》的进一步阐述感谢Clifford E. Cummings对此做出贡献。原文链接http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA.pdf
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑