UVM实战笔记(二)

第二章 一个简单的UVM验证平台

2.1 DUT

本章的DUT(Design Under Test,待测设计,也可叫做DUV,Design Under Verification)定义如下:

//my_dut.v

module dut(	  clk,
	 		rst_n,
	 		  rxd,
			rx_dv,
  	 		  txd,
			tx_en);
			
input 				clk;
input 	 	 	  rst_n;
input   [7:0]       rxd;		    //receive data             
input		 	  rx_dv;			//receive enable signal
output  [7:0]   	txd;			//transfer data
output  		  tx_en;			//transfer enable signal

reg 	[7:0]       txd;
reg  			  tx_en;

always@(posedge clk)
begin
    //reset
	if(!rst_n)
	begin
		txd 	<= 8'b0;
		tx_en   <= 1'b0;
	end
	else
	begin
		txd 	<= rxd;
		tx_en   <= rx_dv;
	end
end
endmodule

DUT通过rxd接收数据,通过txd发送出去。rx_dv为输入信号的有效性指示,tx_en是发送数据的有效性指示。

2.2 验证平台的组成

验证用于找出DUT的bug,这个过程通常是把DUT放入一个验证平台中实现的。验证平台的组件包括:

  • Driver:驱动器,用于产生激励。验证平台要模拟DUT的各种真实使用情况,这意味这要给DUT施加各种激励(正常/异常激励)。

  • Scoreboard/Checker(下文用Scoreboard,简写Scb):检查板,根据DUT的输出来判断DUT的行为是否与预期相符合。

  • Monitor:监测器,收集DUT的输出并把他们传递给Scb。

  • Reference Model(简写Rfm):参考模型,模拟硬件DUT的功能,给出期望值并发送给Scb。

在这里插入图片描述

2.3 只有driver的测试平台

UVM是一个类库,几乎所有的东西都是使用类(class)来实现。driver,monitor,reference model,scoreboard等组成部分都是类。类中包含函数(function)和任务(task),通过这些函数和任务可以完成driver的输出激励功能,完成monitor的检测功能,完成参考模型的计算功能,完成scoreboard的比较功能。要实现一个功能时,首先应该想到的是从UVM的某个类派生一个新的类。所以使用UVM第一条准则验证平台中所有的组件应该派生自UVM中的类

//my_driver.sv

class my_driver extends uvm_driver;
	function new(string name = "my_driver", uvm_component parent = null);
		super.new(name, parent);
	endfunction
	extern virtual task main_phase(uvm_phase phase);
endclass

task my_driver::main_phase(uvm_phase phase);
	top_tb.rxd <= 8'b0;
	top_tb.rx_dv <= 1'b0;
    
    //wait until rst_n turns high
    while(!top_tb.rst_n)	@(posedge top_tb.rst_n);
    
    //rst_n is high, start to drive simulation
    //for cycle transfer one random value of 0~255 to rxd every clk
	for(int i=0;i<256;i++)
	begin
		@(posedge top_tb.clk);
		top_tb.rxd <= $random(0, 255);
		top_tb.rx_dv <= 1'b1;
        //use uvm macro to print information, similar to $display in SV
		`uvm_info("my_driver", "data is drived", UVM_LOW)
	end
    
    //simulation finished turn the rx_dv low
	@(posedge top_tb.clk);
	top_tb.rx_dv <= 1'b0;
endtask
  • 简单起见,先说明各组件的执行流程,至于为什么这么执行后续文章会说明。driver组件会执行其main_phase任务,激励就按照main_phase的代码进行发送。
  • `uvm_info为uvm的打印信息宏,在这里简单理解对标SV的$display函数,后文详细介绍。

以上为my_driver的定义,如果要使用my_driver必须首先实例化my_driver(OOP特性不再赘述,对OOP特性不了解的可以找一个OOP语言学习下,推荐C++)。

//top_tb.sv

//pre-compile instruction
//declare time unit and time precious
`timesacle 1ns/1ps

//include uvm macro head file(file replace)
`include "uvm_macros.svh"

//import UVM standard package
import uvm_pkg::*;

`include "my_driver.sv"

module top_tb;
    
    reg clk;
    reg rst_n;
    reg [7:0] rxd;
    reg rx_dv;
    wire [7:0] rxd;
    wire tx_en;
    
    //instance dut(connect dut port to corresponding signal)
    dut my_dut(.clk(clk),
               .rst_n(rst_n),
               .rx_dv(rx_dv),
               .txd(txd),
               .tx_en(tx_en));
    
    //initial block to define simulation process
    initial
    begin
        my_driver drv;
        //instance my_dviver
        drv = new("drv", null);
		//call drv.main_phase to run simulation
        drv.main_phase(null);
        $finish();
    end
    
    //initial block to generate system clock
    initial
     begin
         clk = 0;
         forever
         begin
             #100 clk = ~clk;
         end
     end
    
    //initial block to generator DUT reset signal
    initial
    begin
        rst_n = 1'b0;
        #1000;
        rst_n = 1'b1;
    end
            
endmodule

2.3.1 加入factory机制

使用factory之前需要使用`uvm_component_utils()进行宏注册。宏注册完成的事情非常多,其中之一是将类登记在UVM内部维护的一张表中,这张表就是实现factory功能实现的基础。需要对driver.sv和top_tb.sv做以下修改。

//my_driver.sv

class my_driver extends uvm_driver;
	
	//macor utils
	`uvm_component_utils(my_driver)
        
	function new(string name = "my_driver", uvm_component parent = null);
		super.new(name, parent);

		//add debug information
		`uvm_info("my_driver", "new is called", UVM_LOW);
	endfunction
	extern virtual task main_phase(uvm_phase phase);
endclass
    
task my_driver::main_phase(uvm_phase phase);
	`uvm_info("my_driver", "main_phase is called", UVM_LOW)
     ...
endtask
//top_tb.sv

module top_tb;
...
	//call run_test to run simulation
	initial
	begin
		run_test("my_driver");
	end
endmodule
//simulation output
new_is called
main_phase is called
  • 首先在my_driver定义中使用`uvm_component_utils进行了宏注册。在top_tb中取代drv.main_phase(null)的直接调用方式,使用run_test直接运行仿真run_test会创建(隐式调用new函数)一个my_drvier的实例,并且会自动调用my_driver的main_phase。这种由一个类名(字符串)创建一个类的实例,这就是uvm_component_utils宏注册带来的效果。所有的uvm_component及其派生类都应该使用uvm_component_utils进行宏注册。
  • 另外一个地方时main_phase自动调用了。在UVM验证平台中,只要一个类使用uvm_component_utils注册并且此类被实例化了,那么这个类的main_phase就会被自动调用。其实实现driver等于实现其main_phase任务,所以在driver中最终要的是实现其main_phase任务。

