UVM使用经验

0. 介绍

这篇积累一下UVM使用过程中遇到的一些问题、bug和经验。

1. 经验

1.1 phase相关

======================================================================================

1.每个phase函数或者任务都要加上super. * _phase(phase)*

之前再看《UVM实战》的时候以为只有build_phase需要使用super,但是今天在使用callback机制的时候,connect_phase中没有用super.,出现了一些怪异的错误。

好吧,我刚才把connect_phase注释了,跑一下发现也没错,醉了,以后留意一下吧。

======================================================================================

2. 如果两个component有端口连接,需要注意connect_phase,是否要seper.connect_phase(phase)

今天遇到一个问题,我们知道uvm_driver中有个seq_item_port,在uvm_sequencer中有一个seq_item_export,今天我在写的时候,出现了一个错误:

1
2
UVM_ERROR @ 0: uvm_test_top.env.i_agt.sqr.rsp_export [Connection Error] connection count of 0 does not meet required minimum of 1
UVM_FATAL @ 0: reporter [BUILDERR] stopping due to build errors

​ 查了一下,好像是跟connect_phase有关,也不太清楚,我就看代码,发现我的sequencer、driver、agent,他们的connect_phase中都没有用super.connect_phase。因为看张强的那本书说,用不用都一样,今天我就省略了。然后我就把他们的super.connect_phase都加上了,结果正确了。

​ 然后我又逐个注释掉,看到底是哪一个在起作用,结果发现只有在我重载了sequencer的connect_phase,但是在其中没有写super.connect_phase,才会出错,其他任何情况都不会出现这个错误。最简单的避免错误的方法就是如果用不到这个phase,就不要写出来,不写就没错,如果写了那么还是把super给加上吧。

​ 然后我又用自定义port、export、imp来测试了一下,我不用seq_item_port和seq_item_export,发现怎么写都没有错,怪哉啊。

所以一个不严谨的结论就是,sequencer的connect_phase要写super.connect_phase(phase),否则就别写connect_phase函数。

======================================================================================

​ 2019–08-29

​ 可配置的验证平台中,会通过uvm_config_db#(..)::set来设置一些控制参数,来看重验证平台组件的生成,比如uvm_agent中的is_active。

​ 比如现在是在给agent设置is_active,那么我们希望agent能知道我们重新设置了这个变量,为了实现这个在UG中强烈建议加上super.build_phase(uvm UG 1.1 P44),而且出现了好多次。当然用这个也是有前提的,这个算是config_db自动获取,我们需要:

1. 将agent用factory宏注册。
 2. 将agent中需要 自动获取的变量用filed automation。
 3. config_db中set函数的第三个参数和agent中的变量名是一样的。

======================================================================================

​ 2019–08-29

我们创建component要在build_phase中,比如我们在base_test中例化了env,并在base_test::build_phase中创建了它,那么在特定的case(继承自base_test)中,我们不必再例化env和创建它,只需要调用super.build_phase就可以了。

======================================================================================

​ 2019–08-29

如果想要再case中向sequence中传递sequencer指针,那么uvm_config_db::set要写在build_phase之后的connect_phase中。

详细见这篇博客UVM——Fun with UVM Sequences - Coding and Debugging中的有用-CONTROLLING OTHER SEQUENCES一节

======================================================================================

1.2 callback

======================================================================================

1.在使用回调的时候,自己定义的回调对象不能跟sequence在同一个phase。可以是sequence在build phase上启动,而callback object在connect phase中创建。

======================================================================================

1.3 运行仿真环境

======================================================================================

我们知道,UVM中的factory机制可以实现通过字符串创建对象,从书上知道,在VCS仿真的时候我们可以通过用+UVM_TESTNAME选项来指定不同的case。

如果我们的sequence都编译好了,也就是说已经执行了vcs -sverilog ….这条脚本,那么我们可以通过

1
2
3
./simv +UVM_TESTNAME=case1
./simv +UVM_TESTNAME=case2
./simv +UVM_TESTNAME=case3

来执行不同的case。

但是如果我们的sequence被修改了,那么我们需要用vcs脚本重新编译一下,再用simv仿真,这样改变之后的效果才能呈现出来。

