常用数据类型

Verilog HDL中总共有19种数据类型,数据类型是用来表示数字电路硬件中的数据储存和传送元素的。在本书中,我们先只介绍4个最基本的数据类型,它们分别是:reg型,wire型,integer型和parameter型。

 

其他数据类型在后面的章节里逐步介绍,读者也可以查阅附录中Verilog HDL语法参考书的有关章节逐步掌握。其他的类型如下:large型、medium型、scalared型、time型、small型、tri型、trio型、tri1型、triand型、trior型、trireg型、vectored型、wand型和wor型。

 

这些数据类型除time型外都与基本逻辑单元建库有关,与系统设计没有很大的关系。在一般电路设计自动化的环境下,仿真用的基本部件库是由半导体厂家和EDA工具厂家共同提供的。系统设计工程师不必过多地关心门级和开关级的Verilog HDL语法现象。Verilog HDL语言中也有常量和变量之分,它们分别属于以上这些类型。下面对最常用的几种进行介绍。

 

常量

常量是在程序运行过程中其值不能被改变的量。下面首先对在Verilog HDL语言中使用的数字及其表示方式进行介绍。

 

1.数字

(1)整数。

在Verilog HDL中,整型常量有以下4种进制表示形式。

① 二进制整数(b或B)。

② 十进制整数(d或D)。

③ 十六进制整数(h或H)。

④ 八进制整数(o或O)。

数字表达方式有以下3种。

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

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

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

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

 

8'b10101100     //位宽为8的数的二进制表示,'b表示二进制

8'ha2             //位宽为8的数的十六进制,'h表示十六进制。

 

(2)x和z值。

在数字电路中,x代表不定值,z代表高阻值。一个x可以用来定义十六/八/二进制数的四/三/一位二进制数的状态。z的表示方式同x类似。z还有一种表达方式是可以写作?。在使用case表达式时建议使用这种写法,以提高程序的可读性,如下例所示:

 

4'b10x0          //位宽为4的二进制数从低位数起第二位为不定值

4'b101z          //位宽为4的二进制数从低位数起第一位为高阻值

12'dz            //位宽为12的十进制数其值为高阻值(第一种表达方式)

12'd?            //位宽为12的十进制数其值为高阻值(第二种表达方式)

8'h4x            //位宽为8的十六进制数其低四位值为不定值

 

(3)负数。

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

 

-8'd5            //这个表达式代表5的补数(用8位二进制数表示)

8'd-5            //非法格式

 

(4)下划线(underscore_)。

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

 

16'b1010_1011_1111_1010          //合法格式

8'b_0011_1010                     //非法格式

 

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

 

10=32'd10=32'b1010               //十进制和二进制

1=32'd1=32'b1                     //十进制和二进制

-1=-32'd1=32'hFFFFFFFF          //十进制和十六进制

'BX=32'BX=32'BXXXXXXX…X         //默认声明为32位

"AB"=16'B01000001_01000010       //每个字母用8位表示

 

2.参数(Parameter)

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

 

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

 

parameter是参数型数据的确认符,确认符后跟着一个用逗号分隔开的赋值语句表。在每一个赋值语句的右边必须是一个常数表达式。也就是说,该表达式只能包含数字或先前已定义过的参数,例如:

 

parameter  msb=7;                                     //定义参数msb为常量7

parameter  e=25, f=29;                              //定义两个常数参数

parameter  r=5.7;                                     //声明r为一个实型参数

parameter  byte_size=8, byte_msb=byte_size-1;    //用常数表达式赋值

parameter  average_delay = (r+f)/2;                  //用常数表达式赋值

 

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

 

module Decode(A,F);                                    //模块声明

      parameter Width=1, Polarity=1;                   //参数声明

      ……………

endmodule

 

module Top;

      wire[3:0] A4;                                      //连线资源声明

      wire[4:0] A5;

      wire[15:0] F16;

      wire[31:0] F32;

      Decode  #(4,0) D1(A4,F16);                     //模块引用,并传递参数(4,0)

      Decode  #(5)  D2(A5,F32);                      //模块引用,并传递参数(5)

endmodule

 

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

 

变量

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

 

1.网络类型变量

网络类型表示结构实体(例如门)之间的物理连接。网络类型的变量不能储存值,而且它必需受到驱动器(例如门或连续赋值语句,assign)的驱动。如果没有驱动器连接到网络类型的变量上,则该变量就是高阻的,即其值为z。

 

常用的网络类型变量包括wire型和tri型。这两种变量都是用于连接器件单元,它们具有相同的语法格式和功能。之所以提供这两种名字来表达相同的概念是为了与模型中所使用的变量的实际情况相一致。

 

wire型变量通常是用来表示单个门驱动或连续赋值语句驱动的网络型数据,tri型变量则用来表示多驱动器驱动的网络型数据。如果wire型或tri型变量没有定义逻辑强度(logic strength),在多驱动源的情况下,逻辑值会发生冲突,从而产生不确定值。

 

