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