news 2026/10/27 1:13:45

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Verilog基础:在testbench中使用阻塞赋值和非阻塞赋值的区别

相关阅读

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


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

下面是一个按照上述原则编码的D触发器模块的testbench,本文将针对进行讨论。

// 例1 module Dff_t; reg rst_n = 1; reg clk = 0; reg data_in = 0; reg data_out; // D触发器 always@(posedge clk, negedge rst_n)begin if(!rst_n) data_out <= 1'b0; else data_out <= data_in; end //时钟产生 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

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

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

图1 仿真波形

如果不按照例1进行编码会发生什么?在继续浏览下面的内容前,首先要了解非阻塞赋值的执行过程。

非赋阻塞值是分两步进行的,第一步是将赋值号<=的右表达式进行求值,并且调度一个非阻塞赋值的更新事件到非阻塞赋值更新区,接着执行其他语句;第二步是在当前仿真时间的所有阻塞赋值和非阻塞赋值右表达式求值(它们存在于活跃事件区)完成后,将更新事件从非阻塞赋值更新区激活到活跃事件区并执行(多个更新事件的执行顺序取决于其被调度的顺序,即右表达式求值的顺序),此时非阻塞赋值才算真正完成。总的来说就是,非阻塞赋值分为两步:右表达式求值与更新。

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

// 例2 module Dff_t; reg rst_n = 1; reg clk = 0; reg data_in = 0; reg data_out; // D触发器 always@(posedge clk, negedge rst_n)begin if(!rst_n) data_out <= 1'b0; else data_out <= data_in; end //时钟产生 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

图2 错误的波形

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

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

1、如果第一步时信号clk的右表达式求值比信号data_in的右表达式求值先执行,进行第二步时,信号clk的更新应该先执行,则其又触发了@(posedge clk),接着是执行信号data_in的更新,还是执行信号data_out的右表达式求值,是不确定的。如果先执行信号data_in的更新,则信号data_out得到的是信号data_in的新值即1;如果先执行信号data_out的右表达式求值,则信号data_out得到的是信号data_in的旧值即0。

2、如果第一步时信号data_in的右表达式求值比信号clk的右表达式求值先执行,进行第二步时,信号data_in的更新应该先执行,则最后信号data_out得到的一定是信号data_in的新值即1。

使用Modelsim的Wave Expand Time Toolbar可以展开Event时间,结果如图3所示。有关Wave Expand Time Toolbar的更多信息,可以参考下面的博客。Simens EDA:在Modelsim/QuestaSim中观察一个时间片(Time Slot)中Simulation/Delta Cycle发生的事件https://blog.csdn.net/weixin_45791458/article/details/157656532?spm=1001.2014.3001.5501

图3 展开后的波形

从图3中可以看出,Modelsim选择了信号clk(0->1)、信号data_in(0->1)、信号data_out(0->1)这样的事件顺序。

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

// 例3 module Dff_t; reg rst_n = 1; reg clk = 0; reg data_in = 0; reg data_out; // D触发器 always@(posedge clk, negedge rst_n)begin if(!rst_n) data_out <= 1'b0; else data_out <= data_in; end //时钟产生 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在时钟沿处改变后的值;也可能出现图1所示的正确波形。

以仿真时间10为例,由于initial结构和always结构是并行的,因此无法确定哪一个阻塞赋值的是先进行的,此时有多种执行方式:

1、如果信号clk的阻塞赋值先进行,则其又触发了@(posedge clk),接着是执行信号data_out的右表达式求值,还是执行信号data_in的阻塞赋值,是不确定的。如果先执行信号data_out的右表达式求值,则信号data_out得到的是信号data_in的旧值即0;如果先执行信号data_in的阻塞赋值,则信号data_out得到的是信号data_in的新值即1。

2、如果信号data_in的阻塞赋值先进行,则最后信号data_out得到的一定是信号data_in的新值即1。

