Verilog HDL基本程序结构
用Verilog HDL描述的电路设计就是该电路的Verilog HDL模型,也称为模块,是Verilog的基本描述单位。模块描述某个设计的功能或结构以及与其他模块通信的外部接口,一般来说一个文件就是一个模块,但并不绝对如此。模块是并行运行的,通常需要一个高层模块通过调用其他模块的实例来定义一个封闭的系统,包括测试数据和硬件描述。一个模块的基本架构如下:
module module_name (port_list)
//声明各种变量、信号
reg //寄存器
wire//线网
parameter//参数
input//输入信号
output/输出信号
inout//输入输出信号
function//函数
task//任务
……
//程序代码
initial assignment
always assignment
module assignment
gate assignment
UDP assignment
continous assignment
endmodule
说明部分用于定义不同的项,例如模块描述中使用的寄存器和参数。语句用于定义设计的功能和结构。说明部分可以分散于模块的任何地方,但是变量、寄存器、线网和参数等的说明必须在使用前出现。一般的模块结构如下:
module <模块名> (<端口列表>)
<定义>
<模块条目>
endmodule
其中,<定义>用来指定数据对象为寄存器型、存储器型、线型以及过程块。<模块条目>可以是initial结构、always结构、连续赋值或模块实例。
下面给出一个简单的Verilog模块,实现了一个二选一选择器。
例2-1 二选一选择器(见图2-1)的Verilog实现
图2-1 例2-1所示的二选一电路
module muxtwo(out, a, b, s1);
input a, b, s1;
output out;
reg out;
always @ (s1 or a or b)
if (!s1) out = a;
el out = b;
endmodule
模块的名字是muxtwo,模块有4个端口:三个输入端口a、b和s1,一个输出端口out。由于没有定义端口的位数,所有端口大小都默认为1位;由于没有定义端口a, b, s1的数据类型,这3个端口都默认为线网型数据类型。输出端口out定义为reg类型。如果没有明确的说明,则端口都是线网型的,且输入端口只能是线网型的。
第3节 VerilogHDL语言的数据类型和运算符
2.3.1 标志符
标志符可以是一组字母、数字、_下划线和$符号的组合,且标志符的第一个字符必须是字母或者下划线。另外,标志符是区别大小写的。下面给出标志符的几个例子:
Clk_100MHz
diag_state
_ce
P_o1_02
需要注意的是,Verilog HDL定义了一系列保留字,叫作关键字,具体资料可查阅相关标准。只有小写的关键字才是保留字,因此在实际开发中,建议将不确定是否是保留字的标志符首字母大写。例如:标志符if(关键字)与标志符IF是不同的。
2.3.2 数据类型
数据类型用来表示数字电路硬件中的数据存储和传送元素。Verilog HDL中总共有19种数据类型,本书只介绍4个常用的数据类型:wire型、reg型、memory型和parameter型,其他类型将在后续章节中逐步介绍。
1.wire型
wire型数据常用来表示以assign关键字指定的组合逻辑信号。Verilog程序模块中输入、输出信号类型默认为wire型。wire型信号可以用做方程式的输入,也可以用做“assign”语句或者实例元件的输出。
wire型信号的定义格式如下:
wire [n-1:0] 数据名1,数据名2,……数据名N;
这里,总共定义了N条线,每条线的位宽为n。下面给出几个例子:
wire [9:0] a, b, c; // a, b, c都是位宽为10的wire型信号
wire d;
2.reg型
reg是寄存器数据类型的关键字。寄存器是数据存储单元的抽象,通过赋值语句可以改变寄存器存储的值,其作用相当于改变触发器存储器的值。reg型数据常用来表示always模块内的指定信号,代表触发器。通常在设计中要由always模块通过使用行为描述语句来表达逻辑关系。在always块内被赋值的每一个信号都必须定义为reg型,即赋值操作符的右端变量必须是reg型。
reg型信号的定义格式如下:
reg [n-1:0] 数据名1,数据名2,……数据名N;
这里,总共定义了N个寄存器变量,每条线的位宽为n。下面给出几个例子:
reg [9:0] a, b, c; // a, b, c都是位宽为10的寄存器
reg d;
reg型数据的缺省值是未知的。reg型数据可以为正值或负值。但当一个reg型数据是一个表达式中的操作数时,它的值被当作无符号值,即正值。如果一个4位的reg型数据被写入-1,在表达式中运算时,其值被认为是+15。
reg型和wire型的区别在于:reg型保持最后一次的赋值,而wire型则需要持续的驱动。
3.memory型
Verilog通过对reg型变量建立数组来对存储器建模,可以描述RAM、ROM存储器和寄存器数组。数组中的每一个单元通过一个整数索引进行寻址。memory型通过扩展reg 型数据的地址范围来达到二维数组的效果,其定义的格式如下:
reg [n-1:0] 存储器名 [m-1:0];
其中,reg [n-1:0]定义了存储器中每一个存储单元的大小,即该存储器单元是一个n位位宽
的寄存器;存储器后面的[m-1:0]则定义了存储器的大小,即该存储器中有多少个这样的寄存器。例如:
reg [15:0] ROMA [7:0];
这个例子定义了一个存储位宽为16位,存储深度为8的一个存储器。该存储器的地址范围是0到8。
需要注意的是:对存储器进行地址索引的表达式必须是常数表达式。
尽管memory型和reg型数据的定义比较接近,但二者还是有很大区别的。例如,一个由n个1位寄存器构成的存储器是不同于一个n位寄存器的。
reg [n-1 : 0] rega; // 一个n位的寄存器
reg memb [n-1 : 0]; // 一个由n个1位寄存器构成的存储器组
一个n位的寄存器可以在一条赋值语句中直接进行赋值,而一个完整的存储器则不行。
rega = 0; // 合法赋值
memb = 0; // 非法赋值
如果要对memory型存储单元进行读写必须要指定地址。例如:
memb[0] = 1; // 将memeb中的第0个单元赋值为1。
reg [3:0] Xrom [4:1];
Xrom[1] = 4’h0;
Xrom[2] = 4’ha;
Xrom[3] = 4’h9;
Xrom[4] = 4’hf;
4.parameter型
在Verilog HDL中用parameter来定义常量,即用parameter来定义一个标志符表示一个常数。采用该类型可以提高程序的可读性和可维护性。
parameter型信号的定义格式如下:
parameter 参数名1 = 数据名1;
下面给出几个例子:
parameter s1 = 1;
parameter [3:0] S0=4'h0,
S1=4'h1,
S2=4'h2,
S3=4'h3,
S4=4'h4;
2.3.3 模块端口
模块端口是指模块与外界交互信息的接口,包括3种类型:
∙ input: 模块从外界读取数据的接口,在模块内不可写。
∙ output:模块往外界送出数据的接口,在模块内不可读。
∙ inout:可读取数据也可以送出数据,数据可双向流动。
2.3.4 常量集合
Verilog HDL有下列4种基本的数值:
0:逻辑0或“假”
1:逻辑1或“真”
x:未知
z:高阻
其中x、z是不区分大小写的。Verilog HDL中的数字由这四类基本数值表示。
Verilog HDL中的常量分为3类:整数型、实数型以及字符串型。下划线符号“_”可以随意用在整数和实数中,没有实际意义,只是为了提高可读性。例如:56等效于5_6。
1.整数
整数型可以按如下两种方式书写:简单的十进制数格式以及基数格式。
(1)简单的十进制格式
简单的十进制数格式的整数定义为带有一个“+”或“-”操作符的数字序列。下面是这种简易十进制形式整数的例子。
45 十进制数45
-46 十进制数-46
简单的十进制数格式的整数值代表一个有符号的数,其中负数可使用两种补码形式表示。例如,32在6位二进制形式中表示为100000,在7位二进制形式中为0100000,这里最高位0表示符号位;-15在5位二进制中的形式为10001,最高位1表示符号位,在6位二进制中为110001,最高位1为符号扩展位。
(2)基数表示格式
基数格式的整数格式为:
[长度] '基数 数值
长度是常量的位长,基数可以是二进制、十进制、十六进制之一。数值是基于基数的数字序列,且数值不能为负数。下面是一些具体实例:
6'b9 6位二进制数
5'o9 5位八进制数
9'd6 9位十进制数
2.实数
实数可以用下列两种形式定义:
(1)十进制计数法,例如:
2.0
16539.236
(2)科学计数法
这种形式的实数举例如下,其中e与E相同。
235.12e2 其值为23512
5e-4 其值为0.0005
根据Verilog语言的定义,实数通过四舍五入隐式地转换为最相近的整数。
3.字符串
字符串是双引号内的字符序列。字符串不能分成多行书写。例如:
“counter”
用8位ASCII值表示的字符可看作是无符号整数,因此字符串是8位ASCII值的序列。为存储字符串“counter”,变量需要 位。
reg [1: 8*7] Char;
Char = ''counter'';
2.3.5 运算符和表达式
在Verilog HDL语言中运算符所带的操作数是不同的,按其所带操作数的个数可以分为三种:
∙ 单目运算符:带一个操作数,且放在运算符的右边。
∙ 双目运算符:带两个操作数,且放在运算符的两边。
∙ 三目运算符:带三个操作数,且被运算符间隔开。
Verilog HDL语言参考了C语言中大多数算符的语法和句义,运算范围很广,其运算符按其功能分为下列9类:
1. 基本算术运算符
在Verilog HDL中,算术运算符又称为二进制运算符,有下列5种:
∙ + 加法运算符或正值运算符,如:s1+s2; +5;
∙ - 减法运算符或负值运算符,如:s1-s2; -5;
∙ * 乘法运算符,如s1*5;
∙ / 除法运算符,如s1/5;
∙ % 模运算符,如s1%2;
在进行整数除法时,结果值要略去小数部分。在取模运算时,结果的符号位和模运算第一个操作数的符号位保持一致。例如:
运算表达式 结果 说明
12.5/3 4 结果为4,小数部分省去
12%4 0 整除,余数为0
-15%2 -1 结果取第一个数的符号,所以余数为-1
13/-3 1 结果取第一个数的符号,所以余数为1
注意:在进行基本算术运算时,如果某一操作数有不确定的值X,则运算结果也是不确定值X。
2. 赋值运算符
赋值运算分为连续赋值和过程赋值两种。
(1)连续赋值
连续赋值语句和过程块一样也是一种行为描述语句,有的文献中将其称为数据流描述形式,但本书将其视为一种行为描述语句。
连续赋值语句只能用来对线网型变量进行赋值,而不能对寄存器变量进行赋值,其基本的语法格式为:
线网型变量类型 [线网型变量位宽] 线网型变量名;
assign #(延时量) 线网型变量名 = 赋值表达式;
例如:
wire a;
assign a = 1'b1;
一个线网型变量一旦被连续赋值语句赋值之后,赋值语句右端赋值表达式的值将持续对被赋值变量产生连续驱动。只要右端表达式任一个操作数的值发生变化,就会立即触发对被赋值变量的更新操作。
在实际使用中,连续赋值语句有下列几种应用:
∙ 对标量线网型赋值
wire a, b;
assign a = b;
∙ 对矢量线网型赋值
wire [7:0] a, b;
assign a = b;
∙ 对矢量线网型中的某一位赋值
wire [7:0] a, b;
assign a[3] = b[1];
∙ 对矢量线网型中的某几位赋值
wire [7:0] a, b;
assign a[3:0] = b[3:0];
∙ 对任意拼接的线网型赋值
wire a, b;
wire [1:0] c;
assign c ={a ,b};
(2)过程赋值
过程赋值主要用于两种结构化模块(initial模块和always模块)中的赋值语句。在过程块中只能使用过程赋值语句(不能在过程块中出现连续赋值语句),同时过程赋值语句也只能用在过程赋值模块中。
过程赋值语句的基本格式为:
<被赋值变量><赋值操作符><赋值表达式>
其中,<赋值操作符>是“=”或“<=”,它分别代表了阻塞赋值和非阻塞赋值类型。3.5.1节对阻塞赋值和非阻塞赋值操作进行了详细解释。
过程赋值语句只能对寄存器类型的变量(reg、integer、real和time)进行操作,经过赋值后,上面这些变量的取值将保持不变,直到另一条赋值语句对变量重新赋值为止。过程赋值操作的具体目标可以是:
∙ reg、integer、real和time型变量(矢量和标量);
∙ 上述变量的一位或几位;
∙ 上述变量用{}操作符所组成的矢量;
∙ 存储器类型,只能对指定地址单元的整个字进行赋值,不能对其中某些位单独赋值。
例2-2 给出一个过程赋值的例子。
reg c;
always @(a)
begin
c = 1'b0;
end
3. 关系运算符
关系运算符总共有以下8种:
∙ > 大于
∙ >= 大于等于
∙ < 小于
∙ <= 小于等于
∙ == 逻辑相等
∙ != 逻辑不相等
∙ === 实例相等
∙ !== 实例不相等
在进行关系运算符时,如果操作数之间的关系成立,返回值为1;关系不成立,则返回值为0;若某一个操作数的值不定,则关系是模糊的,返回的是不定值X。
实例算子“===”和“!==”可以比较含有X和Z的操作数,在模块的功能仿真中有着广泛的应用。所有的关系运算符有着相同优先级,但低于算术运算符的优先级。
4. 逻辑运算符
Verilog HDL中有3类逻辑运算符: