文章目录
总览
在测试中,test 调用 sequence,并将 sequence 传递给 sequencer。sequencer 生成事务并将它们传递给 driver,driver 将事务转换为信号和数据,并将它们发送到 DUT 中。DUT 处理信号和数据,并将响应发送回 driver。driver 接收响应并将其转换为事务,然后将其传递给 sequencer。sequencer 接收事务并返回给 test。
总的来说,sequence 在 UVM 中的工作流程如下:
test 调用 sequence。
sequencer 生成事务并将其传递给 driver。
driver 将事务转换为信号和数据,并将其发送到 DUT 中。
DUT 处理信号和数据,并将响应发送回 driver。
driver 接收响应并将其转换为事务,然后将其传递给 sequencer。
sequencer 接收事务并返回给 test。
sequence的启动(调用)方式
sequence怎么启动,即sequence传递给哪个sequencer,或者sequencer调用哪个sequnce的body;
sequence的启动方式有显示启动和隐士启动两种套路,但其本质一样。
- 显式启动(直接启动)——调用start()方法启动。
在base_test中启动
class base_test extends uvm_test;
my_env env;
...
endclass
task base_test::main_phase(uvm_phase);
base_sequence seq;
seq = base_sequence::type_id::create("seq");
seq.start( env.agt.sqr);
endtask
...
- 隐式启动 ——使用uvm_config_db机制配置default_sequence启动。
class base_test extends uvm_test;
my_env env;
...
endclass
function base_test::build_phase(uvm_phase phase);
super.build_phase(phase);
base_sequence seq = new("seq");
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.agt.sqr.main_phase",
"default_sequence",
seq);
endfunction
sequence与sequencer中的数据结构
如上图所示,
sequence中包含sequencer的句柄;
sequencer中包含sequence的句柄;
所谓的sequence启动,就是将sequence中的sequencer句柄执行sequencer对象,将sequencer中的sequence句柄指向sequence对象。
完成连接后,sequencer就可以调用sequence中的方法,sequence就可以调用sequencer中的方法,从此双方就可以相互通信。
virtual sequencer, p_sequencer, m_sequencer
为什么要引入virtual sequencer?
如上图所示,env中引入了Vsqr, 包含sqrA和sqrB。
引入Vsqr,本质是可以进一步去耦合,在一个Vsqr中将所有的sqr管理在一起,方便查找/修改/调用等等。
如果没有Vsqr,想要查找sqrA,则需要通过xxx.env.agtA.sqrA的路径找到sqrA,但当有一个Vsqr后,我们就可以直接通过Vsqr.sqrA直接找到sqrA。
因此virtual sequencer是一个管理思想,引入后,无需关心每个sqr的具体路径,具体名字等等,做到了去耦合。
注意,Vsqr中的sqrA和agtA中的sqrA要指向同一个实体,否则就乱套了,因此要在connect_phase中(也就是上图中的第0步),做以下连接
// step 0
Vsqr.sqrA = agtA.sqrA;
Vsqr.sqrB = agtB.sqrB;
为什么要引入m_sequencer?
sequence中藏了一个sequencer基类指针,指向sequencer,该指针就是m_sequencer。
class uvm_sequence_item extends uvm_transaction;
...
protected uvm_sequencer_base m_sequencer;
...
endclass
m_sequencer的连接过程是在sequence启动过程中完成连接的。(手动启动,或者自动启动时)
// step 1 , sequence启动时,完成sequencer中的sequence指针指向sequence实体
// step 2 , sequence启动时,完成sequence中的sequencer指针指向sequencer实体
为什么要引入p_sequencer?
为什么需要p_sequencer?
就要回到最初的目的,每个sequence中,需要有一个启动该sequence的sequencer指针,我们把它起名叫p_sequencer(子类)。有了该指针后,在sequence中便可以个性化的调用对应sequencer的变量和方法。
但每个sequence中只有sequencer的基类指针m_sequencer, 该m_sequencer指向的是子类的对象。
因此,sequence中若要事项子类对象的方法和属性,需要先将m_sequencer 做个类型转换,转换成子类的句柄p_sequencer。
这个过程每个用户sequence中均可能需要,因此UVM中,实现了一个宏叫uvm_declare_p_sequencer,专门给用户实现上述过程的声明和转换等。
`define uvm_declare_p_sequencer(SEQUENCER) \
SEQUENCER p_sequencer;\
virtual function void m_set_p_sequencer();\
super.m_set_p_sequencer(); \
if( !$cast(p_sequencer, m_sequencer)) \
`uvm_fatal("DCLPSQ", \
$sformatf("%m %s Error casting p_sequencer, please verify that this sequence/sequence item is intended to execute on this type of sequencer", get_full_name())) \
endfunction
// step 3/4
完成p_sequencer的声明和赋值