表1所示为在同等驱动强度下,两个驱动源驱动的wire型和tri型变量的真值表。

表1                                                        wire/tri型变量真值表

wire/tri型变量双驱动源运算结果

       驱动源1

驱动源2

0

1

x

z

0

0

x

x

0

1

 

1

x

1

x

x

x

x

x

z

0

1

x

z

 

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

 

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

 

也可以如下表示:

 

wire [n:1] 变量名1,变量名2,…,变量名i;    //共有i条总线,每条总线内有n条线路

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

 

wire  a;                                      //定义了一个一位的wire型变量

wire [7:0] b;                              //定义了一个八位的wire型变量

wire [4:1] c, d;                          //定义了两个四位的wire型变量

 

2.寄存器型变量

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

 

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

 

reg型变量常用来表示用于“always”模块内的指定信号,常代表触发器。通常,在设计中要由“always”块通过使用行为描述语句来表达逻辑关系。在“always”块内被赋值的每一个信号都必须定义成reg型。和wire型变量类似,reg型变量的声明格式如下:

 

reg [n-1:0] 变量名1,变量名2,…,变量名i;       //共有i条总线,每条总线内有n条线路

 

也可以如下表示:

reg [n:1]   变量名1,变量名2,…,变量名i;       //共有i条总线,每条总线内有n条线路

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

reg  rega;                          //定义了一个一位的名为rega的reg型变量

reg [3:0]  regb;                  //定义了一个四位的名为regb的reg型变量

reg [4:1]  regc, regd;          //定义了两个四位的名为regc和regd的reg型变量

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

 

3.存储器型变量

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

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

或:

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

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

 

reg [7:0]  mema[255:0];                //定义一个名为mema的256×8的存储器

 

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

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

 

parameter  wordsize=16, memsize=256;                           //定义两个参数

reg [wordsize-1:0] mem[memsize-1:0],writereg, readreg;  //使用可变参数来定义存储器

 

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

 

reg [n-1:0] rega;                   //一个n位的寄存器

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

 

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

 

rega =0;                             //合法赋值语句

mema =0;                             //非法赋值语句

 

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

 

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

 

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

 

常用运算符

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

  • 算术运算符:(+,-,×,/,%)。
  • 赋值运算符:(=,<=)。
  • 关系运算符:(>,<,>=,<=)。
  • 逻辑运算符:(&&,||,!)。
  • 条件运算符:(?:)。
  • 位运算符:(~,|,^,&,^~)。
  • 移位运算符:(<<,>>)。
  • 拼接运算符:({ })。
  • 其他

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

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

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

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

 

例如:

clock = ~clock;          // ~ 是一个单目取反运算符,clock是操作数。

c = a | b;                // | 是一个二目按位或运算符,a 和 b是操作数。

r = s ? t : u;           // ?: 是一个三目条件运算符,s,t,u是操作数。

 

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

1.基本的算术运算符

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

  • +:(加法运算符或正值运算符,如ega+regb、+3)。
  • −:(减法运算符或负值运算符,如rega−3、−3)。
  • ´:(乘法运算符,如rega´3)。
  • /:(除法运算符,如5/3)。
  • % :(模运算符或求余运算符,要求%两侧均为整型数据,如7%3的值为1)。

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

 

10%3          1       //余数为1

11%3          2        //余数为2

12%3          0       //余数为0,即无余数

-10%3         -1      //结果取第一个操作数的符号位,所以余数为-1

11%3          2       //结果取第一个操作数的符号位,所以余数为2.

 

注意

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

 

2.位运算符

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

  • ~ :(取反)
  •  & :(按位与)
  • | :(按位或)
  • ^ :(按位异或)
  •  ^~:(按位同或(异或非))

 

说明:

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

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

 

“取反”运算符~

~是一个单目运算符,用来对一个操作数进行按位取反运算。如表2所示为单目运算符~的运算规则表。

表2                                                             ~ 运算规则表

~运算

操  作  数

结    果

1

0

0

1

x

x

 

举例说明:

rega='b1010;         //rega的初值为'b1010

rega=~rega;         //rega的值进行取反运算后变为'b0101

 

“按位与”运算符&

按位与运算就是将两个操作数的相应位进行与运算,其运算规则如表3所示。

表3                                                              & 运算规则表

& 运算

          操作数1

操作数2

0

1

x

0

0

0

0

1

0

1

x

x

0

x

x

 

“按位或”运算符 |

按位或运算就是将两个操作数的相应位进行或运算,其运算规则如表4所示。

表4                                                              | 运算规则表

| 运算

           操作数1

操作数2

0

1

x

0

0

1

x

1

1

1

1

x

x

1

x

 

