UVM基础-m_sequencer和p_sequencer

1.1 sequence与sequencer之间的数据交互

在UVM的sequence机制中,sequence其实是独立与验证环境之外的部件,作为uvm_object而存在,那么sequence最终是要在某个sequencer上启动的,那么启动的方式有两种:

  • 在tc中实例化sequence,并在main_phase中调用:

tar_seq.start(env.i_agt.tar_sqr)

  • 在tc中通过config_db set的方式,通过default_sequence启动:

config_db #(uvm_object_wrap)::set(this, “env.i_agt.sqr.main_phase”, “default_sequence”, tar_seq)

       此时sequence会自动调用其pre_body,body,post_body函数,一般会在body中使用`uvm_do系列宏产生tr,这个是最基本的sequence机制的用法,但本文不讨论sequence机制,先记住这两个启动的方法,重点关注通过start函数启动sequence的方式,因为通过“default_sequence”的方式启动seq,本质上也会调用seq的start函数。

       真正工程中,存在一个很常见的场景,就是sequence中想要使用sequencer里面的环境变量,用来控制sequence对tr的产生和调度,比如:

1.	class case0_sequence extends uvm_sequence#(uvm_sequence_item);
2.	      my_transaction tr;
3.	      int    pkt_data_size = 100;
4.	......
5.	      extern function void new(string name="case0_sequence");
6.	      extern virtual task pre_body();
7.	      extern virtual task body();
8.	      extern virtual task post_body();
9.	endclass
10.	
11.	function void case0_sequence::new(string name="case0_sequence");
12.	      super.new(name);
13.	endfunction
14.	
15.	task case0_sequence::pre_body();
16.	      if (starting_phase != null) 
17.	            starting_phase.raise_objection(this);
18.	endtask:pre_body
19.	
20.	task case0_sequence::body();
21.	      `uvm_do_with(tr, {tr.data_size == this.pkt_data_size;})
22.	endtask:body
23.	
24.	task case0_sequence::post_body();
25.	       if(starting_phase != null)
26.	            starting_phase.drop_objection(this);
27.	endtask:post_body

这段代码里,声明了一个变量叫pkt_data_size,在body函数中将其作为约束传递给uvm_do宏,但其实这样的传递本身是没有任何意义的,而更加常见的场景是,pkt_data_size其实是一个变量,这个变量最好从sequencer中获取,这样tc中就可以改变这个值,将这个值作为sequence中uvm_do_with宏的传参,来控制sequence对tr的约束。将21行的uvm_do_with宏改为:

`uvm_do_with(tr, {tr.data_size == tar_sqr.pkt_data_size})

       但是,sequence机制中,其实允许在sequence中再实例化一个sequencer,实现这个功能,这会使得uvm的component和object变得极其混乱,不符合验证方法论极简的思想。工程中的做法,是要在sequence中使用`uvm_declare_p_sequencer的宏,将自己的sequencer指定过去,然后调用p_sequencer的变量pkt_data_size,即可实现对sequence的变量控制,如下图代码块:

1.	class case0_sequence extends uvm_sequence#(uvm_sequence_item);
2.	      my_transaction tr;
3.	      `uvm_declare_p_sequencer(my_sqr);
4.	......
5.	      extern function void new(string name="case0_sequence");
6.	      extern virtual task pre_body();
7.	      extern virtual task body();
8.	      extern virtual task post_body();
9.	endclass
10.	
11.	function void case0_sequence::new(string name="case0_sequence");
12.	      super.new(name);
13.	endfunction
14.	
15.	task case0_sequence::pre_body();
16.	      if (starting_phase != null) 
17.	            starting_phase.raise_objection(this);
18.	endtask:pre_body
19.	
20.	task case0_sequence::body();
21.	      `uvm_do_with(tr, {tr.data_size == p_sequencer.pkt_data_size;})
22.	endtask:body
23.	
24.	task case0_sequence::post_body();
25.	       if(starting_phase != null)
26.	            starting_phase.drop_objection(this);
27.	endtask:post_body
28.	
29.	
30.	......
31.	class my_sqr extends uvm_sequencer(uvm_sequence_item);
32.	      int pkt_data_size = 100;
33.	      `uvm_component_utils_begin(my_sqr)
34.	            `uvm_field_int(pkt_data_size, UVM_ALL_ON)
35.	      `uvm_component_utils_end
36.	
37.	       function void new(string name = "my_sqr", uvm_component parent);
38.	             super.new(name, parent);
39.	       endfunction
40.	......
41.	endclass

这样既可以实现sequence中使用sequencer的变量控制了,但其实这里引入了p_sequncer的概念。

1.2 sequence在 sequencer上启动

       那么sequence是如何在sequencer上启动的?其实在整个sequence代码的编写过程中,是没有任何关于sequencer的信息的,只有在启动sequence的时候,调用start函数,会将自己例化的sequencer传递给start作为参数。其实问题的根因就在这个start函数上,start函数是uvm_sequence的基类中uvm_sequence_base中的函数:

 

这个函数第一个参数是uvm_sequencer_base类型的参数,将tar_sqr作为实参传递进去,其实是一个父类句柄指向子类的对象的过程。注意函数体里直接调用了set_item_context,这个函数其实是uvm_sequence_base的基类uvm_sequence_item的函数:

 

 

       这个函数有两个参数,第二个参数为设置传递的实参tar_sqr,注意这里会调用set_sequencer函数,这个函数同样在uvm_sequence_item中:

 

这里会将sequencer赋值给m_sequencer,m_sequencer其实是uvm_sequence_item中的一个uvm_sequencer_base的类声明的实例,为protected类型:

 

       看到这里,其实在sequence中,会有一个叫uvm_sequencer_base的类:m_sequencer,这个句柄可以在子类中使用,因为声明时被protected保护。然后这个m_sequencer在子类sequence调用start函数的时候,会指向uvm_sequencer_base的子类,也就是tar_sqr。这里不得不提一下uvm sequence机制中类的继承关系:

sequence继承主线:

case0_sequence => uvm_sequence => uvm_sequence_base => uvm_sequence_item

sequencer继承主线:

tar_sqr => uvm_sequencer => uvm_sequencer_param_base => uvm_sequencer_base。

看到这里,总结一下:

  • case0_sequence中有一个uvm_sequencer_base的基类m_sequencer;
  • 在调用case0_sequence的start函数的时候,会将子类tar_sqr的对象实例赋值给m_sequencer基类,也就是m_sequencer最终会指向tar_sqr的对象。
  • 通过调用start函数的其他机制,以及m_sequencer指向tar_sqr的对象,seq最终会在tar_sqr中启动,并且根据上述机制,case0_sequence中能够使用m_sequencer的成员变量。

2.1 m_sequencer和p_sequencer的关系

       从1.2节可以看到,sequence最终是通过start函数,将m_sequencer指向了tar_sqr,并成功在tar_sqr上启动的,那么是不是可以通过在case0_sequence中使用m_sequencer进行变量约束呢?如如下代码:

1.	class case0_sequence extends uvm_sequence#(uvm_sequence_item);
2.	      my_transaction tr;
3.	......
4.	      extern function void new(string name="case0_sequence");
5.	      extern virtual task pre_body();
6.	      extern virtual task body();
7.	      extern virtual task post_body();
8.	endclass
9.	
10.	function void case0_sequence::new(string name="case0_sequence");
11.	      super.new(name);
12.	endfunction
13.	
14.	task case0_sequence::pre_body();
15.	      if (starting_phase != null) 
16.	            starting_phase.raise_objection(this);
17.	endtask:pre_body
18.	
19.	task case0_sequence::body();
20.	      `uvm_do_with(tr, {tr.data_size == m_sequencer.pkt_data_size;})
21.	endtask:body
22.	
23.	task case0_sequence::post_body();
24.	       if(starting_phase != null)
25.	            starting_phase.drop_objection(this);
26.	endtask:post_body
27.	
28.	
29.	......
30.	class my_sqr extends uvm_sequencer(uvm_sequence_item);
31.	      int pkt_data_size = 100;
32.	      `uvm_component_utils_begin(my_sqr)
33.	            `uvm_field_int(pkt_data_size, UVM_ALL_ON)
34.	      `uvm_component_utils_end
35.	
36.	       function void new(string name = "my_sqr", uvm_component parent);
37.	             super.new(name, parent);
38.	       endfunction
39.	......
40.	endclass

       这样其实是不可以的,仿真会直接报错,为什么?其实是一个类的复制的问题,继承类的句柄赋值给父类的句柄,父类句柄只能指向子类继承的父类的对象,是没办法访问到子类对象本身的成员变量的,会包类空间越界访问错误。思考一下怎么做可以实现访问?答案很简单,其实借助$cast就可以,在case0_sequence中声明一个my_sqr类型的对象,通过cast方式,将m_sequencer cast给my_sqr,如下所示代码:

1.	class case0_sequence extends uvm_sequence#(uvm_sequence_item);
2.	      my_transaction tr;
3.	......
4.	      extern function void new(string name="case0_sequence");
5.	      extern virtual task pre_body();
6.	      extern virtual task body();
7.	      extern virtual task post_body();
8.	endclass
9.	
10.	function void case0_sequence::new(string name="case0_sequence");
11.	      super.new(name);
12.	endfunction
13.	
14.	task case0_sequence::pre_body();
15.	      if (starting_phase != null) 
16.	            starting_phase.raise_objection(this);
17.	endtask:pre_body
18.	
19.	task case0_sequence::body();
20.	      my_sqr tar_sqr;
21.	      $cast(tar_sar, m_sequencer)
22.	      `uvm_do_with(tr, {tr.data_size == tar_sar.pkt_data_size;})
23.	endtask:body
24.	
25.	task case0_sequence::post_body();
26.	       if(starting_phase != null)
27.	            starting_phase.drop_objection(this);
28.	endtask:post_body
29.	
30.	
31.	......
32.	class my_sqr extends uvm_sequencer(uvm_sequence_item);
33.	      int pkt_data_size = 100;
34.	      `uvm_component_utils_begin(my_sqr)
35.	            `uvm_field_int(pkt_data_size, UVM_ALL_ON)
36.	      `uvm_component_utils_end
37.	
38.	       function void new(string name = "my_sqr", uvm_component parent);
39.	             super.new(name, parent);
40.	       endfunction
41.	......
42.	endclass

这里讲下为什么能cast(m_sequencer其实是my_sqr的父类),在start调用时,已经将m_sequencer指向了my_sqr的对象,实际上时子类和子类的cast,因此可以成功,那么就可以通过tar_sqr访问my_sqr的成员变量了。

2.2 `uvm_declare_p_sequencer(my_sqr)的作用

