异步FIFO-2

文章详细介绍了使用UVM进行FIFO设计验证的过程,包括验证思路、组件搭建、环境配置、transaction定义、driver和monitor的实现,以及scoreboard和referencemodel的使用。同时,文章提到了在实际操作中遇到的问题和解决方法,如配置数据库的使用和测试用例的编写。
摘要由CSDN通过智能技术生成

1.参考

1.验证思路

1.V Verification

  1. 例化1个深度为8,位宽为8的异步FIFO;
  2. 读时钟是写时钟的2倍,即读快写慢
  3. 先对FIFO进行写操作,直到其写满,写入的数据为随机数据
  4. 然后对FIFO进行读操作,直到其读空 然后对FIFO写入4个随机数据后,同时对其进行读写操作

2.UVM Verification:

  1. top_tb.sv 是整个验证平台的顶层模块(测试用具)。在它里面连接了待测设计和验证平台。
  2. my_if.sv 定义了一个接口,用来连接待测设计和验证组件。
  3. my_transaction.sv 定义了在验证平台中传递信息的事务。它里面包含一些变量。
  4. my_driver.sv 发送激励。
  5. in_moniotor.sv 在信号输入端口监测。
  6. out_monitor.sv 在信号输出端口监测。
  7. my_sequencer.sv 是 UVM 搭建的验证平台中不可或缺的部件,负责中转由 sequence 传来的transaction。
  8. i_agt.sv 是一个容器类,里面实例化了 my_driver、my_sequencer 和 in_monitor。
  9. o_agt.sv 也是一个容器类,里面实例化了 out_monitor。
  10. my_model 是参考模型,由于 fifo 实现的功能较为简单,此处的参考模型仅仅是把输入的数据复制一遍然后输出。
  11. my_scoreboard.sv 是计分板,用于比较待测硬件的输出和参考模型的输出是否一致。
  12. my_env.sv 是一个容器,它里面实例化了 i_agt、o_agt、my_model、my_scoreboard 等部件。
  13. base_test.sv 它里面实例化 my_env,同时也会规定信息打印的规则。
  14. my_case0 和 my_case1 是两个测试用例。my_case0 可以运行成功,my_case1 运行不成功,运行不成功的原因是 uvm-1.2 版本不支持 default_sequence 的使用或者是 uvm-1.2 版本支持,但是我还不会用。
  15. fifo_rst_mon.sv 和 fifo_chk_rst.sv,前者用来检测复位信号,后者用来检测复位后寄存器的复位值是否正确。由于这个是我第一次用 UVM 搭建的验证平台,所以许多地方还不成熟,比如 my_model 仅仅是为了验证平台的完整而添加的,实际上并不需要。

3.http://t.csdn.cn/hfKe4

2.实践

1.搭建环境组件

component大致结构

class   A    extends    uvm_     ;			//类定义
`uvm_component_utils( A )						//宏注册

function new (string name="  A  ",uvm_component parent =null);		//构建函数
	super.new(name,parent);
endfunction

//例化组件abc a1b1c1

//component的phase机制
extern virtual function void build_phase( )
extern virtual function void connect_phase( )

endclass

//————————————————————————————————————————
function void A::build_phase( )
	super.build_phase(  );
	a1=a::type_id::create("a1",this);			//创建组件abc对象
endfunction

//————————————————————————————————————————
//在agent/env组件中用到connect_phase
function void A::connect_phase( )
	super.connect_phase(  );
	.connect();
endfunction
	

2.根据白皮书思路更新组件

1.只有driver的验证平台(task main)
2.对driver实例化,构建顶层验证平台top_tb
3.给driver加入factory、objection机制(后期objection迁移至sqr)
4.加入virtual interface
定义interface ,目的连接dut和driver,dut-interface-driver
在顶层模块top_tb 例化interface(fifo_if1)和dut,用(if.)实现连接
driver作为一个类不能例化interface,所以使用vif

virtual interface fifo_if fifo_if2;

利用config_db机制连接top_tb中的if和driver中的vif
在top_tb里set

initial begin
  uvm_config_db#(virtual fifo_if)::set(null,"uvm_test_top.env.i_agent1.dri","vif",fifo_if1);
end
//向"uvm_test_top.env.i_agentt1.dri"的"vif"变量传递一个virtual fifo_if类型的数据,数据为fifo_if1
//树根固定是uvm_test_top

在driver的build_phase里get,未成功完成config报错fatal

if(!uvm_config_db#(virtual fifo_if)::get(this,"","vif",fifo_if2))
       `uvm_fatal("my_driver","virtual interface must be set for vif!!!")
