interface中除了可以定义变量,还可以定义任务和函数。还可以在其中使用always和initial语句。
interface可以代替driver做很多事情,但是并不能代替driver做所有的事情。interface只适用于做一些低层次的转换,如8b10b转换、曼彻斯特编码等。这些转换动作是与transaction完全无关的。
使用interface代替driver的第一个好处是可以让driver从底层繁杂的数据处理中解脱出来,更加专注于处理高层数据。第二个好处是有更多的数据出现在interface中,这会对调试起到很大的帮助。
不过,当使用interface完成这些转换后,如果想构造这些转换异常的测试用例,则稍显麻烦。如构造一个8b10b转换的错误,需要在interface中加入一个标志位err_8b10b,根据此标志位的数据决定向数据线上发送何种数据。
而如果这种转换是在driver完成的,有两种选择,一是在正常的driver中加入异常driver的处理代码;二是重新编写一个全新的异常driver,将原来的driver使用factory机制重载掉。
无论是哪种方式都能实现其目的。相比来说,在interface上实现转换能够更有助于调试,这一优势完全可以弥补其劣势。
一般可能需要三种可变时钟:
实现可变时钟,可以使用config_db
,在测试用例中设置时钟周期:
function void my_case0::build_phase(uvm_phase phase);
uvm_config_db#(real)::set(this, "", "clk_half_period", 200.0);
endfunction
在top_tb中使用config_db::get
得到设置的周期:
initial begin
static real clk_half_period = 100.0;
clk = 0;
#1;
if(uvm_config_db#(real)::get(uvm_root::get(), "uvm_test_top", "clk_half_period", clk_half_period))
`uvm_info("top_tb", $sformatf("clk_half_period is %0f", clk_half_per iod), UVM_MEDIUM)
forever begin
#(clk_half_period*1.0ns) clk = ~clk;
end
end
这里采用之前说过的非线性的设置和获取,由于config_db::set
是在0时刻执行,而如果
config_db::get
也在0时刻执行,那么可能无法得到设置的数值,所以在top_tb中,在config_db::get
前有1个时间单位的延迟。
以上只适用第一种可变时钟的情况。对于第二种可变时钟,可以使用如下方式:
initial begin
static real clk_half_period = 100.0;
clk = 0;
fork
forever begin
uvm_config_db#(real)::wait_modified(uvm_root::get(), "uvm_test_to p", "clk_half_period");
void'(uvm_config_db#(real)::get(uvm_root::get(), "uvm_test_top", " clk_half_period", clk_
`uvm_info("top_tb", $sformatf("clk_half_period is %0f", clk_half_p eriod), UVM_MEDIUM)
end
forever begin
#(clk_half_period*1.0ns) clk = ~clk;
end
join
end
在测试用例中可以随着时间的变换而设置不同的时钟:
task my_case0::main_phase(uvm_phase phase);
#100000;
uvm_config_db#(real)::set(this, "", "clk_half_period", 200.0);
#100000;
uvm_config_db#(real)::set(this, "", "clk_half_period", 150.0);
endtask
但是,使用这种config_db的方式很难实现第三种可变时钟。要实现第三种时钟,可以专门编写一个时钟接口:
interface clk_if();
logic clk;
endinterface
在top_tb中实例化这个接口,并在需要时钟的地方以如下的方式引用:
clk_if cif();
dut my_dut(.clk(cif.clk),
.rst_n(rst_n),
为可变时钟从uvm_component派生一个类:
class clk_model extends uvm_component;
`uvm_component_utils(clk_model)
virtual clk_if vif;
real half_period = 100.0;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual clk_if)::get(this, "", "vif", vif))
`uvm_fatal("clk_model", "must set interface for vif")
void'(uvm_config_db#(real)::get(this, "", "half_period", half_perio d));
`uvm_info("clk_model", $sformatf("clk_half_period is %0f", half_peri od), UVM_MEDIUM)
endfunction
virtual task run_phase(uvm_phase phase);
vif.clk = 0;
forever begin
#(half_period*1.0ns) vif.clk = ~vif.clk;
end
在env中,实例化此类:
class my_env extends uvm_env;
clk_model clk_sys;
virtual function void build_phase(uvm_phase phase);
clk_sys = clk_model::type_id::create("clk_sys", this);
endfunction
endclass
在这种使用方式中,时钟接口被封装在了一个component中。在需要新的时钟模型时,只需要从clk_model派生一个新的类,然后在新的类中实现时钟模型。使用factory机制的重载功能将clk_model用新的类重载掉。通过这种方式,可以将时钟设置为任意想要的行为。
参考自张强《UVM实战》
公众号:程序员Marshall