2.5 Verilog HDL基础
Verilog HDL是目前应用最为广泛的硬件描述语言,1995年被IEEE采纳为国际标准硬件描述语言,至今已公布了Verilog—1995 、Verilog—2001和SystemVerilog—2005三种版本。Verilog HDL可以进行算法级(Algorithm)、寄存器传输级(RTL)、逻辑级(Logic)、门级(Gate)和版图级(Layout)等各个层次的电路设计和描述。采用Verilog HDL进行电路设计与工艺无关性,这使得设计者在进行电路设计时可以不必过多考虑工艺实现的具体细节,设计者只需要利用计算机的强大功能,在EDA工具的支持下,通过Verilog HDL的描述,即可完成数字电路和系统的设计,大大减少了设计者的繁重劳动。
本章介绍Verilog HDL的语言规则、数据类型和语句结构,作为数字逻辑电路设计的基础。
2.5.1 Verilog HDL设计模块的基本结构
Verilog HDL程序设计由模块(module)构成,设计模块的基本结构如图2.28所示。一个完整的Verilog HDL设计模块包括端口定义、I/O声明、变量类型声明和功能描述等4个部分。
图2.28 Verilog HDL程序模块结构
1. 模块端口定义
模块端口定义用来声明电路设计模块的输入/输出端口,端口定义格式如下:
module模块名(端口1,端口2,端口3,…);
在端口定义的圆括弧中,是设计电路模块与外界联系的全部输入/输出端口信号或引脚,它是设计实体对外的一个通信界面,是外界可以看到的部分(不包含电源和接地端),多个端口名之间用“,”分隔。例如,在3人表决器(参见例2.2)的设计中,可以用decide作为设计电路的Verilog HDL设计模块名,f是电路的输出端,a、b和c是电路的输入端,则decide模块的端口定义为:
module decide(f,a,b,c);
说明:Verilog HDL的端口定义、I/O声明和程序语句中的标点符号及圆括弧均要求用半角符号书写。
2. 模块内容
模块内容包括I/O声明、变量类型声明和功能描述。
(1)模块的I/O声明
模块的I/O声明用来声明模块端口定义中各端口数据流动方向,包括:输入(input)、输出(output)和双向(inout)。双向是指既可以作为输入,也可以作为输出的双方向端口。
I/O声明格式如下:
input 端口1,端口2,端口3,…; //声明输入端口 output 端口1,端口2,端口3,…; //声明输出端口
例如,3人表决器的I/O声明为
input a,b,c; output f;
(2)变量类型声明
变量类型声明用来声明设计电路的功能描述中使用的变量的数据类型。变量的数据类型主要有连线(wire)、寄存器(reg)、整型(integer)、实型(real)和时间(time)等,这部分内容将在后续的章节中详细介绍。
(3)功能描述
功能描述是Verilog HDL程序设计中最主要的部分,用来描述设计模块的内部结构和模块端口间的逻辑关系,在电路上相当于器件的内部电路结构。功能描述可以用assign语句、元件例化(instantiate)、always块语句、initial块语句等方法来实现。
2.5.2 Verilog HDL的词法
Verilog HDL源程序由空白符号分隔的词法符号流所组成。词法符号包括空白符、注释、操作符、常数、字符串、标识符和关键词。准确无误地理解和掌握Verilog HDL的词法的规则和用法,对正确地完成Verilog HDL程序设计十分重要。
1. 空白符和注释
Verilog HDL的空白符包括计算机键盘上的空格键、Tab键、换行和换页(ASCII码)符号。空白符用来分隔各种不同的词法符号,合理地使用空白符可以使源程序具有一定的可读性,并反映编程风格。多余的空白符如果不是出现在字符串中,编译源程序时将被忽略。
在Verilog HDL源程序中,注释用来帮助读者理解程序或程序语句,编译源程序时将被忽略。注释分为行注释和块注释两种方式。行注释用符号“//”(两个斜杠)开始,注释到本行结束。例如:
//声明输入端口
是行注释形式。
块注释用“/*”开始,用“*/”结束。块注释可以跨越多行,但它们不能嵌套。例如:
/*input a,b,c; output f; */
是块注释形式。
在Verilog HDL源程序中,注释不仅可以帮助读者理解程序,也可以将某条语句或某段程序用注释方式临时屏蔽起来(不执行),便于调试程序和查错。
2. 常数
Verilog HDL中的常数包括数字、未知x和高阻z三种。数字可以用二进制、十进制、八进制和十六进制等4种不同数制来表示,完整的数字格式为:
<位宽> ’ <进制符号> <数字>
其中,位宽表示数字对应的二进制数的位数宽度(位宽可以省略);进制符号包括b或B(表示二进制数),d或D(表示十进制数),h或H(表示十六进制数),o或O(表示八进制数)。例如,8’b10110001或’b10110001表示位宽为8位的二进制数10110001;8’hf5或’hf5表示位宽为8位的十六进制数f5。
十进制数的位宽和进制符号可以缺省,例如,125表示十进制数125。
另外,用x和z分别表示未知值和高阻值(x和z可以用大写或小写字母书写),它们可以出现在除十进制数以外的数字形式中。x和z的位数由所在的数字格式决定,在二进制数格式中,一个x或z表示1位未知位或1位高阻位;在十六进制数中,一个x或z表示4位未知位或4位高阻位;在八进制数中,一个x或z表示3位未知位或3位高阻位。例如:
’b1111xxxx //等价’hfx ’b1101zzzz //等价’hdz
3. 字符串
字符串是用双引号括起来的可打印字符序列,它必须包含在同一行中。例如,“ABC”,“A BOY.”,“A”,“1234”都是字符串(双引号也是半角符号)。
4. 关键词
关键词(或称为关键字)是Verilog HDL预先定义的单词,它们在程序中有不同的使用目的。例如,module和endmodule用来指出源程序模块的开始和结束;用assign来描述一个逻辑表达式等。Verilog —1995的关键词有97个,Verilog—2001增加了5个,共102个,如表2.14所示。每个关键词全部由小写字母组成,少数关键词中包含“0”或“1”数字。
表2.14 Verilog HDL关键词
5. 标识符
标识符是用户编程时为常量、变量、模块、寄存器、端口、连线、示例和begin-end块等元素定义的名称。标识符可以是字母、数字和下划线“_”等符号组成的任意序列。定义标识符时应遵循如下规则:
① 首字符不能是数字。
② 字符数不能多于1024个。
③ 大小写字母是不同的。
④ 不要与关键词同名。
例如,ina、inb、adder、adder8、name_adder都是正确的标识符;而1a、?b是错误的标识符。
Verilog HDL允许使用转义标识符,转义标识符中可以包含任意的可打印字符,转义标识符从空白符号开始,以反斜杠“\”作为开始标记,到下一个空白符号结束,反斜杠不是标识符的一部分。下面是转义标识符的示例:
\74LS00 \a + b
6. 操作符
操作符也称为运算符,是Verilog HDL预定义的函数符号,这些函数对被操作的对象(即操作数)进行规定的运算,得到一个结果。操作符通常由1~3个字符组成。例如,“ + ”表示加操作,“ = = ”(两个“ = ”字符)表示逻辑等操作,“ = = = ”(3个“ = ”字符)表示全等操作。有些操作符的操作数只有1个,称为单目操作;有些操作符的操作数有2个,称为双目操作;有些操作符的操作数有3个,称为三目操作。
Verilog HDL的操作符有以下9类。
(1)算术操作符(Arithmetic operators)
常用的算术操作符有: +(加)、−(减)、*(乘)、/(除)、%(求余)和**乘方6种。其中%是求余操作符,在两个整数相除的基础上,取出其余数。例如,5 % 6的值为5;13 % 5的值是3。
(2)逻辑操作符(Logical operators)
逻辑操作符包括:&&(逻辑与)、||(逻辑或)、!(逻辑非)。例如,A && B表示A和B进行逻辑与运算;A || B表示A和B进行逻辑或运算;!A表示对A进行逻辑非运算。
(3)位运算(Bitwise operators)
位运算是将两个操作数按对应位进行逻辑操作。位运算操作符包括:~(按位取反)、&(按位与)、|(按位或)、^(按位异或)、^~或~^(按位同或)。例如,设A = ’b11010001,B = ’b00011001,则:
~A = ’b00101110
A & B = ’b00010001
A | B = ’b11011001
A ^ B = ’b11001000
A ^~ B = ’b00110111
在进行位运算时,当两个操作数的位宽不同时,计算机会自动将两个操作数按右端对齐,位数少的操作数会在高位用0补齐。
(4)关系操作符(Relational operators)
关系操作符用来对两个操作数进行比较。关系操作符有: <(小于)、< =(小于等于)、>(大于)、> =(大于等于)。其中,< = 也是赋值运算中的一种赋值符号。
关系运算的结果是1位逻辑值。在进行关系运算时,如果关系成立,则计算结果为“1”,表示“真”;如果关系不成立,则计算结果为“0”,表示“假”;如果某个操作数的值不定,则计算结果为“x”(未知),表示结果是不定或模糊的。
(5)等值操作符(Equality operators)
等值操作符包括:= =(等于)、!=(不等于)、= = =(全等)、!= =(不全等)4种。
等值运算的结果也是1位逻辑值,当运算结果为真时,返回值“1”;为假则返回值“0”。相等操作符(= =)与全等操作符(= = =)的区别是:当进行相等运算时,两个操作数必须逐位相等,其比较结果的值才为“1”(真),如果某些位是不定或高阻状态,其相等比较的结果就会是不定值;而进行全等运算时,对不定或高阻状态位也进行比较,当两个操作数完全一致时,其结果的值才为“1”(真),否则结果为“0”(假)。
例如,设A = ’b1101xx01,B = ’b1101xx01则A = = B的运算的结果为“x”(未知),A = = = B的运算的结果为“1”(真)。
(6)缩减操作符(Reduction operators)
缩减操作符包括:&(缩减与)、~&(缩减与非)、|(缩减或)、~|(缩减或非)、^(缩减异或)、^~或~^(缩减同或)。缩减操作运算法则与逻辑运算操作相同,但操作的运算对象只有一个。在进行缩减操作运算时,对操作数逐位进行与、与非、或、或非、异或、同或等缩减操作运算,运算结果有1位“1”或“0”。例如,设A = ’b11010001,则& A = 0(在与缩减运算中,只有A中的数字全为“1”时,结果才为“1”);|A = 1(在或缩减运算中,只有A中的数字全为“0”时,结果才为“0”)。
(7)转移操作符(Shift operators)
转移操作符包括:>>(右移)、<<(左移)。其使用方法为:
操作数 >> n;//将操作数的内容右移n位,同时从左边开始用0来填补移出的位数。
操作数 << n;//将操作数的内容左移n位,同时从右边开始用0来填补移出的位数。
例如,设A = ’b11010001,则A >> 4的结果是A = ’b00001101;而A << 4的结果是A = ’b00010000。转移操作符常用于移位寄存器的设计。
(8)条件操作符(Conditional operators)
条件操作符为:?:
条件操作符的操作数有3个,其使用格式为:
操作数 = 条件 ? 表达式1 : 表达式2;
即当条件为真(条件结果值为1)时,操作数 = 表达式1;为假(条件结果值为0)时,操作数 = 表达式2。例如:
F = a ? b:c;
上述表达式实现的功能是:当a为“1”(真)时,F = b;当a为“0”(假)时,F = c。
(9)并接操作符(Concatenation operators)
并接操作符为:{}
并接操作符的使用格式为:
{操作数1的某些位,操作数2的某些位,…,操作数n的某些位};
即将操作数1的某些位,与操作数2的某些位,…,与操作数n的某些位并接在一起,构成一个完整的多位数。例如,将d、c、b、a这4个1位二进制变量并接为1个4位二进制数的格式为:{d,c,b,a}。
(10)操作符的优先级
在Verilog HDL中,不同的操作符具有不同的优先等级,如果一个表达式包含多个不同的操作符,则需要按照优先级高的操作符先运算,优先级低的操作符后运算的规则进行,得到相应的结果。Verilog HDL操作符的优先级如表2.15所示。表中顶部的操作符优先级最高,底部的最低,列在同一行的操作符的优先级相同。所有的操作符(?: 操作符除外)在表达式中都是从左向右结合的。圆括弧可以用来改变优先级,并使运算顺序更清晰。对操作符的优先级不能确定时,最好使用圆括弧来确定表达式的优先顺序,既可以避免出错,也可以增加程序的可读性。
表2.15 操作符的优先级
7. Verilog HDL数据对象
Verilog HDL数据对象是指用来存放各种类型数据的容器,包括常量和变量。
(1)常量
常量是一个恒定不变的值数,也称为参数,一般在程序前部定义。常量定义格式为:
parameter常量名1 = 表达式,常量名2 = 表达式,…,常量名n = 表达式;
其中,parameter是常量(参数)定义关键词,常量名是用户定义的标识符,表达式是为常量赋的值。例如:
parameter Vcc = 5, fbus = ’b11010001;
上述语句定义了一个名为Vcc的常量,其值为十进制数5;还定义了另一个常量fbus,其值为二进制数“11010001”。
(2)变量
变量是在程序运行时其值可以改变的量。在Verilog HDL中,变量分为网络型(nets type)和寄存器型(register type)两种。nets型变量是输出值始终根据输入变化而更新的变量,它一般用来定义硬件电路中的各种物理连线。
在nets型变量中,wire型变量是最常用的一种。wire型变量常用来表示以assign语句赋值的组合逻辑变量。在Verilog HDL模块中,输入/输出变量类型默认时自动定义为wire型。wire型变量可以作为任何方程式的输入,也可以作为assign语句和例化元件的输出。wire型变量的取值可以是0、1、x和z。
wire型变量的定义格式如下:
wire变量名1,变量名2,…,变量名n;
例如:
wire a,b,c; //定义了3个wire型的变量,位宽均为1位。 wire[7:0] databus; //定义了1个wire型的数据总线,位宽为8位。 wire[15:0] addrbus; //定义了1个wire型的地址总线,位宽为16位。
register型变量是用来描述硬件系统的基本数据对象的。它作为一种数值容器,不仅可以容纳当前值,也可以保持历史值。变量也是一种连接线,可以作为设计模块中各器件间的信息传送通道。register型变量与wire型变量的根本区别在于register型变量需要被明确地赋值,并且在被重新赋值前一直保持原值。
在Verilog HDL中,register型变量包括有reg(寄存器)、integer(整型)、real(实型)和time(时间)4种,其中integer、real和time类型变量都是纯数学的抽象描述,不对应任何具体的硬件电路,但它们可以描述与模拟有关的计算。例如,可以利用time型变量控制经过特定的时间后关闭显示等。
reg型变量是数字系统中存储设备的抽象,常用于具体的硬件描述,因此是最常用的寄存器型变量。reg型变量定义的关键词是reg,定义格式如下:
reg[位宽] 变量1,变量2,…,变量n;
用reg定义的变量有一个范围选项(即位宽),默认的位宽是1。位宽为1位的变量称为标量,位宽超过1位的变量称为向量。标量的定义不需要加位宽选项,例如:
reg a,b; //定义两个reg型变量a和b(标量)
向量定义时需要位宽选项,例如:
reg[7:0] data; //定义1个8位寄存器型变量,最高有效位是7(27),最低有效位是0(20) reg[0:7] data; //定义1个8位寄存器型变量,最高有效位是0(27),最低有效位是7(20)
向量定义后可以采有多种赋值形式。
为整个向量赋值的形式为:
data = ’b00000000;
为向量的部分位赋值的形式为:
data[5:3]=’b111; //将data的第5、4、3位赋值为“111”
为向量的某一位赋值的形式为:
data[7] = 1;
(3)数组
若干个相同宽度的向量构成数组。在数字系统中,reg型数组变量即为memory(存储器)型变量。例如
reg[7:0]myrom[1023:0]; //定义包含1024个reg型的变量myrom
2.5.3 Verilog HDL的语句
语句是构成Verilog HDL程序不可缺少的部分。Verilog HDL的语句包括赋值语句、条件语句、循环语句、结构声明语句和编译预处理语句等类型,每一类语句又包括几种不同的语句。在这些语句中,有些语句属于顺序执行语句,有些语句属于并行执行语句。
顺序语句与传统的计算机编程语句类似,是按程序书写的顺序自上而下、一条一条地执行的。并行语句是Verilog HDL最具有特色的语句结构。行语句在设计模块中的执行是同步进行的,或者说是并行运行的,其执行方式与语句书写的顺序无关。当多条并行语句都满足执行条件时,它们就同时运行。
1. 赋值语句
在Verilog HDL中,赋值语句常用于描述硬件设计电路输出与输入之间的信息传送,改变输出结果。Verilog HDL有门基元、连续赋值、过程赋值和非阻塞赋值等4种赋值方法(即语句),不同的赋值语句使输出产生新值的方法不同。其中非阻塞赋值应用较少,下面主要介绍门基元、连续赋值和过程赋值语句。
(1)门基元赋值语句
门是实现与、或、非三种基本逻辑和与非、或非、异或等复合逻辑的电路,根据逻辑关系把它们分别称为与门、或门、非门、与非门、或非门和异或门。一般的门电路具有一个输出端和若干个输入端,门基元赋值语句用于描述(设计)这些门电路,语句格式为:
基本逻辑门关键词(门输出,门输入1,门输入2,…,门输入n);
其中,基本逻辑门关键词是Verilog HDL预定义的逻辑门,包括and(与门)、or(或门)、not(非门)、xor(异或门)、nand(与非门)、nor(或非门)等;圆括弧中内容是被描述门的输出和输入变量。例如,具有y为输出和a、b、c、d 4个输入的与非门的门基元赋值语句为:
nand(y,a,b,c,d);
(2)连续赋值语句
连续赋值语句的关键词是assign,赋值符号是“ = ”,赋值语句的格式为:
assign 赋值变量=表达式;
例如,具有a、b、c、d 4个输入和y为输出的与非门的连续赋值语句为:
assign y=~(a&b&c&d);
连续赋值语句的“ = ”号两边的变量都应该是wire型变量。在执行中,输出y的变化跟随输入a、b、c、d的变化而变化,反映了信息传送的连续性。连续赋值语句用于逻辑门和组合逻辑电路的描述。
例如,4输入端与非门的Verilog HDL源程序nand_4.v如下:
module nand_4(y,a,b,c,d); output y; input a,b,c,d; assign #1 y=~(a&b&c&d); endmodule
该程序中的“#1”表示该门的输出与输入变量之间具有1个单位的时间延迟,以保证设计结果与实际电路的延迟性能相近。
(3)过程赋值语句
过程赋值语句出现在initial和always块语句中,赋值符号是“ = ”,语句格式为:
赋值变量 = 表达式;
在过程赋值语句中,赋值号“ = ”左边的赋值变量必须是reg(寄存器)型变量,其值在该语句结束的即可得到。如果一个块语句中包含若干条过程赋值语句,那么这些过程赋值语句是按照语句编写的顺序由上至下一条一条地执行的,前面的语句没有完成,后面的语句就不能执行,就如同被阻塞了一样。因此,过程赋值语句也称为阻塞赋值语句。
2. 条件语句
条件语句包含if语句和case语句,它们都是顺序语句,应放在always块中。
(1)if语句
完整的Verilog HDL的if语句结构如下:
if(表达式) begin 语句; end else if(表达式) begin 语句; end else begin 语句; end
在if语句中,“表达式”一般为逻辑表达式或关系表达式,也可以是位宽为1位的变量。系统对表达式的值进行判断,若值为“1”按“真”处理,执行指定的语句;为“0”、“x”和“z”时,按“假”处理,不执行相关的语句。语句可以是多句,多句时用“begin-end”语句括起来,每条语句用分号“;”分隔;也可以是单句,单句可以省略“begin-end”语句。对于if语句嵌套,如果不清楚if和else的匹配,最好用“begin-end”语句括起来。
根据需要,if语句可以写为另外两种变化形式:
if(表达式) begin语句; end if(表达式) begin语句; end else begin语句; end
(2)case语句
case语句是一种多分支的条件语句,完整的case语句的格式为:
case(表达式) 选择值1: 语句1; 选择值2: 语句2; 选择值n: 语句n; default: 语句n+1; endcase
执行case语句时,首先计算表达式的值,然后执行在条件句中找到的“选择值”与其值相同的语句。当所有的条件句的“选择值”与表达式的值不同时,则执行“default”后的语句。default语句如果不需要,可以去掉。
case语句还有两种变体语句形式,即casez和casex语句,这两种语句与case语句的格式完全相同,它们的区别是:在casez语句中,如果分支表达式某些位的值为高阻z,那么对这些位的比较就不予以考虑,只关注其他位的比较结果。在casex语句中,把不予以考虑的位扩展到未知x,即不考虑值为高阻z和未知x的那些位,只关注其他位的比较结果。
3. 循环语句
循环语句包含for语句、repeat语句、while语句和forever语句4种。
(1)for语句
for语句的语法格式为:
for(循环指针=初值; 循环指针 < 终值; 循环指针=循环指针+步长值) begin 语句; end
for语句可以使一组语句重复执行,语句中的循环指针、初值、终值和步长值是循环语句定义的参数,这些参数一般属于整型变量或常量。语句重复执行的次数由语句中的参数确定,即:
循环重复次数 =(终值-初值)/步长值。
例如语句
for (i = 0; i < 100; i = i + 1);
其循环重复次数为(100 − 0)/1 = 100(次)。
(2)repeat语句
repeat语句的语法格式为:
repeat(循环次数表达式)语句;
例如,用repeat语句控制循环100次的语句为:
repeat(99) begin语句; end
用repeat控制的循环从第0次开始到第99次后结束,共执行100次。
(3)while语句
while语句的语法格式为:
while(循环执行条件表达式) begin 重复执行语句; 修改循环条件语句; end
while语句在执行时,首先判断循环执行条件表达式是否为真。若为真,则执行其后的语句;若为假,则不执行(表示循环结束)。为了使while语句能够结束,在循环执行的语句中必须包含一条能改变循环条件的语句。
(4)forever语句
forever语句的语法格式为:
forever begin语句; end
forever是一种无穷循环控制语句,它不断地执行其后的语句或语句块,永远不会结束。forever语句常用来产生周期性的波形,作为仿真激励变量。例如,让clk产生矩形波的语句为:
#10 foreve #10 clk = !clk;
上述语句表明:clk变量从一个起始值开始,每隔10个标准延迟单位就变化为其相反的值),即由0变化为1,由1变化为0。这样,clk就是一个在0和1两种电平上变化的矩形波。
4. 结构声明语句
在Verilog HDL中,对具有某种独立功能的电路,都是放在过程块中描述的,而任何过程块都是放在结构声明语句中的,结构声明语句包括always、initial、task和function等4种结构。
(1)always块语句
在一个Verilog HDL模块(module)中,always块语句的使用次数是不受限制的,块内的语句也是不断重复执行的。always块语句的语法结构为:
always @(敏感变量表达式) begin // 过程赋值语句; // if语句,case语句; // for语句,while语句,repeat语句; // tast语句,function语句; end
在always块语句中,敏感变量表达式(event-expression)应该列出影响块内取值的所有变量(一般指设计电路的输入变量、模块内部使用的变量和时钟变量),多个变量之间用“or”连接。当表达式中任何变量发生变化时,就会执行一遍块内的语句。块内语句可以包括:过程赋值、if、case、 for、while、repeat、task调用和function调用等语句。
(2)initial块语句
initial块语句的语法格式为:
initial begin 语句; end
initial块语句的使用次数也是不受限制的,但块内的语句仅执行一次,因此initial语句常用于仿真中的初始化。
(3)task块语句
在Verilog HDL模块中,task块语句用来定义任务。任务类似于高级语言中的子程序,用来单独完成某项具体任务,并可以被模块或其他任务调用。利用任务可以把一个大的程序模块分解成为若干小的任务,使程序清晰易懂,而且便于调试。
可以被调用的任务必须事先用task块语句定义,定义格式如下:
task 任务名; 端口声明语句; 变量类型声明语句; begin 语句; end endtask
任务定义与模块(module)定义的格式相同,区别在于任务用task-endtask语句来定义,而且没有端口名列表。
任务调用的格式为:
任务名(端口名列表);
使用任务时,需要注意几点:
① 任务的定义和调用必须在同一个module模块内。
② 定义任务时,没有端口名列表,但要进行端口和数据类型的声明。
③ 当任务被调用时,任务被激活。任务调用与模块调用一样,通过任务名实现,调用时需列出端口名列表,端口名和类型必须与任务定义中的排序和类型一致。
④ 一个任务可以调用别的任务或函数,可调用的任务和函数的个数不受限制。
(4)function块语句
在Verilog HDL模块中,function块语句用来定义函数。函数类似于高级语言中的函数,用来单独完成某项具体操作,并可以作为表达式中的一个操作数,被模块或任务以及其他函数调用,函数调用时返回一个用于表达式的值。
被调用的函数必须事先定义,函数定义格式如下:
function[最高有效位:最低有效位] 函数名; 端口声明语句; 类型声明语句; begin语句;end endfunction
在函数定义语句中,“[最高有效位:最低有效位]”是函数调用返回值的位宽。
例如,求最大值的函数max如下。
function[7:0] max; input[7:0] a,b; begin if (a> = b) max = a; else max=b; end endfunction
函数调用的格式为:
函数名(关联参数表);
函数调用一般出现在模块、任务或函数语句中。通过函数的调用来完成某些数据的运算或转换。例如,调用求最大值的max函数语句为:
peak = max(data,peak);
其中,data和peak是与函数定义的两个参数a、b关联的关联参数。通过函数的调用,求出data和peak中的最大值,并用函数名max返回。
函数和任务存在以下几点区别:
① 任务可以有任意不同类型输入/输出参数,函数不能将inout类型作为输出。
② 任务只可以在过程语句中调用,不能在连续赋值语句assign中调用;函数可以作为表达式中的一个操作数,在过程赋值语句和连续赋值语句中调用。
③ 任务可以调用其他任务或函数;函数可以调用其他函数,但不能调用任务。
④ 任务不向表达式返回值,函数向调用它的表达式返回一个值。
5. 语句的顺序执行与并行执行
Verilog HDL中有顺序执行语句和并行执行语句之分。Verilog HDL的always块属于并行语句,块中的语句是顺序语句,按照程序书写的顺序执行。always块本身却是并行语句,它与其他always语句以及initial、assign语句、例化元件语句都是同时(即并行)的。由于always语句的并行行为和顺序行为的双重特性,使它成为Verilog HDL程序中使用最频繁和最能体现Verilog HDL风格的一种语句。
always块语句中有一个敏感变量表,表中列出的任何变量的改变,都将启动always块语句,使always块语句内相应的顺序语句被执行一次。实际上,用Verilog HDL描述的硬件电路的全部输入变量都是敏感变量,为了使Verilog HDL的软件仿真与综合和硬件仿真对应起来,应当把always块语句中所有输入变量都列入敏感变量表中。敏感参数有电平(高电平与低电平)和边沿(上升沿与下降沿)两种类型,在编程中,对于电平类型的敏感参数可以不列出,而边沿类型的敏感参数则一定要列出。
2.5.4 不同抽象级别的Verilog HDL模型
Verilog HDL是一种用于逻辑电路设计的硬件描述语言。用Verilog HDL描述的电路称为该设计电路的Verilog HDL模型。
Verilog HDL具有行为描述和结构描述功能。行为描述是对设计电路的逻辑功能的描述,并不用关心设计电路使用哪些元件以及这些元件之间的连接关系。行为描述属于高层次的描述方法,在Verilog HDL中,行为描述包括系统级(System Level)、算法级(Algorithm Level)和寄存器传输级(RTL,Register Transfer Level)等3种抽象级别。
结构描述是对设计电路的结构进行描述,即描述设计电路使用的元件及这些元件之间的连接关系。结构描述属于低层次的描述方法,在Verilog HDL中,结构描述包括门级(Gate Level)和开关级(Switch Level)2种抽象级别。
在Verilog HDL的学习中,应重点掌握高层次描述方法,但结构描述也可以用来实现电路的系统设计。
1. 门级描述
Verilog HDL提供了丰富的门类型关键词,用于门级的描述。常用的门级描述关键词包括:not(非门)、and(与门)、nand(与非门)、or(或门)、nor(或非门)、xor(异或门)、xnor(异或非门)、buf(缓冲器),以及bufif1、bufif0、notif1、notif0等各种三态门(缓冲器和三态门的概念将在后继的章节中介绍)。
门级描述语句格式为:
门类型关键词 <例化门的名称>(端口列表);
其中,“例化门的名称”是用户定义的标识符,属于可选项;端口列表按输出、输入、使能控制端的顺序列出。例如:
nand nand2(y,a,b); //例化一个2输入端与非门 xor myxor(y,a,b); //例化一个异或门
2. Verilog HDL的行为级描述
Verilog HDL的行为级描述是最能体现电子设计自动化(EDA)风格的硬件描述方式,它既可以描述简单的逻辑门,也可以描述复杂的数字系统乃至微处理器;既可以描述组合逻辑电路,也可以描述时序逻辑电路。关于Verilog HDL的行为级描述方法的应用举例将在后继的章节中陆续介绍。
3. 用结构描述实现电路系统设计
任何用Verilog HDL描述的电路设计模块(module),均可以作为一个基本元件,被模块例化语句调用,来实现电路系统的设计。
模块例化语句格式与逻辑门例化语句格式相同,具体为:
设计模块名 <例化电路名>(端口列表);
其中,“例化电路名”是用户为系统设计定义的标识符,相当于系统电路板上为插入设计模块元件的插座,而端口列表相当于插座上引脚名表,应与设计模块的输入和输出端口一一对应。Verilog HDL的结构描述方式为大型数字系统的设计带来了方便,其应用示例将在后继的章节中介绍。