《UVM实战》——2.2节只有driver的验证平台

简介:

本节书摘来自华章社区《UVM实战》一书中的第2章,第2.2节只有driver的验证平台,作者 张 强,更多章节内容可以访问云栖社区“华章社区”公众号查看

2.2 只有driver的验证平台
driver是验证平台最基本的组件,是整个验证平台数据流的源泉。本节以一个简单的DUT为例,说明一个只有driver的UVM验证平台是如何搭建的。

2.2.1 最简单的验证平台
在本章中,假设有如下的DUT定义:
代码清单 2-1

  1 module dut(clk,
  2           rst_n,
  3           rxd,
  4           rx_dv,
  5           txd,
  6           tx_en);
  7 input clk;
  8 input rst_n;
  9 input[7:0] rxd;
 10 input rx_dv;
 11 output [7:0] txd;
 12 output tx_en;
 13
 14 reg[7:0] txd;
 15 reg tx_en;
 16
 17 always @(posedge clk) begin
 18    if(!rst_n) begin
 19       txd <= 8'b0;
 20       tx_en <= 1'b0;
 21    end
 22    else begin
 23       txd <= rxd;
 24       tx_en <= rx_dv;
 25    end
 26 end
 27 endmodule

这个DUT的功能非常简单,通过rxd接收数据,再通过txd发送出去。其中rx_dv是接收的数据有效指示,tx_en是发送的数据有效指示。本章中所有例子都是基于这个DUT。
UVM中的driver应该如何搭建?UVM是一个库,在这个库中,几乎所有的东西都是使用类(class)来实现的。driver、monitor、reference model、scoreboard等组成部分都是类。类是像SystemVerilog这些面向对象编程语言中最伟大的发明之一,是面向对象的精髓所在。类有函数(function),另外还可以有任务(task),通过这些函数和任务可以完成driver的输出激励功能,完成monitor的监测功能,完成参考模型的计算功能,完成scoreboard的比较功能。类中可以有成员变量,这些成员变量可以控制类的行为,如控制driver的行为等。当要实现一个功能时,首先应该想到的是从UVM的某个类派生出一个新的类,在这个新的类中实现所期望的功能。所以,使用UVM的第一条原则是:验证平台中所有的组件应该派生自UVM中的类。
UVM验证平台中的driver应该派生自uvm_driver,一个简单的driver如下例所示:
代码清单 2-2

  3 class my_driver extends uvm_driver;
  4
  5    function new(string name = "my_driver", uvm_component parent = null);
  6       super.new(name, parent);
  7    endfunction
  8    extern virtual task main_phase(uvm_phase phase);
  9 endclass
 10
 11 task my_driver::main_phase(uvm_phase phase);
 12    top_tb.rxd <= 8'b0;
 13    top_tb.rx_dv <= 1'b0;
 14    while(!top_tb.rst_n)
 15       @(posedge top_tb.clk);
 16    for(int i = 0; i < 256; i++)begin
 17       @(posedge top_tb.clk);
 18       top_tb.rxd <= $urandom_range(0, 255);
 19       top_tb.rx_dv <= 1'b1;
 20       `uvm_info("my_driver", "data is drived", UVM_LOW)
 21    end
 22    @(posedge top_tb.clk);
 23    top_tb.rx_dv <= 1'b0;
 24 endtask

这个driver的功能非常简单,只是向rxd上发送256个随机数据,并将rx_dv信号置为高电平。当数据发送完毕后,将rx_dv信号置为低电平。在这个driver中,有两点应该引起注意:
所有派生自uvm_driver的类的new函数有两个参数,一个是string类型的name,一个是uvm_component类型的parent。关于name参数,比较好理解,就是名字而已;至于parent则比较难以理解,读者可暂且放在一边,下文会有介绍。事实上,这两个参数是由uvm_component要求的,每一个派生自uvm_component或其派生类的类在其new函数中要指明两个参数:name和parent,这是uvm_component类的一大特征。而uvm_driver是一个派生自uvm_component的类,所以也会有这两个参数。
driver所做的事情几乎都在main_phase中完成。UVM由phase来管理验证平台的运行,这些phase统一以xxxx_phase来命名,且都有一个类型为uvm_phase、名字为phase的参数。main_phase是uvm_driver中预先定义好的一个任务。因此几乎可以简单地认为,实现一个driver等于实现其main_phase。
上述代码中还出现了uvm_info宏。这个宏的功能与Verilog中display语句的功能类似,但是它比display语句更加强大。它有三个参数,第一个参数是字符串,用于把打印的信息归类;第二个参数也是字符串,是具体需要打印的信息;第三个参数则是冗余级别。在验证平台中,某些信息是非常关键的,这样的信息可以设置为UVM_LOW,而有些信息可有可无,就可以设置为UVM_HIGH,介于两者之间的就是UVM_MEDIUM。UVM默认只显示UVM_MEDIUM或者UVM_LOW的信息,本书3.4.1节会讲述如何显示UVM_HIGH的信息。本节中uvm_info宏打印的结果如下:
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):指明此条打印信息的来源,其中括号里的数字表示原始的uvm_info打印语句在my_driver.sv中的行号。
48500000:表明此条信息的打印时间。
drv:这是driver在UVM树中的路径索引。UVM采用树形结构,对于树中任何一个结点,都有一个与其相应的字符串类型的路径索引。路径索引可以通过get_full_name函数来获取,把下列代码加入任何UVM树的结点中就可以得知当前结点的路径索引:
代码清单 2-3

$display("the full name of current component is: %s", get_full_name());
[my_driver]:方括号中显示的信息即调用uvm_info宏时传递的第一个参数。
data is drived:表明宏最终打印的信息。
可见,uvm_info宏非常强大,它包含了打印信息的物理文件来源、逻辑结点信息(在UVM树中的路径索引)、打印时间、对信息的分类组织及打印的信息。读者在搭建验证平台时应该尽量使用uvm_info宏取代display语句。
定义my_driver后需要将其实例化。这里需要注意类的定义与类的实例化的区别。所谓类的定义,就是用编辑器写下:
代码清单 2-4
classs A;
…
endclass
而所谓类的实例化指的是通过new创造出A的一个实例。如:
代码清单 2-5
A a_inst;
a_inst = new();

类的定义类似于在纸上写下一纸条文,然后把这些条文通知给SystemVerilog的仿真器:验证平台可能会用到这样的一个类,请做好准备工作。而类的实例化在于通过new()来通知SystemVerilog的仿真器:请创建一个A的实例。仿真器接到new的指令后,就会在内存中划分一块空间,在划分前,会首先检查是否已经预先定义过这个类,在已经定义过的情况下,按照定义中所指定的“条文”分配空间,并且把这块空间的指针返回给a_inst,之后就可以通过a_inst来查看类中的各个成员变量,调用成员函数/任务等。对大部分的类来说,如果只定义而不实例化,是没有任何意义的;而如果不定义就直接实例化,仿真器将会报错。
对my_driver实例化并且最终搭建的验证平台如下:
代码清单 2-6

  1 `timescale 1ns/1ps
  2 `include "uvm_macros.svh"
  3
  4 import uvm_pkg::*;
  5 `include "my_driver.sv"
  6
  7 module top_tb;
  8
  9 reg clk;
 10 reg rst_n;
 11 reg[7:0] rxd;
 12 reg rx_dv;
 13 wire[7:0] txd;
 14 wire tx_en;
 15
 16 dut my_dut(.clk(clk),
 17            .rst_n(rst_n),
 18            .rxd(rxd),
 19            .rx_dv(rx_dv),
 20            .txd(txd),
 21            .tx_en(tx_en));
 22
 23 initial begin
 24    my_driver drv;
 25    drv = new("drv", null);
 26    drv.main_phase(null);
 27    $finish();
 28 end
 29
 30 initial begin
 31    clk = 0;
 32    forever begin
 33       #100 clk = ~clk;
 34    end
 35 end
 36
 37 initial begin
 38    rst_n = 1'b0;
 39    #1000;
 40    rst_n = 1'b1;
 41 end
 42
 43 endmodule

