1 概述
本节主要讲解 Verilog 的数据类型和表达式。
2 数据类型
Verilog 中主要有两种数据类型:变量(variable)和线网(net)。这两种数据类型主要区别在于它们的赋值(assign)和保持(hold)方式,它们代表了不同的硬件结构。
2.1 线网(net)
线网(net)用于表示结构体(如逻辑门)之间的连接。除了 trireg 之外,所有其他的线网类型都不能保存值,线网的值是由 driver 决定的,例如由连续赋值驱动或者由逻辑门驱动。如果 driver 没有驱动线网,那么线网的值是 z,但是 tri0、tri1、trireg 除外,tri0 将是 0,tri1 将是 1,而 trireg 将保持之前 driver 驱动的值。
wire 线网,仅建立连接,而没有逻辑行为或功能,用于逻辑门的驱动或者连续赋值的驱动。例:
wire [15:0] input_net;
wire reg_flag;
| wire 是最常用的线网,当然还存在其他的线网。比如:
当多个驱动源驱动同一根线的线网,各真值表如下所示:
2.2 reg 寄存器
数据类型 reg 寄存器是硬件存储单元的一种抽象模型,但它并不直接与物理存储器直接对应。一个 reg 变量有一个默认的初始值 x。寄存器变量的默认大小是一个比特。例:
reg [7:0] reg_ff;//八比特的寄存器变量
reg flag;//单比特寄存器变量
|
2.3 integer 整数
数据类型 integer 支持过程赋值语句中的数值计算。整数可用来表示主机的字长。负整数是用补码的形式存储,而且 integer 变量默认值为 0。integer 类型也是一种寄存器数据类型。
Verilog 运算符是以补码算术形式在整数上进行运算,并且最高位是表示值的符号。例:
wire [15:0] data_out;
//十六比特的寄存器变量
reg [15:0] data_reg;
//十六比特的寄存器变量
integer i;
always@(*)
begin
for(i=0;i<15;i=i+1)
//循环 15 次
begin
data_reg = data_out[15 - i]; //依次赋值
end
end
|
2.4 real 实数
实数用关键字 real 来声明,是用双精度存储,典型的一个 64 位值,也可用十进制或科学计数法来表示。实数默认值为 0。不能把实数类型的变量连接在模块的端口上,如果将一个实数赋值给一个整数,则只有实数的整数部分会赋值给整数。例:
real data;
integer data_reg;
initial begin
data = 30.5;
//赋值
end
initial begin
data_reg = data;
//赋值
end
| data_reg 等于 30。
2.5 time 时间
数据类型 time 支持 Verilog 模型程序中关于时间的计算,time 变量存储为无符号 64 位的量值。可以使用系统函数$time 获取当前仿真时间。例:
time first_time;
initial begin
#1000;
first_time = $time;
//获取仿真时间
end
|
2.6 数组
reg 类型的一维数组称为存储器,可以理解为数组。这种构造是寄存器变量声明的扩展,用来提供存储器,比如相同字长的多个可寻址的单元。
数组维数没有限制。线网数组也可以用于连接实例模块的端口。数组中的每个元素都可以作为一个标量或者向量,以同样的方式来使用,形如:<数组名>[<下标>]。对于多维数组来讲,用户需要说明其每一维的索引。例:
integer
data_1[7:0];
reg [3:0] data_2[3:0];
wire [7:0] data_3[3:0];
wire
data_4[6:0][5:0];
reg [31:0] data_5[1:0][5:0][8:0][15:0];
|
2.7 字符串
对于字符串,Verilog 没有专门的数据类型对应。一个字符串必须通过一条过程赋值语句存入一个大小合适的寄存器中。一个大小合适的寄存器数组为要保存的字符串的每个字符留有 8 个比特的存储空间。例:
reg [8*num_char-1:0] string; //预留 8 比特的数 | 这个例子表明 num_char 个字符中的每一个字符都用 8 个比特来存储编码。如果“hello”被赋给 string,那么num_char 至少为 5 才可以保存字符串。如果给一个数组的赋值比将要处理的数组的字符数少,那么从最高位开始使用在未使用的位置上赋值 0。
2.8 常量
Verilog 中的常量可用关键词 parameter 来声明,它在声明的同时还可以对其进行常量赋值。一个常量的值在仿真过程中是不会改变的,常量表达式可以用于对一个常量值进行声明。例:
paramter data_1 = 31;
paramter data_2 = 131;
paramter data_3 = 231;
paramter data_4 = data_3*3-data_2;
paramter data_5 = 34,data_6 = data_4 - data_1;
| 还有一个常量 localparam,它与 paramter 的区别是只作用于某一个模块内部,且为常量,而 paramter 可以通过调用进行赋值。
3 表达式
Verilog 的表达式是将操作符合操作数结合起来产生一个结果,我们先来介绍操作符和操作数。
3.1 操作数
操作数可以为上一小节讲到的所有数据类型声明的数。比如,常数、整数、线网、寄存器、时间等。
3.2 操作符
3.2.1 算术运算符
算术运算符是双目操作符,对 2 个操作数进行算术运算,包括加(+)、减(-)、乘(*)、除(/)、求幂(**)、取模(%)。这些操作数的数值可以是二进制、八进制、十进制和十六进制的形式。例:
reg [3:0] A,B;
wire [4:0] sum,diff1,diff2,neg;
wire [7:0] mult;
assign sum =A+B;
assign diff1=A-B;
assign diff2=B-A;
assign neg=-B;
assign mult = A*B;
| 负数的表示在十进制下直接在前面添加“-”号,二进制中使用补码进行表示负数。
3.2.2 按位运算符
按位运算符有按位取反(~)、按位与(&)、按位或(|)、按位异或(^)和按位异或非(^~或~^)。按位操作符对 2 个操作数的每 1bit 数据进行按位操作。
如果 2 个操作数位宽不相等,则用 0 向左扩展补充较短的操作数。取反操作符只有一个操作数,它对操作数的每 1bit 数据进行取反操作。例:
wire [3:0] A=4'b1101,B=4'b1001,C=4'b0001,D=4'b1111;
~A -> 4'b0010
A&B-> 4'b1001
A|D-> 4'b1111
C^D-> 4'b1110
B^~C->4'b1000
|
3.2.3 缩减运算符
缩减运算符包含缩减与(&)、缩减与非(~&)、缩减或(|)、缩减或非(~|)、缩减异或(^)、缩减异或非(^~或~^)。缩减运算符是单目运算符,通过在一个数据上运算可以得到单个位的结果值。例:
wire [3:0] A=4'b1101;
&A -> 1'b0
~&A -> 1'b1
|A -> 1'b1
~|A -> 1'b0
^A -> 1'b1
^~A -> 1'b0
|
3.2.4 逻辑运算符
逻辑运算符有逻辑非(!)、逻辑与(&&)、逻辑或(||)、逻辑相等(==)、逻辑不等(!=)、情形相等(===)、情形不等(!==)。
Verilog 逻辑运算符作为逻辑连接,对布尔操作数进行运算得到布尔结果。该操作数可以是线网、寄存器或是可以得到布尔结果的表达式。情形相等(===)用来确定两个操作数的对应位上是否完全一致,其中包括了值为 X 和 Z 的比较。
3.2.5 关系运算符
关系运算符包括小于(<)、大于(>)、小于等于(<=)、大于等于(>=)。它用来比较两个操作数得到一个布尔值。如果操作数是线网或者寄存器类型的,其值可按无符号字对待。如果任何一个位是未知的,或者关系不明确的,则返回的结果是 X。
3.2.6 移位运算符
移位操作符包括左移(<<),右移(>>),算术左移(<<<),算术右移(>>>)。移位操作符是双目操作符,两个操作数分别表示要进行移位的向量信号(操作符左侧)与移动的位数(操作符右侧)。算术左移和逻辑左移时,右边低位会补 0。逻辑右移时,左边高位会补 0;而算术右移时,左边高位会补充符号位,以保证数据缩小后值的正确性。例:
A = 4'b1100 ;
B = 4'b0010 ;
A = A >> 2 ;
//结果为 4'b0011
A = A << 1;
//结果为 4'b1000
A = A <<< 1 ;
//结果为 4'b1000
C = B + (A>>>2);
//结果为 2 + (-4/4) = 1, 4'b0001
|
3.2.7 条件运算符
Verilog 条件赋值运算符根据一个 condition_expression 的值选择一个表达式用于求值。条件运算符的语法格式如下:
condition_expression ? true_expression : false_expression
如果 condition_expression 为真(逻辑值为 1),则运算结果为 true_expression;如果 condition_expression 为假(逻辑值为 0),则计算结果为 false_expression。例:
assign sel = (data[1:0] == 2'b00) ? sel_1 : sel_2 ;//data 为 00 时,选择 sel_1,否则为 sel_2 | 条件运算符可以进行嵌套,完成一个多次选择的逻辑。例:
assign hsel = (data[1:0] == 2'b00) ? sel_1 : //data 为 00 时,选择 sel_1
(data[1:0] == 2'b01) ? sel_2 : //data 为 01 时,选择 sel_2
(data[1:0] == 2'b10) ? sel_3 : //data 为 10 时,选择 sel_3
(data[1:0] == 2'b11) ? sel_4 ; //data 为 11 时,选择 sel_4
|
3.2.8 拼接运算符
拼接运算符可以将两个或多个的操作数形成一个单字。这个运算在形成逻辑总线的时候特别有用,拼接的结果仍然按照所给的字的顺序排列。拼接运算可以用到循环和任意程序的嵌套中。例:
{4{a}}={a,a,a,a}
{2{a},{{2'b01},{3'b110}}}
|
3.2.9 运算符的优先级
Verilog 是从左向右计算表达式的。布尔表达式的计算过程中,如果可以判断最终值为真或假,则马上结束运算。优先级由高到低,同一行优先级相同。
!~ | ** | */ % | + - | << >> <<< >>> | < > <= >=
== != === !==
| & ~& | ^ ^~ ~^ | | ~| | && | || | ?: |
|