内建数据类型
我们知道,Verilog中,有两种基本的数据类型:reg
和wire
,reg
在always
、initial
、task
和funciton
中被赋值,wire
使用assign
赋值。
在systemVerilog中,引入了新的逻辑(logic)类型来代替reg
类型和部分wire
类型的功能,因此在sv中,编译器可自动判断logic
是reg
还是wire
。之所以说取代了部分wire
类型的功能,是因为logic
跟wire
还是有点区别的:wire
类型可以被多重驱动,比如inout
类型;但logic
类型不能被多重驱动,因此inout
类型不能被定义为logic
。
所以,总结logic的用法,
单驱动时logic可完全替代reg和wire
多驱动时,如inout类型端口,使用wire
systemVerilog主要是做仿真用的,当然,现在越来越多的人开发FPGA也都是用systemVerilog。为了提高仿真器的性能并减少内存的使用量,它引入了双状态数据类型。什么是双状态数据类型?就是它的值只能是0或者1这两个状态,而Verilog中,wire
和reg
都是四状态数据类型,除了0和1之外,还可能是z
或者x
,上面讲到的logic
就是四双态数据类型。
bit b; //双状态,单比特
bit [7:0] b8; //双状态,8位无符号数
int i; //双状态,32位有符号数
int unsigned ui; //双状态,32位无符号数
byte b8; //双状态,8位有符号数
shortint s; //双状态,16位有符号数
longint l; //双状态,64位有符号数
integer i4; //四状态,32位有符号数
time t; //四状态,64位无符号数
real r; //双状态,双精度浮点数
可以看到,systemVerilog中,既有双状态数据类型,也有四状态数据类型,这就很容易带来一个问题。当某个模块的输出是logic型,而在例化时,输出到了一个双状态类型上。如果都是正常的0或者1,那没什么问题;如果输出为x
或者z
,那这些值就被转换成了0或者1,关于这一点的内容,我们后续面会专门讲到。
SystemVerilog中的数组跟C是很像的,下面两种定义方式的效果是一样的。
int arr[0:15]; //包含16个int类型的数组
int c_arr[16];
多维数组的定义方式:
int arr1[0:7][0:3]; //完整的声明
int arr2[8][4]; //紧凑的声明
arr2[7][3] = 10; //设置最后一个元素为10
在C中,是不对数组越界进行检查的,当从一个越界的地址上读数时,也可以得到结果,这个结果是内存中的某个数据;但SystemVerilog中有数组越界的检查,当代码中试图从一个越界的地址中读取数据时,会返回数组元素类型的缺省值。对于四状态类型的数组,比如logic
,会返回X
,对于双状态类型的数组,比如int
或bit
,会返回0
。
这适用于所有的数组类型,包括定宽数组、动态数组、关联数组和队列,也同时适用于地址中含有X
或Z
的情况。wire
在没有驱动时输出Z
.
在声明一个数组时,可以直接对其初始化,也可以先声明数组,再进行赋值,跟C的用法基本一致,但赋值的语法有所区别。
int arr1[3] = '{1,2,3};
int arr2[4];
arr2 = '{0,1,2,3};
arr2[0:2] = '{4,5,6};
arr2 = '{4{7}}; //四个值都是7
arr2 = '{8,9,default:10}; //{8,9,10,10}
int arr3[2][3] = '{'{0,1,2},'{3,4,5}}; //多维数组初始化
数组遍历最常用的语法就是for
,SystemVerilog提供了for
和foreach
关键字来进行数组的遍历,其中for的用法跟C中基本一致,foreach的用法倒是跟Python中的for itm in
的用法很像,下面程序中$size
表示数组中元素个数。
initial begin
byte src[5], dst[5];
for(int i=0; i<$size(src); i++)
src[i] = i;
foreach (dst[j])
dst[j] = src[j] * 2;
end
我们前面讲了多维数组的初始化,下面来看下多维数组的遍历,在语法上还是有区别的,这也是SystemVerilog蛋疼的地方,现在编程语言虽然很多,但一些常规的语法都是一样的,在使用SystemVerilog中要多注意一下。
int md[2][3] = '{'{0,1,2},'{3,4,5}};
initial begin
foreach(md[i,j]) //注意,不是md[i][j]
$display("md[%0d][%0d] = %0d", i, j, md[i][j]) // 这里使用md[i][j]
end
在遍历时,如果不需要所有维度,可以在foreach
循环中忽略掉。
initial begin
byte md[2][3] = '{`{0,1,2},'{3,4,5}};
foreach(md[i]) begin
foreach(md[,j])
$display("%0d", md[i][j]);
end
end
复制和比较是数组中很常用的操作,在C中,比较数组是否相同需要用到strcmp()
函数,数组的拷贝需要用到memcpy()
函数,但SystemVerilog中将该操作简化,这一操作跟Python很类型。
initial begin
byte src[4] = '{0,1,2,3,4},
dst[4] = '{4,3,2,1,0};
if (src == dst)
$display("src == dst");
else
$display("src != dst");
dst = src; //数组拷贝操作
end
SystemVerilog仿真器在存放数组时一般都是使用32比特的字边界,所以byte
、shortint
和int
都是存放在一个字中,而longint
存放在两个字中,这样就是采用非合并数组的方式。
bit [7:0] b_unpack[3]; // 非合并数组定义
bit [2:0][7:0] b_pack; // 合并数组定义
在合并数组的声明中,合并的位和数组大小作为数据类型的一部分必须在变量名前面指定,而且数组大小定义的格式必须是[msb:lsb],而不是[size]。
内存中的存放方式如下:
非合并时数组的存放:
合并数组的存放:
讲到这里,我们就再补充一点,对于logic
和integer
等四状态类型,仿真器通常使用两个或两个以上连续的字来存放,这会比双状态变量多占用一倍的空间。
往期文章:
Vivado中模块封装成edif和dcp
自动驾驶入门之视觉定位坐标转换
什么是噪声温度?-174dBm/Hz又是什么?
FPGA 中的有符号数乘法
为什么推荐使用XPM?
RAM IP Core中 Write First Read First和No Change的区别
Vivado调试小结:ILA debug中的数据也许并不可信
FPGA复位的正确打开方式
如何使用Git进行Vivado工程的管理
大家一致避免使用的锁存器为什么依然存在于FPGA中?我们对锁存器有什么误解?
影响FPGA时序的进位链(Carry Chain), 你用对了么??
Virtex7 Microblaze下DDR3测试
Matlab高效编程技巧
生成Verilog HDL例化模板
DCM/DLL/PLL/MMCM区别
FPGA时序约束教程:
FPGA时序约束理论篇之建立保持时间
FPGA时序约束理论篇之时序路径与时序模型
3. FPGA时序约束理论篇之IO约束
4. FPGA时序约束理论篇之时钟周期约束
5. FPGA时序约束理论篇之两种时序例外
6. FPGA时序约束理论篇之xdc约束优先级
7. FPGA时序约束实战篇之梳理时钟树
8. FPGA时序约束实战篇之主时钟约束
9. FPGA时序约束实战篇之衍生时钟约束
10. FPGA时序约束实战篇之延迟约束
11. FPGA时序约束实战篇之伪路径约束
12. FPGA时序约束实战篇之多周期路径约束
13. Vivado时序约束辅助工具
14. FPGA时序约束之Tcl命令的对象及属性
欢迎关注微信公众号:
加FPGA技术交流群的朋友,请加微信:xhclsys2