Verilog HDL语法概要

Verilog的词法非常类似于C语言,比如标识符是大小写敏感的;所有的关键字都是小写;单行注释以“ //”开头;多行注释以“ /*”开头、以“*/”结尾,标识符的组成等等。这里假定读者具有C语言语法基础,因此不对Verilog语法作详细介绍,重点介绍Verilog特有的语言现象。

数据类型及数的表示

四值逻辑

Verilog HDL 有下列四种逻辑值:

0:逻辑 0 或“假”;

1:逻辑 1 或“真”;

x:不确定值(Unknown Value);

z:高阻。

数据类型

有两类重要的数据类型,变量类型和线网类型。它们的主要区别在于赋值和维持数据的方式。

线网类型包括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-1995只能定义一维数组)。举例如下:

reg [7:0] mema[0:255]; // 声明一个字长为8位、有256个单元的存储器
reg arrayb[7:0][0:255]; // 声明一个二维数组,字长1位
wire w_array[7:0][5:0]; // 声明wire型二维数组
integer inta[1:64]; // 声明有64个元素的integer型数组
time chng_hist[1:1000] //声明有1000个元素的time型数组

运算符

Verilog的运算符见表 1。形式上大部分和C语言类似,只有归约运算符(Reduction)、并接/复制运算符(Concatenation,replication)以及算术移位运算符是Verilog特有的。优先级见表 2

表 1. Verilog运算符
分类 运算符及功能 简要说明

算术运算符

+ 加

- 减

* 乘

/ 除

% 取余

** 乘方

二元运算符,即有两个操作数。

%是求余运算符,在两个整数相除基础上,取余数。

例如,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}。

表 2. 运算符的优先级

+ - ! ~ & ~& | ~| ^ ~^ ^~ (一元)

**

* / %

+ -(二元)

<< >> <<< >>>

< <= > >=

== != === !==

& (二元)

^ ^~ ~^ (二元)

| (二元)

&&

||

?:

{} {{}}

模块

模块是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 取消。

`ifdef, `else, `elsif, `endif, `ifndef

这些编译指示字用于条件编译。下面是一个例子,如果之前已经用 `define 定义了BEHAVIORAL,持续赋值语句被编译;否则,一个与门被实例化。

module and_op
(
  output a;
  input b, c
);

`ifdef BEHAVIORAL
  wire a = b & c;
`else
  and a1 (a,b,c);
`endif

endmodule

`include

`include 编译指示字用于插入文件的内容。文件既可以用相对路径名定义,也可以用全路径名定义,和C语言的#include用法相当。