从零开始,搭建一个简单的UVM验证平台(二)
前言
这篇系列将从0开始搭建一个UVM验证平台,来帮助一些学习了SV和UVM知识,但对搭建完整的验证环境没有概念的朋友。

在上个上个文章中,已经搭建了一个只有driver的验证平台,涉及到了类的继承与派生、factory工厂机制、phase机制、objection机制。下面我们要对之前的平台进行进一步完善。

interface & virtual interface
在前面的代码中,driver的等待时钟事件@(posedge top_tb.clk)以及DUT中输入端口赋值(top_tb.data_i_valid <= 1'b1)都是使用的绝对路径。假如clk信号的层次从top_tb.clk变成了top_tb.clk_inst.clk,那么就需要对原来的代码进行大量修改。
避免使用绝对路径的方式就是interface。在SystemVerilog中使用interface来链接验证平台与DUT的端口,interface的定义如下:
定义了interface之后,在top_tb中例化DUT时,就可以直接使用:
在module中可以声明interface,但是如果要在driver中使用interface,因为driver是一个类,所以不能直接声明interface(语法报错),在类中使用的是virtual interface。
因为driver类定义了interface,所以我们还需要将my_driver类中的main_phase任务中的data和valid信号用interface中的信号,修改后的含有interface的driver.sv为:
现在my_drive中的绝对路径就消除了,提高了代码的复用性。但是目前还存在一个问题:如何将top_tb中的input_if和my_driver中的vif对应起来呢?
config_db机制
在top_tb里,我们生成激励是用driver类来生成,driver生成激励后送到driver中的virtual interface中的vif.data_if,如何把driver中的vif.data_if赋值给top_tb中的input_if.data_if呢?如果直接赋值:
会直接报错,因为driver是UVM通过run_test语句实例化了一个脱离top_tb层次结构的实例,建立了一个新的结构。对于这种脱离top_tb层次结构,又希望在top_tb中对其进行某些操作的实例,需要用到UVM的config_db机制。
config_db机制分为set和get两个操作。
在top_tb中执行set操作:
在my_driver中执行get操作:
这里引用了build_phase,与main_phase一样,是UVM内建的一个phase,当UVM启动后,会自动执行build_phase。build_phase在new函数之后,main_phase之前执行。
build_phase主要的作用就是通过config_db的set和get操作来做一些数据的配置和传递,以及实例化成员变量等。值得注意的是,super.build_phase这条语句是必须的,在父类uvm_build_phase 中执行了一些必要的操作,必须显式地调用并执行它。
build_phase和main_phase的区别
build_phase与main_phase的不同点在于,build_phase是一个function,而main_phase是一个task。我们知道task和function的最大区别就在于function是没有延时的,即不消耗仿真时间,在仿真开始的瞬间就执行了;而task类型的main_phase中则可以添加一些带有延时的操作,譬如@(posedge clk)、wait等操作。
在build_phase中出现的uvm_fatal宏,这个宏和uvm_info的功能是差不多的,都是打印一些信息,它们的区别在于:uvm_fatal在打印第二个参数所示的信息后,会直接调用verilog 的finish函数来终止仿真。uvm_fatal的出现,表示验证平台出现了重大问题而无法继续下去,必须停止仿真并做相应的检查,只要是uvm_fatal打印的信息,就一定是非常关键的。
config_db的set和get都有四个参数,在set/get之前,有一个#(virtual my_if),这里跟的是我们需要传输的数据类型,因为我们要传输的是一个接口类型,所以填virtual my_if,如果要传输一个整型则写config_db#(int)。
set和get的第一和第二个参数后面再说,先讲第三第四个参数。set的第二个参数是路径索引,索引到操作的目标所在的实例名称;set和get的第三个参数必须完全一致(可以为变量名);set的第四个参数表示要把哪个interface传递给my_driver;get的第四个参数表示要把接收到的interface传递给my_driver的哪个成员变量。
阶段代码总结
到此为止,我们已经写了四个模块,分别是:driver.sv、my_if.sv、top_tb.sv和dut.sv,代码分别为:
dut.sv
driver.sv
my_if.sv
top_tb.sv
下面我们在上面的基础上进行进一步的扩充和拓展。
添加transaction
接下来我们还要为我们的验证平台添加monitor、scoreboard、reference model等验证平台组件。这些组件之间的信息传递是基于transaction的,因此我们在添加组件之前,先要添加transaction。
transaction是一个抽象的概念。一般来说,物理协议中的数据交换都是以帧或者包为单位的,通常在一帧或者一个包中要定义好各项参数,一笔transactiin就是一个包。在不同的验证平台中会有不同的transaction。
transaction定义
在transaction定义中,有两点值得注意:一是my_transaction的基类是uvm_sequence_item。在UVM中,所有的transaction都要从uvm_sequence_item派生,只有从uvm_sequence_item派生的transaction才可以使用UVM中强大的sequence机制;二是transaciton在使用factory机制时是使用uvm_object_utils,my_transaction和my_driver是由区别的:在整个仿真期间,driver是一直存在的,transaction不同,它在仿真的某一时间产生,经过driver驱动,再经过reference model处理,最终由scoreboard比较完成后,其生命周期就结束了。一般来说,这种类都是派生自uvm_object或者uvm_object的派生类。UVM中具有这种特征的类都要使用uvm_object_utils宏来注册。
基于transaction的driver
完成transaction.sv的定义后,就可以在my_driver中实现基于transaction的驱动。
仿真结果:

在transaction中循环了8次drive_password_trans任务。
在drive_password_trans任务中,我们传输一次password
drive_password_trans任务功能描述:
这里因为data_q设置的是8bit,而password一次是64bit的,所以设置data_q为队列,需要入队8次才能把password完整的存下来,所以我们for循环循环8次,每次让8bit数据从队列的尾部进入,也即push_back。
之后等待三个时钟上升沿,然后把队列中的首部数据(pop_front)排出送给my_driver的interface “vif”,直到队列排空。

至此,我们就完成了添加transaction的driver设计,涉及到的知识有interface、virtual interface、config_db机制、transaction设计等内容。至此不要忘了,我们加入transaction的目的是因为monitor、scoreboard等验证组件之间的交互是通过transaction的,因此,接下来我们就要添加UVM的其他组件了。