“按位异或”运算符^(也称之为XOR运算符)

按位异或运算就是将两个操作数的相应位进行异或运算,其运算规则如表5所示。

 

表5                                                              ^ 运算规则表

^ 运算

            操作数1

操作数2

0

1

x

0

0

1

x

1

1

0

x

x

x

x

x

 

“按位同或”运算符^~

按位同或运算就是将两个操作数的相应位先进行异或运算再进行非运算,其运算规则如表6所示。

 

表6                                                           ^~ 运算规则表

^~ 运算

          操作数1

操作数2

0

1

x

0

1

0

x

1

0

1

x

x

x

x

x

 

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

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

 

3.逻辑运算符

在Verilog HDL语言中存在3种逻辑运算符。

  • &&:(逻辑与)
  • ||:(逻辑或)
  • ! :(逻辑非)

“&&”和“||”是二目运算符,它要求有两个操作数,如(a>b)&&(b>c),(a<b)||(b<c)。“!”是单目运算符,只要求一个操作数,如!(a>b)。如表7所示为逻辑运算的真值表。它表示当a和b的值为不同的组合时,各种逻辑运算所得到的值。

 

表7                                                            逻辑运算真值表

操  作  数

逻辑运算及结果

a

b

!a

!b

a&&b

a||b

 

逻辑运算符中“&&”和“||”的优先级别低于关系运算符,“!”的优先级别高于算术运算符,例如。

 

(a>b)&&(x>y)       可写成:      a>b && x>y

(a==b)||(x==y)     可写成:      a==b || x==y

(!a)||(a>b)        可写成:      !a || a>b

 

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

 

4.关系运算符

关系运算符共有以下4种。

  • a < b:(a小于b)
  • a > b:(a大于b)
  • a <= b:(a小于或等于b)
  • a >= b:(a大于或等于b)

 

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

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

 

a < size-1            //这种表达方式等同于下面一行的表达方式

a < (size-1)

size - (1 < a)     //这种表达方式不等同于下面一行的表达方式

size - 1 < a

 

从上面的例子可以看出这两种不同运算符的优先级别。当表达式size -(1<a)进行运算时,关系表达式先被运算,然后返回结果值0或1被size减去。而当表达式 size -1<a 进行运算时,size先被减去1,然后再同a相比。

 

5.等式运算符

在Verilog HDL语言中存在4种等式运算符。

  • = =:(等于)
  • != :(不等于)
  • = = =:(等于)
  • != =:(不等于)

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

 

“= = =”和“!= =”运算符则不同,它在对操作数进行比较时,对某些位的不定值x和高阻值z也进行比较。两个操作数必需完全一致,其结果才是1,否则为0。“= = =”和“!= =”运算符常用于case表达式的判别,所以又称为“case等式运算符”。

 

这4个等式运算符的优先级别是相同的。下面画出“= =”与“= = =”的真值表,帮助理解两者间的区别。

 

表8                                                          等式运算符真值表

= = = 运算

         操作数1

操作数2

0

1

x

z

0

1

0

0

0

1

0

1

0

0

x

0

0

1

0

z

0

0

0

1

= = 运算

          操作数1

操作数2

0

1

x

z

0

1

0

x

x

1

0

1

x

x

x

x

x

x

x

z

x

x

x

x

 

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

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

if(A===1’bx) $display( "AisX" ); //当A等于X时,这个语句执行

 

6.移位运算符

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

<<:(左移位运算符)

>>:(右移位运算符)

其使用方法如下:

 

a >> n;

a << n;

 

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

 

module  shift;

     reg [3:0]  start, result;

     initial begin

          start  = 1;                    //start在初始时刻设为值0001

          result = (start<<2);       //移位后,start的值0100,然后赋给result

     end

endmodule

 

从上面的例子可以看出,start在移过两位以后,用0来填补空出的位。进行移位运算时应注意移位前后变量的位数,下面举例说明。

 

4’b1001<<1 = 5’b10010;                //左移1位后用0填补低位

4’b1001<<2 = 6’b100100;               //左移2位后用00填补低位

1<<6 = 32’b1000000;                    //左移6位后用000000填补低位

4’b1001>>1 = 4’b0100;                  //右移1位后,低1位丢失,高1位用0填补

4’b1001>>4 = 4’b0000;                  //右移4位后,低4位丢失,高4位用0填补

 

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)先将操作数的第一位与第二位进行与、或、非运算。

(2)将运算结果与第三位进行与、或、非运算,依次类推,直至最后一位。

 

例如:

reg [3:0] B;

reg C;

C = &B;

 

相当于:

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

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

 

9.优先级别

各种运算符的优先级别关系如表9所示。

表9                                                          运算符优先级别表

运  算  符

优 先 级 别

! ~

* / %

+ -

<< >>

< <= > >=

== != === !==

&

^ ^~

|

&&

||

? :