UVM
Study about UVM
API
new(string name = “”, uvm_component parent = null)
这里的name是字符串类型,为什么这里面需要有name以及parent,这是因为是uvm_component的要求,这里的new继承自uvm_component
uvm_info(“my_driver”,”data is drived”,UVM_LOW)
这里的uvm_info功能类似于display不过功能更加强大。”my_driver” - 用于打印归类 “data is drived” - 是打印的信息, UVM_LOW是打印的冗余级别,有些信息非常关键可以设置为UVM_LOW,有些信息可有可无可以设置为UVM_HIGH,介于两者之间的就可以设置为UVM_MEDIUM,UVM默认只显示UVM_MEDIUM和UVM_LOW的信息
`uvm_fatal(“my_driver”, “virtual interface must be set for vif!!!”)
`uvm_fatal在uvm中行使着警告的作用,在触发uvm_fatal后会直接结束仿真
`uvm_component_utils(my_driver)
工厂机制的实现被集中在这个宏之中,这个宏能做的事情非常多,其中一件事情就是把my_driver登记在UVM内部的一张表中,所有派生自uvm_component及其派生类都应使用`uvm_component_utils注册
run_test(“my_driver”)
创建一个my_driver的实例,并调用其中main_phase
raise_objection(this)
开启仿真平台,raise_objection必须在第一个消耗仿真时间的语句之前
drop_objection(this)
关闭仿真平台,类似于$finish的作用
- uvm_config_db#(virtual my_if)::set(null,”uvm_test_top”,”vif”,input_if)
- uvm_config_db#(virtual my_if)::get(this,””,”vif”,vif)
uvm_config_db中set和get主要行使着类似寄信和收信的作用,其中virtual my_if是寄信的类型,null是绝对路径,“uvm_test_top”是相对路径,“vif”是寄信的名称,“input_if”是寄信的内容
- type_name::type_id::create(name,parent)
通过这个函数对于组件进行实例化,只有factory注册过的类才能用这种方式进行实例化,只有使用这种实例化的方法才能在后面使用factory机制中最为强大的重载功能
- uvm_tim_analysis_fifo #(my_transaction) agt_mdl_fifo
初始化transaction级的fifo,用于组件间通信
- uvm_analysis_port#(my_transaction) ap
创建写入端口用于写入数据
- uvm_blocking_get_port#(my_transaction)
创建接收端口用于接收数据
- uvm_object_utils_begin(my_transaction)与uvm_object_utils_end(my_transaction)
用于实现my_transaction的factory注册
- uvm_field_int
用于实现UVM的field_automation机制
- `uvm_do(m_trans)
其主要作用:1.创建一个my_transaction的实例m_trans;2.将其随机化;3.最终将其送给sequencer。
- drv.seq_item_port.connect(sqr.seq_item_export)
建立driver和sequencer的连接
- seq.start(i_agt.sqr)
在env中启动sequence,向sequencer发出请求
基础知识
objection机制
UVM中通过objection机制控制验证平台的关闭
field_automation 机制
通过一些UVM的宏可以自动实现print,compare,copy等函数
phase
main_phase
在其中进行一些主要的操作,消耗仿真时间,是一个task型的phase
build_phase
build_phase 是UVM中内建的一个phase,当UVM启动后,会自动执行build_phase。build_phase在new函数之后main_phase之前执行,在build_phase中主要通过config_db的get和set传递一些数据。一般在build_phase中会调用super.build_phase,这是因为其父类的build_phase执行了一些必要的操作这里必须要显示调用并执行他。build_phase与main_phase不同的是,他是一个函数型的phase不需要消耗仿真时间
build_phase在UVM中的执行顺序一般是从树根到树叶
connect_phase
connect_phase 与 build_phase类似,其执行的时间是在build_phase执行之后执行,用于组件端口的连接。
与build_phase不同的是,connect_phase的执行顺序是由树叶到树根
transaction
传统的testbench的操作一般都是信号级的,但是在uvm中信息的传递是基于transaction,即事务级。在uvm中所有transaction都是从uvm_sequence_item派生而来,这里没有用uvm_component_utils注册factory,而使用uvm_object_utils注册factory,因为其类的祖先是object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class my_transaction extends uvm_sequence_item;
rand bit [47:0] dmac;
rand bit [47:0] smac;
rand bit [15:0] ether_type;
rand byte pload[];
rand bit [31:0] crc;
constraint pload_cons(
pload.size >= 46;
pload.size <= 1500;
)
function bit[31:0] calc_crc();
return 32'h0;
endfunction
function void post_randomize();
crc = calc_crc;
endfunction
`uvm_object_utils(my_transaction)
function new(string name = "my_transaction");
super.new(name);
endfunction
uvm_env
为了方便对于各个组件进行实例化,故引入了env这个容器类,在其中通过type_name::type_id::create这种方式对于组件进行实例化(一般在build_phase中进行)。
agent
因为driver和monitor处理的是同一种协议,在同一套既定的规则下做着不同的事情,由于二者的这种相似性,UVM通常将二者封装在一起,成为一个agent,因此不同的agent就代表了不同的协议。
is_active是uvm_agent的变量,其默认值是UVM_ACTIVE代表需要实例化driver的,若不需要driver的情况下可以设置为UVM_PASSIVE。
一般可以通过uvm_config_db#(uvm_active_passive_enum)::set(this, "i_agt", "is_active", UVM_ACTIVE)
进行传递
reference model
reference model用于完成和dut一样的功能,reference model的输出被scoreboard获取用于与dut作比较。ref model 与 i_agt 的通信是值得注意的,在UVM中经常使用TLM(Transaction Level Model)来进行组件间通信,在UVM的transaction级别的通信中,数据发送的方式有很多种,其中一种是使用uvm_analysis_port。UVM中接受数据的方法也有很多种,其中一种是使用uvm_blocking_get_port。这也是一个参数化的类,其中传递的transaction的类型,在my_model进行声明,build_phase中进行初始化,main_phase中使用port.get进行数据传递。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 在monitor中声明此变量
uvm_analysis_port#(my_transaction) ap;
//2. 在build_phase阶段对于uvm_analysis_port进行实例化
function build_phase
ap = new("ap",ap);
endfunction
// 3. 在main_phase中对于数据进行收集与传递
task main_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将两个端口联系在一起,fifo的类型是uvm_tim_analysis_fifo,其本质是一个参数化的类,其中参数存储的是transaction类型。
1
2
uvm_tim_analysis_fifo #(my_transaction) agt_mdl_fifo;
agt_mdl_fifo = new("agt_mdl_fifo",this);
在connect_phase将两者相连
1
2
3
4
5
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
scoreboard
scoreboard的作用收集dut的数据和ref model的数据进行比较,一般通过fork建立起两个线程,一个线程获取ref model的数据并将其push到expect_queue中去,另一个线程从o_agt获取数据并将获取到的数据与expect_queue中的数据进行比较。
sequencer
一般transaction激励都是由sequencer产生并由driver驱动,如果说sequencer是手枪,那么sequence就是弹夹用来为sequencer提供子弹。sequence与sequencer的区别是sequencer是一个uvm_component,而sequence是一个uvm_object,与transaction一样,它也有生命周期,不过它的生命周期略长于transaction,需要等所有transaction发射完毕,sequence的生命才会结束。
一个典型的sequence如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class my_sequence extends uvm_sequence #(my_transaction);
my_transaction my_trans;
function new(string name = "my sequence")
super.new(name);
endfunction
virtual task body();
repeat(10)begin
`uvm_do(my_trans);
end
#1000
endtask
`uvm_object_utils(my_sequence);
一个sequence向sequencer发送transaction前,需要向sequencer发送一个请求,sequencer把请求放在一个仲裁队列中,他需要完成两件事,1.检测是否有sequence发起transaction请求 2.检测driver是否有申请transaction。
随之而来的问题是driver如何向sequencer发起请求,在uvm_driver中有成员变量seq_item_port,而在uvm_sequencer中有成员变量seq_item_export,这两者之间可以建立一个“通道”。
接下来的问题是sequence如何向sequencer送出transaction呢?一般只需要在env的main_phase启动sequence即可,当sequence里的子弹打完即可关闭仿真平台
1
2
3
4
5
6
7
task my_env::main_phase(uvm_phase phase);
my_sequence seq;
phase.raise_objection(this);
seq = my_sequence::type_id::create("seq");
seq.start(i_agt.sqr);
phase.dtop_objection(this);
endtask
前面是在env中手动启动sequense,但是在实际应用中更多还是通过default_sequence
的方式启动sequence,具体方式如下,其中i_agt.sqr.main_phase
可以确定在哪个phase启动sequence,第三个参数和第四个参数为UVM默认。
1
2
3
4
5
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(uvm_object_wrapper)::set(this,"i_agt.sqr.main_phase","default_sequence",my_sequence::type_id::get())
前文中启动sequence时需要raise objection
和drop objection
,在default_sequence
中通过seq.starting_phase = phase
和 seq.start(this)
实现,然后在sequence的main_phase中通过starting_phase.raise_objection(this)
和starting_phase.drop_objection(this)
使用objection机制
base_test
通常来说树根是一个基于uvm_test派生的类,其主要的作用是实例化my_env,并设置default_sequence以及整个验证平台的超时退出时间,并在report_phase阶段打印UVM_ERROR的信息,其在main_phase结束之后执行,对于不同的测试用例可以通过run_test(case0)
与run_test(case1)
来实现,也可以使用UVM_TEST_NAME通过命令行传入。下图是UVM的启动流程,
一些细节
UVM打印信息的设置
set_report_verbosity_level()
设置打印信息冗余度阈值以及get_report_verbosity_level()得到冗余度阈值,若涉及层次调用则需要在connect_phase及之后phase进行调用,若不涉及层次调用则可以在connect_phase之前进行调用。set_report_verbosity_level_hier()
则是设置在某个ID之下的所有子类的冗余度阈值set_report_id_verbosity()
对于不同ID的冗余度阈值进行设置,ID是第一个UVM_INFO的第一个参数+UVM_VERBOSITY=UVM_HIGH
可以通过命令行对于整个环境的冗余度进行设置set_report_severity_override(UVM_WARNING, UVM_ERROR)
对于信息严重性进行重载,将driver中的所有UVM_WARNING重载后UVM_ERRORset_report_max_quit_count(5)
ERROR达到某一数量结束仿真,不必等到所有objection被撤销,get_max_quit_count()
可以查询当前退出阈值set_report_severity_action(UVM_WARNING, UVM_DISPLAY|UVM_COUNT)
可以将警告加入计数,第二个参数中可以使用UVM_STOP
来加入断点调试
寄信的优先级
在UVM中通常使用uvm_config_db::set
以及uvm_config_db::get
来进行跨层次组件的变量传递,若有多个组件向同一个组件发送邮件,其优先级是越接近根节点的优先级越高。若优先级相同则比较寄信的时间,以最后一个为准。
uvm_config_db对于通配符是支持的,所以对于下面两个代码可以合并为一句,但是不建议使用通配符。
1
2
3
4
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv","vif",input_if);
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.mon","vif",input_if);
uvm_config_db#(virtual my_if)::set(null, "*.i_agt.*","vif",input_if);
由于uvm_config_db的第二个参数是字符串类型,即使路径设置错误也不会报错,只会不调用,可以用check_config_usage()
检测此函数调用时,有哪些参数设置过,哪些参数没有设置过,config_db
的set和get语句一般在build_phase中实现,所以check_config_usage()
可以放在connect_phase阶段
对于类而言要先定义后实例化,这两部分缺一不可
1
2
3
4
5
class A;
endclass
A a_inst // 定义
a_inst = new() // 实例化