ZYNQ之FPGA学习----Verilog HDL语法(1)

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 = {32{1'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 + ba加上b
-a - ba减去b
*a * ba乘以b
/a / ba除以b
%a % ba模除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 > ba大于b
<a < ba小于b
>=a >= ba大于等于b
<=a <= ba小于等于b
==a == ba等于b
!=a != ba不等于b
===a === ba全等b
!==a !==ba非全等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)逻辑运算符
逻辑运算符是连接多个关系表达式用的,可实现更加复杂的判断,一般不单独使用,都需要配合具体语句来实现完整的意思,如下表所示:

符号使用说明
!aa的非
&&a && ba与b,如果a和b都为1,a && b结果为1,表示真
I Ia I I ba或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相同的位进行相与
Ia 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 = {4{B}, 3'd4};  //结果为 Y2=7'b111_1100
Y3 = {32{1'b0}};  //结果为 Y3=32h0,常用作寄存器初始化时匹配位宽的赋初值

(8)运算符的优先级
运算符优先级,如下表所示:

运算符优先级
!、~最高
*、/、%次高
+、-
<<、>>
<、<=、>、>=
==、! =、 ===、! ==
&
^、 ^~
I
&&
I I次低
?最低

3 Verilog的程序框架

3.1 程序注释

Verilog HDL 中有两种注释的方式,一种是以“/ * ”符号开始,“ * /”结束,在两个符号之间的语句都是注释语句,因此可扩展到多行。另一种是以//开头的语句,它表示以//开始到本行结束都属于注释语句。

3.2 关键字

Verilog 和 C 语言类似,都因编写需要定义了一系列保留字,叫做关键字(或关键词)。这些保留字是识别语法的关键,Verilog所有关键字如下表所示:

andalwaysassignbeginbuf
bufif0bufif1casecasexcasez
cmosdeassigndefaultdefparamdisable
edgeelseendendcaseendfunction
endprimitiveendmoduleendspecifyendtableendtask
eventforforceforeverfork
functionhighz0highz1ififnone
initialinoutinputintegerjoin
largemacromodulemediummodulenand
negedgenornotnotif0notif1
nmosoroutputparameterpmos
posedgeprimitivepulldownpulluppull0
pull1rcmosrealrealtimereg
releaserepeatrnmosrpmosrtran
rtranif0rtranif1scalaredsmallspecify
specparamstrengthstrong0strong1supply0
supply1tabletasktrantranif0
tranif1timetritriandtrior
triregtri0tri1vectoredwait
wandweak0weak1whilewire
worxnorxor

经常使用的关键字主要如下表所示:

关键字含义
module模块开始定义
input输入端口定义
output输出端口定义
inout双向端口定义
parameter信号的参数定义
wirewire信号定义
regreg信号定义
always产生reg信号语句的关键字
assign产生wire信号语句的关键字
begin语句的起始标志
end语句的结束标志
posedge/ negedge时序电路的标志
caseCase语句起始标记
defaultCase语句的默认分支标志
endcaseCase语句结束标记
ifif/else语句标记
elseif/else语句标记
forfor语句标记
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。解决办法就是 if 必须带 else 分支,case 必须带default 分支

只有不带时钟的 always 语句 if 或者 case 语句不完整才会产生 latch,带时钟的语句 if或者 case 语句不完整描述不会产生 latch。

4.6 Verilog 时序控制

Verilog 提供了 2 大类时序控制方法:时延控制和事件控制。事件控制主要分为边沿触发事件控制与电平敏感事件控制。

时延控制

基于时延的时序控制出现在表达式中,它指定了语句从开始执行到执行完毕之间的时间间隔。时延可以是数字、标识符或者表达式。根据在表达式中的位置差异,时延控制又可以分为常规时延与内嵌时延。

  • 常规时延:遇到常规延时时,该语句需要等待一定时间,然后将计算结果赋值给目标信号。
  • 内嵌时延:遇到内嵌延时时,该语句先将计算结果保存,然后等待一定的时间后赋值给目标信号。

边沿触发事件控制

在 Verilog 中,事件是指某一个 reg 或 wire 型变量发生了值的变化

  • 一般事件控制:事件控制用符号 @ 表示。语句执行的条件是信号的值发生特定的变化。关键字 posedge 指信号发生边沿正向跳变,negedge 指信号发生负向边沿跳变,未指明跳变方向时,则 2 种情况的边沿变化都会触发相关事件。
  • 命名事件控制:声明 event(事件)类型的变量,并触发该变量来识别该事件是否发生。命名事件用关键字 event 来声明,触发信号用 -> 表示。
  • 敏感列表:当多个信号或事件中任意一个发生变化都能够触发语句的执行时,Verilog 中使用"或"表达式来描述这种情况,用关键字 or 连接多个事件或信号。这些事件或信号组成的列表称为"敏感列表"。当然,or 也可以用逗号 , 来代替。

电平敏感事件控制

前面所讨论的事件控制都是需要等待信号值的变化或事件的触发,使用 @+敏感列表 的方式来表示的。Verilog 中还支持使用电平作为敏感信号来控制时序,即后面语句的执行需要等待某个条件为真。Verilog 中使用关键字 wait 来表示这种电平敏感情况。

致谢领航者ZYNQ开发板,开启FPGA学习之路!

希望本文对大家有帮助,上文若有不妥之处,欢迎指正

分享决定高度,学习拉开差距

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鲁棒最小二乘支持向量机

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值