IC验证“UVM验证平台加入objection机制和virtual interface机制“(七)

在上一节中,**《IC验证"UVM验证平台加入factory机制"(六)》**虽然输出了“main_phase is called”,但是“data is drived”并没有输出。而main_phase是一个完整的任务,没有理由 只执行第一句,而后面的代码不执行。看上去似乎main_phase在执行的过程中被外力“杀死”了,事实上也确实如此。

UVM中通过objection机制来控制验证平台的关闭。细心的读者可能发现,在上节的例子中,并没有如**《IC验证"一个简单的UVM验证平台"是如何搭建的(五)》**所示显式地调用 finish语句来结束仿真。但是在运行上节例子时,仿真平台确实关闭了。在每个phase中,UVM会检查是否有objection被提起 (raise_objection),如果有,那么等待这个objection被撤销(drop_objection)后停止仿真;如果没有,则马上结束当前phase。

加入了objection机制的driver如下所示:

 文件:src/ch2/section2.2/2.2.3/my_driver.sv 
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); 
5225 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中第一个消耗仿真时间 [1]的语句之前。如$display语句是不消耗仿真时间的,这些语句可 以放在raise_objection之前,但是类似@(posedge top.clk)等语句是要消耗仿真时间的。按照如下的方式使用raise_objection是无法 起到作用的:

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); 
53top_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 

加入virtual interface

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

`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的定义 比较简单:

文件: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时,就可以直接使用:

文件: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); 
5619
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传递给 它:

class my_driver extends uvm_driver; 
my_if drv_if; 
…
endclass 

读者可以试一下,这样的使用方式是会报语法错误的,因为my_driver是一个类,在类中不能使用上述方式声明一个 interface,只有在类似top_tb这样的模块(module)中才可以。在类中使用的是virtual interface:

文件:src/ch2/section2.2/2.2.4/my_driver.sv 
3 class my_driver extends uvm_driver; 
574
5 virtual my_if vif; 

在声明了vif后,就可以在main_phase中使用如下方式驱动其中的信号:

文件: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 

可以清楚看到,代码中的绝对路径已经消除了,大大提高了代码的可移植性和可重用性。
58剩下的最后一个问题就是,如何把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操作:

文件:src/ch2/section2.2/2.2.4/top_tb.sv 
44 initial begin 
45 uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if); 
46 end 

在my_driver中,执行get操作:

文件: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); 
5916 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的实例。读者可以通过把代码
$display("the full name of current component is: %s", get_full_name());中的语句插入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类型的数据,那么可以使用如下方式:

initial begin 
uvm_config_db#(int)::set(null, "uvm_test_top", "var", 100); 
end 

而在my_driver中应该使用如下方式:

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); 
61if(!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中这么做:

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中这么做:

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)) 
62`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 
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是使用UVM搭建IC仿真验证平台的一般步骤: 1. 制定验证计划,定义验证目标和测试需求。 2. 根据设计规范或设计文档,编写UVM环境的基础类和基础组件。包括顶层Testbench组件、驱动器(Driver)组件、监控器(Monitor)组件、功能覆盖率组件、约束随机测试(Constrained Random Test)组件、测试用例等。 3. 根据测试需求,编写测试用例,包括测试场景、测试向量和期望输出。 4. 编写顶层Testbench组件,包括时钟、复位、生成测试向量和监控输出等。 5. 编写驱动器组件,将测试向量传输到DUT(Design Under Test)。 6. 编写监控器组件,监控DUT的输出,包括状态信号、数据信号和错误信号等。 7. 编写功能覆盖率组件,对DUT的各个模块进行覆盖率统计,包括语句覆盖率、分支覆盖率、条件覆盖率等。 8. 编写约束随机测试组件,对DUT进行随机测试,包括随机生成测试向量、约束随机测试等。 9. 集成所有组件,构建完整的UVM测试环境。 10. 运行仿真,生成仿真波形,分析仿真结果,检查DUT是否符合设计规范或设计文档的要求。 请注意,以上步骤仅为一般性的流程,实际情况可能因设计复杂度、验证需求和仿真工具等因素而有所不同。同时,UVM验证方法学需要学习一定的知识和技能,如果您对UVM方法学不熟悉,建议先学习相关的教程和资料。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值