//路径为this

5.加入transaction,object类,自动化域机制
object类大致结构

class   B    extends    uvm_     ;			//类定义
`uvm_object_utils_begin( B )						//宏注册
`uvm_field_int(x,UVM_ALL_ON)						//自动化域机制
...
`uvm_object_utils_end

function new (string name="  B " );		//构建函数
	super.new(name);
endfunction

一个transaction就是一个包
物理协议中的数据交换以帧/包为单位,一个帧/包里要定义好各项参数
以太网协议,每个包的大小至少是64byte,包中包含了源地址,目的地址,包的类型,整个包的crc校验数据等

class my_transaction extends uvm_sequence_item;

    rand bit[47 : 0] dmac;					//48bit的以太网目的地址
    rand bit[47 : 0] smac;					//以太网源地址
    rand bit[15 : 0] ether_type;			//以太网类型
    rand byte        pload[];				//携带的数据大小
    rand bit[31 : 0] crc;					//前面所有数据的校验值

    constraint pload_cons {					//携带的数据大小被限制在1-1500byte
    pload.size >= 1;
    pload.size <= 1500;
}

    function bit[31 : 0] calc_crc();		//crc的计算方法,网上可查
      return 32'h0;
    endfunction

    function void post_randomize();
      crc = calc_crc;
    endfunction

   `uvm_object_utils_begin(my_transaction)
   `uvm_field_int(dmac,UVM_ALL_ON)
   `uvm_field_int(smac,UVM_ALL_ON)
   `uvm_field_int(ether_type,UVM_ALL_ON)
   `uvm_field_array_int(pload,UVM_ALL_ON)
   `uvm_field_int(crc,UVM_ALL_ON)
   `uvm_object_utils_end

    function new(string name = "my_transaction");
      super.new(name);
    endfunction
    
   
endclass

6.在driver中实现对transaction的驱动
task main_phase里调用task drive_one_pkt
task drive_one_pkt(fifo_transaction tr)里例化transaction
7.加入env
8.加入mon
输入monitor检测wrdata,输出monitor检测rddata。考虑到检测端口不一致,写了两个monitor
与driver行为相对,driver负责把transaction的数据转变成dut数据,monitor用于收集dut数据并转换成transaction交到后续组件处理
monitor也需要vif
需要时刻收集数据,永不停歇,所以main_phase使用while(1)
9.封装成agent
不同agent代表不同的协议

//枚举类型变量 is_active 有两个值
typedef enum bit{UVM_PASSIVE=0,UVM_ACTIVE=1} uvm_active_passive_enum;
uvm_active_passive_enum is_active=UVM_ACTIVE;					//uvm_agent中默认值,需要例化dri、sqr
uvm_active_passive_enum is_active=UVM_PASSIVE;					//只需要例化mon

在上一层env中的build_phase中指定agent.is_active值
10.加入ref model
//fifo仅仅是复制一遍transaction
在env中例化ref model
11.transaction从agent传到ref model,再传给scoreboard
组件间的transaction传输采用TLM,TLM包括了ap的write和port的get/put
异步fifo中通信的组件包括了 in_mon→ref model→scoreboard←out_mon
在这四个组件中例化TLM接口
在上层env中例化fifo后连接四个组件的端口
实现in_mon→【write】(fifo)【get】→ref model→【write】(fifo)【get】→scoreboard←【get】(fifo)【write】←out_mon
(1)in_mon中
声明ap接口和例化,调用ap的广播功能write

