lab1
1.工厂注册、创建和覆盖机制
TODO-1.1、1.2
- 使用以下方式创建uvm_objects或uvm_component对象:
1、直接使用new()函数:t1 = new(“t1”);调用了trans类中new函数实例化一个t1对象。
2、uvm_component_registry #(T,Tname)或uvm_object_registry #(T,Tname)中的create创建函数:
comp_type::type_id::create(string name, uvm_component parent);
object_type::type_id::create(string name);
3、factory机制创建实例方式,有两种——一种根据类名创建;一种根据类型创建(只要是在工厂注册过的类,最终创建都是通过该函数):
uvm_factory::create_component_by_type(uvm_object_wrapper requested_type,
string parent_inst_path=“”,
string name=“”,
uvm_component parent);
uvm_factory::create_component_by_name(uvm_object_wrapper requested_type,
string parent_inst_path=“”,
string name=“”,
uvm_component parent);
uvm_factory::create_object_by_type(uvm_object_wrapper requested_type,
string parent_inst_path=“”,
string name=“”);
uvm_factory::create_object_by_name(uvm_object_wrapper requested_type,
string parent_inst_path=“”,
string name=“”);
4、uvm_component中两个创建函数(这两个函数分别调用了,工厂中的两个类名创建函数:“uvm_factory::create_component_by_name();”和“uvm_factory::create_object_by_name();”):
function uvm_component create_component (string requested_type_name, string name)
function uvm_object create_object (string requested_type_name, string name)
- “uvm_factory f = uvm_factory::get();”中——**get()**函数,是用来调用uvm_coreservice_t::get_factory这个函数的,作用:当需要“工厂”时,在核心服务类中找到它,并进行例化(只能例化一个工厂的实例)。
- 🐛由于UVM版本问题,object类型对象创建时,实例名传不进去,导致打印的是默认的名字。
TODO-1.3、1.4
- 使用以下方式覆盖uvm_objects或uvm_component对象:
1、类型覆盖
set_type_override_by_type(trans::get_type(), bad_trans::get_type());——需要利用get_type()函数获取原类和覆盖类的类型。
set_type_override(“unit”, “big_unit”);——直接用原类型名称和覆盖类型名称即可。
原类型名::type_id::set_type_override(覆盖类型名::get_type());
2、实例覆盖(还需要获取被覆盖的实例路径)
set_inst_override_by_type(trans::get_type(), bad_trans::get_type(), t2::get_full_name());
set_inst_override(“unit”, “big_unit”, u2::get_full_name());
- 覆盖的条件:
1、原类型和覆盖类型都需要注册在工厂里,且覆盖类型应该是原类型的子类,原类型中方法应声明virtual。
2、要在实例创建前,进行类型覆盖,且只有利用工厂创建的实例才能被覆盖(像利用new函数创建的实例便不会被覆盖)。
相当于声明对象时,是原类型(父类)的对象句柄,在这些对象实例创建之前,用覆盖类型(子类)去覆盖原类型。那么在利用工厂创建实例时,会查看被创建类型(原类型)是否被覆盖,若已覆盖,则最终创建的类型为覆盖类型,即父类(原类型)句柄指向子类(覆盖类型)对象。(应该时工厂自动创建了子类对象实例,并将该子类对象句柄赋值给父类句柄)
那么使用该句柄调用函数时,会先查看父类(原类型)中的方法,此时由于该方法在定义时被指定为virtual虚函数,所以利用多态性方法调用,转而调用子类(覆盖类型)中的方法。
思考
- 分别移除trans类型和unit类型的```uvm_object_utils(trans)和`uvm_component_utils(unit)``注册宏声明,并再进行编译仿真,观察结果。
编译会报错:“Failed to find the name ‘type_id’ in scope 'trans/‘unit’”,即未在工厂中注册的话,利用工厂进行实例创建时会找不到该类型。
但如果把t2t3t4/u2u3u4也注释掉,便不会报错,进行仿真的话也会创建t1/u1实例。所以无论注不注册,都可以利用new函数进行创建实例。
2.域自动化和uvm_object
- 通过域的自动化,使在注册类的同时声明需要参与到对象复制、克隆、打印、比较等操作的成员变量。
TODO-2.1
- 域的自动化的宏方法。
`uvm_object_utils_begin(trans)//注册类时,将需要参与上述操作的成员变量用域的自动化宏进行声明
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_enum(op_t, op, UVM_ALL_ON)
`uvm_field_string(name, UVM_ALL_ON)
`uvm_object_utils_end
TODO-2.2、2.3
- 学习uvm_object::compare()方法,掌握uvm_pkg中常见的一些全局控制对象,例如uvm_default_comparer,该对象可参考uvm_comparer类提供的方法。uvm_pkg中其他全局控制对象还包括uvm_default_printer、uvm_default_packer等
is_equal = t1.compare(t2);//compare函数会返回值:0-不相等、1-相等
//默认情况下,比较器uvm_package::uvm_default_comparer最大输出的错误比较信息默认是1。
//也就是说,发生了一个错误比较之后,就不再进行后续比较。
//根据需要,可用下面语句修改comparer默认的最大输出错误比较信息。
uvm_default_comparer.show_max = 10;//注意:该句要放在1(compare函数)之前,才会生效!
TODO-2.4
do_compare()
是compare()
的回调函数,也就是执行完compare()
后会自动执行do_compare()
。若do_compare()不执行,那么compare()函数也不会执行。
virtual function bit do_compare (uvm_object rhs,uvm_comparer comparer)
//rhs参数是uvm_object类型。 因此,必须在比较前将其转换为这个对象的类型。
mytype rhs_;
$cast(rhs_, rhs);
TODO-2.5、2.6
- 学习uvm_object::print()方法以及uvm_object::copy()方法。
//打印方法:
t1.print();
t2.print();
//拷贝方法:
t1.copy(t2);
3.Phase机制
- phase机制使得验证环境从组建到连接、再到执行得以分阶段执行,按照层次结构和phase顺序严格执行,继而避免一些依赖关系,也使得UVM用户正确地将不同地代码放置到不同地phase块中。
- phase机制是component独有的,其引入原因为:
**1、在验证环境实现层次化时保证例化的先后关系以及各组件之间的连接。**即只有例化顶层的组件(如agent),才能创建空间来容纳底层组件(如driver、monitor)。
2、将UVM仿真阶段层次化(各个phase之间有先后顺序,同一phase中的层次化组件之间的phase也有先后关系),很大程度上解决了因代码顺序杂乱可能会引发的问题。
**3、SV无法在底层组件例化之前完成对底层的配置。**例如:在没例化前修改底层的一个变量
TODO-3.1
- build_phase为自顶向下运行,connect_phase和report_phase为自底向上运行;
- run_phase通过fork…join_none的形式全部启动。 所以, 更准确的说法是,不同层次自下而上的启动,同一层次同时在运行。
TODO-3.2
- 由于run_phase和12个task phase是并行执行的,所以他们同时开始,也就是run_phase和reset_phase同时开始,运行时长为1us。而12个task phase是按自上而下顺序执行的,所以main_phase在reset_phase执行完后才开始,开始时间为1us,运行1us后结束。因此仿真结束时间为2us。
4.config机制
- 该实验完成:(虚拟)接口传递、单一变量传递、对象传递。
TODO-4.1
- 完成接口从UVM_config模块到验证环境的传递,使得C1和C2可以得到接口。
- 在哪里set,哪里get:在顶层tb去set,在底层的build_phase中去get(传递变量和对象也一样)。
- 接口传递(也就是set)要发生在run_test(在build phase里get,而build phase在run_test里)之前,保证在进入build phase之前virtual interface已经传到uvm_config_db里。
1、**要在顶层tb里set:**在module中通过config_db机制的set函数设置virtual interface时, set函数的第一个参数如果为null,UVM会自动把第一个参数替换为uvm_root:: get(), 即uvm_top。因此存在两种写法。
2、在c1和c2的build_phase里get。
TODO-4.2
- 完成配置对象config.obj从uvm_config.test到c1和c2的传递。
将各组件中,需要配置的参数变量整合到一个config.obj类型的对象里。然后在test中将该对象set进去,组件中get该对象,选择该对象中需要用到的参数变量即可。
TODO-4.3
- 完成在顶层uvm_config_test中对c1.val和c2.val的变量设置。
底层组件c1和c2先声明并定义变量var1和var2的默认值,然后在test中set这两个变量的更新值,c1和c2中再去get这个更新值。
所以在test中,可以在build_phase里对底层组件的变量(需在底层组件例化creat前)加以配置(set),然后底层组件在其各自的build_phase中get配置信息,既实现了变量值的修改,又无需重新编译。
思考
- 接口传递与run_test()是否存在顺序?
接口传递(set)要在run_test()之前。
- 将test中c1的例化(creat)提到config_db操作之前,是否可行?
将c1的例化(creat)提到config_db操作之前,发现结果不变。
因为c1中有对变量val1的初始值,而且get语句在打印语句之后,所以"before config get"打印出的val1的值就是c1中val1的初始值。如果1、2调换位置,那么"before config get"打印的val1的值就是test中set的值了。
`uvm_info("GETINT", $sformatf("before config get, var1 = %0d", var1), UVM_LOW) uvm_config_db#(int)::get(this, "", "var1", var1); `uvm_info("GETINT", $sformatf("after config get, var1 = %0d", var1), UVM_LOW)
也就是说,test中set的变量值和组件中变量的初始值,都会保存在一个资源池中,等到get语句后,才发生值的替换,否则则采用该变量的初始值,若没有初始值,那么直接会采用test中set的变量值。这与组件的创建和set语句的次序无关(次序不同,也只是值存进这个池子的顺序不同而已)!
- 满足以下条件时,可以省略get语句(set语句无法省略)。
1、使用uvm_field_int(域的自动化)对成员变量进行注册。
2、build_phase中包含super.build_phase语句。
注意:当采用以上操作时,执行完super.build_phase这一句后,变量的值就已经得到了更新。所以若想得到更新前的值,应该在super.build_phase该语句之前去获取。