欢迎光临专业集成电路测试网~~欢迎加入IC测试QQ群:111938408

专业IC测试网

芯片验证】UVM源码计划(三) —— 从copy看obje

时间:2024-08-24 18:22来源:尼德兰的喵 芯时代青年 作者:ictest8_edit 点击:

 

【芯片验证】UVM源码计划(二) —— 一起研究下create/clone/copy

在本篇开始前先补充一位大佬关于上一篇的评论,详情可在阅读原文的评论区查看:

 

感谢指正与指导,果然芯片验证这件事深奥无比其乐无穷,没有经验或者结合实际深入分析过,很多时候即使读源码也只能知其然不知其所以然。因此在这里还是要重申一下,因为我自己的经验并不充分,对很多地方的理解是很有局限性甚至错误的,大家请批判性的阅读。

COPY函数的实现

先看一下uvm_object中copy函数的实现:
// copy
// ----

function void uvm_object::copy (uvm_object rhs);
  //For cycle checking
  static int depth;
  if((rhs !=null)  && (uvm_global_copy_map.get(rhs) != null)) begin
    return;
  end

  if(rhs==null) begin
    uvm_report_warning("NULLCP", "A null object was supplied to copy; copy is ignored", UVM_NONE);
    return;
  end

  uvm_global_copy_map.set(rhs, this);
  ++depth;

  __m_uvm_field_automation(rhs, UVM_COPY, "");
  do_copy(rhs);

  --depth;
  if(depth==0) begin
    uvm_global_copy_map.clear();
  end
endfunction

关于uvm_global_copy_map和depth的地方咱们先不管,删繁就简把这两个相关代码搞掉之后就简单多了:

// copy
// ----

function void uvm_object::copy (uvm_object rhs);
  if(rhs==null) begin
    uvm_report_warning("NULLCP", "A null object was supplied to copy; copy is ignored", UVM_NONE);
    return;
  end
  __m_uvm_field_automation(rhs, UVM_COPY, "");
  do_copy(rhs);
endfunction

也就是copy函数就做了三步事情:检查输入的rhs是否已经new()分配了空间,调用__m_uvm_field_automation(rhs, UVM_COPY, "")执行复制操作(注意是把传入的rhs复制给自身哈,别搞反了),然后通过do_copy再执行补充的复制操作。这个时候我们来看看这三个函数的定义:

extern function void copy (uvm_object rhs);
extern virtual function void do_copy (uvm_object rhs);
extern virtual function void __m_uvm_field_automation (uvm_object tmp_data__, int what__, string str__);

一件很有意思的事,do_copy和__m_uvm_field_automation是virtual function而copy是非virtual function,这里暗含着什么意思呢?就是说使用者不要在继承类里重载copy,你去搞do_copy和__m_uvm_field_automation就好了,copy函数就这么放着你别动(这里就不得不说我之前做的gen_uvm_agent脚本有问题了,回头我改了去)。而__m_uvm_field_automation和do_copy根据后面阅读源码可以知道,前者是类内的field注册时自动重载的,那么自然没有注册的field又想进行复制那自然就在do_copy里去搞了。同样的道理,其他各类操作也是这样设置的:

function string uvm_object::sprint(uvm_printer printer=null);
...
    __m_uvm_field_automation(null, UVM_PRINT, "");
    do_print(printer);
...
endfunction

function bit  uvm_object::compare (uvm_object rhs,
                                   uvm_comparer comparer=null);
...
    __m_uvm_field_automation(rhs, UVM_COMPARE, "");
    dc = do_compare(rhs, comparer);
...
endfunction

function void uvm_object::m_pack (inout uvm_packer packer);
...
  __m_uvm_field_automation(null, UVM_PACK, "");
  do_pack(packer);
...
endfunction

就不一一进行列举啦大家看的时候明白这个思路就可以,回到copy的主线上来。do_copy的描述写的很清楚是user-definable的,也就是说你自己看情况去补充他:

  // Function: do_copy
  //
  // The do_copy method is the user-definable hook called by the copy method.
  // A derived class should override this method to include its fields in a copy
  // operation.
  //
  // A typical implementation is as follows:
  //
  //|  class mytype extends uvm_object;
  //|    ...
  //|    int f1;
  //|    function void do_copy (uvm_object rhs);
  //|      mytype rhs_;
  //|      super.do_copy(rhs);
  //|      $cast(rhs_,rhs);
  //|      field_1 = rhs_.field_1;
  //|    endfunction
  //
  // The implementation must call ~super.do_copy~, and it must $cast the rhs
  // argument to the derived type before copying.

所以说此时的重心就是看下__m_uvm_field_automation(null, UVM_PACK, "")是如何实现的。在uvm_object文件中这是一个空函数:

