一、config文件夹

config文件:

  1. 对不同组件的变量进行声明,任何你想要全局共享的变量都可以放进来

  2. 声明virtual_if、reg_model_block和vip_config

  3. 工厂注册和域的自动化(方便后期通过log查看)

  4. 如果有vip_config要在new函数里创建

    function new(string name ="your_config");
        super.new(name);
         vip_cfg = vip_config::type_id::create("vip_cfg");
      endfunction
    

二、tb文件夹

1、tb文件

  1. 定义module

  2. 包含uvm包和头文件

     import uvm_pkg::*;
      `include "uvm_macros.svh"
    
  3. 包含你自己的包

     import your_pkg::*;
    
  4. 定义所需的bit clk,rstn,使用initial语句块和非阻塞赋值语句

  5. 例化interface,也许有多个,全都需要例化

    • 将module中定义的clk,rstn赋值给if_inst的clk,rstn
    • 如果有需要的话,也可以在此处将一些dut信号抓取给interface,但是尽量避免这种行为
  6. 例化DUT,并进行连接

    • 连接dut全部通过interface进行,所以除了module中定义的clk,rstn外其他信号都在if文件中定义
  7. initial块开始测试,并且将全部例化后的interface实例通过config传递给需要的组件

    • interface只能在module这儿例化,别的地方都是virtual,所以需要传递一下

      initial begin 
        uvm_config_db#(virtual apb_if)::set(uvm_root::get(), "uvm_test_top.env.vip_mst", "vif", apb_if_inst);
        uvm_config_db#(virtual rkv_timer_if)::set(uvm_root::get(), "uvm_test_top", "vif", timer_if_inst);
        run_test("");
      end
      
    • tun_test函数的含义:Phases all components through all registered phases. If the optional test_name argument is provided, or if a command-line plusarg, +UVM_TESTNAME=TEST_NAME, is found, then the specified component is created just prior to phasing. The test may contain new verification components or the entire testbench, in which case the test and testbench can be chosen from the command line without forcing recompilation. If the global (package) variable, finish_on_completion, is set, then $finish is called after phasing completes.

2、if文件

interface中的信号是连接软件和硬件的

  1. 声明所有dut所需的接口信号以及你在验证中可能需要的一些变量信号,大多数都是logic类型

  2. 为信号做连接,多使用assign语句和always语句块

     assign vip_clk_gated = vip_clk;
    
     always @(vip_rstn,clk) begin
        if(!vip_rstn)
          out <= 0;
        else if(a)
          out <= clk;
     end
    

三、env文件夹

1、pkg文件

  1. pkg也要包含UVM包和头文件,还需要你所引用的vip的包等。
  2. pkg要包含所有的文件,建议编辑一个添加一个

2、env文件

  1. 对所有的组件进行声明

    • 一般是 类型 名称 的声明方式
    • 一般来说有config,virtual_sequencer,reg_model_block,reg_adapter,scoreboard,coverage_model和predictor,以及可能用到的vip_master_agent
    • predictor很特别,因为直接使用了uvm的类,他需要这样声明:
      uvm_reg_predictor #(your_transfer) predictor;
      
  2. 对所有的组件进行创建:build_phase

    1. config机制的传递

      • 注意config的get以及按照组件层次的分发

      • reg_model_block和config是从base_test里set到env的

      • reg_model_block要分发

         uvm_config_db#(reg_model_block)::set(this,"*","rgm", rgm);
        
      • config要分发给virt_sqr,vip_master,scoreboard,coverage_model

        set给virt_sqr的config,要在base_virtual_sequence中get

    2. 使用工厂机制创建

      • 一定要先config再创建,确保创建前以及得到正确的配置

      • 对reg_model_block:

        if(!uvm_config_db#(reg_model_block)::get(this,"","rgm", rgm)) begin
            rgm = reg_model_block::type_id::create("rgm", this);
           rgm.build();//rgm不是组件,没有build_phase,必须手动启动
        end
        
  3. 连接:connect_phase

    • 一般的连接是virtual_sqr和sqr

    • 寄存器模型的map的链接

    • moniter和predictor

    • moniter和scoreboard、coveragemodel

    • 用到一个添加一个就好了

      function void connect_phase(uvm_phase phase);
          super.connect_phase(phase);
          virt_sqr.vip_mst_sqr = vip_mst.sequencer;
          rgm.map.set_sequencer(vip_mst.sequencer,adapter);
          vip_mst.monitor.item_collected_port.connect(predictor.bus_in);
          predictor.map=rgm.map;
          predictor.adapter = adapter;
          vip_mst.monitor.item_collected_port.connect(scb.vip_trans_observed_imp);
          vip_mst.monitor.item_collected_port.connect(cgm.vip_trans_observed_imp);
      endfunction
      
  4. 报告:report_phase

    • 报告总比较次数和错误次数

3、reg_adapter文件

模式比较固定,只需要改变your_transfer类型

class reg_adapter extends uvm_reg_adapter;
  
  `uvm_object_utils(reg_adapter)
  
  function new(string name = "reg_adapter");
    super.new(name);
    provides_respones= 1;
  endfunction

  function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
    your_transfer t = your_transfer::type_id::create("t");
    t.trans_kind = (rw.kind == UVM_WRITE)? WRITE: READ;
    t.addr = rw.addr;
    t.data = rw.data;
    t.idle_cycles = 1;
    return t;
  endfunction

  function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
    your_transfer t;
    if(!$cast(t,bus_item))begin
        `uvm_fatal("provided bus_item is not of the corrent type")
      return;
    end
    rw.kind = (t.trans_kind == WRITE)? UVM_WRITE: UVM_READ;
    rw.addr = t.addr;
    rw.data = t.data;
    rw.status = (t.trans_status = OK)? UVM_IS_OK :UVM_NOT_OK;
  endfunction
endclass

4、subscriber文件

scoreboard和coverage_model的父类,监听monitor的数据

  1. 声明一个imp,监听monitor发送的信息

       uvm_analysis_imp#(vip_transfer,subscriber) vip_trans_observed_imp;
    
  2. 声明一个uvm_reg_bus_op类型的op,为后面的write函数做准备

    • 用于寄存器和内存访问的通用总线事务的结构体
    • kind (UVM_WRITE / UVM_READ), addr, data, n_bits, byte_en, and status(UVM_IS_OK / UVM_NOT_OK)
  3. 声明所需要的event及uvm_event_pool

    • event用来捕捉对寄存器的访问并触发相应的event

    • scoreboard接受消化这些event

       protected uvm_event_pool _ep;
      
  4. 声明cfg,rgm, vif

  5. build_phase

    • 创建imp

    • 拿到从env中config::set的cfg

    • 创建_ep和event

       _ep = new("_ep");
          regacc_e  = _ep.get("regacc_e");
      	enable_e  = _ep.get("enable_e");
          ntren_e  = _ep.get("intren_e");
          reload_e  = _ep.get("reload_e");
      
  6. run_phase

    task run_phase(uvm_phase phase);
        super.run_phase(phase);
        fork
          do_events_trigger();
          do_listen_events();
        join_none
    endtask
    
  7. 其他所需函数

    virtual function void write(vip_transfer tr);//为monitor的item_collected_port而准备
        uvm_reg r;
        r = rgm.map.get_reg_by_offset(tr.addr)//获取对应的reg
        if(r!=null) begin
          regacc_e.trigger(r);
          rop.addr = tr.addr;
          rop.kind = tr.trans_kind == apb_pkg::WRITE ? UVM_WRITE : UVM_READ;
          rop.data = tr.data;
          rop.status = tr.trans_status == apb_pkg::OK ? UVM_IS_OK : UVM_NOT_OK;
          rop.n_bits = 32;
          rop.byte_en = 4'b1111;
        end
      endfunction
    
    virtual task do_events_trigger();
        uvm_object tmp;
        uvm_reg r;
        forever begin
            regacc_e.wait_trigger_data(tmp);//等待trigger并获取trigger(T)
          void'($cast(r, tmp));
          #1ps;// 确保rgm中的mirror值已经被predictor根据monitor transaction更新,避免竞争
          case(r.get_name())
            "CTRL": begin
                if(rgm.CTRL.ENABLE.get() == 1'b1) 	 enable_e.trigger();//读取rgm中寄存器的值来判断事件的触发
                if(rgm.CTRL.INTREN.get() == 1'b1) 	       intren_e.trigger();
            end
            "VALUE": begin
              if(rgm.VALUE.get() != 0) value_e.trigger();
            end
            "RELOAD" : begin
              if(rgm.RELOAD.get() != 0) reload_e.trigger();
            end
            "INTSTAT" : begin
              if(rgm.INTSTAT.get == 1'b1) timer_intrclr_e.trigger();
            end
          endcase
        end
      endtask
    
      virtual task do_listen_events();
          //空函数,直接在scoreboard和coverage_model中覆盖
      endtask
    

5、scoreboard文件

  1. run_phase

    task run_phase(uvm_phase phase);
        super.run_phase(phase);//包含了父类的do_event_trigger和listen_events
        fork
            do_counting();//参考模型
            do_differing();//做对比
        join_none
      endtask
    
  2. do_listen_events

    根据subscriber中定义的event来设置相应的函数,用来对refmod的参数做配置

     task do_listen_events();
        fork
          listen_counter_reset();
          listen_counter_enable();
          listen_counter_intren();
          listen_counter_reload();
          listen_counter_value();
          listen_counter_intrclr();
        join_none
      endtask
    
     virtual task listen_counter_reset();
        forever begin
            @(negedge vif.vip_rstn);
          counter = 0;
        end
      endtask
    
      virtual task listen_counter_enable();
        forever begin
          enable_e.wait_trigger();
          if(rop.kind == UVM_WRITE) begin
            cfg.enable = 1;
          end
        end
      endtask
    
  3. do_counting

    搭建refmod

  4. do_differing

    dut的输出和refmod的输出作比较

6、coverage_model文件

基于测试点,一般就是收集寄存器配置,和你感兴趣的信号

  1. 定义covergroup

    covergroup your_reg_cg with function sample(bit[31:0] val , string field);//覆盖默认的采样函数
        option.name = "your CTRL register"
        ENABLE: coverpoint val iff(field == "ENABLE"){
          bins stat_en = {1'b1};
          bins stat_dis= {1'b0};
          bins to_enable = (1'b1 => 1'b0);
          bins to_disable = (1'b0 => 1'b1);
        }
    endgroup
    
  2. 例化covergroup

  3. 定义do_listen_events()

    • 根据事件的触发,进行采样

7、virtual_sequencer文件

  1. 声明config、vip_master_sequencer
  2. 工厂注册,例化

四、test文件夹

1、base_test文件

一般是初始化clock和寄存器配置,没有就先不管

  1. 声明test需要的组件,一般是config,environment和register_model_block

  2. bulid_phase

    • register_model_block需要手动build

    • register_model_block和config需要通过config_db机制向environment分发

    • 一般也会将rgm放到config中

    • 需要get到vif

      if(!uvm_config_db#(virtual timer_if)::get(this,"","vif", cfg.vif))
            `uvm_fatal("cannot get virtual interface from config DB")
      
    • 可以添加上覆盖率的选项

      if($value$plusargs("COV_ENABLE=%d", cfg.cgm_enable)) begin
       `uvm_info("PLUSARGS", $sformatf("get runtime option +COV_ENABLE=%0d", cfg.cgm_enable), UVM_HIGH)
       end
      
  3. run_phase

     task run_phase(uvm_phase phase);
      super.run_phase(phase);
      phase.raise_objection(this);
         phase.phase_done.set_drain_time(this, 100ns);//为dut的延迟而准备
      phase.drop_objection(this);
     endtask
    

2、your_test

根据需求定义所需的test

  1. run_phase
    • 例化对应的seq并且挂载到virtual_sequencer上
      task run_phase(uvm_phase phase);
       your_seq seq =  your_seq::type_id::create("seq", this);
       super.run_phase(phase);
       phase.raise_objection(this);
       `uvm_info("TESTINFO", "CASE STARTED", UVM_LOW)
       if(seq.randomize())
         seq.start(env.virt_sqr);
       else
         `uvm_fatal("RNDFAIL", "sequence randomization failure")
       `uvm_info("TESTINFO", "CASE FINISHED", UVM_LOW)
        phase.drop_objection(this);
       endtask
      

