在Verilog可以用4种数值来描述其构建的电路的电平逻辑,除了event类型和real类型外,几乎所有的数据类型都可以用这4种数值来表示。
不同的仿真软件对这些数值的显示可能各不相同,在xilinx的vivado软件这4种数值的显示是这样的:1是全覆盖的绿色横框;0 是绿色横条;x 是半覆盖的红色横框;z 是半覆盖的蓝色横框。
Verilog中主要有两大类数据类型:
Net用来构建电路中不同元素的物理连接,自身不存储任何数据,它的值只取决于所连接的驱动电路的值(默认值为z,即高阻态)。Net类数据下有很多子类型的数据,比如wire、tri、wand、wor、tri1等等,其中wire类型是最常用的,其他类型数据在实践中几乎不会被用到。
下图中的net_11就是一个net型数据(确切的说应该是wire数据),它左边连接着与门的输出端、右边连接着触发器的输入端。它自身不能存储任何数据,它的值取决于它左侧所连接的与门的输出端,然后它又将这个值赋给它右侧所连接的触发器。
下面是几种wire类型的声明方式:
wire flag; //声明一个wire
wire flag1,flag2; //声明多个wire
wier flag = 1'b0; //声明wire的同时并赋值
除了wire类型外,还有一些其他的net类型。由于verilog语言的灵活性,这些net类型几乎都可以使用wire类型 + 门级电路来构建,所以在实践中,这些类型一般都不会被使用。这里稍微了解一下就可以:
Variable(变量)类型用来存储电路中的数据,它会一直保存数据直到下一次改写。Variable类型有reg、time、integer、real和realtime5个子类型,其中最为常用的是reg(寄存器)类型。
reg和wire是verilog中最为常用的两种数据类型,它俩可以分别用来构建组合逻辑电路和时序逻辑电路。
reg以时钟信号为基准,用来在电路中存储数据,触发器(flip-flop)就是一种典型的存储数据的电路。下面是几种reg变量的声明方式:
reg flag; //声明一个reg
reg flag1,flag2; //声明多个reg
reg flag = 1'b0; //声明reg的同时并赋值
integer(整数)
整数变量用关键字 integer 来声明,声明时不用指明位宽--位宽和编译器有关(一般为32 bit)。在实践中,integer变量一般用作辅助使用(integer的引入并不会生成硬件电路),例如指定某个reg/wire的位宽(方便更改),或在循环语句中充当循环参数等。例如:
integer i;
always@(*) begin
for (i=0;i<=5;i=i+1) begin
······
end
end
real(实数)
实数用关键字 real 来声明,可用十进制或科学计数法来表示。real 声明不能带有范围,默认值为 0。real 可以用来存储浮点数。例如:
real data1 =1e3;
real data2 = 3.14;
time、realtime(时间)
time/realtime变量可以用保存仿真时间,位宽多为64bit,调用系统函数$time/$realtime可以获取当前仿真时间,time和realtime的区别在于,一个是存储整数变量(time),另一个存储实数变量(realtime)。例如:
time t1 = 25;
realtime rt1 = 2.5;
在声明wire或reg变量时,如果不指定范围,那么此时的wire/reg就是一个标量(scalar);反之,如果指定了范围,那么此时的wire/reg就是一个向量(Vector)。
wire类型的向量与标量形式:
wire [3:0] n0; //4位宽的向量wire
wire n1; //单位宽的标量wire
reg类型的向量与标量形式:
reg [3:0] d0; //4位宽的向量reg
reg d1; //单位宽的标量reg
向量最左端的值被称为最高有效位MSB(Most Significant Bit),最右端的值被称为最低有效位 LSB(Least Significant Bit)。MSB和LSB的值可以是任意整数(正整数、负整数和零),而且MSB即可以大于LSB,也可以小于LSB,但两者不能相等(相等就是标量了)
对于向量,可以根据其地址,来对某一位或某几位进行操作。
位操作(bit-selsct)
可以通过地址索引的方式,对向量中的某一位进行选取和操作,这就是位操作bit-selsct。例如:
reg [7:0] addr; //声明一个8位宽的reg型变量(向量)addr
addr [0] = 1'b1; //直接对addr的第0位进行操作,将其赋值为1
addr [3] = 1'b0; //直接对addr的第3位进行操作,将其赋值为0
部分操作(part-selsct)
既然可以通过地址作为索引,来对向量的某一位进行操作,那么必然也可以通过多位地址来对向量的多位进行操作,这就是部分操作part-selsct。例如:
reg [31:0] addr; //声明一个32位宽的reg型变量(向量)addr
addr[23:16] = 8'h23; //直接对addr的第16~23位进行操作,将其赋值为8'h23,即8'b0010_0011
Verillog 还支持指定 bit 位后固定位宽的向量域选择访问。开始位可以是变化的,但是位宽必须固定为常数,这个语法在循环等语句中非常实用。
reg [31-:8] addr1; //等价于 reg [31:24] addr1。实践中起始位31可以变化,位宽固定为8,等于选取该变量的某个8位
reg [0+:8] addr2; //等价于 reg [0:8] addr2。实践中起始位0可以变化,位宽固定为8,等于选取该变量的某个8位
在 Verilog 中可以声明 reg, wire, integer, time, real类型的数组(Array)。数组维数没有限制,其中的每个元素可以是标量或者向量。
reg y1[3:0]; //1维数组,共有4个元素,每个元素位宽为1(即标量)
reg [3:0] y2[3:0]; //1维数组,共有4个元素,每个元素位宽为4
reg [3:0] y3[3:0][15:0]; //2维数组,共有4*16 = 64个元素,每个元素位宽为4
数组与向量一样,也可以通过地址索引的方式进行访问。
y1 = 0; //非法赋值--不能同时对4个元素赋值
y1[0] = 1'b0; //对y1的第0个元素赋值为1'b0
y2[2] = 4'b1111; //对y2的第2个元素赋值为4'b1010
y3[2] [1] = 4'b1100; //对y3的第2行、第1个元素赋值为4'b1100
需要注意的是,向量是一个单独的元件,其位宽为 n;而数组则由多个元件组成,其每个元件的位宽为 n 或 1。尽管他们的访问方式类似,但它们在定义上就有所区别。
reg [n-1:0] rega; // rega是一个reg向量,它只有一个元件,位宽为n
reg mema [n-1:0]; // mema 是一个数组,它有n个元件,每个元件的位宽为1
存储器(Memory)就是一种一维寄存器数组,它可以用来构建 RAM(可读写) 或 ROM(仅可读)。
reg [7:0] mem [255:0]; //可以理解为构建了一个256个格子的存储器,每个格子都可以存储8个bit的数据