OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 1325|回复: 0

[国产FPGA] 《ATK-DFPGL22G 之FPGA开发指南》第七章 Verilog HDL语法(上)

[复制链接]

1118

主题

1129

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4672
金钱
4672
注册时间
2019-5-8
在线时间
1224 小时
发表于 2023-10-30 15:14:34 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-10-27 10:52 编辑

第七章 Verilog HDL语法

1)实验平台:正点原子 ATK-DFPGL22G开发板

2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-PGL22G.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)FPGA技术交流QQ群:435699340

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

VerilogHDL(Hardware Description Language)是在用途最广泛的C语言的基础上发展起来的一种硬件描述语言,具有灵活性高、易学易用等特点。Verilog HDL可以在较短的时间内学习和掌握,目前已经在FPGA开发/IC设计领域占据绝对的领导地位。
本章包括以下几个部分:         
1.1          Verilog概述
1.2          Verilog基础知识
1.3          Verilog程序框架
1.4          Verilog高级知识点
1.5          Verilog编程规范

1.1 Verilog概述
本节主要描述了VerilogHDL(以下简称Verilog)简介、Verilog和VHDL以及和C语言的区别。

1.1.1 Verilog简介
Verilog是一种硬件描述语言,以文本形式来描述数字系统硬件的结构和行为的语言,用它可以表示逻辑电路图、逻辑表达式,还可以表示数字逻辑系统所完成的逻辑功能。

数字电路设计者利用这种语言,可以从顶层到底层逐层描述自己的设计思想,用一系列分层次的模块来表示极其复杂的数字系统。然后利用电子设计自动化(EDA)工具,逐层进行仿真验证,再把其中需要变为实际电路的模块组合,经过自动综合工具转换到门级电路网表。接下来,再用专用集成电路ASIC或FPGA自动布局布线工具,把网表转换为要实现的具体电路结构。

Verilog语言最初是于1983年由Gateway Design Automation公司为其模拟器产品开发的硬件建模语言。由于他们的模拟、仿真器产品的广泛使用,Verilog HDL作为一种便于使用且实用的语言逐渐为众多设计者所接受。在一次努力增加语言普及性的活动中,Verilog HDL语言于1990年被推向公众领域。Verilog语言于1995年成为IEEE标准,称为IEEE Std1364-1995,也就是通常所说的Verilog-95。

设计人员在使用Verilog-95的过程中发现了一些可改进之处。为了解决用户在使用此版本Verilog过程中反映的问题,Verilog进行了修正和扩展,这个扩展后的版本后来成为了电气电子工程师学会Std1364-2001标准,即通常所说的Verilog-2001。Verilog-2001是对Verilog-95的一个重大改进版本,它具备一些新的实用功能,例如敏感列表、多维数组、生成语句块、命名端口连接等。目前,Verilog-2001是Verilog的最主流版本,被大多数商业电子设计自动化软件支持。

1.1.2 为什么需要Verilog
在FPGA设计里面,我们有多种设计方式,如原理图设计方式、编写描述语言(代码)等方式。一开始很多工程师对原理图设计方式很钟爱,这种输入方式能够很直观的看到电路结构并快速理解,但是随着电路设计规模的不断增加,逻辑电路设计也越来越复杂,这种设计方式已经越来越不满足实际的项目需求了。这个时候Verilog语言就取而代之了,目前Verilog已经在FPGA开发/IC设计领域占据绝对的领导地位。

1.1.3 Verilog和VHDL区别
这两种语言都是用于数字电路系统设计的硬件描述语言,而且都已经是IEEE的标准。 VHDL 1987年成为标准,而Verilog是1995年才成为标准的。这是因为VHDL是美国军方组织开发的,而Verilog是由一个公司的私有财产转化而来。为什么Verilog能成为IEEE标准呢?它一定有其独特的优越性才行,所以说Verilog有更强的生命力。

这两者有其共同的特点:
1. 能形式化地抽象表示电路的行为和结构;
2. 支持逻辑设计中层次与范围地描述;
3. 可借用高级语言地精巧结构来简化电路行为和结构;
4. 支持电路描述由高层到低层的综合转换;
5. 硬件描述和实现工艺无关。