第2行把uvm_macros.svh文件通过include语句包含进来。这是UVM中的一个文件,里面包含了众多的宏定义,只需要包含一次。
第4行通过import语句将整个uvm_pkg导入验证平台中。只有导入了这个库,编译器在编译my_driver.sv文件时才会认识其中的uvm_driver等类名。
第24和25行定义一个my_driver的实例并将其实例化。注意这里调用new函数时,其传入的名字参数为drv,前文介绍uvm_info宏的打印信息时出现的代表路径索引的drv就是在这里传入的参数drv。另外传入的parent参数为null,在真正的验证平台中,这个参数一般不是null,这里暂且使用null。
第26行显式地调用my_driver的main_phase。在main_phase的声明中,有一个uvm_phase类型的参数phase,在真正的验证平台中,这个参数是不需要用户理会的。本节的验证平台还算不上一个完整的UVM验证平台,所以暂且传入null。
第27行调用finish函数结束整个仿真,这是一个Verilog中提供的函数。
运行这个例子,可以看到“data is drived”被输出了256次。
2.2.2 加入factory机制
上一节给出了一个只有driver、使用UVM搭建的验证平台。严格来说这根本就不算是UVM验证平台,因为UVM的特性几乎一点都没有用到。像上节中my_driver的实例化及drv.main_phase的显式调用,即使不使用UVM,只使用简单的SystemVerilog也可以完成。本节将会为读者展示在初学者看来感觉最神奇的一点:自动创建一个类的实例并调用其中的函数(function)和任务(task)。
要使用这个功能,需要引入UVM的factory机制:
代码清单 2-7

  3 class my_driver extends uvm_driver;
  4
  5    `uvm_component_utils(my_driver)
  6    function new(string name = "my_driver", uvm_component parent = null);
  7       super.new(name, parent);
  8       `uvm_info("my_driver", "new is called", UVM_LOW);
  9    endfunction
 10    extern virtual task main_phase(uvm_phase phase);
 11 endclass
 12
 13 task my_driver::main_phase(uvm_phase phase);
 14    `uvm_info("my_driver", "main_phase is called", UVM_LOW);
 15    top_tb.rxd <= 8'b0;
 16    top_tb.rx_dv <= 1'b0;
 17    while(!top_tb.rst_n)
 18       @(posedge top_tb.clk);
 19    for(int i = 0; i < 256; i++)begin
 20       @(posedge top_tb.clk);
 21       top_tb.rxd <= $urandom_range(0, 255);
 22       top_tb.rx_dv <= 1'b1;
 23       `uvm_info("my_driver", "data is drived", UVM_LOW);
 24    end
 25    @(posedge top_tb.clk);
 26    top_tb.rx_dv <= 1'b0;
 27 endtask

