关于verilog里阻塞与非阻塞赋值的个人理解

        最近在做数字的东西,因此一直在学习verilog的语法,看的是夏宇闻老师的《verilog数字系统设计教程》这本书,在看到第14章深入理解阻塞与非阻塞赋值的不同时,结合书后面的誓言RISC_CPU,关于时序问题,产生了一些疑问,因此写了一个简单的程序,探索一下相关的内容,文笔拙劣,理解也并不完全正确,想写出来与大家分享一下,希望能够得到一些指点。

先引用书上的两个例子:

1.采用阻塞赋值,不能自行触发的振荡器

module osc(clk);
  output clk;
  reg clk;
  initial #10 clk = 0;
  always @(clk)
    begin
      $display("At%tns, be excuted.", $time);//just for test.
      #10;
      clk = ~clk;
    end
endmodule

        在initial块中,经过10个单位的延迟,clk被立即阻塞赋值为0。当clk电平从不定态变为0的事件发生时,使always块的@(clk)条件触发,经过10个单位时间的延迟,计算RHS表达式(~clk)得到1,并立即更新LHS的值,clk立即被赋予1。由于在此期间不允许其他语句的干扰,即使always循环回到判断触发条件@(clk),由于此时clk电平已经为1,无法感知从0到1曾经发生过的变化,所以就阻塞在那里,只有等待clk变为0才能进入该always块,因此,这是一个不能自触发的振荡器,不能产生时钟波形。

2. 采用非阻塞赋值的自触发振荡器

module osc(clk);
  output clk;
  reg clk;
  initial #10 clk = 0;
  always @(clk)
    begin
      $display("At%tns, be excuted.", $time);//just for test.
      #10;
      clk <= ~clk;
    end
endmodule

        与上例相比,只是赋值方式有"="变为"<="。@(clk)的第一次触发之后,非阻塞赋值的RHS表达式便计算出来,并把值赋给LHS的事件安排在更新事件队列中。在非阻塞赋值更新事件队列被激活之前,又遇到@(clk)触发语句,并且always块再次对clk的变化产生反应。当非阻塞LHS的值在同一时刻被更新时,@(clk)再一次触发。该例是自触发方式,虽例中的代码能产生周期时钟信号,但在编写仿真测试模块时,不推荐该写法。
 

关于阻塞与非阻塞的实践:

1. 一个简单的时钟分频模块

module delayornot(fetch, clk, rst);
  output fetch;
  input clk, rst;
  reg fetch;
  integer i;
  always @(posedge clk or negedge rst)
    begin
      if (!rst)
        begin
          fetch <= 0;
          i <= 0;
        end
      else
        begin
          if (i == 4)// divided by 5.
            begin
              fetch <= ~fetch;
              i <= 0;
            end
          else
            i <= i+1;
        end
    end 
endmodule

2. 测试模块

module delay_tb();

  wire fetch;
  reg clk, rst;
  reg [3:0] num1;
  reg [3:0] num2;
  reg [3:0] num3;
  
  initial
    begin
      num1 = 4'b0;
      num2 = 4'b0;
      num3 = 4'b0;
      clk = 0;
      rst = 1;
      #80;
      rst = 0;
      #80;
      rst = 1;
      #500;
    end
  
  always #40 clk = ~clk;
    
  delayornot mydelay(fetch, clk, rst);

  always @(posedge fetch)
    begin
      num1 <= num1 + 1;
    end
    
  always @(posedge fetch)
    begin
      if (fetch)
        num2 <= num2 + 1;
    end
    
 always @(posedge clk)
    begin
      $display("At%tns, be excuted.",$time);//just for test.
      if (fetch)
        begin
          num3 <= num3 + 1;
        end
    end

3. 仿真结果

        从仿真图中可以看到,delayornot模块实际上是一个5分频的模块,fetch信号采用非阻塞赋值方式。

        在当fetch信号上升沿到来时,由于num1和num2所在always块触发条件为fetch的上升沿,因此在fetch上升沿,触发了num1和num2所在的always块,因此各自加1。而num3所在的always块触发条件为clk信号的上升沿,注意图中fetch信号上升沿到来的位置。此时,clk信号为上升沿,因此触发了num3的always块。但注意,此时,由于fetch信号在其模块中为非阻塞赋值,即等fetch信号相应always块结束时,才进行赋值操作。因此,此时fetch信号为低电平,即if (fetch)不成立,因此num3未进行加1操作。num3在下一个clk上升沿时才加1,并且在fetch下降时来临时依然加1,原因类似。

       可能有人会疑问,那为什么num2中的if (fetch)就成立了呢?因为,num2所在always块检测的是fetch信号的上升沿,当其上升沿到来时,fetch已经为高电平,因此条件成立。verilog里很重要的一点是,若不加#10等延时命令,则指令执行往往有概念上的先后,而并无实质上的先后。通俗的讲,fetch信号上升沿到来时,num1和num2所在always块才被触发,而num3所在always块检测的是clk的上升沿,也就是说在fetch信号被赋值之前该always块就已经被触发执行了,因此与fetch信号的上升沿无关。

4. 修改num3所在always块代码

always @(*)
    begin
      $display("At%tns, be excuted.",$time);//just for test.
      if (fetch)
        begin
          num3 <= num3 + 1;
        end
    end

        always @(*)的作用与always @(fetch or num3)的作用一致。(*)里面的敏感变量由综合器根据always块里面的输入变量自动添加。

        此时,进行仿真,发现modelsim报错。# ** Error: (vsim-3601) Iteration limit reached at time 520 ns.

        根据$display语句在屏幕的输出发现,程序在520ns的时候一直在触发always块。从上面的波形图可以发现,520ns为fetch信号第一次出现上升沿的位置。此时,通过代码可以发现,fetch信号变化则触发该always块,并且if (fetch)成立,因此num3的值发生变化,进而又触发该always块,陷入死循环。


        如果把代码中的非阻塞赋值改为阻塞赋值,则波形图如图所示。为何不会出现死循环,因为它被阻塞了,原理参考于开篇所给出的采用阻塞赋值不能自行触发振荡器。

        此时由于触发条件为fetch信号或者num3的变化,所以num3与num1和num2同步。如果这里采用always @(posedge clk)以及阻塞赋值的话,则不会同步。

5. 针对出现死循环的问题

module osc(clk);
  output clk;
  reg clk;
  initial #10 clk = 0;
  always @(clk)
    begin
      $display("At%tns, be excuted.", $time);//just for test.
      //#10;
      clk <= ~clk;
    end
endmodule

        修改采用非阻塞赋值的自触发振荡器的代码,将#10注释掉。仿真发现modelsim报同样的错误。# ** Error: (vsim-3601) Iteration limit reached at time 10 ns.

        在同一时间,一直进入该always块,陷入死循环,因而报错。而加上#10延时命令,就可以避免该问题。
 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值