前言:
本文基于《UVM实战》学习所得,留痕
1.浅谈测试用例中的sequence、callback、factory机制
从某种程度上来说,callback、sequence、factory机制,都可以搭建测试用例。但是有什么区别呢?
1.1 sequence机制:
UVM一直提倡的生成激励的方式,UVM为此做了大量的工作,如构建了许多宏、嵌套的sequence、virtual sequence、可重用性等。
1.2 callback机制:
能够实现所有的测试用例,但是某些测试用例用 sequence来实现则更加方便。virtual sequence的协调功能在callback机制中就很难实现。
1.3 factory机制:
重载driver使得一些在sequence中比较难实现的测试用例轻易地在driver中实现。如果放弃sequence,只使用factory机制实现测试,似乎一切都回到了起点(在driver中控制发送包的种类、数量,对于objection的控制又 要从sequence中回到driver中)
不推荐单独使用callback机制、factory机制:
- ·引入sequence的原因是将数据流产生的功能从driver中独立出来。取消sequence相当于一种倒退,会使得driver的职能不明 确,与现代编程中模块化、功能化的趋势不合。
- ·虽然用driver实现某些测试用例比sequence更加方便,但是对于另外一些测试用例,在sequence里做起来会比driver中更加方 便。
- ·sequence的强大之处在于,它可以在一个sequence中启动另外的sequence,从而可以最大程度地实现不同测试用例之间 sequence的重用。但是对于driver来说,要实现这样的功能,只能将一些基本的产生激励的函数写在基类driver中。用户会发现到最 后这个driver的代码量非常恐怖。
- ·使用virtual sequence可以协调、同步不同激励的产生。当放弃sequence时,在不同的driver之间完成这样的同步则比较难。
总结:
callback机制、sequence机制和factory机制并不是互斥的,三者都能分别实现同一目的。三者互相结合时,将产生许多新的解决问题的方式。如果在建立验证平台和测试用例时,能够择优选择其中最简单的一种实现方式,那么搭建出来的验证平台 一定是足够强大、足够简练的。实现同一事情有多种方式,为用户提供了多种选择,高扩展性是UVM取得成功的一个重要原因。
2.细说测试用例中的sequence、callback、factory机制使用套路
2.1 callback机制
对于VIP的开发者来说,预留一个callback函数/任务接口时需要做以下几步:
- 定义一个A类
- 声明一个A_pool类
- 在要预留callback函数/任务接口的类中调用uvm_register_cb宏
- 在要调用callback函数/任务接口的函数/任务中,使用uvm_do_callbacks宏
对于VIP的使用者来说,需要做如下几步:
- 从A派生一个类,在这个类中定义好pre_tran
- 在测试用例的connect_phase(或者其他phase)中将从A派 生的类实例化,并将其加入A_pool中
2.1.1 VIP开发者的操作过程:
通过代码注释说明
//callbacks.sv
//1.定义一个A类
//A类一定要从uvm_callback派生,
//另外还需要定义一个pre_tran的任务,此任务的类型一定要是virtual的,
//因为从A派生的类需要重载这个任务。
class A extends uvm_callback;
virtual task pre_tran(my_driver drv, ref my_transaction tr);
endtask
endclass
//2.声明一个A_pool
//A_pool的声明,只需要一个typedef语句即可。
//在这个声明中
//要指明这是一个A类型的池子,和这个池子将会被哪个类使用
typedef uvm_callbacks#(my_driver, A) A_pool;
//my_driver.sv
typedef class A;
class my_driver extends uvm_driver#(my_transaction);
virtual my_if vif;
`uvm_component_utils(my_driver)
//3.在要预留callback函数/任务接口的类中调用uvm_register_cb宏
//这个声明与A_pool的类似,要指明 my_driver 和 A
`uvm_register_cb(my_driver, A)
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
endfunction
extern task main_phase(uvm_phase phase);
extern task drive_one_pkt(my_transaction tr);
endclass
task my_driver::main_phase(uvm_phase phase);
vif.data <= 8'b0;
vif.valid <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
while(1) begin
seq_item_port.get_next_item(req);
//4.在要调用callback函数/任务接口的函数/任务中,使用uvm_do_callbacks宏
//uvm_do_callbacks宏
//第一个参数是调用pre_tran的类的名字,这里是 my_driver,
//第二个参数是哪个类具有pre_tran,这里是 A,
//第三个参数是调用的是函数/任务,这里是 pre_tran,在指明是pre_tran时,要顺便给出pre_tran的参数
`uvm_do_callbacks(my_driver, A, pre_tran(this, req))
drive_one_pkt(req);
seq_item_port.item_done();
end
endtask
task my_driver::drive_one_pkt(my_transaction tr);
byte unsigned data_q[];
int data_size;
data_size = tr.pack_bytes(data_q) / 8;
//`uvm_info("my_driver", "begin to drive one pkt", UVM_LOW);
repeat(3) @(posedge vif.clk);
for ( int i = 0; i < data_size; i++ ) begin
@(posedge vif.clk);
vif.valid <= 1'b1;
vif.data <= data_q[i];
end
@(posedge vif.clk);
vif.valid <= 1'b0;
//`uvm_info("my_driver", "end drive one pkt", UVM_LOW);
endtask
2.1.1 VIP使用者的操作过程:
//my_case0.sv
class case0_sequence extends uvm_sequence #(my_transaction);
my_transaction m_trans;
function new(string name= "case0_sequence");
super.new(name);
endfunction
virtual task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (10) begin
`uvm_do(m_trans)
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask
`uvm_object_utils(case0_sequence)
endclass
//1.从A派生一个类,在这个类中定义好pre_tran
class my_callback extends A;
virtual task pre_tran(my_driver drv, ref my_transaction tr);
`uvm_info("my_callback", "this is pre_tran task", UVM_MEDIUM)
endtask
`uvm_object_utils(my_callback)
endclass
class my_case0 extends base_test;
function new(string name = "my_case0", uvm_component parent = null);
super.new(name,parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
`uvm_component_utils(my_case0)
endclass
function void my_case0::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
case0_sequence::type_id::get());
endfunction
//在测试用例的connect_phase中将从A派生的类实例化,并将其加入A_pool中
//(也可以在build_phase中例化,但是一定要在前面callback函数/任务的phase之前)
function void my_case0::connect_phase(uvm_phase phase);
my_callback my_cb;
super.connect_phase(phase);
my_cb = my_callback::type_id::create("my_cb");
A_pool::add(env.i_agt.drv, my_cb);
endfunction
2.2 factory机制
factory机制重要的一点是提供重载功能。一般来说,如果要用B类重载A类,那么B类是要派生自A类的。在派生时,要保留A 类的大部分代码,只改变其中一小部分。
2.3 sequence机制
放弃建造强大sequence的想法,尽量做到一看见名字就可以知道这个sequence的用处。