目录
verilog中将寄存器(register)类型reg和线网(net)类型wire区分的较为清楚,SV则在此基础上引入了一个新的数据类型logic。
SV作为侧重于验证的语言,并不十分关切logic对应的逻辑应该被综合为寄存器还是线网,因为logic被使用的场景如果是验证环境,那么它只会作为单纯的变量进行赋值操作,而这些变量也只属于软件环境构建。
数据类型
与logic相对应的类型是bit类型,它们均可以构建矢量类型(vector)而它们的区别在于logic为四值逻辑,即可以表示0、1、x、z;bit为二值逻辑,只可以表示0和1。
四值逻辑类型:integer、logic、reg、net-type(例如wire、tri)
二值逻辑类型:byte、shortint、int、longint、bit
如果按照有符号和无符号的类型进行划分,那么可以将常见的变量类型划分为:
有符号类型:byte、shortint、int、longint、integer
无符号类型:bit、logic、reg、net-type(例如wire、tri)
在遇到这些变量类型时,应该注意他们的逻辑类型和符号类型,因为在变量运算中,应该尽量避免两种不一致的变量进行操作,而导致以外的错误。比如有符号数和无符号数的混用导致结果错误:
如上题,byte类型是有符号的,把8位的有符号数赋值给9位的无符号数,第一步它会先把自己扩展成9位,因为signed_vec的符号位为1,所以扩展位第9位也会为1,即signed_vec会将自己扩展成:9‘h180,然后再赋值给result_vec,最后这句赋值的输出结果为:result_vec = 'h180。
第二次赋值时,先把有符号数,通过unsigned 转换成无符号数再赋值,转换成无符号数后变为8’h80,然后再扩展为9位,高位补0,最后result_vec = unsigned(signed_vec)的结果为:result_vec = 'h080。
上面展示的转换数据类型的方式称为---静态转换,即需要在转换的表达式前面加上单引号。该方式不会对转换值做检查,如果转换失败我们也不会知道。所以与之对应的动态转换$cast(tgt,src) 也被经常运用到转换操作中,会在仿真的时候告诉你转换成功还是失败,静态转换是在编译的过程中进行的。
静态转换和动态转换均需要操作符号或者系统函数介入,我们统称为显示转换。而不需要进行转换的一些操作,我们称之为隐式转换。
对于上面这种,4位的四值逻辑,赋值给3位的二值逻辑,结果是什么样呢?高位赋值给低位,那么溢出的位就会被截掉,所以只剩下11x,这里要注意,四值逻辑中的x赋值给二值逻辑的时候,就是0。所以上面的赋值结果为b_vec = 110
综上,在对不同数据类型进行操作的时候,应当注意:
1.数据类型是四值逻辑还是两值逻辑?
2.数据是有符号还是无符号的?
3.数据的位宽是多少,是否会出现上溢情况?
数组
数组的声明
这两种声明都是可以声明出一个一维数组,数组的大小都是16。
多维度数组声明:
实际在敲代码的时候,只在module里敲上面三行会报错,赋值只能在声明的时候直接赋值,或者在过程块里面赋值,不能直接赋值。所以如果在声明后对变量赋值,需要在initial过程语句块中赋值。
初始化和赋值
这里要声明一点,变量的赋值必须在声明之后,哪怕是声明a→赋值a→声明b→赋值b,这样写也会报错!!!因为赋值a操作在声明b操作之前也是违法的。
执行报错。试了一下,无论是questasim还是vcs,编译都会报错。所以任何赋值都不能在变量声明之前!
合并数组与非合并数组
合并数组声明:bit [3] [7:0] b_pack;
非合并数组声明:bit [7:0] b_unpack [3]; //表示的都是24bit数据容量,但是实际的硬件容量来说,哪个存储容量更小呢?
合并数组它所占的空间是连续存储的,在存储空间里连续存储三个8bit。即连续占用24bit存储空间,对硬件来说就是一个WORD(32位操作系统一个WORD是4byte,64位就是8byte),而非合并数组,则会开三个WORD,分别存放b_unpack的三个8bit,占用三个WORD。
结果:b_pack占用实际存储1WORD,b_unpack占用实际存储3WORD。
问:如果把上面这题的bit类型,换成logic类型呢?结果有什么变化?
logic类型是四值逻辑,每1位需要2bit的实际存储空间,所以对合并数组来说,连续存储24位的 logic 变量需要48bit实际存储空间,如果是32位的操作系统,就需要2WORD来存储,而对于非合并数组来说,存储8位的logic变量,需要16bit实际存储空间,分配的1WORD也够用,所以非合并数组仍然占用3WORD实际存储空间。
基本数组操作: for 和 foreach
$size()函数可以取到变量的维度,src是位宽为32位,深度为5的二维数组。$size(src) = 5,取的是src的深度,而不是位宽。所以循环就是从0~4给src赋值,而foreach会把dst中所有元素遍历一次,我们会发现变量j没有声明但是可以使用,这就是foreach的特点,在foreach后面括号中写了什么变量,在foreach的循环里就得用该变量。
对多维数组使用foreach语法和我们想的不太一样,使用时并不想[ i ][ j ]这样把每个下标分别列在不同的方括号里,而是用逗号隔开后放在同一个方括号里:[ i , j ]。
要补充的是,foreach循环会遍历原始声明中的数组范围。数组f[5]等同于f[0:4],而foreach(f[i])等同于for (int i=0;i<=4;i++)。对于数组rev[6:2]来说,foreach(rev[i])语句等同于for(int i=6;i>=2;i--)。
数组的复制和比较
数组的复制,可以直接用一个等号复制。
动态数组
刚才提到的数组是定长数组,数组的长度在编译的时候就已经确定了。如果在程序运行时再确定数组的宽度就需要使用“动态数组”了。动态数组的最大特点就是可以在仿真时灵活调节数组的大小,类似C中的malloc函数。
动态数组在一开始声明时,需要利用‘[ ]’来声明,而数组此时是空的,即0容量。其后,需要使用‘new [ ] ’来分配空间,在方括号中传递数组的宽度。此外,也可以在调用new[ ] 时将数组名一并传递,将已有的数组的值复制到新数组中。
动态数组分配空间,及复制。d2 = dyn,这条语句,d2在赋值之前还是0个元素,赋值之后就是5个元素了,这就是动态数组。
dyn = new[20] (dyn)这条语句把dyn分配20个新值,并且低5五位是dyn的原始值,高15位是新值,都是0。然后紧接着下一条语句,dyn = new [100]分配100个新值,dyn全都变成0,旧的值全没了。
dyn.delete();可以删除动态数组中的所有元素,同样的还可以使用dyn = new [0]; 以及dyn = ‘{ };都可以删除动态数组中的所有元素。
当你把一个定宽数组复制给一个动态数组时,System verilog 会调用构造函数new[ ] 来分配空间并复制数值。
关联数组
假设我们需要对几个G字节寻址范围的处理器进行建模,在测试中发现,这个处理器可能只访问了用来存放可执行代码和数据的几百或几千个字节,其余的几G字节的存储空间都浪费了。针对这种情况,System Verilog提供了关联数组类型,用来保存稀疏矩阵的元素。
关联数组采用在方括号中放置数据类型的形式来进行声明,例如[int] 或 [Packet]
看过一个面经,面试官问怎么用非遍历的方式对稀疏矩阵结果顺序输出?
foreach会找到稀疏分布的元素1,2,4,8,16等,但输出的时候未必按照顺序输出,如果想要按照顺序输出,则可以先把标号放到一个数组里,然后利用数组的排序函数sort()来对下标进行排序,接着再按照下标对结果进行输出。
数组缩减方法
最常用的缩减方法是sum,它对数组中的所有元素求和。但是必须对System verilog的操作位宽规则小心,缺省情况下,如果把一个单比特数组的所有元素相加,其和也是单比特的。
绿皮书中说第二条和第三条display出来的结果都会是5,但经过实际测试,结果均为1,在这里给绿皮书纠错。
队列
队列结合了链表和数组的优点,可以在它的任何地方添加或删除元素,并且通过索引实现对任一元素的访问。队列的声明是使用带美元符号的下标:[ $ ],队列元素的标号从0到$。队列不需要new[ ] 去创建空间,你只需要使用队列的方法为其增减元素,一开始其空间为0。
队列的一个简单使用就是通过其自带方法push_back()和pop_front()的结合来实现FIFO的用法。
如图使用了多个自带的队列函数。但是并不是所有的Systemverilog 仿真器都支持使用insert()一个队列,比如q.insert(3,q2);即在q的第三个位置之后插入队列q2,这个我使用questasim编译报错。
这里要说一点,在对数组赋值的时候无论是定长数组还是动态数组,我们都要加一个单引号来表示赋值,而在队列的赋值中,我们没有使用单引号,这点注意。
如果把$放在一个范围表达式的左边,那么$将代表最小值,例如[$:2]就代表[0:2]。如果$放在表达式的右边,则代表最大值。
用户自定义结构
typedef语句可以用来创建新的类型,可以把parameter和typedef语句放到一个程序包(package)里,以使它们能被整个设计和测试平台所共用,用户自定义的最有用的类型是双状态的32比特的无符号整数,在测试平台中,很多数值都是正整数,例如字段长度或事务次数,所以把对uint的定义放到通用定义程序包中,这样就可以在仿真程序的任何地方使用它。
用户自定义变量
用户自定义数组
用户自定义结构体
由于结构体struct 只是一个数据的集合,所以它是可综合的。如果想在设计代码中对一个复杂的数据类型进行建模,例如像素,可以把它放到struct里。
字符串变量
System Verilog 中的string类型可以用来保存长度可变的字符串。单个字符是byte类型。长度为N的字符串中,元素编号从0到N-1。注意跟C语言不一样的是,字符串的结尾并不带标识符null,所有尝试使用字符“\0”的操作都会被忽略。
substr(start,end)函数能提取出从位置start到end之间的所有字符。
SV重要的数据类型基本就这么多。
参考资料:绿皮书20~50页