2.1内建数据类型
- logic类型变量只能有一个驱动,当信号有两个驱动比如inout信号必须定义为wire类型变量。
- 常用数据类型:
双状态:bit(常用) ,byte(有符号8位) ,int(有符号32位),real(双精度浮点数)
四状态:integer(32位有符号),time(64位无符号)
$isunknown()操作符:表达式中任意位出现X或者Z时返回1;
2.2 定宽数组
- 声明方式:
//定宽数组的声明
int array [0:7][0:3];
//注意不要写成[7:0][3:0]
int array [8][4]
//给最后一个元素赋值
array[7][3]=1;
- 存储方式:
仿真器在存据时使用32bit的字边界,如图2-1所示,在非合并数组中,低位用来存储数据,高位不使用。
- 初始化
//数组初始化
int a[4]=’{4,3,2,1};
int b[2]=’{2{4}};
a =’{4,3,default:1}//其余为置为1;
- 数组只有在进行比较或者赋值时可以进行聚合操作,其他的需要进行循环操作,比如加法。
//数组的比较,赋值
bit [31:0] src[5],dst[5],add[5];
dst=src;
$display("dst %s src",(dst==src)?"==","!=");
foreach(add[i])
add[i]=dst[i]+src[i];
- 合并数组:
合并数组与非合并数组:bit [3:0][7:0] a [3]
bit [3:0][7:0]定义数组数据类型 a[3]是前方数据类型的数据个数。
@操作符只能用于标量或者合并数组,对于非合并数组,由于存储在不同的寄存器(或者说地址中),不能作为一个完整的敏感列表。比如a[2] 可以写成@(a[0] or a[1])
2.3动态数组:
//动态数组
声int a[];//声明
在使用的时候需要确定空间大小,用new[ ]来传递数组宽度,比如a=new[5];
dyn=new[ 20] dyn; //表示先复制原来dyn的数据,在申请满20个空间;
dyn=new[100]; //直接申请100个空间。
2.4队列
可以在任何地点进行元素操作,性能损失比动态数组小得多,会随时记录闲置的空间。
//队列的一些操作/方法:
int q[$]//声明
q.insert(i,j)
//表示在第i位插入数据j,后续的数据顺移。要注意队列是从第0位开始的。
q.delete(i)
//删除第i个元素
q.push_front(j)
//队列最前面插入数据j back则是从最后面
q.pop_back
//从队列尾部排出一个数据
关联数组:仿真器用树或者哈希表的形式来存放关联数组,有额外开销,关联数组用来存放超大容量的数组,偶尔使用,需要时在做研究。
SV中有链表,但是队列更高效,所以不常使用。
2.5数组的方法
//数组的缩减方法
bit x[10];
int sum,product,AND,OR,XOR;
//加,乘,与,或,异或
sum=x.sum+32'b0;//拓展到32位,不然结果为0or1
product=x.product;
AND=x.and;
OR=x.or;
XOR=x.xor;
//数组的定位方法
int f[6]='{1,2,2,2,5,6};
int tq[$];
int count;
tq=f.min()//1
tq=f.max()//6
tq=f.unique()//{1,5,6}
tq=f.find with (item>2)//{5,6}
tq=f.find_index with (item>2)//{4,5}
//index表示返回索引值而不是变量值,队列是从0开始计的
tq=f.find_first_index with (item>2)//{4}
//寻找第一个大于二的值并返回他的索引值
tq=f.find_last_index with (item>2)//最后一个{5}
count=f.sum with (item>2)//2
//item>2为真则返回1,所以返回两个1,加起来是2
count=f.sum with ((item>2)?item:0)//11
//为真返回item 加起来就是11
f.reverse();//倒序{652221}
f.reshuffle();//乱序重排
f.sort();//上升排列
f.rsort();//下降排列
2.6 创建新类型
//verilog宏定义
'define opsize 8;
'define opreg reg ['opsize-1:0];
//注意都要带'
//SV用户自定义
parameter opsize=8;
typedef reg [opsize-1:0] opreg_t;
//用户定义一般用_t结尾
typedef struct {bit[7:0]r,g,b;} pixel;
2.7 类型转换
SV不允许在没有进行显式转化的情况下将不同类别的数据直接赋值,用显式转化的目的在于提醒数据可能越界。
//向上转化:
base_clase bc;
sub_class sc = new();
bc = sc; //父类句柄指向子类对象,这是正确的
//向下转化:
base_class bc;
sub_class sc;
bc = new();
sc = bc; // 子类句柄指向父类对象(这是错误的);
$cast(sc,bc); // 此时通过cast方式仍然不行;
base_class bc;
sub_class sc1,sc2;
sc2 = new();
bc = sc2;
sc1 = bc; // 不行
$cast(sc1,bc); // 通过cast方式可以实现,可以看到bc的句柄类型虽然是父类,但其指的对象类型是子类
虽然父类指向了子类,但他还是父类类型,用$cast进行强制类型转化。总的来说父类可以指向子类,然后可转化为子类,子类不能指向父类;
对于方法来说,SV是根据对象类型来识别的,所以父类句柄指向子类对象的时候可以通过父类句柄调用子类的方法,但是对于成员变量来说,SV是根据句柄来时别的,所以需要进行类型转化进行成员变量数值的拷贝。
究其原因还是存储空间的分配,成员变量和方法存储的地方都不一样,子类所需的空间大于等于父类,用父类句柄指向子类的时候,父类的东西在子类中都可以找到,而子类指向父类就会越界,子类多出来的东西(比如成员变量)在父类对象哪里找不着,所以不行。举例子,父类包含一个整型变量,占四个字节,new的时候就分配四个字节,子类有两个整型需要8个字节。子类句柄指向父类对象的时候,就是将父类对象的地址赋给子类句柄,那根本没有8个字节,而访问成员变量的时候根据句柄来,这不就是寄了。
输出结果
C++程序的内存格局通常分为四个区:全局数据区(data area),代码区(code area),栈区(stack area),堆区(heap area)(即自由存储区)。全局数据区存放全局变量,静态数据和常量;所有类成员函数和非成员函数代码存放在代码区;为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区;余下的空间都被称为堆区。根据这个解释,我们可以得知在类的定义时,类成员函数是被放在代码区,公共方便访问。而类的静态成员变量在类定义时就已经在全局数据区分配了内存,因而它是属于类的。对于非静态成员变量,我们是在类的实例化过程中(构造对象)才在栈区或者堆区为其分配内存,是为每个对象生成一个拷贝,所以它是属于对象的。
2.8 枚举类型
//流操作符
//操作符<<将数据从右到左编程1/0数据流
//>>则是从左到右
int h;
bit [7:0] a,b,c,d;
bit [7:0] j[4]='{8'ha,8'hb,8'hc,8'hd};
h={>>{j}};//0x0a0b0c0d
h={<<byte{j}}//按字节倒序0xd0c0b0a0
{>>{a,b,c,d}}=j;//将j分散到四个字节变量中
//枚举 用于状态机转换
typedef enum {IDLE,START,STOP,FINISH} fsm;
//默认IDLE=0;START=1...一直顺下去
//就不用了像verilog中一样,一个个parameter
//第一个要么赋值成0,要么赋值
typedef enum {IDLE,START=2,STOP=5,FINISH} fsm;
//对应分别为0 2 5 6
我们就可以例化 fsm state,nstate;
//状态机的现状态以及下一个状态
//if(state==IDLE)nstate=START;