IC验证--基础介绍

UVM寄存器模型简介

在IC验证中常会模拟软件对硬件进行操作,这个操作就是软件会通过总线来对设计中的一些寄存器进行配置,来修改硬件表现出来的行为。
在实际的情况中,硬件可能有很多寄存器,比如对soc而言,在soc top level可能会有一些寄存器,同时soc中又集成了很多个IP,IP又有不同的submodule,每个IP和submodule又可能拥有属于自己的寄存器,在实际的仿真中,为了便于这些寄存器的统一管理,通常会使用uvm的寄存器模型。
uvm的寄存器模型就是为了方便在实际仿真中读写DUT的寄存器。通常对寄存器的访问又可以分为前门访问(Frontdoor)和后门访问(Backdoor)。前门访问就是模拟实际软件的行为,通过总线来对寄存器读写。由于前门访问会消耗实际仿真时间,对于一个需要大量配置寄存器的DUT来说,为了减小仿真的时间,在前门访问通路都accessable之后,会选择用后门访问的方式来读写这些寄存器。后门访问就是通过RTL hierarchy(dut.a.b.register = xx)的方式,直接把结果写道DUT中,这种方式是不消耗仿真时间的。
uvm的寄存器模型最主要就是为了实现这两种寄存器的访问方式,因此在寄存器模型中必要需要管理访问寄存器所需的address(前门访问)和hierarchy(后门访问)。而管理这两样的就是uvm_reg_map,也就是每个uvm_reg或者uvm_reg_block的default_map。
从uvm的源码来看,寄存器的容器可以分为uvm_reg_field,uvm_reg,uvm_reg_block。其中uvm_reg_field对应的就是实际RTL中的寄存器,以32位总线位宽的宽寄存器模型来说,一个32bit对应一个uvm_reg,一个uvm_reg可能包含多个uvm_reg_field,如图:

