目录
1、模块的结构
Verilog结构位于在module和endmodule声明语句之间,每个Verilog程序包括4个主要部分:端口定义、I/O说明、内部信号声明和功能定义。
1.1 模块的端口定义
模块的端口声明了模块的输入输出口。格式
module 模块名(口1, 口2, 口3, 口4, ...);
模块的端口表示的是模块的输入和输出口,也就是说,它与别的模块联系端口的标识。在模块被引用时,在引用的模块中,有些信号要输入到被引用的模块中,有的信号需要从被引用的模块中取出来。在引用模块时其端口可以用两种方法连接:
(1)在引用时,严格按照模块定义的端口顺序来连接,不用标明原模块定义时规定的端口名,例如:
模块名(连接端口1信号名,连接端口2信号名,连接端口3信号名,...);
(2)在引用时用 “.” 符号,标明原模块是定义时规定的端口名,例如:
模块名(端口1名(连接信号1名),端口2名(连接信号2名),...);
这样表示的好处在于可以用端口名与被引用模块的端口相对应,而不必严格按端口顺序对应,提高了程序的可读性和可移植性,例如:
MyDesignMK M1(.sin(SerialIn), .pout(ParallelOut),...);
其中,.sin和.pout都是M1的端口名,而M1则是与 MyDesignMK完全一样的模块。My DesignMK已经在另一个模块中定义过,它有两个端口,即sin和pout。与sin口连接的信号名为 SerialLy,与pout端口连接的是信号名为 Parallelout。
1.2 模块内容
模块的内容包括I/O说明、内部信号声明和功能定义。、
1.2.1 I/O说明的格式
I/O说明也可以写在端口声明语句里。其格式如下:
module module_ name( input portl ,input port2,..output portl ,output port2... );
1.2.2 内部信号说明
在模块内用到的和与端口有关的wire和reg类型变量的声明。如:
reg [width-1 : 0]R变量1,R变量2...;
wire [width-1 : 0]W变量1,W变量2...;
1.3 功能定义
模块中最重要的部分是逻辑功能定义部分。有3种方法可在模块中产生逻辑。
(1)用“assign“声明语句如:
assign a = b & c; //描述了一个有两个输人的与门
这种方法的句法很简单,只需写一个“assign“ ,后面再加一个方程式即可。
(2)用实例元件如:
//表示在设计中用到一个跟与门(and)一样的名为ul的与门,其输人端为a、b,输出为q。
//输出延迟为2个单位时间。
//要求每个实例元件的名字必须是唯一的,以避免与 其他调用与门(and)的实例混淆。
and #2 ul(q, a, b);
采用实例元件的方法像在电路图输人方式下调入库元件一样,键入元件的名字和相连的引脚即可。
(3)用“always“块 如:
//用“always“块的例子生成了一个带有异步清除端的D触发器。
always @ (posedge clk or posedge clr);
begin
if(elr)
q <= 0;
else if(en)
q <= d;
end
采用“assign“语句是描述组合逻辑最常用的方法之一。而“always“块既可用于 描述组合逻辑,也可描述时序逻辑。“always“块可用很多种描述手段来表达逻辑,例如上例就用了if-else语句来表达逻辑关系。如按一定的风格来编写“always”块,可以通过综合工具把源代码自动综合成用门级结构表示的组合或时序逻辑电路。
注:如果用Verilog模块实现一定的功能,首先应该清楚哪些是同时发生的,哪些是顺序发生的。上面例子分别采用了“assign“语句、实例元件和“always“块。这3个例子描述的逻辑功能是同时执行的。也就是说,如果把这3项写到-个Verilog模块文件中去,它们的顺序不会影响实现的功能。这3项是同时执行的,也就是并发的。
然而,在“always“模块内,逻辑是按照指定的顺序执行的。“always“块中的语句称为“顺序语句”,因为它们是顺序执行的。所以,“always”块也称为“过程块”。请注意,两个或更多的*always“模块都是同时执行的,而模块内部的语句是顺序执行的。看一下“always“内的语句,就会明白它是如何实现功能的。if - else if - else 必须顺序执行,否则其功能就没有任何意义。如果else语句在if语句之前执行,其功能就会不符合要求。为了能实现上述描述的功能,“always”模块内部的语句将按照书写的顺序执行。
(1)在Verilog模块中所有过程块(如:initial块、always块)、连续赋值语句、实例引用都是并行的;
(2)它们表示的是一种通过变量名互相连接的关系;
(3)在同一模块中这三者出现的先后秩序没有关系;
(4)只有连续赋值语句assign和实例引用语句可以独立于过程块而存在于模块的功能定义部分。
以上4点与C语言有很大的不同。许多与C语言类似的语句只能出现在过程块中,而不能随意出现在模块功能定义的范围内。
2、数据类型及其常量和变量
VerilogHDL中总共有19种数据类型。数据类型是用来表示数字电路硬件中的数据储存和传送元素的。在本教材中先介绍4个最基本的数据类型,它们是:
reg型、wire型、integer型和parameter型
其他的类型是:
large型、medium型、scalared型. time型、small型、tri型、trio型、tril型,triand型、 trior型、trireg型、vectored型、wand型和wor型。
这14种数据类型除time型外都与基本逻辑单元建库有关,与系统设计没有很大的关系。
Verilog HDL语言中也有常量和变量之分,它们分别属于以上这些类型。下面就最常用的几种进行介绍。
2.1 常量
在程序运行过程中,其值不能被改变的量称为常量。以下为Verilog HDL语言中使用的数字及其表示方式。
2.1.1 数字
(1)整数在Verilog HDL中,整型常量即整常数有以下4种进制表示形式:
1)二进制整数(b或B);
2)十进制整数(d或D);
3)十六进制整数(h或H);
4)八进制整数(o或O)。
数字表达方式有以下3种:
1) <位宽><进制><数字> ,这是一种全面的描述方式。
2)在<进制><数字> 这种描述方式中,数字的位宽采用默认位宽(这由具体的机器系统决定,但至少32位)。
3)在<数字>这种描述方式中,采用默认进制(十进制)。
在表达式中,位宽指明了数字的精确位数。例如:一个4位二进制数的数字的位宽为4,一个4位十六进制数数字的位宽为16(因为每单个十六进制数就要用4位二进制数来表示)。如:
8'b10101100 //位宽为8的数的二进制表示,'b表示二进制
(2)x和z值
在数字电路中,x表示不定值,z代表高阻值。一个x可以用来定义十六进制数的4位二进制数的状态,八进制数的3位,二进制数的1位。z的表示方式同x类似。z还有一种表达方法是可以写作“?”。在使用case表达式时建议使用这种写法,以提高程序的可读性。如
4'b10x0 //位宽为4的二进制数从低位起第2位为不定值
4'b101z //位宽为4的二进制数从低位起第1位为高阻值
12'dz //位宽为12的十进制数,其值为高阻值(第1种表达方式)
12'd? //位宽为12的十进制数,其值为高阻值(第2种表达方式)
8'h4x //位宽为8的十六进制数,其低4位值为不定值
(3) 负数
一个数字可以被定义为负数,只需在位宽表达式前加一个减号,减号必须写在数字定义表达式的最前面。
注:减号不可以放在位宽和进制之间,也不可以放在进制和具体的数之间。
-8'd5 //5的补数(用八位二进制数表示)
8'd-5 //非法格式
(4)下划线
下划线可以用来分隔开数的表达以提高程序的可读性。
下划线不可以用在位宽和进制处,只能用在具体的数字之间。
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
"AB" = 16'B01000001_01000010 //字符串AB,为十六进制数16'h4142
2.1.2 参数型
在Verilog HDL中用parameter来定义常量,即用parameter来定义一个标识符代表一个常量,格式如下:
parameter 参数名1 = 表达式, 参数名2 = 表达式, … , 参数名n = 表达式;
parameter是参数型数据的确认符。确认符后跟着一个用逗号分隔开的赋值语句表。在每一个赋值语句的右边必须是一个常数表达式。也就是说,该表达式只能包含数字或先前已定义过的参数。
parameter msb = 7; //定义参数msb为常量7
parameter e = 25, f = 29; //定义两个常数参数parameterr=5.7;∥声明r为一个实型参数
parameter byte_size = 8, byte_msb = byte_size - 1; //用常数表达式赋值
parameter average_delay = (r + f) / 2; //用常数表达式赋值
2.3 变量
2.3.1 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]代表该数据的位宽,即该数据有几位;最后跟着的是数据的名字。如果一次定义多个数据,数据名之间用逗号隔开。声明语句的最后要用分号表示语句结束。
wire a; //定义了一个1位的wire型数据
wire[7:0]b; //定义了一个8位的wire 型数据
wire[4:1]c, d; //定义了二个4位的wire型数据
2.3.2 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);最后跟着的是数据的名字。如果一次定义多个数据,数据名之间用逗号隔开。声明语句的最后要用分号表示语句结束。看下面的几个例子:
reg rega; //定义了一个1位的名为rega的reg型数据
reg[3:0]regb; ∥定义了一个4位的名为regb的reg型数据
reg[4:1]regc,regd; ∥定义了二个4位的名为regc和regd的reg型数据
reg型数据的默认初始值是不定值。reg型数据可以赋正值,也可以赋负值。但当一个reg型数据是一个表达式中的操作数时,它的值被当作是无符号值,即正值。例如,当一个4位的寄存器用做表达式中的操作数时,如果开始寄存器被赋以值-1,则在表达式中进行运算时,其值被认为是+15。
注意:reg型只表示被定义的信号将用在“always”模块内,理解这一点很重要。并不是说reg型信号一定是寄存器或触发器的输出,虽然reg型信号常常是寄存器或触发器的输出,但并不一定总是这样。
2.3.3 memory型
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位的存储器。该存储器的地址范围是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
进行寻址的地址索引可以是表达式,这样就可以对存储器中的不同单元进行操作。表达式的值可以取决于电路中其他的寄存器的值。