UVM实战读书笔记-----持续更新

附录:systemverilog 使用简介

SystemVerilog是一种面向对象的编程语言,面向对象语言最重要的特点是所有的功能都要在类(class)里实现。

一、结构体的使用

struct animal {
    char name[20];
    int birthday;/*example: 20030910*/
    char category[20];/*example: bird, non_bird*/
    int food_weight;
    int is_healthy;
};

void print_animal(struct animal * zoo_member){
    printf("My name is %s\n", zoo_member->name);
    printf("My birthday is %d\n", zoo_member->birthday);
    printf("I am a %s\n", zoo_member->category);
    printf("I could eat %d gram food one day\n", zoo_member->food_weight);
    printf("My healthy status is %d\n", zoo_member->is_healthy);
}
void main()
{
    struct animal members[20];
    strcpy(members[0].name, "parrot");
    members[0].birthday = 20091021;
    strcpy(members[0].category, "bird");
    members[0].food_weight = 20;
    members[0].is_healthy = 1;
    print_animal(&members[0]);
}

二、从结构体到类

        类将结构体和它相应的函数集合在一起,成为一种新的数据组织形式。在这种新的数据组织形式中,有两种成分, 一种是来自结构体的数据变量,在类中被称为成员变量
另外一种来自与结构体相对应的 函数 ,被称为一个 类的接口
class animal;
    string name;
    int birthday;    /*example: 20030910*/
    string category;    /*example: bird, non_bird*/
    int food_weight;
    int is_healthy;
    function void print();
        $display("My name is %s", name);
        $display("My birthday is %d", birthday);
        $display("I am a %s", category);
        $display("I could eat %d gram food one day", food_weight);
        $display("My healthy status is %d", is_healthy);
    endfunction
endclass
当一个类被定义好后,需要将其实例化才可以使用。当实例化完成后,可以调用其中的函数:
initial begin
    animal members[20];
    members[0] = new();
    members[0].name = "parrot";
    members[0].birthday = 20091021;
    members[0].category = "bird";
    members[0].food_weight = 20;
    members[0].is_healthy = 1;
    members[0].print();
end
这里使用了 new 函数。 new 是一个比较特殊的函数,在类的定义中,没有出现 new 的定义,但是却可以直接使用它。在面向对象编程的术语中,new 被称为构造函数。编程语言会默认提供一个构造函数,所以这里可以不定义而直接使用它。

三、类的封装

         类有三大特征:封装、继承和多态 ,本节讲述封装

        在上节的例子中,animal中所有的成员变量对于外部来说都是可见的,所以在initial语句中可以直接使用直接引用的方式对其进行赋值,为了避免这种情况,面向对象的开发者们设计了私有变量SystemVerilog中为local,其他编程语言各不相同,如private)当一个变量被设置为local类型后,那么这个变量就会具有两大特点

1、此变量只能在类的内部由类的函数/任务进行访问。
2、在类外部使用直接引用的方式进行访问会提示出错

例:

class animal;
    string name;
    local int birthday;        /*example: 20030910*/
    local string category;    /*example: bird, non_bird*/
    local int food_weight;
    local int is_healthy;
endclass
由于 不能进行直接引用式的赋值 ,所以需要在 类内部 定义一个初始化函数来对类进行初始化:

function void init(string iname, int ibirthday, string icategory, int ifood _weight, int iis_healthy);
    name = iname;
    birthday = ibirthday;
    category = icategory;
    food_weight = ifood_weight;
    is_healthy = iis_healthy;
endfunction
除了成员变量可以被定义为 local 类型外,函数 / 任务也可以被定义为 local 类型

四、类的继承

        面向对象编程的第二大特征就是继承。在一个动物园中,有两种动物,一种是能飞行的鸟类,一种是不能飞行的爬行动物。 假设动物园中有100只鸟类、200只爬行动物。在建立动物园的管理系统时,需要实例化100animal变量,这100个变量的category 都要设置为bird,同时需要实例化200animal变量,这200个变量的category都要设置为non_bird100次或者200次做同样一件事情 是比较容易出错的。

        考虑到这种情况,面向对象编程的开创者们提出了继承的概念。分析所要解决的问题,并找出其中的共性,用这些共性构建一个基类(或者父类);在此基础上,将问题分类,不同的分类具有各自的共性,使用这些分类的共性构建一个派生类(或者子类)。

        

        一个动物园中所有的动物都可以抽像成上节所示的animal类,在animal类的基础上,派生(继承)出bird类和non_bird类:
 

 class 子类 extends 父类;

 endclass
//local 不可访问内部成员
class animal;
    string name;
    local int birthday;        /*example: 20030910*/
    local string category;    /*example: bird, non_bird*/
    local int food_weight;
    local int is_healthy;
endclass


class bird extends animal;
    function new();
        super.new();
        category = "bird";
    endfunction
endclass


class non_bird extends animal;
    function new();
        super.new();
        category = "non_bird";
    endfunction
endclass
         当子类从父类派生后,子类天然地具有了父类所有的特征,父类的成员变量也是子类的成员变量,父类的成员函数同时也是子类的成员函数。除了具有父类所有的特征外,子类还可以有自己额外的成员变量和成员函数 ,如对于bird类,可以定义自己的fly函数

        如果一个变量是local类型的,那么它是不能被外部直接访问的。如果父类中某成员变量是local类型,那么子类不可以访问父类的local变量和函数。父类中的成员变量想让子类访问,同时不想被外部访问,那么可以将这些变量声明为protected类型,与local类似,protected关键字同样可以应用于函数/任务中.


//protected子类可访问,外部不可访问

class animal;
    string name;
    protected int birthday;/*example: 20030910*/
    protected string category;/*example: bird, non_bird*/
    protected int food_weight;
    protected int is_healthy;
endclass

五、类的多态

假设在 animal 中有函数 print_homehown
class animal;
    string name;
    protected int birthday;/*example: 20030910*/
    protected string category;/*example: bird, non_bird*/
    protected int food_weight;
    protected int is_healthy;

    function void print_hometown();
        $display("my hometown is on the earth!");
    endfunction
endclass

 
同时,在 bird non_bird 类中也有自己的 print_hometown 函数:
//子类
class bird extends animal;
    function void print_hometown();
        $display("my hometown is in sky!");
    endfunction
endclass


class non_bird extends animal;
    function void print_hometown();
        $display("my hometown is on the land!");
    endfunction
endclass
现在,有一个名字为 print_animal 的函数:
/*
关键字automatic用于声明自动变量(automatic variables)。
自动变量是在声明时创建并在每次进作用域时分配内存,在离开作用域时自动释放内存。
这意味着自动变量的生命周期仅限于其所在的作用域。
*/

function automatic void print_animal(animal p_animal);
    p_animal.print();
    p_animal.print_hometown();
endfunction

接下来调用,print_animal的参数是一个animal类型的指针,如果实例化了一个bird,并且将其传递给print_animal函数,这样做是完全允许 的,因为bird是从animal派生的,所以bird本质上是个animal

initial begin
    bird members[20];
    members[0] = new();
    members[0].init("parrot", 20091021, "bird", 20, 1);  initialize
    print_animal(members[0]);  //把类当参数传递
End

/*
结果:  my hometown is on the earth!
即调用的不是子类brid, 而是父类animal的函数
*/
如果要想得到正确的结果,那么在print_animal 函数中调用 print_hometown 之前要进行类型转换
function automatic void print_animal2(animal p_animal);
    bird p_bird;
    non_bird p_nbird;
    p_animal.print();

    if($cast(p_bird, p_animal))
        p_bird.print_hometown();
    else if($cast(p_nbird, p_animal))
        p_nbird.print_hometown();
endfunction
        如果将members[0] 作为参数传递给此函数,那么可以得到期待的结果。 cast是一个类型转换函数 。从 animal bird 或者 non_bird 类型的转换是 父类向子类的类型转换,这种类型转换必须通过cast来完成。  但是反过来,子类向父类的类型转换可以由系统自动完成,如调用print_animal 时, members[0] bird 类型的,系统自动将其转换成 animal 类型。
        
但是 print_animal2 的作法显得非常复杂,并且代码的可重用性不高。现在只有 bird non_bird 类型,如果再多加一种类型,那么就需要重新修改这个函数。在调用print_animal print_animal2 时,传递给它们的 members[0] 本身是 bird 类型的,那么有没有一种 方法可以自动调用bird print_hometown 函数呢?这个问题的答案就是 虚函数

        

animal bird non_bird 中分别定义 print_hometown2 函数,只是在定义时其前面要加上 virtual关键字
class animal;
    virtual function void print_hometown2();
        $display("my hometown is on the earth!");
    endfunction
endclass


class bird extends animal;
    virtual function void print_hometown2();
        $display("my hometown is in sky!");
    endfunction
endclass


class non_bird extends animal;
    virtual function void print_hometown2();
        $display("my hometown is on the land!");
    endfunction
endclass
print_animal3 中调用此函数:
function automatic void print_animal3(animal p_animal);
    p_animal.print();
    p_animal.print_hometown2();
endfunction
initial 语句中将 members[0] 传递给此函数后,打印出的结果就是 “my hometown is in sky
,这正是想要的结果。如果在 initial 中实例化了一个non_bird ,并将其传递给 print_animal3
initial begin
    non_bird members[20];
    members[0] = new();
    members[0].init("tiger", 20091101, "non_bird", 2000, 1);
    print_animal(members[0]);
end
那么打印出的结果就是“my hometown is on the land!”。在print_animal3中,同样都是调用print_hometown2函数,但是输出的结果却不同,表现出不同的形态,这就是多态 。多态的实现要依赖于虚函数,普通的函数,如print_hometown是不能实现多态的。

在SystemVerilog(以下简称SV)中,并没有像 C++ 中使用 virtual 关键字来声明虚函数。在 SystemVerilog 中,多态性可以通过继承和重载的方式来实现。

class Animal;
  task makeSound();
    $display("Animal makes a sound.");
  endtask
endclass

class Cat extends Animal;
  task makeSound();
    $display("Cat meows.");
  endtask
endclass

class Dog extends Animal;
  task makeSound();
    $display("Dog barks.");
  endtask
endclass

module testbench;
  initial begin
    Animal animal1;
    Animal animal2;

    animal1 = new Cat();
    animal2 = new Dog();

    animal1.makeSound();  // 输出:Cat meows.
    animal2.makeSound();  // 输出:Dog barks.
  end
endmodule

六、randomize与constraint

SystemVerilog是一门用于验证的语言。验证中,很重要的一条是能够产生一些随机的激励。为此,SystemVerilog为所有的类定义了randomize方法
class animal;
    bit [10:0] kind;
    rand bit[5:0] data;
    rand int addr;
endclass

initial begin
    animal aml;
    aml = new();
    assert(aml.randomize());
end
在一个类中只有定义为 rand类型 的字段才会在调用 randomize方法时进行随机化 。上面的定义中, data addr 会随机化为一个随机值,而kind randomize 被调用后,依然是默认值 0
randomize 对应的是 constraint 在不加任何约束的情况下,上述animal中的data经过随机化后,其值为0~'h3F中的任一值。可以定义一个constraint对其值进行约束:
class animal;
    rand bit[5:0] data;

    constraint data_cons{
        data > 10;
        data < 30;
    }
endclass
经过上述约束后, data 在随机时,其值将会介于 10 30 之间。
除了在类的定义时对数据进行约束外,还可以在调用 randomize 时对数据进行约束:
initial begin
    animal aml;
    aml = new();
    assert(aml.randomize() with {data > 10; data < 30;});
end

第1章 与UVM的第一次接触

1、前身是OVM,

2、要学习如何使用sequence机制、factory机制、callback机制、寄存器模型(

register model)等。三大 EDA 厂商 synopsys Mentor Cadence
3、DUT( Design Under Test

 

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

2.1验证平台的组成

1、验证平台要模拟DUT(design under test)的各种真实使用情况,这意味着要给DUT施加各种激励,有正常的激励,也有异常的激励;有这种模式的激励,也有那种模式的激励。激励的功能是由 driver 来实现的。
2、验证平台要能够根据DUT的输出来判断DUT的行为是否与预期相符合,完成这个功能的是 记分板 scoreboard,也被称为 checker ,本书统一以scoreboard来称呼)。既然是判断,那么牵扯到两个方面:一是判断什么,需要把什么拿来判断,这里很明显 是DUT的输出;二是判断的标准是什么。
3、· 验证平台要收集DUT的输出并把它们传递给scoreboard ,完成这个功能的是monitor
4、验证平台要能够给出预期结果。在记分板中提到了判断的标准,判断的标准通常就是预期。假设DUT是一个加法器,那么当在它的加数和被加数中分别输入1,即输入1+1时,期望DUT输出2。
当DUT在计算1+1的结果时,验证平台也必须相应完成同样的过程,也计算一次1+1。在验证平台中,完成这个过程的是 参考模型(reference model

 典型的验证平台

 2.2只有driver的验证平台

2.2.1最简单的验证平台

假设有如下的DUT
module dut(clk,
rst_n,
rxd,
rx_dv,
txd,
tx_en);

input clk;
input rst_n;
input[7:0] rxd;
input rx_dv;
output [7:0] txd;
output tx_en;

reg[7:0] txd;
reg tx_en;

always @(posedge clk) begin
	if(!rst_n) begin
			txd <= 8'b0;
			tx_en <= 1'b0;
	end
	else begin
		txd <= rxd;
		tx_en <= rx_dv;
	end
end
endmodule
UVM 中的 driver 应该如何搭建? UVM 是一个库,在这个库中,几乎所有的东西都是使用类(
class)来实现的,  driver、 monitor、reference model、scoreboard等组成部分都是类,
通过这些函数和任务可以完成 driver 的输出激励功能,完成monitor 的监测功能,完成参考模型的计算功能,完成 scoreboard的比较功能。当要实现一个功能时,首先应该想到的是从UVM的某个类派生出一个新的类,在这个新的类中实现所期望的功能。所以,使用UVM的第一条原则是: 验证平台中所有的组件应该派生自UVM中的类
UVM 验证平台中的 driver应该派生自uvm_driver ,一个简单的driver(my_driver.sv )如下例所示:
//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;
	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'b1;
		`uvm_info("my_driver", "data is drived", UVM_LOW)
        //打印信息
	end
	@(posedge top_tb.clk);
	top_tb.rx_dv <= 1'b0;
endtask

这个 driver 的功能非常简单,只是向 rxd 上发送 256 个随机数据,并将 rx_dv 信号置为高电平。当数据发送完毕后,将 rx_dv 信号置为低电平.
所有派生自uvm_driver的类的new函数有两个参数,一个是string类型的name,一个是uvm_component类型的parent。 name参数,就是名字, parent下文再介绍,这两个参
数是由uvm_component要求的,每一个派生自uvm_component或其派生类的类在其new函数中要指明两个参数: name和parent ,这是uvm_component类的一大特征。
driver所做的事情几乎都在 main_phase 中完成。 UVM由phase来管理验证平台的运 行,这些phase统一以xxxx_phase来命名,且 都有一个类型为uvm_phase、名字为phase的参数 。main_phase是uvm_driver中预先定义好的一个任务。因此几乎可以简单地认为, 实现一个driver等于实现其main_phase。
 
`uvm_info("my_driver", "data is drived", UVM_LOW)
第一个参数是字符串,用于把打印的信息归类;
第二个参数也是字符串,是具体需要打印的信息
第三个参数则是冗余级别。关键的设置为UVM_LOW
打印结果如下
UVM_INFO my_driver.sv(20)
@48500000
:drv[my_driver]data is drived
uvm_info 宏打印的结果中有如下几项:
UVM_INFO关键字:表明这是一个uvm_info宏打印的结果。除了uvm_info宏外,还有uvm_error宏、uvm_warning宏,后文中将会介绍
my_driver.sv(20):指明此条打印信息的来源,其中括号里的数字表示行号
48500000:表明此条信息的打印时间
drv这是driver在UVM树中的路径索引,  UVM采用树形结构,对于树中任何一个结点,都有一个与其相应的字符串类型的路径索引,  路径索引可以通过get_full_name函数来获取,把下列代码加入任何UVM树的结点中就可以得知当前结点的路径索引:$display("the full name of current component is: %s", get_full_name());
[my_driver]:方括号中显示的信息即调用uvm_info宏时传递的第一个参数。
data is drived:表明宏最终打印的信息
尽量使用 uvm_info 宏取代 display 语句.
定义driver后要将其实例化
//定义类
classs A;
    …
endclass

//实例化
//实例化指的是通过new创造出A的一个实例

A  a_inst;
a_inst = new();
my_driver 实例化并且最终搭建的验证平台如下:
//top_tb.sv

`timescale 1ns/1ps
`include "uvm_macros.svh"  //是UVM中的一个文件,里面包含了众多的宏定义

import uvm_pkg::*;          Import the entire uvm_pkg into the validation platform 
               
`include "my_driver.sv"  Introducing a written driver class (derived from UVM_driver)

module top_tb;

reg clk;
reg rst_n;
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);   //传入name 和 parent 
	drv.main_phase(null);
	$finish();
end

initial begin
	clk = 0;
	forever begin
		#100 clk = ~clk;
	end
end

initial begin
	rst_n = 1'b0;

	#1000;
	rst_n = 1'b1;
end

endmodule

2.2.2 加入factory机制

自动创建一个类的实例并调用其中的函数(function)和任务 (task)。
//my_driver.sv
class my_driver extends uvm_driver;

	`uvm_component_utils(my_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'b1;
		`uvm_info("my_driver", "data is drived", UVM_LOW);
	end
	@(posedge top_tb.clk);
	top_tb.rx_dv <= 1'b0;
endtask
factory 机制的实现被集成在了一个宏中: uvm_component_utils 。这个宏所做的事情非常多,其中之一就是将 my_driver 登记在UVM内部的一张表中,这张表是 factory 功能实现的基础。
在给 driver 中加入 factory 机制后,还需要对 top_tb 做一些改动
//top_tb.sv

`timescale 1ns/1ps
`include "uvm_macros.svh"  //是UVM中的一个文件,里面包含了众多的宏定义

import uvm_pkg::*;          Import the entire uvm_pkg into the validation platform 
    `              
`include "my_driver.sv"  Introducing a written driver class (derived from UVM_driver)

module top_tb;

reg clk;
reg rst_n;
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
     run_test("my_driver");
 end

//输出
//new is called
//main_phased is called


initial begin
	clk = 0;
	forever begin
		#100 clk = ~clk;
	end
end

initial begin
	rst_n = 1'b0;

	#1000;
	rst_n = 1'b1;
end

endmodule
一个 run_test 语句会创建一个 my_driver 的实例,并且会自动调用 my_driver main_phase。UVM根据这个字符串创建了其所代表类的一个实例。
所有派生自uvm_component及其派生类的类都应该使用uvm_component_utils宏注册
 在 UVM 验证平台中,只要一个类使用uvm_component_utils 注册且此类被实例化了,那么这个类的 main_phase 就会自动被调用

2.2.3 加入objection机制

虽然输出了“main_phase is called”,但是my_driver task  里面的 “data is drived”并没有输出?
UVM 中通过 objection机制 来控制验证平台的关闭,在每个phase中,UVM会检查是否有objection被提起(raise_objection),如果有,那么等待这个objection被撤销(drop_objection)后停止仿真;如果没有,则马上结束当前phase
加入了 objection 机制的 driver
//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);
	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'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

//data is drived”按照预期输出了256次。
raise_objection语句必须在 main_phase 中第一个消耗仿真时间 [1] 的语句(如@always)之前
$display 语句是不消耗仿真时间的,这些语句可以放在raise_objection 之前.

2.2.4 加入virtual interface

避免绝对路径,如 top.clk_inst.clk,每次修改麻烦,避免绝对路径的一个方法是使用宏:
`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.clk);
	for(int i = 0; i < 256; i++)begin
		@(posedge `TOP.clk);
		`TOP.rxd <= $urandom_range(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
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中使用interface ? 

class my_driver extends uvm_driver;
        my_if drv_if;  //可以在module中这样
 //因为my_driver是一个类,在类中不能使用上述方式声明一个interface
endclass

因为my_driver是一个类,在类中不能使用上述方式声明一个interface,要用virtual interface

//my_driver.sv
class my_driver extends uvm_driver;
    virtual my_if vif;
//my_driver.sv
 
class my_driver extends uvm_driver;
	
	virtual my_if vif;
	//用interface来避免绝对路径
	
	`uvm_component_utils(my_driver)
	//加入factory机制
	
	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);
	//加入objection机制 通过objection机制来控制验证平台的关闭
	
	`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'b1;
		`uvm_info("my_driver", "data is drived", UVM_LOW);
	end
	@(posedge vif.clk);
	vif.valid <= 1'b0;
	
	phase.drop_objection(this);
	//加入objection机制
	
endtask

剩下的最后一个问题就是,如何把top_tb中的input_ifmy_driver中的vif对应,最简单的方法莫过于直接赋值,但是top_tb.my_driver.xxx是不可以的。这个问题的终极原因在于UVM通过run_test语句实例化了一个脱离了top_tb层次结构的实例,建立了一个新的层次结构

UVM引进了config_db机制, 在config_db机制中,分为set和get两步操作。所谓set操作,读者可以简单地理解成是“寄信”,而get则相当于是“收信”。
在top_tb中执行set操作:
//config_db机制
//top_tb.sv
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!!!")
endfunction
这里引入了 build_phase 。与 main_phase 一样, build_phase 也是 UVM 中内建的一个phase。build_phase在new函数之后main_phase之前执行。在build_phase中主要通过config_db的set和get操作来传递一些数据,以及实例化成员变量等。
set函数的第四个参数表示要将哪个interface 通过config_db传递给my_driverget函数的第四个参数表示把得到的interface传递给哪个my_driver的成员变量。
set函数的第二个参数表示的是路径索引, UVM 通过 run_test 语句创建一个名字为 uvm_test_top 的实例。 假如要向my_driver的 var 变量传递一个int 类型的数据,那么可以使用如下方式
//tob_tb.sv
initial begin
    uvm_config_db#(int)::set(null, "uvm_test_top", "var", 100);
end



//my_driver.sv
class my_driver extends uvm_driver;
	int var;
	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!!!")
	if(!uvm_config_db#(int)::get(this, "", "var", var))
		`uvm_fatal("my_driver", "var must be set!!!")
endfunction
从这里可以看出,可以向 my_driver 许多信。上文列举的两个例子是 top_tb my_driver 传递了两个不同类型的数据,其实也可以传递相同类型的不同数据。假如my_driver 中需要两个 my_if ,那么可以在 top_tb 中这么做:
//tob_tb.sv
initial begin
    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif2", output_if);
end



//my_driver.sv
virtual my_if vif;
virtual my_if vif2;
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!!!")
	if(!uvm_config_db#(virtual my_if)::get(this, "", "vif2", vif2))
	`uvm_fatal("my_driver", "virtual interface must be set for vif2!!!")
endfunction

2.3 为验证平台加入各个组件

2.3.1加入transaction

在这些组件之间,信息的传递是基于 transaction 的.。一般来说,物理协议中的数据交换都是以帧或者包为单位的,通常在一帧或者一个包中要定义好各项参数,每个包的大小不一样.以以太网为例,每个包的大小至少是64byte。这个包中要包括源地址、目的地址、包的类型、整个包的CRC校验数据等。
transaction就是用于模拟这种实际情况,一笔transaction就是一个包
一个简单的 transaction 的定义如下:
//my_transaction.sv

//所有的transaction都要从uvm_sequence_item派生
class my_transaction extends uvm_sequence_item;

	rand bit[47:0] dmac;   //48bit的以太网目的地址
	rand bit[47:0] smac;   //smac是48bit的以太网源地址
	rand bit[15:0] ether_type;    //ether_type是以太网类型
	rand byte pload[];    //pload是其携带数据的大小
	rand bit[31:0] crc;   //CRC是前面所有数据的校验值

	constraint pload_cons{      //大小被限制在46~1500byte
		pload.size >= 46;
		pload.size <= 1500;
	}

	function bit[31:0] calc_crc();
		return 32'h0;
	endfunction

	function void post_randomize();  //post_randomize是SystemVerilog中提供的一个函数
		crc = calc_crc;
	endfunction

	`uvm_object_utils(my_transaction)

	function new(string name = "my_transaction");
		super.new(name);
	endfunction
endclass
post_randomize SystemVerilog 中提供的一个函数,当某个类的实例的 randomize 函数被调用后, post_randomize会紧随其后无条件地被调用。在UVM中,所有的transaction都要从uvm_sequence_item派生。
使用了 uvm_object_utils 。从本质上来说, my_transaction 与my_driver是有区别的,在整个仿真期间, my_driver 是一直存在的, my_transaction不同,它有生命周期,
uvm_sequence_item 的祖先就是 uvm_object.
当完成 transaction 的定义后,就可以在 my_driver 中实现基于 transaction 的驱动:
//my_driver.sv
 
class my_driver extends uvm_driver;
	
	virtual my_if vif;
	//用interface来避免绝对路径
	
	`uvm_component_utils(my_driver)
	//加入factory机制
	
	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 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!!!")
	endfunction
	
	extern virtual task main_phase(uvm_phase phase);
	
	
endclass


task my_driver::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	//加入objection机制 通过objection机制来控制验证平台的关闭
	
	//加入transaction的驱动
	my_transaction tr;
	for(int i = 0; i < 2; i++) begin
		tr = new("tr");
		
		//先使用randomize将tr随机化
		assert(tr.randomize() with {pload.size == 200;});
		
		//通过drive_one_pkt任务将tr的内容驱动到DUT的端口上
		drive_one_pkt(tr);
	end
	
	`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'b1;
		`uvm_info("my_driver", "data is drived", UVM_LOW);
	end
	@(posedge vif.clk);
	vif.valid <= 1'b0;
	
	phase.drop_objection(this);
	//加入objection机制
	
endtask



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 >> 8);
	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'b1;
		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

2.3.2加入env

引入一个容器类,在这个容器类中实例化drivermonitor、reference model和scoreboard等。在调用 run_test时,传递的参数不再是my_driver,而是这个容器类,即让UVM自动创建这个容器类的实例。在UVM中,这个容器类称为 uvm_env:
class my_env extends uvm_env;

	my_driver drv;
	//由于my_driver在uvm_env中实例化,
	//所以my_driver的父结点(parent)就是my_env。
	
	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);
	endfunction

	`uvm_component_utils(my_env)
	//uvm_component_utils宏来实现factory的注册
	
endclass
当加入了 my_env 后,整个验证平台中存在两个 build_phase ,一个是 my_env 的,一个是 my_driver 的。那么这两个 build_phase 按 照何种顺序执行呢?在UVM 的树形结构中, build_phase 的执行遵照从树根到树叶的顺序,即先执行 my_env build_phase ,再执行 my_driver的 build_phase

2.3.3加入monitor

验证平台中实现监测 DUT 行为的组件是monitor。driver负责把transaction级别的数据转变成DUT的端口级别,并驱动给DUT, monitor的行为与其相对,用于收集DUT的端口数据,并将其转换成transaction交给后续的组件如reference model、scoreboard等处理
一个monitor 的定义如下:
`ifndef MY_MONITOR__SV
`define MY_MONITOR__SV
class my_monitor extends uvm_monitor;

   virtual my_if vif;

   `uvm_component_utils(my_monitor)
   //uvm_component_utils宏来实现factory的注册
   
   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 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);
   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
   //pop dmac
   for(int i = 0; i < 6; i++) begin
      tr.dmac = {tr.dmac[39:0], data_q.pop_front()};
   end
   //pop smac
   for(int i = 0; i < 6; i++) begin
      tr.smac = {tr.smac[39:0], data_q.pop_front()};
   end
   //pop ether_type
   for(int i = 0; i < 2; i++) begin
      tr.ether_type = {tr.ether_type[7:0], data_q.pop_front()};
   end

   psize = data_q.size() - 4;
   tr.pload = new[psize];
   //pop payload
   for(int i = 0; i < psize; i++) begin
      tr.pload[i] = data_q.pop_front();
   end
   //pop crc
   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


`endif
当完成 monitor 的定义后,可以在 env 中对其进行实例化
class my_env extends uvm_env;

	my_driver drv;
	//由于my_driver在uvm_env中实例化,
	//所以my_driver的父结点(parent)就是my_env。
	
	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);
		o_mon = my_monitor::type_id::create("o_mon", this);
	endfunction

	`uvm_component_utils(my_env)
	//uvm_component_utils宏来实现factory的注册
	
endclass
需要引起注意的是这里实例化了两个 monitor ,一个用于监测 DUT 的输入口,一个用于监测 DUT 的输出口。 DUT 的输出口设置一个monitor 没有任何疑问,但是在 DUT 的输入口设置一个 monitor 有必要吗?由于 transaction 是由 driver 产生并输出到 DUT 的端口上,所以 driver 可以直接将其交给后面的 reference model

 env中实例化monitor后,要在top_tb中使用config_dbinput_ifoutput_if传递给两个monitor

2.3.4 封装成agent

上一节在验证平台中加入monitor 时,读者看到了 driver monitor 之间的联系:两者之间的代码高度相似。其本质是因为二者处理的是同一种协议.UVM中通常将二者封装在一起,成为一个agent 。因此,不同的 agent 就代表了不同的协议。
//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);

	`uvm_component_utils(my_agent)
endclass


function void my_agent::build_phase(uvm_phase phase);
	super.build_phase(phase);
    //is_active是uvm_agent的一个成员变量
	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
在把 driver monitor 封装成 agent 后,在 env 中需要实例化 agent ,而不需要直接实例化 driver monitor
class my_env extends uvm_env;

   my_agent  i_agt;
   my_agent  o_agt;
   
   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);
      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
完成 i_agt o_agt 的声明后, my_envbuild_phase中对它们进行实例化后,需要指定各自的工作模式是active模式还是 passive模式。现在,整棵UVM 树变为了如图 2-6 所示形式。

 2.3.5 加入reference model

reference model 用于完成和 DUT 相同的功能。reference model的输出被 scoreboard 接收,用于和DUT 的输出相比较
//my_model
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


//单纯地复制一份从i_agt得到的tr,并传递给后级的scoreboard中
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);  //my_copy是一个在my_transaction中定义的函数
		`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_env 中实例化。其实例化方式与 agent driver相似,
在加入my_model后,整棵 UVM 树变成了如图 2-7 所示的形式。

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 。在 my_monitor 中定义如下变量:
//my_monitor.sv
uvm_analysis_port #(my_transaction) ap;
//uvm_analysis_port是一个参数化的类,其参数就是这个analysis_port需要传递的数据的类型,在本节中是my_transaction
声明了 ap 后,需要在 monitor build_phase 中将其实例化
virtual function void build_phase(uvm_phase phase);
    ap = new("ap", this);
endfunction
main_phase 中,当收集完一个 transaction 后,需要将其写入 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_monitor my_model 中定义并实现了各自的端口之后,通信的功能并没有实现,还需要在 my_env 中使用 fifo 将两个端口联系在一起。在my_env 中定义一个 fifo ,并在 build_phase 中将其实例化.
fifo 的类型是 uvm_tlm_analysis_fifo ,它本身也是一个参数化的类
之后,在 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
这里引入了 connect_phase 。与 build_phase main_phase 类似,connect_phase也是 UVM 内建的一个 phase ,它在 build_phase 执行完成之后马上执行。但是与build_phase 不同的是,它的执行顺序并不是从树根到树叶,而是从树叶到树根 —— 先执行 driver 和monitor的 connect_phase ,再执行 agent connect_phase ,最后执行 env connect_phase

2.3.6 加入scoreboard

在验证平台中加入了 reference model monitor 之后,最后一步是加入 scoreboard。  my_scoreboard 的代码如下:
class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];

    //通过exp_port获取来源于reference model较的数据
	uvm_blocking_get_port #(my_transaction) exp_port;

    //通过act_port获取来源于o_agt的monitor的数据
	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

function bit my_compare(my_transaction tr);
	bit result;

	if(tr == null)
		`uvm_fatal("my_transaction", "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
		for(int i = 0; i < pload.size(); i++) begin
			if(pload[i] != tr.pload[i])
				result = 0;
		end
	return result;
endfunction

        my_scoreboard要比较的数据一是来源于reference model,二是来源于o_agtmonitor。前者通过exp_port获取,而后者通过act_port获取。在main_phase中通过fork建立起了两个进程,一个进程处理exp_port的数据,当收到数据后,把数据放入expect_queue中;

另外一个进程处理act_port的数据,这是DUT的输出数据,当收集到这些数据后,从expect_queue中弹出之前从exp_port收到的数据,并调用my_transactionmy_compare函数。

采用这种比较处理方式的前提是 exp_port要比act_port 先收到数据
完成my_scoreboard 的定义后,也需要在 my_env 中将其实例化

2.3.7 加入field_automation机制

2.3.3 节中引入 my_mointor 时,在 my_transaction 中加入了 my_print 函数;在 2.3.5 节中引入 reference model 时,加入了 my_copy 函数;在2.3.6 节引入 scoreboard 时,加入了 my_compare 函数。上述三个函数虽然各自不同,但是对于不同的 transaction 来说,都是类似的:它们都需要逐字段地对transaction 进行某些操作。
UVM中的 field_automation机制,使用uvm_field系列宏实现过定义某些规则自动实现这三个函数
class my_transaction extends uvm_sequence_item;

	rand bit[47:0] dmac;   //48bit的以太网目的地址
	rand bit[47:0] smac;   //smac是48bit的以太网源地址
	rand bit[15:0] ether_type;    //ether_type是以太网类型
	rand byte pload[];    //pload是其携带数据的大小
	rand bit[31:0] crc;   //CRC是前面所有数据的校验值

	constraint pload_cons{      //大小被限制在46~1500byte
		pload.size >= 46;
		pload.size <= 1500;
	}
	
	function bit[31:0] calc_crc();
		return 32'h0;
	endfunction

	function void post_randomize();  //post_randomize是SystemVerilog中提供的一个函数
		crc = calc_crc;
	endfunction

	`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

	function new(string name = "my_transaction");
		super.new(name);
	endfunction
	
	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
	
endclass
当使用上述宏注册之后,可以直接调用 copy compare print 等函数,而无需自己定义
引入 field_automation 机制的另外一大好处是简化了 driver和monitor , my_driver drv_one_pkt 任务可以简化为:
//引入field_automation机制后的简化
task my_driver::drive_one_pkt(my_transaction tr);
	byte unsigned data_q[];
	int data_size;

	data_size = tr.pack_bytes(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

2.4 UVM的终极大作:sequence

2.4.1 在验证平台中加入sequencer

sequence机制用于产生激励, driver 只负责驱动 transaction ,而不负责产生transaction. sequence机制有两大组成部分,一是sequence,二是sequencer。先介绍sequencer。 一个 sequencer 的定义如下:
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
在加入 sequencer 后,整个 UVM 树的结构变成如图 所示的形式。

2.4.2 sequence机制

 sequence不属于验证平台的任何一部分,从本质上来说,sequencer是一个uvm_component,而sequence是一个uvm_object,一个sequence应该使用uvm_object_utils宏注册到factory中。

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的类型,
每一个 sequence都有一个 body 任务,当一个 sequence 启动之后,会自动执行 body 中的代码。
UVM_do的作用:
1、  创建一个 my_transaction 的实例 m_trans
2、将其随机化
3、最终将其送给 sequencer。
一个 sequence 在向 sequencer 发送 transaction 前,要先向 sequencer 发送一个请求,sequencer把这个请求放在一个仲裁队列中。作 为sequencer,它需做两件事情:第一,检测仲裁队列里是否有 某个 sequence发送transaction的请求;第二,检测driver是否申请 transaction。
1、如果仲裁队列里有发送请求,但是 driver 没有申请 transaction ,那么 sequencer 将会一直处于等待 driver 的状态,直到 driver 申请新的transaction
2、如果仲裁队列中没有发送请求,但是 driver sequencer 申请新的 transaction ,那么 sequencer 将会处于等待 sequence 的状态。
3、如果仲裁队列中有发送请求,同时 driver 也在向 sequencer 申请新的 transaction ,那么将会同意发送请求,sequence产生 transaction并交给 sequencer ,最终 driver 获得这个 transaction
 
driver 如何向 sequencer 申请 transaction?
uvm_driver 中有成员变量 seq_item_port ,而在 uvm_sequencer 中有成员变量seq_item_export,这两者之间可以建立一个 通道”。在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

2.4.3 default_sequence的使用

使用 default_sequence 的方式非常简单,只需要在某个 component (如 my_env )的 build_phase 中设置如下代码即可:
        
uvm_config_db#(uvm_object_wrapper)::set(this,
	"i_agt.sqr.main_phase",
	"default_sequence",
	my_sequence::type_id::get());
这是除了在 top_tb 中通过 config_db 设置 virtual interface 后再一次用到 config_db 的功能,

2.5建造测试用例

目前树根是my_env,  ,树根是一个基于 uvm_test派生的类,真正的测试用例都是基于 base_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_sequence",
		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_severity_count(UVM_ERROR);

	if (err_num != 0) begin
		$display("TEST CASE FAILED");
	end
	else begin
		$display("TEST CASE PASSED");
	end
endfunction
base_test 派生自 uvm_test ,使用 uvm_component_utils 宏来注册到 factory中,通常在base_test中做如下事情:第一,设置整个验证平台的超时退出时间;第二,通过config_db设置验证平台中某些参数的值。

 

2.5.2 UVM中测试用例的启动
要测试一个DUT 是否按照预期工作,需要对其施加不同的激励,这些激励被称为测试向量或pattern。在命令行中指定参数来启动不同的测试用例
//启动my_case0
initial begin
    run_test("my_case0");
end

//启动my_case1
initial begin
    run_test("my_case1");
end

修改代码,重新编译后才能运行,不太方便,于是可以这样

initial begin
    run_test();
end

//UVM会利用UVM_TEST_NAME从命令行中寻找测试用例的名字

<sim command>
 +UVM_TEST_NAME=my_case0

 第3章 UVM基础

3.1 uvm_component与uvm_object

3.1.1uvm_component派生自uvm_object

uvm_object UVM中最基本的类,uvm_component有两大特性是uvm_object所没有的,一是通过在new的时候指定parent参数来形成一种树形的组织结构,二是有phase的自动执行特点.

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

eachanm

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

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

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

打赏作者

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

抵扣说明:

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

余额充值