在线咨询
eetop公众号 创芯大讲堂 创芯人才网
切换到宽版

EETOP 创芯网论坛 (原名:电子顶级开发网)

手机号码,快捷登录

手机号码,快捷登录

找回密码

  登录   注册  

快捷导航
搜帖子
查看: 4497|回复: 10

[其它] SV学习笔记

[复制链接]
发表于 2020-6-4 16:47:05 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

x
数组操作


对于verilog,数组经常会被用来做数据存储,例如reg[15:0] RAM[0:4095];//存储数组

SV将Verilog这种声明数组的方式称之为非组合型数组,即数组中的成员之间存储数据都是相互独立的。
这种方式虽然消耗更多的存储空间,但更利于查找元素。


SV保留了飞组合型数组声明方式,并且扩展了允许的类型,包括:event,logic,bit,byte,int,longint,shortreal,real类型
SV也保留了Verilog索引非组合型数组或者数组片段的能力,这种方式为数组以及数组片段的拷贝带来了方便。
例如:


int a1[7:0][1023:0];//非组合型数组
int a2[1:8][1:1024];//非组合型数组
a2=a1;//拷贝整个数组
a2[3]=a1[0];//拷贝一个数组片段


SV组合型数组
SV将Verilog的向量作为组合型数组声明方式
wire [3:0] select;//4比特的组合型数组
reg [63:0] data;//64~
SV也进一步允许多维组合型数组的声明
logic [3:0][7:0] data;//2维组合数组
组合型数组不同于非组合型数组的地方在于声明方式。组合型数组的索引范围在数组名的左侧,
而数组的维度是从左到右识别。组合数组更节省空间。
组合型数组会进一步规范数据的存储方式,而不需要关心编译器或者操作系统的区别。


组合型(packed)除了可以运用的数组声明,也可以用来描述 结构体
typedef struct packed {
logic [7:0] crc;
logic [63:0] data;
}data_word;
data_word [7:0] darrary;//1位组合型数组,元素也为组合型结构体


组合型数组和其数组片段也可以灵活选择,用来拷贝和赋值等
logic [3:0][7:0] data;//2维组合型数组
wire [31:0] out=data;//整个数组
wire sign==data[3][7];//单个比特
wire [3:0] nib=data [0][3:0];//数组片段
byte high_byte;
assign high_byte;
assign high_byte=data[3];//8比特的数组片段
logic [15:0] word;
assign word=data[1:0];//2个数组片段


组合型数组和非组合数组在做初始化时有明显差别,组合型数组的元素是连续存放的。可以对所有元素统一赋值。
非组合型数组(unpacked)数组初始化时,则需要通过’{}来对数组的每一个维度经行赋值。
int d [0:1][0:3]='{'{7,3,0,5},'{2,0,1,6}};
非组合型数组赋值繁琐,所以可以在其初始化时,用default关键词对所有元素默认赋值
int a1 [0:7][0:1023]='{default:8'h55};


非组合型数组的数据成员或者数组本身均可以为其赋值
byte a[0:3][0:3];
a[1][0]=8'h5;//


以下是组合型数组的赋值方法
logic [1:0][1:0][7:0] a;//3维数组
a[1][2][3]=1'b0;//为1比特赋值
a=32'hF1A3C5E7;//给整个数组赋值
a[1][0][3:0]=4'hF;//给一个数组片段赋值
a[0]=16'hFACE;//给一个数组片段赋值f
a={16'bz,16'b0};//通过连接运算符赋值
对于组合型数组,由于数组会被视为向量,因此当赋值左右两侧操作数的大小和维度不相同时,也可以做赋值
如果当尺寸不相同时,则会通过截取或者扩展右侧操作数的方式来对左侧操作数赋值。
对于非组合型型数组则要求左右两侧操作书的维度和大小必须严格一致。


SV添加了foreach循环来对一维或者多维数组进行循环索引,而不需要制定该数组的维度大小。
int sum [1:8][1:3];
foreach (sum[i,j]) sum[j]=i+j;//数组初始化
foreach循环结构中的变量无需声明。
foreach循环结构中的变量是只读的,其作用域只在此循环结构中。


