• Verilog HDL


     简介

    Verilog HDL的语法与C语言的语法有许多类似的地方,但也有许多不同的地方。我们学习Verilog HDL语法要善于找到不同点,着重理解如:

    • 阻塞〔Blocking〕和非阻塞〔Non-Blocking〕赋值的不同;
    • 顺序块和并行块的不同;
    • 块与块之间的并行执行的概念;
    • task和function的概念等等。

    Verilog HDL还有许多系统函数和任务也是C语言中没有的如:$monitor、$readmemb、$stop等等,而这些系统任务在调试模块的设计中是非常有用的,我们只有通过阅读大量的Verilog调试模块实例,经过长期的实践,经常查阅Verilog语言参考手册才能逐步掌握。

    Verilog HDL是一种用于数字逻辑电路设计的语言。用Verilog HDL描述的电路设计就是该电路的Verilog HDL模型。

    Verilog HDL既是一种行为描述的语言也是一种结构描述的语言。这也就是说,既可以用电路的功能描述也可以用元器件和它们之间的连接来建立所设计电路的Verilog HDL模型。

    Verilog模型可以是实际电路的不同级别的抽象。这些抽象的级别和它们对应的模型类型共有以下五种:

    • 系统级(system):用高级语言结构实现设计模块的外部性能的模型。
    • 算法级(algorithm):用高级语言结构实现设计算法的模型。
    • RTL级(Register Transfer Level):描述数据在寄存器之间流动和如何处理这些数据的模型。
    • 门级(gate-level):描述逻辑门以及逻辑门之间的连接的模型。
    • 开关级(switch-level):描述器件中三极管和储存节点以及它们之间连接的模型。

    一个复杂电路系统的完整Verilog HDL模型是由若干个Verilog HDL模块构成的,每一个模块又可以由若干个子模块构成。其中有些模块需要综合成具体电路,而有些模块只是与用户所设计的模块交互的现存电路或激励信号源。

    利用Verilog HDL语言结构所提供的这种功能就可以构造一个模块间的清晰层次结构来描述极其复杂的大型设计,并对所作设计的逻辑电路进行严格的验证。

    Verilog HDL行为描述语言作为一种结构化和过程性的语言,其语法结构非常适合于算法级和RTL级的模型设计。这种行为描述语言具有以下功能:

    • · 可描述顺序执行或并行执行的程序结构。
    • · 用延迟表达式或事件表达式来明确地控制过程的启动时间。
    • · 通过命名的事件来触发其它过程里的激活行为或停止行为。
    • · 提供了条件、if-else、case、循环程序结构。
    • · 提供了可带参数且非零延续时间的任务(task)程序结构。
    • · 提供了可定义新的操作符的函数结构(function)。
    • · 提供了用于建立表达式的算术运算符、逻辑运算符、位运算符。

    ·Verilog HDL语言作为一种结构化的语言也非常适合于门级和开关级的模型设计。因其结构化的特点又使它具有以下功能:

    1. 提供了完整的一套组合型原语(primitive);
    2. 提供了双向通路和电阻器件的原语;
    3. 可建立MOS器件的电荷分享和电荷衰减动态模型。

    Verilog HDL的构造性语句可以精确地建立信号的模型。这是因为在Verilog HDL中,提供了延迟和输出强度的原语来建立精确程度很高的信号模型。

    信号值可以有不同的的强度,可以通过设定宽范围的模糊值来降低不确定条件的影响。

    Verilog HDL作为一种高级的硬件描述编程语言,有着类似C语言的风格。其中有许多语句如:if语句、case语句等和C语言中的对应语句十分相似。如果读者已经掌握C语言编程的基础,那么学习Verilog HDL并不困难,我们只要对Verilog HDL某些语句的特殊方面着重理解,并加强上机练习就能很好地掌握它,利用它的强大功能来设计复杂的数字逻辑电路。

    简单的Verilog HDL模块

    1.简单的Verilog HDL程序介绍

    下面先介绍几个简单的Verilog HDL程序,然后从中分析Verilog HDL程序的特性。

    例[1]:module...endmodule

    1. module adder ( count,sum,a,b,cin );
    2. input [2:0] a,b;
    3. input cin;
    4. output count;
    5. output [2:0] sum;
    6. assign {count,sum} = a + b + cin;
    7. endmodule

    这个例子通过连续赋值语句描述了一个名为adder的三位加法器可以根据两个三比特数a、b和进位(cin)计算出和(sum)和进位(count)。 从例子中可以看出整个Verilog HDL程序是嵌套在module和 endmodule 声明语句里的。

    例[2]:注释

    1. module compare ( equal,a,b );
    2. output equal; //声明输出信号equal
    3. input [1:0] a,b; //声明输入信号a,b
    4. assign equal=(a==b)?10;
    5. /*如果a、b 两个输入信号相等,输出为1。否则为0*/
    6. endmodule

    这个程序通过连续赋值语句描述了一个名为compare的比较器。对两比特数 a、b 进行比较,如a与b相等,则输出equal为高电平,否则为低电平。在这个程序中,/*........*/和//.........表示注释部分,注释只是为了方便程序员理解程序,对编译是不起作用的。

    例[3]:基本元件

    1. module trist2(out,in,enable);
    2. output out;
    3. input in, enable;
    4. bufif1 mybuf(out,in,enable);
    5. endmodule

    这个程序描述了一个名为trist2的三态驱动器。程序通过调用一个在Verilog语言库中现存的三态驱动器实例元件bufif1来实现其功能。

    例[4]:模块调用(例化)

    1. module trist1(out,in,enable);
    2. output out;
    3. input in, enable;
    4. mytri tri_inst(out,in,enable);
    5. //调用由mytri模块定义的实例元件tri_inst
    6. endmodule
    7. module mytri(out,in,enable);
    8. output out;
    9. input in, enable;
    10. assign out = enable? in : 'bz;
    11. endmodule

    这个程序例子通过另一种方法描述了一个三态门。在这个例子中存在着两个模块。模块trist1调用由模块mytri定义的实例元件tri_inst。模块trist1是顶层模块。模块mytri则被称为子模块。

    通过上面的例子可以看到:

    • · Verilog HDL程序是由模块构成的。每个模块的内容都是嵌在moduleendmodule两个语句之间。每个模块实现特定的功能。模块是可以进行层次嵌套的。正因为如此,才可以将大型的数字电路设计分割成不同的小模块来实现特定的功能,最后通过顶层模块调用子模块来实现整体功能。
    • · 每个模块要进行端口定义,并说明输入输出口,然后对模块的功能进行行为逻辑描述。
    • · Verilog HDL程序的书写格式自由,一行可以写几个语句,一个语句也可以分写多行。
    • · 除了endmodule语句外,每个语句和数据定义的最后必须有分号
    • · 可以用/*.....*///.......对Verilog HDL程序的任何部分作注释。一个好的,有使用价值的源程序都应当加上必要的注释,以增强程序的可读性和可维护性。

    2.模块的结构

    Verilog的基本设计单元是“模块”(block)。一个模块是由两部分组成的,一部分描述接口,另一部分描述逻辑功能,即定义输入是如何影响输出的。下面举例说明:

    1. module block (a,b,c,d);
    2. input a,b;
    3. output c,d;
    4. assign c= a | b ;
    5. assign d= a & b;
    6. endmodule

    请看上面的例子,程序模块下边是一个电路图的符号。在许多方面,程序模块和电路图符号是一致的,这是因为电路图符号的引脚也就是程序模块的接口。而程序模块描述了电路图符号所实现的逻辑功能。上面的Verilog设计中,模块中的第二、第三行说明接口的信号流向,第四、第五行说明了模块的逻辑功能。以上就是设计一个简单的Verilog程序模块所需的全部内容。

    从上面的例子可以看出,Verilog结构完全嵌在moduleendmodule声明语句之间,每个Verilog程序包括四个主要部分:端口定义、I/O说明、内部信号声明、功能定义

    3.模块的端口定义

    模块的端口声明了模块的输入输出口。其格式如下:

    module  模块名(口1,口2,口3,口4, ………);
    

    4.模块内容

    模块的内容包括I/O说明、内部信号声明、功能定义。

    I/O说明的格式如下:

    1. 输入口: input 端口名1,端口名2,………,端口名i; //(共有i个输入口)
    2. 输出口: output 端口名1,端口名2,………,端口名j; //(共有j个输出口)

    I/O说明也可以写在端口声明语句里。其格式如下:

    1. module module_name(input port1,input port2,…
    2. output port1,output port2… );

    内部信号说明:

    在模块内用到的和与端口有关的wire 和 reg 变量的声明。

    如:

    1. reg [width-1 : 0] R变量1,R变量2 。。。。;
    2. wire [width-1 : 0] W变量1,W变量2 。。。。;
    3. ………..

    功能定义:

    模块中最重要的部分是逻辑功能定义部分。有三种方法可在模块中产生逻辑。

    1).用“assign”声明语句

    如: assign a = b & c;
    

    这种方法的句法很简单,只需写一个“assign”,后面再加一个方程式即可。例子中的方程式描述了一个有两个输入的与门

    2).用实例元件

    如: and and_inst( q, a, b );
    

    采用实例元件的方法象在电路图输入方式下,调入库元件一样。键入元件的名字和相连的引脚即可,表示在设计中用到一个跟与门(and)一样的名为and_inst的与门,其输入端为a, b,输出为q。要求每个实例元件的名字必须是唯一的,以避免与其他调用与门(and)的实例混淆。

    3).用“always”块

    如:

    1. always @(posedge clk or posedge clr)
    2. begin
    3. if(clr) q <= 0;
    4. else if(en) q <= d;
    5. end

    采用“assign”语句是描述组合逻辑最常用的方法之一而“always”块既可用于描述组合逻辑也可描述时序逻辑。上面的例子用“always”块生成了一个带有异步清除端的D触发器。“always”块可用很多种描述手段来表达逻辑,例如上例中就用了if...else语句来表达逻辑关系。如按一定的风格来编写“always”块,可以通过综合工具把源代码自动综合成用门级结构表示的组合或时序逻辑电路。

    注意:

    如果用Verilog模块实现一定的功能,首先应该清楚哪些是同时发生的,哪些是顺序发生的。上面三个例子分别采用了“assign”语句、实例元件和“always”块。这三个例子描述的逻辑功能是同时执行的。也就是说,如果把这三项写到一个 VeriIog 模块文件中去,它们的次序不会影响逻辑实现的功能。这三项是同时执行的,也就是并发的

    然而,在“always”模块内,逻辑是按照指定的顺序执行的。“always”块中的语句称为“顺序语句”,因为它们是顺序执行的。请注意,两个或更多的“always”模块也是同时执行的,但是模块内部的语句是顺序执行的。 看一下“always”内的语句,你就会明白它是如何实现功能的。 if..else… if必须顺序执行,否则其功能就没有任何意义。如果else语句在if语句之前执行,功能就会不符合要求!为了能实现上述描述的功能,“always”模块内部的语句将按照书写的顺序执行。

    Verilog语法之二:常量

    Verilog HDL中总共有十九种数据类型,数据类型是用来表示数字电路硬件中的数据储存和传送元素的。我们先只介绍四个最基本的数据类型,它们是:

    reg型、wire型、integer型、parameter型

    其它的类型如下:

    large型、medium型、scalared型、time型、small型、tri型、trio型、tri1型、triand型、trior型、trireg型、vectored型、wand型、wor型。这些数据类型除time型外都与基本逻辑单元建库有关,与系统设计没有很大的关系,我们无需刻意去掌握

    在一般电路设计自动化的环境下,仿真用的基本部件库是由半导体厂家和EDA工具厂家共同提供的。系统设计工程师不必过多地关心门级和开关级的Verilog HDL语法现象。

    一.常量之数字

    1.整数:

    在Verilog HDL中,整型常量即整常数有以下四种进制表示形式:

    1) 二进制整数(b或B)

    2) 十进制整数(d或D)

    3) 十六进制整数(h或H)

    4) 八进制整数(o或O)

    数字表达方式有以下三种:

    1) <位宽><进制><数字>这是一种全面的描述方式。

    2) <进制><数字>在这种描述方式中,数字的位宽采用缺省位宽(这由具体的机器系统决定,但至少32位)。

    3) <数字>在这种描述方式中,采用缺省进制十进制。

    在表达式中,位宽指明了数字的精确位数。例如:一个4位二进制数的数字的位宽为4,一个4位十六进制数的数字的位宽为16(因为每单个十六进制数就要用4位二进制数来表示)。见下例:

    1. 8'b10101100 //位宽为8的数的二进制表示, 'b表示二进制
    2. 8'ha2 //位宽为8的数的十六进制,'h表示十六进制。

    2.x和z值:

    在数字电路中,x代表不定值,z代表高阻值。

    一个x可以用来定义十六进制数的四位二进制数的状态,八进制数的三位,二进制数的一位。

    z的表示方式同x类似。z还有一种表达方式是可以写作?。在使用case表达式时建议使用这种写法,以提高程序的可读性。见下例:

    1. 4'b10x0 //位宽为4的二进制数从低位数起第二位为不定值
    2. 4'b101z //位宽为4的二进制数从低位数起第一位为高阻值
    3. 12'dz //位宽为12的十进制数其值为高阻值(第一种表达方式)
    4. 12'd? //位宽为12的十进制数其值为高阻值(第二种表达方式)
    5. 8'h4x //位宽为8的十六进制数其低四位值为不定值

    3.负数:

    一个数字可以被定义为负数,只需在位宽表达式前加一个减号,减号必须写在数字定义表达式的最前面。注意减号不可以放在位宽和进制之间也不可以放在进制和具体的数之间。见下例:

    1. -8'd5 //这个表达式代表5的补数(用八位二进制数表示)
    2. 8'd-5 //非法格式

    4.下划线(underscore_):

    下划线可以用来分隔开数的表达以提高程序可读性。但不可以用在位宽和进制处,只能用在具体的数字之间。见下例:

    1. 16'b1010_1011_1111_1010 //合法格式
    2. 8'b_0011_1010 //非法格式

    当常量不说明位数时,默认值是32位,每个字母用8位的ASCII值表示。

    例:

    1. 1032'd1032'b1010
    2. 1=32'd1=32'b1
    3. -1=-32'd1=32'hFFFFFFFF
    4. ‘BX=32'BX=32'BXXXXXXX…X
    5. “AB”=16'B01000001_01000010

    二.常量之参数(Parameter)

    在Verilog HDL中用parameter来定义常量,即用parameter来定义一个标识符代表一个常量,称为符号常量,即标识符形式的常量,采用标识符代表一个常量可提高程序的可读性和可维护性。parameter型数据是一种常数型的数据,其说明格式如下:

    parameter 参数名1=表达式,参数名2=表达式, …, 参数名n=表达式;
    

    parameter是参数型数据的确认符,确认符后跟着一个用逗号分隔开的赋值语句表。在每一个赋值语句的右边必须是一个常数表达式。

    也就是说,该表达式只能包含数字或先前已定义过的参数。见下列:

    1. parameter msb=7; //定义参数msb为常量7
    2. parameter e=25, f=29; //定义二个常数参数
    3. parameter r=5.7; //声明r为一个实型参数
    4. parameter byte_size=8, byte_msb=byte_size-1; //用常数表达式赋值
    5. parameter average_delay = (r+f)/2; //用常数表达式赋值

    参数型常数经常用于定义延迟时间和变量宽度。在模块或实例引用时可通过参数传递改变在被引用模块或实例中已定义的参数。下面将通过两个例子进一步说明在层次调用的电路中改变参数常用的一些用法。

    [例]:在引用Decode实例时,D1,D2的Width将采用不同的值4和5,且D1的Polarity将为0。可用例子中所用的方法来改变参数,即用 #(4,0)向D1中传递 Width=4,Polarity=0; 用#(5)向D2中传递Width=5,Polarity仍为1。

    1. module Decode(A,F);
    2. parameter Width=1, Polarity=1;
    3. ……………
    4. endmodule
    5. module Top;
    6. wire[3:0] A4;
    7. wire[4:0] A5;
    8. wire[15:0] F16;
    9. wire[31:0] F32;
    10. Decode #(4,0) D1(A4,F16);
    11. Decode #(5) D2(A5,F32);
    12. endmodule

    [例]:下面是一个多层次模块构成的电路,在一个模块中改变另一个模块的参数时,需要使用defparam命令

    1. module Test;
    2. wire W;
    3. Top T ( );
    4. endmodule
    5. module Top;
    6. wire W
    7. Block B1 ( );
    8. Block B2 ( );
    9. endmodule
    10. module Block;
    11. parameter P = 0;
    12. endmodule
    13. module Annotate;
    14. defparam
    15. Test.T.B1.P = 2,
    16. Test.T.B2.P = 3;
    17. endmodule

     

    Verilog语法之三:变量

    变量即在程序运行过程中其值可以改变的量,在Verilog HDL中变量的数据类型有很多种,这里只对常用的几种进行介绍。

    网络数据类型表示结构实体(例如门)之间的物理连接。网络类型的变量不能储存值,而且它必需受到驱动器(例如门或连续赋值语句,assign)的驱动。

    如果没有驱动器连接到网络类型的变量上,则该变量就是高阻的,即其值为z常用的网络数据类型包括wire型和tri型。这两种变量都是用于连接器件单元,它们具有相同的语法格式和功能。

    之所以提供这两种名字来表达相同的概念是为了与模型中所使用的变量的实际情况相一致。wire型变量通常是用来表示单个门驱动或连续赋值语句驱动的网络型数据,tri型变量则用来表示多驱动器驱动的网络型数据。

    如果wire型或tri型变量没有定义逻辑强度(logic strength),在多驱动源的情况下,逻辑值会发生冲突从而产生不确定值。下表为wire型和tri型变量的真值表。 

    wire/tri01xz
    00xx0
    1x1x1
    xxxxx
    z01xz

    一. wire型

    wire型数据常用来表示用于以assign关键字指定的组合逻辑信号。Verilog程序模块中输入输出信号类型缺省时自动定义为wire型。wire型信号可以用作任何方程式的输入,也可以用作“assign”语句或实例元件的输出。

    wire型信号的格式同reg型信号的很类似。其格式如下:

    wire [n-1:0] 数据名1,数据名2,…数据名i; //共有i条总线,每条总线内有n条线路 
    

    wire [n:1] 数据名1,数据名2,…数据名i; 
    

    wire是wire型数据的确认符,[n-1:0]和[n:1]代表该数据的位宽,即该数据有几位。最后跟着的是数据的名字。如果一次定义多个数据,数据名之间用逗号隔开。声明语句的最后要用分号表示语句结束。看下面的几个例子。

    1. wire a; //定义了一个一位的wire型数据
    2. wire [7:0] b; //定义了一个八位的wire型数据
    3. wire [4:1] c, d; //定义了二个四位的wire型数据

    二. reg型

    寄存器是数据储存单元的抽象。寄存器数据类型的关键字是reg。通过赋值语句可以改变寄存器储存的值,其作用与改变触发器储存的值相当。

    Verilog HDL语言提供了功能强大的结构语句使设计者能有效地控制是否执行这些赋值语句。这些控制结构用来描述硬件触发条件,例如时钟的上升沿和多路器的选通信号。reg类型数据的缺省初始值为不定值,x。

    reg型数据常用来表示用于“always”模块内的指定信号,常代表触发器。通常,在设计中要由“always”块通过使用行为描述语句来表达逻辑关系。在“always”块内被赋值的每一个信号都必须定义成reg型。

    reg型数据的格式如下:

    reg [n-1:0] 数据名1,数据名2,… 数据名i;
    

    reg [n:1]  数据名1,数据名2,… 数据名i;
    

    reg是reg型数据的确认标识符,[n-1:0]和[n:1]代表该数据的位宽,即该数据有几位(bit)。最后跟着的是数据的名字。如果一次定义多个数据,数据名之间用逗号隔开。声明语句的最后要用分号表示语句结束。看下面的几个例子:

    1. reg rega; //定义了一个一位的名为rega的reg型数据
    2. reg [3:0] regb; //定义了一个四位的名为regb的reg型数据
    3. reg [4:1] regc, regd; //定义了两个四位的名为regc和regd的reg型数据

    对于reg型数据,其赋值语句的作用就象改变一组触发器的存储单元的值

    在Verilog中有许多构造(construct)用来控制何时或是否执行这些赋值语句。这些控制构造可用来描述硬件触发器的各种具体情况,如触发条件用时钟的上升沿等,或用来描述具体判断逻辑的细节,如各种多路选择器。

    reg型数据的缺省初始值是不定值x。reg型数据可以赋正值,也可以赋负值。但当一个reg型数据是一个表达式中的操作数时,它的值被当作是无符号值,即正值。例如:当一个四位的寄存器用作表达式中的操作数时,如果开始寄存器被赋以值-1,则在表达式中进行运算时,其值被认为是+15。

    注意:

    reg型只表示被定义的信号将用在“always”块内,理解这一点很重要。并不是说reg型信号一定是寄存器或触发器的输出。虽然reg型信号常常是寄存器或触发器的输出,但并不一定总是这样。

    初学者往往会对wire和reg的用法混淆,下面是对wire和reg用法的总结:

    wire用法总结

    1.wire可以在Verilog中表示任意宽度的单线/总线

    2.wire可以用于模块的输入和输出端口以及一些其他元素并在实际模块声明中

    3.wire不能存储值(无状态),并且不能在always @块内赋值(=或<=)左侧使用。

    4. wire是assign语句左侧唯一的合法类型

    5.wire只能用于组合逻辑

    reg用法总结

    1. 类似于电线,但可以存储信息(有内存,有状态)允许连接到模块的输入端口,但不能连接到实例化的输出

    2. 在模块声明中,reg可以用作输出,但不能用作输入

    3. 在always@(......)语句块内,= 或者 <= 赋值语句的左边必须是是reg变量

    在initial语句块内,= 赋值语句的左边必须是是reg变量

    4. Reg不能用于assign赋值语句的左侧

    5. 当与@(posedge clock)块一起使用时,reg可用于创建寄存器

    6. reg可用于组合逻辑和时序逻辑

    构建一个模块module时

    input必须是wire

    output可以是wire也可以是reg

    inout必须是wire

    例化模块时

    外部连接input端口的可以是wire也可以是reg

    外部连接output端口的必须是wire

    外部连接inout端口的必须是wire

    三. memory型

    Verilog HDL通过对reg型变量建立数组来对存储器建模,可以描述RAM型存储器,ROM存储器和reg文件。数组中的每一个单元通过一个数组索引进行寻址。在Verilog语言中没有多维数组存在。 memory型数据是通过扩展reg型数据的地址范围来生成的。其格式如下:

    reg [n-1:0] 存储器名[m-1:0]

    在这里,reg[n-1:0]定义了存储器中每一个存储单元的大小,即该存储单元是一个n位的寄存器。存储器名后的[m-1:0]或[m:1]则定义了该存储器中有多少个这样的寄存器。最后用分号结束定义语句。下面举例说明:

    reg [7:0] mema[255:0];
    

    这个例子定义了一个名为mema的存储器,该存储器有256个8位的存储器。该存储器的地址范围是0到255。注意:对存储器进行地址索引的表达式必须是常数表达式。

    另外,在同一个数据类型声明语句里,可以同时定义存储器型数据和reg型数据。见下例:

    1. parameter wordsize=16, //定义二个参数。
    2. memsize=256;
    3. reg [wordsize-1:0] mem[memsize-1:0],writereg, readreg;

    尽管memory型数据和reg型数据的定义格式很相似,但要注意其不同之处。如一个由n个1位寄存器构成的存储器组是不同于一个n位的寄存器的。见下例:

    1. reg [n-1:0] rega; //一个n位的寄存器
    2. reg mema [n-1:0]; //一个由n个1位寄存器构成的存储器组

    一个n位的寄存器可以在一条赋值语句里进行赋值,而一个完整的存储器则不行。见下例:

    1. rega =0; //合法赋值语句
    2. mema =0; //非法赋值语句

    如果想对memory中的存储单元进行读写操作,必须指定该单元在存储器中的地址。下面的写法是正确的。

    mema[3]=0; //给memory中的第3个存储单元赋值为0。
    

    进行寻址的地址索引可以是表达式,这样就可以对存储器中的不同单元进行操作。表达式的值可以取决于电路中其它的寄存器的值。例如可以用一个加法计数器来做RAM的地址索引。

     Verilog语法之四:运算符

    Verilog HDL语言的运算符范围很广,其运算符按其功能可分为以下几类:

    1) 算术运算符(+,-,×,/,%)

    2) 赋值运算符(=,<=)

    3) 关系运算符(>,<,>=,<=)

    4) 逻辑运算符(&&,||,!)

    5) 条件运算符(?:)

    6) 位运算符(~,|,^,&,^~)

    7) 移位运算符(<<,>>)

    8) 拼接运算符({ })

    9) 其它

    在Verilog HDL语言中运算符所带的操作数是不同的,按其所带操作数的个数运算符可分为三种:

    1) 单目运算符(unary operator):可以带一个操作数,操作数放在运算符的右边。

    2) 二目运算符(binary operator):可以带二个操作数,操作数放在运算符的两边。

    3) 三目运算符(ternary operator):可以带三个操作,这三个操作数用三目运算符分隔开。

    见下例:

    1. clock = ~clock; // ~是一个单目取反运算符, clock是操作数。
    2. c = a | b; // 是一个二目按位或运算符, a 和 b是操作数。
    3. r = s ? t : u; // ?: 是一个三目条件运算符, s,t,u是操作数。

    下面对常用的几种运算符进行介绍。

    1.算术运算符

    在Verilog HDL语言中,算术运算符又称为二进制运算符,共有下面几种:

    1) + (加法运算符,或正值运算符,如 rega+regb,+3)

    2) - (减法运算符,或负值运算符,如 rega-3,-3)

    3) × (乘法运算符,如rega*3)

    4) / (除法运算符,如5/3)

    5) % (模运算符,或称为求余运算符,要求%两侧均为整型数据。如7%3的值为1)

    在进行整数除法运算时,结果值要略去小数部分,只取整数部分。而进行取模运算时,结果值的符号位采用模运算式里第一个操作数的符号位。见下例。

    1. 模运算表达式 结果 说明
    2. 10%3 1 余数为1
    3. 11%3 2 余数为2
    4. 12%3 0 余数为0即无余数
    5. -10%3 -1 结果取第一个操作数的符号位,所以余数为-1
    6. 11%3 2 结果取第一个操作数的符号位,所以余数为2.

    注意: 在进行算术运算操作时,如果某一个操作数有不确定的值x,则整个结果也为不定值x。

    2.位运算符

    Verilog HDL作为一种硬件描述语言,是针对硬件电路而言的。在硬件电路中信号有四种状态值1,0,x,z.在电路中信号进行与或非时,反映在Verilog HDL中则是相应的操作数的位运算。Verilog HDL提供了以下五种位运算符:

    1) ~ //取反

    2) & //按位与

    3) | //按位或

    4) ^ //按位异或

    5) ^~ //按位同或(异或非)

    说明:

    • 位运算符中除了~是单目运算符以外,均为二目运算符,即要求运算符两侧各有一个操作数.
    • 位运算符中的二目运算符要求对两个操作数的相应位进行运算操作。

    下面对各运算符分别进行介绍:

    1) "取反"运算符~

    ~是一个单目运算符,用来对一个操作数进行按位取反运算。

    其运算规则见下表:

    ~
    10
    01
    xx

    举例说明:

    1. rega='b1010;//rega的初值为'b1010
    2. rega=~rega;//rega的值进行取反运算后变为'b0101

    2) "按位与"运算符&

    按位与运算就是将两个操作数的相应位进行与运算,

    其运算规则见下表:

    &01x
    0000
    101x
    x0xx

    3) "按位或"运算符|

    按位或运算就是将两个操作数的相应位进行或运算。

    其运算规则见下表:

    |01x
    001x
    1111
    xx1x

    4) "按位异或"运算符^(也称之为XOR运算符)

    按位异或运算就是将两个操作数的相应位进行异或运算。

    其运算规则见下表:

    ^01x
    001x
    110x
    xxxx

    5) "按位同或"运算符^~

    按位同或运算就是将两个操作数的相应位先进行异或运算再进行非运算.

    其运算规则见下表:

    ^~01x
    010x
    101x
    xxxx

    6) 不同长度的数据进行位运算

    两个长度不同的数据进行位运算时,系统会自动的将两者按右端对齐.位数少的操作数会在相应的高位用0填满,以使两个操作数按位进行操作.

    3 逻辑运算符

    在Verilog HDL语言中存在三种逻辑运算符:

    1) && 逻辑与

    2) || 逻辑或

    3) ! 逻辑非

    "&&"和"||"是二目运算符,它要求有两个操作数,如(a>b)&&(b>c),(a

    "!"是单目运算符,只要求一个操作数,如!(a>b)。

    下表为逻辑运算的真值表。它表示当a和b的值为不同的组合时,各种逻辑运算所得到的值。

    ab!a!ba&&ba||b

    逻辑运算符中"&&"和"||"的优先级别低于关系运算符,"!" 高于算术运算符。见下例:

    • (a>b)&&(x>y) 可写成: a>b && x>y
    • (a==b)||(x==y) 可写成:a==b || x==y
    • (!a)||(a>b) 可写成: !a || a>b

    为了提高程序的可读性,明确表达各运算符间的优先关系,建议使用括号.

    4.关系运算符

    关系运算符共有以下四种:

    a < b a小于b

    a > b a大于b

    a <= b a小于或等于b

    a >= b a大于或等于b

    在进行关系运算时,如果声明的关系是假的(flase),则返回值是0,如果声明的关系是真的(true),则返回值是1,如果某个操作数的值不定,则关系是模糊的,返回值是不定值。

    所有的关系运算符有着相同的优先级别。关系运算符的优先级别低于算术运算符的优先级别。见下例:

    1. a < size-1 //这种表达方式等同于下面
    2. a < (size-1) //这种表达方式。
    3. size - ( 1 < a ) //这种表达方式不等同于下面
    4. size - 1 < a //这种表达方式。

    从上面的例子可以看出这两种不同运算符的优先级别。当表达式size-(1

    5.等式运算符

    在Verilog HDL语言中存在四种等式运算符:

    1) == (等于)

    2) != (不等于)

    3) === (等于)

    4) !== (不等于)

    这四个运算符都是二目运算符,它要求有两个操作数。"=="和"!="又称为逻辑等式运算符。其结果由两个操作数的值决定。由于操作数中某些位可能是不定值x和高阻值z,结果可能为不定值x。

    而"==="和"!=="运算符则不同,它在对操作数进行比较时对某些位的不定值x和高阻值z也进行比较,两个操作数必需完全一致,其结果才是1,否则为0"==="和"!=="运算符常用于case表达式的判别,所以又称为"case等式运算符"。这四个等式运算符的优先级别是相同的。下面画出==与===的真值表,帮助理解两者间的区别。

    ==01xz
    01000
    10100
    x0010
    z0001
    ===01xz
    010xx
    101xx
    xxxxx
    zxxxx

    下面举一个例子说明“==”和“===”的区别。

    例:

    1. if(A==1’bx) $display(“AisX”); (当A等于X时,这个语句不执行)
    2. if(A===1’bx) $display(“AisX”); (当A等于X时,这个语句执行)

    6.移位运算符

    在Verilog HDL中有两种移位运算符:

    << (左移位运算符) 和 >>(右移位运算符)。

    其使用方法如下:

    1. a >> n;//a右移n位
    2. a << n;//a左移n位

    a代表要进行移位的操作数,n代表要移几位。这两种移位运算都用0来填补移出的空位。下面举例说明:

    1. module shift;
    2. reg [3:0] start, result;
    3. initial
    4. begin
    5. start = 1; //start在初始时刻设为值0001
    6. result = (start<<2);
    7. //移位后,start的值0100,然后赋给result
    8. end
    9. endmodule

    从上面的例子可以看出,start在移过两位以后,用0来填补空出的位。

    进行移位运算时应注意移位前后变量的位数,下面将给出一例。

    例:

    1. 4’b1001<<1 = 5’b10010;
    2. 4’b1001<<2 = 6’b100100;
    3. 1<<6 = 32’b1000000;
    4. 4’b1001>>1 = 4’b0100;
    5. 4’b1001>>4 = 4’b0000;

    7.位拼接运算符(Concatation)

    在Verilog HDL语言有一个特殊的运算符:位拼接运算符{}。用这个运算符可以把两个或多个信号的某些位拼接起来进行运算操作。其使用方法如下:

    {信号1的某几位,信号2的某几位,..,..,信号n的某几位}
    

    即把某些信号的某些位详细地列出来,中间用逗号分开,最后用大括号括起来表示一个整体信号。见下例:

    {a,b[3:0],w,3’b101}
    

    也可以写成为

    {a,b[3],b[2],b[1],b[0],w,1’b1,1’b0,1’b1}
    

    在位拼接表达式中不允许存在没有指明位数的信号。这是因为在计算拼接信号的位宽的大小时必需知道其中每个信号的位宽。

    位拼接还可以用重复法来简化表达式。见下例:

    {4{w}} //这等同于{w,w,w,w}
    

    位拼接还可以用嵌套的方式来表达。见下例:

    {b,{3{a,b}}} //这等同于{b,a,b,a,b,a,b}
    

    用于表示重复的表达式如上例中的4和3,必须是常数表达式。

    8.缩减运算符(reduction operator)

    缩减运算符是单目运算符,也有与或非运算

    其与或非运算规则类似于位运算符的与或非运算规则,但其运算过程不同。位运算是对操作数的相应位进行与或非运算,操作数是几位数则运算结果也是几位数。

    而缩减运算则不同,缩减运算是对单个操作数进行或与非递推运算,最后的运算结果是一位的二进制数。

    缩减运算的具体运算过程是这样的:第一步先将操作数的第一位与第二位进行或与非运算,第二步将运算结果与第三位进行或与非运算,依次类推,直至最后一位。

    例如:

    1. reg [3:0] B;
    2. reg C;
    3. C = &B;

    相当于:

    C =( (B[0]&B[1]) & B[2] ) & B[3];
    

    由于缩减运算的与、或、非运算规则类似于位运算符与、或、非运算规则,这里不再详细讲述,请参照位运算符的运算规则介绍。

    9.优先级别

    下面对各种运算符的优先级别关系作一总结。见下表:

    优先级不用记,原因:

    1.极少在一条语句里用多个操作符,如果用了,说明你的逻辑写的不好

    2.如果有,加括号更保险

    Verilog语法之五:标识符与关键字

    1 标识符

    标识符是用户在描述时给Verilog对象起的名字,比如例子中的模块名huamayi,端口名字x、y、o,以及例化的基本门元件o1、n1、a1等。

    标识符必须以字母(a-z, A-Z)或( _ )开头,后面可以是字母、数字、( $ )或( _ )。如下图中的红字都是用户定义的标识符。

    1. module huamayi(o,x,y);
    2. output o;
    3. input x,y;
    4. wire or_wire,not_wire;
    5. or o1(or_wire,x,y);
    6. not n1(not_wire,y);
    7. and a1(o,or_wire,not_wire);
    8. endmodule

    2 关键字

    在Verilog HDL中,所有的关键词是事先定义好的确认符,用来组织语言结构。关键词是用小写字母定义的,因此在编写原程序时要注意关键词的书写,以避免出错。

    下图中的红字就是一些关键字的例子。

    1. module huamayi(o,x,y);
    2. output o;
    3. input x,y;
    4. wire or_wire,not_wire;
    5. or o1(or_wire,x,y);
    6. not n1(not_wire,y);
    7. and a1(o,or_wire,not_wire);
    8. endmodule

    关键词无需特意无记忆,下面是Verilog HDL中使用的关键词:

    always, and, assign,begin,buf,bufif0,bufif1,case,casex,casez,cmos,deassign, default,defparam,disable,edge,else,end,endcase,endmodule,endfunction,endprimitive, endspecify, endtable, endtask, event, for, force, forever, fork, function,highz0, highz1, if,initial, inout, input,integer,join,large,macromodule,medium,module, nand,negedge,nmos,nor,not,notif0,notifl, or, output, parameter, pmos, posedge, primitive, pull0, pull1, pullup, pulldown, rcmos, reg, releses, repeat, mmos, rpmos, rtran, rtranif0,rtranif1,scalared,small,specify,specparam,strength,strong0, strong1, supply0, supply1, table, task, time, tran, tranif0, tranif1, tri, tri0, tri1, triand, trior, trireg,vectored,wait,wand,weak0,weak1,while, wire,wor, xnor, xor

  • 相关阅读:
    Java中的数组、Set、List、Map类型的互相转换总结
    [xmake]构建静态库和动态库
    Transformer pytorch实现逐行详解
    网络文件传输程序设计(上)
    二叉树的操作
    【实践篇】Redis最强Java客户端(四)之Ression分布式集合使用指南
    常用的Lambda操作
    交换机和路由器技术-28-OSPF的NSSA区域
    LVS负载均衡群集
    未来十年的Python前景会是什么样的?
  • 原文地址:https://blog.csdn.net/m0_61687959/article/details/126136781