但是运行上述仿真,只输出了打印信息,main_phase中发送的值并没有被打印,这涉及到UVM中的objection机制。

2.3.2 加入objection机制

UVM中objection机制来控制验证平台的关闭。在第一个例子中调用了$finish来显式关闭平台。实际上在每个phase中,UVM会检查是否有objection被提起(raise_objection)。如果有那么等待这个objection被撤销(drop_objection)后停止仿真。如果没有,则马上结束当前phase

//my_driver.sv

//add objection to main_phase
task my_driver::main_phase(uvm_phase phase);
	phase.raise_objection(this);

	`uvm_info("my_driver", "main_phase is called", UVM_LOW)
	top_tb.rxd <= 8'b0;
	top_tb.rx_dv <= 1'b0;
    
    while(!top_tb.rst_n)	@(posedge top_tb.rst_n);
    
	for(int i=0;i<256;i++)
	begin
		@(posedge top_tb.clk);
		top_tb.rxd <= $random(0, 255);
		top_tb.rx_dv <= 1'b1;
		`uvm_info("my_driver", "data is drived", UVM_LOW)
	end
    
	@(posedge top_tb.clk);
	top_tb.rx_dv <= 1'b0;
        
    phase.drop_objection(this);
endtask

在加入了objection之后,激励就会执行。这里可以简单理解为drop_objection是finish的替代形式,不同的是在drop_objection之前必须先调用raise_objection。raise_objection和drop_objection语句总是成对存在

另外一个注意地方是raise_objection语句必须在main_phase中第一个消耗仿真时间的语句之前。如$display语句时不消耗仿真时间的,这些语句可以放在raise_objection之前,但是类似@(posedge top.clk)等语句是消耗放着年时间的,raise_objection之前。如果将消耗仿真时间的语句放在raise_objection之前,那么objection机制将失效,此phase不会执行。

2.3.3 加入virtual interface

在以上的例子中,driver中的等待时钟时间(@posedge top.clk),给DUT中输入端口赋值(top.rx_dv <= 1’b1)都是使用绝对路径绝对路径的使用大大减弱了验证平台的可移植性。从根本上说,应该尽量杜绝在验证平台中使用绝对路径。

其一是使用宏(Macro)

//my_driver.sv