uvm_analysis_port #(fifo_transaction) ap;						//声明ap接口


function void  in_monitor::build_phase(uvm_phase phase);		//在build中例化ap接口
ap = new("ap",this);

task in_monitor::main_phase(uvm_phase phase);					//在main中收集完transaction调用写入ap接口
    collect_one_pkt(tr);
    ap.write(tr);

在i_agent中例化ap端口,与in_mon的ap相连

uvm_analysis_port #(fifo_transaction) ap;						//声明ap接口

//connect_phase中
ap = mon.ap;

(2)ref model中
面向in_mon 使用uvm_blocking_get_port,声明例化,调用get功能
面向scoreboard 声明ap接口和例化,调用ap的广播功能write

uvm_blocking_get_port#(fifo_transaction) port;
uvm_analysis_port#(fifo_transaction) ap;


function void fifo_refmodel::build_phase(uvm_phase phase);
    port = new("port",this);
    ap = new("ap",this);


task fifo_refmodel::main_phase(uvm_phase phase);
    port.get(tr);
    ap.write(new_tr);

(3)env中
定义fifo将两个端口相连,声明例化相连

//声明fifo
uvm_tlm_analysis_fifo#(fifo_transaction) iagt_refmodl_fifo;
uvm_tlm_analysis_fifo#(fifo_transaction) refmodl_scb_fifo;
uvm_tlm_analysis_fifo#(fifo_transaction) scb_oagt_fifo;

//在build中例化
function void fifo_env::build_phase(uvm_phase phase);
  iagt_refmodl_fifo=new("iagt_refmodl_fifo",this);
  refmodl_scb_fifo=new("refmodl_scb_fifo",this);
  scb_oagt_fifo=new("scb_oagt_fifo",this);

//在connect中连接
function void fifo_env::connect_phase( );
  super.connect_phase();
  i_agent1.ap.connect(iagt_refmodl_fifo.analysis_export);
  refmodl.port.connect(iagt_refmodl_fifo.blocking_get_export);
  refmodl.ap.connect(refmodl_scb_fifo.analysis_export);
  scb.exp_port.connect(refmodl_scb_fifo.blocking_get_export);
  scb.act_port.connect(scb_oagt_fifo.blocking_get_export);
  o_agent1.ap.connect(scb_oagt_fifo.analysis_export);
endfunction

12.加入scoreboard
13.加入sequencer
14.sequence机制
sequence激励 对应于测试用例case

//case0.sv中包含

class case0_sequence extends uvm_sequence #(my_transaction);

class my_case0 extends base_test;

如何启动测试用例?启动测试用例是通过设置default_sequence来启动sequence
整个验证环节有多个测试用例/case,在互不影响的条件下启动sequence,最理想的方法是在命令行中指定参数

//top_tb

initial begin   
    run_test();
end
+UVM_TESTNAME=case0

因此在class my_case0 extends base_test的build_phase中设置default_sequence

//default_sequence

uvm_config_db#(uvm_object_wrapper)::set(this,
                                          "env.i_agent1.sqr.main_phase",
                                          "default_sequence",
                                          case0_sequence::type_id::get());
//第二个参数是相对第一个参数的相对路径
//第二个参数main_phase使sequencer知道在哪个phase启动default_sequence这个sequence
//第三个第四个参数以及uvm_config_db#(uvm_object_wrapper)是uvm的规定

15.加入basetest
16.基于basetest扩展case0

//case0.sv中包含
//
class case0_sequence extends uvm_sequence #(my_transaction);
例化transaction
在task body()里do transaction 创建transaction
//
class my_case0 extends base_test;
在build_phase里设置default_sequence
在run_phase里
例化case0_sequence ,挂起objection,创建case0_sequence ,调用case0_sequence.start启动case0_sequence的task body同时指定sqr,落下objection

