热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

阻塞赋值语句(“=”)和非阻塞赋值语句(“<=”)

在Verilog中有两种类型的赋值语句:阻塞赋值语句(“”)和非阻塞赋值语句(“<”)。正确地使用这两种赋值语句对于Verilog的设

在Verilog中有两种类型的赋值语句:阻塞赋值语句(“=”)和非阻塞赋值语句(“<=”)。正确地使用这两种赋值语句对于Verilog的设计和仿真非常重要。下面我们以例子说明阻塞和非阻塞赋值的区别。

  

        我们先来看几段代码及其对应的电路:

HDL源代码 对应的RTL电路
module Shifter1(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q1 = D;
Q2 = Q1;
Q3 = Q2;
end
endmodule
▲ 大家可以看到Q1、Q2被优化掉了
module Shifter2(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q1 <= D;
Q2 <= Q1;
Q3 <= Q2;
end
endmodule
module Shifter3(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q3 = Q2;
Q2 = Q1;
Q1 = D;
end
endmodule
module Shifter4(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;
always @(posedge Clk)
begin
Q1 <= D;
Q2 = Q1;
Q3 = Q2;
end
endmodule
module Shifter5(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q1 <= D;
Q2 <= Q1;
Q3 = Q2;
end
endmodule
module Shifter6(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;
always @(posedge Clk)
begin
Q1 <= D;
Q2 = Q1;
Q3 <= Q2;
end
endmodule

        从上面的例子中,我们可以看出,在阻塞赋值语句中,赋值的次序非常重要,而在非阻塞赋值语句中,赋值的次序并不重要。


 

  下面我们具体分析一下阻塞和非阻塞赋值的语义本质:

  阻塞:在本语句中“右式计算”和“左式更新”完全完成之后,才开始执行下一条语句;
  非阻塞:当前语句的执行不会阻塞下一语句的执行。


  先看阻塞赋值的情况:我们来看这段代码:

    always @(posedge Clk)
begin
Q1 = D;
Q2 = Q1;
Q3 = Q2;
end

  always语句块对Clk的上升沿敏感,当发生Clk 0~1的跳变时,执行该always语句。
        在begin...end语句块中所有语句是顺序执行的,而且最关键的是,阻塞赋值是在本语句中“右式计算”和“左式更新”完全完成之后,才开始执行下一条语句的。
        在本例中,D的值赋给Q1以后,再执行Q2 = Q1;同样在Q2的值更新以后,才执行Q3 = Q2。这样,最终的计算结果就是Q3 = D。
        所有的语句执行完以后,该always语句等待Clk的上升沿,从而再一次触发begin...end语句。


  接下来,再看看非阻塞赋值的情况。
       所谓非阻塞赋值,顾名思义,就是指当前语句的执行不会阻塞下一语句的执行。

 always @(posedge Clk)
begin
Q1 <= D;
Q2 <= Q1;
Q3 <= Q2;
end

   首先执行Q1 <= D,产生一个更新事件,将D的当前值赋给Q1,但是这个赋值过程并没有立刻执行,而是在事件队列中处于等待状态。
        然后执行Q2 <= Q1,同样产生一个更新事件,将Q1的当前值(注意上一语句中将D值赋给Q1的过程并没有完成,Q1还是旧值)赋给Q2,这个赋值事件也将在事件队列中处于等待状态。
        再执行Q3 <= Q2,产生一个更新事件,将Q2的当前值赋给Q3,这个赋值事件也将在事件队列中等待执行。
        这时always语句块执行完成,开始对下一个Clk上升沿敏感。

  那么什么时候才执行那3个在事件队列中等待的事件呢?只有当当前仿真时间内的所有活跃事件和非活跃事件都执行完成后,才开始执行这些非阻塞赋值的更新事件。这样就相当于将D、Q1和Q2的值同时赋给了Q1、Q2和Q3。

  注:
            *仿真器首先按照仿真时间对事件进行排序,然后再在当前仿真时间里按照事件的优先级顺序进行排序。

     *活跃事件是优先级最高的事件。在活跃事件之间,它们的执行顺序是随机的。阻塞赋值(=)、连续赋值(assign)以及非阻塞赋值的右式计算等都属于活跃事件。

 


 

  下面通过一个典型案例,进一步说明阻塞赋值和非阻塞赋值的区别。

  这里有一个数组:Data[0]、Data[1]、Data[2]和Data[3],它们都是4比特的数据。我们需要在它们当中找到一个最小的数据,同时将该数据的索引输出到LidMin中,这个算法有点类似于“冒泡排序”的过程,而且需要在一个时钟周期内完成。例如,如果这4个数据中 Data[2]最小,那么LidMin的值则为2。

   module Bubble_Up(
Rst_n,
Clk,
Data,
Lid_Min
);
input Rst_n;
input Clk;
input [3:0] Data [0:3];
output [1:0] Lid_Min;
reg [1:0] Lid_Min;
always @(posedge Clk or negedge Rst_n)
begin
if (~Rst_n)
begin
Lid_Min <= 2'd0;
end
else
begin
if (Data[0] <= Data[Lid_Min]) //"<="表示小于等于
begin
Lid_Min <= 2'd0; //"<="表示非阻塞赋值
end

if (Data[1] <= Data[Lid_Min])
begin
Lid_Min <= 2'd1;
end

if (Data[2] <= Data[Lid_Min])
begin
Lid_Min <= 2'd2;
end

if (Data[3] <= Data[Lid_Min])
begin
Lid_Min <= 2'd3;
end
end
end
endmodule

  我们的原意是首先将Lid_Min设置为一个初始值(任意值都可以),然后将Data[0]~Data[3]与Data[Lid_Min]进行比较,每比较一个数,就将较小的索引暂存在Lid_Min中,然后再进行下一次比较。当4组数据比较完成之后,最小的数据索引就会保留在Lid_Min 中。

  我们在以上代码中使用了非阻塞赋值,结果发现,仿真波形根本不是我们所需要的功能,如图所示,图中的Data[0]~Data[3]分别为 11、3、10和12,Lid_Min的初始值为0。按道理来说,Lid_Min的计算结果应该为1,因为Data[1]最小,但仿真波形却为2。

 

  为什么会得出这样的结果呢?

  在时钟上升沿到来以后,且Rst_n信号无效时开始执行以下4个语句,假设这时候的Lid_Min是0,Data[0]~Data[3]分别为11、3、10和12:

               if (Data[0] <= Data[Lid_Min])    //"<="表示小于等于
begin
Lid_Min <= 2'd0; //"<="表示非阻塞赋值
end

if (Data[1] <= Data[Lid_Min])
begin
Lid_Min <= 2'd1;
end

if (Data[2] <= Data[Lid_Min])
begin
Lid_Min <= 2'd2;
end

if (Data[3] <= Data[Lid_Min])
begin
Lid_Min <= 2'd3;
end

  第一句的if为真,因此执行Lid_Min <= 2’d0,而这时候,Lid_Min并没有立刻被赋值,而是调度到事件队列中等待执行,这是非阻塞赋值的特点。

  第二句的if为真,因此执行Lid_Min <= 2’d1,这是Lid_Min也没有立刻被赋值为1,而是调度到事件队列中等待执行。当前的Lid_Min还是0,没有发生任何变化。

  同样,第三句的if也为真,因此执行Lid_Min <= 2’d2,将更新事件调度到事件队列中等待执行。当前的Lid_Min还是0。

  而第四句的if为假,因此直接跳过Lid_Min <= 2’d3,这时跳出always语句,等待下一个时钟上升沿。

  在以上的always语句执行完成以后,仿真时间没有前进。这时存在于事件队列中当前仿真时间上的3个被调度的非阻塞更新事件开始执行,它们分别将Lid_Min更新为0、1和2。

  按照Verilog语言的规范,这3个更新事件属于同一仿真时间内的事件,它们之间的执行顺序随机,这就产生了不确定性。一般的仿真器在实现的时候是根据它们被调度的先后顺序执行的,事件队列就像一个存放事件的FIFO,它是分层事件队列的一部分,如图所示:

 

  这3个事件在同一仿真时间被一一执行,而真正起作用的时最后一个更新事件,因此在仿真的时候得到的最终结果时Lid_Min为2。

  然后我们想要得到的结果是,在每个if语句判断并执行完成以后,Lid_Min先暂存这个中间值,再进行下一次比较,也就是说在进行下一次比较之前,这个Lid_Min必须被更新,而这一点也正是阻塞赋值的特点,因此我们将代码作如下更改:

module Bubble_Up(
Rst_n,
Clk,
Data,
Lid_Min
);
input Rst_n;
input Clk;
input [3:0] Data [0:3];
output [1:0] Lid_Min;
reg [1:0] Lid_Min;
always @(posedge Clk or negedge Rst_n)
begin
if (~Rst_n)
begin
Lid_Min <= 2'd0;
end
else
begin
if (Data[0] <= Data[Lid_Min]) //"<="表示小于等于
begin
Lid_Min = 2'd0; //"<="表示非阻塞赋值
end

if (Data[1] <= Data[Lid_Min])
begin
Lid_Min = 2'd1;
end

if (Data[2] <= Data[Lid_Min])
begin
Lid_Min = 2'd2;
end

if (Data[3] <= Data[Lid_Min])
begin
Lid_Min = 2'd3;
end
end
end
endmodule

  其仿真波形如图所示:

 

  在代码仿真过程中,第二句的if为真,执行Lid_Min = 2'd1,根据阻塞赋值的特点,Lid_Min被立刻赋值为1。在执行第三句if的时候,if (Data[2] <= Data[Lid_Min])为假,直接跳过Lid_Min = 2'd2不执行,同样也跳过Lid_Min = 2'd3不执行。Lid_Min被最终赋值为1,这正是我们想要的结果。

        另外,为了使代码看起来更简洁,我们使用for语句改写了代码:

     module Bubble_Up(
Rst_n,
Clk,
Data,
Lid_Min
);
input Rst_n;
input Clk;
input [5:0] Data [0:3];
output [1:0] Lid_Min;
reg [1:0] Lid_Min;
integer i;
always @(posedge Clk or negedge Rst_n)
begin
if (~Rst_n)
begin
Lid_Min = 2'd0;
end
else
begin
for (i = 2'd0; i <= 2'd3; i = i + 2'd1)
begin
if (Data[i] <= Data[Lid_Min])
begin
Lid_Min = i;
end
end
end
end
endmodule

  这种写法与前面展开的写法完全等效,功能完全一致。今后大家在读代码时发现带有for语句的电路功能比较难理解,可以将这些语句展开,增强代码的可读性。


推荐阅读
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文介绍了蓝桥训练中的闰年判断问题,并提供了使用Python代码进行判断的方法。根据给定的年份,判断是否为闰年的条件是:年份是4的倍数且不是100的倍数,或者是400的倍数。根据输入的年份,输出结果为yes或no。本文提供了相应的Python代码实现。 ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • springmvc学习笔记(十):控制器业务方法中通过注解实现封装Javabean接收表单提交的数据
    本文介绍了在springmvc学习笔记系列的第十篇中,控制器的业务方法中如何通过注解实现封装Javabean来接收表单提交的数据。同时还讨论了当有多个注册表单且字段完全相同时,如何将其交给同一个控制器处理。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
author-avatar
miss文女1977
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有