SV系统函数
$dimensions(array_name)用来返回数组的维度。
$left(array_name,deimension)返回指定维度的最左索引值(msb)。

logic [1:2][7:0] word [0:3][4:1];
$left(word,1) will return 0
$left(word,2) will return 4
$left(word,3) will return 1
$left(word,4) will return 7


与$ left类似的还有¥right low high(array_name,dimension).
$size可以返回指定维度的尺寸大小
$increment如果指定维度的最左索引值大于或等于最右索引值,那么返回1,否则返回-1
$bits(expression)可以用来返回数组存储的比特数目。

 楼主| 发表于 2020-6-6 17:10:29 | 显示全部楼层
数组类型


与之前的定长数组相比,SV还提供了可以重新确定大小的动态数组。
动态数组在声明时需要使用[],这表示不会在编译时确定它的尺寸,而是在仿真时来确定
动态数组一开始为空,而需要使用new[]为其分配空间。


比如:
int dyn[],d2[];//声明动态数组
initial begin
dyn=new[5];//分配五个元素
foreach(dyn[j]) dyn[j]=j;//对元素经行初始化
d2=dyn;//复制一个动态数组
d2[0]=5;//修改复制的值
$display(dyn[0], d2[0]);//打印数值0和5
dyn=new[20](dyn);//分配20个整数值并复制
dyn=new[100];//分配100个新的整数值


dyn.delete();//删除所有元素
end


动态数组的内置方法
size()返回动态数组的大小
delete()清空数组,使其尺寸变为0
动态数组在声明时可以完成其初始化
bit[7:0]mask[]='{……};


队列
结合了数组和链表
可以在队列任何位置添加或者删除数据成员
可以通过索引来访问队列的任何一个成员
通过[$]来声明队列,队列的索引值从0到$。
可以通过内建方法push_back()\push_front()\pop_back()\pop_front()来顺序添加或者移除获得数据成员
insert(val,pos)来指定位置插入数据成员。


如果想要在仿真时创建一个大的数组,也许动态数组是一个选择,不过有的时候,我们并不需要那么大的一个数组。
由于处理器在访问存储时的访问时随机或者散乱的,这意味在一个测试中,处理器也许只会访问几百个存储地址,
而剩下的大多数的地址都将被初始化为0并且浪费时的存储空间。


sv引入了关联数组,可以用来存放散列的数据成员。这一点同其他脚本语言例如Perl或者python类似,关联数组的索引
类型除了为整形以外还可以为字符串或者其他类型,而关联数组存储的数据成员也可以为任意类型。


byye assoc[byte], idx=1;
initial begin
//对稀疏分布的元素经行初始化
do begin
assos[idx]=idx;
idx=idx<<1;
end while(idx=!0);
// 使用foreach遍历所有索引值
foreach(assos)
$display("assos[%h]=%h",i,assoc);
……




缩减方法


基本的数组缩减方法是把一个数组缩减成一个值。
最常用的缩减方法是sum,它对数组中的所有元素求和。
其他的数据缩减方法还有product(积),and(与),or(或)和xor(异或)。
对于非合并数组,可以使用数组定位方法,其返回值将是是一个队列而非一个数据成员。
int f[6]='{1,6,2,6,8,6};
int d[]='{2,4,6,8,10};
int q[$]={1,3,5,7},
   tq[$]
tq=q.min();
tq=d.max();
tq=f.unique


定位方法


使用foreach也可以实现数组的搜索,不过使用find...with则在查找满足条件的数据成员时,更为方便
int d[]='{9,1,8,3,4,4},tq[$];
//找出所有大于三的元素
tq=d.find with(item>3);//{9,8,4,4}
//等效代码
tq.delete();
foreach (d)
if(d>3)
tq.push_back(d);


排序方法


可以通过排序方法改变数组中元素的顺序,可以对它们经行正向、逆向或者乱序的排列。
int d[]='{9,1,8,3,4,4};
d.reverse();//'{4,4,3,8,1,9};
d.sort();//'{1,3,4,4,8,9};
d.rsort();//'{9,8,4,4,3,1}
d.shuffle();//'{9,4,3,8,1,4}
 楼主| 发表于 2020-6-8 00:49:30 | 显示全部楼层
