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

2024-03-27 03:36

本文主要是介绍Verilog基础:在testbench中使用阻塞赋值和非阻塞赋值的区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

相关阅读

Verilog基础icon-default.png?t=N7T8https://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)beginif(!rst_n)data_out <= 1'b0;elsedata_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;endendmodule

        其中时钟信号和异步复位信号使用了阻塞赋值,而数据信号使用了非阻塞赋值。如果不是这样,就无法保证产生如图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;endendmodule

图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;endendmodule

图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;endendmodule

图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;endendmodule

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

这篇关于Verilog基础:在testbench中使用阻塞赋值和非阻塞赋值的区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/850794

相关文章

MyBatis中$与#的区别解析

《MyBatis中$与#的区别解析》文章浏览阅读314次,点赞4次,收藏6次。MyBatis使用#{}作为参数占位符时,会创建预处理语句(PreparedStatement),并将参数值作为预处理语句... 目录一、介绍二、sql注入风险实例一、介绍#(井号):MyBATis使用#{}作为参数占位符时,会

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

prometheus如何使用pushgateway监控网路丢包

《prometheus如何使用pushgateway监控网路丢包》:本文主要介绍prometheus如何使用pushgateway监控网路丢包问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录监控网路丢包脚本数据图表总结监控网路丢包脚本[root@gtcq-gt-monitor-prome

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

SpringBoot中如何使用Assert进行断言校验

《SpringBoot中如何使用Assert进行断言校验》Java提供了内置的assert机制,而Spring框架也提供了更强大的Assert工具类来帮助开发者进行参数校验和状态检查,下... 目录前言一、Java 原生assert简介1.1 使用方式1.2 示例代码1.3 优缺点分析二、Spring Fr

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL