config_db机制
概述
- UVM提供了uvm_config_db配置类以及几种方便的变量设置方法,来实现仿真时的环境控制,常见的uvm_config_db类的使用方式包括:
- 传递virtual interface到环境中
- 设置单一的变量值,int, string, enum等
- 传递配置对象到环境
UVM中的路径
get_full_name
一个component(如my_driver) 内通过该函数可以得到此component的路径
function void my_driver::build_phase(); super.build_phase(phase); $display("%s", get_full_name()); endfunction
set与get函数的参数
- config_db机制用于在UVM验证平台间传递参数。它们通常都是成对出现的。
- set函数是寄信,
- get函数是收信。
- 如在某个测试用例的build_phase中可以使用如下方式寄信:
// 寄信方式 uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 100); //第一个和第二个参数联合起来组成目标路径,与此路径符合的目标才能收信。 //第一个参数必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。 // 第三个参数表示一个记号,用以说明这个值是传给目标中的哪个成员的, // 第四个参数是要设置的值。 // 收信方式 uvm_config_db#(int)::get(this, "", "pre_num", pre_num);
// get函数中的第一个参数和第二个参数联合起来组成路径。 // 第一个参数也必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。 // 一般的,如果第一个参数被设置为this,那么第二个参数可以是一个空的字符串。 // 第三个参数就是set函数中的第三个参数,这两个参数必须严格匹配, // 第四个参数则是要设置的变量。
特殊情况
- set函数的第一个参数为null时,UVM会自动把第一个参数替换为uvm_root::get() ,即uvm_top。换句话说,以下两种写法是完全等价的:
initial begin uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif", input_if); end initial begin uvm_config_db#(virtual my_if)::set(uvm_root::get(), "uvm_test_top.env.i_ag t. drv", "vif", input_if); end
- get函数的参数灵活设置
uvm_config_db#(int)::get(this.parent, "drv", "pre_num_max", pre_num_max); 或者: uvm_config_db#(int)::get(null, "uvm_test_top.env.i_agt.drv", "pre_num_max", p re_num_max);
- set及get函数中第三个参数可以与get函数中第四个参数不一样,只要保持set和get中第三个参数一致即可也可以。
uvm_config_db#(int)::set(this, "env.i_agt.drv", "p_num", 100); uvm_config_db#(int)::get(this, "", "p_num", pre_num);
省略get语句
- set与get函数一般都是成对出现,但是在某些情况下,是可以只有set而没有get语句,即省略get语句。
- 只要使用uvm_field_int注册,并且在build_phase中调用super.build_phase() ,就可以省略在build_phase中的如下get语句
int pre_num; `uvm_component_utils_begin(my_driver) `uvm_field_int(pre_num, UVM_ALL_ON) `uvm_component_utils_end function new(string name = "my_driver", uvm_component parent = null); super.new(name, parent); pre_num = 3; endfunction virtual function void build_phase(uvm_phase phase); `uvm_info("my_driver", $sformatf("before super.build_phase, the pre_num is %0d", pre_num), UVM_LOW) super.build_phase(phase); `uvm_info("my_driver", $sformatf("after super.build_phase, the pre_num is %0d", pre_num), UVM_LOW) if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif)) `uvm_fatal("my_driver", "virtual interface must be set for vif!!!") endfunction // 省略下面get语句: uvm_config_db#(int)::get(this, "", "pre_num", pre_num); // build_phase中的super.build_phase语句,当执行到driver的super.build_phase时,会自动执行get语句。
// 这种做法的前提是: // 第一,my_driver必须使用uvm_component_utils宏注册; // 第二,pre_num必须使用uvm_field_int宏注册; // 第三,在调用set函数的时候,set函数的第三个参数必须与要get函数中变量的名字相一致,即必须是pre_num。
跨层次的多重设置
通常情况下,都是设置一次,get一次。如果在test和env中都进行设置,那么会get到哪一个设置的值?
- 同一层次的多重设置当跨层次来看待问题时,高层次的set设置优先;当处于同一层次时,时间优先
- UVM规定层次越高,那么它的优先级越高;这里的层次指的是在UVM树中的位置,越靠近根结点uvm_top,则认为其层次越高。uvm_test_top的层次是高于env的,所以uvm_test_top中的set函数的优先级高
- 同一层次,获得最新设置的值,即设置时间
// 比较层次结构,最后获得值为999 function void my_case0::build_phase(uvm_phase phase); super.build_phase(phase); … uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 999); `uvm_info("my_case0", "in my_case0, env.i_agt.drv.pre_num is set to 999",UVM_LOW) virtual function void build_phase(uvm_phase phase); super.build_phase(phase); … uvm_config_db#(int)::set(this, "i_agt.drv", "pre_num", 100); `uvm_info("my_env", "in my_env, env.i_agt.drv.pre_num is set to 100",UVM_LOW) endfunction
// 同一层次,比较设置时间,driver得到的pre_num的值是100 function void my_case0::build_phase(uvm_phase phase); super.build_phase(phase); … uvm_config_db#(int)::set(uvm_root::get(), "uvm_test_top.env.i_agt.drv", "pre_num", 999); `uvm_info("my_case0", "in my_case0, env.i_agt.drv.pre_num is set to 999", UVM_LOW) virtual function void build_phase(uvm_phase phase); super.build_phase(phase); … uvm_config_db#(int)::set(uvm_root::get(), "uvm_test_top.env.i_agt.drv", "pre_num", 100); `uvm_info("my_env", "in my_env, env.i_agt.drv.pre_num is set to 100",UVM_LOW)
endfunction
同一层次的多重设置
uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 100); uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 109); // 当上面两个语句同时出现在测试用例的build_phase中时,driver最终获取到的值将会是109
非直线的设置与获取
- 在uvm_test_top,env或者i_agt中,对driver中的某些变量通过config_db机制进行设置,称为直线的设置
- 若在其他component,如scoreboard中,对driver的某些变量使用config_db机制进行设置,则称为非直线的设置
- 非直线的设置,会有一定的风险,应该避免这种情况的出现
// 要进行非直线的设置,需要仔细设置set函数的第一个和第二个参数 function void my_scoreboard::build_phase(uvm_phase phase); … uvm_config_db#(int)::set(this.m_parent, "i_agt.drv", "pre_num", 200); `uvm_info("my_scoreboard", "in my_scoreboard, uvm_test_top.env.i_agt.drv.pre_num is set to 200", U endfunction // 或 function void my_scoreboard::build_phase(uvm_phase phase); super.build_phase(phase); uvm_config_db#(int)::set(uvm_root::get(), "uvm_test_top.env.i_agt.drv", "pre_num", 200); endfunction
- 在my_driver中使用config_db::get获得其他任意component设置给my_driver的参数,称为直线的获取
- 假如要在其他的component,如在reference model中获取其他component设置给my_driver的参数的值,称为非直线的获取。
- 非直线的获取可以在某些情况下避免config_db::set的冗余
function void my_model::build_phase(uvm_phase phase); super.build_phase(phase); port = new("port", this); ap = new("ap", this); `uvm_info("my_model", $sformatf("before get, the pre_num is %0d", drv_pre_num), UVM_LOW) void'(uvm_config_db#(int)::get(this.m_parent, "i_agt.drv", "pre_num", drv_pre_num)); `uvm_info("my_model", $sformatf("after get, the pre_num is %0d", drv_pre_num), UVM_LOW) endfunction // 或 void'(uvm_config_db#(int)::get(uvm_root::get(), "uvm_test_top.env.i_agt.drv","pre_num", drv_pre_num));
config_db机制对通配符的支持
- 通配符的存在使得原本非常清晰的设置路径变得扑朔迷离,不推荐使用通配符。
- 即使要用,也尽可能不要过于“省略”,推荐方式2
// 方式1: initial begin uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv","vif",input_if); uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.mon","vif",input_if); uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.o_agt.mon","vif",output_if); end // 方式2: initial begin uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt*", "vif", input_if); uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.o_agt*", "vif", output_if); `uvm_info("top_tb", "use wildchar in top_tb's config_db::set!", UVM_LOW) end // 方式3 initial begin uvm_config_db#(virtual my_if)::set(null, "*i_agt*", "vif", input_if); uvm_config_db#(virtual my_if)::set(null, "*o_agt*", "vif", output_if); end
check_config_usage
- config_db机制功能非常强大,能够在不同层次对同一参数实现配置。
- 但它的一个致命缺点是,其set函数的第二个参数是字符串,如果字符串写错,那么根本就不能正确地设置参数值
- UVM提供了一个函数check_config_usage,它可以显示出截止到此函数调用时有哪些参数是被设置过但是却没有被获取过
- 由于config_db的set及get语句一般都用于build_phase阶段,所以此函数一般在connect_phase被调用:
virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); check_config_usage(); endfunction
set_config与get_config
- set_config_int与uvm_config_db#(int) ::set是完全等价的
- 使用set_config_int来代替uvm_config_db#(int) ::set的代码
function void my_case0::build_phase(uvm_phase phase); … uvm_config_db#(uvm_object_wrapper)::set(this, "env.i_agt.sqr.main_phase", "default_sequence", case0_sequence::type_id::get()); set_config_int("env.i_agt.drv", "pre_num", 999); set_config_int("env.mdl", "rm_value", 10); endfunction
- get_config_int与uvm_config_db#(int) ::get是完全等价的
- 使用get_config_int来获取参数值:
function void my_model::build_phase(uvm_phase phase); int rm_value; super.build_phase(phase); … void'(get_config_int("rm_value", rm_value))); `uvm_info("my_model", $sformatf("get the rm_value %0d", rm_value), UVM_LOW)
endfunction
- 参数可以使用set_config_int设置,而使用uvm_config_db#(int) ::get来获取;
- 使用uvm_config_db#(int) ::set来设置,而使用get_config_int来获取。
- set/get_config_string和set/get_config_object。它们分别对应uvm_config_db#(string) ::set/get和uvm_config_db#(uvm_object) ::set/get
- config_db比set/get_config强大的地方在于,它设置的参数类型并不局限于以上三种。常见的枚举类型、virtual interface、bit类型、队列等都可以成为config_db设置的数据类型
- UVM提供命令行参数来对它们进行设置
<sim command> +uvm_set_config_int=<comp>,<field>,<value> <sim command> +uvm_set_config_string=<comp>,<field>,<value> <sim command> +uvm_set_config_int="uvm_test_top.env.i_agt.drv,pre_num,'h8" // 在设置int型参数时,可以在其前加上如下的前缀:'b、'o、'd、'h,分别表示二进制、八进制、十进制和十六进制的数据。如果不加任何前缀,则默认为十进制。
config_db的调试
- check_config_usage函数,它能显示出截止到函数调用时,系统中有哪些参数被设置过但是没有被读取过。这是config_db调试中最重要的一个函数。
- 除了这个函数外,UVM还提供了print_config函数
- 不会列出default sequence的相关信息。
virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); print_config(1); endfunction // 参数1表示递归的查询,若为0,则只显示当前component的信息 // 运行结果 # UVM_INFO @ 0: uvm_test_top [CFGPRT] visible resources: # <none> # UVM_INFO @ 0: uvm_test_top.env [CFGPRT] visible resources: # <none> # UVM_INFO @ 0: uvm_test_top.env.agt_mdl_fifo [CFGPRT] visible resources: # <none> … # UVM_INFO @ 0: uvm_test_top.env.i_agt.drv [CFGPRT] visible resources: # vif [/^uvm_test_top.env.i_agt.drv$/] : (virtual my_if) X X x x # - # pre_num [/^uvm_test_top.env.i_agt.drv$/] : (int) 999 # - …# UVM_INFO @ 0: uvm_test_top.env.i_agt.mon [CFGPRT] visible resources: # vif [/^uvm_test_top.env.i_agt.mon$/] : (virtual my_if) X X x x # - …# UVM_INFO @ 0: uvm_test_top.env.mdl [CFGPRT] visible resources: # rm_value [/^uvm_test_top.env.mdl$/] : (int) 10 # - …# UVM_INFO @ 0: uvm_test_top.env.o_agt.mon [CFGPRT] visible resources: # vif [/^uvm_test_top.env.o_agt.mon$/] : (virtual my_if) X X x x # -
命令行参数
- UVM还提供了一个命令行参数UVM_CONFIG_DB_TRACE来对config_db进行调试
<sim command> +UVM_CONFIG_DB_TRACE
换一个phase使用config_db
- config_db几乎都是在build_phase中,由于其config_db::set的第二个参数是字符串,所以经常出错。
- 一个component的路径可以通过get_full_name() 来获得,要想避免config_db::set第二个参数引起的问题,一种可行的想法是把这个参数使用get_full_name()
uvm_config_db#(int)::set(null, env.i_agt.drv.get_full_name(), "pre_num", 100); // 若要对sequence的某个参数设置,可以: uvm_config_db#(int)::set(null, {env.i_agt.sqr.get_full_name(),".*"}, "pre_num", 100);
- 但是在build_phase时,整棵UVM树还没有形成,使用env.i_agt.drv的形式进行引用会引起空指针的错误。所以,要想这么使用,有两种方法:
- 一种是所有的实例化工作都在各自的new函数中完成
- 第二种方式是将uvm_config_db::set移到connect_phase中
config_db的替代者
- config_db::set函数的第二个参数带来的不便,因此要尽量减少config_db的使用
- config_db设置的参数有两种:
- 一种是结构性的参数,如控制driver是否实例化的参数is_active
function void my_agent::build_phase(uvm_phase phase); super.build_phase(phase); if (is_active == UVM_ACTIVE) begin sqr = my_sequencer::type_id::create("sqr", this); drv = my_driver::type_id::create("drv", this); end mon = my_monitor::type_id::create("mon", this); endfunction // 对于这种参数,可以在实例化agent时同时指明其is_active的值: virtual function void build_phase(uvm_phase phase); super.build_phase(phase); if(!in_chip) begin i_agt = my_agent::type_id::create("i_agt", this); i_agt.is_active = UVM_ACTIVE; end … endfunction
- 非结构性的参数,如向某个driver中传递某个参数
uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 100); // 可以完全在build_phase之后的任意phase中使用绝对路径引用进行设置 function void my_case0::connect_phase(uvm_phase phase); env.i_agt.drv.pre_num = 100; endfunction
- 在top_tb中使用config_db对interface进行的传递,可以使用绝对路径的方式
unction void base_test::connect_phase(uvm_phase phase); env0.i_agt.drv.vif = testbench.input_if0; …
endfunction // 方式2: 使用静态变量实现 // 新建一个类,将此验证平台中所有可能用到的interface放入此类中作为成员变量 class if_object extends uvm_object; … static if_object me; static function if_object get(); if(me == null) begin me = new("me"); end return me; endfunction virtual my_if input_vif0; virtual my_if output_vif0; virtual my_if input_vif1; virtual my_if output_vif1;
endclass // 在top_tb中为这个类的interface赋值 module top_tb; … initial begin if_object if_obj; if_obj = if_object::get(); if_obj.input_vif0 = input_if0; if_obj.input_vif1 = input_if1; if_obj.output_vif0 = output_if0; if_obj.output_vif1 = output_if1; end endmodule // base_test的connect_phase(或build_phase之后的其他任一phase) 对所有的interface进行赋值 function void base_test::connect_phase(uvm_phase phase); if_object if_obj; if_obj = if_object::get(); v_sqr.p_sqr0 = env0.i_agt.sqr; v_sqr.p_sqr1 = env1.i_agt.sqr; env0.i_agt.drv.vif = if_obj.input_vif0; env0.i_agt.mon.vif = if_obj.input_vif0; env0.o_agt.mon.vif = if_obj.output_vif0; env1.i_agt.drv.vif = if_obj.input_vif1; env1.i_agt.mon.vif = if_obj.input_vif1; env1.o_agt.mon.vif = if_obj.output_vif1; endfunction
set函数的第二个参数的检查
- 可以在一定程度上(并不能检查所有!) 实现对第二个参数有效性的检查
function void check_all_config(); check_config::check_all(); endfunction
- 这个全局函数会调用check_config的静态函数check_all
`ifndef __PW_CHECK_CONFIG_SV__ `define __PW_CHECK_CONFIG_SV__ class check_config extends uvm_object; static uvm_component uvm_nodes[string]; static bit is_inited = 0; `uvm_object_utils(check_config) function new(string name = "check_config"); super.new(name); endfunction static function void init_uvm_nodes(uvm_component c); uvm_component children[$]; string cname; uvm_component cn; uvm_sequencer_base sqr; is_inited = 1; if(c != uvm_root::get()) begin cname = c.get_full_name(); uvm_nodes[cname] = c; if($cast(sqr, c)) begin string tmp; $sformat(tmp, "%s.pre_reset_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.reset_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.post_reset_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.pre_configure_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.configure_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.post_configure_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.pre_main_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.main_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.post_main_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.pre_shutdown_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.shutdown_phase", cname); uvm_nodes[tmp] = c; $sformat(tmp, "%s.post_shutdown_phase", cname); uvm_nodes[tmp] = c; end end c.get_children(children); while(children.size() > 0) begin cn = children.pop_front(); init_uvm_nodes(cn); end endfunction static function bit path_reachable(string scope); bit err; int match_num; match_num = 0; foreach(uvm_nodes[i]) begin err = uvm_re_match(scope, i); if(err) begin //$display("not_match: name is %s, scope is %s", i, scope); end else begin //$display("match: name is %s, scope is %s", i, scope); match_num++; end end return (match_num > 0); endfunction static function void check_all(); uvm_component c; uvm_resource_pool rp; uvm_resource_types::rsrc_q_t rq; uvm_resource_types::rsrc_q_t q; uvm_resource_base r; uvm_resource_types::access_t a; uvm_line_printer printer; c = uvm_root::get(); if(!is_inited) init_uvm_nodes(c); rp = uvm_resource_pool::get(); q = new; printer=new(); foreach(rp.rtab[name]) begin rq = rp.rtab[name]; for(int i = 0; i < rq.size(); ++i) begin r = rq.get(i); //$display("r.scope = %s", r.get_scope()); if(!path_reachable(r.get_scope)) begin `uvm_error("check_config", "the following config_db::set's path is not reachable in your verification environment, please check") r.print(printer); r.print_accessors(); end end end endfunction endclass function void check_all_config(); check_config::check_all(); endfunction `endif
- 这个函数先根据is_inited的值来调用init_nodes函数,将uvm_nodes联合数组初始化。is_inited和uvm_nodes是check_config的两个静态成员变量
class check_config extends uvm_object; static uvm_component uvm_nodes[string]; static bit is_inited = 0;
- 在init_nodes函数中使用递归的方式遍历整棵UVM树,并将树上所有的结点加入到uvm_nodes中。uvm_nodes的索引是相应结点的get_full_name的值,而存放的值就是相应结点的指针
- 初始化的工作只进行一次。当下一次调用此函数时将不会进行初始化。
- 在初始化完成后,check_all函数将会遍历config_db库中的所有记录。
- 对于任一条记录,检查其路径参数,并将这个参数与uvm_nodes中所有的路径参数对比,如果能够匹配,说明这条路径在验证平台中是可达的。
- 这里调用了path_reachable函数
- config_db::set的第二个参数支持通配符,所以path_reachable通过调用uvm_re_match函数来检查路径是否匹配。
- uvm_re_match是UVM实现的一个函数,它能够检查两条路径是否一样。当uvm_nodes遍历完成后,如果匹配的数量为0,说明路径根本不可达,此时将会给出一个UVM_ERROR的提示
- 由于要遍历整棵UVM树的结点,所以这个check_all_config函数只能在build_phase之后才能被调用,如connect_phase等
参考
张强UVM实战config_db