UVM的sequence机制最大的作用就是将test case和testbench分离开来。 对一个项目而言,testbench是相对稳定的框架,而针对各个module要有不同的测试内容,所以具体的test case 的差异非常大。在UVM中, test和sequence类总是成对出现,实现了testbench和具体的test case的结合。test类中可以针对具体的测试内容对testbench做一些差异化配置,在sequence类中则是实现test case的具体细节。
所以,大的项目大的团队中,做testbench(test 类以及以下的uvm树结构等)的是一个团队,对具体module做test case的是另一团队。UVM sequence机制很好的支持不同团队任务的的分割和结合。
transaction,sequence,sequencer,driver的相互关系参考下图
sequence启动后,会根据参数设置情况,自动执行pre_start(), pre_body(), parent_seq.pre_do(),parent_seq.mid_do(), body(), parent_seq.post_do(), post_body, post_start()等函数/任务。
seq.start (m_sequencer, null, , 1);
// The following methods will be called in start()
seq.pre_start(); (task)
seq.pre_body(); (task) if call_pre_post == 1
parent_seq.pre_do() (task) if parent_seq != null
parent_seq.mid_do(this) (func) if parent_seq != null
seq.body() (task) your code
parent_seq.post_do(this) (func) if parent_seq != null
seq.post_body() (task) if call_pre_post == 1
sub_seq.post_start() (task)
(1)使用start任务.
task my_case0::main_phase(uvm_phase phase);
case0_sequence cseq;
cseq = new("cseq");
cseq.starting_phase = phase;
cseq.start(env.i_agt.sqr);
endtask
(2)使用uvm_config_db#(uvm_object_wrapper)配置default_sequence
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
case0_sequence::type_id::get());
(3)使用uvm_config_db#(uvm_sequence_base)配置default_sequence
function void my_case0::build_phase(uvm_phase phase);
case0_sequence cseq;
super.build_phase(phase);
cseq = new("cseq");
uvm_config_db#(uvm_sequence_base)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
cseq);
endfunction
该示例在uvm1.1可以正常运行,uvm1.2中有问题。
default_sequence最终也是调用sequence的start任务。在uvm_sequence_base基类中的start任务原型是:
virtual task start (
uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1
)
parent_sequence控制这三个任务/函数会不会执行: parent_seq.pre_do() (task),parent_seq.mid_do(this) (func),parent_seq.post_do(this) (func) 。
call_pre_post控制着pre_body,post_body任务的执行与否。
this_priority是发送给sequencer的transaction的优先级值。只能是大于-1的整数。
在同一个sequencer上可以启动多个sequence,每个sequence在启动时可以指定一个priority值,priority值越大优先级越高。设置方法:
`uvm_do_pri(m_trans, 100)
`uvm_do_pri_with(m_trans, 200, {m_trans.pload.size < 500;})
sequencer默认的仲裁算法是SEQ_ARB_FIFO。所有仲裁算法有6种:
a.将仲裁队列中所有transaction的priority值求和得到sum值
b.之后随机产生一个在0到sum之间的一个threshold值
c.从仲裁队列的最前面开始,依次对每个transaction计算priority的加权值(将所有该transaction之前的priority值求和)
d.取加权值最先大于threshold的那个transaction
注意: 在UVM 1.2, 带这些宏“UVM_”前缀; 在 UVM 1.1,不带“UVM_“前缀.
使用sequencer的成员函数set_arbitration来配置优先级算法:
env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);
仲裁算法详细讲解可参考:UVM Tutorial for Candy Lovers – 26. Sequence Arbitration
1. 用lock()和grab()排他性占用sequencer
在sequence的body()中使用lock()...unlock()或grab()...ungrab(), 可以让在其中间发送的所有transaction连续的获取sequencer的仲裁,从而连续的发送到driver而不被别是sequence打断。
二者的区别仅在仲裁机制不一样:
lock会放在仲裁队列的末尾,sequencer将原来就在仲裁队列里的transaction发送完后才执行lock的transaction,一旦lock占用sequencer后,只有unlock后才释放。
grab会放在仲裁队列的最前面,sequencer会立即执行grab的transaction,同样一旦grab占用sequencer后,只有ungrab后才释放。
grab实时性强,是插队操作。unlock则不是。
2. sequence的is_revelant函数和wait_for_relevant任务。
1.uvm_do 相关宏
`uvm_do(SEQ_OR_ITEM)
`uvm_do_pri(SEQ_OR_ITEM, PRIORITY)
`uvm_do_with(SEQ_OR_ITEM, CONSTRAINTS)
`uvm_do_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS)
//uvm_do_on 发到指定的sequencer
`uvm_do_on(SEQ_OR_ITEM, SEQR)
`uvm_do_on_pri(SEQ_OR_ITEM, SEQR, PRIORITY)
`uvm_do_on_with(SEQ_OR_ITEM, SEQR, CONSTRAINTS)
`uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS)
2. uvm_create及uvm_send系列宏
`uvm_send_pri(SEQ_OR_ITEM, PRIORITY)
`uvm_rand_send(SEQ_OR_ITEM)
`uvm_rand_send_pri(SEQ_OR_ITEM, PRIORITY)
`uvm_rand_send_with(SEQ_OR_ITEM, CONSTRAINTS)
`uvm_rand_send_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS
3. start_item和finish_item任务。
create_item(item) \
sequencer.wait_for_grant(prior) (task) \ start_item \
parent_seq.pre_do(1) (task) / \
`uvm_do* macros
parent_seq.mid_do(item) (func) \ /
sequencer.send_request(item) (func) \finish_item /
sequencer.wait_for_item_done() (task) /
parent_seq.post_do(item) (func) /
嵌套(nested)即在sequence的body中使用另外已经定义好的sequence。当然需要他们均有一致的transaction类型。执行顺序参考下图。seq2在seq1的body中启动,seq1.start()中参数parent_sequence为空,seq2.start()中参数parent_sequence为this。
常用的嵌套,也称作层次化(sequence hierarchy), 在实践中会广泛用到。 通常对简单基本的测试场景放在一些基础sequence里,复杂场景的sequence均有许多基础sequence组合而成。如下图所示:
正常一种sequence->sequencer->driver只支持一种类型的trans,如果要在同一sequence中向sequencer发出不同类型的trans,可以:
1)sequencer和driver声明时要使用基类uvm_sequence_item
2)在driver中,需要将从seq_item_port收到的req使用cast进行向下类型转换。
继承:一般把公用的函数或任务放在一个base_sequence,其他sequence均从它派生出来。
sequence类里有一个uvm_sequencer_base类型m_sequencer指针,当sequence和sequencer关联后,m_sequencer会自动指向该sequencer,但通过m_sequencer不能直接使用seqr里的变量,否则会出现编译错误。只能使用cast强制向子类转换后,才能通过m_sequencer.xxx来访问该seqr内的xxx变量。
UVM引入p_sequencer,可以自动的实现上面所述的cast动作,从而可以在sequence中自由使用关联sequencer内的变量。
p_sequencer并不是UVM自动地在sequence的创建的,需要用户使用`uvm_declare_p_sequencer宏声明,之后UVM会自动实现指向seqr及cast的动作。
在实际应用中,dut往往是很复杂的系统,不单单只有一种接口。而我们testbench中的driver只能驱动一种接口,对应一种transaction的sequence。如果需要对多个接口同时进行激励,就需要的virtual sequence/sequencer。
Virtual sequencer 的特点:含有sub sequencer的句柄,用以控制这些sub sequencer。它并不和任何driver相连 Virtual sequencer,本身并不处理具体的trans/seq。
Virtual sequence 的作用:和virtual sequencer相关联的就是virtual sequence,它的作用是协调不同的subsequencer中sequence的执行顺序。
Virtual 的含义: 这里的virtual 区别用 system Verilog中用在function/task/class声明前,用于修饰的virtual。virtual sequence/sequencer的virtual主要是指这种sequence/sequencer不像直接作用在具体driver上的sequence/sequencer,它不处理具体的transaction,主要是来做不同类型sequence间的控制和调度的。
1 :定义virtual sequencer,里面包含各个env里的子sequencer类型的指针。
2 :在base_test里实现virtual sequencer的例化,和sub sequencer的连接。
> base_test作为uvm_test_top,即uvm树形结构的最顶层,负责chip_env和virtual sequencer的规划。
> 实例化virtual sequencer。
> 将virtual sequencer与各env的sequencer连接在一起,具体实现是通过function connect_phase中将virtual sequencer的中个sub sequencer的指针,指向各个具体的sequencer。
3 :定义virtual sequence。
> 在里边对多个sequence实例化。
> 要声明指向对应的virtual sequencer的p_sequencer,用于使用virtual sequencer中的sub sequencer句柄。
> 利用`uvm_do_on类型宏,在指定的sub sequencer上运行具体的sequence。
注意:`uvm_do这样的宏,针对的处理对象,不仅仅是transaction,还可以处理sequence。
4 :在具体test定义中,利用uvm_config_db将virtual sequencer的default_sequence设置为具体的virtual sequence。
除了在uvm树中的component中用config_db外,在sequence中也可以用,虽然sequence是一个uvm_object类。用config_db时,关键在path的参数,一个sequence的完整路径,均是有该sequence关联的sequencer和该sequence的实例对象名组成。例如如:
sequencer的路径 + sequence实例名
uvm_test_top.env.a_agent.a_sequencer.test0_sequence
1. 在test中设置sequence中参数count如:
uvm_config_db#(int)::set(this, "env.i_agt.sqr.*", "count", 9);
注意: 由于sequence实例化名字不固定,路径中对应的sequence实例化名字要使用通配符。
2. 在sequence中使用get:
uvm_config_db#(int)::get(null,get_full_name(),"count",count);
注意:其中第一个参数不能使用this指针,因为sequence不是component,是能使用null/uvm_root::get();
1. 在sequence的body中set参数:
uvm_comfig_db#(bit)::set(uvm_root::get(),"uvm_test_top,env.scb","en_compare",1);
2. 在component/sequence中get的方法:
由于component都是在build phase中调用get函数,如果需要在main phase中get该参数,就需要使用wait_modified任务不停检测参数的变化,然后再使用get获取参数。
fork
//在这个进程中不停检测参数的变化
while(1) begin
uvm_comfig_db#(bit)::wait_modified(this,“”,“en_compare”)
void`(uvm_config_db#(bit)::get(this,"","en_compare",en_compare));
end
// 该component的主体内容
begin
......
end
join
1. 在sequence中使用uvm_do后,使用get_reponse(rsp)获得从driver返回的reponse
2. 在driver中,返回一个rsp的方法:
while(1) begin
seq_item_port.get_next_item(req);
drive_it_to_vif(req);
rsp = new("rsp") // 创建 rsp
rsp.set_id_info(req);// 将req的id等信息复制给rsp
seq_item_port.put_response(rsp); // 返回rsp
seq_item_port.item_done(); // 也可省掉put_reponse的调用,直接使用item_done返回,如:
// seq_item_port.item_done(rsp)
end
driver可以一次返回多个response,要在driver中多次调用put_response,在sequence中多次调用get_response.
sequencer内部为rsp维护一个队列,默认深度为8,所以多次返回超过8各rsp,有溢出的风险。
response的类型默认是和request的类型一样的,如需返回不同类型的response,需要在声明sequence->sequencer->driver类时传入req和rsp两种参数类型。
在sequence中使用get_response获取rsp,是阻塞的方式。如需非阻塞方式获取,需要使用response_handler,它会自动的在另外一进程中接受response.
1.在sequence中的pre_body()中使用use_reponse_handler()打开该功能。
2.重载response_handler函数(类似中断服务程序,需要写入对rsp处理的内容),注意:要在里面使用cast强制类型转换
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
1.声明seq lib时要指明所产生的transaction类型,
2.seq lib 的new函数中要调用init_sequence_library
3.要调用uvm_sequence_library_utils将seq lib注册。
4.在单个sequence中要使用宏uvm_add_to_seq_lib将其加入指定seq lib中。
5.可以一对多,也可多对一
6.将seq lib设为sequencer的default sequence
其他将seq加入seq lib的方法可参考: UVM Sequence Library - Usage, Advantages, and Limitations
UVM_SEQ_LIB_RAND: 完全随机
UVM_SEQ_LIB_RANDC: random cycle order,先随机排序,再按顺序执行,再保证每个seq执行一遍后,执行剩余次数。
UVM_SEQ_LIB_ITEM: 不执行里面的seq,而是自己产生trans,等同于一普通seq
UVM_SEQ_LIB_USER: 用户自定义,需重载select_sequence函数。
使用config_db 配置方法:
uvm_config_db#(uvm_sequence_lib_mode)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence.selection_mode",
UVM_SEQ_LIB_RANDC);
seqence library和sequence,也是调用start任务执行,只后顺序执行seq lib的pre_start,pre_body, body,post_body,post_start。在seq lib的body中,按照配置的random的方式启动各个sequence。在调用start任务启动sequence时,给参数call_pre_post传入0值,因此每个sequence的pre_body 和 post_body不会运行。
参考: