【芯片验证】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函数是以深拷贝为目的的,拷贝源与拷贝结果都有各自的空间。那么对于复杂结构比如其中的类实例是怎么操作以保证深拷贝的呢?
|