但是两者也各有特点。Verilog推出已经有20年了,拥有广泛的设计群体,成熟的资源,且Verilog容易掌握,只要有C语言的编程基础,通过比较短的时间,经过一些实际的操作,可以在1个月左右掌握这种语言。而VHDL设计相对要难一点,这个是因为VHDL不是很直观,一般认为至少要半年以上的专业培训才能掌握。

近10年来,EDA界一直在对数字逻辑设计中究竟用哪一种硬件描述语言争论不休,目前在美国,高层次数字系统设计领域中,应用Verilog和VHDL的比率是80%和20%;日本与中国台湾和美国差不多;而在欧洲VHDL发展的比较好;在中国很多集成电路设计公司都采用Verilog。我们推荐大家学习Verilog,本教程全部的例程都是使用Verilog开发的。

1.1.4 Verilog和C的区别
Verilog是硬件描述语言,在编译下载到FPGA之后,会生成电路,所以Verilog全部是并行处理与运行的;C语言是软件语言,编译下载到单片机/CPU之后,还是软件指令,而不会根据你的代码生成相应的硬件电路,而单片机/CPU处理软件指令需要取址、译码、执行,是串行执行的。

Verilog和C的区别也是FPGA和单片机/CPU的区别,由于FPGA全部并行处理,所以处理速度非常快,这个是FPGA的最大优势,这一点是单片机/CPU替代不了的。

1.2 Verilog基础知识
本节主要讲解了Verilog的基础知识,包括5个小节,下面我们分别给大家介绍这5个小节的内容。

1.2.1 Verilog的逻辑值
我们先看下逻辑电路中有四种值,即四种状态:
逻辑 0:表示低电平,也就是对应我们电路的GND;
逻辑 1:表示高电平,也就是对应我们电路的VCC;
逻辑 X:表示未知,有可能是高电平,也有可能是低电平;
逻辑 Z:表示高阻态,外部没有激励信号是一个悬空状态。

如下图所示:                           
image001.png
图 7.2.1 Verilog逻辑值

1.2.2 Verilog的标识符
定义
标识符(identifier)用于定义模块名、端口名和信号名等。Verilog的标识符可以是任意一组字母、数字、$和_(下划线)符号的组合,但标识符的第一个字符必须是字母或者下划线。另外,标识符是区分大小写的。以下是标识符的几个例子:
Count
COUNT //与Count不同。
R56_68
FIVE$

虽然标识符写法很多,但是要简洁、清晰、易懂,推荐写法如下:
count
fifo_wr

不建议大小写混合使用,普通内部信号建议全部小写,参数定义建议大写,另外信号命名最好体现信号的含义。

规范建议
以下是一些书写规范的要求:
1、用有意义的有效的名字如sum、cpu_addr等。
2、用下划线区分词语组合,如cpu_addr。
3、采用一些前缀或后缀,比如:时钟采用clk前缀:clk_50m,clk_cpu;低电平采用_n后缀:enable_n;
4、统一缩写,如全局复位信号rst。
5、同一信号在不同层次保持一致性,如同一时钟信号必须在各模块保持一致。
6、自定义的标识符不能与保留字(关键词)同名。
7、参数统一采用大写,如定义参数使用SIZE。

1.2.3 Verilog的数字进制格式
Verilog数字进制格式包括二进制、八进制、十进制和十六进制,一般常用的为二进制、十进制和十六进制。
二进制表示如下:4’b0101表示4位二进制数字0101;
十进制表示如下:4’d2表示4位十进制数字2(二进制0010);
十六进制表示如下:4’ha表示4位十六进制数字a(二进制1010),十六进制的计数方式为0,1,2…9,a,b,c,d,e,f,最大计数为f(f:十进制表示为15)。
当代码中没有指定数字的位宽与进制时,默认为32位的十进制,比如100,实际上表示的值为32’d100。

1.2.4 Verilog的数据类型
在Verilog语法中,主要有三大类数据类型,即寄存器类型、线网类型和参数类型。从名称中,我们可以看出,真正在数字电路中起作用的数据类型应该是寄存器类型和线网类型。

1)       寄存器类型
寄存器类型表示一个抽象的数据存储单元,它只能在always语句和initial语句中被赋值,并且它的值从一个赋值到另一个赋值过程中被保存下来。如果该过程语句描述的是时序逻辑,即always语句带有时钟信号,则该寄存器变量对应为寄存器;如果该过程语句描述的是组合逻辑,即always语句不带有时钟信号,则该寄存器变量对应为硬件连线;寄存器类型的缺省值是x(未知状态)。

