FPGA学习----Verilog HDL语法
Posted 鲁棒最小二乘支持向量机
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FPGA学习----Verilog HDL语法相关的知识,希望对你有一定的参考价值。
1 Verilog 概述
Verilog HDL(Hardware Description Language)是在用途最广泛的 C 语言的基础上发展起来的一种硬件描述语言
, 具有灵活性高、 易学易用
等特点。 Verilog HDL 可以在较短的时间内学习和掌握, 目前已经在 FPGA开发/IC 设计领域占据绝对的领导地位。
1.1 Verilog 简介
Verilog 是一种硬件描述语言,以文本形式来描述数字系统硬件的结构和行为的语言,用它可以表示逻辑电路图、逻辑表达式
,还可以表示数字逻辑系统所完成的逻辑功能
。
数字电路设计者利用这种语言,可以从顶层到底层
逐层描述自己的设计思想,用一系列分层次的模块来表示极其复杂的数字系统。然后利用电子设计自动化(EDA)工具,逐层进行仿真验证,再把其中需要变为实际电路的模块组合,经过自动综合工具转换到门级电路网表。接下来,再用专用集成电路 ASIC 或FPGA 自动布局布线工具,把网表转换为要实现的具体电路结构。
1.2 Verilog和VHDL的异同
Verilog和VHDL共同的特点:
- 能形式化地抽象表示
电路的行为和结构
; - 支持逻辑设计中层次与范围地描述;
- 可借用高级语言地精巧结构来简化电路行为和结构;
- 支持电路描述由
高层到低层的综合转换
; - 硬件描述和实现工艺无关。
Verilog和VHDL的区别:
- Verilog 拥有广泛的设计群体,成熟的资源,
Verilog 容易掌握
,只要有 C 语言的编程基础,通过比较短的时间,经过一些实际的操作,可以在 1 个月左右掌握这种语言; - VHDL 设计
相对要难一点
,这个是因为 VHDL 不是很直观,一般认为至少要半年以上的专业培训才能掌握。
1.3 Verilog和C的区别
Verilog和C的区别:
- Verilog 是硬件描述语言,在编译下载到 FPGA 之后,会生成电路,所以 Verilog 全部是
并行处理与运行的
; - C 语言是软件语言,编译下载到单片机/CPU 之后,还是软件指令,单片机/CPU 处理软件指令需要取址、译码、执行,是
串行执行的
。 - Verilog 和 C 的区别也是 FPGA 和单片机/CPU 的区别, 由于 FPGA 全部并行处理, 所以
处理速度非常快
,这个是 FPGA 的最大优势。
1.4 Verilog主要应用
专用集成电路(ASIC),就是具有专门用途和特殊功能的独立集成电路器件。Verilog 作为硬件描述语言,主要用来生成专用集成电路
。主要通过 3 个途径来完成:
1、可编程逻辑器件
FPGA 和 CPLD 是实现这一途径的主流器件。他们直接面向用户,具有极大的灵活性和通用性,实现快捷,测试方便,开发效率高而成本较低。
2、半定制或全定制 ASIC
通俗来讲,就是利用 Verilog 来设计具有某种特殊功能的专用芯片。根据基本单元工艺的差异,又可分为门阵列 ASIC,标准单元 ASIC,全定制 ASIC。
3、混合 ASIC
主要指既具有面向用户的 FPGA 可编程逻辑功能和逻辑资源,同时也含有可方便调用和配置的硬件标准单元模块,如CPU,RAM,锁相环,乘法器等。
1.5 Verilog设计实例
4 位宽 10 进制计数器,代码如下:
module counter10(
//端口定义
input rstn, //复位端,低有效
input clk, //输入时钟
output [3:0] cnt, //计数输出
output cout); //溢出位
reg [3:0] cnt_temp ; //计数器寄存器
always@(posedge clk or negedge rstn) begin
if(! rstn)begin //复位时,计时归0
cnt_temp <= 4'b0 ;
end
else if (cnt_temp==4'd9) begin //计时10个cycle时,计时归0
cnt_temp <=4'b000;
end
else begin //计时加1
cnt_temp <= cnt_temp + 1'b1 ;
end
end
assign cout = (cnt_temp==4'd9) ; //输出周期位
assign cnt = cnt_temp ; //输出实时计时器
endmodule
1.6 Verilog设计方法
设计方法
Verilog 的设计多采用自上而下
的设计方法(top-down)。即先定义顶层模块功能,进而分析要构成顶层模块的必要子模块;然后进一步对各个模块进行分解、设计,直到到达无法进一步分解的底层功能块。这样,可以把一个较大的系统,细化成多个小系统,从时间、工作量上分配给更多的人员去设计,从而提高了设计速度,缩短了开发周期
。
设计流程
Verilog 的设计流程,一般包括以下几个步骤:
- 需求分析:对用户提出的功能要求进行分析理解,做出电路系统的整体规划,形成详细的技术指标,确定初步方案。
- 功能划分:正确地分析电路需求,进行逻辑功能的总体设计,设计整个电路的功能、接口和总体结构,考虑功能模块的划分和设计思路,各子模块的接口和时序(包括接口时序和内部信号的时序)等。
- 文本描述:可以用任意的文本编辑器,也可以用专用的 HDL 编辑环境,对所需求的数字电路进行设计建模,保存为 .v 文件。
- 功能仿真(前仿真):对建模文件进行编译,对模型电路进行功能上的仿真验证,查找设计的错误并修正。此时的仿真验证并没有考虑到信号的延迟等一些 timing 因素,只是验证逻辑上的正确性。
- 逻辑综合:综合(synthesize),就是在标准单元库和特定的设计约束的基础上,将设计的高层次描述(Verilog 建模)转换为门级网表的过程。逻辑综合的目的是产生物理电路门级结构,并在逻辑、时序上进行一定程度的优化,寻求逻辑、面积、功耗的平衡,增强电路的可测试性。但不是所有的 Verilog 语句都是可以综合成逻辑单元的,例如时延语句。
- 布局布线:根据逻辑综合出的网表与约束文件,利用各种基本标准单元库,对门级电路进行布局布线。至此,已经将 Verilog 设计的数字电路,设计成由标准单元库组成的数字电路。
- 时序仿真(后仿真):布局布线后,电路模型中已经包含了时延信息。利用在布局布线中获得的精确参数,用仿真软件验证电路的时序。单元器件的不同、布局布线方案都会给电路的时序造成影响,严重时会出现错误。出错后可能就需要重新修改 RTL(寄存器传输级描述,即 Verilog 初版描述),重复后面的步骤。
- FPGA/CPLD 下载或 ASIC 制造工艺生产:完成上面所有步骤后,就可以通过开发工具将设计的数字电路目标文件下载到 FPGA/CPLD 芯片中,然后在电路板上进行调试、验证。如果要在 ASIC 上实现,则需要制造芯片。一般芯片制造时,也需要先在 FPGA 板卡上进行逻辑功能的验证。
2 Verilog 基础知识
2.1 Verilog的逻辑值
逻辑电路的四种值:
- 逻辑 0:表示低电平,也就是对应电路的 GND;
- 逻辑 1:表示高电平,也就是对应电路的 VCC;
- 逻辑 X:表示未知,有可能是高电平,也有可能是低电平;
- 逻辑 Z:表示高阻态,外部没有激励信号是一个悬空状态。
2.2 Verilog的标识符
标识符(identifier):用于定义模块名、端口名和信号名
等。
- Verilog 的标识符可以是任意一组字母、数字、$和_(下划线)符号的组合;
- 标识符的第一个字符必须是字母或者下划线;
- 标识符是区分大小写的;
- 不建议大小写混合使用,普通内部信号建议全部小写,参数定义建议大写,另外信号命名最好体现信号的含义。
reg [3:0] counter ; //reg 为关键字, counter 为标识符
input clk; //input 为关键字,clk 为标识符
input CLK; //CLK 与 clk是 2 个不同的标识符
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。
2.4 Verilog的数据类型
在 Verilog 语法中,主要有三大类数据类型,即寄存器类型、线网类型和参数类型
。真正在数字电路中起作用的数据类型应该是寄存器类型和线网类型。
(1)寄存器类型
寄存器类型表示一个抽象的数据存储单元,它只能在 always 语句和 initial 语句中被赋值,并且它的值从一个赋值到另一个赋值过程中被保存下来。如果该过程语句描述的是时序逻辑,即 always 语句带有时钟信号, 则该寄存器变量对应为寄存器; 如果该过程语句描述的是组合逻辑, 即 always 语句不带有时钟信号,则该寄存器变量对应为硬件连线;寄存器类型的缺省值是 x(未知状态)。寄存器数据类型有很多种,如 reg、integer、real 等,其中最常用的就是 reg 类型
。
reg clk_temp;
reg flag1, flag2 ;
(2)线网类型
线网表示 Verilog 结构化元件间的物理连线。它的值由驱动元件的值决定,例如连续赋值或门的输出。如果没有驱动元件连接到线网,线网的缺省值为 z(高阻态)。线网类型同寄存器类型一样也是有很多种,如 tri 和 wire 等,其中最常用的就是 wire 类型
。
wire [1:0] results ; //数据
wire data_en ; //数据使能信号
(3)参数类型
参数其实就是一个常量,常被用于定义状态机的状态、数据位宽和延迟大小等,由于它可以在编译时修改参数的值,因此它又常被用于一些参数可调的模块中,使用户在实例化模块
时,可以根据需要配置参数。在定义参数时,可以一次定义多个参数,参数与参数之间需要用逗号隔开。需要注意的是参数的定义是局部的,只在当前模块中有效。
部分实例程序
当位宽大于 1 时,wire 或 reg 即可声明为向量的形式。程序如下:
reg [3:0] counter ; //声明4bit位宽的寄存器counter
wire [32-1:0] gpio_data; //声明32bit位宽的线型变量gpio_data
wire [8:2] addr ; //声明7bit位宽的线型变量addr,位宽范围为8:2
reg [0:31] data ; //声明32bit位宽的寄存器变量data, 最高有效位为0
Verillog 还支持指定 bit 位后固定位宽的向量域选择访问。
- [bit+: width] : 从起始 bit 位开始递增,位宽为 width。
- [bit-: width] : 从起始 bit 位开始递减,位宽为 width。
//下面 2 种赋值是等效的
A = data1[31-: 8] ;
A = data1[31:24] ;
//下面 2 种赋值是等效的
B = data1[0+ : 8] ;
B = data1[0:7] ;
对信号重新进行组合成新的向量时,需要借助大括号。程序如下:
wire [31:0] temp1, temp2 ;
assign temp1 = byte1[0][7:0], data1[31:8]; //数据拼接
assign temp2 = 321'b0; //赋值32位的数值0
在 Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。数组维数没有限制。线网数组也可以用于连接实例模块的端口。数组中的每个元素都可以作为一个标量或者向量,以同样的方式来使用,形如:<数组名>[<下标>]。对于多维数组来讲,需要说明其每一维的索引。程序如下:
integer flag [7:0] ; //8个整数组成的数组
reg [3:0] counter [3:0] ; //由4个4bit计数器组成的数组
wire [7:0] addr_bus [3:0] ; //由4个8bit wire型变量组成的数组
wire data_bit[7:0][5:0] ; //声明1bit wire型变量的二维数组
reg [31:0] data_4d[11:0][3:0][3:0][255:0] ; //声明4维的32bit数据变量数组
存储器变量就是一种寄存器数组,可用来描述 RAM 或 ROM 的行为。程序如下:
reg membit[0:255] ; //256bit的1bit存储器
reg [7:0] mem[0:1023] ; //1Kbyte存储器,位宽8bit
mem[511] = 8'b0 ; //令第512个8bit的存储单元值为0
参数用来表示常量,用关键字 parameter 声明,只能赋值一次。程序如下:
parameter data_width = 10'd32 ;
parameter i=1, j=2, k=3 ;
parameter mem_size = data_width * 10 ;
2.5 Verilog的运算符
Verilog 中的运算符按照功能可以分为下述类型:1、算术运算符、 2、关系运算符、3、逻辑运算符、 4、条件运算符、 5、位运算符、 6、移位运算符、 7、拼接运算符。
(1)算术运算符
算术运算符是数学运算里面的加减乘除,数字逻辑处理有时候也需要进行数字运算,所以需要算术运算符。 常用的算术运算符主要包括加减乘除和模除 (模除运算也叫取余运算) 如下表所示:
符号 | 使用 | 说明 |
---|---|---|
+ | a + b | a加上b |
- | a - b | a减去b |
* | a * b | a乘以b |
/ | a / b | a除以b |
% | a % b | a模除b |
reg [3:0] a, b;
reg [4:0] c ;
a = 4'b0010 ;
b = 4'b1001 ;
c = a+b; //结果为c=b'b1011
c = a/b; //结果为c=4,取整
Verilog 实现乘除比较浪费组合逻辑资源,尤其是除法。一般 2 的指数次幂的乘除法使用移位运算来完成运算,详情可以看移位运算符章节。非 2 的指数次幂的乘除法一般是调用现成的 IP,QUARTUS/ISE 等工具软件会有提供,不过这些工具软件提供的 IP 也是由最底层的组合逻辑(与或非门等)搭建而成的。
(2)关系运算符
关系运算符主要是用来做一些条件判断用的,在进行关系运算符时,如果声明的关系是假的,则返回值是 0,如果声明的关系是真的,则返回值是 1;所有的关系运算符有着相同的优先级别,关系运算符的优先级别低于算术运算符的优先级别,如下表所示:
符号 | 使用 | 说明 |
---|---|---|
> | a > b | a大于b |
< | a < b | a小于b |
>= | a >= b | a大于等于b |
<= | a <= b | a小于等于b |
== | a == b | a等于b |
!= | a != b | a不等于b |
=== | a === b | a全等b |
!== | a !==b | a非全等b |
A = 4 ;
B = 3 ;
X = 3'b1xx ;
A > B //为真
A <= B //为假
A >= Z //为X,不确定
A = 4 ;
B = 8'h04 ;
C = 4'bxxxx ;
D = 4'hx ;
A == B //为真
A == (B + 1) //为假
A == C //为X,不确定
A === C //为假,返回值为0
C === D //为真,返回值为1
(3)逻辑运算符
逻辑运算符是连接多个关系表达式用的,可实现更加复杂的判断,一般不单独使用,都需要配合具体语句来实现完整的意思,如下表所示:
符号 | 使用 | 说明 |
---|---|---|
! | !a | a的非 |
&& | a && b | a与b,如果a和b都为1,a && b结果为1,表示真 |
I I | a I I b | a或b,如果a或者b有一个为1,a I I b结果为1,表示真 |
A = 3;
B = 0;
C = 2'b1x ;
A && B // 为假
A || B // 为真
! A // 为假
! B // 为真
A && C // 为X,不确定
A || C // 为真,因为A为真
(A==2) && (! B) //为真,此时第一个操作数为表达式
(4)条件运算符
条件操作符一般来构建从两个输入中选择一个作为输出的条件选择结构,功能等同于 always 中的if-else 语句,如下表所示:
符号 | 使用 | 说明 |
---|---|---|
?: | a?b:c | 如果a为真,就选择b,否则选择c |
assign hsel = (addr[9:8] == 2'b00) ? hsel_p1 :
(addr[9:8] == 2'b01) ? hsel_p2 :
(addr[9:8] == 2'b10) ? hsel_p3 :
(addr[9:8] == 2'b11) ? hsel_p4 ;
(5)位运算符
位运算符是一类最基本的运算符,可以认为它们直接对应数字逻辑中的与、或、非门等逻辑门。常用的位运算符如下表所示:
符号 | 使用 | 说明 |
---|---|---|
~ | ~a | 将a的每个位进行取反 |
& | a & b | 将a的每个位与b相同的位进行相与 |
I | a I b | 将a的每个位与b相同的位进行相或 |
^ | a ^ b | 将a的每个位与b相同的位进行相异或 |
位运算符的与、或、非与逻辑运算符逻辑与、逻辑或、逻辑非使用时候容易混淆,逻辑运算符一般用在条件判断上,位运算符一般用在信号赋值上。
A = 4'b0101 ;
B = 4'b1001 ;
C = 4'bx010 ;
~A //4'b1010
A & B //4'b0001
A | B //4'b1101
A^B //4'b1100
A ~^ B //4'b0011
B | C //4'b1011
B&C //4'bx000
(6)移位运算符
移位运算符包括左移位运算符和右移位运算符,这两种移位运算符都用 0 来填补移出的空位。如下表所示:
符号 | 使用 | 说明 |
---|---|---|
<< | a<<b | 将a左移b位 |
<< | a>>b | 将a右移b位 |
A = 4'b1100 ;
B = 4'b0010 ;
A = A >> 2 ; //结果为 4'b0011
A = A << 1; //结果为 4'b1000
A = A <<< 1 ; //结果为 4'b1000
C = B + (A>>>2); //结果为 2 + (-4/4) = 1, 4'b0001
假设 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 语言中没有的,就是位拼接运算符。用这个运算符可以把两个或多个信号的某些位拼接起来进行运算操作。如下表所示:
符号 | 使用 | 说明 |
---|---|---|
a,b | 将a和b拼接起来,作为一个新信号 |
A = 4'b1010 ;
B = 1'b1 ;
Y1 = B, A[3:2], A[0], 4'h3 ; //结果为Y1='b1100_0011
Y2 = 4B, 3'd4; //结果为 Y2=7'b111_1100
Y3 = 321'b0; //结果为 Y3=32h0,常用作寄存器初始化时匹配位宽的赋初值
(8)运算符的优先级
运算符优先级,如下表所示:
运算符 | 优先级 |
---|---|
!、~ | 最高 |
*、/、% | 次高 |
+、- | |
<<、>> | |
<、<=、>、>= | |
==、! =、 ===、! == | |
& | |
^、 ^~ | |
I | |
&& | |
I I | 次低 |
? | 最低 |
3 Verilog的程序框架
3.1 程序注释
Verilog HDL 中有两种注释的方式,一种是以“/ * ”符号开始,“ * /”结束,在两个符号之间的语句都是注释语句,因此可扩展到多行。另一种是以//开头的语句,它表示以//开始到本行结束都属于注释语句。
3.2 关键字
Verilog 和 C 语言类似,都因编写需要定义了一系列保留字,叫做关键字(或关键词)
。这些保留字是识别语法的关键,Verilog所有关键字如下表所示:
and | always | assign | begin | buf |
---|---|---|---|---|
bufif0 | bufif1 | case | casex | casez |
cmos | deassign | default | defparam | disable |
edge | else | end | endcase | endfunction |
endprimitive | endmodule | endspecify | endtable | endtask |
event | for | force | forever | fork |
function | highz0 | highz1 | if | ifnone |
initial | inout | input | integer | join |
large | macromodule | medium | module | nand |
negedge | nor | not | notif0 | notif1 |
nmos | or | output | parameter | pmos |
posedge | primitive | pulldown | pullup | pull0 |
pull1 | rcmos | real | realtime | reg |
release | repeat | rnmos | rpmos | rtran |
rtranif0 | rtranif1 | scalared | small | specify |
specparam | strength | strong0 | strong1 | supply0 |
supply1 | table | task | tran | tranif0 |
tranif1 | time | tri | triand | trior |
trireg | tri0 | tri1 | vectored | wait |
wand | weak0 | weak1 | while | wire |
wor | xnor | xor |
经常使用的关键字主要如下表所示:
关键字 | 含义 |
---|---|
module | 模块开始定义 |
input | 输入端口定义 |
output | 输出端口定义 |
inout | 双向端口定义 |
parameter | 信号的参数定义 |
wire | wire信号定义 |
reg | reg信号定义 |
always | 产生reg信号语句的关键字 |
assign | 产生wire信号语句的关键字 |
begin | 语句的起始标志 |
end | 语句的结束标志 |
posedge/ negedge | 时序电路的标志 |
case | Case语句起始标记 |
default | Case语句的默认分支标志 |
endcase | Case语句结束标记 |
if | if/else语句标记 |
else | if/else语句标记 |
for | for语句标记 |
endmodule | 模块结束定义 |
3.3 编译指令
以反引号“ ` ”开始的某些标识符是 Verilog 系统编译指令。编译指令为 Verilog 代码的撰写、编译、调试等提供了极大的便利。
`define
在编译阶段, `define 用于文本替换,类似于 C 语言中的 #define。一旦 指令被编译,其在整个编译过程中都会有效。
`undef
用来取消之前的宏定义
`include
使用 `include 可以在编译时将一个 Verilog 文件内嵌到另一个 Verilog 文件中,作用类似于 C 语言中的 #include 结构。该指令通常用于将全局或公用的头文件包含在设计文件里。
`timescale
在 Verilog 模型中,时延有具体的单位时间表述,并用 `timescale 编译指令将时间单位与实际时间相关联。该指令用于定义时延、仿真的单位和精度,格式为:
`timescale time_unit / time_precision
time_unit 表示时间单位,time_precision 表示时间精度,它们均是由数字以及单位 s(秒),ms(毫秒),us(微妙),ns(纳秒),ps(皮秒)和 fs(飞秒)组成。时间精度可以和时间单位一样,但是时间精度大小不能超过时间单位大小。
`timescale 的时间精度设置是会影响仿真时间的。时间精度越小,仿真时占用内存越多,实际使用的仿真时间就越长。所以如果没有必要,应尽量将时间精度设置的大一些。
`default_nettype
该指令用于为隐式的线网变量指定为线网类型,即将没有被声明的连线定义为线网类型。
`resetall
该编译器指令将所有的编译指令重新设置为缺省值,可以使得缺省连线类型为线网类型。
`celldefine, `endcelldefine
这两个程序指令用于将模块标记为单元模块,他们包含模块的定义。例如一些与、或、非门,一些 PLL 单元,PAD 模型,以及一些 Analog IP 等。
`unconnected_drive, `nounconnected_drive
在模块实例化中,出现在这两个编译指令间的任何未连接的输入端口,为正偏电路状态或者为反偏电路状态。
3.4 程序框架
以 LED 闪烁程序展示 Verilog 的程序框架
module led_twinkle(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位,低电平有效
output [1:0] led //2位LED灯
);
reg [25:0] cnt;
//对计数器的值进行判断,以输出 LED 的状态
assign led = (cnt < 26'd2500_0000) ? 2'b01 : 2'b10 ;//板载50M时钟=20ns,0.5s/20ns=25000000,需要25bit
//计数器在 0~5000_000 之间进行计数
always @ ( posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 26'd0;
else if(cnt < 26'd5000_0000)
cnt <= cnt + 1'b1;
else
cnt <= 26'd0;
end
ila_0 u_ila_0 (
.clk(clk), // input wire clk
.probe0(probe0), // input wire [0:0] probe0
.probe1(probe1), // input wire [1:0] probe1
.probe2(probe2) // input wire [25:0] probe2
);
endmodule
if 条件后面如果只有一条赋值语句时,if 后面可以加 begin 和 end,也可以不加;如果超过一条赋值语句时,就必须加上 begin 和 end。
4 Verilog 高级知识点(一)
4.1 阻塞赋值(Blocking)
阻塞赋值,即在一个 always 块中,后面的语句会受到前语句的影响,具体来说,在同一个always 中,一条阻塞赋值语句如果没有执行结束,那么该语句后面的语句就不能被执行,即被“阻塞”。也就是说 always 块内的语句是一种顺序关系,这里和 C 语言很类似。符号“=” 用于阻塞的赋值
(如:b = a;),阻塞赋值“=”在 begin 和 end 之间的语句是顺序执行,属于串行语句。
- RHS:赋值等号右边的表达式或变量可以写作 RHS 表达式或 RHS 变量;
- LHS:赋值等号左边的表达式或变量可以写作 LHS 表达式或 LHS 变量;
阻塞赋值的执行可以认为是只有一个步骤的操作,即计算 RHS 的值并更新 LHS,此时不允许任何其他语句的干扰,所谓的阻塞的概念就是值在同一个 always 块中,其后面的赋值语句从概念上来讲是在前面一条语句赋值完成后才执行的。
4.2 非阻塞赋值(Non-Blocking)
符号“<=”用于非阻塞赋值(如:b <= a;)
,非阻塞赋值是由时钟节拍决定,在时钟上升到来时,执行赋值语句右边,然后将 begin-end 之间的所有赋值语句同时赋值到赋值语句的左边,注意:是 begin—end 之间的所有语句,一起执行,且一个时钟只执行一次,属于并行执行语句。这个是和 C 语言最大的一个差异点,大家要逐步理解并行执行的概念。
非阻塞赋值的操作过程可以看作两个步骤:
- (1)赋值开始的时候,计算 RHS;
- (2)赋值结束的时候,更新 LHS。
所谓的非阻塞的概念是指, 在计算非阻塞赋值的 RHS 以及 LHS 期间, 允许其它的非阻塞赋值语句同时计算 RHS 和更新 LHS。
在描述组合逻辑电路的时候,使用阻塞赋值,比如 assign 赋值语句和不带时钟的 always 赋值语句,这种电路结构只与输入电平的变化有关系,代码如下:
assign赋值语句
assign data = (data_en == 1'b1) ? 8'd255 : 8'd0;
不带时钟的always语句
always @(*) begin
if (en) begin
a = a0;
b = b0;
end
else begin
a = a1;
b = b1;
end
end
在描述时序逻辑的时候,使用非阻塞赋值,综合成时序逻辑的电路结构,比如带时钟的 always 语句;这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化,代码如下:
always @( posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n ) begin
a < = 1'b0;
b <= 1'b0;
end
else begin
a <= c;
b <= d;
end
使用非阻塞赋值避免竞争冒险:在设计电路时,always 时序逻辑块中多用非阻塞赋值,always 组合逻辑块中多用阻塞赋值;在仿真电路时,initial 块中一般多用阻塞赋值。
4.3 assign 和 always 区别
assign 语句和 always 语句是 Verilog 中的两个基本语句,这两个都是经常使用的语句。
assign 语句使用时不能带时钟
。
always 语句可以带时钟,也可以不带时钟。在 always 不带时钟时,逻辑功能和 assign 完全一致,都是只产生组合逻辑。比较简单的组合逻辑推荐使用 assign 语句,比较复杂的组合逻辑推荐使用 always 语句。
4.4 带时钟 和不带时钟的 always
always 语句可以带时钟,也可以不带时钟
。在 always 不带时钟时,逻辑功能和 assign 完全一致,虽然产生的信号定义还是 reg 类型,但是该语句产生的还是组合逻辑。 在 always 带时钟信号时,这个逻辑语句才能产生真正的寄存器。
4.5 什么是 latch
latch 是指锁存器
,是一种对脉冲电平敏感的存储单元电路。锁存器和寄存器都是基本存储单元,锁存器是电平触发的存储器,寄存器是边沿触发的存储器。两者的基本功能是一样的,都可以存储数据。锁存器是组合逻辑产生的,而寄存器是在时序电路中使用,由时钟触发产生的。
latch 的主要危害是会产生毛刺(glitch)
,这种毛刺对下一级电路是很危险的。并且其隐蔽性很强,不易查出。因此,在设计中,应尽量避免 latch 的使用。 代码里面出现 latch 的两个原因是在组合逻辑中, if 或者 case 语句不完整的描述, 比如 if 缺少 else 分支,case 缺少 default 分支,导致代码在综合过程中出现了 latch。
[从零开始学习FPGA编程-16]:快速入门篇 - 操作步骤2-4- Verilog HDL语言描述语言基本语法(软件程序员和硬件工程师都能看懂)
软件:Quartus II,语言:VHDL或verilog HDL?
[从零开始学习FPGA编程-10]:快速入门篇 - 操作步骤2 - Verilog HDL语言Module与硬件电路对应关系快速概览