ZYNQ学习:Verilog语言心得(九)

前言

我宣布,前面的心得少部分有问题!

今天发现一个重大问题,就是在编写tb文件时,使用了非阻塞赋值来产生时钟,这使得所有赋值都时同时进行的,会导致与硬件仿真不符的情况。

以后需要在时钟产生的时候进行阻塞赋值

把:

always #10 sys_clk <= ~sys_clk;

修改成:

always #10 sys_clk = ~sys_clk;

 下面举一些例子来说明这两者的区别:

边沿检测电路:

源文件:

module edge_detect(
    input  sys_clk    ,
    input  sys_rst_n  ,
    input  signal     ,
    
    output sig_edge   ,
    output sig_posedge,
    output sig_negedge
    );
    
    reg signal_d1;
    
    //signal_d1
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(!sys_rst_n)
            signal_d1 <= 1'b0;
        else
            signal_d1 <= signal;
        end
    
    assign sig_edge    =  signal ^   signal_d1;
    assign sig_posedge =  signal && !signal_d1;
    assign sig_negedge = !signal &&  signal_d1;
    
    endmodule

tb文件:

`timescale 1ns/1ns
module tb_edge_detect();
    
    parameter CLK_PERIOD = 20;
    
    reg  sys_clk    ;
    reg  sys_rst_n  ;
    reg  signal     ;

    wire sig_edge   ;
    wire sig_posedge;
    wire sig_negedge;
    
    initial begin
        sys_clk   <= 1'b0;
        sys_rst_n <= 1'b0;
        signal    <= 1'b0;
       #(CLK_PERIOD/2)
        sys_rst_n <= 1'b1;
        signal    <= 1'b1; 
       #(CLK_PERIOD  )
        signal    <= 1'b0;
       #(CLK_PERIOD*2)
        signal    <= 1'b1;
       #(CLK_PERIOD*2)
        signal    <= 1'b0;
        end
    
    always #(CLK_PERIOD/2)
        sys_clk <= !sys_clk; //注意这里的非阻塞赋值
    
    edge_detect u_edge_detect(
        .sys_clk    (sys_clk    ),
        .sys_rst_n  (sys_rst_n  ),
        .signal     (signal     ),

        .sig_edge   (sig_edge   ),
        .sig_posedge(sig_posedge),
        .sig_negedge(sig_negedge)
        );
    
    endmodule

波形图:

分析:

问题1:出现了毛刺

非阻塞赋值导致了,时钟信号和信号同时到达,但是signal_d1是一个内部变量,这样问题就来了,因为assign了:

assign sig_edge    =  signal ^   signal_d1;

所以这是一个连续信号,sig_edge 在时钟上升沿这一瞬间是这样判断的:

1.时钟上升沿,数据signal也变化了,因为数据signal是输入的信号,所以取后值,signal是1

2.signal_d1是always语句赋值的内部信号,所以取前值,取0

3.所以这一时刻的值为0^1 = 1,出现了毛刺

问题2:signal_delay没有延迟一周期

在第一个时钟到来的时候,因为时钟使用的是非阻塞赋值,所以时钟是和信号一起变化的,这样一来信号会取变化之后的值,所以当执行:

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(!sys_rst_n)
            signal_d1 <= 1'b0;
        else
            signal_d1 <= signal;
        end

这个语句块中的else语句时,signal已经取后面的值了,所以是同步变化的。信号没有延后一周期

如果时钟产生信号使用阻塞赋值,则时钟始终是第一个产生的,不存在这个问题:

修改一下tb代码:

`timescale 1ns/1ns
module tb_edge_detect();
    
    parameter CLK_PERIOD = 20;
    
    reg  sys_clk    ;
    reg  sys_rst_n  ;
    reg  signal     ;

    wire sig_edge   ;
    wire sig_posedge;
    wire sig_negedge;
    
    initial begin
        sys_clk   <= 1'b0;
        sys_rst_n <= 1'b0;
        signal    <= 1'b0;
       #(CLK_PERIOD/2)
        sys_rst_n <= 1'b1;
        signal    <= 1'b1; 
       #(CLK_PERIOD  )
        signal    <= 1'b0;
       #(CLK_PERIOD*2)
        signal    <= 1'b1;
       #(CLK_PERIOD*2)
        signal    <= 1'b0;
        end
    
    always #(CLK_PERIOD/2)
        sys_clk = !sys_clk; //注意这里的阻塞赋值
    
    edge_detect u_edge_detect(
        .sys_clk    (sys_clk    ),
        .sys_rst_n  (sys_rst_n  ),
        .signal     (signal     ),

        .sig_edge   (sig_edge   ),
        .sig_posedge(sig_posedge),
        .sig_negedge(sig_negedge)
        );
    
    endmodule

