- 一、 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的成员变量。
- 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的调度和控制。