函数和任务
函数function不会消耗仿真时间,而任务task则可能会消耗仿真时间。
函数无法调用任务,而任务可以调用函数。
一个函数只能返回一个数值,而任务则不能返回数值
函数可以作为一个表达式中的操作数,而该操作数的值即函数的返回值。

参数传递
input参数方向在方法调用时,属于值传递。即传递的过程中,外部变量的值会经过拷贝,赋值给输入参数。
output、inout也会在传入或者传出的时候发生值传递的过程。
值传递的过程只发生在方法的调用时和返回时。
ref参数在传递时不会发生值拷贝,而是将变量指针传递到方法中,在方法内部对该参数的操作将会
同时影响外部变量。
如果为了避免外部传入的ref参数会被方法修改,则可以添加const修饰符,来表示变量时只读变量。

参数默认值
sv允许方法声明输入参数时指定参数的默认值。
带有参数默认值的方法被调用时,如果这些参数没有被传递值,那么编译器将会为这些参数传入对应的默认值。
SV允许类似于模块例化,可由参数位置在调用方法时传递参数,也可以由参数名字映射的方式来传递参数。

SV允许不带ref进行数组参数的传递,这是数组会被复制到堆栈区里。这种操作的代价很高,除非是对特别小的数组。
ref参数的第二个好处实在任务里可以修改变量并且修改结果对调用它的函数随时可见。

在SV中可以为参数指定一个缺省值,如果在调用时不指明参数,则使用缺省值。
在编写子程序代码时最容易犯的错误就是,往往会忘记,在缺省的情况下参数的类型是与其前一个参数相同的,而第一个参数
的缺省类型的是单比特输入。

对简单的int变量使用ref通常并无必要,但编译器不会对此作出任何反应,连警告都没有,所以你不会意识到正在使用一个错误的
方向类型。如果在子程序中使用了非缺省输入类型的参数,应该明确指明所有参数的方向。


发表于 2020-6-10 18:13:52 | 显示全部楼层
有兴趣整理一下投稿公众号吗~
我有一个个人公众号,摸鱼范式,也是IC设计验证相关的
 楼主| 发表于 2020-6-11 16:08:24 | 显示全部楼层


空白MAX 发表于 2020-6-10 18:13
有兴趣整理一下投稿公众号吗~
我有一个个人公众号,摸鱼范式,也是IC设计验证相关的 ...


巧了,我关注你这个公众号了,上面有很多UVM实战的文章我都抄了,哦不,我是说我都学习了。发公众号的话,我这些太初级了,还不成体系呀,慢慢来吧。

发表于 2020-6-11 16:52:14 | 显示全部楼层
先插眼关注一波,打算开始学习sv。
 楼主| 发表于 2020-6-12 15:49:44 | 显示全部楼层
线程
线程即独立运行的程序。
线程需要被触发,可以结束或者不结束。
在module中的initial和always,都可以看做是独立的线程,它们会在仿真时刻开始,而选择结束或者不结束。
硬件模拟中由于都是always语句块,所以可以看成是多个独立运行的线程,而这些线程会一直占用仿真资源,因为它们不会结束。
验证环境都需要有initial语句块去创建,而在仿真过程中,验证环境中的对象可以创建和销毁,因此验证环境的资源是动态的。


验证环境中initial块与语句有两种分组方式,使用begin…end或fork...jion.
begin..end中的语句以顺序方式执行,而frok...jion中的语句则以并发方式执行。
与fork...jion(等待所有线程结束)类似的并行方式语句还包括frok...join_any(等待最短子线程结束,其余会在后台执行),fork...jion_none。

对于frok...jion_any frok..jion_none继续执行后,其一些未完成的子线程仍将在后台运行。
如果要等待这些子程序全部完成,或者停止这些子线程,可以使用wait fork或者disable fork。

SV可以通过延迟控制或者事件等待来完成时序控制。
延迟控制即通过 # 来完成。
#10 rega=regb;
事件(event)控制即通过@来完成。
@r rega=regb;
@(posedge clock)rega=regb;
wait语句也可以与事件或者表达式结合来完成。
real ADR[];
initial wait{ADR.size()>0}...;


线程的同步


