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

相关推荐
内有小猪卖1 小时前
时序约束 记录
fpga开发
Cao1234567893214 小时前
FPGA时钟设计
fpga开发
JNTeresa7 小时前
锁存器知识点详解
fpga开发
Cao12345678932110 小时前
FPGA基础之基础语法
fpga开发
一大Cpp10 小时前
通过Quartus II实现Nios II编程
fpga开发
7yewh11 小时前
Verilog 语法 (二)
fpga开发
边缘计算社区1 天前
FPGA与边缘AI:计算革命的前沿力量
人工智能·fpga开发
S&Z34631 天前
[官方IP] Shift RAM
网络协议·tcp/ip·fpga开发
S&Z34631 天前
[FPGA Video IP] Video Processing Subsystem
网络协议·tcp/ip·fpga开发·video
FPGA_Linuxer1 天前
FPGA 100G UDP纯逻辑协议栈
网络协议·fpga开发·udp