目录
在Systemverilog搭建的验证平台中,需要对各组件进行参数配置,但是配置各组件必须得在各组件实例化之后才能配置参数,例如test中必须得执行env = new();才能配置env.i_agt.drv.pen_num = 10;。再比如接口指针,需要就需要为每个组件设定设定set_interface();方法,非常繁琐。
而UVM提供的config_db机制可在组件实例化前就设定好配置信息,这样就可在tb的initial块中就进行设定了。真正将这些配置信息落实在各component,是在testbench运行过程build_phase中。
1. component的路径索引 uvm_component::get_full_name();
在UVM:类库中提到了UVM树和各component路径索引的概念,此处再强调一下,config_db机制需要写明路径。

component路径索引就是UVM树中从树根到对应component的路径,使用uvm_component类的get_full_name()方法
即
function void my_agent::build_phase(uvm_phase);
super.build_phase(phase);
drv = my_driver::type_id::create("drv",this); //在i_agt的build_phase中这样例化drv
...
endfunction
function void my_driver::build_phase(uvm_phase);
super.build_phase(phase);
uvm_report_info("my_driver",get_full_name(),UVM_LOW); //消息打印路径索引
endfunction
//打印消息为:"uvm_test_top.env.i_agt.drv"
注意,打印的路径是从uvm_test_top实例开始的,这是因为开发者只需定义my_test,而UVM树根uvm_top实例是自动创建的无需开发者定义。
打印出来的路径中,取的是各component的name,而不是对象名称!
也就是说如果drv = my_driver::type_id::create("driver",this);,那么打印的路径就变成了"uvm_test_top.env.i_agt.driver",但绝不推荐这么做,强烈建议各component的name与对象名保持一致!!
1.1. 获取component索引信息的其他方法
get_full_name()可获取索引路径,还有其他一些方法可获取该component的其他信息。
//...\questasim\verilog_src\uvm-1.2\src\base\uvm_component.svh
function void uvm_component::get_children(ref uvm_component children[$]);
function int uvm_component::get_first_child(ref string name);
function int uvm_component::get_next_child(ref string name);
function uvm_component uvm_component::get_child(string name);
function int uvm_component::get_num_children();
function string uvm_component::get_full_name();
function uvm_component uvm_component::get_parent ();
function int unsigned uvm_component::get_depth();
//...\questasim\verilog_src\uvm-1.2\src\base\uvm_root.svh
extern static function uvm_root uvm_root::get(); //即返回指向实例uvm_top的uvm_root类句柄,注意是static类型
2. uvm_config_db 机制
接下来讲config_db机制,主要用于build_phase阶段各component的配置,例如各种参数、接口指针等等。
相比于Systemverilog(SV)下的testbench中各组件的属性配置,config_db机制有的巨大好处在于:各component实例化之前就可进行配置
例如
class my_env;
my_agent i_agt;
my_agent o_agt;
my_model mdl;
my_scoreboard scb;
...
function void build_phase();
i_agt.generator.loop = 5; //my_env向其子组件drv作配置,需要先执行drv = new();
...
endfunction
endclass
但UVM中的config机制就可以在run_test();之前就将配置“预定”。
2.1. uvm_config_db::set()与uvm_config_db::get()
用法很简单,先使用uvm_config_db::set()将配置信息写好,相应的component使用uvm_config_db::get()获取配置信息,整个过程类似“寄信”和“收信”
先上函数原型
//...\questasim64_2020.1\verilog_src\uvm-1.2\src\base\uvm_config_db.svh
class uvm_config_db#(type T=int) extends uvm_resource_db#(T);
...
static function bit get(uvm_component cntxt, //返回类型是bit,表示是否成功get
string inst_name,
string field_name,
inout T value);
...
static function void set(uvm_component cntxt,
string inst_name,
string field_name,
T value);
static function bit exists(uvm_component cntxt, string inst_name,
string field_name, bit spell_chk=0);
...
endclass
● Type T:要配置的成员类型,默认为int
● uvm_component cntxt :set或get方法的发起者component
● string inst_name:从发起者component到要配置的目标component,在UVM树中的索引路径。
例如
uvm_config_db#(int)::set(uvm_root::get(),"uvm_test_top.env.i_agt.drv","pre_num",100);的set方法,发起者是uvm_top对象,目标是drv
● string field_name:域名称,一种标记,但一般为要配置的成员变量名。
set方法和get方法中的目标component相同、field_name相同时才能正确构成set和get一对。
● T value:set方法中,表示将要为field_name成员设定的值。在get方法中,表示要赋值的成员
见下例
function void my_test::build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id_create("env",this);
uvm_config_db#(int)::set(this,"env.i_agt.drv","pre_num",100);
//以下均是为目标drv的pre_num配置100
//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",100);
//uvm_config_db#(int)::set(uvm_root::get(),"uvm_test_top.env.i_agt.drv","pre_num",100);
...
endfunction
function void my_driver::build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(int)::get(this,"","pre_num",pre_num))
uvm_report_info("CONFIG","PRE_NUM CONFIG FAILED!",UVM_LOW);
//注意这一句与下面这句话的区别
//uvm_config_db#(int)::get(uvm_root::get(),"uvm_test_top.env.i_agt.drv","pre_num",pre_num);
//uvm_config_db#(int)::get(uvm_test_top,"env.i_agt.drv","pre_num",pre_num);
...
endfunction
上面代码中,那几个set均是为drv.pre_num进行配置100,但发起者不同
对于那几个get而言,由于get是定义在
my_driver::build_phase(phase);中的,所以如果该UVM树中有好几个my_driver对象,那么每一个my_driver对象在执行build_phase时都会调用一次get代码。
所以在上述情况下,uvm_config_db#(int)::get(this,"","pre_num",pre_num);就表示创建的每一个my_driver对象都要为自己的pre_num成员get一个配置值。
而如果是后面那两句get则表示,只为my_driver类对象drv的pre_num成员get一个配置值。
使用
uvm_component::check_config_usage()检查是否有set的成员未被get
● bit spell_chk:是否检查当前的cntxt、inst_name和field_name是否被set过,1表示开启检查0不开启检查
2.2. 多重uvm_config_db::set:先看发起者,再看时间
意思是如果有多个地方对同一component的同一成员进行了set,那该成员的值最终是多少呢?
UVM设定的原则是先看发起者,发起者层次越高,优先级越高。再看时间,时间越晚,优先级越高
例如下面的例子,求问对象drv的pre_num成员最终被配置为多少?
function void my_driver::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(int)::get(this,"","pre_num",pre_num);
...
endfunction
function void my_test::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",500); //发起者为uvm_top
uvm_config_db#(int)::set(this,"env.i_agt.drv","pre_num",100); //发起者为uvm_test_top
uvm_config_db#(int)::set(this.env,"i_agt.drv","pre_num",999); //发起者为env
...
endfunction
function void my_env::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
答案是200。
可以看出my_test::build_phase(uvm_phase phase);中有三个set都是给drv.pre_num配置的,但第一个set语句发起者是uvm_root::get()表示树根优先级最高,所以下面的uvm_test_top或env对drv进行配置都无效,尽管他们都是晚配置。
然后在my_env::build_phase(uvm_phase phase);中的set的发起者也是uvm_root::get(),此时层次相同。UVM遵循晚时间的原则,即哪个set执行最晚哪个set有效。根据phase机制,my_env的build_phase执行晚于uvm_test_top的build_phase,所以pre_num最终被配置为200。
多次 get
config机制允许set一次然后多次get,也就是说并不是get之后UVM就删除了该记录
uvm_config_db#(int)::get(this,"","pre_num",pre_num1);
uvm_config_db#(int)::get(this,"","pre_num",pre_num2); //会得到与pre_num1相同的数值
禁止 set 或 get 给父类句柄
其实就是源码中T必须一致,下面这个例子就会报类型不匹配的错误
尽管允许父类句柄指向子类对象,但在get中不可这么用
class father;
//...
endclass
class son extends father;
//...
endclass
father f = new();
son s = new();
uvm_config_db#(father)::set(uvm_root::get(),"uvm_test_top","f",s); //报错,类型不匹配
uvm_config_db#(son)::get(this,"","s",f); //报错,类型不匹配
2.3. 用法
最推荐的写法就是发起者直接定为this
uvm_config_db#(int)::set(this,"env.i_agt.drv","pre_num",100);
uvm_config_db#(int)::get(this,"","pre_num",pre_num);
一般来说,tb中的env都属于不动产,真正对env进行控制和激励产生都是来自uvm_test_top(权限最高),但它要对很多很多具有嵌套关系的组件进行配置,工作量很大,直接配置的化找索引路径可能都要找半天。
考虑上述情况,建议uvm_test_top在build_phase中完成全部参数配置。
例如接口interface,常见的就是从tb中set给test,test再分发
interface 配置:tb的initial块内set
对接口一定要使用config_db机制进行配置。但interface作为连接uvm_test_top与dut的媒介,从复用性考虑,interface一般并不在component的build_phase进行配置,而是在testbench的initial块内且run_test前进行set
例如
class my_driver extends uvm_driver;
virtual intf vif; //接口指针
int pre_num = 10;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(int)::get(this,"","pre_num",pre_num);
uvm_config_db#(virtual intf)::get(this,"","vif",vif);
...
endfunction
endclass
class my_test extends uvm_test;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(virtual intf)::get(this,"","vif",i_intf);
uvm_config_db#(virtual intf)::set(this,"env.i_agt.drv","vif",i_intf);
env = my_env::type_id::create("env",this);
...
endfunction
endclas
module tb;
//...
initial begin
uvm_config_db#(virtual intf)::set(uvm_root::get(),"uvm_test_top","vif",i_intf);
uvm_config_db#(virtual intf)::set(uvm_root::get(),"uvm_test_top","vif",o_intf);
run_test("my_test");
end
endmodule
2.4. 聚合参数
假如说一个验证平台,参数特别多,这些参数得在base_test::build_phase()完成配置,这个没办法改变是吧。
然后,每个参数再来一个config::set语句,又是一大堆,会很壮观,怎么办呢?聚合参数
顾名思义,将全部配置参数(例如interface什么的)都放在一个叫cfg的类中,然后将这个cfg实例set出去,能节省很多代码量。
config对象定义如下:
class config extends uvm_object;
virtual chnl_intf chnl_vif;
virtual fmt_intf fmt_vif;
virtual apb_intf apb_vif;
rand int fmt_drv_fifo_bound;
rand int fmt_drv_data_nidles;
rand int chnl_drv_data_nidles;
rand int chnl_drv_pkt_nidles;
//...
`uvm_object_utils_begin(config)
`uvm_field_int(fmt_drv_fifo_bound,UVM_ALL_ON)
//...
`uvm_object_utils_end
function new(string name = "config ");
super.new(name);
endfunction
endclass
定义好了config类之后,就可以用啦!定义在base_test类,这个毋庸置疑把。
class base_test extends uvm_test;
env e;
config cfg; //config实例
//...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
e = env::type_id:create("e",this);
cfg = config::type_id:create("cfg");
if(!uvm_config_db#(virtual chnl_intf)::get(this,"","chnl_vif", chnl_vif))
`uvm_error("INTF_ERROR",$sformatf("interface handle at %s is NULL",get_full_name()))
if(!uvm_config_db#(virtual reg_intf)::get(this,"","apb_vif", apb_vif))
`uvm_error("INTF_ERROR",$sformatf("interface handle at %s is NULL",get_full_name()))
//...
cfg.chnl_vif = chnl_vif; //配置cfg
cfg.apb_vif = apb_vif;
cfg.rgm = mcdf_rgm::type_id::create("rgm");
//...
cfg.fmt_drv_fifo_bound = 4096;
//...
uvm_config_db#(config)::set(this,"*","cfg",cfg); //set给env
endfunction
endclass
class chnl_driver extends uvm_driver #(chnl_trans);
config cfg;
//...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(agent_config)::get(this,"","cfg",cfg);
endfunction
task run_phase(uvm_phase phase);
chnl_trans req,rsp;
seq_item_port.get_next_item(req);
@(cfg.chnl_vif.clk); //直接用
cfg.chnl_vif.drv_ck.data <= req.data;
//...
endtask
endclass
cfg类不好一点的是每个component能看到其他component的参数,存在篡改别人参数的风险
注意在build_phase内需要各component自己例化的成员不能放在cfg里,例如uvm_analysis_port等
不建议将reg model也放入config里,因为会出现文件循环嵌套的问题。即rgm_pkg包含reg_pkg,reg_pkg包含config_pkg,config_pkg(含有rgm)包含rgm_pkg
层级配置
如果参数太多,还可以将cfg划定层次,例如mcdf_cfg包含了scb_cfg、chnl_cfg等等,毕竟一堆参数放在一个cfg里也会麻木。
但如果组件太多,什么scb、refmod、driver等等,config_db一个一个set也会麻烦。
则可以选择使用下面的命令,进行层级set。例如test层set给env层,env层set给agent层,agent层set给driver、monitor层。
`uvm_confg_db#(mcdf_cfg)::set(this,"*","cfg",cfg);
一句
uvm_config_db#(config)::set(this,"*","cfg",cfg);就全部set住了
但是要注意一,build_phase中,先例化,再set
如果你config_set的第二个参数是*,就必须得先例化再set,反过来就没孩子就set,错误
个人觉得这边是借鉴了UVM的build_phase思路的,就是每个component只能例化自己的儿子,所以cfg作set的时候也只能set给自己的儿子
动态配置
指的是在run_phase实现验证平台参数修改,如何修改呢,依靠sequence
因为sequence::body()就是在run_phase运行的,所以在body()内通过p_sequencer索引cfg,实现动态修改,见例码
task consistence_basic_virtual_sequence::do_data();
p_sequencer.cfg.data_nidles = 2;
p_sequencer.cfg.pkt_nidles = 5;
fork
`uvm_do_on(chnl_seq[0],p_sequencer.chnl_sqr[0]);
`uvm_do_on(chnl_seq[1],p_sequencer.chnl_sqr[1]);
`uvm_do_on(chnl_seq[2],p_sequencer.chnl_sqr[2]);
join
#10us;
p_sequencer.cfg.data_nidles = 2;
p_sequencer.cfg.pkt_nidles = 10;
//...
endtask
需要说明的是,driver的参数动态配置也可以通过transaction实现,但如果我想修改其他component,例如monitor、scoreboard之类的呢?所以选择cfg更合适。
3. 原理:“键键值”型关联数组
有一个全局的uvm_resource_pool类对象uvm_resources,可看作“键 - 键 - 值”型关联数组,通过双键来取值。
set方法本质上是在uvm_resources中创建一个键键值,第一个键是索引路径(由发起者cntxt.get_full_name()和inst_name组成)、第二个键是field_name,第三个值就是T类型的value
所以一下两句话就是等价的
uvm_config_db#(int)::set(this,"comp1.comp2","num",num);
//上面这一行等价于
int resources[string][string];
resources[{get_full_name(),"comp1.comp2"}]["num"] = num;
get方法本质上就是通过键键,从uvm_resources中获取值
uvm_config_db#(int)::get(this,"","num",get_num);
//上面这一行等价于
resources[get_full_name()]["num"] = get_num;
exists就是已知键键,查看uvm_resources中是否有相应的值
这也就解释了为什么config_db机制能在component实例化之前就能进行配置。先将而配置信息存储至uvm_resources,等实例化之后需要时,在取出来。
同时该原理也表明,只要键键一致就一定能配置成功,例如下面代码uvm_config_db#(int)::set(uvm_test_top.env,"i_agt.drv.efgh","abcd",120);和uvm_config_db#(int)::get(drv,"efgh","abcd",pre_num);也能实现为drv.pre_num配置120
但建议写成真实UVM树索引路径和待配置成员,同时别忘了发起者cntxt决定了该set/get语句的权威
仅类型不同的 set 和 get 也会成功
两局set 或 两句 get 中的 两个键相同,但是类型不同也能够区分出来,而不会覆盖,看下面的例子
class test;
//...
uvm_config_db#(apb_config)::set(this,"*","cfg",tcfg);
uvm_config_db#(apb_master_config)::set(this,"*","cfg",tmst_cfg);
uvm_config_db#(apb_slave_config)::set(this,"*","cfg",tslv_cfg);
//...
endclass
class env;
//...
uvm_config_db#(apb_config)::get(this,"","cfg",ecfg);
uvm_config_db#(apb_master_config)::get(this,"","cfg",emst_cfg);
uvm_config_db#(apb_slave_config)::get(this,"","cfg",eslv_cfg);
//...
endclass
上面这几个set和get都会成功,并且每个set语句之间不会相互覆盖,每个get语句之间也不会相互覆盖。
其实原理很简单,虽然这3个set和这3个get的双键分别相同,但是类型不同。所以对于UVM来说,其实是创建、索引了三个不同的关联数组,底层可以这么理解
class test;
//...
apb_confg resource1[string][string];
resource1[{get_full_name(),*}]["cfg"] = tcfg;
apb_master_config resource2[string][string];
resource2[{get_full_name(),*}]["cfg"] = tmst_cfg;
apb_slave_config resource3[string][string];
resource3[{get_full_name(),*}]["cfg"] = tslv_cfg;
//...
endclass
class env;
//...
ecfg = resource1[get_full_name()]["cfg"];
emst_cfg = resource2[get_full_name()]["cfg"];
eslv_cfg = resource3[get_full_name()]["cfg"];
//...
endclass