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

相关阅读

Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm=1001.2014.3001.5482


作为一个硬件描述语言,Verilog HDL常常需要使用语句描述并行执行的电路,但其实在仿真器的底层,这些并行执行的语句是有先后顺序的,然而Verilog标准并没有将这些事件调度的顺序定死,而是给予了仿真器厂商一定的自由去实现自己的产品,这就导致了设计者如果不遵循一定的编程习惯,会导致意想不到的仿真结果,下面是一些相关的规则。

4、描述时序逻辑时使用非阻塞赋值

首先以一个三级触发器为例说明描述时序逻辑时使用非阻塞赋值,其电路如图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} = 3'b111; 
    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} <= 3'b111; 
    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 <= 1'b1; 
      q2 <= 1'b1;
      q3 <= 1'b1;
    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 <= 1'b1; 
      q2 <= 1'b1;
      q1 <= 1'b1;
    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 <= 1'b1;
    end
    else begin
      q3 <= q2;
    end   

  always @(posedge clk or negedge pre_n)  
    if (!pre_n) begin
      q2 <= 1'b1;
    end
    else begin
      q2 <= q1^q3;
    end 

  always @(posedge clk or negedge pre_n)  
    if (!pre_n) begin
      q1 <= 1'b1;
    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 <= 1'b1;
    end
    else begin
      q1 <= q3; 
    end 

  always @(posedge clk or negedge pre_n)  
    if (!pre_n) begin
      q2 <= 1'b1;
    end
    else begin
      q2 <= q1^q3;
    end 

  always @(posedge clk or negedge pre_n)  
    if (!pre_n) begin
      q3 <= 1'b1;
    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

相关推荐
szxinmai主板定制专家1 小时前
基于 PC 的控制技术+ethercat+linux实时系统,助力追踪标签规模化生产,支持国产化
arm开发·人工智能·嵌入式硬件·yolo·fpga开发
坏孩子的诺亚方舟8 小时前
modelsim基础2_modelsim仿真分析
fpga·modelsim·仿真分析
tiger1198 小时前
FPGA 在大模型推理中的应用
人工智能·llm·fpga·大模型推理
我爱我家diyer11 小时前
pwm控制器的默认输出状态
stm32·硬件工程
恒锐丰小吕12 小时前
屹晶微 EG2003 中压200V半桥驱动芯片技术解析
嵌入式硬件·硬件工程
博览鸿蒙12 小时前
如何为春招的金三银四做好准备
fpga开发
FPGA小迷弟13 小时前
FPGA处理图像需要用到的主流接口详解
学习·fpga开发·verilog·fpga·modelsim
恒锐丰小吕13 小时前
屹晶微 EG2121 中压250V半桥驱动芯片技术解析
嵌入式硬件·硬件工程
LeoZY_13 小时前
CH347 USB转JTAG功能使用笔记:CH347根据SVF文件实现任意FPGA下载
笔记·stm32·嵌入式硬件·fpga开发·硬件架构·硬件工程
博览鸿蒙13 小时前
FPGA在高性能计算中的应用:数据流加速与优化
fpga开发