`define TOP top_tb
task my_driver::main_phase(uvm_phase phase);
	phase.raise_objection(this);

	`uvm_info("my_driver", "main_phase is called", UVM_LOW)
	`TOP.rxd <= 8'b0;
	`TOP.rx_dv <= 1'b0;
    
    while(!`TOP.rst_n)	@(posedge `TOP.rst_n);
    
	for(int i=0;i<256;i++)
	begin
		@(posedge `TOP.clk);
		`TOP.rxd <= $random(0, 255);
		`TOp.rx_dv <= 1'b1;
		`uvm_info("my_driver", "data is drived", UVM_LOW)
	end
    
	@(posedge `TOP.clk);
	`TOP.rx_dv <= 1'b0;
        
    phase.drop_objection(this);
endtask

这样,当修改路径时,只需要修改宏的定义即可

另一种方法是使用interface。在SV中使用interface来连接验证平台与DUT的端口

//interface.sv

//define interface
interface my_if(input clk, input rst_n);
	logic [7:0] data;
	logic valid;
endinterface

定义了interface后,在top_tb中实例化DUT时,就可以直接使用。

//top_tb.sv
...
my_if input_if(clk, rst_n);
my_if output_if(clk, rst_n);

dut my_dut(.clk(clk),
		   .rst_n(rst_n),
		   .rxd(input_if.data),
		   .rx_dv(input_if.valid),
		   .txd(output_if.data),
		   .tx_en(output_if.valid));
...

那么如何在driver中连接端口呢?由于driver是一个类,类中不能有interface,而应该使用virtual interface

class my_driver extends uvm_driver;
	virtual my_if vif;
endclass

在class中声明了virtual interface后就可以在main_phase中使用如下方式驱动信号:

//my_driver.sv

task my_driver::main_phase(uvm_phase phase);
	phase.raise_objection(this);

	`uvm_info("my_driver", "main_phase is called", UVM_LOW)
	vif.data <= 8'b0;
	vif.valid <= 1'b0;
    
    while(!vif.rst_n)	@(posedge vif.clk);
    
	for(int i=0;i<256;i++)
	begin
		@(posedge vif.clk);
		vif.data <= $random(0, 255);
		vif.valid <= 1'b1;
		`uvm_info("my_driver", "data is drived", UVM_LOW)
	end
    
	@(posedge vif.clk);
	vif.valid <= 1'b0;
        
    phase.drop_objection(this);
endtask

加入了virtual interface后可以看出代码中的绝对路径已经全部消除,增加了代码的可移植性

最后一个问题是,如何把top_tb中的input_if和my_driver中的vif对应起来?不能通过直接赋值。因为在top_tb中,通过run_test语句建立一个my_driver的实例,但是如何引用这个实例?不能直接引用,其原因是UVM通过run_test语句实例化了一个脱离top_tb层次结构的实例,建立了一个新的层次结构。

对于这种脱离了top_tb层次结构,同时又期望在top_tb中对其进行赋值操作的实例,UVM引入了config_db机制。在config_db机制中,分为set和get两步操作,所谓set操作,可以简单理解为“寄信”,而get操作则相当于“收信”。在top_tb中执行set操作:

initial
begin
	uvm_config_db #(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end

在my_driver中,执行get操作:

virtual function void build_phase(uvm_phase phase);
	super.build_phase(phase);
	`uvm_info("my_driver", "build_phase is called", UVM_LOW)
	if(!uvm_config_db #(virtual my_if)::get(this, "", "vif", vif))
		`uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
endfuntion

这里引入了build_phase,与main_phase一样,build_phase也是UVM内建的一个phase。当UVM启动后,会自动执行build_phase。在build_phase中出现了uvm_fatal宏,uvm_fatal宏是一个类似uvm_info的宏,但是只有两个参数。当uvm_fatal打印完信息后,会直接调用个V的finish函数来结束仿真。uvm_fatal的出现表示验证平台出现了重大问题无法继续进行下去,必须停止仿真并做相应的检查。

config_db的set和get都有四个参数。这两个函数的第三个参数必须完全一致set函数的第四个参数表示要将哪个interface通过config_db传递给my_driverget函数的第四个参数表示把得到的interface传递给my_driver的成员变量set函数的第二个参数表示的是路径的索引在top_tb中通过run_test创建了一个my_driver的实例,这个实例的名字是uvm_test_top无论传递给run_test的参数是什么,创建的实例名字都为uvm_test_top

set和get函数使用::是因为这两个函数都属于静态函数。而uvm_config_db #(virtual my_if)则是一个参数化的类,其参数就是要寄信的类型,这里是virtual my_if。

2.3 为验证平台加入其它组件

2.3.1 加入transaction

以上的操作都是基于信号级的,本节开始将引入reference model,monitor,scoreboard等组件,在这些组件间,信息的传递是基于transaction(事务)的。

transaction是一个抽象的概念,一般来说,物理协议中的数据交换都是以帧或者包为单位的。通常在一帧或一个包中要定义好各参数,每个包的大小不一样。很少会有协议以bit或byte为单位进行数据交换,以以太网为例,每个包的大小至少是64byte,包中包括源地址,目的地址,包的类型,整个包的CRC校验数据等。transaction就是用来模拟这种实际情况,一个transaction就是一个包。不同的验证平台会有不同的transaction。

//my_transaction.sv

class my_transaction extends uvm_sequence_item;
	rand bit [47:0] dmac;			//destination address
	rand bit [47:0] smac;			//source address
	rand bit [15:0] ether_type;		//internet type
	rand byte 		pload[];		//load package size
	rand bit [31:0] crc;			//crc check code
	
	constraint pload_cons{
		pload.size >= 46;
		pload.size <= 1500;
	}
	
	function bit[31:0] calc_crc();
		return 32'h0;
	endfuntion
	
	function void post_randomize();		//callback function of randomize
		crc = calc_crc;
	endfunction
	
	`uvm_object_utils(my_transaction)
	
	function new(string name="my_transaction");
		super.new(name);
	endfunction
endclass

有两点需要注意:

  • UVM中所有的transaction基类都为uvm_sequence_item只有从uvm_sequence_item派生的transaction才可以使用UVM中强大的sequence机制
  • 没有使用uvm_component_utils来进行factory注册,而是使用了uvm_object_utils。从根本上来说,my_transaction与my_driver是不同的。在整个仿真期间,my_driver是一直存在的,my_transaction不同,它有生命周期。他在仿真的某一时间产生,经过driver驱动,在经过Rfm处理,最终由Scb比较完成后,其生命周期就结束了。一般来说,这种类都是派生自uvm_object或者uvm_object的派生类。uvm_transaction的祖先是uvm_object,所以使用uvm_object_utils宏来进行宏注册。
//my_driver.sv

task my_driver::main_phase(uvm_phase phase);
	my_transaction tr;
	for(int i=0;i<2;i++)
	begin
		asset(tr.randomize() with {pload.size == 200;});
		driver_one_pkg(tr);
	end
endtask

task my_driver::drive_one_pkg(my_transaction tr);
	bit [47:0] tmp_data;
	bit [7:0]  data_q[$];
	
	tmp_data = tr.dmac;
	//store tmp_data to data_q by 8-bit
	for(int i=0;i<6;i++)
	begin
		data_q.push_back(tmp_data[7:0]);
		tmp_data = (tmp_data >> 8);
	end
	...
	tmp_data = tr.crc;
	for(int i=0;i<4;i++)
	begin
		data_q.push_back(tmp_data[7:0]);
		tmp_data = (tmp_data >> 8);
	end
	`uvm_info("my_driver", "begin to drive one pkg", UVM_LOW)
	repeat(3) @(posedge vif.clk);

	//drive simulation to DUT
	while(data_q.size() > 0)
    begin
        @(posedge vif.clk);
		vif.valid <= 1'b1;
        vif.data <= data_q.pop_front();
    end
        
    @(posedge vif.clk);
	vif.valid <= 1'b0;
    `uvm_info("my_driver", "end driver one pkg", UVM_LOW)
endtask

2.3.2 加入environment

uvm_env是一个容器类。在这个容器类中实例化driver,monitor,Ref和Scb等。在调用run_test时,传递的参数不再是my_driver,而是这个uvm_env(即让UVM自动创建uvm_env的实例)。

//my_env.sv

class my_env extends uvm_env;
	my_driver drv;
	function new(string name = "my_env", uvm_component parent);
		super.new(name, parent);
	endfunction
	
	virtual function void build_phase(uvm_phase phase);
		super.build(phase);
		//use factory to instance drv
		drv = my_driver::type_id::create("drv", this):
	endfunction
	
	`uvm_component_utils(my_env)
endclass
  • 所有env应该派生自uvm_env,且与my_driver一样,容器类在仿真中也是一直存在的,使用uvm_component_utils宏来实现factory的注册。
  • 在my_env的build_phase中使用了factory的实例化方法type_name::type_id::create来实例化drv。只有使用factory注册过的类才能使用这种方法来实例化,而后文中讲的factory重载也必须通过create的方法来创建(使用new方法创建的实例化不能使用factory重载)。create实例化时传入两个参数,“drv”表示实例的名称,this表示该实例的父节点(类似new函数中的parent参数)。通过父节点的形式,UVM建立了属性的组织结构。在这个树形的组织结构中,由run_test创建的实例是数根(root,这里是uvm_env),并且树根的名字是固定的,为uvm_test_top。
//top_tb.sv

initial
begin
	run_test("my_env");
end

initial
begin
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.drv","vif", input_if);
end

由于加入了my_env,使得验证平台的层次结构发生了变化。所以在top_tb中使用config_db机制传递vif时要改变相应的路径为(uvm_test_top.drv,其中uvm_test_top为run_test自动创建的实例名,而drv为my_driver实例化时传入的实例名),run_test的参数也变成了my_env。

在树根之后会有树叶,树叶的实现需要在my_env的build_phase中手动实现。无论树根还是树叶,都必须由uvm_component或者其派生类继承而来。

加入my_env后的验证平台树如下:

在这里插入图片描述

2.3.3 加入monitor

monitor用于监测DUT的行为。driver负责把transaction级别的数据转换成DUT的端口级别并驱动DUT。而monitor的行为与其相反,用于收集DUT的端口数据,并将其转换成transaction交给后续的组件,如Rfm,Scb等处理。

//my_monitor.sv

class my_monitor extends uvm_monitor;
	virtual my_if vif;

	`uvm_component_utils(my_monitor)
        
	function new(string name = "my_monitor", uvm_component parent = null);
		super.new(name, parent);
	endfunction
        
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
			`uvm_fatal("my_monitor", "virtual interface must be set for vif!!!")
	endfunction
            
	extern task main_phase(uvm_phase phase);
	extern task collection_one_pkt(my_transaction tr);
endclass

task my_monitor::main_phase(uvm_phase phase);
	my_transaction tr;
	while(1)
	begin
		tr=new("tr");
		collection_one_pkt(tr);
	end
endtask

task my_monitor::collect_one_pkt(my_transaction tr);
	bit [7:0] data_q[$];
	int psize;

	while(1)
	begin
		@(posedge vif.clk);
		if(vif.valid) break;
	end
        
	`uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);

	while(vif.valid)
	begin
		data_q.push_back(vif.data);
		@(posedge vif.clk);
	end
        
	for(int i=0;i<6;i++)
	begin
		tr.dmac = {tr.dmac[39:0], data_q.pop_front()};
	end
        
	...
        
	for(int i=0;i<4;i++)
	begin
   		tr.crc = {tr.crc[23:0], data_q.pop_front()};
	end
        
	`uvm_info("my_monitor", "end collect one pkt, print it:", UVM_LOW)
 	tr.my_print();
endtask

需注意以下几点:

  • 所有的monitor都应该派生自uvm_monitor。
  • 与my_driver相似,my_monitor中也需要有一个virtual my_if。
  • uvm_monitor在整个仿真中是一直存在的,所以它是一个component,使用uvm_component_utils注册。
  • monitor需要时刻收集数据,所以main_phase使用while(1)循环来实现的。
//my_transaction.sv

function void my_print();
	$display("dmac = %0h", dmac);
	$display("smac = %0h", smac);
	$display("ether_type = %0h", ether_type);
	for(int i=0;i<pload.size;i++)
	begin
		$display("pload[%0d]=%0h", i, pload[i]);
	end
	$display("crc=%0h", crc);
endfunction

当完成monitor类的定义后,可以在env中对其实例化

//my_env.sv

class my_env extends uvm_env;
	my_driver drv;
	my_monitor i_mon;
	my_monitor o_mon;
	
	function new(string name = "my_env", uvm_component parent);
		super.new(name, parent);
	endfunction
	
	virtual function void build_phase(uvm_phase phase);
		super.build(phase);
		drv = my_driver::type_id::create("drv", this);
		i_mon = my_monitor::type_id::create("i_mon", this);
		o_mon = my_monitor::type_id::create("o_mon", this);
	endfunction
	
	`uvm_component_utils(my_env)
endclass

这里实例化两个monitor,一个用于监测DUT的输入,一个用于监测DUT的输出。其实driver也可以将输入数据发送给refm,这里推荐使用输入monitor来检测

在env实例化monitor后,要在top_tb中使用config_db将input_if和output_if传递给两个monitor:

//top_tb.sv

initial
begin
	uvm_config_db #(virtual my_if)::set(null, "uvm_test_top.drv", "vif", input_if);
	uvm_config_db #(virtual my_if)::set(null, "uvm_test_top.i_mon", "vif", input_if);
	uvm_config_db #(virtual my_if)::set(null, "uvm_test_top.o_mon", "vif", onput_if);
end

加入monitor后的验证平台树如下:

在这里插入图片描述

2.3.4 加入uvm_agent

agent:代理类,属于容器类(就是一个空盒子,本身没什么作用,用于装东西),将DUT的输入部分和输出部分进行封装,使验证平台的层次结构更加明显,便于重用。

上一节中,monitor和driver的代码高度相似,其本质是因为两者处理的是同一协议(在同样一套既定的规则下做着不同的事情)由于二者代码相似,UVM通常将二者封装在一起,成为一个agent,因此不同的agent就代表了不同的协议。

//my_agent.sv

class my_agent extends uvm_agent;
	my_driver drv;
	my_monitor mon;
	
	function new(string name, uvm_component parnet);
		super.new(name, parent);
	endfunction
	
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual function void connect_phase(uvm_phase phase);
	
	`uvm_component_utils(my_agent)
endclass

function void my_agent::build_phase(uvm_phase phase);
	super.build_phase(phase);
	if(is_active == UVM_ACTIVE)
	begin
		drv = my_driver::type_id::create("drv", this);
	end
	mon = my_monitor::type_id::create("mon", this);
endfunction
    
function void my_agent::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
endfunction

所有的agent都要派生自uvm_agent,其本身是一个component。

其中build_phase中根据is_active来决定是否要例化driveris_active是uvm_agent的唯一成员变量。其是一个枚举类型,有UVM_ACTIVE和UVM_PASSIVE两种模式。UVM_ACTIVE就是实例化driver和sequencer(后文中会讲解),monitor。而UVM_PASSIVE则只会实例化monitor,即该agent只具有监测功能。

在这里插入图片描述

在env中需要实例化agent而不需要实例化driver和monitor。

//my_env.sv

class my_env extends uvm_env;
	my_agent i_agt;
	my_agent o_agt;
	
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		i_agt = my_agent::type_id::create("i_agt", this);
		o_agt = my_agent::type_id::create("o_agt", this);
		
		//specified is_active value
		i_agt.is_active = UVM_ACTIVE;
		o_agt.is_active = UVM_PASSIVE;
	endfunction
endclass

在加入了agent组件后,验证平台的层级结构发生了改变,使用config_db时需修改变量的路径(uvm_test_top_.i_agt.drv),不再赘述。

需要注意的是:

  • 只有uvm_component才可作为树的节点uvm_object不可以作为UVM树的节点(new函数无父节点)
  • 其次,uvm_driver和uvm_monitor实例化过程是在my_agent的build_phase中进行(根据层级结构,子节点的实例化在父节点的build_phase中进行,而不能跨层级实例化)的而不是在my_env的build_phase进行。所有的实例化要在build_phase中进行,如果在build_phase之后的phase进行实例化,会报错。

加入agent后的验证平台树如下:

在这里插入图片描述

2.3.5 加入reference model

Rfm用于完成和DUT相同的功能。Rfm的输出被Scb接收,用于和DUT的输出相比较。DUT如果很复杂,那么Rfm也会相当复杂。

//my_model.sv

class my_model extends uvm_component;
	uvm_blocking_get_port #(my_transaction) port;
	uvm_analysis_port #(my_transaction) ap;
	
	extern function new(string name, uvm_component parent);
	extern function void build_phase(uvm_phase phase);
	extern virtual task main_phase(uvm_phase phase);
	`uvm_component_utils(my_model)
endclass

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

function void my_model::build_phase(uvm_phase phase);
	super.build_phase(phase);
	port = new("port", this);
	ap = new("ap", this);
endfunction

task my_model::main_phase(uvm_phase phase);
	my_transaction tr;
	my_transaction new_tr;
	super.main_phase(phase);
	while(1)
	begin
		port.get(tr);
		new_tr = new("new_tr");
		new_tr.my_copy(tr);
		`uvm_info("my_model", "get one transaction, copy and print it:", UVM_LOW)
		new_tr.my_print();
		ap.write(new_tr);
	end
endtask

在my_model的main_phase中,只是单纯复制一份从i_agt得到的tr,并传递给后级的scb中。这里实现了两个my_transaction的复制函数。

//my_transaction.sv

function void my_copy(my_transaction tr);
	if(tr==null)	`uvm_fatal("my_transaction", "tr is null!!!")
	dmac = tr.dmac;
	smac = tr.smac;
	ether_type = tr.ether_type;
	pload = new[tr.pload.size()];
	for(int i=0; i < pload.size(); i++)
	begin
		pload[i] = tr.pload[i];
	end
	crc = tr.crc;
endfunction

其中需要说明的是my_model如何从i_agt中得到my_transaction,并把my_transaction传递给my_scoreboard。在UVM中,通常使用TLM(Transaction Level Modeling)实现component之间transaction级别的通信。在UVM的transaction级别的通信中,数据发送有多种方式,一种是使用uvm_analysis_port。UVM中组件的事务及通信通信一种是通过TLM进行的,如果要使用TLM通信,需要做三件事:

  • 在要通信的组件内声明端口,在其build_phase中实例化端口
  • 在通信的组件上一层组件的connect_phase进行端口连接
  • 调用端口的方法进行transaction传输

声明uvm_analysis_port:

//my_monitor.sv

uvm_analysis_port #(my_transaction) ap;

uvm_analysis_port是一个参数化的类,其参数就是这个analysis_port需要传递的数据类型。声明ap后,需要在monitor的build_phase中实例化。

//my_monitor.sv

virtual function void build_phase(uvm_phase phase);
	ap = new("ap", this);
endfunction

在main_phase中,当收集完了一个transaction后,需要将其通过uvm_analysis_port的一个内建函数write写入ap中。

task my_monitor::main_phase(uvm_phase phase);
	my_transaction tr;
	while(1)
	begin
		tr = new("tr");
		collect_one_pkt(tr);
		ap.write(tr);
	end
endtask

还需要将my_env中使用fifo将两个端口连接在一起。在my_env中定义一个fifo,并在build_phase中将其实例化。

//my_env.sv

uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
	agt_mdl_fifo = new("agt_mdl_fifo", this);

fifo的类型是uvm_tlm_analysis_fifo,本身是一个参数化的类,其参数是存储在其中的transaction类型。

之后,在connect_phase中将fifo分别与my_monitor中的analysis_port和my_model的blocking_get_port相连。

function void my_env::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	i_agt.ap.connect(agt_mdl_fifo.analysis_export);
	mdl.port.connect(agt_mdl_fifo.blocking_get_export);
endfunction

与build_phase相似,connect_phase也是UVM内建的一个phase。他在build_phase执行之后马上执行。但是与build_phase不同的是,他的执行顺序不是从树根到树叶(从上到下),而是从树叶到树根(从下到上)。

为什么要用fifo?因为analysis_port是非阻塞性质的,ap.write函数调用完成后马上返回,不会等待数据被接收。如果write被调用时,blocking_get_port正在忙于其他事情,而没有准备好接收新的数据时,此时被write函数写入的my_transaction就需要一个暂存的位置,这就是fifo。

与my_monitor中的ap不同的是,不需要对my_agent中的ap进行实例化,只需要在my_agent的connect_phase的connect_phase中将monitor的值赋给它,换句话说,这就相当于是一个指向my_monitor的ap指针

根据前面介绍的connect_phase的执行顺序,my_agent的connect_phase的执行顺序早于my_env的connect_phase的执行顺序,从而可以保证i_agt.ap.connect语句时,i_agt_ap不是一个空指针。

加入Rfm后的验证平台树如下:

在这里插入图片描述

2.3.6 加入scoreboard

加入Scb是最后一步。

//my_scoreboard.sv

class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];
	uvm_blocking_get_port #(my_transcation) exp_port;
	uvm_blocking_get_port #(my_transcation) act_port;
	`uvm_component_utils(my_scoreboard)
	
	extern function new(string name, uvm_component parent=null);
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual task main_phase(uvm_phase phase);
endclass

function my_scoreboard::new(string name, uvm_component parent = null);
	super.new(name, parent);
endfunction

function void my_scoreboard::build_phase(uvm_phase phase);
	super.build_phase(phase);
	exp_port = new("exp_port", this);
	act_port = new("act_port", this);
endfunction

task my_scoreboard::main_phase(uvm_phase phase);
	my_transaction get_expect, get_actual, tmp_tran;
	bit result;
	super.main_phase();
	fork
		while(1)
		begin
			exp_port.get(get_expect);
			expect_queue.push_back(get_expect);
		end
		while(1)
		begin
			act_port.get(get_actual);
			if(expect_queue.size() > 0)
			begin
				tmp_tran = expect_queue.pop_front();
				result = get_actual.my_compare(tmp_tran);
				if(result)
				begin
					`uvm_info("my_scoreboard", "Compare SUCESSFULLY", UVM_LOW)
				end
				else
				begin
					`uvm_error("my_scoreboard", "Compare FAILD")
					$display("the expect pkt is");
					tmp_tran.my_print();
					$display("the actual pkt is");
					get_actual.my_print();
				end
			end
			else
			begin
				`uvm_error("my_scoreboard", "Received from DUT, while Export Que is empty!")
				$display("the unexpected pkt is");
				get_actual.my_print();
			end
		end
	join
endtask

my_scoreboard要比较的数据来源于refm和o_agt的monitor。前者通过exp_port获取,后者通过act_port获取。在main_phase中开辟两个线程,一个线程处理exp_port的数据,将接收的到数据缓存至队列。另一个处理act_port的数据。并使用my_compare函数进行数据比对。采用这种比较处理方式的前提是exp_prot要比act_port先收到数据由于DUT处理数据需要延时,而refm是基于高级语言,一般不需要延时,因此可以保证exp_port的数据在act_port的数据之前到来

my_compare函数原型如下:

//my_scoreboard.sv
function bit my_compare(my_transaction tr);
	bit result;
	if(tr == null)
		`uvm_fatal("my_transcation", "tr is null!")
	result = ((dmac == tr.dmac) &&
			  (smac == tr.smac) &&
			  (ether_type == tr.ether_type) &&
			  (crc == tr.crc));
	if(pload.size() != tr.pload.size())		result = 0;
	else
	begin
		for(int i=0;i<pload.size;i++)
		begin
			if(pload[i] != tr.pload[i])		result = 0;
		end
	end
	return result;
endfunction

加入Scb后的验证平台树如下:

在这里插入图片描述

2.3.7 加入field_automation机制

引入my_monitor时,在my_transaction中加入了my_print函数;在引入refm时,加入了my_copy函数;引入scb时,加入了my_compare函数。上述三个函数虽然各不相同,但是对于不同的transaction来说,都是类似的,都需要逐字段对transaction进行某些操作

UVM引入了field_automation机制,使用uvm_field系列宏实现:

//my_transaction.sv

class my_transaction extends uvm_sequence_item;
	rand bit [47:0] dmac;
	rand bit [47:0] smac;			
	rand bit [15:0] ether_type;		
	rand byte 		pload[];		
	rand bit [31:0] crc;
    
    `uvm_object_utils_begin(my_transaction)
    	`uvm_field_int(dmac, UVM_ALL_ON)
    	`uvm_field_int(smac, UVM_ALL_ON)
    	`uvm_field_int(ether_type, UVM_ALL_ON)
    	`uvm_field_array_int(pload, UVM_ALL_ON)
    	`uvm_field_int(crc, UVM_ALL_ON)
    `uvm_object_utils_end
endclass

以上使用uvm_object_utils_begin和uvm_object_utils_end来实现my_transaction的注册。在这两个宏中间使用uvm_field宏注册所有的字段uvm_field系列宏随着transaction的成员不同而不同。当使用uvm_field注册成员变量后,可以直接调用copy,compare,print等函数,无需自己实现。极大的简化了验证平台的搭建,提高了效率引入field_automation机制的另一个好处是简化了driver和monitor(减少了重复性代码的使用)

//my_driver.sv

task my_driver::drive_one_pkt(my_transaction tr);
	byte unsigned data_q[];
	int data_size;
	
	data_size = tr.pack_types(data_q) / 8;
	`uvm_info("my_driver", "begin to drive one pkt", UVM_LOW)
	repeat(3) @(posedge vif.clk);
	for(int i=0;i<data_size;i++)
	begin
		@(posedge vif.clk)
		vif.valid <= 1'b1;
		vif.data <= data_q[i];
	end
	@(posedge vif.clk);
	vif.valid <= 1'b0;
	`uvm_info("my_driver", "end drive one pkt", UVM_LOW)
endtask

pack_bytes将tr中的所有字段变成byte流放入data_q中。pack_bytes极大地减少代码量。在把所有的字段变成byte流放入data_q中时,字段按照uvm_field系列宏书写的顺序排列。

//my_monitor.sv

task my_monitor::collect_one_pkt(my_transaction tr);
	byte unsigned data_q[$];
	byte unsigned data_array[];
	logic [7:0] data;
	logic valid = 0;
	int data_size;
	
	`uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW)
	while(vif.valid)
	begin
		data_q.push_back(vif.data);
		@(posedge vif.clk);
	end
	data_size = data_q.size();
	data_array = new[data_size];
	for(int i=0;i<data_size;i++)
	begin
		data_array[i] = data_q[i];
	end
	tr.pload = new[data_size - 18];
	data_size = tr.unpack_types(data_array) / 8;
	`uvm_info("my_monitor", "end collect one pkt", UVM_LOW)
endtask

使用unpack_bytes函数将data_q中的byte流转换成tr中的各字段。unpack_bytes函数的输入参数必须是一个动态数组,所以需要先把收集到的,放在data_q中的数据复制到一个动态数组中。

2.4 sequence机制(核心)

之前的验证平台,激励都是在driver中产生的,但是一个规范化的UVM验证平台中,driver只负责驱动transaction,而不负责产生transactionsequence机制由两部分组成,一个是sequence,另一个是sequencer。

2.4.1 加入sequencer
sequencer:转发item类。在没有sequencer之前,激励的调度和加载全部由driver完成。模块化编程要求组件具有单一的功能,这样才能使平台具有最大的灵活度,所以将激励的调度这个功能从driver中剥离出来由sequencer担任,driver只负责加载机理。

//my_sequencer.sv

class my_sequencer extends uvm_sequencer #(my_transaction);
	function new(string name, uvm_component parent);
		super.new(name, parnet);
	endfunction
	`uvm_component_utils(my_sequencer)
endclass

sequencer的定义非常简单,继承自uvm_sequencer,并使用uvm_component_utils宏注册到factory中。uvm_sequencer是一个参数化的类,其参数是my_transaction,即此sequencer产生的transaction的类型。uvm_driver也是一个参数化的类,应该在定义driver时指明此driver要驱动的transaction类型。

//my_driver.sv

class my_driver extends uvm_driver #(my_tansaction);

这样定义的好处就是可以直接使用uvm_driver中的某些语定义的成员变量,如uvm_driver中(UVM源码)的成员变量req,他的类型就是传递给uvm_driver的参数,在这里也就是my_transaction。

//my_agent.sv

class my_agent extends uvm_agent;
	my_driver drv;
	my_monitor mon;
	my_sequencer sqr;
	
	uvm_analysis_port #(my_transaction) ap;
	...
	
endclass

function void my_agent::build_phase(uvm_phase phase);
	super.build_phase(phase);
	if(is_active == UVM_ACTIVE)
	begin
		drv = my_driver::type_id::create("drv", this);
		sqr = my_sequencer::type_id::create("sqr", this);
	end
	mon = my_monitor::type_id::create("mon", this);
endfunction
    
function void my_agent::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	ap = mon.ap;
endfunction

在加入sequencer后,整棵UVM树如下所示:

在这里插入图片描述

2.4.2 加入sequence

sequence的位置比较特殊,sequence不属于验证平台的任何一部分,但是他与sequencer之间有着密切的联系只有在sequencer的帮助下,sequence的transaction才能最终转发给driver。sequence就像一个弹夹,里面的子弹是transaction,而sequencer是一把枪,三者相互关联

除了联系外,sequence与sequencer还有显著的区别。sequencer是一个uvm_component,而sequence是一个uvm_object。**与transaction一样,sequence也有其生命周期,它的声明周期比my_transaction要更长一些,其内部的transaction全部发送完毕后,它的声明周期也就结束了。**sequence应该使用uvm_object_utils宏注册到factory中。

//my_sequence.sv

class my_sequence extends uvm_sequence #(my_transaction);
	my_transaction m_trans;
	function new(string name = "my_sequence");
		super.new(name);
	endfunction
	
	virtual task body();
		repeat(10)
		begin
			`uvm_do(m_trans)
		end
		#1000;
	endtask
	`uvm_object_utils(my_sequence)
	end
endclass

所有sequence都应该派生自uvm_sequence,并在定义时指定要产生的transaction类型(参数类)。每一个sequence都有一个body任务,当一个sequence启动之后,会自动执行body中的代码

上述代码中uvm_do宏完成了三件事:

  • 创建一个my_transaction实例
  • 将其随机化
  • 最终将其送给sequencer

如果不用uvm_do宏也可也可以使用start_item与finish_item的方式产生transaction

以下讨论sequence,sequencer,driver交互的过程

一个sequence在向sequencer发送transaction前,要先向sequencer发送一个请求,sequencer把这个请求放入一个仲裁队列中。sequencer需要做两件事:

  • 检测仲裁队列中是否有某个sequence发送transaction请求
  • 检测driver是否申请transaction

根据是sequence,driver是否有transaction请求分为三种情况:

  • 如果仲裁队列有发送请求,但是driver没有申请transaction,那么sequence将会一直处于等待driver的状态。直到driver申请新的transction。此时sequencer同意sequence的发送请求,sequence在得到sequencer的批准后,产生一个transcation并交给sequencer,sequencer把这个transaction交给driver。
  • 如果仲裁队列中没有发送请求,但是driver向sequencer申请新的transaction,那么sequencer将会处于等待sequence状态,一直到有sequence递交发送请求,sequencer马上同意这个请求,sequence产生transaction并交给sequencer,最终driver获得这个transaction。
  • 如果仲裁队列中有发送请求,同时driver也在向sequencer申请新的transaction,那么将会同意发送请求,sequence产生transaction并交给sequencer,最终由driver获得这个transaction。

driver是通过uvm_driver中的成员变量seq_item_port,而在uvm_sequencer中有成员变量seq_item_export,这两者可以建立一个通道(TLM端口连接),通道中传递的transaction类型就是定义my_sequencer和my_driver时指定的transaction类型。这里并不需要显式指定通道类型,标准UVM库已经定义好了,只需要在my_agent中使用connect函数把两者联系在一起

//my_agent.sv

function void my_agent::connect_phase(uvm_phase);
	super.connect_phase(phase);
	if(is_active == UVM_ACTIVE)
	begin
		drv.seq_item_port.connect(sqr.seq_item_export);
	end
endfuntion

当把两者联系好后,就可以在driver中通过get_next_item任务向sequencer申请新的transacton

//my_driver.sv

task my_driver::main_phase(uvm_phase phase);
	vif.data <= 8'b0;
	vif.valid <= 1'b0;
	while(!vif.rst_n)	@(posedge vif.clk);
	while(1)
	begin
		seq_item_port.get_next_item(req);
		drive_one_pkt(req);
		seq_item_port.item_done();
	end
endtask

get_next_item任务用来得到一个新的req,并驱动它,驱动完成后调用item_done通知sequencer。当出现sequencer发送出去了transaction,而driver并没有得到的情况下时,sequencer会保留这份transaction。如果在driver下次调用get_next_item时,item_done被调用,那么sequencer就认为driver已经得到了这个transaction,并把这个transaction删除,item_done可以理解为增加可靠性的一种握手机制

uvm_do宏产生了一个transaction并交给sequencer,driver取走这个transaction后,uvm_do宏并不会立刻返回执行下一次的uvm_do宏,而是等待在那里,直到driver返回item_done信号,此时,uvm_do宏才算是执行完毕,返回后并开始执行下一个uvm_do宏,产生新的transaction。

sequence如何向sequencer中发送transaction?只需要在某个component(如my_sequencer,my_env)的main_phase中启动这个sequence即可。

//my_env.sv

task my_env::main_phase(uvm_phase phase);
	my_sequence seq;
	phase.raise_objection(this);
	seq = my_sequence::type_id::create("seq");
	seq.start(i_agt.sqr);
	phase.drop_objection(this);
endtask

首先创建一个my_sequencer的实例,之后调用start任务start任务的参数是一个sequencer指针,如果不知名指针,则sequence不知道将产生的transaction交个哪个sequencer。

在UVM中,objection一般伴随着sequence,通常只在sequence出现的地方才提起和撤销objection,当sequence发送完毕transacton就可以结束仿真了。可以在sequencer中启动sequence:

//my_sequencer.sv

task my_sequencer::main_phase(uvm_phase phase);
	my_sequence seq;
	phase.raise_objection(this);
	seq = my_sequence::type_id::create("seq");
	seq.start(this);
	phase.drop_objection(this);
endtask

除了get_next_item中,还可以使用try_next_item。get_next_item是阻塞的,它会一直等到有新的transaction才会返回。try_next_item则是非阻塞的,它尝试着询问sequencer是否有新的transaction,如果有则得到此transaction,否则直接返回。以下为使用非阻塞的try_next_item的my_driver:

//my_driver.sv

task my_driver::main_phase(uvm_phase phase);
	vif.data <= 8'b0;
	vif.valid <= 1'b0;
	while(!vif.rst_n)	@(posedge vif.clk);
	while(1)
	begin
		seq_item_port.try_next_item(req);
		if(req == null)	@(posedge vif.clk)
		else
		begin
			driver_one_pkt(req);
			seq_item_port.item_done();
		end
	end
endtask

相比于get_next_item,try_next_item的行为更加接近真实driver的行为,当有数据时,就驱动数据,否则总线将一直处于空闲状态。

验证平台在加入sequence之后就变成一个完整的验证平台了,如下图:

在这里插入图片描述

2.4.3 加入default sequence

上节中使用手动启动sequence,在实际应用中,多使用default_sequence的方式启动sequence

//my_env.sv

virtual function void build_phase(uvm_phase phase);
	super.build_phase(phase);
	uvm_config_db #(uvm_object_wrapper)::set(this,
											 "i_agt.sqr.main_phase",
											 "default_sequence",
											 my_sequence::type_id::get());
endfunction

uvm_config_db的第一个第二参数共同决定了配置参数“default_sequence”的路径。第二个参数中出现了main_phase,这是UVM在设置default_sequence时的要求,sequence便在该组件的main_phase启动sequence。

除了在my_env的build_phase中设置default_sequence外,还可以在其他地方设置,比如top_tb中。

//top_tb.sv
module top_tb;
	...
	initial
	begin
		uvm_config_db#(uvm_object_wrapper)::set(null,
												"uvm_test_top.i_agt.sqr.main_phase",
												"default_sequence",
												my_sequence::type_id::get());
	end
endmodule

config_db通常是成对出现的,但是这里不需要在sequencer中使用config_db::get的方式来获取default_sequence,因为UVM已经做好了这些

使用default_sequence启动sequence的方式取代了上一节中的sequencer的main_phase手工启动sequence的相关语句。

uvm_sequence这个基类中,有一个变量名为starting_phase,他的类型是uvm_phase,sequencer在启动default_sequence时,会自动做如下操作:

task my_sequencer::main_phase(uvm_phase phase);
	...
	seq.starting_phase = phase;
	seq.start(this);
endtask

因此可以在sequence中使用starting_phase进行提起和撤销objection

//my_sequence.sv

class my_sequence extends uvm_sequence #(my_transaction);
	my_transaction m_trans;
	virtual task body();
		if(starting_phase != null)	starting_phase.raise_objection(this);
		repeat(10)	`uvm_do(m_trans)
		#1000;
		if(starting_phase != null)	starting_phase.drop_objection(this);
		`uvm_object_utils(my_sequence)
	endtask
endclass

从而,objection完全与sequence关联在一起,在其他任何地方都不需要再设置objection。

2.5 构建测试用例

2.5.1 加入base_test

在一个实际的UVM验证平台中,uvm_env并不是树根。通常来说,树根是一个基于uvm_test派生的类。

//base_test.sv

class base_test extends uvm_test;
	my_env env;
	function new(string name="base_test", uvm_component parent = null);
		super.new(name, parent);
	endfunction
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual function void report_phase(uvm_phase phase);
	`uvm_component_utils(base_test)
endclass

function void base_test::build_phase(uvm_phase phase);
	super.build_phase(phase);
	env = my_env::type_id::create("env", this);
	uvm_config_db #(uvm_object_wrapper)::set(this,
											 "env.i_agt.sqr.main_phase",
											 "default_sequencer",
											 my_sequence::type_id::get());
endfunction

function void base_test::report_phase(uvm_phase phase);
	uvm_report_server server;
	int err_num;
	super.report_phase(phase);

	server = get_report_server();
	err_num = server.get_serverity_count(UVM_ERROR);

	if(err_num != 0)		$display("TEST CASE FAILED");
	else					$display("TEST CASE PASSED");
endfunction

base_test派生自uvm_test,使用uvm_component_utils宏注册到factory中。在build_phase中实例化my_env,并设置sequencer的default_sequence。

除了以上操作,通常还在base_test中做如下事情:

  • 设置整个验证平台的超时退出时间
  • 通过config_db设置验证平台的某些参数的值

在加入uvm_test后,整棵UVM树如下所示:
在这里插入图片描述

2.5.2 启动测试用例

要测试一个DUT是否按照预期工作,需要对其施加不同的激励,这些激励被称为测试向量或者pattern一种激励作为一个测试用例,不同的激励就是不同的测试用例。测试用例的数量是衡量验证人员工作成果的最直接目标

随着验证的进行,测试用例的数量一直在增加,在增加的过程中,很重要的一点是保证后加的测试用例不影响已经建好的测试用例。上文是通过default_sequence的形式启动my_sequence,如何在不影响my_sequence的前提下将其启动呢?最理想的办法是在命令行中指定参数来启动不同的测试用例

class case0_sequence extends uvm_sequence #(my_transaction);
	my_transaction m_trans;
	...
	virtual task body();
		if(starting_phase != null) starting_phase.raise_objection(this);
	repeat (10)
    begin
	`uvm_do(m_trans)
	end
    #100;
	if(starting_phase != null)  starting_phase.drop_objection(this);
	endtask
    ...
endclass
        
//my_case0.sv
        
class my_case0 extends base_test;
	function new(string name = "my_case0", uvm_component parent = null);
		super.new(name,parent);
	endfunction
        
    extern virtual function void build_phase(uvm_phase phase);
	`uvm_component_utils(my_case0)
endclass

function void my_case0::build_phase(uvm_phase phase);
	super.build_phase(phase);
	uvm_config_db#(uvm_object_wrapper)::set(this,
											"env.i_agt.sqr.main_phase",
											"default_sequence",
											case0_sequence::type_id::get());
endfunction

//my_case1.sv
    
class case1_sequence extends uvm_sequence #(my_transaction);
	my_transaction m_trans;
	...
	virtual task body();
		if(starting_phase != null)		starting_phase.raise_objection(this);
		repeat (10)		`uvm_do_with(m_trans, { m_trans.pload.size() == 60;})
        #100;
	if(starting_phase != null)		starting_phase.drop_objection(this);
	endtask
    ...
endclass
        
class my_case1 extends base_test;
	function new(string name = "my_case1", uvm_component parent = null);
		super.new(name,parent);
	endfunction
	extern virtual function void build_phase(uvm_phase phase);
	`uvm_component_utils(my_case1)
endclass
        
function void my_case1::build_phase(uvm_phase phase);
	super.build_phase(phase);
	uvm_config_db#(uvm_object_wrapper)::set(this,
											"env.i_agt.sqr.main_phase",
											"default_sequence",
											case1_sequence::type_id::get());
endfunction

如果需要启动my_case0,需要在top_tb中更改run_test的参数

initial
begin
	run_test(“my_case0”);
end

如果需要启动my_case1,也需要更改run_test参数

initial
begin
	run_test("my_case1");
end

当my_case0运行时需要修改代码,重新编译后才能运行。当my_case1运行时也许如此。这样相当不方便,为了解决这个问题,UVM提供了对不加参数的run_test的支持

//top_tb.sv
initial
begin
	run_test();
end

在run_test不加参数时,UVM会利用UVM_TEST_NAME从命令行中寻找测试用例的名字,创建它的实例并运行

<sim command> 			//run my_case0
+UVM_TEST_NAME=my_case0

<sim command> 			//run my_case1
+UVM_TEST_NAME=my_case1

整个验证平台的启动和执行的流程如下图:

在这里插入图片描述

  • 首先执行top_tb模块,运行其中的initial块(系统时钟,复位,赋值,run_test等)
  • 其次运行全局的无参数的run_test任务(top_tb中的run_test的initial块),此时启动整个验证平台
  • 由于run_test()是无参数的,UVM通过命令行+UVM_TEST_NAME的来接收运行的实例名(在启动平台时通过命令行或者脚本指定UVM_TEST_NAME参数)
  • 依次执行UVM平台的各Phase
  • 所有phase执行完毕,结束仿真

整棵UVM树如下所示(uvm_test_top为要运行的用例my_casen):
在这里插入图片描述

参考资料:

UVM实战(卷I)张强 编著 机械工业出版社

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖小张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值