电子产业一站式赋能平台

PCB联盟网

搜索
查看: 44|回复: 0
收起左侧

【芯片验证】UVM源码计划(三) —— 从copy看object field注册机制

[复制链接]
匿名  发表于 2024-8-23 11:56:00 |阅读模式
前言上一篇讨论了一些关联内容并且简单的看了看clone和create的源码,这一篇咱们接着往下聊由copy函数来看看object的域注册机制。
【芯片验证】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看一下这个函数的实现:
  • 创建了一个my_trans类型的数据local_data__,并在确定了输入的tmp_data__非空后通过$cast将local_data__指向了tmp_data__,这么做为了避免类型冲突;
  • super.__m_uvm_field_automation(tmp_data__, what__, str__),确保本类型基类中声明和赋值的各项成员变量也进行了递归的拷贝;
  • 而后case(what_),copy中调用传参传的what是什么呢?是UVM_COPY,所以就执行了下面的UVM_COPY分支;
  • 在分支里确认了local_data__是否为空,如果tmp_data__为空则不会进行$cast操作那么local_data__则必然为空那么就直接退出(因为没有拷贝源了呀),否则的话就将local_data__.my_addr拷贝给自己的my_addr;
    [/ol]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所以这里!((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可以看到这段代码里有两个执行分支:
  • if((FLAG)&UVM_REFERENCE || local_data__.ARG == null) ARG = local_data__.ARG;
  • l_obj = local_data__.ARG.clone();...$cast(ARG, l_obj);
    [/ol]这两个分支都很有意思,为了便于理解咱们先看第二个分支:
                  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(),这说明了两点:
  • 对于类对象在拷贝的时候也是执行的深拷贝,clone本身就分配了地址空间使拷贝双方彻底分离;
  • 如果类对象是uvm_component类,那么走到这就会报UVM_ERROR;
    [/ol]那么类对象是uvm_component类怎么办呢?我们去看第一个分支:
    if((FLAG)&UVM_REFERENCE || local_data__.ARG == null) ARG = local_data__.ARG;UVM_REFERENCE在哪里呢,在刚刚的代码里但是被注释掉了:
    //parameter UVM_DEEP         = (1仔细在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
    endfunctionuvm_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,标记着当前递归克隆进行的深度。

    系列文章入口——
    【芯片设计】SoC 101(一):绪论
    【芯片设计】FIFO漫谈(零)从无处不在的FIFO开始说起
    【芯片设计】计算机体系结构(一)虚拟内存
    【芯片设计】深入理解AMBA总线(零)绪论
    【芯片设计】握手协议的介绍与时序说明
    【芯片设计】复位那些小事 —— 复位消抖
    【芯片设计】快速入门数字芯片设计(一)Introduction
    【芯片验证】UVM源码计划(零)下定决心读源码前的自测环节
    【芯片设计】异步电路碎碎念(一) 到底什么是异步电路
    【芯片设计】从RTL到GDS(一):Introduction
    其他文章链接——
    【芯片验证】sva_assertion: 15道助力飞升的断言练习
    【芯片验证】可能是RTL定向验证的巅峰之作
    【芯片验证】RTL仿真中X态行为的传播 —— 从xprop说起
    【芯片验证】年轻人的第一个systemVerilog验证环境全工程与解析
    【芯片设计】verilog中有符号数和无符号数的本质探究
    【芯片设计】论RTL中always语法的消失术
    【芯片设计】代码即注释,注释即代码
    【芯片设计】700行代码的risc处理器你确实不能要求太多了
    入职芯片开发部门后,每天摸鱼之外的时间我们要做些什么呢
    如何计算系统的outstanding 和 burst length?
    芯片搬砖日常·逼死强迫症的关键词不对齐事件
    熟人社会里,一群没有社会价值的局外人
  • 本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x
    回复

    使用道具

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表