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
UVM实战读书笔记-----持续更新_第1张图片

 

第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
UVM实战读书笔记-----持续更新_第2张图片

 典型的验证平台

UVM实战读书笔记-----持续更新_第3张图片

 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
UVM实战读书笔记-----持续更新_第4张图片

 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 所示形式。
UVM实战读书笔记-----持续更新_第5张图片

 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 所示的形式。

UVM实战读书笔记-----持续更新_第6张图片

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 中将其实例化
UVM实战读书笔记-----持续更新_第7张图片

 

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

你可能感兴趣的:(IC验证,UVM)