当前位置: 首页 > article >正文

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

相关阅读

Verilog基础icon-default.png?t=O83Ahttps://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


http://www.kler.cn/news/328564.html

相关文章:

  • 车辆重识别(注意力 U-Net:学习在哪些区域寻找胰腺)论文阅读2024/10/01
  • 单体到微服务架构服务演化过程
  • 无人机跟踪
  • [网络]抓包工具介绍 tcpdump
  • 15.面试算法-树基础
  • 【深度学习】低维向量映射到高维空间的方法
  • 服装分类检测系统源码分享
  • 数据库重建索引的作用?
  • 编码能力提升计划 - 华为OD统一考试(E卷)
  • 2025届CS保研经验简帖
  • C#语言的历史与发展:从基础到高级的全面解析
  • Spring Boot技术在足球青训管理中的创新应用
  • 深入理解 WebSocket:实时通信的利器
  • 从基础到进阶:Docker 实践与应用的全方位解析
  • 维修保养记录接口-维修保养记录API-汽车接口
  • 【DAY20240927】经典深度学习模型对比:LeNet5、CNN、ResNet20、AlexNet、TextCNN 与 VGG-11
  • list的模拟实现
  • MySQL SQL基础常见面试题整理
  • PHP反序列化3(属性绕过)
  • 《PMI-PBA认证与商业分析实战精析》 第3章 需要评估
  • Typora使用与Markdown语法详细教程
  • 在Windows上安装Git
  • go+redis基于tcp实现聊天室
  • 8--苍穹外卖-SpringBoot项目中套餐管理 详解(二)
  • XR图和XS图中X-bar图的最大不同
  • uniapp 常用高度状态栏,导航栏,tab栏,底部安全高度
  • C++ 游戏开发
  • Docker 安装 Citus 单节点集群:全面指南与详细操作
  • Linux 文件目录结构(详细)
  • 【PostgreSQL】入门篇——如何创建、删除和管理数据库及其用户,包括权限设置和角色管理