Verilog基础:在testbench中使用阻塞赋值和非阻塞赋值的区别

相关阅读

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


本文详细阐述了在一个testbench中,应该如何使用阻塞赋值与非阻塞赋值。首先说结论,建议在testbench中,对时钟信号(包括分频时钟)使用阻塞赋值,对其他同步信号使用非阻塞赋值。

下面是一个简单的D触发器模块,本文将针对它的testbench进行讨论。

复制代码
module Dff(input clk, rst_n, data_in, output reg data_out);
    always@(posedge clk, negedge rst_n)begin
        if(!rst_n)
            data_out <= 1'b0;
        else
            data_out <= data_in;
    end
endmodule

在Verilog仿真时,仿真波形与真实波形是有一定差距的,这体现在同步信号的改变与时钟沿一直是对齐的,而真实情况下,数据信号在时钟沿后需要延迟一段时间才会发生改变。

体现在上面的模块中就是数据信号data_in的改变是与时钟信号clk同步的,data_out的改变也是与时钟信号clk同步的。

图1展示了一个简单的仿真波形,其中信号data_in和信号data_out的改变都与时钟沿同步,需要注意的是在时钟沿处,信号data_out得到的是信号data_in在时钟沿处的原值,而不是改变后的值。

图1 仿真波形

也就是说,一个更加真实的波形可能如图2所示。

图2 真实波形

下面给出图1所示波形的testbench。

复制代码
module Dff_t;
    reg rst_n = 1;
    reg clk = 0;
    reg data_in = 0;
    wire data_out;

    Dff dff(.clk(clk), .rst_n(rst_n), .data_in(data_in), .data_out(data_out));
    //时钟产生
    always begin
        #10 clk = ~clk;
    end

    //异步复位信号
    initial begin
        #3 rst_n = 0;
        #3 rst_n = 1;
    end

    //同步数据输入
    initial begin
        #10 data_in <= 1;
        #20 data_in <= 0;
        #20 data_in <= 1;
        #20 data_in <= 0;
    end
    

endmodule

其中时钟信号和异步复位信号使用了阻塞赋值,而数据信号使用了非阻塞赋值。如果不是这样,就无法保证产生如图1所示的仿真波形,下面将分别讨论。

1、如时钟信号使用非阻塞赋值,数据信号也使用非阻塞赋值

复制代码
module Dff_t;
    reg rst_n = 1;
    reg clk = 0;
    reg data_in = 0;
    wire data_out;

    Dff dff(.clk(clk), .rst_n(rst_n), .data_in(data_in), .data_out(data_out));
    //时钟产生
    always begin
        #10 clk <= ~clk;
    end

    //异步复位信号
    initial begin
        #3 rst_n = 0;
        #3 rst_n = 1;
    end

    //同步数据输入
    initial begin
        #10 data_in <= 1;
        #20 data_in <= 0;
        #20 data_in <= 1;
        #20 data_in <= 0;
    end
    

endmodule

图3 错误的波形(一种可能)

此时进行仿真,可能会出现图3所示的错误波形,信号data_out得到的是信号data_in在时钟沿处改变后的值。

拿第一个时钟上升沿即10ns时举例,此时时钟信号clk被非阻塞赋值,同时data_in被非阻塞赋值。首先说明非阻塞赋值的过程,非阻塞赋值是分两步进行的,第一步是将赋值号<=右表达式求值,在当前仿真时间的所有赋值和非阻塞赋值右表达式求值(活跃事件)完成后,再进行第二步,即非阻塞赋值的赋值(非阻塞赋值的赋值顺序由求值顺序决定),即非阻塞赋值分为两步:求值与赋值,后文仅使用"赋值"一词代表非阻塞赋值中的赋值这个步骤,注意其与阻塞赋值的区别。

由于initial结构和always结构是并行的,因此无法确定哪一个非阻塞赋值的右表达式求值是先进行的,但可以确定的是,信号clk的赋值和信号data_in的赋值以某种先后顺序被调度到之后(非阻塞赋值更新区)执行。当进行第二步时,clk的赋值和data_in的赋值都从非阻塞赋值更新区激活到活跃事件区执行,此时有多种执行方式:

1、如果clk的赋值先执行(即之前clk非阻塞赋值右表达式先求值),则其又触发了@(posedge clk),接着是执行data_out非阻塞赋值右表达式求值,还是执行data_in的赋值,是不确定的,它们都是活跃事件。如果先执行data_out非阻塞赋值右表达式求值,则data_out得到的是data_in的旧值即0;如果先执行data_in的赋值,则则data_out得到的是data_in的新值即1(图3可能就是这种情况)。

2、如果data_in的赋值先执行(即之前data_in非阻塞赋值右表达式先求值),则最后data_out得到的一定是data_in的新值即1(图3可能就是这种情况)。

2、如时钟信号使用阻塞赋值,数据信号也使用阻塞赋值

复制代码
module Dff_t;
    reg rst_n = 1;
    reg clk = 0;
    reg data_in = 0;
    wire data_out;

    Dff dff(.clk(clk), .rst_n(rst_n), .data_in(data_in), .data_out(data_out));
    //时钟产生
    always begin
        #10 clk = ~clk;
    end

    //异步复位信号
    initial begin
        #3 rst_n = 0;
        #3 rst_n = 1;
    end

    //同步数据输入
    initial begin
        #10 data_in = 1;
        #20 data_in = 0;
        #20 data_in = 1;
        #20 data_in = 0;
    end
    

endmodule

图4 错误的波形(一种可能)

此时进行仿真,可能会出现图4所示的错误波形,信号data_out得到的是信号data_in在时钟沿处改变后的值。

