前面大体说明了uvm的结构,类型关系以及启动过程。现在最关心的大概也就是uvm是如何把一串激励发送给被测对象的了。对于传统的基于verilog的验证平台,基本套路就是各种module中的task相互调用,最终把激励施加到顶层模块例化的被测对象中,但是具体步骤各个团队又有挺大差异。基于UVM的平台,本质其实也是这样,但是它把各种调用关系进行了更严格的限定,所以所有使用uvm的团队,实现的验证平台都更加相似。下面以之前的apb/spi接口为例具体讲解一下。这里把之前的图再贴过来一下。之前已经说过有关sequencer,driver,monitor,env等的大体功能,这些可以被认为是验证平台的硬体,除此之外,还有一些在这之间流动的软体需要近一步说明一下。uvm_sequence_item就是这种软体最基本的构造单元。比如可以定义apb端的sequence_item,其中包括读写信息,数据地址这些成员。class apb_transfer extendsuvm_sequence_item; rand bit[31:0]addr; randapb_direction_enumdirection; rand bit[31:0]data; rand intunsigneddelay = 0; constraint c_direction { direction inside {APB_READ, APB_WRITE }; } constraint c_delay { delay <= 10="" ;="">=>//这里需要将其中的变量注册一下,这些后边具体讲 `uvm_object_utils_begin`uvm_field_int`uvm_field_enum`uvm_field_int `uvm_object_utils_end function new ;super.new; endfunction//对其他方法没有进行特别的设定endclass : apb_transfer比uvm_sequence_item稍微高一层极的信息单元是uvm_sequence,这个可以认为是一连串的uvm_sequence_item,可以表达一个完整的操作,下面是读fifo的一个例子。class read_rx_fifo_seq extends uvm_sequence#;//此类从uvm_sequence派生得到,uvm_sequence是一个参数化的类,这里类型赋值为apb_transfer,它被定义在apb_pkg这个包中 functionnew;super.new;endfunction // Registersequence with a sequencer`uvm_object_utils rand bit[31:0] read_addr; rand intunsigned del = 0; rand intunsigned num_of_rd; constraintnum_of_rd_ct { ;="">=> constraintdel_ct { ;="">=> constraintaddr_ct {; }//一个sequence最重要的行为就是body,里边定义这个信息序列都发送什么东西 virtual taskbody;`uvm_info, $sformatf, UVM_LOW)response_queue_error_report_disabled = 1;for ="">read_addr =`RX_FIFO_REG;//rx fifo address`uvm_do_withendendtask//uvm_do_with这个宏负责把各个最基本的sequence_item加上约束发送出去。endclass : read_rx_fifo_seq如何把这个sequence发送出去呢?这就需要在testcase里边把这个sequence通过sequencer发出去。class read_rx_fifo_test extends uvm_test; `uvm_component_utils spi_apb_env spi_apb_env_0;//test中例化env function new;super.new; endfunction : new virtual function void build_phase;super.build_phase;spi_apb_env_0 = spi_apb_env::type_id::create; endfunction : build_phasevirtual taskmain_phase;super.main_phase;//执行基类的任务read_rx_fifo_seq_inst =read_rx_fifo_seq::type_id::create;//产生一个seqread_rx_fifo_seq_inst.start;//将seq通过sqr发送 endtaskendclass : read_rx_fifo_testsequencer得到了这个序列,就要把它交给driver,然后driver把其中的信息元素放置到与dut连接的接口上。这样就将需要的激励施加给了被测对象。class apb_master_driver extends uvm_driver #; // 连接driver与dut的虚接口. virtual apb_if vif; function new ;super.new; endfunction : new // 在外部定义以下任务 extern virtual function voidbuild_phase; extern virtual function voidconnect_phase; extern virtual task run_phase; extern virtual protected taskget_and_drive; extern virtual protected task drive_transfer; extern virtual protected taskdrive_address_phase ; extern virtual protected task drive_data_phase;endclass : apb_master_driverfunction void apb_master_driver::connect_phase; super.connect_phase; if ::get)`uvm_error,".vif"})//将虚接口通过配置从顶层取出来,并赋值给vifendfunction : connect_phasetask apb_master_driver::run_phase; get_and_drive;endtask : run_phase// 在执行时一直在从sqr取item并且赋值给viftask apb_master_driver::get_and_drive; while begin forkbeginforever begin@)seq_item_port.get_next_item;//从sqr取到itemdrive_transfer;//发送seq_item_port.item_done;//告知sqr使其产生下一个itemendendjoin_anydisable fork; endendtask : get_and_drive//具体一个发送过程task apb_master_driver::drive_transfer ; drive_address_phase;//发送地址 drive_data_phase;//发送数据endtask : drive_transfertask apb_master_driver::drive_address_phase ; int slave_indx; slave_indx =cfg.get_slave_psel_by_addr; vif.paddr <=>=> vif.psel <=>=><> vif.penable <=>=> if begin vif.prwd<=>=>end else begin vif.prwd<=>=> vif.pwdata<=>=> end @;endtask : drive_address_phasetask apb_master_driver::drive_data_phase ; vif.penable <=>=> @; if begin trans.data =vif.prdata; end vif.penable <=>=>vif.psel<=>=>endtask : drive_data_phase再重复一下这个基本的步骤,在test的main_phase中,由基本的sequence_item组成的sequence被放到sequencer上执行,sequencer将它转交给driver,其后driver通过虚接口将具体信号施加到被测对象上,实现uvm中激励的产生传输与施加过程。这里就产生了两个问题,一个是sequencer如何将sequence_item转交给driver,另一个是driver如何通过虚接口将具体信号施加到被测对象上。这里具体解释对于第一个问题,我们在driver中有这么2句seq_item_port.get_next_item;seq_item_port.item_done;这里的seq_item_port就是driver与sequencer通信的接口。在uvm_driver中可以看到这个定义为 uvm_seq_item_pull_port #seq_item_port;这里的REQ,RSP都是对应uvm_driver的sequence_item。而uvm_seq_item_pull_port是一个uvm专用的tlm口同样,在uvm_sequencer中有 uvm_seq_item_pull_imp #seq_item_export;而在上层的uvm_agent中有 virtual function voidconnect_phase;drv.seq_item_port.connect; endfunction这里drv跟sqr分别是例化的uvm_driveruvm_sequencer的名字,可以看到他们两个实质是通过这个tlm接口来传递item的。下面图大致说明了这个链接建立以及传送数据的过程。首先在build后的connect 时段,会确立drv.seq_item_port与sqr.seq_item_export的链接,也就是检查port与export的一一对应关系,并将与port对应的export映射给m_if,而后每次执行driver中的get_next_item与item_done实际就是执行了sequencer中的get_next_item与item_done。而这实际上就是往一个队列中不断存数与取数的过程。详细代码在uvm_sequencer中接下来再看第2个问题,driver通过虚接口驱动信号的过程。这里是apb接口的定义interface apb_if ;parameterPADDR_WIDTH = 32;parameterPWDATA_WIDTH = 32;parameterPRDATA_WIDTH = 32; logic [PADDR_WIDTH-1:0]paddr;logicprwd; logic [PWDATA_WIDTH-1:0] pwdata;logicpenable; logic[15:0]psel; logic [PRDATA_WIDTH-1:0] prdata;logicpslverr;logicpready;endinterface : apb_if下面是顶层TB中的一段 reg clock; reg reset; apb_if apb_if_0; //例化接口 dut_dummy dut,.apb_reset,.apb_if);//例化被测对象,链接前面定义的接口 initial beginuvm_config_db#::set;//通过config_db将 apb_if_0这个实际的接口,传送到验证平台下层各组件的虚假口中run_test;//运行测试 end这里的 uvm_config_db#::set对应之前driver里边的uvm_config_db#::get也就是在顶层set,底层get,然后通过uvm_config_db这个类似数据库的玩意,实现从顶层module到底层class中接口的链接,从而driver中的信息流进dut里边。