class dut_ID extends uvm_reg;

   uvm_reg_field REVISION_ID;
   uvm_reg_field CHIP_ID;
   uvm_reg_field PRODUCT_ID;

   function new(string name = "dut_ID");
      super.new(name,32,UVM_NO_COVERAGE);
   endfunction

   virtual function void build();
      this.REVISION_ID = uvm_reg_field::type_id::create("REVISION_ID");
          this.CHIP_ID = uvm_reg_field::type_id::create("CHIP_ID");
       this.PRODUCT_ID = uvm_reg_field::type_id::create("PRODUCT_ID");

      this.REVISION_ID.configure(this, 8,  0, "RO",   0, 8'h03, 1, 0, 1);
          this.CHIP_ID.configure(this, 8,  8, "RO",   0, 8'h5A, 1, 0, 1);
       this.PRODUCT_ID.configure(this, 10, 16,"RO", 0, 10'h176, 1, 0, 1);
   endfunction
   
   `uvm_object_utils(dut_ID)
   
endclass

这个里面最重要的就是configure函数:

   // Function: configure
   //
   // Instance-specific configuration
   //
   // Specify the ~parent~ register of this field, its
   // ~size~ in bits, the position of its least-significant bit
   // within the register relative to the least-significant bit
   // of the register, its ~access~ policy, volatility,
   // "HARD" ~reset~ value, 
   // whether the field value is actually reset
   // (the ~reset~ value is ignored if ~FALSE~),
   // whether the field value may be randomized and
   // whether the field is the only one to occupy a byte lane in the register.
   //
   // See <set_access> for a specification of the pre-defined
   // field access policies.
   //
   // If the field access policy is a pre-defined policy and NOT one of
   // "RW", "WRC", "WRS", "WO", "W1", or "WO1",
   // the value of ~is_rand~ is ignored and the rand_mode() for the
   // field instance is turned off since it cannot be written.
   //
 function void uvm_reg_field::configure(uvm_reg        parent,
                                       int unsigned   size,
                                       int unsigned   lsb_pos,
                                       string         access,
                                       bit            volatile,
                                       uvm_reg_data_t reset,
                                       bit            has_reset,
                                       bit            is_rand,
                                       bit            individually_accessible); 
                                                                  

通过这个函数配置完之后的寄存器大概得到这些信息:
在这里插入图片描述
这些信息能够让TB知道寄存器在初始化之后的值以及在一个32bit的地址中的相对位置,还没有具体的前后门可以访问的信息。当他被集成到uvm_reg_block之后;

class dut_regmodel extends uvm_reg_block;

   rand dut_ID       ID;
   function new(string name = "slave");
      super.new(name,UVM_NO_COVERAGE);
   endfunction

   virtual function void build();
   // create
      ID = dut_ID::type_id::create("ID");
   // configure
      ID.configure(this,null,"ID");
      ID.build();
      default_map = create_map("default_map", 'h0, 4, UVM_LITTLE_ENDIAN, 1);
      default_map.add_reg(ID,   'h0,  "RW");  
   endfunction
   
   `uvm_object_utils(dut_regmodel)
   
endclass : dut_regmodel    

这里面的几个函数一个一个看:

1. configure函数
   // Function: configure
   //
   // Instance-specific configuration
   //
   // Specify the parent block of this register.
   // May also set a parent register file for this register,
   //
   // If the register is implemented in a single HDL variable,
   // its name is specified as the ~hdl_path~.
   // Otherwise, if the register is implemented as a concatenation
   // of variables (usually one per field), then the HDL path
   // must be specified using the <add_hdl_path()> or
   // <add_hdl_path_slice> method.
   //
  function void uvm_reg::configure (uvm_reg_block blk_parent,
                                  uvm_reg_file regfile_parent=null,
                                  string hdl_path = "");
   if (blk_parent == null) begin
     `uvm_error("UVM/REG/CFG/NOBLK", {"uvm_reg::configure() called without a parent block for instance \"", get_name(), "\" of register type \"", get_type_name(), "\"."})
     return;
   end

   m_parent = blk_parent;
   m_parent.add_reg(this);
   m_regfile_parent = regfile_parent;
   if (hdl_path != "")
     add_hdl_path_slice(hdl_path, -1, -1);
endfunction: configure

configure的第一个参数表示当前reg属于哪个block,,第二个参数是regfile,暂时不讲,最后一个参数就是hdl_path,就是表示这个寄存器在DUT中的hierachy的一个节点,也就是后门路径对应的名字。
其中add_reg函数会把当前的uvm_reg添加到parent参数也就是uvm_reg_block的uvm_reg队列中。

2.build函数

这个函数就是掉用了我们例化uvm_reg_field定义的函数,需要手动调用。

3. create_map函数

uvm_reg_block有一个预先定义好的default_map变量,这个default_map会管理所有寄存器相关的操作,

   // Variable: default_map
   //
   // Default address map
   //
   // Default address map for this block, to be used when no
   // address map is specified for a register operation and that
   // register is accessible from more than one address map.
   //
   // It is also the implicit address map for a block with a single,
   // unnamed address map because it has only one physical interface.
   //
uvm_reg_map default_map;
//------------------------------------------------------------------------------
//
// Class: uvm_reg_map
//
// :Address map abstraction class
//
// This class represents an address map.
// An address map is a collection of registers and memories
// accessible via a specific physical interface.
// Address maps can be composed into higher-level address maps.
//
// Address maps are created using the <uvm_reg_block::create_map()>
// method.
//------------------------------------------------------------------------------
class uvm_reg_map extends uvm_object;

   `uvm_object_utils(uvm_reg_map)
   
   // info that is valid only if top-level map
   local uvm_reg_addr_t     m_base_addr;
   local int unsigned       m_n_bytes;
   local uvm_endianness_e   m_endian;
   local bit                m_byte_addressing;
   local uvm_object_wrapper m_sequence_wrapper;
   local uvm_reg_adapter    m_adapter;
   local uvm_sequencer_base m_sequencer;
   local bit                m_auto_predict;
   local bit                m_check_on_read;

   local uvm_reg_block      m_parent;

   local int unsigned       m_system_n_bytes;

   local uvm_reg_map        m_parent_map;
   local uvm_reg_addr_t     m_parent_maps[uvm_reg_map];   // value=offset of this map at parent level
   local uvm_reg_addr_t     m_submaps[uvm_reg_map];       // value=offset of submap at this level
   local string             m_submap_rights[uvm_reg_map]; // value=rights of submap at this level

在调用default_map的add_reg函数:

// add_reg

function void uvm_reg_map::add_reg(uvm_reg rg, 
                                   uvm_reg_addr_t offset,
                                   string rights = "RW",
                                   bit unmapped=0,
                                   uvm_reg_frontdoor frontdoor=null);

   if (m_regs_info.exists(rg)) begin
      `uvm_error("RegModel", {"Register '",rg.get_name(),
                 "' has already been added to map '",get_name(),"'"})
      return;
   end

   if (rg.get_parent() != get_parent()) begin
      `uvm_error("RegModel",
         {"Register '",rg.get_full_name(),"' may not be added to address map '",
          get_full_name(),"' : they are not in the same block"})
      return;
   end
   
   rg.add_map(this);

   begin
   uvm_reg_map_info info = new;
   info.offset   = offset;
   info.rights   = rights;
   info.unmapped = unmapped;
   info.frontdoor = frontdoor;
   m_regs_info[rg] = info;
   end
endfunction

可以看出来,add_reg函数主要做了两件事,1,像uvm_reg对象中添加map信息 2,default_map保留一份记录register的info,包含了寄存器的偏移地址,access权限还有frontdoor信息。
至此寄存器的基本配置基本完成,后续只需要将寄存器模型集成到环境中。

寄存器模型集成到tb

   virtual function void build_phase(uvm_phase phase);
      if (regmodel == null) begin
         regmodel = dut_regmodel::type_id::create("regmodel",,get_full_name());
         regmodel.build();
         regmodel.lock_model();
         
         apb = apb_agent::type_id::create("apb", this);
`ifdef EXPLICIT_MON
         apb2reg_predictor = new("apb2reg_predictor", this);
`endif
      end
      
      begin
        string hdl_root = "tb_top.dut";
        void'($value$plusargs("ROOT_HDL_PATH=%s",hdl_root));
        regmodel.set_hdl_path_root(hdl_root);
      end

   endfunction

先例化model,最后调用lock_model函数,防止后续添加寄存器,对模型修改。

   // Function: lock_model
   //
   // Lock a model and build the address map.
   //
   // Recursively lock an entire register model
   // and build the address maps to enable the
   // <uvm_reg_map::get_reg_by_offset()> and
   // <uvm_reg_map::get_mem_by_offset()> methods.
   //
   // Once locked, no further structural changes,
   // such as adding registers or memories,
   // can be made.
   //
   // It is not possible to unlock a model.
   //
   
   virtual function void connect_phase(uvm_phase phase);
      if (apb != null) begin
         reg2apb_adapter reg2apb = new;
         regmodel.default_map.set_sequencer(apb.sqr,reg2apb);
`ifdef EXPLICIT_MON
         apb2reg_predictor.map = regmodel.default_map;
         apb2reg_predictor.adapter = reg2apb;
         regmodel.default_map.set_auto_predict(0);
         apb.mon.ap.connect(apb2reg_predictor.bus_in);
`else
         regmodel.default_map.set_auto_predict(1);
`endif
      end
      regmodel.print();
   endfunctio

其中在调用lock_model的时候也会完成寄存的address initial。

Xinit_address_mapsX();
function void uvm_reg_block::Xinit_address_mapsX();
   foreach (maps[map_]) begin
      uvm_reg_map map = map_;
      map.Xinit_address_mapX();
   end
      //map.Xverify_map_configX();
endfunction
最后调用map的初始化方法,对所有map的所有registers进行初始化:
   foreach (m_submaps[l]) begin
     uvm_reg_map map=l;
     map.Xinit_address_mapX();
   end
   
   foreach (m_regs_info[rg_]) begin
     uvm_reg rg = rg_;
     m_regs_info[rg].is_initialized=1;
     if (!m_regs_info[rg].unmapped) begin
        string rg_acc = rg.Xget_fields_accessX(this);
       uvm_reg_addr_t addrs[];
        
       bus_width = get_physical_addresses(m_regs_info[rg].offset,0,rg.get_n_bytes(),addrs);
   m_regs_info[rg].addr = addrs; 

通过 get_physical_addresses可以得到所有寄存器的物理地址,也就是前门地址。

寄存器模型前门访问

以寄存器的write_reg_by_name方法为例:

// write_reg_by_name

task uvm_reg_block::write_reg_by_name(output uvm_status_e   status,
                                      input  string              name,
                                      input  uvm_reg_data_t      data,
                                      input  uvm_path_e     path = UVM_DEFAULT_PATH,
                                      input  uvm_reg_map      map = null,
                                      input  uvm_sequence_base   parent = null,
                                      input  int                 prior = -1,
                                      input  uvm_object          extension = null,
                                      input  string              fname = "",
                                      input  int                 lineno = 0);
   uvm_reg rg;
   this.fname = fname;
   this.lineno = lineno;

   status = UVM_NOT_OK;
   rg = this.get_reg_by_name(name);
   if (rg != null)
     rg.write(status, data, path, map, parent, prior, extension);

endtask: write_reg_by_name

其实也就是调用uvm_reg的write方法:

task uvm_reg::write(output uvm_status_e      status,
                    input  uvm_reg_data_t    value,
                    input  uvm_path_e        path = UVM_DEFAULT_PATH,
                    input  uvm_reg_map       map = null,
                    input  uvm_sequence_base parent = null,
                    input  int               prior = -1,
                    input  uvm_object        extension = null,
                    input  string            fname = "",
                    input  int               lineno = 0);

   // create an abstract transaction for this operation
   uvm_reg_item rw;

   XatomicX(1);

   set(value);

   rw = uvm_reg_item::type_id::create("write_item",,get_full_name());
   rw.element      = this;
   rw.element_kind = UVM_REG;
   rw.kind         = UVM_WRITE;
   rw.value[0]     = value;
   rw.path         = path;
   rw.map          = map;
   rw.parent       = parent;
   rw.prior        = prior;
   rw.extension    = extension;
   rw.fname        = fname;
   rw.lineno       = lineno;

   do_write(rw);

   status = rw.status;

   XatomicX(0);

endtask

创建一个uvm_reg_item的对象,也就是最后seq传输的对象,其中XatomicX函数是旗语,保证同时只有一个访问:

         // ...VIA USER FRONTDOOR
         if (map_info.frontdoor != null) begin
            uvm_reg_frontdoor fd = map_info.frontdoor;
            fd.rw_info = rw;
            if (fd.sequencer == null)
              fd.sequencer = system_map.get_sequencer();
            fd.start(fd.sequencer, rw.parent);
         end

         // ...VIA BUILT-IN FRONTDOOR
         else begin : built_in_frontdoor

            rw.local_map.do_write(rw);

         end

如果我们再add_reg时添加了frontdoor对象则会优先使用我们定义的函数。否则默认使用我们定义的adapter来进行前门访问。

	  // uvm_reg_sequence line 147
      uvm_reg_item reg_item;
      reg_seqr.peek(reg_item);
      do_reg_item(reg_item);
      reg_seqr.get(reg_item);
      #0;
      
     //uvm_reg_sequence line 147
	// do_write(uvm_reg_item rw)
task uvm_reg_map::do_write(uvm_reg_item rw);

  uvm_sequence_base tmp_parent_seq;
  uvm_reg_map system_map = get_root_map();
  uvm_reg_adapter adapter = system_map.get_adapter();
  uvm_sequencer_base sequencer = system_map.get_sequencer();

  if (adapter != null && adapter.parent_sequence != null) begin
    uvm_object o;
    uvm_sequence_base seq;
    o = adapter.parent_sequence.clone();
    assert($cast(seq,o));
    seq.set_parent_sequence(rw.parent);
    rw.parent = seq;
    tmp_parent_seq = seq;
  end

  if (rw.parent == null) begin
     rw.parent = new("default_parent_seq");
     tmp_parent_seq = rw.parent;
  end

  if (adapter == null) begin
    rw.set_sequencer(sequencer);
    rw.parent.start_item(rw,rw.prior);
    rw.parent.finish_item(rw);
    rw.end_event.wait_on();
  end
  else begin
    do_bus_write(rw, sequencer, adapter);
  end

  if (tmp_parent_seq != null)
    sequencer.m_sequence_exiting(tmp_parent_seq);

endtask

最后do_bus_write函数就是直接调用我们adapter中定义的reg2bus,

      adapter.m_set_item(rw);
      bus_req = adapter.reg2bus(rw_access);
      adapter.m_set_item(null);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值