现在看一下前面的问题:

1.延后成功一周期,因为第一个时钟上升沿到来的时候,时钟先变化signal后变化,所以d1取值为signal跳变之前的值,没有问题

2.尖峰毛刺,仍然存在一个小缺口,这是由于组合逻辑电路导致的,无法避免,assign赋值是电路的联通,如果输入同时变化,结果输出又回到原始状态就会这样丢失一瞬间的值,但是always就不会,因为always在这种突变时刻取到的是很稳定的突变之前的值,所以不会有波形的丢失

寄存器延迟电路:

源代码:

module reg_1(
    input  sys_rst_n,
    input  sys_clk  ,
    input  a        ,
    
    output reg a_delay
    );
    
    reg a_1;
    
    //a_1
    always @(posedge sys_clk or negedge sys_rst_n) begin //当时钟上升沿到来的时候,把输入a的值赋给a_1
        if(!sys_rst_n) begin
            a_1     <= 1'b0;
            a_delay <= 1'b0;
            end
        else begin
            a_1     <= a;
            a_delay <= a_1;
            end
        end

    endmodule

tb文件:

`timescale 1ns/1ns
module tb_reg_1();
    
    parameter CLK_PERIOD = 5'd20;
    
    reg  sys_rst_n;
    reg  sys_clk  ;
    reg  a        ;
    
    wire a_delay  ;
     
    initial begin
        sys_clk   <= 1'b0;
        sys_rst_n <= 1'b0;
        a         <= 1'b0;
       #(CLK_PERIOD/2)
        sys_rst_n <= 1'b1;
        a         <= 1'b0;
       #(CLK_PERIOD)
        a         <= 1'b1;
       #(CLK_PERIOD*2)
        a         <= 1'b0;
        end
    always #(CLK_PERIOD/2)
        sys_clk <= !sys_clk;
    
    reg_1 u_reg_1(
        .sys_rst_n(sys_rst_n),
        .sys_clk  (sys_clk  ),
        .a        (a        ),
        
        .a_delay  (a_delay  )
        );
    
    endmodule
    
    
    
    

波形图:

分析:

问题:方法太复杂

用非常复杂的方法实现了延迟一拍,非常麻烦,首先用一个内部寄存器a_1,把非阻塞输入信号a转为了一个内部信号a_1,然后利用a_1在时钟沿到来的时候取前值的特点,让a_delay=a,从而实现了延迟一拍的寄存器,非常不科学。

修改一下tb代码:

    always #(CLK_PERIOD/2)
        sys_clk = !sys_clk;

 修改后的波形图:

这样其实就延时了两拍

分析:第一个时钟上升沿到来的时候,因为时钟是阻塞赋值,所以时钟先变化,执行:

a_1     <= a;

语句的时候,a取变化前的值,即0,所以直接就已经延后一周期了,a_delay则又延后了一周期。这样就重复了,所以我们需要修改一下源代码,不需要a_1了:

module reg_1(
    input  sys_rst_n,
    input  sys_clk  ,
    input  a        ,
    
    output reg a_delay
    );
    
    
    //a_delay
    always @(posedge sys_clk or negedge sys_rst_n) begin //当时钟上升沿到来的时候,把输入a的值赋给a_delay
        if(!sys_rst_n) 
            a_delay <= 1'b0;
        else
            a_delay <= a;
        end

    endmodule

修改后产生的波型图:

正常完成延迟一周期的功能 

总结

通过上面两个例子我们可以看出,如果不把sys_clk在tb中写成阻塞赋值就会带来大量的麻烦,时钟和其他信号总是同时到来,需要反复思考上升下降沿,信号的值到底是什么,这样非常费脑子,而且会导致大量的错误,所以我们要把时钟写成阻塞赋值,总是时钟第一个到,其他突变的量全部取值为之前的值,这样就很好判断了。

疑问

我是跟着正点原子的教程来的,教程里面就是非阻塞赋值,但是我又看了野火和其他的一些教程,都是阻塞赋值,这就很奇怪,是正点搞错了吗?

然后我去看为什么正点原子的例程是对的,延后都正常,结果正点tb文件全是这样写的:

后面复杂的工程也是一样:

都搞个延迟201ns避开问题,延迟201就可以避免在上升沿进行跳变,从而忽略刚刚我们讨论的问题,然后我就觉得这应该是一种习惯问题,可以用非阻塞赋值配合延迟多一点避开上升沿跳变,也可以写阻塞赋值从而不用考虑避免上升沿跳变这一问题。

这是目前的探究结果。

然而问题还没有结束,最终仿真都是为了能在硬件实现,这两种都能实现功能所以都没有问题。所以硬件仿真里面到底是哪种情况呢,这个等学了ILA调试再回来看看这一篇文章,到时候试一试。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值