virtual sequence是控制多个sequencer中激励生成的序列。由于sequence,sequencer和driver集中在单个接口上,因此几乎所有测试平台都需要virtual sequence来协调不同接口之间的激励和交互。virtual sequence在子系统或系统级别的测试台上也很有用,可以使单元级别的sequence以协调的方式运行。下图从概念上展示了这一点,其中virtual sequence具有三个sequencer的句柄,这些sequencer连接到driver,以连接到DUT的三个独立接口。然后,virtual sequence可以在每个接口上生成subsequence,并在相应的subsequencer上运行它们。
要实现UVM driver和sequence,我们需要先定义sequence item类,然后sequence和driver类才能将其用作事务进行通信。以下是sequence item,sequence和driver类的示例代码。可以使用类似的方法使用UVM处理任何编程代码。
class rw_txn extends uvm_sequence_item;
rand bit[7:0] addr; //address of transaction
typedef enum {READ, WRITE} kind_e; //read or write type
rand kind_e sram_cmd;
rand bit[7:0] datain; //data
//Register with factory for dynamic creation
`uvm_object_utils(rw_txn)
//constructor
function new (string name = "rw_txn");
super.new(name);
endfunction
//Print utility
function string convert2string();
return $psprintf("sram_cmd=%s addr=%0h datain=%0h,sram_cmd.name(),addr,datain);
endfunction
endclass
class sram_sequence extends uvm_sequence#(rw_txn) ; //Register with factory
`uvm_object_utils(sram_sequence)
function new(string name ="sram_sequence");
super.new(name);
endfunction
//Main Body method that gets executed once sequence is started
task body();
rw_txn rw_trans; //Create 10 random SRAM read/write transaction and send to driver
repeat(10) begin
rw_trans = rw_txn::type_id::create(.name("rw_trans"),.contxt(get_full_name()));
start_item(rw_trans); //start arbitration to sequence
assert (rw_trans.randomize());//randomize item
finish_item(rw_trans); //send to driver
end
endtask
endclass
class sram_driver extends uvm_driver#(rw_txn);
`uvm_component_utils(sram_driver)
virtual sram_if vif; //Interface that groups dut signals
function new(string name,uvm_component parent = null);
super.new(name,parent);
endfunction
//Build Phase
//Get the virtual interface handle from config_db
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual sram_if)::get(this, "", "sram_if", vif))
begin
`uvm_fatal("SRAM/DRV", "No virtual interface specified")
end
endfunction
//Run Phase
//Implement the Driver-Sequencer API to get an item
//Based on if it is Read/Write - drive on SRAM interface the corresponding pins virtual
task run_phase(uvm_phase phase);
super.run_phase(phase);
this.vif.read_enable <= '0;
this.vif.write_enable <= '0;
forever begin
rw_txn tr;
@ (this.vif.master_cb);
//First get an item from sequencer
seq_item_port.get_next_item(tr);
@ (this.vif.master_cb); //wait for a clock edge
uvm_report_info("SRAM_DRIVER ", $psprintf("Got Transaction%s",tr.convert2string()));
//Decode the SRAM Command and call either the read/write function
case (tr.sram_cmd)
rw_txn::READ: drive_read(tr.addr, tr.dataout);
rw_txn::WRITE: drive_write(tr.addr, tr.datain);
endcase
//Handshake DONE back to sequencer
seq_item_port.item_done();
end
endtask: run_phase
//Drive the SRAM signals needed for a Read
virtual protected task drive_read(input bit [31:0] addr, output logic [31:0] data);
this.vif.master_cb.addr <= addr;
this.vif.master_cb.write_enable <= '0;
this.vif.master_cb.read_enable <= '1;
@ (this.vif.master_cb);
this.vif.master_cb.read_enable <= '0;
data = this.vif.master_cb.dataout;
endtask: drive_read
//Drive the SRAM signals needed for a Write
virtual protected task drive_write(input bit [31:0] addr, input bit [31:0] data);
this.vif.master_cb.addr <= addr;
this.vif.master_cb.write_enable <= '1;
this.vif.master_cb.read_enable <= '0;
@ (this.vif.master_cb);
this.vif.master_cb.write_enable <= '0;
endtask: drive_write
endclass
UVM中的“工厂”是一个特殊的查找表,其中记录了所有UVM组件和事务。在UVM中创建组件和事务对象的推荐方法是使用工厂方法create()。使用工厂创建对象可以很方便地实现类型覆盖,而不必更改验证环境的结构或修改验证环境的代码。
UVM中用于创建组件或事务对象的推荐方法是使用内置方法::type_id::create(),而不是直接调用构造函数new()。
create方法在内部调用工厂,查找所请求的类型,然后调用构造函数new()来创建对象。这样可以轻松地重写类型,可以指定类的类型(基类,一个或派生类),并且所有其他测试平台组件将能够创建该类类型的对象而无需任何代码更改。
new()构造函数将仅创建给定类型的对象,因此使用new()将不允许在运行时更改类类型。因此,使用new()意味着测试平台代码将需要根据要使用的不同类型进行更改。
使用uvm_object_utils()宏注册uvm_sequence类,uvm_component_utils()宏注册uvm_component类,下面是示例代码
class test_seq_c extends uvm_sequence;
`uvm_object_utils(test_seq_c)
class test_driver_c extends uvm_component;
`uvm_component_utils(test_driver_c)
工厂是UVM中使用的一种特殊查找表,用于创建组件或事务类型的对象。使用工厂创建对象的好处是,测试平台构建可以在运行时决定创建哪种类型的对象。因此,一个类可以用另一个派生类替换,而无需任何实际代码更改。为确保此功能,建议所有类都在工厂注册。如果不注册到工厂,则将无法使用工厂方法::type_id::create()构造对象。
UVM工厂允许在构造时将一个类替换为另一个派生类。通过将一个类替换为另一个类而不需要编辑或重新编译测试平台代码,这对于控制测试平台的行为很有用。
类型覆盖意味着每次在测试平台层次结构中创建组件类类型时,都会在其位置创建替代类型。这适用于该组件类型的所有实例。
另一方面,实例覆盖意味着仅覆盖组件类的特定实例。组件的特定实例由该组件在UVM组件层次结构中的位置进行索引。
由于只有UVM组件类可以在UVM测试平台中具有层次结构,因此实例覆盖只能作用于组件类,而sequence(或者说object)只能覆盖类型。
不,只有UVM_component类是UVM测试平台层次结构的一部分,从而可以使用实例覆盖。sequence_item或sequence不是UVM测试平台层次结构的一部分,因此只能使用类型覆盖来覆盖,类型覆盖将覆盖该类型的所有对象。
uvm_objection类提供了一种在多个组件和sequence之间共享计数器的方法。每个组件/sequence可以异步地"raise"和"drop" objections,这会增加或减少计数器值。当计数器达到零(从非零值开始)时,将发生"all dropped"情况。
objection机制最常用于UVM phase机制中,以协调每个run_time phase的结束。在phase中启动的用户进程会首先raise objections,并在进程完成后drop objections。当一个phase中的所有进程都放下objections时,该phase的objections计数器清零。这种“all dropped”的情况说明每个进程都同意结束该phase。
下面是一个示例,说明如何在sequencer(my_sequencer)上启动sequence(my_test_sequence)并在sequence执行后drop objections
task run_phase( uvm_phase phase);
phase.raise_objection( this );
my_test_sequence.start(my_sequencer);
phase.drop_objection( this );
endtask
如果由于超出最大时间的某些错误而导致测试无法进行,那么仿真超时机制有助于停止仿真。在UVM中,set_global_timeout(timeout)是一个便捷函数,用于将uvm_top.phase_timeout变量设置为超时值。如果run()阶段在该这个时间内之前没有结束,则仿真将停止并报告错误。
在顶层模块中调用此函数,该模块会按以下方式启动测试
module test;
initial begin
set_global_timeout(1000ns);
end
initial begin
run_test();
end
endmodule
与基于module的测试平台(所有module静态地存在于层次结构中)不同,基于类的测试平台需要管理不同对象的创建以及这些对象中各种task和function的执行。phase是基于类的测试平台中重要的概念,它具有一致的测试平台执行流程。从概念上讲,测试执行可以分为以下阶段-配置,创建测试平台组件,运行时激励和测试结束。UVM为每一个阶段中定义了标准phase。
UVM使用标准phase来排序仿真过程中发生的主要步骤。有三组阶段,按以下顺序执行。
在UVM中,所有组件(例如test,Env,Agent,Driver,Sequencer)都基于uvm_component类,并且组件始终具有层次结构。build_phase()方法是uvm_component类的一部分,用于从父组件构造所有子组件。因此,要构建测试平台层次结构,始终需要先拥有一个父对象,然后才能构造其子对象,并可以使用build_phase进一步构造其子对象。因此,build_phase()总是自顶向下执行。
例如:uvm_test类调用build_phase,构造该test的所有uvm_env组件,而每个uvm_env类的build_phase()应该构造属于该uvm_env的所有uvm_agent组件,然后继续。对于所有其他phase,调用顺序实际上并不重要。所有组件的run_phase()是并行运行的。
phase_ready_to_end(uvm_phase phase)是组件类的回调方法,当相应phase的所有objection均被放下并且该phase将要结束时,会调用该方法。组件类可以使用此回调方法来定义phase即将结束时需要执行的任何功能。
例如,如果某个组件希望将phase结束延迟到某个条件,甚至在所有objections均被放下之后,也可以使用此回调方法来完成。
再比如,如果一个激励或应答sequence正在运行,在主sequence结束之前,则可以使用main_phase()中的phase_ready_to_end()回调方法来停止那些激励或应答sequence。
uvm_config_db机制支持在不同的测试平台组件之间共享配置和参数。用名为uvm_config_db的配置数据库启用该功能。任何测试台组件都可以使用变量,参数,对象句柄等填充配置数据库。
其他测试平台组件可以从配置数据库访问这些变量,参数,对象句柄,而无需真正知道其在层次结构中的位置。
例如,顶层测试平台模块可以通过uvm_config_db储存虚接口句柄。然后,任何uvm_driver或uvm_monitor组件都可以查询uvm_config_db获取此虚接口的句柄,并将其用于通过接口实际访问信号。
get()和set()是用于从uvm_config_db存储或检索信息的主要方法。任何验证组件都可以使用set()方法为config_db存储一些配置信息,还可以控制哪些其他组件对相同信息具有可见性。可以将其设置为具有全局可见性,或者仅对一个或多个特定测试平台组件可见。get()函数从数据库中检查与参数匹配的共享配置。
get()和set()方法的语法如下:
uvm_config_db#(<type>)::set(uvm_component context, string inst_name, string field_name,<type> value)
uvm_config_db#(<type>)::get(uvm_component context, string inst_name, string field_name, ref value)
context指定从中调用get / set的当前类或组件。inst_name是从中调用get / set的组件实例的名称。field_name是在config_db中设置/获取的对象或参数或变量的名称。<type>标识config_db中设置/获取的配置信息的类型。对于对象句柄,type是类名,而对于其他变量,type是数据类型名,代表了该变量的类型。
建议不要在UVM中这么做。通常,较高级别的组件使用句柄设置配置数据库,而较低级别的组件则使用get / set方法获取它们。
实例化DUT和接口的顶级testbench模块在uvm_config_db中例化虚接口。然后,测试类或UVM组件层次结构中的任何其他组件可以使用get()方法查询uvm_config_db,获得此虚接口的句柄并将其用于访问信号。
下面展示了如何进行此操作。为APB总线master实例化了DUT和物理接口,然后,将虚接口句柄设置到uvm_config_db。
module test;
logic pclk;
logic [31:0] paddr;
//Instantiate an APB bus master DUT
apb_master apb_master(.pclk(pclk),*);
//Instantiate a physical interface for APB interface
apb_if apb_if(.pclk(pclk), *);
initial begin //Pass this physical interface to test class top //which will further pass it down to env->agent->drv/sqr/mon
uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top", "vif",apb_if);
end
endmodule
下面是APB Env类,该类使用uvm_config_db中的get()方法检索在顶层测试模块中设置的虚接口。
class apb_env extends uvm_env;
`uvm_component_utils(apb_env);
//ENV class will have agent as its sub component
apb_agent agt;
//virtual interface for APB interface
virtual apb_if vif; //Build phase - Construct agent and get virtual interface handle fromtest and pass it down to agent
function void build_phase(uvm_phase phase);
agt = apb_agent::type_id::create("agt", this);
if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))begin
`uvm_fatal("config_db_err", "No virtual interface specified forthis env instance")
end
uvm_config_db#(virtual apb_if)::set( this, "agt", "vif", vif);
endfunction: build_phase
endclass : apb_env
UVM具有phase机制,由一组构建阶段,运行阶段和检查阶段组成。在run()阶段进行实际的测试仿真,并且在此phase中,每个组件都可以在开始时raise_objection和drop_objection。一旦所有组件都drop_objection,则run_phase完成,然后所有组件的check_phase执行,然后测试结束。
这是正常仿真结束的方式,但是如果某些组件由于设计或测试平台中的错误而挂起,则仿真超时也可以终止run_phase。当run_phase启动时,并行超时计时器也会启动。如果在run_phase完成之前超时计时器达到指定的超时限制,则将发出一条错误消息,然后将执行run_phase之后的所有phase,最后测试结束。