UVM基础-Callback机制

1.1 Callback机制基本概念

       以最高效的方式完成芯片验证,一直以来都是验证人员的首要目标,那么最直接的方式就是环境的移植和重用,一个优秀的验证工程师,在开发环境的过程中,一定会考虑环境的继承和重用;一般情况下,每个工程师都有对同一个基线环境的修改需求,以适配自身的验证任务,那么环境的原始开发者,就应该考虑到环境的使用者有这样的差异性。将重用过程的这些差异点,用一种验证技术隔离起来,uvm提供了提供了这样的机制,也就是callback机制。

       实际上,callback在systemverilog中经常会用到,比如一个被rand修饰的变量,在调用其randomize函数的时候,systemverilog会先调用这个变量的pre_random(),random()和post_random(),在post_random被调用的时候,实际上就是运用了callback机制,如果我们post_random函数体中做一些动作,比如将一个rand类中的某些rand的成员变量赋值为固定值,那么在调用randomize的时候,会调用post_random的callback,使某些特定的值被固化。

       如何利用callback机制体现验证环境的差异?实际上,callback的概念有点类似于c语言的接口,如果想要实现某个功能,如果c语言有接口函数,那么直接调用接口,改写接口函数而实现用户需求的功能。而uvm的callback也是类似的思想,环境的开发者在一些组件中预留好接口,而在开发的过程中将这些接口函数嵌入到环境开发里,这样如果有用户想要实现满足自身需求的功能,只需要改写这个接口函数,即可达到自己的验证目的。比如,在一个driver组件中,driver收到包后,会将包的数据转换为时序驱动出去,但是有的用户希望在driver收到的包里插入一些固定的id字段;而有的用户希望在包里插入一些特殊序列,以便于验证过程中的识别和检测。那么这些差异性,作为driver的开发者就应该考虑到,将差异点封装为callback,以供环境的使用者去重构。

1.2 Callback机制的用法

callback机制怎么用,我们以两个维度说明,一个是环境的开发者,也就是预留callback接口的设计;另一个是环境的使用者,即希望通过callback来实现自己的验证需求。

作为环境的开发者,一定要实现知道环境的使用者有哪些需求,从需求角度做有目的的预留;如果没办法获取需求,那就要实现自己预期要留有哪些接口,以方便环境的使用者做二次开发。

1.3 作为环境的开发者

以driver的开发为例,作为环境的开发者,应该先预留有callback的接口,代码为:

1.	typedef class my_driver;
2.	class pkt_process_callback extends uvm_callback;
3.	      virtual function void pkt_pre_trans(my_driver drv, my_transaction tr);
4.	      endfunction
5.	endclass;
6.	
7.	class my_driver extends uvm_driver(uvm_sequence_item);
8.	      ......
9.	      virtual my_if      vif;
10.	      my_transaction tr;
11.	      `uvm_component_utils(my_driver)
12.	      `uvm_register_cb(pkt_process_callback)
13.	
14.	      function void new(string name="my_driver", uvm_component parent);
15.	             super.new(name, parent);
16.	      endfunction
17.	     
18.	      virtual function void build_phase(uvm_phase phase);
19.	              super.build_phase(phase);
20.	              if(!uvm_config_db#(virtual my_if)::get(this, "", "interface", vif));
21.	                       `uvm_fatal(get_type_name(), "vif is none!");
22.	       endfunction:build_phase
23.	
24.	       virtual task void run_phase(uvm_phase phase);
25.	              super.run_phase(phase);
26.	              while(1) begin
27.	                       seq_item_port.get_next_item(req);
28.	                       if(!$cast(tr, req))
29.	                               `uvm_fatal(get_type_name(), "error!");
30.	                       `uvm_do_callback(my_driver, pkt_process_callback, pkt_pre_trans(this, tr));
31.	                       drv_pkt();
32.	                       seq_item_port.item_done();
33.	               end
34.	        endtask:run_phase
35.	       
36.	        virtual task drv_pkt();
37.	        ......
38.	        endtask:drv_pkt
39.	endclass

作为环境的开发者,需要预留callback接口,需要做的事情为:

  • 先开发一个callback的类,继承自uvm_callback,然后在类里定义好预留的接口函数或者任务。
  • 然后在需要调用callback的组件中,先对callback类进行注册,使用`uvm_register_cb(tar_component, callback_cls),这个宏有两个参数,第一个参数是使用callback接口的类名,第二个参数是预留好的callback类名。
  • 然后在需要使用callback函数接口的地方,调用`uvm_do_callback(tar_component_handle, callback_cls, intf_function(a,b,c) ),这个宏也有三个参数,第一个参数为使用callback接口函数的类句柄,一般为this指针,因为只有在注册cb的地方才会使用callback,第二个参数为具有接口函数的句柄,也就是callback的类名,最后一个是想调用的接口函数。
  • 要注意的是,开发的callback类中,接口函数一定要被virtual修饰,因为只有被virtual修饰后,环境的使用者才可以改写接口函数,实现自己的功能,也就是说,callback的底层原理实际上是类的多态特性。

以上,作为环境的开发者,预留好接口的组件即开发完成。

1.4 作为环境的使用者

作为环境的使用者,思考的重点是如何利用已有的接口函数实现自己的功能,一般会在tc中,对重定义driver的接口函数。

1.	typedef class my_driver;
2.	typedef class my_transaction;
3.	class my_callback extends pkt_process_callback;
4.	        function void pkt_pre_trans(my_driver drv, my_transaction tr);
5.	                 tr.data_load = 32'h555aaa;
6.	                 `uvm_info(get_type_name(),"my callback is ok");
7.	        endfunction:pkt_pre_trans
8.	endclass
9.	
10.	class my_tc extends base_test;
11.	        ......
12.	        my_callback my_cb;
13.	        `uvm_component_utils(my_tc);
14.	        function void new(string name="my_tc", uvm_component parent);
15.	            super.new(name, parent);
16.	        endfunction 
17.	        
18.	        virtual function void build_phase(uvm_phase phase);
19.	               super.build_phase(phase); 
20.	               my_cb =  my_callback::type_id::create("my_cb");
21.	                ......
22.	         endfunction:build_phase
23.	         
24.	         virtual function void connect_phase(uvm_phase phase);
25.	               super.connect_phase(phase);
26.	               uvm_callback#(my_driver, pkt_process_callback)::add(env.i_agt.drv, my_cb);
27.	               ......
28.	         endfunction:connect_phase
29.	endclass

作为环境的使用者,应该做的动作是:

  • 先定义一个类,继承自预留好的callback类,然后改写里面的接口函数为自己想要实现的功能。
  • 在test_case中,声明这个继承的callback类,然后在build_phase中实例化。
  • 在connect_phase中,或者其他phase,注意执行顺序,将继承的callback类加入到uvm_callback的池子中,即pool,使用uvm_callback#(component, callback)::add(tar_component_handle, callback_handle)中的静态类方法add,还有一种方式,就是让环境的开发者预先定义好pool,即做一个声明:typedef uvm_callback#(my_driver, pkt_process_callback) a_pool;然后环境的使用者将代码26行改写为:a_pool::add(env.i_agt.drv, my_cb)。

实际上,uvm_callback机制的原理,是在driver中调用callback接口的地方,会循环从a_pool中寻找是否有接口被改写,找到了会调用改写后的函数,如果池子中没有类,则会调用原始的callback类,也就是原始接口,一般为空。实际的uvm关于callback机制的源码更为复杂,请读者自行研究。

如果有一个验证环境的组件,被重载过,那么原有的callback接口如何继承?uvm也提供了相应的方法,假设还是上述实例,有一个new_driver要重载my_driver,那么需要在new_driver中继承原有my_driver的callback:

1.	class new_driver extends my_driver;
2.	      ......
3.	      `uvm_component_utils(new_driver)
4.	      `uvm_set_super_type(new_driver, my_driver);
5.	
6.	      function void new(string name="new_driver", uvm_component parent);
7.	             super.new(name, parent);
8.	      endfunction
9.	     
10.	       virtual task void run_phase(uvm_phase phase);
11.	              super.run_phase(phase);
12.	              while(1) begin
13.	                       seq_item_port.get_next_item(req);
14.	                       if(!$cast(tr, req))
15.	                               `uvm_fatal(get_type_name(), "error!");
16.	                       `uvm_do_callback(my_driver, pkt_process_callback, pkt_pre_trans(this, tr));
17.	                       drv_pkt();
18.	                       seq_item_port.item_done();
19.	               end
20.	        endtask:run_phase
21.	       
22.	        virtual task drv_pkt();
23.	        ......
24.	        endtask:drv_pkt
25.	endclass

需要注意的是,在new_driver中需要调用`uvm_set_super_type(new_driver, my_driver),实现对my_driver callback接口的继承,那么在new_driver中调用callback接口的地方,还是`uvm_do_callback宏传参依旧传递的是my_driver的类,其他的地方不用改,那么new_driver会自动继承my_driver的所有callback接口。

2.1 重用单元级验证

对于单元验证而言,一个好的环境,除了能够高效的实现所有对DUT功能的验证之外,还应该具备可重用性,除了上文提到的callback技术之外,一个好的单元验证环境,还能够被集成到集成验证环境中,或者系统验证环境中,想象一个集成验证功能模块为下图所示:

对于每一个模块,都有自己的单元验证环境,那么如何将单元验证环境重用到继承验证subsys_a的环境中,一种做法是:

       这种环境显然十分庞大,其实在chip_env中完全没有对子env进行重构,只是单纯的做了子环境的拼接,在这个基础上去掉了B和C的driver,而没有做其他动作,激励源只需要复用A的子环境激励源即可。优点是环境的继承和重用方案简单,但缺点也很明显,就是会降低仿真的效率,而且过多的组件引入会增加调试的工作量。那么考虑另外一个重用方案:

 

 

这个方案中,删除了重复功能的模块,即B的i_agt的monitor和C的i_agt的monitor。那么实际上,这个时候需要在intergration_env中重定义tlm互联结构。实现方案框图如下:

通常情况下,由于单元验证环境开发的差异性,在重用的过程中可能还需要引入适配器组件和tlm fifo,环境结构变为:

通常情况下,这样的集成验证由于引入了新的适配组件,新组建在phase机制上,和子环境的运行适配上,都会给调试带来很大的工作量,要正确处理好这方面的问题,才能做到env在chip级别的重用。

对于集成验证方案的理解:

集成验证方案的定制依赖系统或者集成dut的模块配合与耦合度,在一定程度上,集成验证的思路和SOC集成开发实际上是一样的,SOC的集成要考虑模块之间的互联,通信交互,数据流,总线等方案的设计,那么集成验证要考虑的实际上是对于每个单元验证环境的交互,单元验证环境组件之间的交互,工作量在于对各级单元验证环境之间通信机制的方案输出,以及数据流处理过程中对于数据包结构的适配。

另外,如果要实现真真意义的环境集成验证,必须要向单元验证的同事提出需求,比如说dut a的输出数据包应该和dut b的输入数据包一致,这样才能减少集成验证对A2B transever组件的开发工作量。但这通常很难做到,因此对于SOC集成验证的挑战在于:

  • 针对集成模块的特点,制定合适的环境复用性方案;
  • 重构集成验证级别的TLM通讯机制;
  • 对数据流通路上的数据包进行充分的适配。
  • 完全复用单元验证的参考模型,和比对函数。
  • 对单元验证的需求,要预留好集成验证的tlm通讯接口。

2.2 对sequence的重用

       2.1节的例子,如果子模块在整个系统中处于集成的输入,那么直接复用输入子模块的激励源和sequence即可。但是通常情况下,一个复杂的系统中,系统的结构一般都是这样的

        由此可见,A和D,以及F是整个subsys的输入,这个时候,使用一个模块的激励源是不够的,要复用三个模块的激励源进行集成验证,此时就会用到sequence中常用的技术:virtual sequence和virtual sequencer。系统级的virtual sequence可以包含A,D,F的子sequence,将每个子模块的启动,或者验证用例相关的sequence做功能组合,开发virtual sequence,而在virtual sequence中启动,需要用到virtual sequencer中定义的sequencer,这类的集成验证环境,要将virtual sequencer提到chip env级别,做新开发,virtual sequencer中可以包含A,D,F中实际的sequencer,也可以单元验证的virtual sequencer,做sequencer的嵌套,只需要在chip env级的virtual sequencer中的sequencer指针指向各单元env的sequencer即可。

在chip env中重用sequence的挑战:

  • 要将virtual sequencer提到chip env级别做新开发,将chip env中的要作为输入的sequencer实例化到chip env的virtual sequencer中,并在chip env中做指针传递。
  • 如果单元验证环境也涉及到多个sequencer,和virtual sequencer,可以在chip env中的virtual sequencer中嵌套单元验证的virtual sequencer。
  • sequence复用方案以virtual sequence实现,vseq中实例化系统输入相关单元的sequence,并在各自的sequencer上启动。
  • 这样复杂的系统,在复用上需要考虑的不仅是sequence的复用和sequencer的重构,同样要和单元验证env的复用绑定在一起考虑,也就是在2.1节的例子中,再加上对sequence的复用。

2.3 Ral的重用

       一般的,芯片级的总线往往会有总线总裁逻辑,即经过地址的译码,映射到不同的子模块总线接口,进而访问到子模块的寄存器,如图所示:

 

对于每个单元验证环境,总线较为单一,通过ral可以前门或者后门的方式访问到DUT寄存器,但是芯片在chip级重用后,chip级无法知道每个子模块的寄存器地址空间,因此ral需要在chip级重用,首先要将总线驱动模块复用到chip级:

       然后再将子模块的RAL在chip级重用,即通过uvm_reg_block实现子模块ral的嵌套,在chip级reg_block给每个子模块的ral地址分配地址空间,通过default_map.add_submap的方式添加,并在调用configure函数的时候传递后门路径。在chip_env中,需要将各级子模块env的ral模型指针赋值为chip_ral中例化的子模块ral即可。环境就变成如下图所示:

 

对于RAL模型在芯片级的重用,总结:

  • 通过层次化uvm_reg_block技术,将子模块的ral模型例化到chip级的ral中,实例化default_map的时候,将子模块reg_block添加到default_map.add_submap中,并指明各自模块的基地址,在调用子模块ral的configure函数的时候传递后门访问路径。
  • 在chip env中声明chip_ral,并且在build_phase中实例化,且需要将子模块env中的ral指针赋值为chip_ral的子模块ral指针。并调用chip_ral的configure函数,传递后门访问路径,以及lock_model,reset等初始化函数;在connect_phase中设置chip_ral的bus_sequencer和adapter,设置set_auto_predict(1)。
  • 需要将总线的agent激励从子模块env移植到chip_env中,并在chip_env中实例化,通过dut的仲裁逻辑对子模块env进行访问。

 参考资料:《UVM实战》

 

  • 6
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
回答: uvmcallback机制是一种在UVM组件中使用的机制,它允许在特定的操作或任务之前或之后嵌入回调函数。\[1\]\[2\]这个机制的作用是在driver中调用callback接口的地方,循环从一个池子中寻找是否有接口被改写,如果找到了,就会调用改写后的函数,如果池子中没有类,则会调用原始的callback类,一般为空。\[3\]通过使用callback机制,可以在验证环境中创建并登记UVM callback实例,实现一些特定的功能,如错误注入等。 #### 引用[.reference_title] - *1* [【UVM基础CallBack机制快速上手指南](https://blog.csdn.net/ReCclay/article/details/123900059)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [uvm_callback机制](https://blog.csdn.net/weixin_46017929/article/details/107211671)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [UVM基础-Callback机制](https://blog.csdn.net/qq_36955425/article/details/130916042)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值