测试平台中的所有线程都需要同步并交换数据。
一个线程等待另一个,例如验证环境需要等待所有激励结束、比较结束才可以结束仿真。
比如检测器需要将监测到的数据发送至检查器,检查器又需要从不同的缓存获取数据进行比较。

可以通过event来说声明一个event变量,并且去触发它。
这个event变量可以用来控制多个线程间的同步。
可以通过 -> 来触发事件。
其他等待该事件的线程可以通过@操纵符或者wait()来检查event触发状态来完成。
event done,blast;
event done_too=done;
task tringger(event v);
->ev;
endtask
...
fork
@done_tool#1 trigger(done);
jion
fork
->blast;
wait(blast.triggered);//完成电平触发等待
join



wait_order可以使得线程保持等待,知道在参数列表中的事件event按照顺序从左到又依次完成。
如果参数列表中的事件被触发但是没有按照要求的顺序,那么会使得等待操作失败。
wait_order(a,b,c);
wait_order(a,b,c) else $display("")
bit success;
wait_order(a,b,c)success=1; else success=0;


旗语(semaphore)
在创建旗语的时候,会为其分配固定的钥匙数量。
使用旗语的进程必须先获得其钥匙,才可以继续执行。
旗语的钥匙数量可以有多个,等待旗语钥匙的线程也可以有多个。
旗语通常用于互斥,对共享资源的访问控制,以及基本的同步。


声明旗语和例化旗语是两件事。
创建旗语,并为其分配钥匙的方式如下:
semaphore sm;
sm=new();
创建一个具有固定钥匙数量的旗语:new(N=0)
从旗语那里获取一个或多个钥匙(阻塞型):get(N=1)
将一个或多个钥匙返回到旗语中:put(N=1)
尝试获取一个或多个钥匙而不会阻塞(非阻塞型):try_get(N=1)
new()的原型如下:
function new(int keyCount=0);
keyCount指定最初分配的旗语的钥匙数目。
当更多钥匙放入旗语时,钥匙数目可以超过初始时的keyCount数量,而不是删除。
keyCount的默认值为0.
new()函数返回旗语的句柄


put()方法用于将钥匙数量返回给旗语。

put()的原型如下:
function void put(int keyCount=1);
keyCount指定返回旗语的钥匙数量。默认值为1。
调用semaphore.put()函数时,指定数量的钥匙将返回到旗语。
如果其他进程已经在等待旗语,则该进程应在有足够数量钥匙的情况下返回。


get()方法用于从旗语中获取指定数量的钥匙
get()的原型如下:
task get(int keyCount=1);
keyCount指定从旗语获取所需的钥匙数,默认值为1.
如果指定数量的钥匙可用,则该方法返回并继续执行。
如果指定数量的钥匙不足,进程将阻塞,直到钥匙数目充足。
旗语的等待队列时FIFO。


tyr_get()方法用于从旗语中获取指定数量的钥匙,但不会被阻塞。
function int try_get(int keyCount=1);
keyCount指定从旗语处获取所需的钥匙数目,默认值为1.
如果指定数量的钥匙可用,则该方法返回正数并继续执行。
如果指定数量的钥匙不足,则该方法返回0。


线程通信


信箱mailbox:精小的SV原生FIFO。在线程之间做数据通信或者内部数据缓存时可以考虑此元素。
semaphore:共享资源的安全卫视。如果多线程间要对某一公关资源做访问,即可以使用这个要素。
event:最小信息量的触发,即单一的通知功能。可以用来做事件的触发,也可以多个事件组合起来用来做线程的同步。


发表于 2020-6-16 17:51:07 | 显示全部楼层
建议在csdn上以博客形式发
 楼主| 发表于 2020-6-16 18:52:42 | 显示全部楼层


年轻的韭菜 发表于 2020-6-16 17:51
建议在csdn上以博客形式发


谢谢支持,我会继续努力的,等以后有了稍微完整的知识框架,我会系统地整理出来发博客的
 楼主| 发表于 2020-6-16 21:19:25 | 显示全部楼层
实现激励发生器


实例:


class chnl_trans;
rand bit[31:0] data[];
rand int ch_id;
rand int pkt_id;
rand int data_nidles;