寄存器数据类型有很多种,如reg、integer、real等,其中最常用的就是reg类型,它的使用方法如下:
  1. //regdefine
  2. reg  [31:0]  delay_cnt;    //延时计数器
  3. reg          key_flag ;    //按键标志
复制代码
2)       线网类型
线网表示Verilog结构化元件间的物理连线。它的值由驱动元件的值决定,例如连续赋值或门的输出。如果没有驱动元件连接到线网,线网的缺省值为z(高阻态)。线网类型同寄存器类型一样也是有很多种,如tri和wire等,其中最常用的就是wire类型,它的使用方法如下:
  1. //wiredefine
  2. wire         data_en;        //数据使能信号
  3. wire  [7:0]  data   ;        //数据
复制代码
3)       参数类型
我们再来看下参数类型,参数其实就是一个常量,常被用于定义状态机的状态、数据位宽和延迟大小等,由于它可以在编译时修改参数的值,因此它又常被用于一些参数可调的模块中,使用户在实例化模块时,可以根据需要配置参数。在定义参数时,我们可以一次定义多个参数,参数与参数之间需要用逗号隔开。这里我们需要注意的是参数的定义是局部的,只在当前模块中有效。它的使用方法如下:
  1. //parameterdefine
  2. parameter    DATA_WIDTH = 8;  //数据位宽为8位
复制代码

1.2.5 Verilog的运算符
大家看完了Verilog的数据类型,我们再来介绍下Verilog的运算符。Verilog中的运算符按照功能可以分为下述类型:1、算术运算符、 2、关系运算符、3、逻辑运算符、 4、条件运算符、 5、位运算符、 6、移位运算符、 7、拼接运算符。下面我们分别对这些运算符进行介绍。

1)       算术运算符
算术运算符,简单来说,就是数学运算里面的加减乘除,数字逻辑处理有时候也需要进行数字运算,所以需要算术运算符。常用的算术运算符主要包括加减乘除和模除(模除运算也叫取余运算)如下表所示:
QQ截图20231027104538.png
表 7.2.1 算术运算符

大家要注意下,Verilog实现乘除比较浪费组合逻辑资源,尤其是除法。一般2的指数次幂的乘除法使用移位运算来完成运算,详情可以看移位运算符章节。非2的指数次幂的乘除法一般是调用现成的IP,QUARTUS/ISE等工具软件会有提供,不过这些工具软件提供的IP也是由最底层的组合逻辑(与或非门等)搭建而成的。

2)       关系运算符
关系运算符主要是用来做一些条件判断用的,在进行关系运算符时,如果声明的关系是假的,则返回值是0,如果声明的关系是真的,则返回值是1;所有的关系运算符有着相同的优先级别,关系运算符的优先级别低于算术运算符的优先级别如下表所示。
QQ截图20231027104805.png
表 7.2.2 关系运算符

3)       逻辑运算符
逻辑运算符是连接多个关系表达式用的,可实现更加复杂的判断,一般不单独使用,都需要配合具体语句来实现完整的意思,如下表所示。
QQ截图20231027104810.png
表 7.2.3 逻辑运算符

4)       条件运算符
条件操作符一般来构建从两个输入中选择一个作为输出的条件选择结构,功能等同于 always中的if-else语句,如下表所示。
QQ截图20231027104816.png
表 7.2.4 条件运算符
5)       位运算符
位运算符是一类最基本的运算符,可以认为它们直接对应数字逻辑中的与、或、非门等逻辑门。常用的位运算符如下表所示。
QQ截图20231027104832.png
表 7.2.5 位运算符

位运算符的与、或、非与逻辑运算符逻辑与、逻辑或、逻辑非使用时候容易混淆,逻辑运算符一般用在条件判断上,位运算符一般用在信号赋值上。

6)       移位运算符
移位运算符包括左移位运算符和右移位运算符,这两种移位运算符都用0来填补移出的空位。如下表所示。
QQ截图20231027104839.png
表 7.2.6 移位运算符

