概述
目录
0.前言
1.整体环境搭建
1.1 interface搭建
1.2 clk generator搭建
1.3 rst generator搭建
1.4 environment搭建
1.5 top搭建
1.5.1 fifo_top搭建
1.5.2 顶层top搭建
2.添加剩余组件
2.1 transaction组件
2.2 generator组件
2.3 driver组件
2.4 monitor组件
2.5 scoreboard组件
2.6 更新environment
2.7 更新top
3.仿真结果
4.结语
0.前言
整个SV学习完毕后,为了进一步巩固知识,初步了解并搭建出简单的验证环境,故为一个异步fifo模块儿搭建验证环境;该异步fifo的具体实现及详细介绍可见这篇文章:异步FIFO---Verilog实现
(这里只是通过这篇文章理解一下异步fifo的实现原理,这篇文章验的原RTL代码并非这篇文章中的)
首先奉上整个验证流程的架构框图如下,后面的验证环境将基于此图进行搭建:
对于验证一个IP,首先要指定验证策略并提取验证点,做出相应的test plan,该异步fifo功能单一简单,故为其指定如下:
1.整体环境搭建
该部分将整个验证架构最基本的部分搭建起来,包括interface、clk generator、rst generator、environment、top这5个文件;这将能给DUT生成时钟激励,及复位激励,该部分的正确搭建是后续添加其它组件的基础。
1.1 interface搭建
个人习惯,上来先搭建interface文件方便后续连接
interface fifoPorts #(parameter DSIZE=8);
logic wclk;//声明所有输入/出接口
logic rclk;
logic [DSIZE-1:0]wdata;
logic [DSIZE-1:0]rdata;
logic wfull;
logic rempty;
logic winc,rinc;
logic wrst_n,rrst_n;
clocking wcb@(posedge wclk);//同步驱动winc和wdata
output winc;
output wdata;
endclocking
clocking rcb@(posedge wclk);//同步驱动rinc
output rinc;
endclocking
modport TB(
output wrst_n,rrst_n,wdata,winc,rinc,wclk,rclk,
input rdata,wfull,rempty);//为TB指定信号方向
modport DUT(
input wrst_n,rrst_n,wdata,winc,rinc,wclk,rclk,
output rdata,wfull,rempty);//为DUT指定信号方向
endinterface
1.2 clk generator搭建
class clockGenerator;
logic wclk,rclk;
int period;//时钟半周期
virtual fifoPorts itf;//定义虚接口把软件中的信号给硬件
function new(virtual fifoPorts itf);//
this.itf=itf;
endfunction
/*>==================clkActivate=============<*/
task automatic clkActivate(input string clkName);//通过外部输入的wclk/rclk决定生成什么clk
if(clkName=="wclk")begin//wclk
$display("%0t:Calling task wclkActivate",$time);
this.itf.wclk=1'b0;
forever begin
#this.period this.itf.wclk++;
end
end
else begin//rclk
$display("%0t:Calling task rclkActivate",$time);
this.itf.rclk=1'b0;
forever begin
#this.period this.itf.rclk++;//通过自加形成0 1 0 1时钟反转
end
end
endtask
/*>==================endclkActivate=============<*/
/*>==================clkGenerator=============<*/
task automatic clkGenerator(input string clkName,input int clkPeriod);//通过外部调用输入的wclk/rclk和周期决定clk的周期和类型
$display("%0t:Calling task clkGenerator for %s",$time,clkName);
this.period=clkPeriod/2;
fork
clkActivate(clkName);
join_none
endtask
/*>==================endclkGenerator=============<*/
endclass
1.3 rst generator搭建
class resetGenerator;
logic wrst,rrst;
int period;//复位多少个时钟周期
virtual fifoPorts itf;//定义虚接口把软件中的信号给硬件
function new(virtual fifoPorts itf);//
this.itf=itf;
endfunction
/*>==================rstActivate=============<*/
task automatic rstActivate(input string rstName);//根据外部调用名称决定是wrst/rrst
if(rstName=="rrst")begin
$display("%0t:Calling task rrstActivate",$time);
this.itf.rrst_n=1'b1;
repeat(this.period) @(posedge this.itf.rclk);
this.itf.rrst_n=1'b0;
repeat(this.period) @(posedge this.itf.rclk);
this.itf.rrst_n=1'b1;
end
else begin
$display("%0t:Calling task wrstActivate",$time);
this.itf.wrst_n=1'b1;
repeat(this.period) @(posedge this.itf.wclk);
this.itf.wrst_n=1'b0;
repeat(this.period) @(posedge this.itf.wclk);
this.itf.wrst_n=1'b1;
endtask
/*>==================endrstActivate=============<*/
/*>==================rstGenerator=============<*/
task automatic rstGenerator(input string rstName,input int rstPeriod);//外部调用决定复位哪个信号,复位多长时间
$display("%0t:Calling task rstGenerator for %s",$time,clkName);
this.period=rstPeriod;
rstActivate(rstName);
endtask
/*>==================endrstGenerator=============<*/
endclass
1.4 environment搭建
class environment #(parameter DSIZE=8);
virtual fifoPorts itf;
clockGenerator w_clkGen;//声明句柄
clockGenerator r_clkGen;
resetGenerator wrstGen;
resetGenerator rrstGen;
function new(virtual fifoPorts itf);
this.itf=itf;
w_clkGen=new(itf);//实例化
r_clkGen=new(itf);
wrstGen=new(itf);
rrstGen=new(itf);
endfunction
extern task wcgu(string msg="wclk",int clk_p=10);
extern task rcgu(string msg="rclk",int clk_p=10);
extern task wrgu(string msg="wrst",int clk_p=10);
extern task rrgu(string msg="rrst",int clk_p=10);
endclass
task environment::wcgu(string msg="wclk",int clk_p=10);
w_clkGen.clkGenerator(msg,clk_p);
endtask
task environment::rcgu(string msg="rclk",int clk_p=10);
r_clkGen.clkGenerator("rclk",10);
endtask
task environment::wrgu(string msg="wrst",int clk_p=10);
wrstGen.rstGenerator("wrst",10);
endtask
task environment::rrgu(string msg="rrst",int clk_p=10);
rrstGen.rstGenerator("rrst",10);
endtask
1.5 top搭建
1.5.1 fifo_top搭建
module fifo_top #(parameter DSIZE=8,parameter ADDRSIZE=4) (fifoPorts.DUT itf);
reg wclk_tmp;
always(*)begin
#1 wclk_tmp=itf.wclk;//通过延迟来使wclk和rclk不同相位
end
fifo1 #(.DSIZE(DSIZE),.ASIZE(ADDRSIZE))//为异步fifo设置数据位宽和深度 i0(
.rdata(itf.rdata),
.wfull(itf.wfull),
.rempty(itf.rempty),
.wdata(itf.wdata),
.winc(itf.winc),
.wclk(wclk_tmp),
.wrst_n(itf.wrst_n),
.rinc(itf.rinc),
.rclk(itf.rclk),
.rrst_n(itf.rrst_n));//DUT和interface连接
endmodule
1.5.2 顶层top搭建
我们可以将用到的参数和需要编译的所有组件放到包中,方便top调用,使代码更简洁易懂。
package fifo_params;
parameter DSIZE=8;
parameter ADDRSIZE=4;
parameter DEPTH=1<<ADDRSIZE;
typedef enum{WR,RD} opt_e;
`include"用到的相关组件"
······
······
endpackage
module top();
import fifo_params::*;
fifoPorts #(DSIZE) itf();//例化interface
fifo_top i0(itf.DUT);//DUT和interface连接
environment #(DSIZE) env;
initial begin
env=new(itf);例化验证环境,方便后续调用里面的任务
env.wcgu("wclk",10);
env.rcgu("rclk",10);
fork//读写同时复位
env.wrgu("wrst",10);
env.rrgu("rrst",10);
join
repeat(10) @(posedge itf.rclk);
$finish;
end
endmodule
至此整个验证环境的大框架就搭建好了,如下图所示,剩下的就是将剩余的每个组件写好,然后在environment中连接起来,并在top中调用即可。
2.添加剩余组件
2.1 transaction组件
如果generator是枪的话,transaction就是子弹
class packet;
rand bit[DSIZE-1:0] data;
rand opt_e opt;
endclass
2.2 generator组件
用来将随机化后的transaction发送出去,相当于把激励发送出去,这里我们发送给generator和driver之间用于通信的信箱。
class generator #(parameter DSIZE=8);
packet gpkt;
mailbox GSMbx;
function new(mailbox GSMbx);
this.GSMbx=GSMbx;
endfunction
task send(int sendNumber);
$display("%0t:Send task,Starting Send...sendNumber=%0d",$time,sendNumber);
repeat(sendNumber)begin
gpkt=new;
assert(gpkt.randomize);
$display(gpkt);
GSMbx.put(gpkt);
end
$display("%0t:Send task,End Send",$time);
endtask
endclass
2.3 driver组件
从generator和driver之间的mailbox中取出数据,通过虚接口发送给DUT。
class driver #(parameter DSIZE=8);
virtual fifoPorts itf;
mailbox GSMbx;
mailbox DWMbx;
packet dpkt;
function new(virtual fifoPorts itf,mailbox GSMbx,mailbox DWMbx);
this.itf=itf;
this.GSMbx=GSMbx;
this.DWMbx=DWMbx;
endfunction
task Write(input int putInMbx,input int writeNumber);
int i=0;
$display("%0t:Driver.Write task,Starting write...writeNumber=%0d",$time,writeNumber);
do begin
this.itf.wcb.winc<=1'b1;
GSMbx.get(dpkt);
this.itf.wcb.wdata<=dpkt.data;
pkt.opt=WR;
if(putInMbx)begin
this.DWMbx.put(dpkt);
end
@(posedge itf.wclk);
i++;
end while(i<writeNumber);
$display("%0t:Driver.Write task,End write",$time);
endtask
endclass
2.4 monitor组件
从DUT中接收数据,给monitor和scoreboard的mailbox中。
class monitor #(parameter DSIZE=8);
virtual fifoPorts itf;
mailbox MRMbx;
packet mpkt;
function new(virtual fifoPorts itf,mailbox MRMbx);
this.itf=itf;
this.MRMbx=MRMbx;
endfunction
task Read(input int putInMbx,input int readNumber);
int i=0;
$display("%0t:Monitor.Read task,Start Reading...readNumber=%0d",$time,readNumber);
do begin
mpkt=new();
this.itf.rinc=1'b1;
@(posedge itf.rclk);
if(putInMbx)begin
this.mpkt.data=itf.rdata;
this.mpkt.opt=RD;
this.MRMbx.put(mpkt);
end
i++;
end while(i<readNumber);
this.itf.rinc=1'b0;
$display("%0t:Monitor.Read task,End Reading...readNumber=%0d",$time);
endtask
endclass
2.5 scoreboard组件
比较Driver和monitor的数据是否一致,由于fifo只是当一个数据的中转站,所以数据一致。如果是复杂的DUT,还要创建ref model,将接收到的数据和ref model比较。
class scoreboard #(parameter DSIZE=8);
mailbox DWMbx,MRMbx;
packet wpkt,rpkt;
function new(mailbox DWMbx,MRMbx);
this.DWMbx=DWMbx;
this.MRMbx=MRMbx;
endfunction
task compareData;
int loopw,loopr;
printMbxContent(DWMbx,"Golden Data is:");
printMbxContent(MRMbx,"Actual Data is:");
$display("%0t:Scoreboard.Compare task,Starting Compare...",$time);
loopw=DWMbx.num();
loopr=MRMbx.num();
if(loopw!=loopr)begin
$display("%0t:FAILED for size-mismatch",$time);
end
else begin
for(int i=0;i<loopw;i++)begin
DWMbx.get(mpkt);
MRMbx.get(rpkt);
if(mpkt.data==rpkt.data)
$display("%0t:PASS:Read and Write are same:R%0h=W%0h",$time,this.rpkt.data,this.wpkt.data);
else
$display("%0t:FAILED:Read and Write are different:R%0h=W%0h",$time,this.rpkt.data,this.wpkt.data);
end
end
endtask
task printMbxContent(input mailbox mbx,string message);
int mbxElements;
packet pkt;
packet q[$];
mbxElements=mbx.num();
for(int i=0;i<mbxElements;i++)begin
mbx.get(pkt);
q.push_back(pkt);
mbx.put(pkt);
end
foreach(q[i])begin
$write("%0h",q[i].data);
end
$display(" ");
endtask;
endclass
2.6 更新environment
以上只是定义好了各个组件,在环境中我们要将他们相连到一起。
class environment #(parameter DSIZE=8);
virtual fifoPorts itf;
clockGenerator w_clkGen;//声明句柄
clockGenerator r_clkGen;
resetGenerator wrstGen;
resetGenerator rrstGen;
generator #(DSIZE) gen;
driver #(DSIZE) drv;
monitor #(DSIZE) mon;
scoreboard #(DSIZE) scb;
mailbox wbox,rbox,sbox;
function new(virtual fifoPorts itf);
this.itf=itf;
w_clkGen=new(itf);//实例化
r_clkGen=new(itf);
wrstGen=new(itf);
rrstGen=new(itf);
wbox=new();
rbox=new();
sbox=new();
gen=new(sbox);
drv=new(itf,sbox,wbox);
mon=new(itf,rbox);
scb=new(wbox,rbox);
endfunction
extern task wcgu(string msg="wclk",int clk_p=10);
extern task rcgu(string msg="rclk",int clk_p=10);
extern task wrgu(string msg="wrst",int clk_p=10);
extern task rrgu(string msg="rrst",int clk_p=10);
extern task gendata(int sendNumber=16);
extern task fifoWrite(input int putInMbx=1,int writeNumber=16);
extern task fifoRead(input int putInMbx=1,int readNumber=16);
extern task compareResult;
endclass
task environment::wcgu(string msg="wclk",int clk_p=10);
w_clkGen.clkGenerator(msg,clk_p);
endtask
task environment::rcgu(string msg="rclk",int clk_p=10);
r_clkGen.clkGenerator("rclk",10);
endtask
task environment::wrgu(string msg="wrst",int clk_p=10);
wrstGen.rstGenerator("wrst",10);
endtask
task environment::rrgu(string msg="rrst",int clk_p=10);
rrstGen.rstGenerator("rrst",10);
endtask
task environment::gendata(int sendNumber=16);
gen.send(sendNumber);
endtask
task environment::fifoWrite(input int putInMbx=1,int writeNumber=16);
drv.write(putInMbx,writeNumber);
endtask
task environment::fifoRead(input int putInMbx=1,int readNumber=16);
mon.read(putInMbx,readNumber);
endtask
task environment::compareResult;
scb.compareResult;
endtask
2.7 更新top
module top();
import fifo_params::*;
fifoPorts #(DSIZE) itf();//例化interface
fifo_top i0(itf.DUT);//DUT和interface连接
environment #(DSIZE) env;
initial begin
env=new(itf);例化验证环境,方便后续调用里面的任务
env.wcgu("wclk",10);
env.rcgu("rclk",10);
fork//读写同时复位
env.wrgu("wrst",10);
env.rrgu("rrst",10);
join
repeat(10) @(posedge itf.wclk);
env.gendata(25);
env.fifoWrite(25);
repeat(10) @(posedge itf.wclk);
env.fifoRead(25);
repeat(10) @(posedge itf.rclk);
env.compareResult;
$finish;
end
endmodule
3.仿真结果
先简单验证一个复位功能是否正常,读写指针是否在复位后回0了:
然后验证数据是否能正常写入,正常读出;在异常状态,写满和读空时相应信号是否拉起:
验证wclk和rclk相同时,数据同时写入和读出是否正常,这里可以看到可能是由于DUT本身特性,rempty晚拉低了几个时钟周期,导致数据读出不准确,且丢了后面几个数据:
然后验证不同时钟下同步读写是否正常,先是写数据快,读数据慢,同时为了避免上述rempty延迟的问题,采取了先写延迟读的方式:
下面是读快写慢:
4.结语
至此这个非常简单的验证小项目就结束了,通过本项目实战操练了SV语法的具体运用,同时自己搭建了简单的验证环境;但也存在一些问题,如对DUT理解不是很到位,导致验出来的波形不知道是DUT的bug还是本该如此,后续会对异步fifo加强理解。
转行之路长漫漫,自己目前也只是初入茅庐,所以文章中不对的地方还敬请批评指正!需要学习的东西还是太多,但只要坚持学习,勤学多练,相信自己一定能够成功!
最后
以上就是纯情酒窝为你收集整理的SV小项目—异步fifo的简单验证环境搭建(全)的全部内容,希望文章能够帮你解决SV小项目—异步fifo的简单验证环境搭建(全)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复