factory机制的实现被集成在了一个宏中:uvm_component_utils。这个宏所做的事情非常多,其中之一就是将my_driver登记在UVM内部的一张表中,这张表是factory功能实现的基础。只要在定义一个新的类时使用这个宏,就相当于把这个类注册到了这张表中。那么factory机制到底是什么?这个宏还做了哪些事情呢?这些属于UVM中的高级问题,本书会在后文一一展开。
在给driver中加入factory机制后,还需要对top_tb做一些改动:
代码清单 2-8

  7 module top_tb;
  …
 36 initial begin
 37    run_test("my_driver");
 38 end
 39
 40 endmodule
这里使用一个run_test语句替换掉了代码清单2-6中第23到28行的my_driver实例化及main_phase的显式调用。运行这个新的验证平台,会输出如下语句:
new is called
main_phased is called

一个run_test语句会创建一个my_driver的实例,并且会自动调用my_driver的main_phase。仔细观察run_test语句,会发现传递给它的是一个字符串。UVM根据这个字符串创建了其所代表类的一个实例。如果没有UVM,读者自己能够实现同样的功能吗?
根据类名创建一个类的实例,这是uvm_component_utils宏所带来的效果,同时也是factory机制给读者的最初印象。只有在类定义时声明了这个宏,才能使用这个功能。所以从某种程度上来说,这个宏起到了注册的作用。只有经过注册的类,才能使用这个功能,否则根本不能使用。请记住一点:所有派生自uvm_component及其派生类的类都应该使用uvm_component_utils宏注册。
除了根据一个字符串创建类的实例外,上述代码中另外一个神奇的地方是main_phase被自动调用了。在UVM验证平台中,只要一个类使用uvm_component_utils注册且此类被实例化了,那么这个类的main_phase就会自动被调用。这也就是为什么上一节中会强调实现一个driver等于实现其main_phase。所以,在driver中,最重要的就是实现main_phase。
上面的例子中,只输出到“main_phase is called”。令人沮丧的是,根本没有输出“data is drived”,而按照预期,它应该输出256次。关于这个问题,牵涉UVM的objection机制。
2.2.3 加入objection机制
在上一节中,虽然输出了“main_phase is called”,但是“data is drived”并没有输出。而main_phase是一个完整的任务,没有理由只执行第一句,而后面的代码不执行。看上去似乎main_phase在执行的过程中被外力“杀死”了,事实上也确实如此。
UVM中通过objection机制来控制验证平台的关闭。细心的读者可能发现,在上节的例子中,并没有如2.2.1节所示显式地调用finish语句来结束仿真。但是在运行上节例子时,仿真平台确实关闭了。在每个phase中,UVM会检查是否有objection被提起(raise_objection),如果有,那么等待这个objection被撤销(drop_objection)后停止仿真;如果没有,则马上结束当前phase。
加入了objection机制的driver如下所示:
代码清单 2-9

 13 task my_driver::main_phase(uvm_phase phase);
 14   phase.raise_objection(this);
 15   `uvm_info("my_driver", "main_phase is called", UVM_LOW);
 16   top_tb.rxd <= 8'b0;
 17   top_tb.rx_dv <= 1'b0;
 18   while(!top_tb.rst_n)
 19     @(posedge top_tb.clk);
 20   for(int i = 0; i < 256; i++)begin
 21     @(posedge top_tb.clk);
 22     top_tb.rxd <= $urandom_range(0, 255);
 23     top_tb.rx_dv <= 1'b1;
 24     `uvm_info("my_driver", "data is drived", UVM_LOW);
 25   end
 26   @(posedge top_tb.clk);
 27   top_tb.rx_dv <= 1'b0;
 28   phase.drop_objection(this);
 29 endtask 