假设a有8bit数据位宽,那么a<<2,表示a左移2bit,a还是8bit数据位宽,a的最高2bit数据被移位丢弃了,最低2bit数据固定补0。如果a是3(二进制:00000011),那么3左移2bit,3<<2,就是12(二进制:00001100)。一般使用左移位运算代替乘法,右移位运算代替除法,但是这种也只能表示2的指数次幂的乘除法。

7)       拼接运算符
Verilog中有一个特殊的运算符是C语言中没有的,就是位拼接运算符。用这个运算符可以把两个或多个信号的某些位拼接起来进行运算操作。如下表所示。
QQ截图20231027104846.png
表 7.2.7 位拼接运算符

8)       运算符的优先级
介绍完了这么多运算符,大家可能会想到究竟哪个运算符高,哪个运算符低。为了便于大家查看这些运算符的优先级,我们将它们制作成了表格,如下表所示。
QQ截图20231027104857.png
表 7.2.8 运算符的优先级

1.3 Verilog程序框架
在介绍Verilog程序框架之前,我们先来看下Verilog一些基本语法,基础语法主要包括注释和关键字。

1.3.1 注释
VerilogHDL中有两种注释的方式,一种是以“/*”符号开始,“*/”结束,在两个符号之间的语句都是注释语句,因此可扩展到多行。如:
  1. /*statement1 ,
  2. statement2,
  3. ......
  4. statementn*/
复制代码
以上n个语句都是注释语句。
另一种是以//开头的语句,它表示以//开始到本行结束都属于注释语句。如:
  1. //statement1
复制代码
我们建议的写法:使用//作为注释。

1.3.2 关键字
Verilog和C语言类似,都因编写需要定义了一系列保留字,叫做关键字(或关键词)。这些保留字是识别语法的关键。我们给大家列出了Verilog中的关键字,如下表所示。
QQ截图20231027104917.png
表 7.3.1 Verilog的所有关键字

虽然上表列了很多,但是实际经常使用的不是很多,实际经常使用的主要如下表所示。
QQ截图20231027104926.png
表 7.3.2 Verilog常用的关键字

注意只有小写的关键字才是保留字。例如,标识符always(这是个关键词)与标识符ALWAYS(非关键词)是不同的。