rand int pkt_nidles;
bit rsp;
local static int obj_id=0;
constraint cstr{
  soft data.size inside {[4:8]};
  foreach(data)

   data=='hC000_000+(this.ch_id<<24)+(this.pkt_id<<8)+i;
soft ch_id==0;
soft pkt_id==0;
data_nidles inside {[0:2]};
ptk_nidles inside {[1:10]};

};


   function new();
  this.obj_id++;
endfunction


function chnl_trans clone();//克隆,动作一,创建一个实例,动作二,将当前实例的数据成员拷贝到新实例对应的成员,最后返回句柄。
chnl_trans c=new();
c.data=this.data;
c.ch_id=this.ch_id;
c.pkt_id=this.pkt_id;
c.data_nidles=this.data_nidles;
c.pkt_nidles=this.pkt_nidles;


c.rsp=this.rsp;
return c;
endfunction
endclass:chnl_trans












chnl_initiator;//驱动激励的组件,从chnl_generator获取chnl_trans数据,并且将其中的数据驱动到chnl_interface


class chnl_initiator;
local string name;
local virtual chnl_intf intf;
mailbox #(chnl_trans) req_mb;
mailbox #(chnl_trans) rsp_mb;


function new(string name="chnl_initiator");
  this.name=name;
endfunction


function void set_interface(virtual chnl_intf intf);
  if(intf==null)
   $error("interface handle is NULL
       please check if target interface has been intantiated");
  else
   this.intf=intf;
endfunction


task run();
this.drive();
endtask


task drive();
chnl_trans req, rsp;
@(posedge intf.rstn);
forever begin
   this.req_mb.get(req);
   this.chnl_write(req);
   rsp=req.clone();
   rsp.rsp=1;
   this.rsp_mb.put(rsp);
end
endtask


task chnl_write(input chnl_trans t);
foreach(t.data) begin
   @(posedge intf.clk);
   intf.ch_valid<=1;
   intf.ch_data<=t.data;
   @(negedge intf.clk);
   wait(int.ch_ready=='b1);
   $display("%0t channel initiator [%s] sent data %x,
        $time, name, t.data");
   repeat(t.data_nidles) chnl_idle();
  end
  repeat(t.pkt_nidles) chnl_idle();
endtask


task chnl_idle();
  @(posedge intf.clk);
  intf.ch_valid<=0;
  intf.ch_data<=0;
endtask

endclass:chnl_initiator






class chnl_generator;
  rand int pkt_id=-1;

  rand int ch_id=-1;

  rand int data_nidles=-1;

  rand int pkt_nidles=-1;

  rand int data_size=-1;
  rand int ntrans=10;

  mailbox #(chnl_trans) req_mb;

  mailbox #(chnl_trans) rsp_mb;
  constraint cstr{
   soft ch_id==-1;
   soft pkt_id==-1;
   soft data_size==-1;
   soft data_nidles==-1;
   soft pkt_nidles==-1;
   soft trans==10;
}
function new();
this.req_mb=new();
this.rsp_mb=new();
endfunction



task run();
repeat(ntrans) send_trans();//调用多次创建数据十五并发送给chnl_initiator
run_stop_flags.put();//调用用来控制仿真结束的旗语,并返回一把钥匙
endtask


//generate transaction and put into local mailbox
task send_tans();
chnl_trans req, rsp;
req=new();
assert(req.randmoize with {local::ch_id>=0->ch_id==local::ch_id;
               local::pkt_id>=0->pkt_id==local::pkt_id;
               local::data_nidles>=0->data_nidles=local::data_nidles;               local::pkt_nidles>=0->pkt_nidles==local::pkt_nidless;
               local::data_size>0->data.size()==local::data_size;

               })

   eles $fatal("RNDFAIL channel packet randomization failure!");
  this.pkt_id++;
  this.req_mb.put(req);
  this.rsp_mb.get(rsp);
  assert(rsp.rsp)
   eles $error("[REPERR] %0t error response received!", $time);
  endtask
endclass:chnl_generator


您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

站长推荐 上一条 /3 下一条

小黑屋| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-4-24 00:34 , Processed in 0.056478 second(s), 7 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
快速回复 返回顶部 返回列表