基于2.1节的内容,其实`uvm_declare_p_sequencer(my_sqr)的作用,就是在case0_sequence中声明了一个my_sqr类型的句柄p_sequencer,将m_sequencer通过cast的方式赋值了p_sequencer,看一下这个宏的定义:

值得注意的是,在cast的过程中,其实是改写了m_set_p_sequencer这个虚方法,其实这个方法是见过的,也就是1.2节,在uvm_sequence_item基类中set_sequencer函数中:

这个函数的原型是在uvm_sequence_item基类中:

空函数直接返回。

因此,这个宏改写了uvm_sequence_item的m_set_p_sequencer函数,将m_sequencer通过cast的方式传递给p_sequencer,这样就可以在case0_sequence中调用tar_sqr的成员变量了。

总结

       其实这个问题,底层根源是一个类的赋值的问题,画个图解释下:

       case0_sequence的start函数,会调用uvm_sequence_base的start,这个start会调用uvm_sequence_item中的set_item_context函数,这个函数会调用set_sequencer函数,这个函数会干两件事,第一件是将函数的uvm_sequencer_base类型的形式参数赋值给m_sequencer,m_sequencer是在uvm_sequence_item中的一个uvm_sequencer_base类型的句柄,另一件事是调用m_set_p_sequencer虚函数,这个函数本身在uvm_sequence_item基类中没有声明函数体。在start的过程中,会将m_sequencer句柄指向my_sqr的对象。

       如果在case0_sequence中声明了`uvm_declare_p_sequencer宏,会干两件事,一件是声明一个my_sqr类型的p_sequencer;另一件是这个函数会重载uvm_sequence_item中m_set_p_sequencer函数,函数体中将m_sequencer赋值给p_sequencer,为什么能将父类赋值给子类,因为在赋值的过程中,m_sequencer已经指向了子类my_sqr,同类可以cast。

       通过这套机制,在case0_sequence中就可以调用sequencer中的成员变量,实现对tr的约束控制,以及其他tr或者sequence的调度和控制。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值