在开始学习时,读者可以简单地将drop_objection语句当成是finish函数的替代者,只是在drop_objection语句之前必须先调用raise_objection语句,raise_objection和drop_objection总是成对出现。加入objection机制后再运行验证平台,可以发现“data is drived”按照预期输出了256次。
raise_objection语句必须在main_phase中第一个消耗仿真时间的语句之前。如$display语句是不消耗仿真时间的,这些语句可以放在raise_objection之前,但是类似
@(posedge top.clk)等语句是要消耗仿真时间的。按照如下的方式使用raise_objection是无法起到作用的:
代码清单 2-10
task my_driver::main_phase(uvm_phase phase);
   @(posedge top_tb.clk);
   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

2.2.4 加入virtual interface
在前几节的例子中,driver中等待时钟事件(@posedge top.clk)、给DUT中输入端口赋值(top.rx_dv <= 1' b1)都是使用绝对路径,绝对路径的使用大大减弱了验证平台的可移植性。一个最简单的例子就是假如clk信号的层次从top.clk变成了top.clk_inst.clk,那么就需要对driver中的相关代码做大量修改。因此,从根本上来说,应该尽量杜绝在验证平台中使用绝对路径。
避免绝对路径的一个方法是使用宏:
代码清单 2-11

`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
这样,当路径修改时,只需要修改宏的定义即可。但是假如clk的路径变为了top_tb.clk_inst.clk,而rst_n的路径变为了top_tb.rst_inst.rst_n,那么单纯地修改宏定义是无法起到作用的。
避免绝对路径的另外一种方式是使用interface。在SystemVerilog中使用interface来连接验证平台与DUT的端口。interface的定义比较简单:
代码清单 2-12
文件:src/ch2/section2.2/2.2.4/my_if.sv
  4 interface my_if(input clk, input rst_n);
  5
  6   logic [7:0] data;
  7   logic valid;
  8 endinterface
定义了interface后,在top_tb中实例化DUT时,就可以直接使用:
代码清单 2-13
文件:src/ch2/section2.2/2.2.4/top_tb.sv
 17 my_if input_if(clk, rst_n);
 18 my_if output_if(clk, rst_n);
 19
 20 dut my_dut(.clk(clk),
 21            .rst_n(rst_n),
 22            .rxd(input_if.data),
 23            .rx_dv(input_if.valid),
 24            .txd(output_if.data),
 25            .tx_en(output_if.valid));
那么如何在driver中使用interface呢?一种想法是在driver中声明如下语句,然后再通过赋值的形式将top_tb中的input_if传递给它:
代码清单 2-14
class my_driver extends uvm_driver;
  my_if  drv_if;
  …
endclass
读者可以试一下,这样的使用方式是会报语法错误的,因为my_driver是一个类,在类中不能使用上述方式声明一个interface,只有在类似top_tb这样的模块(module)中才可以。在类中使用的是virtual interface:
代码清单 2-15
文件:src/ch2/section2.2/2.2.4/my_driver.sv
  3 class my_driver extends uvm_driver;
  4
  5   virtual my_if vif;
在声明了vif后,就可以在main_phase中使用如下方式驱动其中的信号:
代码清单 2-16
文件:src/ch2/section2.2/2.2.4/my_driver.sv
 23 task my_driver::main_phase(uvm_phase phase);
 24    phase.raise_objection(this);
 25    `uvm_info("my_driver", "main_phase is called", UVM_LOW);
 26    vif.data <= 8'b0;
 27    vif.valid <= 1'b0;
 28    while(!vif.rst_n)
 29       @(posedge vif.clk);
 30    for(int i = 0; i < 256; i++)begin
 31       @(posedge vif.clk);
 32       vif.data <= $urandom_range(0, 255);
 33       vif.valid <= 1'b1;
 34       `uvm_info("my_driver", "data is drived", UVM_LOW);
 35    end
 36    @(posedge vif.clk);
 37    vif.valid <= 1'b0;
 38    phase.drop_objection(this);
 39 endtask

可以清楚看到,代码中的绝对路径已经消除了,大大提高了代码的可移植性和可重用性。
剩下的最后一个问题就是,如何把top_tb中的input_if和my_driver中的vif对应起来呢?最简单的方法莫过于直接赋值。此时一个新的问题又摆在了面前:在top_tb中,通过run_test语句建立了一个my_driver的实例,但是应该如何引用这个实例呢?不可能像引用my_dut那样直接引用my_driver中的变量:top_tb.my_dut.xxx是可以的,但是top_tb.my_driver.xxx是不可以的。这个问题的终极原因在于UVM通过run_test语句实例化了一个脱离了top_tb层次结构的实例,建立了一个新的层次结构。
对于这种脱离了top_tb层次结构,同时又期望在top_tb中对其进行某些操作的实例,UVM引进了config_db机制。在config_db机制中,分为set和get两步操作。所谓set操作,读者可以简单地理解成是“寄信”,而get则相当于是“收信”。在top_tb中执行set操作:
代码清单 2-17

 44 initial begin
 45   uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
 46 end
在my_driver中,执行get操作:
代码清单 2-18
文件:src/ch2/section2.2/2.2.4/my_driver.sv
 13   virtual function void build_phase(uvm_phase phase);
 14     super.build_phase(phase);
 15     `uvm_info("my_driver", "build_phase is called", UVM_LOW);
 16     if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
 17        `uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
 18   endfunction

这里引入了build_phase。与main_phase一样,build_phase也是UVM中内建的一个phase。当UVM启动后,会自动执行build_phase。build_phase在new函数之后main_phase之前执行。在build_phase中主要通过config_db的set和get操作来传递一些数据,以及实例化成员变量等。需要注意的是,这里需要加入super.build_phase语句,因为在其父类的build_phase中执行了一些必要的操作,这里必须显式地调用并执行它。build_phase与main_phase不同的一点在于,build_phase是一个函数phase,而main_phase是一个任务phase,build_phase是不消耗仿真时间的。build_phase总是在仿真时间($time函数打印出的时间)为0时执行。
在build_phase中出现了uvm_fatal宏,uvm_fatal宏是一个类似于uvm_info的宏,但是它只有两个参数,这两个参数与uvm_info宏的前两个参数的意义完全一样。与uvm_info宏不同的是,当它打印第二个参数所示的信息后,会直接调用Verilog的finish函数来结束仿真。uvm_fatal的出现表示验证平台出现了重大问题而无法继续下去,必须停止仿真并做相应的检查。所以对于uvm_fatal来说,uvm_info中出现的第三个参数的冗余度级别是完全没有意义的,只要是uvm_fatal打印的信息,就一定是非常关键的,所以无需设置第三个参数。
config_db的set和get函数都有四个参数,这两个函数的第三个参数必须完全一致。set函数的第四个参数表示要将哪个interface通过config_db传递给my_driver,get函数的第四个参数表示把得到的interface传递给哪个my_driver的成员变量。set函数的第二个参数表示的是路径索引,即在2.2.1节介绍uvm_info宏时提及的路径索引。在top_tb中通过run_test创建了一个my_driver的实例,那么这个实例的名字是什么呢?答案是uvm_test_top:UVM通过run_test语句创建一个名字为uvm_test_top的实例。读者可以通过把代码清单2-3中的语句插入my_driver(build_phase或者main_phase)中来验证。
无论传递给run_test的参数是什么,创建的实例的名字都为uvm_test_top。由于set操作的目标是my_driver,所以set函数的第二个参数就是uvm_test_top。set函数的第一个参数null以及get函数的第一和第二个参数可以暂时放在一边,后文会详细说明。
set函数与get函数让人疑惑的另外一点是其古怪的写法。使用双冒号是因为这两个函数都是静态函数,而uvm_config_db#(virtual my_if)则是一个参数化的类,其参数就是要寄信的类型,这里是virtual my_if。假如要向my_driver的var变量传递一个int类型的数据,那么可以使用如下方式:
代码清单 2-19

initial begin
   uvm_config_db#(int)::set(null, "uvm_test_top", "var", 100);
end
而在my_driver中应该使用如下方式:
代码清单 2-20
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中这么做:
代码清单 2-21
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中这么做:
代码清单 2-22
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
相关文章
|
13天前
|
测试技术 Shell 开发者
UVM与验证环境一文通
UVM与验证环境一文通
66 0
|
4月前
|
Java 数据安全/隐私保护
Neo4j【付诸实践 01】SpringBoot集成报错org.neo4j.driver.exceptions.ClientException:服务器不支持此驱动程序支持的任何协议版本(解决+源代码)
Neo4j【付诸实践 01】SpringBoot集成报错org.neo4j.driver.exceptions.ClientException:服务器不支持此驱动程序支持的任何协议版本(解决+源代码)
70 1
|
5月前
|
网络协议 流计算 Windows
2.5 Windows驱动开发:DRIVER_OBJECT对象结构
在Windows内核中,每个设备驱动程序都需要一个`DRIVER_OBJECT`对象,该对象由系统创建并传递给驱动程序的`DriverEntry`函数。驱动程序使用此对象来注册与设备对象和其他系统对象的交互,并在操作系统需要与驱动程序进行交互时使用此对象。`DRIVER_OBJECT`对象还包含了与驱动程序所管理的设备对象相关联的设备扩展结构,以及用于处理`I/O`请求的函数指针等信息。它是驱动程序与操作系统内核之间的桥梁,用于协调设备的操作和管理。
45 0
2.5 Windows驱动开发:DRIVER_OBJECT对象结构
|
6月前
|
前端开发
【前端验证】对uvm_info宏的进一步封装尝试
【前端验证】对uvm_info宏的进一步封装尝试
|
网络协议 流计算 Windows
驱动开发:探索DRIVER_OBJECT驱动对象
本章将探索驱动程序开发的基础部分,了解驱动对象`DRIVER_OBJECT`结构体的定义,一般来说驱动程序`DriverEntry`入口处都会存在这样一个驱动对象,该对象内所包含的就是当前所加载驱动自身的一些详细参数,例如驱动大小,驱动标志,驱动名,驱动节等等,每一个驱动程序都会存在这样的一个结构。
60356 0
驱动开发:探索DRIVER_OBJECT驱动对象
RK3399平台开发系列讲解(USB设备驱动)5.41、ECM Function Driver代码分析
RK3399平台开发系列讲解(USB设备驱动)5.41、ECM Function Driver代码分析
117 0
RK3399平台开发系列讲解(USB设备驱动)5.41、ECM Function Driver代码分析
RK3399平台开发系列讲解(内核入门篇)1.51、platform_get_resource 函数实现细节
RK3399平台开发系列讲解(内核入门篇)1.51、platform_get_resource 函数实现细节
93 0
RK3399平台开发系列讲解(内核入门篇)1.51、platform_get_resource 函数实现细节
RK3399平台开发系列讲解(内核入门篇)1.52、Platform 中 probe函数的调用栈分析
RK3399平台开发系列讲解(内核入门篇)1.52、Platform 中 probe函数的调用栈分析
89 0
 RK3399平台开发系列讲解(内核入门篇)1.52、Platform 中 probe函数的调用栈分析
【UVM源码学习】uvm_report_server
【UVM源码学习】uvm_report_server
171 0
【UVM源码学习】uvm_report_server
|
编译器
【UVM避坑】记录 UVM / SV 使用过程中遇到的琐碎问题
【UVM避坑】记录 UVM / SV 使用过程中遇到的琐碎问题
846 0
【UVM避坑】记录 UVM / SV 使用过程中遇到的琐碎问题

热门文章

最新文章