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的旧值,因为非阻塞赋值的赋值一定在所有非阻塞赋值的求值后进行(根据非阻塞赋值的定义)。

相关推荐
9527华安14 小时前
国产安路FPGA开发设计培训课程,提供开发板+工程源码+视频教程+技术支持
fpga开发·fpga·安路·视频教程·培训·安路fpga
UVM_ERROR21 小时前
硬件设计实战:解决Valid单拍采样失效问题(附非阻塞赋值与时序对齐核心要点)
驱动开发·fpga开发·github·芯片
brave and determined1 天前
可编程逻辑器件学习(day36):从沙粒到智能核心:芯片设计、制造与封装的万字全景解析
fpga开发·制造·verilog·fpga·芯片设计·硬件设计·芯片制造
步达硬件1 天前
【FPGA】FPGA开发流程
fpga开发
我爱C编程2 天前
【仿真测试】基于FPGA的完整16QAM通信链路实现,含频偏锁定,帧同步,定时点,Viterbi译码,信道,误码统计
fpga开发·16qam·帧同步·卷积编码·viterbi译码·维特比译码·频偏锁定
云雾J视界2 天前
AI芯片设计实战:用Verilog高级综合技术优化神经网络加速器功耗与性能
深度学习·神经网络·verilog·nvidia·ai芯片·卷积加速器
s09071363 天前
ZYNQ DMA to UDP 数据传输系统设计文档
网络协议·fpga开发·udp
燎原星火*3 天前
QSPI IP核 基本参数
fpga开发
XINVRY-FPGA3 天前
XCVU9P-2FLGC2104I Xilinx AMD Virtex UltraScale+ FPGA
嵌入式硬件·机器学习·计算机视觉·fpga开发·硬件工程·dsp开发·fpga
FPGA_小田老师3 天前
FPGA Debug:PCIE一直自动重启(link up一直高低切换)
fpga开发·pcie debug·pcie初始化问题