CPLD/FPGA/Verilog_Verilog中阻塞与非阻塞的区别_cpld 非阻塞赋值-程序员宅基地

技术标签: FPGA  

转自:http://blog.csdn.net/yangtalent1206/article/details/6430119

在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
1.jpg▲ 大家可以看到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
3.jpg
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
3.jpg
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
2.jpg
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
3.jpg
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
2.jpg


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

 


 

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

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

 


  先看阻塞赋值的情况:

  我们来看这段代码:

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 [5: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。

4.jpg

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

  在时钟上升沿到来以后,且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,它是分层事件队列的一部分,如图所示:

5.jpg

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

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

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; 

    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

  其仿真波形如图所示:

6.jpg

  在代码仿真过程中,第二句的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语句的电路功能比较难理解,可以将这些语句展开,增强代码的可读性。


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/gtatcs/article/details/8746436

智能推荐

如何在iPhone或iPad上安装iOS 11 Beta-程序员宅基地

文章浏览阅读2.6k次。The public beta of iOS 11 is now available for iPhones and iPads. Anyone who wants to play withiOS 11’s new featurescan install it today. However, we recommend backing up your device first so you ca..._ios11bate

UE4_UE5结合offline voice recognition插件做语音识别功能_ue5语音识别-程序员宅基地

文章浏览阅读6.2k次,点赞4次,收藏24次。市面上主流的语音识别大多是用科大讯飞的SDK,但是那个也不是完全免费使用的,于是我选择使用offline voice recognition的语音识别,购买插件终生使用。offline voice recognition插件在UE官方商城卖200多元。我将它需要的资源都打包成一个rar,分享给有需要的人。其中就有两个UE工程,一个是UE4.27版本的,另外一个是UE5的版本。并且也下载了两个中文的语言包,一个是简版,另外一个是完整版,对于 只是做简单的主意指令的只需要用简版的语言包即可,大大提升识别速度。第_ue5语音识别

联想计算机哪款好用,华为和联想电脑哪个好用-程序员宅基地

文章浏览阅读1.8k次。对于一些具有电脑购买需求的朋友来说,在选择购买的时候经常会有一个困惑,那就是华为的电脑和联想的电脑哪一个好用?如果您现在也存在着这样的问题,那么不妨一起来看一下下面对于这两个品牌电脑的相关介绍。联想是比较早生产电脑的一家厂商,本身也是依靠做台式电脑和笔记本电脑起家的,所以也在经过长时间的发展之后一定有成熟的一面,我的一个朋友在多年之前购买了一台联想台式电脑,现在他仍然还在用着这一款台式电脑,由此可..._联想电脑和华为电脑哪个好

为什么搜狗输入法显示服务器异常,为什么搜狗拼音输入法一直出现错误报告?...-程序员宅基地

文章浏览阅读2.6k次。官方论坛有解释和解决办法 对近期输入法弹出str数据错误报告的声明和致歉 尊敬的搜狗拼音用户: 近期我们收到了很多对搜狗拼音输入法3.5奥运版str数据文件错误报告的问题反馈。其某个版本中出现错误后法会弹出类似下面所述的错误报告,影响了您的操作和体验,对此我们非常抱歉。 str数据文件格式错误: C:Documents and SettingsuserApplication DataSogouPY..._sogoupy/sgim_keymap.bin

dreamtalk 学习笔记-程序员宅基地

文章浏览阅读523次,点赞8次,收藏7次。dreamtalk 学习笔记

毕业设计 SpringBoot的自习室预约管理系统_共享自习室预约系统结构图-程序员宅基地

文章浏览阅读350次。在网络高速发展的时代,众多的软件被开发出来,给学生带来了很大的选择余地,而且人们越来越追求更个性的需求。在这种时代背景下,学院只能以学生为导向,所以自习室预订系统是必须的。系统采用了Java技术,将所有业务模块采用以浏览器交互的模式,选择MySQL作为系统的数据库,开发工具选择eclipse来进行系统的设计。基本实现了自习室预订系统应有的主要功能模块,本系统有管理员;首页、个人中心、学生管理、公告信息管理、座位预订管理、自习室管理、留言板管理、系统管理,学生;_共享自习室预约系统结构图

随便推点

wps中的大客户版本_wps 大客户版-程序员宅基地

文章浏览阅读462次,点赞11次,收藏8次。网上的WPS各个版本基本都带有稻香插件,广告一堆堆,用户需要注册才能减少广告的显示,但依然时不时跳出来显示。其实WPS给大学做过一些版本,这种定制版本应用于大学院系的文档编写使用。最关键的是这个版本没有广告,没有稻香,没有推荐模板,只有纯净的WPS。界面虽然不华丽,风格还是很传统office2000风格,不是烦人的多标签页,也不会隐藏。工具栏不会藏着掖着直接横排展示所有图标,鼠标不用切换页,所有工具一目了然。这集美大学版本是办公软件的不二之选。_wps 大客户版

Android自动化页面测速在美团的实践-程序员宅基地

文章浏览阅读587次,点赞8次,收藏19次。我们都知道ViewPager的Tab切换是可以通过一个 OnPageChangeListener 对象进行监听的,所以我们可以为ViewPager添加一个自定义的Listener对象,在切换时记录一个时间,这样可以通过用这个时间减去页面创建后的时间得出这个多余的等待时间,上报时在总时间中减去即可。这里的 getConfigModel() 方法中,会使用页面的类名或者全路径类名,去初始化时解析的配置Map中进行id的匹配,如果匹配到说明页面需要测速,就会创建测速对象 PageObject 进行测速。

百度竞价悄然改版-程序员宅基地

文章浏览阅读42次。前段时间百度因为“魏则西事件”而被要求整改,百度的竞价排名也被推向了风口浪尖。最近有网友@闪电精灵SEO张扬爆料:百度竞价已改版,竞价显示4条,或许取消了右侧排名! 但是在经过风波之后,百度的确有了一些变化,最明显的变化就是对推广的网站的标注,之前的对百度推广只有推广两个字,现在是成了蓝色..._python 百度竞价排名

《剑指offer》学习笔记_面试题35_复杂链表的复制-程序员宅基地

文章浏览阅读334次。题目描述 请实现一个函数,复制一个复杂链表。在复杂链表中,每个节点除了一个next指针指向下一个节点,还有一个random指针指向链表中的任意节点或者为空。 思路 1.分治先复制当前的节点,然后递归的分别复制next或random。在复制next和random的过程中会重复复制相同节点,因此需要一个map来记录当前的复制情况。2.拆分法首先,将复制节点拼接到原始节点的...

嵌入式linux项目介绍与分享-基于 Linux 下 Socket 网络编程的局域网聊天室_linux项目局域网聊天室-程序员宅基地

文章浏览阅读1.4k次,点赞52次,收藏14次。本项目是基于 Linux 下 Socket 网络编程的局域网聊天室,实现了账号注册与登录、私聊消息、群发消息、发送离线消息、查看聊天记录、修改昵称密码等功能,并设置管 理员,实现将用户禁言、解禁、踢出聊天室等,采用多线程并发服务器模型处理多个客户端的同时连接和请求,服务器创建并管理用户数据、在线用户数据、聊天数据、离线消息数据等 SQlite 数据库,并提供后台服务,客户端通过 TCP 协议建立与服务器的稳定连接,并通过格式化输入输出实现与用户的交互。_linux项目局域网聊天室

微服务spring cloud 五件套整合,eureka、rabbitMQ、hystrix、zuul、config、feign_微服务有哪几套-程序员宅基地

文章浏览阅读830次。把spring cloud微服务的五件套做了一下整合,一篇文章概括了eureka、rabbitMQ、hystrix、zuul、config、feign,使用的时候复制、粘贴就可以了,不用再单独的到处找啦。_微服务有哪几套

推荐文章

热门文章

相关标签