======================================================================================

1.4 config_db使用

======================================================================================

在top module中,要通过config_db来设置虚接口,然后运行run_test()例化test case,在这里有个顺序问题,需要先config_db,再run_test()

1
2
3
4
initial begin
uvm_config_db#(virtual wd_bfm)::set(null,"*","bfm",bfm);
run_test();
end

要不然报错:driver得不到接口

1
UVM_FATAL comp/driver.sv(15) @ 0: uvm_test_top.env.i_agt.drv [driver] Failed get bfm!!!

可以猜测,uvm_config_db机制可能只是向一个表中记录设置参数的路径信息,当你创建对象的时候,你先要访问这个表,看有没有给对象设置了信息,如果先创建了对象,在对象的build_phase中get参数,就会发生get不到的情况,所以需要先set,然后再创建对象。

但是如果是在组件中使用的话,先创建对象还是先config_db都可以,比如:

在environment中创建i_agt,需要设置is_active变量。下面这两种方法都能run。

1
2
3
4
5
function void enviroment::build_phase(uvm_phase phase);
i_agt=agent::type_id::create("i_agt",this);
uvm_config_db#(uvm_active_passive_enum)::set(this,"i_agt","is_active",UVM_ACTIVE);
// i_agt.is_active = UVM_ACTIVE;
endfunction
1
2
3
4
5
function void enviroment::build_phase(uvm_phase phase);
uvm_config_db#(uvm_active_passive_enum)::set(this,"i_agt","is_active",UVM_ACTIVE);
i_agt=agent::type_id::create("i_agt",this);
// i_agt.is_active = UVM_ACTIVE;
endfunction

======================================================================================

config_db如果配置的对象是个参数类,那么可以将类的参数写出来,也可以不写出来,都能配置对,只要你set和get同步就行,别一个写了参数,一个没写,比如我要传递 sequencer#(transaction)到virtual sequence中。