// __m_uvm_field_automation
// ------------------

function void uvm_object::__m_uvm_field_automation (uvm_object tmp_data__,
                                              int        what__,
                                              string     str__ );
  return;
endfunction

显然我们从名字也能看出来,这个必然是在field注册时进行重载的,所以咱们转头到uvm_object_define.svh文件里看看,很轻松就找到了位置:

`define uvm_field_utils_begin(T) \
   function void __m_uvm_field_automation (uvm_object tmp_data__, \
                                     int what__, \
                                     string str__); \
   begin \
     T local_data__; /* Used for copy and compare */ \
     ...

没错,就是在我们进行field注册时UVM帮我们完成的这个函数!这个时候我们带入实例看一下就更清楚了,巧了源码里就提供了我们常见的用法:

//|  class my_trans extends uvm_sequence_item;
//|
//|    cmd_t  cmd;
//|    int    addr;
//|    int    data[$];
//|    my_ext ext;
//|    string str;
//|
//|    `uvm_object_utils_begin(my_trans)
//|      `uvm_field_enum     (cmd_t, cmd, UVM_ALL_ON)
//|      `uvm_field_int      (addr, UVM_ALL_ON)
//|      `uvm_field_queue_int(data, UVM_ALL_ON)
//|      `uvm_field_object   (ext,  UVM_ALL_ON)
//|      `uvm_field_string   (str,  UVM_ALL_ON)
//|    `uvm_object_utils_end
//|
//|    function new(string name="mydata_inst");
//|      super.new(name);
//|    endfunction
//|
//|  endclass

那么结合`uvm_object_utils_end的源码:

`define uvm_object_utils_end \
     end \
   endfunction \

我们就知道了,中间这个field注册区完成的就是这个__m_uvm_field_automation function。那么咱们进一步的简化举例,这是一个my_trans类中只有一个int类型数据my_addr,然后把注册宏展开并且只保留copy的部分:

`uvm_object_utils_begin(my_trans)
  `uvm_field_int(my_addr, UVM_ALL_ON)
`uvm_object_utils_end

展开的结果就是这样,注册宏变成了一个完整的__m_uvm_field_automation函数:

function void __m_uvm_field_automation (uvm_object tmp_data__, int what__, string str__);
   begin
     my_trans local_data__; /* Used for copy and compare */
     ...
     super.__m_uvm_field_automation(tmp_data__, what__, str__);
     if(tmp_data__ != null) begin
       if(!$cast(local_data__, tmp_data__)) return;
     end
     ...
     begin//`uvm_field_int start
       case (what__)
         ...
         UVM_COPY:
           begin
             if(local_data__ == null) return;
             if(!((FLAG)&UVM_NOCOPY)) my_addr = local_data__.my_addr;
           end
          ...
       endcase
     end//`uvm_field_int end
     ...
  end
endfunction

看一下这个函数的实现:

1. 创建了一个my_trans类型的数据local_data__,并在确定了输入的tmp_data__非空后通过$cast将local_data__指向了tmp_data__,这么做为了避免类型冲突;

2. super.__m_uvm_field_automation(tmp_data__, what__, str__),确保本类型基类中声明和赋值的各项成员变量也进行了递归的拷贝;

3. 而后case(what_),copy中调用传参传的what是什么呢?是UVM_COPY,所以就执行了下面的UVM_COPY分支;

4. 在分支里确认了local_data__是否为空,如果tmp_data__为空则不会进行$cast操作那么local_data__则必然为空那么就直接退出(因为没有拷贝源了呀),否则的话就将local_data__.my_addr拷贝给自己的my_addr;

if(!((FLAG)&UVM_NOCOPY)) my_addr = local_data__.my_addr这句话单独说一下,省的后面忘了。FLAG传的是啥呢?一般来说就是UVM_ALL_ON,具体是啥在这:

parameter UVM_DEFAULT     = 'b000010101010101;
parameter UVM_ALL_ON      = 'b000000101010101;
parameter UVM_FLAGS_ON    = 'b000000101010101;
parameter UVM_FLAGS_OFF   = 0;

//Values are or'ed into a 32 bit value
//and externally
parameter UVM_COPY         = (1<<0);
parameter UVM_NOCOPY       = (1<<1);
parameter UVM_COMPARE      = (1<<2);
parameter UVM_NOCOMPARE    = (1<<3);
parameter UVM_PRINT        = (1<<4);
parameter UVM_NOPRINT      = (1<<5);
parameter UVM_RECORD       = (1<<6);
parameter UVM_NORECORD     = (1<<7);
parameter UVM_PACK         = (1<<8);
parameter UVM_NOPACK       = (1<<9);
//parameter UVM_DEEP         = (1<<10);
//parameter UVM_SHALLOW      = (1<<11);
//parameter UVM_REFERENCE    = (1<<12);
parameter UVM_PHYSICAL     = (1<<13);
parameter UVM_ABSTRACT     = (1<<14);
parameter UVM_READONLY     = (1<<15);
parameter UVM_NODEFPRINT   = (1<<16);

