UVM框架简要

本学习主要是对《UVM实践》卷Ⅰ进行简要的总结归纳,具体实现参考本书。
以下图逐步展开和封装进行UVM环境搭建学习理解。
在这里插入图片描述

UVM driver

UVM driver是验证平台最基本的组件,是整个验证平台数据流的源泉。

UVM是一个库,在这个库中,几乎所有的东西使用类(class)来实现。其中: driver, monitor, referencemodel, scoreboard等组成部分都是类。类有函数(function)还有任务(task),通过这些函数和任务可以完成driver的输出激励功能。完成monitor的监测功能,完成参考模型的计算功能,完成scoreboard的比较功能。类中有成员变量,这些成员变量可以控制类的行为,如控制driver的行为验证平台中的所有组件应该派生自UVM中的类。
UVM验证平台中的driver应该派生自uvm_driver,一个简单的driver如下例所示:
'ifndef MY_DRIVER_SV
'define MY_DRIVER_SV
class my_driver extends uvm_driver;//派生
注意两点:

  1. 所有派生自uvm_driver的类的new函数有两个参数,一个事string类型的name,一个是uvm_component类型的parent。关于name参数,就是名字而已。至于parent。这两个参数是uvm_component的要求,每一个派生自uvm_component或其派生类的类在其new函数中药指明两个参数:name和parent ,这是uvm_component类的一大特点。而uvm_drive是派生自uvm_component类。所以也会有两个参数。
  2. Driver所做的事情几乎都在main_phase中,而UVM由phase来管理验证平台的运行,这些phase统一以xxxx_phase来命名,且都有一个类型为uvm_phase、名字为phase的参数。main_phase是uvm_driver中预先定义好的一个任务。几乎可以简单的认为,实现一个driver等于实现其main_phase。
    my_driver.sv
`ifndef MY_DRIVER_SV
`define MY_DRIVER_SV
class my_driver extends uvm_driver;
	
	function new(string name = "my_driver", uvm_component parent = null);
		super.new(name, parent);
		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);
	top_tb.rxd <= 8'b0;
	top_tb.rx_dv <= 1'b0;
	while(!top_tb.rst_n)
		@(posedge top_tb.clk);
	for(int i= 0; i< 256: i++)begin
		@(posedge top_tb.clk);
		top_tb.rxd <= $urandom_range(0, 255);
		top_tb.rx_dv <= 1'bl;
		uvm_info("my driver", "data is drived", UVM LOW);
	end 
	@(posedge top_tb.clk);
	top_tb.rx_dv <= 1'b0;
endtask
`endif

testbench top

timescale 1ns/1ps
include "uvm macros.svh"
import uvm_pkg::*;
include "my_driver.sv"
module top_tb;
	reg clk: 
	reg rstn;
	reg[7:0] rxd;
	reg rx_dv;
	wire[7:0] txd;
	wire tx_en;
    dut my_dut(.clk(clk),
               .rst_n(rst_n), 
               .rxd (rxd), 
               .rx_dv(rx_dv), 
               .txd (txd), 
               .tx_en(tx_en));
    initial begin
    	my_driver drv;
    	drv = new("drv", null);
    	drv.main_phase(null);
    	$finish();
    initial begin 
    	clk = 0;
    	forever begin
    	#100 clk = ~clk;
    end
    initial begin
        rst_n = 1'b0;
        #1000;
        rst_n = 1'b1;
    end
endmodule    

factory 机制

factory机制,可以将当前定义的类登记在UVM内部一张表里。在testbench中直接调用run_test(“class_name"),如果该class_name在UVM表中用factory机制注册过,即可自动例化并执行。

将my_driver类登记在UVM内部一张表中,在testbench中直接调用run_test("my_driver"),即可自动实例化并执行。

my_driver.sv

`ifndef MY_DRIVER_SV
`define MY_DRIVER_SV
class my_driver extends uvm_driver;
	uvm_component_utils(my driver) //将类登记在uvm内部一张表
	function new(string name = "my_driver", uvm_component parent = null);
		super.new(name, parent);
		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);
	top_tb.rxd <= 8'b0;
	top_tb.rx_dv <= 1'b0;
	while(!top_tb.rst_n)
		@(posedge top_tb.clk);
	for(int i= 0; i< 256: i++)begin
		@(posedge top_tb.clk);
		top_tb.rxd <= $urandom_range(0, 255);
		top_tb.rx_dv <= 1'bl;
		uvm_info("my driver", "data is drived", UVM LOW);
	end 
	@(posedge top_tb.clk);
	top_tb.rx_dv <= 1'b0;
endtask
`endif

testbench top

timescale 1ns/1ps
include "uvm macros.svh"
import uvm_pkg::*;
include "my_driver.sv"
module top_tb;
	reg clk;
	reg rstn;
	reg[7:0] rxd;
	reg rx_dv;
	wire[7:0] txd;
	wire tx_en;
    dut my_dut(.clk(clk),
               .rst_n(rst_n), 
               .rxd (rxd), 
               .rx_dv(rx_dv), 
               .txd (txd), 
               .tx_en(tx_en));
    initial begin 
    	clk = 0;
    	forever begin
    	#100 clk = ~clk;
    end
    initial begin
        rst_n = 1'b0;
        #1000;
        rst_n = 1'b1;
    end
    initial begin
		run_test("my_driver"); //testbench调用
	end
endmodule  	

object机制

UVM中通过objection机制来控制验证平台的关闭。在每个phase中,UVM会检查是否有objection被提起,如果有,会等待这个objection被撤销,该phase即结束。
raise_objection语句必须在main_phase中第一个消耗仿真时间的语句之前。

my_driver.sv代码

`ifndef MY_DRIVER_SV
`define MY_DRIVER_SV
class my_driver extends uvm_driver;
	uvm_component_utils(my driver) //将类登记在uvm内部一张表
	function new(string name = "my_driver", uvm_component parent = null);
		super.new(name, parent);
		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);
	phase.raise_objection(this);  //和phase.drop_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.clk);
	for(int i= 0; i< 256: i++)begin
		@(posedge top_tb.clk);
		top_tb.rxd <= $urandom_range(0, 255);
		top_tb.rx_dv <= 1'bl;
		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
`endif

virtual interface

之前的driver中使用了绝对路径,绝对路径的使用会大大减弱验证平台的可移植性。尽量减小甚至杜绝在验证平台中使用绝对路径。

  • 使用宏:这样当路径修改时,只需要修改宏的定义即可,但是加入clk的路径表位top_tb.clk_inst.clk,而rst_n的路径变为top_tb.rst_inst.rst_n,那么单纯的修改宏定义是无法起到作用的。
  • 使用interface:在systemverilog中使用interface来连接验证平台与DUT的端口。

对于dut的接口信号,我们都要很好的把信号包在virtual interface里面
interface不是UVM特有的virtual my_if vif
在声明vif后,就可以在main_phase中使用如下方式驱动信号从而消除代码的绝对路径,大大提高的代码的可移植性和可重复性。

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

testbench top

timescale 1ns/1ps
include "uvm macros.svh"
import uvm_pkg::*;
include "my_driver.sv"

module top_tb;
	reg clk, rstn;
	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));
    initial begin 
    	clk = 0;
    	forever begin
    	#100 clk = ~clk;
    end
    initial begin
        rst_n = 1'b0;
        #1000;
        rst_n = 1'b1;
    end
    initial begin
		run_test("my_driver"); //testbench调用
	end
	initial begin
		uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if)
endmodule  	

在类中无法直接申明interface的实例,UVM提供了virtual interface用以申明interface的实例
config_db的set和get用于传递数据,以及实例化成员变量。
my_driver.sv代码

`ifndef MY_DRIVER_SV
`define MY_DRIVER_SV
class my_driver extends uvm_driver;
	virtual my_if vif;
	uvm_component_utils(my driver) //将类登记在uvm内部一张表
	function new(string name = "my_driver", uvm_component parent = null);
		super.new(name, parent);
		uvm_info("my driver", "new is called", UVM_LOW);
	endfunction
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		`uvm_info("my_driver", "build phase id called", UVM_LOW);
		if(!uvm_config_deb#(virtual my_if)::get(this, "", "vif", vif))
			`uvm_fatal("my_driver", "virtual interface must be set for vif")
	endfunction
	extern virtual task main_phase(uvm_phase phase);
endclass
task my_driver::main_phase(uvm_phase phase);
	phase.raise_objection(this);  //和phase.drop_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 <= $urandom_range(0, 255);
		vif.valid <= 1'bl;
		uvm_info("my driver", "data is drived", UVM LOW);
	end 
	@(posedge vif.clk);
	vif.valid <= 1'b0;
	phase.drop_objection(this);
endtask
`endif

transaction

  1. 数据流、控制流都可以抽象为transaction。
  2. 有相关联的数据放在一个transaction中。
  3. 所有transaction都派生自uvm_sequence_item。只有从uvm_sequence_item派生的transaction才可以使用后文讲述的UVM中强大的sequence机制
  4. 用uvm_object_utils实现transaction的factory机制注册
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;
	constraint pload_cons{
		pload.size >= 46;
		pload.size <= 1500;
	}
	function bit[31:0] calc_crc();
		return 32'h0;
	endfunction
	function void post_randomize();
		crc = calc_crc;
	endfunction
	`uvm_object_utils(my_transaction)
	function new(string name = "my_transaction") 
		super.new();
	endfunction
endclass

my_driver.sv 伪代码

task my_driver::main_phase(uvm_phase phase);
	my_transaction tr;
	phase.raise_objection(this);  //和phase.drop_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< 2: i++)begin
		tr = new("tr");
		assert(tr.randomize() with {pload.size == 200;});
		drive_one_pkt(tr);
	end 
	repeat(5) @(posedge vif.clk);
	phase.drop_objection(this);
endtask

在main_phase中,先使用randomize将tr随机化,之后通过drive_one_pkt任务将tr的内容驱动到DUT的端口上。在drive_one_pkt中,先将tr中所有的数据压入队列data_q中,之后再将data_q中所有的数据弹出并驱动。将tr中的数据压入队列data_q中的过程相当于打包成一个byte流的过程。这个过程还可以使用SystemVerlog提供的流操作符实现

task my_driver::drive_one_pkt(my_transaction tr);
	bit [47:0] tmp_data;
	bit [7:0] data_q[$];

	//push dmac to data_q
	tmp_data = tr.dmac;
	for (int i= 0;  i <  6; i++) begin 
		data_q.push_back(tmp_data [7:0]);
		tmp_data = (tmp_data >>);
	end
	//push smac to data_q
	//push ether_type to data_q
	//push payload to data_q 
	//push crc to data_q
	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 pkt", UVM_LOW);
	repeat(3) @posedge(vif.clk);
	
	while (data_q.size () >  0) begin
		@(posedge vif.clk);
		vif.valid <= 1'bl;
		vif.data <= data_q.pop_front();
	end
	@(posedge vif.clk);
	vif.valid <= 1'b0;
	'uvm_info ("my_driver", "end drive one pkt", UVM_LOW);
endtask

env

在验证平台中加入reference model-scoreboard等之前,思考一个问题:假设这些组件已经定义好了,那么在验证平台的什么位置对它们进行实例化呢?在top_tb中使用run_test进行实例化显然是不行的,因为run_test函数虽然强大,但也只能实例化一个实例;如果在top_tb中使用2.2.1节中实例化driver的方式显然也不可行,因为run_test相当于在top_tb结构层次之外建立一个新的结构层次,而2.2.1节的方式则是基于top_tb的层次结构,如果基于此进行实例化,那么run_test的引用也就没有太大的意义了;如果在driver中进行实例化则更加不合理

这个问题的解决方案是引入一个容器类,在这个容器类中实例化driver monitor reference-model和scoreboard等。在调用run_test时,传递的参数不再是my_driver,而是这个容器类,即让UVM自动创建这个容器类的实例。在UVM中,这个容器类称为uvm_env

class my_env extends uvm_env;
	my_driver drv;
	funciton new(string name = "my_env", uvm_component parent);
		super.new(name, parent);
	endfunction
	
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		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的定义中,最让人难以理解的是drv的实例化。这里没有直接调用my_driver的new函数,而是使用了一种古怪的方式。这种方式就是factory机制带来的独特的实例化方式。只有使用factory机制注册过的类才能使用这种方式实例化;只有使用这种方式实例化的实例,才能使用后文要讲述的factory机制中最为强大的重载功能。验证平台中的组件在实例化时都应该使用type_name :: type_id: : create的方式。

加入env前,driver是整个平台中的唯一组件,加入env后,通过parent的形式,UVM建立起了树形的组织结构。在树形的组织结构中,由于run_test创建的实例事树根(my_env )并且树根的名字是固定的,是uvm_test_top。而树根之后长出枝叶(my_driver),长出枝叶的过程需要在my_env的build_phase中手动实现。由此无论是树根( my_env, uvm_test_top )还是树叶( my_driver )都必须有uvm_component或者派生类继承而来。env作为UVM树的树根,而driver成为树叶build_phase按照从树env的build_phase到driver的build_phase执行在tb top中使用config db机制传递virtual interface时,需要改变路径。run_test的参数也从driver变成env。

当加入了my_env后,整个验证平台中存在两个build_phase,一个是my_env的,一个是my_driver的。那么这两个build_phase按照何种顺序执行呢?在UVM的树形结构中,build_phase的执行遵照从树根到树叶的顺序,即先执行my_env的build_phase,再执行my_driver的build_phase。当把整棵树的build_phase都执行完毕后,再执行后面的phase。
my_driver在验证平台中的层次结构发生了变化,它一跃从树根变成了树叶,所以在top_tb中使用config_db机制传递virtual my_if时,要改变相应的路径;同时,run_test的参数也从my_driver变为了my_env:

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

set函数的第二个参数从uvm_test_top变为了uvm_test_top.drv,其中uvm_test_top是UVM自动创建的树根的名字,而drv则是在my_env的build_phase中实例化drv时传递过去的名字。如果在实例化drv时传递的名字是my_drv,那么set函数的第二个参数中也应该是my_drv:

class my_env extends uvm_env
	…
	drv = my_driver::type_id::create("my_drv", this);
	… 
endclass
module top_tb;
	… 
	initial begin
	`uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.my_drv", "vif", input_if);
	end
endmodule

加入monitor

monitor用于收集DUT的端口数据,并将其转化为transaction后交给reference-model scoreboard等

  1. 所有monitor都派生自uvm_monitor
  2. monitor中需要有一个virtual interface
  3. 使用uvm_component_utils宏注册
  4. monitor需要一直收集信息
    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 parnet = null);
		super.new(name, parnet);
	endfunction

	virtual function void build_phase(uvm_phase phase)
		sepur.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 collect_one_pkt(my_transaction tr);
endclass

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

task my_monitor::collect_one_pkt(my_transaction tr);
	// todo
endtask

monitor需要在env中实例化,可以在DUT的输入口和输出口分别放置monitor。
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(phase);
		drv= my driver::type id::create("drv", this);
		i_mon = my_monitor::type_id::create("i_mon", this); //提供给reference-model
		o_mon = my_monitor::type_id::create("o_mon", this); //提供给scoreboard
	endtunction

	`uvm_component_utils(my_env)
 endclass

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

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", output_if);
end

封装成agent

driver和monitor本质上是处理同一种协议,鉴于此,UVM把二者封装在一起,成为一个agent,同一个DUT涉及的不同协议,可以封装成不同的agent。

  1. 所有agent都派生于uvm_agent
  2. 使用uvm_component_utils宏实现factory注册
  3. uvm_agent有一个重要的成员is_active,用于指示dirver或者monitor是否需要实例化

my_agent.sv

class my agent extends uvm agent
	 my_driver  drv;
	 my_monitor mon;
	 
	 function new(string name, uvm_component parent);
	 	super.new(name, parent);
	 endfunction
	 
	 extern virtual function void build_phase(uvm_phase phase);
	 extern virtual function void connect_phase(uvm_phase phase);
	 
	 `uvmcomponent_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);
endtunction

function void my_agent::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
endfunction

在把driver和monitor封装成agent后, 在env中需要实例化agent, 而不需要直接实例化driver和monitor了:

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);
	    i_agt.is_active = UVM_ACTIVE; 
	    o_agt.is_active = UVM_PASSIVE;
	endfunction
 	`uvm_component_utils(my_env);
endclass

完成in_agent和out_agent声明以后,在my_env的build_phase中对他们进行实例化后,需要指定各自的工作模式是active还是passive模式。
在这里插入图片描述

由于agent的加入,driver和monitor的层次结构变化了,在top_tb中使用config_db设置virtual my_if时要注意改变路径:

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

加入reference model

reference model用于完成和DUT相同的功能。 reference model的输出被scoreboard接收, 用于和DUT的输出相比较。 DUT如果很复杂, 那么reference model也会相当复杂。 本章的DUT很简单, 所以reference model也相当简单:

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并不复杂, 这其中令人感兴趣的是my_transaction的传递方式。 my_model是从i_agt中得到my_transaction, 并把my_transaction传递给my_scoreboard。 在UVM中, 通常使用TLM(Transaction Level Modeling) 实现component之间transaction级别的通信。

要实现通信, 有两点是值得考虑的: 第一, 数据是如何发送的? 第二, 数据是如何接收的? 在UVM的transaction级别的通信中, 数据的发送有多种方式, 其中一种是使用uvm_analysis_port。

uvm_analysis_port #(my_transaction) ap;

uvm_analysis_port是一个参数化的类, 其参数就是这个analysis_port需要传递的数据的类型, 在本节中是my_transaction。write是uvm_analysis_port的一个内建函数。

UVM的transaction级别通信的数据接收方式也有多种, 其中一种就是使用uvm_blocking_get_port。 这也是一个参数化的类, 其参数是要在其中传递的transaction的类型。 在my_model的第6行中, 定义了一个端口, 并build_phase中对其进行实例化。 在main_phase中, 通过port.get任务来得到从i_agt的monitor中发出transaction。

在my_monitor和my_model中定义并实现了各自的端口之后, 通信的功能并没有实现, 还需要在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的类型, 这里是my_transaction。
之后, 在connect_phase中将fifo分别与my_monitor中的analysis_port和my_model中blocking_get_port相连

my_env.sv
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

这里引入了connect_phase。 与build_phase及main_phase类似, connect_phase也是UVM内建的一个phase, 它在build_phase执行完成之后马上执行。 但是与build_phase不同的是, 它的执行顺序并不是从树根到树叶, 而是从树叶到树根——先执行driver和monitor的connect_phase, 再执行agent的connect_phase, 最后执行env的connect_phase。

加入scoreboard

my_scoreboard.sv
class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];
	uvm_blocking_get_port #(my_transaction) exp_port;
	uvm_blocking_get_port #(my_transaction) 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(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 SUCCESSFULLY", UVM_LOW);
				end
				else begin
					`uvm_error("my_scoreboard", "Compare FAILED");
					$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 Expect Que ue is empty");
				$display("the unexpected pkt is");
				get_actual.my_print();
			end
	    end
	join
endtask

加入field_automation机制

UVM针对transaction定义了field_automation机制,使用uvm_field系列宏实现。

这里使用uvm_object_utils_begin和uvm_object_utils_end来实现my_transaction的factory注册, 在这两个宏中间, 使用uvm_field宏注册所有字段。 uvm_field系列宏随着transaction成员变量的不同而不同, 如上面的定义中出现了针对bit类型的uvm_field_int及针对byte类型动态数组的uvm_field_array_int。

`uvm_object_utils_begin(my_transaction)
	`uvm_field_int(smac, UVM_ALL_ON)
	`uvm_field_int(dmac, 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

加入sequencer

sequence机制用于产生激励, 它是UVM中最重要的机制之一。 在本书前面所有的例子中, 激励都是在driver中产生的, 但是在一个规范化的UVM验证平台中, driver只负责驱动transaction, 而不负责产生transaction。 sequence机制有两大组成部分, 一是sequence, 二是sequencer。 本节先介绍如何在验证平台中加入sequencer。 一个sequencer的定义如下:

my_sequencer.sv
class my_sequencer extends uvm_sequencer #(my_transaction);

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

	`uvm_component_utils(my_sequencer)
endclass

sequencer的定义非常简单, 派生自uvm_sequencer, 并且使用uvm_component_utils宏来注册到factory中。 uvm_sequencer是一个参数化的类, 其参数是my_transaction, 即此sequencer产生的transaction的类型。
sequencer产生transaction, 而driver负责接收transaction。 在前文的例子中, 定义my_driver时都是直接从uvm_driver中派生:

class my_driver extends uvm_driver;

但实际上, 这种定义方法并不多见, 由于uvm_driver也是一个参数化的类, 应该在定义driver时指明此driver要驱动的transaction的类型

class my_driver extends uvm_driver#(my_transaction);

这样定义的好处是可以直接使用uvm_driver中的某些预先定义好的成员变量, 如uvm_driver中有成员变量req, 它的类型就是传递给uvm_driver的参数, 在这里就是my_transaction, 可以直接使用req

my_driver.sv
task my_driver::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	vif.data <= 8'b0;
	vif.valid <= 1'b0;
	while(!vif.rst_n)
		@(posedge vif.clk);
	for(int i = 0; i < 2; i++) begin
		req = new("req");
		assert(req.randomize() with {pload.size == 200;});
		drive_one_pkt(req);
	end
	repeat(5) @(posedge vif.clk);
	phase.drop_objection(this);
endtask

这里依然是在driver中产生激励, 下一节中将会把激励产生的功能从driver中移除。
在完成sequencer的定义后, 由于sequencer与driver的关系非常密切, 因此要把其加入agent中:

my_agent.sv
class my_agent extends uvm_agent ;
	my_sequencer sqr;
	my_driver drv;
	my_monitor mon;

	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
		sqr = my_sequencer::type_id::create("sqr", this);
		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);
	ap = mon.ap;
endfunction

sequence机制

sequence不属于验证平台的任何一部分, 但是它与sequencer之间有密切的联系, 这点从二者的名字就可以看出来。 只有在sequencer的帮助下, sequence产生出的transaction才能最终送给driver; 同样, sequencer只有在sequence出现的情况下才能体现其价值, 如果没有sequence, sequencer就几乎没有任何作用。 sequence就像是一个弹夹, 里面的子弹是transaction, 而sequencer是一把枪。 弹夹只有放入枪中才有意义, 枪只有在放入弹夹后才能发挥威力。

除了联系外, sequence与sequencer还有显著的区别。 从本质上来说:
sequencer是一个 uvm_component, 而sequence是一个uvm_object。 与my_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)
endclass

每一个sequence都应该派生自uvm_sequence, 并且在定义时指定要产生的transaction的类型, 这里是my_transaction。 每一个sequence都有一个body任务, 当一个sequence启动之后, 会自动执行body中的代码。 在上面的例子中, 用到了一个全新的宏:uvm_do。 这个宏是UVM中最常用的宏之一, 它用于: ①创建一个my_transaction的实例m_trans; ②将其随机化; ③最终将其送给sequencer。 如果不使用uvm_do宏, 也可以直接使用start_item与finish_item的方式产生transaction, 6.3.4节将讲述这种方式。 对于初学者来说, 使用uvm_do宏即可。

一个sequence在向sequencer发送transaction前, 要先向sequencer发送一个请求, sequencer把这个请求放在一个仲裁队列中。 作为sequencer, 它需做两件事情: 第一, 检测仲裁队列里是否有某个sequence发送transaction的请求; 第二, 检测driver是否申请transaction。

driver如何向sequencer申请transaction呢? 在uvm_driver中有成员变量seq_item_port, 而在uvm_sequencer中有成员变量seq_item_export, 这两者之间可以建立一个“通道”, 通道中传递的transaction类型就是定义my_sequencer和my_driver时指定的transaction类型, 在这里是my_transaction, 当然了, 这里并不需要显式地指定“通道”的类型, UVM已经做好了。 在my_agent中,使用connect函数把两者联系在一起

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

当把二者连接好之后, 就可以在driver中通过get_next_item任务向sequencer申请新的transaction

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

在实现了driver后, 接下来的问题是sequence如何向sequencer中送出transaction呢? 前面已经定义了sequence, 只需要在某个component( 如my_sequencer、 my_env) 的main_phase中启动这个sequence即可。 以在my_env中启动为例

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_sequence的实例seq, 之后调用start任务。 start任务的参数是一个sequencer指针, 如果不指明此指针, 则sequence不知道将产生的transaction交给哪个sequencer。

这里需要引起关注的是objection, 在UVM中, objection一般伴随着sequence, 通常只在sequence出现的地方才提起和撤销objection。 如前面所说, sequence是弹夹, 当弹夹里面的子弹用光之后, 可以结束仿真了。

也可以在sequencer中启动sequence

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

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

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
			drive_one_pkt(req);
			seq_item_port.item_done();
		end
	end
endtask

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

default_sequence的使用

在上面内容中, sequence是在my_env的main_phase中手工启动的, 作为示例使用这种方式足够了, 但是在实际应用中,使用最多的还是通过default_sequence的方式启动sequence。

使用default_sequence的方式非常简单, 只需要在某个component( 如my_env) 的build_phase中设置如下代码即可

// my_env.sv
virtual funciton 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

这是除了在top_tb中通过config_db设置virtual interface后再一次用到config_db的功能。 与在top_tb中不同的是, 这里set函数的第一个参数由null变成了this, 而第二个代表路径的参数则去除uvm_test_top。 事实上, 第二个参数是相对于第一个参数的相对路径, 由于上述代码是在my_env中, 而my_env本身已经是uvm_test_top了, 且第一个参数被设置为了this, 所以第二个参数中就不需要uvm_test_top了。 在top_tb中设置virtual interface时, 由于top_tb不是一个类, 无法使用this指针, 所以设置set的第一个参数为null, 第二个参数使用绝对路径uvm_test_top.xxx。

另外, 在第二个路径参数中, 出现了main_phase。 这是UVM在设置default_sequence时的要求。 由于除了main_phase外, 还存在其他任务phase, 如configure_phase、 reset_phase等, 所以必须指定是哪个phase, 从而使sequencer知道在哪个phase启动这个sequence。

至于set的第三个和第四个参数, 以及uvm_config_db#( uvm_object_wrapper) 中为什么是uvm_object_wrapper而不是uvm_sequence或者其他, 则纯粹是由于UVM的规定, 用户在使用时照做即可

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

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

还可以在其他的component里设置, 如my_agent的build_phase里:

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

config_db通常都是成对出现的。 在top_tb中通过set设置virtual interface, 而在driver或者monitor中通过get函数得到virtualinterface。 那么在这里是否需要在sequencer中手工写一些get相关的代码呢? 答案是否定的。 UVM已经做好了这些, 读者无需再把时间花在这上面。

使用default_sequence启动sequence的方式取代了代码sequencer的main_phase中手工启动sequence的相关语句, 但是新的问题出现了: 在上一节启动sequence前后, 分别提起和撤销objection, 此时使用default_sequence又如何提起和撤销objection呢?

在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) begin
			`uvm_do(m_trans)
		end
		#1000;
		if(starting_phase != null)
		starting_phase.drop_objection(this);
	endtask

	`uvm_object_utils(my_sequence)
endclass

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

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: FIFO验证是在UVM框架中非常常见的一种验证方式。FIFO是First-In-First-Out的缩写,它是一种特殊的数据结构,它可以按照进入队列的顺序进行数据的读写操作。 在UVM框架中,FIFO验证主要用于验证存储和传输数据的模块,比如FIFO模块和存储器模块。通过使用UVM中提供的各种类和功能,我们可以轻松地进行FIFO验证。 首先,我们需要创建一个FIFO模型,并定义相应的输入和输出接口。然后,我们可以使用UVM中提供的sequencer和driver来生成和驱动随机的输入数据。通过监视器(monitor)和分析器(analyzer),我们可以对输出结果进行检查,并与预期结果进行比较,以判断验证是否通过。 在验证过程中,我们可以使用UVM中提供的各种工具和方法来验证FIFO的各种功能和性能特性,比如空闲状态、写入和读取操作、数据丢失和溢出等等。同时,我们还可以使用UVM提供的各种约束来验证特定的属性和规则,确保FIFO模块的正确性和稳定性。 总之,通过使用UVM框架中提供的各种类和方法,我们可以方便地进行FIFO验证的设计和实现。这种验证方式可以有效地验证FIFO模块的各种功能和性能特性,提高验证的可靠性和效率,为系统级设计的开发和调试提供有力的支持。 ### 回答2: FIFO(First In First Out)是一种数据结构,在UVM(Universal Verification Methodology)框架中,可以使用FIFO来验证设计中的数据传输和存储。 在UVM中,我们可以通过使用uvm_fifomem类来实现FIFO的验证。首先,我们需要定义FIFO的深度(即可以存储的元素数量),并在测试环境中实例化uvm_fifomem类。然后,在测试环境的构造函数中,我们可以通过调用write()函数向FIFO中写入元素,并使用read()函数从FIFO中读取元素。 在进行FIFO验证时,我们可以编写不同的测试用例,来验证FIFO是否能够正确地处理数据。例如,我们可以编写一个测试用例,向FIFO中按顺序写入一些元素,然后再依次读取这些元素,并验证读取的元素与写入的元素是否一致。此外,我们还可以编写其他的测试用例,验证FIFO在各种情况下的正确性,例如在写满的情况下是否能够正确处理数据。 FIFO验证的重点是确认FIFO能够按照预期的顺序读取和写入数据。我们可以使用UVM提供的assert()函数来进行验证。在写入数据时,我们可以使用assert()函数来验证写入数据的完整性和一致性;在读取数据时,我们可以使用assert()函数来验证读取的数据是否与预期一致。此外,我们还可以使用UVM提供的randomize()函数来生成随机的数据,以测试FIFO是否能够正确地处理各种不同的数据。 总结来说,FIFO验证是使用UVM框架来验证FIFO数据结构的正确性。通过使用uvm_fifomem类,我们可以实现FIFO的功能,并编写相应的测试用例来验证FIFO在各种情况下的正确性。使用assert()函数和randomize()函数可以帮助我们进行验证并生成随机的数据,以确保FIFO的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值