使用Modelsim的Wave Expand Time Toolbar可以展开Event时间,结果如图5所示。

图5 展开后的波形

从图5中可以看出,Modelsim选择了信号clk(0->1)、信号data_in(0->1)、信号data_out(0->1)这样的事件顺序。

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

// 例4 module Dff_t; reg rst_n = 1; reg clk = 0; reg data_in = 0; reg data_out; // D触发器 always@(posedge clk, negedge rst_n)begin if(!rst_n) data_out <= 1'b0; else data_out <= data_in; end //时钟产生 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

图6 错误的波形

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

以仿真时间10为例,由于initial结构和always结构是并行的,因此无法确定是右表达式求值还是阻塞赋值先进行,但是信号data_in的阻塞赋值一定在信号clk的更新前进行(这是由非阻塞赋值的定义决定的),所以不管有多少种执行方式,此时只有一种结果:信号data_out得到的一定是信号data_in的新值即1。

使用Modelsim的Wave Expand Time Toolbar可以展开Event时间,结果如图7所示。

图7 展开后的波形

从图7中可以看出,Modelsim选择了信号data_in(0->1)、信号clk(0->1)、信号data_out(0->1)这样的事件顺序。

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

​// 例1 module Dff_t; reg rst_n = 1; reg clk = 0; reg data_in = 0; reg data_out; // D触发器 always@(posedge clk, negedge rst_n)begin if(!rst_n) data_out <= 1'b0; else data_out <= data_in; end //时钟产生 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,以仿真时间10为例,由于initial结构和always结构是并行的,因此无法确定是右表达式求值还是阻塞赋值先进行

1、如果信号clk的阻塞赋值先进行,则其又触发了@(posedge clk),接着是执行信号data_out的右表达式求值,还是执行信号data_in的右表达式求值,是不确定的。但是可以肯定的是,信号data_out得到的是信号data_in的旧值即0,因为信号data_in和信号data_out的更新一定在它们的右表达式求值后进行(这是由非阻塞赋值的定义决定的)。

2、如果信号data_in的右表达式求值先进行,则在其之后信号clk的阻塞赋值进行,其又触发了@(posedge clk),接着执行信号data_out的右表达式求值,则信号data_out得到的是信号data_in的旧值即0,因为信号data_in和信号data_out的更新一定在它们的右表达式求值后进行(这是由非阻塞赋值的定义决定的)。

使用Modelsim的Wave Expand Time Toolbar可以展开Event时间,结果如图8所示。

图8 展开后的波形

从图8中可以看出,Modelsim选择了信号信号clk(0->1)、data_in(0->1)、data_out(0->0)这样的事件顺序。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 1:13:27

英伟达显卡系列与架构、代表产品

NVIDIA的产品线和GPU架构发展历史都非常丰富。无论是面向普通消费者的游戏显卡&#xff0c;还是面向专业图形、科学计算与AI训练的高性能加速卡&#xff0c;都在不同阶段形成了非常清晰的定位。整体来看&#xff0c;NVIDIA的发展主线并不是简单地“做更快的显卡”&#xff0c;而…

作者头像 李华
网站建设 2026/10/27 1:12:44

Verilog基础:表达式中的整数常量(integer)

相关文章 Verilog基础专栏https://blog.csdn.net/weixin_45791458/category_12263729.html ​ 整数常量可以由十进制、十六进制、八进制或二进制格式指定&#xff0c;有两种表现形式。 第一种表现形式 第一种表现形式是一个简单的十进制数&#xff0c;应指定为0到9的数…

作者头像 李华
网站建设 2026/10/27 1:10:08

电容两端电压不能突变:原理、应用与工程实战完整笔记

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:07:13

TIA Context:让AI真正理解PLC程序的工业语义中枢

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:07:11

IC秋招笔试LDO电路详解:PMOS调整管、稳定性与效率分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 1:06:55

变频器PWM载波频率怎么选?噪声、损耗与EMC的工程平衡指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华