所以这里!((FLAG)&UVM_NOCOPY) = !('b101010101&'b10) = !('b0) = 'b1,所以if(!((FLAG)&UVM_NOCOPY)) begin-end块内的代码才会执行。如果在注册时传的flag是UVM_ALL_ON | UVM_NOCOPY,那么!((FLAG)&UVM_NOCOPY)就变成了!((FLAG)&UVM_NOCOPY) = !('b101010111&'b10) = !('b1) = 'b0,所以copy功能就没有实现。

OK这一套下来,通过注册机制实现copy函数的行为就非常明确。每个`uvm_field_int(my_addr, UVM_ALL_ON)都是这样一段begin-case-endcase-end代码,所以每一个注册的field都自动实现了copy功能。从这里也能看出来,UVM中实现的copy函数是以深拷贝为目的的,拷贝源与拷贝结果都有各自的空间。那么对于复杂结构比如其中的类实例是怎么操作以保证深拷贝的呢?


object内类实体注册


顾名思义,就是在一个uvm_object内声明了另外一个object并且在`uvm_object_utils里进行注册(这里用注册可能容易有歧义,但是我也不知道用哪个词更准确,不要跟factory注册弄混了就可以)。最典型的场景就是agent里声明drv/mon/sqr并且进行域注册,毕竟uvm_component本质上也是uvm_object:

//|  class my_comp extends uvm_component;
//|
//|    my_comp_cfg  cfg;
//|
//|    `uvm_component_utils_begin(my_comp)
//|      `uvm_field_object   (cfg,  UVM_ALL_ON)
//|    `uvm_object_utils_end
//|
//|    function new(string name="my_comp_inst", uvm_component parent=null);
//|      super.new(name);
//|    endfunction
//|
//|  endclass

那么此时我们用的哪个宏来注册?上文中也写到了:

`uvm_field_object   (cfg,  UVM_ALL_ON)

于是找到这段宏的定义,我们还是重点看copy的实现:

`define uvm_field_object(ARG,FLAG) \
  begin \
    case (what__) \
...
      UVM_COPY: \
        begin \
          if(local_data__ == null) return; \
          if(!((FLAG)&UVM_NOCOPY)) begin \
            if((FLAG)&UVM_REFERENCE || local_data__.ARG == null) ARG = local_data__.ARG; \
            else begin \
              uvm_object l_obj; \
              if(local_data__.ARG.get_name() == "") local_data__.ARG.set_name(`"ARG`"); \
              l_obj = local_data__.ARG.clone(); \
              if(l_obj == null) begin \
                `uvm_fatal("FAILCLN", $sformatf("Failure to clone %s.ARG, thus the variable will remain null.", local_data__.get_name())); \
              end \
              else begin \
                $cast(ARG, l_obj); \
                ARG.set_name(local_data__.ARG.get_name()); \
              end \
            end \
          end \
        end \
...
    endcase \
  end

可以看到这段代码里有两个执行分支:

1. if((FLAG)&UVM_REFERENCE || local_data__.ARG == null) ARG = local_data__.ARG;

2. l_obj = local_data__.ARG.clone();...$cast(ARG, l_obj);

这两个分支都很有意思,为了便于理解咱们先看第二个分支:

              uvm_object l_obj; \
              if(local_data__.ARG.get_name() == "") local_data__.ARG.set_name(`"ARG`"); \
              l_obj = local_data__.ARG.clone(); \
              if(l_obj == null) begin \
                `uvm_fatal("FAILCLN", $sformatf("Failure to clone %s.ARG, thus the variable will remain null.", local_data__.get_name())); \
              end \
              else begin \
                $cast(ARG, l_obj); \
                ARG.set_name(local_data__.ARG.get_name()); \
              end \

创建了 uvm_object l_obj,而后注意执行了clone()函数:l_obj = local_data__.ARG.clone(),这说明了两点:

1. 对于类对象在拷贝的时候也是执行的深拷贝,clone本身就分配了地址空间使拷贝双方彻底分离;

2. 如果类对象是uvm_component类,那么走到这就会报UVM_ERROR;

那么类对象是uvm_component类怎么办呢?我们去看第一个分支:

if((FLAG)&UVM_REFERENCE || local_data__.ARG == null) ARG = local_data__.ARG;

UVM_REFERENCE在哪里呢,在刚刚的代码里但是被注释掉了:

//parameter UVM_DEEP         = (1<<10);
//parameter UVM_SHALLOW      = (1<<11);
//parameter UVM_REFERENCE    = (1<<12);

仔细在uvm_object_globals.svh里查找,发现在下面重新定义了一次:

// Enum: uvm_recursion_policy_enum
//
// Specifies the policy for copying objects.
//
// UVM_DEEP      - Objects are deep copied (object must implement copy method)
// UVM_SHALLOW   - Objects are shallow copied using default SV copy.
// UVM_REFERENCE - Only object handles are copied.

typedef enum {
  UVM_DEFAULT_POLICY = 0,
  UVM_DEEP           = 'h400,
  UVM_SHALLOW        = 'h800,
  UVM_REFERENCE      = 'h1000
 } uvm_recursion_policy_enum;

注意哈,在枚举里定义和在类里直接定义其实没有本质区别,再仔细一看数值,跟注释掉那里数据没有什么不同所以我不清楚为啥要重新写一次。看这里的解释就明白了,UVM_REFERENCE的意思就是进行句柄拷贝。

所以回头看这里就清晰了很多,uvm_component走下面的分支因为clone的存在会报错,那么就要走上面的分支怎么才能走上面的分支呢?需要在component field自动化注册时这样写:

`uvm_field_object   (ex_comp,  UVM_ALL_ON | UVM_REFERENCE)

仅仅这样还不行,还要满足一个条件就是被拷贝对象内部的这个component还没有type_id::create是一个空句柄,那么接下来才会进行一个空句柄之间的句柄拷贝而不会报错。否则类中的component类实例拷贝就会进入到下面的报错分支。

在这里大家自行感受一下就好了,我个人的看法是UVM希望使用者尽力避免component之间的任何拷贝行为,你最好把那些数值啥的放到cfg_object文件里去随便复制,但是别来搞uvm_component。这里就说这么多好啦,更多的细节大家还是通过阅读源码来挖掘吧。


uvm_global_copy_map与depth


最后我们回到刚刚被删繁就简去掉的那部分uvm_global_copy_map与depth代码,看看这俩在起什么作用:

// copy
// ----

function void uvm_object::copy (uvm_object rhs);
  //For cycle checking
  static int depth;
  if((rhs !=null)  && (uvm_global_copy_map.get(rhs) != null)) begin
    return;
  end

  if(rhs==null) begin
    uvm_report_warning("NULLCP", "A null object was supplied to copy; copy is ignored", UVM_NONE);
    return;
  end

  uvm_global_copy_map.set(rhs, this);
  ++depth;

  __m_uvm_field_automation(rhs, UVM_COPY, "");
  do_copy(rhs);

  --depth;
  if(depth==0) begin
    uvm_global_copy_map.clear();
  end
endfunction

uvm_global_copy_map结构本身很简单,他就是个字典结构罢了:

class uvm_copy_map;
  local uvm_object m_map[uvm_object];
  function void set(uvm_object key, uvm_object obj);
    m_map[key] = obj;
  endfunction
  function uvm_object get(uvm_object key);
    if (m_map.exists(key))
       return m_map[key];
    return null;
  endfunction
  function void clear();
    m_map.delete();
  endfunction
  function void delete(uvm_object v);
    m_map.delete(v);
  endfunction
endclass

所以我不太理解UVM为啥不做一个通用的uvm_object的字典结构,而要单独在这做一下。所以这个uvm_global_copy_map的作用也就很明显了啊,如果发现两个object已经进行过拷贝了那就不用搞了:
  if((rhs !=null)  && (uvm_global_copy_map.get(rhs) != null)) begin
    return;
  end
那么说怎么拷贝时会发现两者已经拷贝过了呢?类A里可能类B的实例,类B里可能有类C实例,估计拷贝过程中保不齐哪个就拷贝过了?其实我不是太清楚这里的含义,不过行为是非常明确的,不行咱就看看他的注释自行体会下吧:

//------------------------------------------------------------------------------
//
// CLASS- uvm_copy_map
//
//
// Internal class used to map rhs to lhs so when a cycle is found in the rhs,
// the correct lhs object can be bound to it.
//------------------------------------------------------------------------------

关于这点,评论区的大佬解释的更加清楚准确,膜拜学习:

 

然后再说这个depth,他是个所有uvm_object copy方法共享的静态变量:

static int depth;

还是刚刚说的,uvm_object类A里可能类B的实例bb,类B里可能有类C实例cc,那么执行A aa1.copy(aa2)这样的操作时,在__m_uvm_field_automation(rhs, UVM_COPY, "")里必然会调用到aa1.bb = aa2.bb.clone(),而clone = create + copy,所以就会进入到B类的copy函数中。因为类A和类B共享depth所以depth会由1赋值为2,标记着当前递归克隆进行的深度。
 
 
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
用户名: 验证码: 点击我更换图片