在验证环境的创建过程build phase中,除了组件的实例化过程,配置阶段也是必不可少的。为了验证环境的复用性,通过外部的参数配置,使得环境在创建的时候可以根据参数的不同来选择创建的组件、组件的实例个数、组件之间的连接以及组件的运行模式等等。在更细致的环境调节(environment tuning)中,有更多的变量需要配置,例如for-loop的阈值、字符串名称、随机变量的生成比重等等。 无论是配置哪些参数,用户都可以在编译时间或者仿真时间来设置。对于编译时间中要调整这些变量,可以通过修改参数、或者引入预处理指令(compiler directive,例如`ifdef/`ifndef/`else/`elsif/`endif)来修改。比起重新编译而言,在仿真中可以通过变量设置来修改环境,就显得更灵活了,而UVM config机制正是提供这么做的方法。

UVM提供了uvm_config_db的配置类以及几种很方便的变量设置方法来实现在仿真时的环境控制。常见的uvm_config_db的使用方式包括有:

  • 传递virtual interface到环境中。
  • 设置单一变量值,例如int、string、enum等。
  • 传递配置对象(config object)到环境。

下面的几段例码分别来对这几种方式进行说明。

interface传递

首先来看看interface的传递,通过这种方便的传递方式很好地解决了连接硬件世界和软件世界。而在之前关于SV的核心篇章中,读者们可以看到,虽然SV可以通过层次化interface的索引来完成传递,但是这种方式不利于软件环境的封装和复用。下面这种方式,使得接口的传递和获取彻底分离开来,而在后台对virtual interface的传递立下功劳的便是uvm_config_db。

interface intf1;
logic enable = 0;
endinterface

module config_interface;
import uvm_pkg::*;
`include "uvm_macros.svh"

class comp1 extends uvm_component;
`uvm_component_utils(comp1)
virtual intf1 vif;
function new(string name, uvm_component parent);
    super.new(name, parent);
endfunction

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 test1 extends uvm_test;
`uvm_component_utils(test1)
comp1 c1;
function new(string name, uvm_component parent);
    super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
    c1 = comp1::type_id::create("c1", this);
endfunction
endclass

intf1 intf();
initial begin
    uvm_config_db#(virtual intf1)::set(uvm_root::get(), "uvm_test_top.c1", "vif", intf);
    run_test("test1");
end

endmodule

输出结果

UVM_INFO @ 0: reporter [RNTST] Running test test1...
UVM_INFO @ 0: uvm_test_top.c1 [SETVAL] vif.enable is 0 before set
UVM_INFO @ 0: uvm_test_top.c1 [SETVAL] vif.enable is 1 after set

从上面这个例子可以看到,接口的传递从硬件世界到UVM环境中的传递可以通过uvm_config_db来实现。在实现过程中需要注意几点:

  • 接口的传递应该发生在run_test()之前。这保证了在进入build phase之前,virtual interface已经传递进入uvm_config_db中。
  • 用户应当把interface与virtual interface的声明区分开来。在传递过程中的类型应当为virtual interface,即实际接口的句柄。

变量设置

在各个test中,可以在build phase阶段对底层组件中的变量加以配置,进而完成在环境例化之前完成配置,使得环境可以按照预期运行。

module config_variable;
import uvm_pkg::*;
`include "uvm_macros.svh"

class comp1 extends uvm_component;
`uvm_component_utils(comp1)
int val1 = 1;
string str1 = "null";
function new(string name, uvm_component parent);
    super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
    `uvm_info("SETVAL", $sformatf("val1 is %d before get", val1), UVM_LOW)
    `uvm_info("SETVAL", $sformatf("str1 is %s before get", str1), UVM_LOW)
    uvm_config_db#(int)::get(this, "", "val1", val1);
    uvm_config_db#(string)::get(this, "", "str1", str1);
    `uvm_info("SETVAL", $sformatf("val1 is %d after get", val1), UVM_LOW)
    `uvm_info("SETVAL", $sformatf("str1 is %s after get", str1), UVM_LOW)
endfunction
endclass

class test1 extends uvm_test;
`uvm_component_utils(test1)
comp1 c1;
    
function new(string name, uvm_component parent);
    super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
    uvm_config_db#(int)::set(this, "c1", "val1", 100);
    uvm_config_db#(string)::set(this, "c1", "str1", "comp1");
    c1 = comp1::type_id::create("c1", this);
endfunction
endclass

initial begin
    run_test("test1");
end

endmodule

输出结果

UVM_INFO @ 0: reporter [RNTST] Running test test1...
UVM_INFO @ 0: uvm_test_top.c1 [SETVAL] val1 is 1 before get
UVM_INFO @ 0: uvm_test_top.c1 [SETVAL] str1 is null before get
UVM_INFO @ 0: uvm_test_top.c1 [SETVAL] val1 is 100 after get
UVM_INFO @ 0: uvm_test_top.c1 [SETVAL] str1 is comp1 after get

config object传递

在实际的test配置中,需要配置的参数不单单数量多,而且还分属于不同的组件。那么,如果对这么多层次中的变量做出类似上面的单一变量设置,一方面需要更多的代码,这就容易出错,不易于阅读,另外一方面也不易于复用,毕竟底层组件的变量有添加或者减少,通过uvm_config_db::set是无法得知是否设置成功的。因此,如果将每个组件中的变量加以整合,首先放置到一个uvm_object中用于传递,那么将会更有利于整体进行配置。

import uvm_pkg::*;
`include "uvm_macros.svh"

class config1 extends uvm_object;
int val1 = 1;
int str1 = "null";
`uvm_object_utils(config1)

function new(string name = "config1");
    super.new(name);
endfunction
endclass

class comp1 extends uvm_component;
`uvm_component_utils(comp1)
config1 cfg;

function new(string name, uvm_component parent);
    super.new(name, parent);
endfunction

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.val1 is %d after get", cfg.val1), UVM_LOW)
    `uvm_info("SETVAL", $sformatf("cfg.str1 is %s after get", cfg.str1), UVM_LOW)
endfunction
endclass

class test1 extends uvm_test;
`uvm_component_utils(test1)
comp1 c1, c2;
config1 cfg1, cfg2;

function new(string name, uvm_component parent);
   super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
    cfg1 = config1::type_id::create("cfg1");
    cfg2 = config1::type_id::create("cfg2");
    cfg1.val1 = 30;
    cfg1.str1= "c1";
    cfg2.val1 = 50;
    cfg2.str1= "c2";
    uvm_config_db#(uvm_object)::set(this, "c1", "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

initial begin
    run_test("test1");
end

endmodule

输出结果

UVM_INFO @ 0: reporter [RNTST] Running test test1...
UVM_INFO @ 0: uvm_test_top.c1 [SETVAL] cfg.val1 is 30 after get
UVM_INFO @ 0: uvm_test_top.c1 [SETVAL] cfg.str1 is c1 after get
UVM_INFO @ 0: uvm_test_top.c2 [SETVAL] cfg.val1 is 50 after get
UVM_INFO @ 0: uvm_test_top.c2 [SETVAL] cfg.str1 is c2 after get