uvm_config_db将数据存入数据库中供set, get, 注意这里存进去了就是一个半全局的变量,如果有一处改变了就会对其他使用了相同值的变量造成影响,和全局变量效果是一样的。因此
1. 在对要传递的对象配置完毕后,尽量单向使用uvm_config_db get到的对象/变量,不要对其赋值(virtual interface除外,本来就希望要驱动DUT).
2. 一定需要赋值的,填好inst_name,不要用*通配,或者在field_name里以示区分。
3. 或者,通常传递的对象派生至uvm_object, 使用copy()函数将传递过来的对象赋值给本地对象,不要采用this.cfg = cfg这样的直接赋值方式。
^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.恶意卖萌分割线^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^
需要对之前的内容进行一些补充。
我们希望传递的内容分为两种,希望改变和不希望改变的(废话)。第一种包括interface, event tree, 第二种包括一些配置信息,你肯定不会希望这个模块收集覆盖率而别的模块因为意外的修改把这个半全局的coverage_enable位disable掉,导致覆盖率白跑。
上面说的第一条,只适用于不想改变的内容,第二条作用有限,实在希望隔离推荐采用第三条提到的方法,但是需要一些附加步骤来实现,下面说明。
这里做个实验:
class env_config;
int id;
int num[];
env_id_wrapper e_id;
function void copy (env_config rhs);
env_config rhs_;
if (!$cast(rhs_, rhs))
$finish();
id = rhs_.id;
e_id = rhs_.e_id;
num = rhs_.num;
endfunction : copy
endclass : env_config
这个config类里面有三个属性:一个内建类型属性,一个内建类型数组,一个object. 内部实现了一个copy函数, 暂时object直接传递句柄,后面会进一步展示。下面在同一层级上建立两个component: monitor和driver
class driver extends uvm_driver;
env_config cfg;
function new (string name = "driver", uvm_component parent);
super.new(name, parent);
endfunction : new
task run_phase (uvm_phase phase);
#10;
begin
$display(">>>>> id: %0d <<<<<", cfg.id);
$display(">>>>> e_id.id: %0d <<<<<", cfg.e_id.id);
$display(">>>>> size: %0d <<<<<", cfg.num.size);
end
endtask : run_phase
endclass : driver
class monitor extends uvm_monitor;
env_config cfg;
function new (string name = "monitor", uvm_component parent);
super.new(name, parent);
endfunction : new
task run_phase (uvm_phase phase);
phase.raise_objection(this);
#1;
// Modify config property here
cfg.id = 456;
cfg.e_id.id = 456;
cfg.num = new[2];
#20;
phase.drop_objection(this);
endtask : run_phase
endclass : monitor
这两个component的行为在run_phase中定义,driver在#10后打印config内的属性,而monitor从#1后待插入一系列修改操作-id都改为456, num的size改为2。做一个容器把他俩包起来:
class agent extends uvm_agent;
driver d;
monitor m;
function new (string name = "agent", uvm_component parent);
super.new(name, parent);
d = new("d", this);
m = new("m", this);
endfunction : new
function void build_phase (uvm_phase phase);
env_config cfg;
if (!uvm_config_db #(env_config)::get(this, "", "env_config", cfg)) begin
$display("CANNOT GET CONFIG");
$finish();
end
begin
env_config to_child_cfg;
to_child_cfg = new;
to_child_cfg.copy(cfg);
d.cfg = to_child_cfg;
m.cfg = to_child_cfg;
end
$display("Config children done @%0t", $time);
endfunction : build_phase
endclass : agent
在顶层把配置好的config给set一下:
cfg.id = 123;
cfg.e_id.id = 123;
cfg.num = new[3];
uvm_config_db #(env_config)::set(uvm_root::get(), "*", "env_config", cfg);
这里设置的id初始值均为123, 初始化num的size为3,看看是否被替换掉:
UVM_INFO @ 0: reporter [RNTST] Running test ...
Config children done @0
>>>>> id: 456 <<<<<
>>>>> e_id.id: 456 <<<<<
>>>>> size: 2 <<<<<
很明显,在直接赋值的情形下,所有内容都被替换掉了。这是因为copy函数里的$cast转换时只是进行了shallow copy, component的cfg句柄,to_child_cfg句柄以及agent中的cfg句柄地址相同,指向的内容当然也就相同。
接下来进行一个小小的改变,在agent传递cfg时分成两个不同的句柄, build_phase中:
function void build_phase (uvm_phase phase);
env_config cfg;
if (!uvm_config_db #(env_config)::get(this, "", "env_config", cfg)) begin
$display("CANNOT GET CONFIG");
$finish();
end
begin
env_config to_child_cfg0;
env_config to_child_cfg1;
to_child_cfg0 = new;
to_child_cfg0.copy(cfg);
to_child_cfg1 = new;
to_child_cfg1.copy(cfg);
d.cfg = to_child_cfg0;
m.cfg = to_child_cfg1;
end
$display("Config children done @%0t", $time);
endfunction : build_phase
这里把cfg分成两个句柄,再来打印一次:
Config children done @0
>>>>> id: 123 <<<<<
>>>>> e_id.id: 456 <<<<<
>>>>> size: 3 <<<<<
这次id和size都没有被修改,为什么object里的id还是被修改了呢?原因在于config函数中对于e_id的copy采用了直接赋值的方式,也就是前面埋下伏笔的红字。在直接赋值的情况下, 虽然to_child_cfg0与to_child_cfg1句柄已经不同,但是其内部的e_id句柄却是与agent里cfg的e_id句柄相同,这一点千万要注意。要解决这个问题,在config中的copy()函数,对于e_id的copy要调用e_id.copy()方法,而不要直接赋值,就可以把这种耦合隔离开来。
当然有时候需要的就是这种耦合,比如前面说的event tree, 所有tb的event资源是相同的才符合应用。
变与不变,取决于应用,理解才是最重要的。