在验证环境的创建过程build phase中,除了组件的实例化,配置也是必不可少的。
为了验证环境的复用性,通过外部的参数配置,使得环境在创建时可以根据不同参数来选择创建的组件类型、组件实例数目、组件之间的连接以及组件的运行模式等。在更细致的环境调节(environment tuning)中有更多的变量需要配置,例如for-loop的阈值、字符串名称、随机变量的生成比重等。比起重新编译来调节变量,如果在仿真中可以通过变量设置来修改环境,那么就更灵活了,而UVM config机制正提供了这样的便捷。
传递virtual interface到环境中;设置单一变量值,例如int、string、enum等;传递配置对象(config object)到环境
UVM中的路径
一个component(如my_driver)内通过get_full_name()函数可以得到此component的路径:
在这幅图中,uwm_test_top实例化时的名字是uvm_test_top,这个名字是由UVM在run_test时自动指定的。uvm_top的名字是__top__,但是在显示路径的时候,并不会显示出这个名字,而只显示从uvm_test_top开始的路径。
路径的概念与通常的层次结构不太一样,虽然基本上它们是一样的。从图3-4中的my_casen看来,drv的层次结构是env.i_agt.drv,其相对于my casen的相对路径是env.i_agt.drv。如果drv在new时指定的名字不是drv,而是driver,即:
drv=my_driver::type_id::create("driver");
那么drv在my_casen看来,层次结构依然是env.i_agt.drv,但其路径变为了env.i_agt.driver。这种变量名与其实例化时传递的名字不一致的情况应该尽量避免。
config_db基础
set和get函数的参数
config_db机制用于在UVM验证平台间传递参数。它们通常都是成对出现的。set函数是寄信,而get函数是收信。如在某个测试用例的build_phase中可以使用如下方式寄信:
uvm_config_db#(int)::set(this,"env.i_agt.drv","pre_num",100);
其中第一个和第二个参数联合起来组成目标路径,与此路径符合的目标才能收信。第一个参数必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。第三个参数表示一个记号,用以说明这个值是传给目标中的哪个成员的,第四个参数是要设置的值。
在driver中的build_phase使用如下方式收信:
uvm_config_db#(int)::get(this,"","pre num",pre num);
get函数中的第一个参数和第二个参数联合起来组成路径。第一个参数也必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。一般的,如果第一个参数被设置为this,那么第二个参数可以是一个空的字符串。第三个参数就是set函数中的第三个参数,这两个参数必须严格匹配,第四个参数则是要设置的变量。
省略get语句
set与get函数一般都是成对出现,但是在某些情况下,是可以省略get语句的。 在之前提到介绍到与uvm_component相的宏时,曾经提及field_automation机制与uvm_component机制的结合。假设在my_driver有成员变量pre_num,把其使用uvm_field_int实现field automation机制:
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
只要使用uvm_field_int注册,并且在build phase中调用super.build_phase(),就可以省略在build_phase中的如下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。
跨层次的多重设置
假如uvm_test_top和env中都对driver的pre_num的值进行了设置。则以uvm_test_top值为准。因为UVM规定层欠越高,优先级越高。
非直线的设置和获取
在UVM树中,driver的路径为uvm_test_top.env.i_agt.drv。在uvm_test_top,env或者i_agt中,对driver中的某些变量通过config_db机制进行设置,称为直线的设置。但是若在其他component,如scoreboard中,对driver的某些变量使用config_db机制进行设置,则称为非直线的设置。
在UVM树中,build_phase是自上而下执行的,但是对于UVM树来说,scb与i_agt处于同一级别中,UVM并没有明文指出同一级别的build_phase的执行顺序。所以当my_driver在获取参数值时,my scoreboard的build phase可能已经执行了,也可能没有执行。应该避免非直线的设置这种情况的出现。
config_db机制对通配符的支持
在config_db::set操作时,其第二个参数都提供了完整的路径,但实际上也可以不提供完整的路径。config_db机制提供对通配符的支持。
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,"*i agt*","vif",input_if);
end
check_config_usege
config_db机制功能非常强大,能够在不同层次对同一参数实现配置。但它的一个致命缺点是,其set函数的第二个参数是字符串,如果字符串写错,那么根本就不能正确地设置参数值。假设要对driver的pre_num进行设置,但是在写第二个参数时,错把iagt写成了iatg。但是也还是一个字符串,System Verilog的仿真器也不会给出任何参数错误
针对这种情况,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
interface传递
interface传递可以很好地解决了连接硬件世界和软件世界。
而在之前SV验证模块中虽然SV可以通过层次化的interface的索引来完成了传递,但是这种方式不利于软件环境的封装和复用。
UVM的uvm_config_db使得接口的传递和获取彻底分离开来。
在实现接口传递的过程中需要注意:
- 接口传递应该发生在run_test)之前。这保证了在进入build phase之前,virtual interface已经被传递到uvm_config_db中。
- 用户应当把interface与virtual interface的声明区分开来,在传递过程中的类型应当为virtual interface,即实际接口的句柄。
interface intf1;
logic enable=0;
endinterface
class compl extends uvm component;
`uvm_component_utils(comp1)
virtual intf1 vif;
...
function void build_phase(uvm phase phase);
if(!uvm config db#(virtual intf1):: get(this,"","vif", vif)) begin
uvm_error("GETVIF","no virtual interface is assigned")
end
`uvm_info("SETVAL",$sformatf("vif. enable is %b before set", vif. enable), UVM LOW)
vif.enable=1;
`uvm info("SETVAL",$sformatf("vif. enable is b after set", vif. enable), UVM LOW)
endfunction
endclass
class testl extends uvm_test;
`uvm_component_utils(test1)
comp1(c1)
...
endclass
intf1 intf();
initial begin
uvm_config_db#(virtual intf1)::set(uvm root::get(),"uvm test top. cl","vif", intf);
run test("test1");
end
UVM_INFO @ 0:reporter [RNTST] Running test test1...
UVM_INFO @ 0:uvm_test_top.cl [SETVAL] vif.enable is 0 before set
UVM_INFO @ 0:uvm_test_top.cl [SETVAL] vif.enable is 1 after set
变量设置
在各个test中,可以在build phase对底层组件的变量加以配置,进而在环境例化之前完成配置,使得环境可以按照预期运行。
例如第一段所示
object传递
在test配置中,需要配置的参数不只是数量多,而且可能还分属于不同的组件。
那么如果对这么多层次中的变量做出类似上面的变量设置,那会需要更多的代码,容易出错还不易于复用,甚至底层组件的变量被删除后,也无法通过uvm_config_db::set()得知配置是否成功。
然而如果将每个组件中的变量加以整合,首先放置到一个uvm_object中,再对中心化的配置对象进行传递,那么将会更有利于整体环境的修改维护。
class configl extends uvm object;
int val1=1;
int str1="null";
uvm_object_uti1s(config1)
...
endclass
class comp1 extends uvm_component;
`uvm_component_uti1s(comp1)
configl cfg;
...
function void build_phase(uvm phase phase);
uvm_object_tmp;
uvm_config_db#(uvm_object):: get(this,"","cfg", tmp);
void'($cast(cfg, tmp));
uvm_info("SETVAL",$sformatf("cfg.vall is %d after get", cfg. val1), UVM_LOW)
uvm_info("SETVAL",$sformatf("cfg.str1 is %s after get", cfg. str1), UVM_LOW)
endfunction
endclass
父类句柄转为子类;因为需要访问子类里面的实例; 细节:对应的类型传递的是父类的句柄uvm_object;类型要严格一致;
class test1 extends uvm_test;
`uvm component utils(test1)
comp1 c1,c2;
config1 cfgl,cfg2;
...
function void build_phase(uvm_phase phase);
cfg1=config1::type id::create("cfg1");
cfg2=configl::type_id::create("cfg2");
cfg1.val1=30;
Cfg1.strl="c1";
Cfg2.val1=50;
cfg2.strl="c2";
uvm_config_db#(uvm_object):set(this,"cl","cfg", cfg1);
uvm_config_db#(uvm object):set(this,"c2","cfg", cfg2);
c1=comp1::type id::create("c1", this);
c2=comp1::type_id::create("c2", this);
endfunction
endclass
建议
在使用配置变量时,应当确保先进行uvm_config_db::get()操作,在获得了正确的配置值以后再使用。
应当尽量确保uvm_config_db::set()方法在相关配置组件创建前调用。这是因为只有先完成配置,相关组件在例化才可以得到配置值继而正确地例化。
在set()方法第一个参数使用当前层次的前提下,对于同一组件的同一个变量,如果有多个高层组件对该变量进行设置,那么较高层组件的配置会覆盖较低层的配置;但是如果是同一层次组件对该变量进行多次配置时,应该遵循后面的配置会覆盖前面的配置。
这篇笔记参考《UVM实战》、《芯片验证漫游指南》和某验证视频整理而成,仅作学习心得交流,如果涉及侵权烦请请告知,我将第一时间处理。
|