在uvm中加入了一个重要的机制即sequence机制用于产生激励将trans、driver的功能进一步简单化,以实现最大程度的复用。其中sequence机制包括sequence和sequencer。
一、sequence机制实现通信
1、第一步
创建sequence类继承于uvm_sequence,创建sequencer类继承于uvm_sequencer代码如下:
class my_sequence extends uvm_sequence #(my_trans);
`uvm_object_utils(my_sequence)
my_trans tr;
function new(string name = “my_sequence”);
super.new(name);
endfunction
virtual task body();
repeat(10) begin
`uvm_do(tr); //start sequence
end
#1000;
endtask
endclass
class sequencer extends uvm_sequencer#(my_trans);
`uvm_component_utils(my_sequencer)
function new(string name ="my_sequencer");
super.new(name);
endfunction
endclass
我们可以看到sequence是属于object,而sequencer属于component,并且在其中没有进行连接
2、第二步
在顶层例化并连接
- 完整的传输路径是sequence产生一个随机化过的trans,将其发送到sequencer中,sequencer将其送到driver中,driver再将其驱动到dut,这样一个完整的数据激励产生并传输的路径便已经完整。因此我们需要将trans与sequence连接,sequence与sequencer连接,sequencer与driver分别连接。
如代码一所示在sequence中例化trans并传参进入sequence,sequence在顶层通过启动start函数可以与对对应的sequencer连接(也可以通过设置sequencer的default_sequence以及使用p_sequencer指向对应的sequencer,再start调用的方式),同时在顶层也会通过内置的port端口完成drv与sqr的连接,示例代码如下:
function void my_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
if(is_active == UVM_ACTIVE) begin
drv.seq_item_port.connect(sqr.seq_item_export); //注意顺序,port在前
end
endfunction
task my_env::main_phase();
my_sequence seq;
phase.raise_objection(this);
seq = my_sequence::type_id::create("seq");
seq.start(my_agent.aqr);
phase.drop_objection(this);
endtask
3、第三步
实际传输
- 实际传输过程中,seq向sqr发送数据前会先发送请求,sqr会将该请求放入仲裁队列中,sqr会时刻检测drv以及seq是否发送请求,当drv以及seq一起发送请求时,数据传输开始,当只有seq发送请求时,sqr会将该请求放入仲裁队列中并等待drv的请求在开始传输数据,而只要收到drv的请求,sqr立马进入等到seq请求的状态,seq请求一到立刻开始传输。
seq请求产生的方式为启动,而drv通过seq_item_port.get_next_item(req)来产生请求,实际关系如下图:
二、sequence启动方式
2.1 start手动启动
配合`uvm_do宏或者p_sequencer挂载到start的m_sequencer中即可启动seq到指定的seq,或者只执行start_item finish_item
如上面代码所示,可以通过例化seq并挂载到sqr的方式进行手动启动:
my_sequence seq;
phase.raise_objection(this);
seq = my_sequence::type_id::create("seq");
seq.start(my_agent.aqr);
phase.drop_objection(this);
- sequence.strat(sequencer,parent_sequence,优先级) ,第一个参数是需要挂载的sequencer;第二个是parent_sequence,一般传入this或者不传入;第三个是优先级;第四个call_pre_post默认为1,则自动执行pre_body/ post_body()函数
- start 执行 pre_start,body等函数。此时就完成了sequence的启动过程。
- 所有sequence都要在sequencer中启动,当sequence启动的时候, 在sequence的 task start 内,会调用 set_item_context() 函数,在 set_item_context() 函数内,会调用 set_sequencer() 函数完成挂载,给m_sequencer赋值
- set_sequencer 函数 会使m_sequencer 句柄指向执行当前 sequence 的 sequencer,即指向 env.vsqr
- 如果没有指定挂载的sequencer,则挂载到parent_sequence的sequencer上
其中`uvm_do宏分为:
参考(UVM基础-sequence系列宏_uvm_do_Lucky是一名ICer的博客-CSDN博客)
这个系列宏实际上集成度较为高,因为宏定义并没有给出seq/tr在哪个sequencer上启动,实际上,该系列宏都是从默认的m_sequencer上启动的,也就是通过seq.start函数传递的sqr指针。
uvm_do更多的是用在一个父sequence对子sequence的启动上:父sequence的do函数会被执行,同时不需要执行子sequence的pre_body()和post_body()。
这些宏可以写在seqeunce中的body函数里,用于发送tr或者做sequence的嵌套使用。
2.2 `uvm_do_on
`uvm_do_on系列宏可以指定sequence或者transaction在哪个sqr上启动,特别是如果系统中包含多个sequencer,顶层通过virtual sequencer控制,可以通过在sequence中使用`uvm_declare_p_sequencer(my_sqr)来指定sequence的启动位置(此时p_sequencer指向my_sqr,可以调用其中内容)。`uvm_do_on系列宏包括:
事实上,无论是`uvm_do系列宏还是`uvm_do_on系列宏,底层都是`uvm_do_on_pri_with这个参数最全的宏定义,不一样的宏分别对这个宏做了参数固化,比如`uvm_do:
`uvm_do_on_pri_with(tr_or_seq, m_sequencer, -1, {})
此时,sequencer默认传递为m_sequencer,优先级为-1,即不具备优先级,约束为空,不具备约束。同理,其他宏也在此宏定义上做了参数的固化:
最后,我们来分析一下`uvm_do_on_pri_with的源码:
这段源码先申明了一个uvm_sequence_base类型的_seq,然后通过`uvm_create_on宏将其实例化出来,并指定在参数SEQR上实例化(指定m_sequencer为SEQR),然后有一个if条件,思考一下,如果SEQ_OR_ITEM参数此时如果是transcation,会发生什么?
这个问题的答案很简单,如果是transaction,cast会返回0,条件成立,执行start_item,此时start_item参数为指定的transaction,并设定优先级。为什么cast会为0?还记得transaction继承自哪个类吗?实际上,transaction继承自uvm_sequence_item,也就是uvm_sequence_base的父类,因此父类的继承类cast给子类的继承类,当然无法cast,返回0。
中间判断了如果transaction执行不了randomize,会报warning,可以不用管它,然后又是一段判断,条件和上一个if一致,如果是transaction,执行finish_item,指定transaction和优先级。如果参数是seq,那么else分支会调用seq的start函数。
分析过后,实际上可以得到一个总结:
- `uvm_do系列宏的底层是`uvm_do_on_pri_with宏,只不过将参数进行了固化;
- `uvm_do_on_pri_with宏会实际上是调用start_item和finish_item方法,并且只有在参数是transaction的时候会调用。
- 如果参数是sequence,会调用sequence的start任务,start任务会接着调用sequence的pre_body,body,post_body,如果子sequence的body中的uvm_do系列宏传递的是transaction,那么会调用start_item和finish_item。
- 如果参数是sequence,最终会调用transaction为参数的uvm_do系列宏,并落在start_item和finish_item任务上。
- start_item和finish_item任务的参数只能是transaction,不能是sequence。
事实上,除了uvm_do系列宏之外,sequence还提供了专门用于发送sequence系列宏,主要包括:uvm_do_seq,uvm_do_seq_with,这两个宏底层是uvm_do和uvm_do_with宏,对其参数进行了固化,并且只能传参seq,并且需要指定sqr。用的比较少,不过多讨论。
2.3 default_sequence自动启动
est中将sequence通过config_db机制set到相应的sequencer(virtual sequencer)的main_phase中
在build_phase中将某个sequence配置成某个sequencer的动态运行的phase中(如main_phase)的default_sequence, 那么在仿真执行到main_phase的时候,squencer的default_sequence就会启动
// 方式一
function void my_case0::build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_config_db#(ubm_object_wrapper)::set(this, // 第一个参数是指定seqr的路径
"env.in_agent.sqr.main_phase", // 第二个参数是指定要执行的phase阶段
"default_sequence", // 第三个参数是指定你的seq
sequence_base::type_id::get()); // uvm_config_db设置default_sequencer
endfunction
// 方式二
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
最后谈一下default_sequence的启动方式。这种启动方式来自OVM,通过将某个sequence配置为default_sequence,可以使得该sequence在仿真开始之后被自动启动。
之所以不建议用default_sequence,大致有这么一些原因:
- 设置default_sequence是在connect_phase或者build_phase中完成,这就意味着将测试用例跟Testbench的构建混在了一起,在环境架构上不干净;
- 使用default_sequence隐藏了太多信息,比如用户并不是很明确sequence在哪个时候被启动,甚至不知道是不是被其他同事配置了default_sequence,一旦出问题很难调试;
- 无法在同一个sequencer上启动其他sequence,而这个需求又是经常存在的。
三、uvm宏使用
3.1 uvm_create以及uvm_send
3.1.1 uvm_create以及uvm_create_on
除了使用uvm_do系列宏可以发送激励之外,uvm还提供了`uvm_create或者`uvm_create_on,用于生成transaction的实例,然后结合`uvm_send宏将transaction发送出去。`uvm_create(seq_or_item),参数是sequence或者transaction,`uvm_create_on(seq_of_item, SEQR),参数有两个,除了要传输的sequence或者transaction之外,还有一个sequencer的指针。实际上,`uvm_create宏的底层就是`uvm_create_on宏:
这个宏先申明了一个uvm_object_wrapper的变量w_,这个类和factory机制有关,然后通过调用SEQ_OR_ITEM的get_type函数返回uvm_object_wrapper的指针,赋值给w_,然后通过cast方法,调用create_item函数,将实例化的类赋值给SEQ_OR_ITEM,那么关键在于create_item:
这个函数位于基类uvm_sequence_base中,会先声明uvm_factory,然后通过create_object_by_type的方式实例化类,实际上是通过类名字符串进行创建实例,并cast给create_item返回值。因此`uvm_create的创建方式,实际上是调用了factory机制进行实例创建,因此是支持factory机制的重载的。
那么也可以给出总结:
- `uvm_create的底层是`uvm_create_on,对其参数进行了封装
- `uvm_create的逻辑是通过factory机制创建sequence或者item的实例,支持sequence的重载和item的重载
- 用这个宏的方式创建实例,等价于seq_or_item::type_id::create(“name”)创建实例的方式。
- 如果使用new函数创建seq或者item的实例,那么不支持重载。
另外,还有一个专门用于生成seq的宏:uvm_create_seq,底层是uvm_create_on宏,对参数做了固化,用的比较少,不做过多讨论。
3.1.2 uvm_send以及uvm_send_pri
这个宏定义用于发送sequence或者item,用法是直接将seq或者item作为宏的参数传递给`uvm_send,`uvm_send_pri宏除了要传递seq或者item之外,还要指明优先级,实际上,`uvm_send的底层是`uvm_send_pri,只不过优先级传参为-1:
分析一下`uvm_send_pri的源码:
这个宏和uvm_do系列宏的逻辑一样,如果是item会调用start_item和finish_item,如果是sequence,会调用sequence的start函数。
值得注意的是uvm_send宏不会对transaction进行随机化,因此如果有随机需求,需要在宏外调用randomize对tr或者seq进行随机,然后再使用uvm_send。
3.1.3 uvm_rand_send
这个系列宏可以对transaction进行随机化,并发送,也就是在uvm_send的基础上集成了seq或者tr的随机。这个系列宏有四个:
事实上,这几个宏的底层都是`uvm_rand_send_pri_with,其他宏在其基础上做了参数固化,对于`uvm_rand_send_pri_with的源码分析,逻辑和其他的宏一样,只不过这个宏除了调用start_item和finish_item之外,还需要调用get_sequencer来获取sequencer,因此,这一系列宏需要和uvm_create系列宏配合使用。
3.2 start_item和finish_item
如果不想用sequence的宏来声明产生transaction的调度,用户也可以直接调用start_item和finish_item方法,启动transaction的调度,需要注意的是,这个方法只能用来传输transaction,不能用来传输sequence,而且在传输transaction之前,需要先实例化transaction,无论是直接调用new函数的方式,还是调用factory机制实例化。
transaction可以进行随机,需要单独写出来,因为start_item和finish_item不具备随机功能。可以写在两个方法之前,也可以写在两个方法中间。
start_item和finish_item可以指定优先级,作为第二参数传递,如果不指定优先级,那么优先级参数默认为-1,即不具备优先级。通过一段代码说明这个的用法,假设有sequence:
1. class my_sequence extends uvm_sequence(uvm_sequence_item);
2. `uvm_object_utils(my_sequence)
3. `uvm_declare_p_sequencer(my_sequencer)
4. ......
5. virutal task body();
6. my_transaction my_tr;
7. my_tr = new("my_tr");
8. //my_tr = my_transaction::type_id::create("my_tr");
9. //`uvm_create(my_tr) or `uvm_create_on(my_tr, p_sequencer);
10. assert(my_tr.randomize() with {my_tr.data_size==200;});
11. start_item(my_tr);
12. //start_item(my_tr, 200); //指定优先级
13. //assert(my_tr.randomize() with {my_tr.data_size==200;});//随机化可以写在中间
14. finish_item(my_tr);
15. //finish_item(my_tr, 200);//指定优先级
16. endtask:body()
17. ......
18. endclass