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

 
同时,在birdnon_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是一个类型转换函数。从animalbird或者non_bird 类型的转换是父类向子类的类型转换,这种类型转换必须通过cast来完成。 但是反过来,子类向父类的类型转换可以由系统自动完成,如调用print_animal时,members[0]bird类型的,系统自动将其转换成animal类型。
        
但是print_animal2的作法显得非常复杂,并且代码的可重用性不高。现在只有birdnon_bird类型,如果再多加一种类型,那么就需要重新修改这个函数。在调用print_animalprint_animal2时,传递给它们的members[0]本身是bird类型的,那么有没有一种 方法可以自动调用birdprint_hometown函数呢?这个问题的答案就是虚函数

        

animalbirdnon_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方法时进行随机化。上面的定义中,dataaddr会随机化为一个随机值,而kindrandomize被调用后,依然是默认值0
randomize对应的是constraint在不加任何约束的情况下,上述animal中的data经过随机化后,其值为0~'h3F中的任一值。可以定义一个constraint对其值进行约束:
class animal;
    rand bit[5:0] data;

    constraint data_cons{
        data > 10;
        data < 30;
    }
endclass
经过上述约束后,data在随机时,其值将会介于1030之间。
除了在类的定义时对数据进行约束外,还可以在调用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厂商synopsysMentorCadence
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_drivermain_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_tbmy_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_randomizeSystemVerilog中提供的一个函数,当某个类的实例的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_envbuild_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时,读者看到了drivermonitor之间的联系:两者之间的代码高度相似。其本质是因为二者处理的是同一种协议.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
在把drivermonitor封装成agent后,在env中需要实例化agent,而不需要直接实例化drivermonitor
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_agto_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中实例化。其实例化方式与agentdriver相似,
在加入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级别 的通信
UVMtransaction级别的通信 中,数据的发送有多种方式,其中一种是使uvm_analysis_port。在my_monitor中定义如下变量:
//my_monitor.sv
uvm_analysis_port #(my_transaction) ap;
//uvm_analysis_port是一个参数化的类,其参数就是这个analysis_port需要传递的数据的类型,在本节中是my_transaction
声明了ap后,需要在monitorbuild_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_monitormy_model中定义并实现了各自的端口之后,通信的功能并没有实现,还需要在my_env中使用fifo将两个端口联系在一起。在my_env中定义一个fifo,并在build_phase中将其实例化.
fifo的类型是uvm_tlm_analysis_fifo,它本身也是一个参数化的类
之后,在connect_phase中将fifo分别与my_monitor中的analysis_portmy_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_phasemain_phase类似,connect_phase也是UVM内建的一个phase,它在build_phase执行完成之后马上执行。但是与build_phase不同的是,它的执行顺序并不是从树根到树叶,而是从树叶到树根——先执行driver和monitor的connect_phase,再执行agentconnect_phase,最后执行envconnect_phase

2.3.6 加入scoreboard

在验证平台中加入了reference modelmonitor之后,最后一步是加入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
当使用上述宏注册之后,可以直接调用copycompareprint等函数,而无需自己定义
引入field_automation机制的另外一大好处是简化了driver和monitor ,my_driverdrv_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、如果仲裁队列中没有发送请求,但是driversequencer申请新的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_objectUVM中最基本的类,uvm_component有两大特性是uvm_object所没有的,一是通过在new的时候指定parent参数来形成一种树形的组织结构,二是有phase的自动执行特点.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

eachanm

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

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

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

打赏作者

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

抵扣说明:

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

余额充值