1.1 config_db的引入:
一般的,在构建验证环境时,都会建立一层harness,作为testbench的顶层,内部包含了待测DUT,interface和一些check模块,以及运行整个UVM环境的run_test()函数。那么UVM的环境组件driver是如何得到这一组interface的,UVM提供了用于组件之间的变量交互机制-config_db机制。
通常在testbench里,会通过config_db#(virtual my_if)::set的方式将interface发出,给到driver,在driver里通过config_db#(virtual my_if)::get的方式得到这一组interface,再执行驱动逻辑。如下图所示代码块:
1. //TestBench Top
2. initial begin
3. config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif", vif);
4. end
5. //driver
6. ......
7. my_if bus;
8. ......
9. function void my_driver::build_phase(uvm_phase phase);
10. super.build_phase(phase);
11. config_db#(virtual my_if)::get(this, "", "vif", bus);
12. ......
13. endfunction:build_phase;
一般我们将set的过程称之为寄信,而get的过程称之为收信,一般寄信的组件中,要说明寄给谁,而收信的组件,要执行寄信的“应答”过程,在这里我将其比作对密钥,密钥对上了,信就能收到。
1.2 config_db的编程规则
set的编程规则:以test_case寄给driver一个名为pkt_num为例:
config_db#(int)::set(this, “env.i_agt.drv”, “pkt_num”, 100)
解析一下,config_db其实是一个参数化类,而SystemVerilog的参数化类是支持编程模板的,这个概念在C语言中很常见。而“#()”,其中括号里声明的类型就是当前要寄出的变量的类型,因为支持模板编程,因此这个类型可以是类,也可以是SV支持的任何类型的变量,set函数是config_db类里面的一个静态函数,因此可以通过“::”作用域运算符直接调用。set函数有4个参数:
第一个参数:表示当前要寄信的组件,一般是一个uvm_component类型的变量,但如果本身不是组件呢?例如在tb顶层去寄信,那么这个位置填null,或者填写uvm_root::get(),其实null会自动替换成uvm_root::get(),也就是UVM树形结构的最顶层。
第二个参数:第二个参数是一个字符串类型,表示要寄信的目标地址,是相对于第一个参数来说的,以当前实例为例,在test_case寄给driver,那么树形结构为test_case.env.i_agt.drv,这个参数相对于第一个参数设置,因此是“env.i_agt.drv”,值得注意的是,这个要体现uvm的树形结构,也就是说组件的实例化除了注册到factory机制后,实例化调用factory的create函数,传参第一个就是树的实例化名字,如果在i_agent里声明了一个my_driver drv的句柄,实例化的时候调用的是drv = my_driver::type_id::create(“m_driver”, this),那么第二个参数要写成“env.i_agt.m_driver”。
第三个参数:这也是一个字符串类型的参数,就是之前提到的密钥,也就是说,my_driver里的get函数第三个参数也应该是“pkt_num”,才能与这个set对上密钥,信才能收到,一般密钥体现寄信的内容即可。
第四个参数:这个参数是具体寄信的值。
get编程规则:在driver中
config_db#(int)::get(this, “”, “pkt_num”, pkt_num);
与set类似,#()里括号中表示要传递的变量的类型,get函数依旧包括四个参数:
第一个参数:表示当前要收信的组件,在driver中收信,那么就写为this。
第二个参数:这个参数是一个字符串类型的参数,与第一个参数一起使用,表示在uvm树形结构中,收信组件的位置,因为在driver中收信,第一个参数已经是this指向driver,那么第二个参数为空即可;如果第一个参数变为:this.m_parent,指向了driver的父类,那么第二个参数要写为:“drv”,相对于父类的路径。规则与set一致,但如果第一个参数写为null,那么第二个参数要写为“uvm_test_top.env.i_agt.drv”。
第三个参数:这个参数就是收信的密钥,要和寄信一致,才能正确的收到。
第四个参数:这个参数为收信组件中要被赋值的变量,也就是在get之后,会将driver中一个int类型的变量pkt_num赋值为100。
1.3 省略get语言
在某些情况下,config_db的get语句可以省略,因为config_db实际上是需要组件在注册到factory中,或者object在注册到uvm_object_utils中的时候才能够使用config_db,那么注册在factory机制中的收信组件,当一个变量被写到field_automation中的时候,就可以省略get,比如如下的driver代码:
1. class my_driver extends uvm_driver#(uvm_sequence_item);
2. int pkt_num;
3. `uvm_component_utils_begin(my_driver)
4. `uvm_field_int(pkt_num, UVM_ALL_ON)
5. `uvm_component_utils_end
6.
7. extern function void new(string name="my_driver", uvm_component parent);
8. extern virtual function void build_phase(uvm_phase phase);
9. extern virtual function void connect_phase(uvm_phase phase);
10. ......
11. extern virtual task main_phase(uvm_phase phase);
12. endclass
13.
14. function void my_driver::new(string name="my_driver", uvm_component parent);
15. super.new(name, parent);
16. endfunction:new
17.
18. function void my_driver::build_phase(uvm_phase phase);
19. super.build_phase(phase);
20. `uvm_info(get_type_name(), $sformatf("pkt num is %d", pkt_num), UVM_HIGH);
21. ......
22. endfunction:build_phase
使用省略get语法的使用方法时应该注意:
- 在收信组件中要声明一个变量,这个变量的名字要和set过程的密钥一摸一样。
- 收信组件首先要注册到factory机制中,这个声明的变量,要注册到field_automation机制中;
- 在build_phase中首先要调用super.build_phase,也就是基类的build_phase,因为注册到factory机制中要收信的变量,会在基类的build_phase中调用get。
因此,良好的编码习惯为,set函数的密钥参数,就应该和收信组件的变量一致,不要搞特殊的密钥,导致代码维护困难。
1.4 config_db对通配符的支持
config_db set的第三个参数支持通配符操作,比如一个最常见的场景,一个input interface不但要发给driver,同样也要发给monitor,那么通常的写法是:
1. config_db #(virtual my_if)::set(uvm_root::get, "uvm_test_top.env.i_agt.drv", "pkt_num", in_vif);
2. config_db #(virtual my_if)::set(uvm_root::get, "uvm_test_top.env.i_agt.mon", "pkt_num", in_vif);
3. config_db #(virtual my_if)::set(uvm_root::get, "uvm_test_top.env.o_agt.mon", "pkt_num", o_vif);
可以使用通配符写为:
1. config_db #(virtual my_if)::set(uvm_root::get, "uvm_test_top.env.i_agt.*", "pkt_num", in_vif);
2. config_db #(virtual my_if)::set(uvm_root::get, "uvm_test_top.env.o_agt.*", "pkt_num", o_vif);
第二个参数可以用*代替。进一步,前面的参数也可以用通配符:
1. config_db #(virtual my_if)::set(uvm_root::get, "*i_agt.*", "pkt_num", in_vif);
2. config_db #(virtual my_if)::set(uvm_root::get, "*o_agt.*", "pkt_num", o_vif);
但一般不建议这样用,因为通配符用的越多,有可能会造成一些想不到的异常,比如在某个组件中本来不想收到来自tc的pkt_num,而是用自己的,因为tc中用了通配符,导致pkt_num变化。一般提倡将寄信的路径显示的写明。
1.5 uvm_object类型组件对config_db的支持
在uvm_component中使用config_db很明确,但是一个uvm_object类可否使用config_db呢? 答案是可以的,但是也存在一个问题,就是config_db的第二个参数的路径应该如何获取,其实uvm_object类中提供了一个获取路径的函数,get_full_name(),比如在sequence中调用这个函数,打印的是:uvm_test_top.env.i_agt.sqr.case0_sequence,为什么路径显示的是sqr?其实也很好理解,就是一个sequence要注定在某个sequencer上启动,因此,sequencer的路径下存在sequence也很合理。因此如果要在sequence中使用get方法,那么要这么写:
config_db#(bit)::get(null, get_full_name(), “ecc_error”, ecc_error);
如果在tc中寄信,那么应该这么写:
config_db#(bit)::set(this, “env.i_agt.sqr*”, “ecc_error”, 1’b1);
这里注意的是使用了通配符,原因是一个sequence的名字一般是不固定的, 而且如果用default_sequence启动的化,名字更加不固定。所以这里要使用通配符。
在非uvm_component组件中使用config_db使用总结:
- 同样这个uvm_object类型的组件要注册到`uvm_object_utils;
- 在寄信的组件中要使用通配符,因为一般情况下,非组件是有声明周期的,而且名称不固定
- 在收信的uvm_object中,因为无法确认其路径,因此使用基类中国的函数:get_full_name()获取,此时get的第一个参数不能是this指针,而要写成null或者uvm_root::get()。