Verilog HDL语法概要
Verilog的词法非常类似于C语言,比如标识符是大小写敏感的;所有的关键字都是小写;单行注释以“ //”开头;多行注释以“ /*”开头、以“*/”结尾,标识符的组成等等。这里假定读者具有C语言语法基础,因此不对Verilog语法作详细介绍,重点介绍Verilog特有的语言现象。
数据类型及数的表示
数据类型
有两类重要的数据类型,变量类型和线网类型。它们的主要区别在于赋值和维持数据的方式。
线网类型包括wire、tri、wand、wor等,在FPGA设计中主要使用wire型。正如wire所表达的含义,可以把它理解为电路中的连线。wire型不存储值,它的值是由驱动端的值决定的。wire型的初始值是z。wire是系统缺省的线网类型,也可以用 `default_nettype
改变,见1.2.4。
变量类型包括reg、integer、time、real和realtime等。reg、integer、time型的初值是x,real和realtime型的初值是0.0。变量类型是数据存储特性的抽象,在一次赋值之后它保持其值直到下一次赋值。reg是可综合为物理元件的类型,其他几个主要用于高层次的抽象建模和仿真。特别需要注意的是,reg型变量并不一定就是逻辑电路中的寄存器,这里不能望文生义,在后面将有具体实例。在最初的Verilog-1995标准中,reg类型被称为寄存器(register)类型,在Verilog-2001中用“变量(variable)”代替了术语“寄存器(register)”,就是为了避免将reg型变量理解为寄存器。
标量和向量
(1)标量和向量的声明
线网和reg型可以指定数据宽度。如果没有指定,缺省为1位,称为标量(scalar),声明方式举例如下:
wire w; // wire型标量 reg a; // reg型标量 wire w1, w2; // 声明2个wire型标量
如果指定了位宽,就称为向量(vector)。声明方式举例如下:
wire [15:0] busa; // 16位总线 reg [3:0] v; // 4位 reg 型向量 reg [-1:4] b; // 6位reg向量 reg [4:0] x, y, z; // 声明3个5位reg型向量
缺省情况下,线网和reg型向量是无符号的;可以用关键字signed声明为有符号的(注:Verilog-1995没有有符号的向量),例如:
reg signed [3:0] signed_reg; // 有符号的4位reg向量,取值范围是-8~+7 wire signed [7:0] s; // 有符号的8位wire向量
(2)向量的位选择和部分选择
从向量中抽取一位称为位选择(bit-selects)。可以用一个表达式指定选择的位,语法如下:
vect[expr]
从向量中抽取几个相邻的位称为部分选择(part-selects)。部分选择有两种表达方式:常量部分选择(constant part-select)和可变部分选择(indexed part-select)。常量部分选择的语法如下:
vect[msb_expr:lsb_expr]
其中msb_expr和lsb_expr都只能是常量。可变部分选择的语法如下:
[base_expr +: width_expr] [base_expr -: width_expr]
width_expr是选择的位宽,必须是常量;base_expr是选择的起始位,可以是常量或变量;+: 表示由base_expr向上增长width_expr位,-: 表示由base_expr向下递减width_expr位。例如:
reg [31: 0] big_vect; //最高位为最大位号 reg [0 :31] little_vect; //最高位为最小位号 reg [63: 0] dword; integer sel; big_vect[ 0 +: 8] // == big_vect[ 7 : 0] big_vect[15 -: 8] // == big_vect[15 : 8] little_vect[ 0 +: 8] // == little_vect[0 : 7] little_vect[15 -: 8] // == little_vect[8 :15] dword[8*sel +: 8] // 具有固定宽度的可变部分选择
因为big_vect在声明时最高位为最大位号,little声明时的最高位为最小位号,所以可变部分引用[0 +: 8]用在big_vect表示big_vect[7:0],而用在little_vect则表示little_vect[0:7],也就是说位号的大小顺序与声明时一致。
最后说明一下,integer类型不能指定数据宽度,而是使用系统的设置,缺省是32位。integer类型不存在标量和向量之分。integer类型的变量是有符号的。
常数的表示
Verilog HDL的定长(Sized)整型常数的表示格式如下:
<位数> ' <基> <数字>
<位数>是用十进制表示的数字的位数;' <基>用来定义此数为十进制('d或 'D)、二进制('b或 'B)、十六进制('h或 'H)、八进制('o或 'O);<数字>即用相应进制表示的数,也可以包含x和z,不区分大小写。举例如下:
4'b1001 // 4位二进制数 5'D3 // 十进制数表示的3,位宽为5位,即二进制数00011 12'habc // 12位十六进制数 3'b01x // 3位二进制数,最低位是不确定值 16'hz // 16位高阻 4'b10?? // ?和z相同,即4'b10zz
如果不指定<位数>,则称为不定长(Unsized)数;此时若<基>省略,则表示十进制。举例如下:
659 // 十进制数 'h 837FF //十六进制数 'o7460 // 八进制数 4af // 非法(十六进制格式应该有 'h)
表示负数的负号应放在最前面,如:
-8'd3 // 用8位二进制补码表示的-3
可以用下划线增强可读性,如:
12'b1111_0000_1010 // 即12'b111100001010
在基的符号前加入s符号,可以显式地表明常数是有符号数。如
8'sh5d // 8位十六进制有符号常数+5DH -6'sd3 // 6位十进制有符号数-3
Verilog中的实数既可以用小数(如0.5),也可以用科学计数法(如3e6,1.7E8)来表达,带小数点的实数在小数点两侧都必须至少有一位数字。
运算符
Verilog的运算符见表 1。形式上大部分和C语言类似,只有归约运算符(Reduction)、并接/复制运算符(Concatenation,replication)以及算术移位运算符是Verilog特有的。优先级见表 2。
分类 | 运算符及功能 | 简要说明 |
---|---|---|
算术运算符 |
+ 加 - 减 * 乘 / 除 % 取余 ** 乘方 |
二元运算符,即有两个操作数。 %是求余运算符,在两个整数相除基础上,取余数。 例如,5%6的值是5;13%5余数3。 |
比较运算符 |
> 大于 < 小于 >= 大于等于 ⇐ 小于等于 == 逻辑相等 != 逻辑不等 === 全等 !== 非全等 && 逻辑与 || 逻辑或 ! 逻辑非 |
!为一元运算符,其他是二元运算符,关系运算的结果是1位逻辑值。如果操作数之间的关系成立,返回值为1;关系不成立,则返回值为0;若某一个操作数为不定值x,则关系是模糊的,返回值是不定值x。 逻辑相等与全等运算符的区别:逻辑相等运算,如果两个操作数中含有不定值或高阻值,则结果为不定值;而全等运算的结果要么为1要么为0。例如: A=8’b1101xx01 B=8’b1101xx01 则A==B 运算结果为x(不定); A===B 运算结果为1(真)。 |
位逻辑运算符 |
~ 按位非 & 按位与 | 按位或 ^ 按位异或 ^ ~ (~ ^) 按位同或 |
“~”是一元运算符,其余都是二元运算符。将操作数按位进行逻辑运算。 |
归约运算符 |
& 归约与 ~& 归约与非 | 归约或 ~| 归约或非 ^ 归约异或 ~ ^ (^ ~) 归约同或 |
一元运算符,对操作数各位的值进行运算。如“&”是对操作数各位的值进行逻辑与运算,得到一个一位的结果值1或0 。 例如:A=8’b11010001,则 &A=0,|A=1。归约与运算A中的数字全为1时,结果才为1;归约或运算A中的数字全为0时,结果才为0。 |
移位运算符 |
<< 逻辑左移 >> 逻辑右移 <<< 算术左移 >>> 算术右移 |
二元运算符,对左侧的操作数进行它右侧操作数指明的位数的移位。逻辑移位和算术左移时空出的位用0补全。算术右移时空出位的补全取决于结果的数据类型,如果是无符号型,补0;如果是有符号型,复制最高位。如果操作数有不定值x或高阻值z,结果为x。 |
条件运算符 |
?: |
三元运算符,即条件运算符有三个操作数。 操作数 = 条件 ? 表达式1 : 表达式2; 当条件为真(值为1)时,操作数=表达式1; 为假(值为0)时,操作数=表达式2。 |
并接运算符 复制运算符 |
{, } {{}} |
将两个或两个以上用逗号分隔的表达式按位连接在一起。还可以用常数来指定重复的次数。例如 {a,{2{a,b}}} 等价于{a,a,b,a,b}。 |
+ - ! ~ & ~& | ~| ^ ~^ ^~ (一元) |
优 先 级 从 高 到 低 |
** |
|
* / % |
|
+ -(二元) |
|
<< >> <<< >>> |
|
< <= > >= |
|
== != === !== |
|
& (二元) |
|
^ ^~ ~^ (二元) |
|
| (二元) |
|
&& |
|
|| |
|
?: |
|
{} {{}} |
模块
模块是Verilog 的基本描述单位,用于描述某个逻辑实体的功能、结构以及与其他模块通信的外部端口。模块可以小到简单的门,也可以大到整个系统,比如,一个计数器、一个存储子系统、一个微处理器等。
具有两个输入端口、一个输出端口的空模块定义如下。
module module_name(a, b, c); input a, b; output c; endmodule
在Verilog-2001中,引入了ANSI风格的端口说明。上例的ANSI风格的模块定义如下。
module module_name ( input a, input b, output c ); endmodule
模块的端口类型有三种:input(输入),output(输出)和inout(双向)。
编译指示字
编译指示字以 `
(键盘上Esc键下面的一个按键)开始。编译指示字的作用域并不限于声明它的文件,在整个编译过程中有效。这里只介绍几个常见的编译指示字。
`default_nettype
前面数据类型及数的表示已经介绍,wire是系统缺省的线网类型,也就是说,如果一个信号没有声明,编译器认为它是wire型标量。这在一定程度上方便了代码编写,但是也带来了一些隐患,比如打字错误会被编译器当作一个新的wire型标识符,再如没有声明的wire型向量会被当作wire型标量,为避免这些问题,可以关闭系统的缺省线网类型,如下。
`default_nettype none
当然,`default_nettype
不仅可以用来关闭系统的缺省线网类型,也可以用来改变系统的缺省线网类型,如将缺省线网类型改为wand型:
`default_nettype wand
`define 和`undef
`default_nettype
用于文本替换,它和 C 语言中的 #define
类似。如:
`define max(a,b) ((a) > (b) ? (a) : (b))
定义的符号在引用时也要以 `
开头,如:
n = `max(p+q, r+s) ;
`undef
用于取消前面定义的宏。如:
`undef max
一旦 `define
指示字被编译,所定义的符号在整个编译过程中都有效,直到用 `undef
取消。