拿第一个时钟上升沿即10ns时举例,此时时钟信号clk被阻塞赋值,同时data_in被阻塞赋值。由于initial结构和always结构是并行的,因此无法确定哪一个阻塞赋值是先进行的,此时有多种执行方式。

1、如果clk的阻塞赋值先进行,则其又触发了@(posedge clk),接着是执行data_out非阻塞赋值右表达式求值,还是执行data_in的阻塞赋值,是不确定的,它们都是活跃事件。如果先执data_out非阻塞赋值右表达式求值,则data_out得到的是data_in的旧值即0;如果先执行data_in的阻塞赋值,则则data_out得到的是data_in的新值即1(图4可能就是这种情况)。

2、如果data_in的阻塞赋值先进行则最后data_out得到的一定是data_in的新值即1(图4可能就是这种情况)。

3、如时钟信号使用非阻塞赋值,数据信号使用阻塞赋值

复制代码
module Dff_t;
    reg rst_n = 1;
    reg clk = 0;
    reg data_in = 0;
    wire data_out;

    Dff dff(.clk(clk), .rst_n(rst_n), .data_in(data_in), .data_out(data_out));
    //时钟产生
    always begin
        #10 clk <= ~clk;
    end

    //异步复位信号
    initial begin
        #3 rst_n = 0;
        #3 rst_n = 1;
    end

    //同步数据输入
    initial begin
        #10 data_in = 1;
        #20 data_in = 0;
        #20 data_in = 1;
        #20 data_in = 0;
    end
    

endmodule

图5 错误的波形

此时进行仿真,一定会出现图5所示的错误波形,信号data_out得到的是信号data_in在时钟沿处改变后的值。

拿第一个时钟上升沿即10ns时举例,此时时钟信号clk被非阻塞赋值,同时data_in被阻塞赋值。由于initial结构和always结构是并行的,因此无法确定是非阻塞赋值的右表达式求值先进行还是阻塞赋值先进行,但是阻塞赋值一定是在非阻塞赋值的赋值前进行的(根据非阻塞赋值的定义),所以不管有多少种执行方式,此时只有一种结果。

1、data_out得到的一定是data_in的新值即1(图5就是这种情况)。

4、时钟信号使用阻塞赋值,数据信号使用非阻塞赋值

复制代码
module Dff_t;
    reg rst_n = 1;
    reg clk = 0;
    reg data_in = 0;
    wire data_out;

    Dff dff(.clk(clk), .rst_n(rst_n), .data_in(data_in), .data_out(data_out));
    //时钟产生
    always begin
        #10 clk = ~clk;
    end

    //异步复位信号
    initial begin
        #3 rst_n = 0;
        #3 rst_n = 1;
    end

    //同步数据输入
    initial begin
        #10 data_in <= 1;
        #20 data_in <= 0;
        #20 data_in <= 1;
        #20 data_in <= 0;
    end
    

endmodule

最后分析正确的testbench,拿第一个时钟上升沿即10ns时举例,此时时钟信号clk被阻塞赋值,同时data_in被非阻塞赋值。由于initial结构和always结构是并行的,因此无法确定是非阻塞赋值的右表达式求值先进行还是阻塞赋值先进行。

1、如果clk的阻塞赋值先进行,则其又触发了@(posedge clk),接着是执行data_out非阻塞赋值右表达式求值,还是执行data_in非阻塞赋值右表达式求值,是不确定的,它们都是活跃事件。但是可以肯定的是,data_out得到的一定是data_in的旧值,因为非阻塞赋值的赋值一定在所有非阻塞赋值的求值后进行(根据非阻塞赋值的定义)。

2、如果data_in非阻塞赋值右表达式求值先进行,则在之后clk阻塞赋值进行后,其又触发了@(posedge clk),接着执行data_out非阻塞赋值右表达式求值,但求值时是使用data_in的旧值,因为非阻塞赋值的赋值一定在所有非阻塞赋值的求值后进行(根据非阻塞赋值的定义)。

相关推荐
KOAN凯擎小妹1 天前
晶振信号质量:上升下降时间与占空比
单片机·嵌入式硬件·fpga开发·信息与通信
cmc10281 天前
148.PCIE参考时钟无法绑定
fpga开发
我爱C编程1 天前
【硬件片内测试】基于FPGA的完整BPSK链路测试,含频偏锁定,帧同步,定时点,Viterbi译码,信道,误码统计
fpga开发·定时·bpsk·帧同步·卷积编码·维特比译码·频偏估计
FPGA_小田老师1 天前
FPGA基础知识(十一):时序约束参数确定--从迷茫到精通
fpga开发·时序约束·建立时间·保持时间·约束参数计算
FPGA_小田老师1 天前
FPGA基础知识(十二):详解跨时钟域约束
fpga开发·时序约束·跨时钟域·约束完整性
第二层皮-合肥2 天前
基于FPGA的雷达信号处理设计工具包分享
fpga开发·信号处理
美好的事情总会发生2 天前
FPGA的LVDS接口电压
嵌入式硬件·fpga开发·硬件工程·智能硬件
卡奥斯开源社区官方2 天前
量子计算“平价革命”深度解析:AMD破局FPGA方案+中国千比特云服务,技术拐点已至?
fpga开发·量子计算
贝塔实验室2 天前
译码器的结构
驱动开发·算法·网络安全·fpga开发·硬件工程·信息与通信·信号处理
bnsarocket3 天前
Verilog和FPGA的自学笔记9——呼吸灯
笔记·fpga开发·verilog·自学·硬件编程