数据类型
01
内建数据类型
Logic的引入背景
相比于verilog仍和net区分的如此清楚,在sv中新引入了一个数据类型logic,他们的区别和联系在于:
bit
与logic对应的是bit类型,他们均可建立矢量类型(vector),区别在于:
要点
exp
logic [7:0] logic_vec=8’b1000_0000;
logic [7:0] logic_vec=8'b1000_0000;
bit [7:0] bit_vec=8'b1000_0000;
byte signed_vec=8'b1000_0000;
initial begin
$display("logic_vec=%d",logic_vec);
$display( "bit_vec=%d",bit_vec);
$display("signed_vec=%d",signed_vec);
$stop();
end
猜一下运行结果是多少?
在遇到这些变量类型时,应注意他们的逻辑类型和符号类型,因为在变量运算中,应该尽量避免两种不一致的变量的进行操作,进而导致意外的错误!
exp
byte signed_vec=8'b1000_0000; //有符号的8位数
bit [8:0]result_vec; //无符号的9位数
initial begin
result_vec = signed_vec;
#10;
$display("@1 result_vec= 'h%x",result_vec);
#20;
result_vec=unsigned'(signed_vec); //转为无符号数
$display("@2 result_vec= 'h%x",result_vec);
$stop();
end
运行结果为:
有符号数signed_vec转为无符号数,首先是用自己的符号位拓宽一位,变为:9’b1_1000_0000。
在编码时,一定要注意操作符左右两侧的符号类型保持一致,如果不一致,首先将其转换为同一类型再进行运算。
对于转换方式,分为静态转换和动态转换,静态转换即在需要转换的表达式前加上单引号“`”即可,仅在编译时完成检查,该方式并不会对转换值做检查,如果转换失败也无从得知。动态转换$cast(tgt,src)在仿真转换过程中检查。
静态转换和动态转换均需要操作符号或系统函数介入,统称为显式转换(expilicit transfer)。
而不需要进行转换的一些操作,称为隐式转换(implicit transfer)。
exp
logic [3:0] x_vec=4'b111x; //四值逻辑
bit [2:0] b_vec; //二值逻辑
//隐式转换
initial begin
$display("@1 x_vec='b%b",x_vec);
b_vec=x_vec;
$display("@2 b_vec='b%b",b_vec);
$stop;
end
运行结果:
首先“111x”会截掉高位,x四值逻辑会转化为二值逻辑,关键在于x,x变为0!
在不同数据类型进行操作时要注意变量的:
02
数组部分
数组声明
int lo_hi[0:15]; //16个int类型变量,数组中有16个元素,从int [0]--->[15]
int c_style[16];
多维数组声明和使用
int array2[0:7][0:3]; //完整声明
int array3[8][4]; //紧凑声明,8:高维度,4:低维度
array2[7][3]=1; //设置最后一个元素为1
初始化和赋值
int ascend[4] = `{0,1,2,3};
//注意单引号的使用,对4个元素初始化,默认是从低到高,如果是非合并类型的(数据不是紧挨着存储的),那么就需要用一个“`”来赋值,队列类型数据是连续存放的,无需使用“`”。
int descend[5];
descend=`{4,3,2,1,0}; //为5个元素赋值
descend[0:2]=`{5,6,7}; //为前3个元素赋值
ascend=`{4{8}}; //4个值全为8
descend=`{9,8,default:-1011}; //{9,8,-1011,-1010,-1011}
存储空间变量
下面的两个变量,都可以表示24bit的数据容量,那么以硬件的角度出发,哪种方式的所需存储空间更小?
合并型: 所有的维度都写到变量的左边
非合并型:数组的任何一个维度写到变量的右边,所有空间连续的存储,更加节省空间。
b_unpack实际会占据3个WORD的存储空间,但是b_pack则只会占据一个WORD的存储空间。
exp
如果用logic类型来存储上面的数据,即24bit,且分别声明变量为:
logic [3][7:0] l_pack;
logic [7:0] l_unpack[3];
那么他们占据的实际存储空间为:______WORD,______WORD。
解析:
“logic [3][7:0] l_pack;”为合并型变量,存储空间连续,表面上看是占据:3*8个bit,实际上每个variable存在0、1两种可能情况,所以需要48bit(>32bit),也就是2WORD。
“logic [7:0] l_unpack[3];”为非合并型变量,存储空间不连续,但是每一组需要8*2=16bit<32bit(1WORD),所以需要3WORD实际存储空间。
03
基本数组操作
for和foreach
exp
initial begin
bit [31:0] src[5],dst[5]; //定义两个非合并型5*32数组,行数为5,列数为32
for(int i=0;i<$size(src);i++) //获取src行数(5),
src[i]=i;
foreach(dst[j]) //对于foreach而言,默认创建j
dst[j]=src[j]*2;
end
复制和比较
对于复制,可以使用复制符号“=”直接进行数组的复制;
对于比较,在不适用循环的情况下,也可以使用“==”或者“!=”来比较数组的内容,不过结果仅限于内容相同或不相同的情形。
exp
bit[31:0]src[5]=`{0,1,2,3,4}; //5行32列的非合并型存储空间
bit [31:0]dst[5]=`{5,4,3,2,1};
if(src==dst)
$display(“src==dst”);
else
$display(“src!=dst”);
src[0]=5; //修改src数组中的第一个元素,src[0实际上指向的是数组的首地址,与第一个元素相同。
动态数组
定宽数组类型:数组宽度在编译时就已确定,如果在程序运行时再确定数组的宽度就需要使用动态数组。
动态数组:最大特点就是在仿真时灵活调节数组的大小,即存储量。
动态数组在一开始声明时,需要使用“[]”来声明,而数组此时是空的,empty,其后需要使用“new[x]”来分配空间,x表示分配的存储空间列数(宽度)。
此外,也可以在调用“new[]”时将数组名也一并传递,将已有数组的值复制到新数组中。
exp
int d1[],d2[]; //声明动态数组
initial begin
d1=new[5]; //分配5个int类型元素
foreach(d1[j])
d1[j]=j; //对d1[5]进行初始化
d2=d1; //复制一个数组
d2[0]=5; //修改复制值
$display(“d1[0],d2[0]”); //显示0,5
d1=new[20](d1); //分配20个数并进行复制
/*attention:复制的结果是d1的最低的五个元素为0,1,2,3,4,其余为0*/
d1=new[100]; //重新分配100个数值,旧值不在存在。
d1.delete(); //删除所有元素,释放空间
/*也可以用d1=new[0]或d1=`{},注意new[0]中必须有0*/
end
exp
下面的例子中,d1初始为{1,2,3}的物理存储空间是否还在?
int d1=`{1,2,3}; //非合并类型变量
int cp[]=d1;
d1=new[3];
解析:不在,因为是把d1的内容,而new[3]重新分配,释放了之前的地址空间。
04
队列
【队列】:结合了链表和数组的优点,可以在任何地方添加或删除元素,并通过索引实现对任意元素的访问。
队列的声明格式为:name[],表示队列元素的标号从0到。
队列不需要new[]去创建空间,因为new[]只作用于动态数组,而使用队列的方法为增减元素,一开始其空间为0。
队列的一个简单使用是通过其自带的push_back()和pop_front()的结合来实现类fifo function。
push_back():从后面给fifo写入一个数据;
pop_front():从前面拿出一个数据。
exp
`timescale 1ns/1ns
module veri();
int j=1;
int v1[$]={3,4};
int v2[$]={0,2,5}; // v1[$]声明列表并初始化,the same to v2[$]
//队列赋值不需要使用“`”,说明是合并类型储存方式
initial begin
#20;
v2.insert(1,j); //在地址1前存储单元插入j,{0,1,2,5}
foreach(v2[i])
$display("v2[%d] is %d",i,v2[i]);
#10;
v2.insert(3,v1); //在v2的地址3前插入列表v1,{0,1,2,3,4,5}
foreach(v2[i]) $display("v2[%d] is %d",i,v2[i]);
#10;
v2.delete(1); //1为address,相当于指针,删除地址1的元素,{0,2,3,4,5}
foreach(v2[i]) $display("v2[%d] is %d",i,v2[i]);
/*在没有1时,即默认情况,删除v2中所有元素*/
$stop();
end
endmodule
运行结果为:
exp
下列操作运行速度更快(通过自有方法)
v2.push_front(6); //在队列头部插入6,à{6,0,2,3,4,5}
luck=v2.pop_back(); //从队列尾部取出给luck
两个EXP运行结果为:
05
关联数组
如果只是偶尔需要创建一个大容量的数组,那么动态数组就足够了,但如果需要一个超大容量的呢?动态数组的限制在于其存储空间一开始就被固定下来,对于稀疏类型的超大容量的数组,该方式存在着浪费,因为很有可能该大容量的数组中有相当多的数据不会被存储和访问。
关联数组:可以用来保存稀疏矩阵的元素,当对一个非常大的地址空间寻址时,该数组只为实际写入的元素分配空间(潜台词就是没有用到的地址就不会有存储空间映射),这种实现方式比定宽数组或动态数组所占用的空间资源要小得多。
在其他软件中,如martlab中典型的哈希结构(Hash)or词典结构(Dictionary),可以灵活而赋予键值(key)和数值(value)。
exp
bit [63:0] assoc[int] ,idx=1;
/*int规定了索引的数值类型*/
/*整体声明了一个关联数组,里面存储的数据是一个64位无符号类型数据,int表示索引他的是一个int type*/
repeat(64) begin
assoc[idx]=idx;
idx=idx<<1;
end
foreach(assoc[i]) //使用foreach遍历数组
$display(“assoc[%h] = %h”,i,assoc[i]);
//使用函数遍历数组
if(assoc.first(idx)) begin //得到第一个索引
do
$display(“assoc[%h] is %h”,idx,assoc[idx]);
while(assoc.next(idx)); //get next index
end
//找到并删除第一个元素
assoc.first(idx); //删去标号地址对应的元素
assoc.delete(idx);
06
结构体struct
verilog最大的缺陷之一是没有数据结构,在sv中可以使用struct语句创建结构体,与c语言类似。
不过struct的功能较少,只是一个数据的集合,其常用方式是将若干相关的变量组合到一个struct结构定义中。
伴随着typedef可用来创建新的类型,并利用新类型来声明更多的变量。
exp
struct { bit [7:0] r , g , b; } pixel; //rgb三个变量是分开存放的,非合并型
/***创建了一个pixel结构体***/
//为了共享该类型,通过typedef创建新类型
typedef struct { bit [7:0] r,g,b; } pixel_s;
pixel_s my_pixel; //声明变量
my_pixel= `{‘h10,’h10,’h10}; //结构体类型的赋值
何时用单引号“`”?
如果是非合并类型的(数据不是紧邻存储),在赋值时需要使用“`”来赋值,队列是连续存放,赋值时无需使用“`”。
07
枚举类型
规范的操作代码和指令(例如ADD、WRITE、MULTI)等有利于代码的编写和维护,它比直接使用’h10这样的常量使用起来具有更好的可读性和可维护性。
枚举类型enum常和typedef搭配使用,便于用户自定义的枚举类型的共享使用,同时枚举类型的出现保证了一些非期望值的出现,降低了设计风险。
exp
typedef enum{INIT,DECODE,IDLE} fsmstate_e; //e表示枚举类型
fsmstate_e psate,nstate; //声明自定义类型变量
initial begin
case(psate)
IDLE: nstate=INIT; //数据赋值
INIT: nstate=DECODE;
default: nstate=IDLE;
$display(“Next state is %s”,nstate.name()); //显示状态名
end
需要注意:枚举值缺省为从0开始递增的整数,也可以自定义枚举值,如:
typedef enum {INIT,DECODE=2,IDLE} fsmstate_e; //0----2----3
08
字符串
所有与字符串相关的处理,都是用string来保存和处理,与字符串处理相关的还包括字符串的格式化函数,即如何形成一个想要的句子?可以使用sv系统方法sformat(),也可以使用$display()。
exp
string s; //字串符定义,输出化为NULL,而非“\0”
initial begin
s=”IEEE ”; //5个char:I-E-E-E-E-_
$display(s.getc(0)); //显示地址0对应的char,即I
$display(s.tolower()); //显示小写ieee
s_putc(s.len()-1,”-”); //将空格替换为“-”
s={s,”P1800”}; //IEEE-P1800拼接
$display(s.substr(2,5)); //显示EE-P
//创建临时字符串
my_log($psprintf(“%s %5d”,s,42)); //生成要打印的字符串
end
task my_log( string message );
//将信息打印到日志中
$display(“@%05:%s”,$time,meesage);
endtask
09
运行环境搭建(以队列为例)
运行环境为VCS,编写Makefile脚本以便于处理,需要注意在编译选项中一定要打开打开“-sverilog”选项,分享一下自己的Makefile脚本:
.PHONY: com sim run_verdi clean
OUTPUT = veri
export demo_name=$(OUTPUT)
#coverage command
CM = -cm line+fsm+cond+branch+tgl
CM_NAME = -cm_name $(OUTPUT)
CM_DIR = -cm_dir ./$(OUTPUT).vdb
#vpd file name
VPD_NAME=+vpdfile+$(OUTPUT).vpd
#compile command
VCS= -vcs +v2k -sverilog -debug_all -timescale=1ns/1ns \
${CM} ${CM_DIR} ${CM_NAME} \
-LDFLAGS \
-rdynamic \
-P ${Verdi_HOME}/share/PLI/VCS/LINUX64/novas.tab \
${Verdi_HOME}/share/PLI/VCS/LINUX64/pli.a \
+vcs+lic+wait \
-full64 \
+notimingcheck \
+nospecify \
+vcs+flush+all \
-o $(OUTPUT) \
-l compile.log \
-f file_list.
SIM=./$(OUTPUT) \
+autoflush \
-l $(OUTPUT).log \
-gui &
com:
${VCS}
sim:
${SIM}
run_verdi:
verdi -f file_list.f -nologo -ssf $(OUTPUT).fsdb &
cov:
dve -covdir $(OUTPUT).vdb &
clean:
rm -rf {OUTPUT} ucli.key inter.* div.* csrc DVEfiles compile.log ${OUTPUT} $(OUTPUT).vdb $(OUTPUT).daidir