1
uvm_config_db(sequencer#(transaction))::set(null,"*","sqr",env.sqr);

get的时候也一样,要加参数

1
uvm_config_db#(sequencer#(transaction)):get(null,get_full_name(),"sqr",get_sqr);

======================================================================================

上面介绍的传递sequencer,有一点要注意,比如我实在test case中set,那么set函数一定不要写在build_phase中,可以写在connect_phase,否则传递失败,但编译器却不会报错,不过但你要用sequencer来启动sequence的时候,又会告诉你说sequencer没有实例化。

1
2
3
4
function void case::connect_phase(uvm_phase phase);
super.connect_phase(phase);
uvm_config_db#(sequencer#(transaction)):get(null,get_full_name(),"sqr",get_sqr);
endfunction

======================================================================================

在路径索引中使用的路径,比如一个i_agt,这个其实是i_agt对象在创建时候的名字,比如

1
i_agt = new("i_agt",this);

名字是new中引号内的字符串,而不是开头的i_agt,当然,这里他们是相同的。

我们可以写成:

1
uvm_config_db#(transaction)::set(this,{i_agt.get_name(),".","mon"},"tr",tr);

1.5 任务和函数

======================================================================================

任务中的变量一定要先定义,然后才能进行接下来的操作。

在phase方法中调用super之前一定要先把变量都声明好了。

1
2
3
4
5
task write();
`uvm_info(get_type_name(),"write",UVM_LOW);
transaction tr;
`uvm_do(tr);
endtask

上面那种写法会说 transaction tr;语句出错,应该把变量的定义写在开头。

1
2
3
4
5
task write();
transaction tr;
`uvm_info(get_type_name(),"write",UVM_LOW);
`uvm_do(tr);
endtask

======================================================================================

1.6 程序块

======================================================================================

在写if else语句的时候,如果写成下面这样会报错,

1
2
3
4
5
6
7
8
if(req==null)
`uvm_info(get_type_name(),"idle transaction",UVM_LOW);
else begin
`uvm_info(get_type_name(),"driver get one tr",UVM_LOW);
rsp=new("rsp");
rsp.set_id_info(req);
seq_item_port.item_done(rsp);
end

if也要加上 begin end

1
2
3
4
5
6
7
8
9
if(req==null) begin
`uvm_info(get_type_name(),"idle transaction",UVM_LOW);
end
else begin
`uvm_info(get_type_name(),"driver get one tr",UVM_LOW);
rsp=new("rsp");
rsp.set_id_info(req);
seq_item_port.item_done(rsp);
end

但是如果单独写if没有else,那么不需要加begin end(if后只有一条语句的时候)

======================================================================================

1.7 端口 port export imp analysis fifo

======================================================================================

看UVM UG里面,端口的new写在build phase 里。

======================================================================================

1.8 tb上的组件

======================================================================================

UG中建议不要再new中创建组件(UG1.1 P62),它说在面向对象的编程中new函数在重载的时候有一些限制,所以,应该用build_phase来代替,在其中创建验证平台组件。

比如说,我们在base_test中例化了env,并在base_test::build_phase中创建了它,那么在特定的case(继承自base_test)中,我们不必再例化env和创建它,只需要调用super.build_phase就可以了。****

======================================================================================

1.9 接口

虚接口将硬件接口和SV仿真环境链接在一起,在顶层通过uvm_config_db机制传递。

之前都是用通配符*来传递,但是如果要传递多个接口那么可能会出现比必要的问题,在UG上看到一个写法:将虚接口传递给agent,然后在agent中通过uvm_config_db 得到虚接口,如果driver或者monitor中需要这个虚接口,可以通过如下方式:

1
2
3
4
5
6
7
8
9
10
11
// driver.sv
apb_vif sigs;
apb_agent agent;
if ($cast(agent, get_parent()) && agent != null) begin // 从agent上拿到接口
sigs = agent.vif;
end
else begin
// 或者agent没有配置虚接口,那么在顶层直接设置到driver中
if (!uvm_config_db#(apb_vif)::get(this, "", "vif", sigs)) begin
`uvm_fatal("APB/DRV/NOVIF", "No virtual interface specified for this driver instance")
end

1.10 一些函数

$root,在SV1800中的介绍:

1
2
Top-level modules are modules that are included in the SystemVerilog source text, but do not appear in any module instantiation statement.
The name $root is used to unambiguously refer to a top-level instance or to an instance path starting from the root of the instantiation tree. $root is the root of the instantiation tree.

$root是在top module上面的实例,算是实例树种的根,类似于UVM中的uvm_top这个实例。

我们可以这样访问:$root.top.inst_A。这样就可以访问到top中的实例inst_A。

2. 问题

2.1 仿真时候卡主了

======================================================================================

运行 ./simv +UVM_TESTNAME=case0 -l sim.log 卡在下面的界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

VCD+ Writer I-2014.03 Copyright (c) 1991-2014 by Synopsys Inc.

应该是忘了在top module中运行run_test

1
2
3
4
initial begin
uvm_config_db#(virtual wd_bfm)::set(null,"*","bfm",bfm);
run_test();
end

======================================================================================

2.2 模块定义

======================================================================================

uvm_driver uvm_sequence uvm_sequencer是参数化的类,可以加上要传递什么类型的transaction,但是uvm_monitor不可以。

======================================================================================

有的时候说找不到class定义,

这说明`include的顺序不对,先用到的要先include,但也可以通过typedef来解决,在那些报错的类所在的文件夹中:

1
typedef class classname;

表明这个类在下文是有定义的。

2.3 驱动接口编写

======================================================================================

driver中在何时驱动接口,接口信号才能在时钟被采样到

这个好像是跟SV的仿真原理和EDA仿真工具都有关系。

我今天的错误情况:

​ TB驱动接口信号在时钟沿改变,DUT采样不到;

改变后的正确情况:

​ TB驱动接口信号提前半个周期,在下降沿改变,DUT采样到。

所以应该在时钟下降沿使数据有效,等待DUT执行结束后,在上升沿使数据无效。

======================================================================================

2.4 `ifdef `endif

Error-[IWNMEE] ifdef or ifndef with no matching
pkg.sv, 3
ifdef or ifndef has no matching else or elsif or `endif.
Check that the directives are balanced.


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!