task my_case0::run_phase(uvm_phase phase);
  case0_sequence seq;								//例化case0_sequence
  phase.raise_objection(this);						//挂起objection
  #4;  
  seq = case0_sequence::type_id::create("seq");		//创建case0_sequence 
  `uvm_info("case0_sequence","case0_sequence begin",UVM_LOW)
  seq.start(env.i_agt1.sqr);						//调用case0_sequence.start启动case0_sequence的task body,指定了sqr
  #3000;
  phase.drop_objection(this);						//落下objection
endtask

3.代码详解

top_tb

`include "uvm_macros.svh"		//把uvm_macros.svh文件添加进来,是UVM中包含众多宏定义的文件,只需包含一次
import uvm_pkg::*;				//将整个uvm_pkg导入验证平台,只有导入这个库,编译器才会认识类名
    `ifndef MY_TRANSACTION__SV
    `define MY_TRANSACTION__SV
     
    //my_codes
     
    `endif
    // 首尾加入这三句话后,在这中间的代码如果被编译过一次,那么哪怕在多个文件都include了它,也只会编译一次,就不会出现重复定义的情况了。

if

interface my_if(input wclk, input rclk, input wrst_n, input rrst_n);
		logic winc,rinc;
        logic [7 : 0] wdata;
        wire  wfull,rempty;
        logic [7 : 0] rdata;

  clocking ckw @(posedge wclk);			//时钟块ckw声明了块中的信号wfull、winc、wdata在时钟的上升沿有效,信号的方向相对于modport DRV
    input  wfull;
    inout  winc;
    inout  wdata;
  endclocking

  clocking ckim @(posedge wclk);
    input wfull;
    inout  winc;
    input  wdata;
  endclocking

  clocking ckom @(posedge rclk);
    input  rempty;
    inout  rinc;
    input  rdata;
  endclocking
  
  modport DUT(
  input winc,
  input rinc,
  input wdata,
  output rdata,
  output wfull,
  output rempty
  );

 modport DRV(
  clocking ckw,
  input rinc,
  input rdata,
  input rempty
);

 modport OMON(
 clocking ckom,
 input wfull,
 input winc,
 input wdata
);
  
  
  


endinterface

driver

task my_driver::main_phase(uvm_phase phase);
    `uvm_info("my_driver","begin!",UVM_LOW)

   while(1) begin     
     seq_item_port.get_next_item(req);			//从seq_item_port端口向sqr去get下一个transaction(req)
     drive_one_pkt(req);						//把transaction打包
     seq_item_port.item_done();
   end    

endtask

//在drive_one_pkt中,将从sqr获取的tr所有数据压入队列data_q,data_q相当于一个byte流,之后再将data_q驱动到vif-dut
task my_driver::drive_one_pkt(my_transaction tr);
    byte unsigned data_q[];
    int data_size,j;
    
    data_size = tr.pack_bytes(data_q)/8;						//一个byte=8bit,用自动化域机制的pack_bytes函数把tr变成byte流放入data_q
    
    `uvm_info("my_driver","begin to drive one pkt",UVM_LOW)
    for(int i = 0; i < data_size; i++) begin
      @fifo_if2.ckw;
     if((!fifo_if2.ckw.wfull) && (fifo_if2.ckw.winc == 1)) begin 			//未写满且写允许的条件下,写数据=data_q
     fifo_if2.ckw.wdata <= data_q[i];
     `uvm_info("my_driver",$sformatf("%0d number is sent,number is %0h",j++,fifo_if2.ckw.wdata),UVM_LOW) 
     end  else if((!fifo_if2.ckw.wfull) && (fifo_if2.ckw.winc == 0)) begin		//未写满但不允许写
     fifo_if2.ckw.winc <= 1;													//将写信号允许
     i--;																		//将i复位一次
     end else begin																//写满的条件下
     fifo_if2.ckw.winc <= 0;													//写信号不允许
     i--;																		//将i复位一次
     end
     end  
endtask

i_agent

function void i_agent::connect_phase(uvm_phase phase);
  super.connect_phase(phase);
  if(is_active==UVM_ACTIVE)begin
    dri.seq_item_port.connect(sqr.seq_item_export);			//是sqr的export接口
  end
  ap=mon.ap;
endfunction

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值