1.3.3 程序框架
我们以LED流水灯程序为例来给大家展示Verilog的程序框架,代码如下所示(注意:代码中前面的行号只是为了方便大家阅读代码与快速定位到行号的位置,在实际编写代码时不可以添加行号,否则编译代码时会报错)。
  1. 1  module led(
  2. 2      input               sys_clk  ,  //系统时钟
  3. 3      input               sys_rst_n,  //系统复位,低电平有效
  4. 4      output  reg  [3:0]  led        //4位LED灯
  5. 5      );
  6. 6  
  7. 7  //parameter define
  8. 8  parameter  WIDTH    = 25        ;
  9. 9  parameter  COUNT_MAX = 25_000_000;  //板载50M时钟=20ns,0.5s/20ns=25000000,需要25bit
  10. 10                                     //位宽
  11. 11
  12. 12 //reg define
  13. 13 reg    [WIDTH-1:0]  counter    ;
  14. 14 reg    [1:0]       led_ctrl_cnt;
  15. 15
  16. 16 //wire define
  17. 17 wire                counter_en  ;
  18. 18
  19. 19 //***********************************************************************************
  20. 20 //**                                 main code
  21. 21 //***********************************************************************************
  22. 22
  23. 23 //计数到最大值时产生高电平使能信号
  24. 24 assign  counter_en = (counter == (COUNT_MAX - 1'b1))  ?  1'b1  :  1'b0;  
  25. 25                                                                                                                                                                                                                        
  26. 26 //用于产生0.5秒使能信号的计数器
  27. 27 always @(posedge sys_clk or negedge sys_rst_n) begin
  28. 28     if (sys_rst_n == 1'b0)
  29. 29         counter <= 1'b0;
  30. 30     else if (counter_en)
  31. 31         counter <= 1'b0;
  32. 32     else
  33. 33         counter <= counter + 1'b1;
  34. 34 end
  35. 35
  36. 36 //led流水控制计数器
  37. 37 always @(posedge sys_clk or negedge sys_rst_n) begin
  38. 38     if (sys_rst_n == 1'b0)
  39. 39         led_ctrl_cnt <= 2'b0;
  40. 40     else if (counter_en)
  41. 41         led_ctrl_cnt <= led_ctrl_cnt + 2'b1;
  42. 42 end
  43. 43
  44. 44 //通过控制IO口的高低电平实现发光二极管的亮灭
  45. 45 always @(posedge sys_clk or negedge sys_rst_n) begin
  46. 46     if (sys_rst_n == 1'b0)
  47. 47         led <= 4'b0;
  48. 48     else begin
  49. 49         case (led_ctrl_cnt)                 
  50. 50             2'd0 : led <= 4'b0001;
  51. 51             2'd1 : led <= 4'b0010;
  52. 52             2'd2 : led <= 4'b0100;
  53. 53             2'd3 : led <= 4'b1000;
  54. 54             default : ;
  55. 55         endcase
  56. 56     end
  57. 57 end
  58. 58
  59. 59 endmodule
复制代码
首先//开头的都是注释,这个之前我们讲解过了。下面我们来看下具体的解释。

第1行为模块定义,模块定义以module开始,endmodule结束,如59行所示。

其次2到5行为端口定义,需要定义led模块的输入信号和输出信号,此处输入信号为系统时钟和复位信号,输出为led控制信号。

7到9行为参数parameter定义,语法如7到9行所示,定义parameter的好处是可以灵活改变参数数字就能控制一些计数器最大计数值或者信号位宽的最大位宽。

12到14行为reg信号定义,reg信号一般情况下代表寄存器,比如此处控制0.5秒使能信号的计数器counter。

16到17行为wire信号定义,wire信号就是硬件连线,比如此处的counter_en,代表计数到最大值时产生高电平使能,本质上是一个硬件连线,其实代表的是一些计数器/寄存器做逻辑判断的结果。

19到21行为moudle开始的注释,不添加工具综合也不会报错,但是我们推荐添加,作为一个良好的编程规范。

23到24行为assign语句的样式,条件成立选择1,否则选择0。

26到34行是always语句的样式,27行代表在时钟上升沿或者复位的下降沿进行信号触发。begin/end代表语句的开始和结束。28到33行为if/else语句,和C语言是比较类似的。29行的“<=”标记代表信号是非阻塞赋值,信号赋值有非阻塞赋值和阻塞赋值两个方式,这个我们后面会详细解释。

36和42行也是一个always语句,和26到34行类似。

44和57行也是一个always语句,不过这个always语句中嵌入了一个case语句,case语句的语法如49到55行所示,需要一个case关键字开始,endcase关键字结束,default作为默认分支,和C语言也是类似的。当然case语句也可以用在不带时钟的always语句中,不过本例子的always都是带有时钟的。不带时钟的always和带时钟的always语句的差异这个我们后面也会详细解释。

59行是endmodule标记,代表模块的结束。

在这里需要补充一点的是,一些初学者可能会有这样一个疑问,在always语句中编写if语句或else语句时,后面需要加begin和end吗?其实这个主要看if条件后面跟着几条赋值语句,如果只有一条赋值语句时,if后面可以加begin和end,也可以不加;如果超过一条赋值语句时,就必须加上begin和end。

if条件只有一条赋值语句时,下面两种写法都是可以的,这里更推荐第一种写法,因为第二种写法会占用更多的行号,代码如下所示:
  1. if(en == 1'b1)
  2.     a <= 1'b1;
  3. 或者
  4. if(en == 1'b1) begin
  5.     a <= 1'b1;
  6. end
  7. 对于if条件超过一条赋值语句的情况,必须添加begin和end,代码如下所示:
  8. if(en == 1'b1) begin
  9.     b <= 1'b1;
  10.     c <= 1'b1;
  11. end
复制代码
好了,程序框架就讲解完了,大家是不是觉得也很简单呢?这些都是基本的语法规范,希望大家能记住这些基础的知识点。如果有些地方大家还是觉得比较抽象,很难理解,没有关系,相信大家会在后面的学习中,会慢慢理解的。

1.4 Verilog高级知识点
前几节主要介绍了Verilog一些基础的知识点和程序框架,本节给大家介绍一些高级的知识点。高级知识点包括阻塞赋值和非阻塞赋值、assign和always语句差异、什么是锁存器、状态机、模块化设计等。

1.4.1 阻塞赋值(Blocking)
阻塞赋值,顾名思义,即在一个always块中,后面的语句会受到前语句的影响,具体来说,在同一个always中,一条阻塞赋值语句如果没有执行结束,那么该语句后面的语句就不能被执行,即被“阻塞”。也就是说always块内的语句是一种顺序关系,这里和C语言很类似。符号“=”用于阻塞的赋值(如:b = a;),阻塞赋值“=”在begin和end之间的语句是顺序执行,属于串行语句。

在这里定义两个缩写:
RHS:赋值等号右边的表达式或变量可以写作RHS表达式或RHS变量;
LHS:赋值等号左边的表达式或变量可以写作LHS表达式或LHS变量;

阻塞赋值的执行可以认为是只有一个步骤的操作,即计算RHS的值并更新LHS,此时不允许任何其他语句的干扰,所谓的阻塞的概念就是值在同一个always块中,其后面的赋值语句从概念上来讲是在前面一条语句赋值完成后才执行的。
为了方便大家理解阻塞赋值的概念以及阻塞赋值和非阻塞赋值的区别,我们这里以在时序逻辑下使用阻塞赋值为例来实现这样一个功能:在复位的时候,a=1,b=2,c=3;而在没有复位的时候,a的值清零,同时将a的值赋值给b,b的值赋值给c,代码以及信号波形图如下图所示:
image004.png
图 7.4.1 阻塞赋值代码

image006.png
图 7.4.2 阻塞赋值的信号波形图

代码中使用的是阻塞赋值语句,从波形图中可以看到,在复位的时候(rst_n=0),a=1,b=2,c=3;而结束复位之后(波形图中的0时刻),当clk的上升沿到来时(波形图中的2时刻),a=0,b=0,c=0。这是因为阻塞赋值是在当前语句执行完成之后,才会执行后面的赋值语句,因此首先执行的是a=0,赋值完成后将a的值赋值给b,由于此时a的值已经为0,所以b=a=0,最后执行的是将b的值赋值给c,而b的值已经赋值为0,所以c的值同样等于0。

1.4.2 非阻塞赋值(Non-Blocking)
符号“<=”用于非阻塞赋值(如:b <= a;),非阻塞赋值是由时钟节拍决定,在时钟上升到来时,执行赋值语句右边,然后将begin-end之间的所有赋值语句同时赋值到赋值语句的左边,注意:是begin—end之间的所有语句,一起执行,且一个时钟只执行一次,属于并行执行语句。这个是和C语言最大的一个差异点,大家要逐步理解并行执行的概念。

非阻塞赋值的操作过程可以看作两个步骤:
(1)赋值开始的时候,计算RHS;
(2)赋值结束的时候,更新LHS。

所谓的非阻塞的概念是指,在计算非阻塞赋值的RHS以及LHS期间,允许其它的非阻塞赋值语句同时计算RHS和更新LHS。

我们下面使用非阻塞赋值同样来实现这样一个功能:在复位的时候,a=1,b=2,c=3;而在没有复位的时候,a的值清零,同时将a的值赋值给b,b的值赋值给c,代码以及信号波形图如下图所示:
image008.png
图 7.4.3 非阻塞赋值代码

image010.png
图 7.4.4 非阻塞赋值的信号波形图

代码中使用的是非阻塞赋值语句,从波形图中可以看到,在复位的时候(rst_n=0),a=1,b=2,c=3;而结束复位之后(波形图中的0时刻),当clk的上升沿到来时(波形图中的2时刻),a=0,b=1,c=2。这是因为非阻塞赋值在计算RHS和更新LHS期间,允许其它的非阻塞赋值语句同时计算RHS和更新LHS。在波形图中的2时刻,RHS的表达是0、a、b,分别等于0、1、2,这三条语句是同时更新LHS,所以a、b、c的值分别等于0、1、2。

在了解了阻塞赋值和非阻塞赋值的区别之后,有些朋友可能还是对什么时候使用阻塞赋值,什么时候使用非阻塞赋值有些疑惑,在这里给大家总结如下。

在描述组合逻辑电路的时候,使用阻塞赋值,比如assign赋值语句和不带时钟的always赋值语句,这种电路结构只与输入电平的变化有关系,代码如下:
示例1:assign赋值语句
  1. assign data  = (data_en == 1'b1)  ?  8'd255  : 8'd0;
复制代码
示例2:不带时钟的always语句
  1. always @(*) begin
  2.     if(en) begin
  3. a = a0;
  4. b= b0;
  5.     end
  6.     elsebegin
  7. a = a1;
  8. b= b1;
  9.     end
  10. end
复制代码
在描述时序逻辑的时候,使用非阻塞赋值,综合成时序逻辑的电路结构,比如带时钟的always语句;这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化,代码如下:
示例3:
  1. always @(posedge sys_clk or negedge sys_rst_n) begin
  2. if (!sys_rst_n) begin
  3. a <= 1'b0;
  4. b<= 1'b0;
  5.     end
  6.     else begin
  7. a <= c;
  8. b<= d;
  9.     end
  10. end
复制代码
1.4.3 assign和always区别
assign语句和always语句是Verilog中的两个基本语句,这两个都是经常使用的语句。

assign语句使用时不能带时钟。

always语句可以带时钟,也可以不带时钟。在always不带时钟时,逻辑功能和assign完全一致,都是只产生组合逻辑。比较简单的组合逻辑推荐使用assign语句,比较复杂的组合逻辑推荐使用always语句。示例如下:
  1. 24  assign  counter_en = (counter == (COUNT_MAX - 1'b1))  ?  1'b1  :  1'b0;  
  2. 45  always @(*) begin
  3. 49          case (led_ctrl_cnt)                 
  4. 50              2'd0    : led = 4'b0001;
  5. 51              2'd1   : led = 4'b0010;
  6. 52              2'd2   : led = 4'b0100;
  7. 53              2'd3    : led = 4'b1000;
  8. 54              default : led = 4'b0000;
  9. 55          endcase
  10. 57  end
复制代码
1.4.4 带时钟和不带时钟的always
always语句可以带时钟,也可以不带时钟。在always不带时钟时,逻辑功能和assign完全一致,虽然产生的信号定义还是reg类型,但是该语句产生的还是组合逻辑。
  1. 44  reg   [3:0] led;
  2. 45  always @(*) begin
  3. 49          case (led_ctrl_cnt)                 
  4. 50              2'd0    : led = 4'b0001;
  5. 51              2'd1    : led = 4'b0010;
  6. 52              2'd2    : led = 4'b0100;
  7. 53              2'd3    : led = 4'b1000;
  8. 54              default : led = 4'b0000;
  9. 55          endcase
  10. 57  end
复制代码
在always带时钟信号时,这个逻辑语句才能产生真正的寄存器,如下示例counter就是真正的寄存器。
  1. 26  //用于产生0.5秒使能信号的计数器
  2. 27  always @(posedge sys_clk or negedge sys_rst_n) begin
  3. 28      if (sys_rst_n == 1'b0)
  4. 29         counter <= 1'b0;
  5. 30      else if (counter_en)
  6. 31         counter <= 1'b0;
  7. 32      else
  8. 33         counter <= counter + 1'b1;
  9. 34  end
复制代码
1.4.5 什么是latch
latch是指锁存器,是一种对脉冲电平敏感的存储单元电路。锁存器和寄存器都是基本存储单元,锁存器是电平触发的存储器,寄存器是边沿触发的存储器。两者的基本功能是一样的,都可以存储数据。锁存器是组合逻辑产生的,而寄存器是在时序电路中使用,由时钟触发产生的。

latch的主要危害是会产生毛刺(glitch),这种毛刺对下一级电路是很危险的。并且其隐蔽性很强,不易查出。因此,在设计中,应尽量避免latch的使用。

代码里面出现latch的两个原因是在组合逻辑中,if或者case语句不完整的描述,比如if缺少else分支,case缺少default分支,导致代码在综合过程中出现了latch。解决办法就是if必须带else分支,case必须带default分支。

大家需要注意下,只有不带时钟的always语句if或者case语句不完整才会产生latch,带时钟的语句if或者case语句不完整描述不会产生latch。

下面为缺少else分支的带时钟的always语句和不带时钟的always语句,通过实际产生的电路图可以看到第二个是有一个latch的,第一个仍然是普通的带有时钟的寄存器。
image011.png

image013.png
图 7.4.5 缺少else的带时钟的always语句电路图

image015.png
图 7.4.6 缺少else的不带时钟的always语句电路图

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-25 21:00

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表