五、seq文件夹

1、base_virtual_sequence

  1. 不需要工厂创建和覆盖等高级用法的话可以不用工厂注册

  2. 需要挂载到virtual_sequencer上

     `uvm_declare_p_sequencer(virtual_squencer);
    
  3. get到的config是在env里set的

     if(!uvm_config_db#(timer_config)::get(p_sequencer,"","cfg", cfg)) begin
          `uvm_fatal("GETCFG","cannot get config object from config DB")
     end
    
  4. 把seq都需要做的方法,放在body中,其他的常用的方法定义好,方便后续的复用。

2、your_sequence

  1. 声明你需要的变量及其约束
  2. 要进行工厂注册
  3. 在body任务里实现你想要的测试功能

六、reg文件夹

1、regmodel文件

对应设计的寄存器模型,可以手写也可以通过脚本自动生成

七、sim文件夹

1、makefile文件

按照两部法或者三步法编写makefile文件,非必须

2、其他文件

  1. 波形文件
  2. log文件
  3. 输出的覆盖率文件

八、vip文件夹

所用的vip文件

九、需要传递的几个信号追踪

  1. interface
    在tb文件里实例化,传递给base_test下的cfg中的virtual timer_if vif,之后通过cfg可以在任何一个需要的地方用config_db机制获取

  2. vip_interface
    在tb文件里实例化,传递给base_test下的env下的apb_mst