UVM的hook与callback
🚛

UVM的hook与callback

Tags
IC验证
基础知识
Published
June 29, 2024
Author
内容摘要
标签
是否记得
notion image

Hook 函数和 Callback 机制

Hook 函数可以理解为一段在特定地点处理特定事件的程序,各种各样预先设定好的钩子函数被挂载在 UVM 中。这些钩子函数可以加工处理事件,也可以不做任何事情,但是它们在验证平台的执行过程中总是会被调用到。寄存器模型的基类提供了多种钩子函数(pre_writepost_writepre_readpost_read 等),这些函数会在寄存器操作的不同阶段被调用。如果验证人员不去赋给这些函数具体的任务和操作,那么它们不会处理任何事情。也就是说这些钩子函数在特定的时间和地点被调入到程序中且一定会被执行,但是执行内容由验证人员来填补。 Callback 机制是继承自 C 语言的概念,是指将某个函数的指针作为参数传递给另外一个函数,当这个指针被用来调用其所属的原函数时,就称其为回调函数。这种机制可以将调用者和被调用这分离,使得代码更为灵活。与钩子函数不同的是,回调函数并不会随着系统函数的执行而自动被执行。
知乎上有一个回答:
Callback 是一种异步调用的实现,打个比方,你希望让你的异地的同事帮助你完成某项工作,为了保证你们业务衔接流畅,你留了你的电话让他有进展和你联系(即Callback函数),那么随着事情的推进,你的同事会不断的打电话跟进让你知道那边的情况,这就是一种异步回调;Callback 本意就是你传递一个函数给对方,当对方的工作有进展的时候就调用这个函数通知你
Hook 则是一种 API 拦截手段,就比如一个公司有两个部门,他们平时会各指派一个业务对接人(API接口)去沟通彼此工作;但是某天因为业务调整,他们之间有一些事务需要外包出去,外包的事项是由你临时代为沟通,两边的对接人都不知道具体详情;为了避免混乱,此时你被作为一个中间人介入,切断原来两个部门之间的联系,他们的联络人都先和你沟通,由你决定哪些事情需要外包,哪些事情需要转达到另一个部门,这时候你就是一个Hook函数,拦截了原有的调用,并重新决定下一步的做什么,如果要继续原有流程也需要你代为转达。Hook的特点就是不改变原有双边的逻辑的情况下,在API接口上插入一个拦截调用的Hook函数,从而截取调用数据、甚至可以改变程序行为。
对于callback,我们可以举一个例子:
// 定义my_driver类型 typedef class my_driver; // 定义pkt_process_callback类,继承自uvm_callback class pkt_process_callback extends uvm_callback; ... // 定义一个虚拟函数pkt_pre_trans,用于数据包预传输处理 virtual function void pkt_pre_trans(my_driver drv, my_transaction tr); endfunction endclass // 定义my_driver类,继承自uvm_driver,并指定其传输的数据类型为uvm_sequence_item class my_driver extends uvm_driver#(uvm_sequence_item); // 声明一个虚拟接口vif virtual my_if vif; // 使用uvm_component_utils宏注册组件 `uvm_component_utils(my_driver) // 使用uvm_register_cb宏注册pkt_process_callback回调函数 `uvm_register_cb(my_driver, pkt_process_callback) // 构造函数 function new(string name="my_driver", uvm_component parent); super.new(name, parent); endfunction // 构建阶段函数 virtual function void build_phase(uvm_phase phase); super.build_phase(phase); // 从配置数据库中获取虚拟接口vif if(!uvm_config_db#(virtual my_if)::get(this, "", "interface", vif)) `uvm_fatal(get_type_name(), "vif is none!"); endfunction // 运行阶段函数 virtual task run_phase(uvm_phase phase); super.run_phase(phase); while(1) begin // 从seq_item_port获取下一个事务项 seq_item_port.get_next_item(tr); // 假设这里应有对tr的处理,但原代码有误,未展示 // 调用pkt_process_callback的pkt_pre_trans函数 uvm_do_callbacks(my_driver, pkt_process_callback, pkt_pre_trans(this, tr)); // 假设的驱动数据包函数,原代码未实现 drv_pkt(); // 通知seq_item_port事务项已完成 seq_item_port.item_done(); end endtask // 假设的驱动数据包任务,原代码未实现完整 virtual task drv_pkt(); endtask endclass
// 定义类型别名 typedef class my_driver; typedef class my_transaction; // 定义my_callback类,继承自pkt_process_callback class my_callback extends pkt_process_callback; // 定义pkt_pre_trans函数,用于处理数据包传输前的回调 ... function void pkt_pre_trans(my_driver drv, my_transaction tr); // 加载数据到transaction对象中 tr.data_load = 32'h555aaa; // 注意:原图中为32'h555aaa,模型答案中错误地更改为32'h053453453 // 使用UVM信息打印功能 uvm_info(get_type_name(), "my callback is ok"); endfunction: pkt_pre_trans endclass // 定义my_tc类,继承自base_test class my_tc extends base_test; // 声明my_callback类型的对象 my_callback my_cb; // 使用UVM宏注册组件 `uvm_component_utils(my_tc) // 构造函数 function void new(string name="my_tc", uvm_component parent); super.new(name, parent); endfunction: new // 构建阶段 virtual function void build_phase(uvm_phase phase); super.build_phase(phase); // 创建my_callback对象 my_cb = my_callback::type_id::create("my_cb"); endfunction: build_phase // 连接阶段 virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); // 将my_callback对象添加到my_driver的回调列表中 uvm_callbacks#(my_driver, pkt_process_callback)::add(env.i_agt.drv, my_cb); endfunction: connect_phase endclass
参考知乎的文章:,callback在uvm中是一个内置的类,我们想要使用它可以直接从uvm_callback中继承。
那大体的使用流程是什么呢?
首先,定义自己的callback类:
class my_callback extends pkt_process_callback; // 定义pkt_pre_trans函数,用于处理数据包传输前的回调 ... function void pkt_pre_trans(my_driver drv, my_transaction tr); // 加载数据到transaction对象中 tr.data_load = 32'h555aaa; // 注意:原图中为32'h555aaa,模型答案中错误地更改为32'h053453453 // 使用UVM信息打印功能 uvm_info(get_type_name(), "my callback is ok"); endfunction: pkt_pre_trans endclass
然后,需要在想要调用callback类的地方进行组件注册,比如,这里在my_driver中注册callback:
`uvm_component_utils(my_driver) `uvm_register_cb(my_driver, pkt_process_callback)
此外,在具体要使用到callback的地方调用这个callback类,比如,这里我们还要在run_phase阶段打印一些数据,就可以:
uvm_do_callbacks(my_driver, pkt_process_callback, pkt_pre_trans(this, tr));
在顶层的环境中,如果我们需要把两个模块连接起来,并使用callback,就也需要在connect_phase阶段添加callback对象:
uvm_callbacks#(my_driver, pkt_process_callback)::add(env.i_agt.drv, my_cb);
至此,我们就实现了在特定位置插入callback回调机制,增加自己需要的功能。