UVM学习笔记
1. 常见的uvm_component
- uvm_driver: 所有的driver都要派生自uvm_driver。
- uvm_monitor: 所有的monitor都要派生自uvm_monitor。
- uvm_sequencer: 所有的sequencer都要派生自uvm_sequencer。
- uvm_scoreboard: 一般的scoreboard都要派生自uvm_scoreboard。
- reference model: reference model直接派生自uvm_component。
- uvm_agent: 所有的env要派生自uvm_agent。
- uvm_env: 所有的env要派生自uvm_env。
- uvm_test: 所有的case要派生自uvm_test。
2. 常见的uvm_object
- uvm_sequence_item: 所有的自定义的transaction要从uvm_sequence_item派生。
- uvm_sequence: 所有的sequence要从uvm_sequence派生。
- config: 所有的config一般直接从uvm_object派生。
- uvm_reg_item: 派生自uvm_sequence_item。
- uvm_reg_map,uvm_mem,uvm_reg_field,uvm_reg,uvm_reg_file,uvm_reg_block等与寄存器有关的众多类都是派生自uvm_object。
- uvm_phase: 派生自uvm_object,其用处主要是控制uvm_component的行为,使得uvm_component平滑的在各个不同的phase之间一次运转。
3. override功能
假设自己定义一个my_driver,在跑80%的case的时候,driver是足够的,但是剩余的20%的case中,我们需要对my_driver的行为行为作出某些改动,这个时候可以用到override功能。使用override功能的第一步是要先从my_driver派生出一个类,把这个类的行为定义好:
class new_driver extends my_driver;
...
`uvm_component_utils(new_driver)
endclass
之后, 再具体的case的build_phase中,调用override相关函数:
class case_x extends base_test;
function void build_phase(uvm_phase phase);
...
set_type_override_by_type(my_driver::get_type(),new_driver::get_type());
endfunction
endclass
经过上述过程之后,这么跑的case_x的时候,系统中运行的my_driver就是new_driver类型的,其行为是new_driver的行为。不过这有个前提,那就是my_driver在agent中实例化的时候,要使用factory的方式实例化:
class my_agent extnds uvm_agent;
my_driver drv;
function void build_phase(uvm_phase phase);
...
drv = my_driver::type_id::create("drv",this);
endfunction
endclass
假如不是使用上面的写法,而是使用drv=new(“drv”,this)的写法进行实例化,那么override功能是不能实现的。
4. phase
4.1 phase的分类
build_phase |
---|
connect_phase |
end_ofelaboration_phase |
run_phase |
extract_phase |
check_phase |
report_phase |
final_phase |
uvm中的phase,按照其是否消耗仿真时间的特性,可以分为两大类,一类是function_phase,其实现是通过函数(function)来实现的;另外一类是run_phase等,它们是消耗仿真时间的,其实现是通过任务(task)来实现的。上述表格中run_phase是task phase,其他的都是function_phase。
注:所谓的仿真时间就是$time函数等到的时间,而运行时间是CPU的时间。直观上,我们觉得一个程序运行的快,就是CPU时间花费的少,即运行时间少。
4.2 phase的执行顺序
- build_phase是自上而下执行的。build_phase是做实例化工作的,driver和monitor都是agent的成员变量,所以它们的实例化都是在agent的build_phase中执行。如果在agent的build_phase之前执行driver的build_phase,此时driver还根本没有实例化,所以调用driver.build_phase只会引发错误。
- 在build_phase的实例化工作,仅仅指的是uvm_component及派生类的实例化,如果在其他phase实例化一个uvm_component的话,那么系统就会报错。如果是uvm_object的实例化,则可以在任何phase完成,当然也包括build_phase。
- 除了build_phase之外,所有的不耗仿真的时间的phase(即function phase)都是自下而上执行的。如connect_phase ,先执行driver和monitor的connect_phase,再执行agent的connect_phase。
- run_phase也是自伤而下的执行,更准确的说是自上而下的启动,同时在运行。
- run_phase分成12个小的phase:
pre_reset_phase |
---|
reset_phase |
post_reset_phase |
pre_configure_phase |
configure_phase |
post_configure_phase |
pre_main_phase |
main_phase |
post_main_phase |
pre_shutdown_phase |
shutdown_phase |
post_shutdown_phase |
这个12个小phase与run_phase完全相同,即自下而上的启动,同事运行
phase的跳转仅限于这12个动态phase之间相互跳转。
run_phase和12个phase之间是并列关系。
main_phase可以启动run_phase,但是run_phase不可以启动main_phase。
5. objection控制验证平台关闭
task main_phase(uvm_phase phase);
super.main_phase(phase);
phase.phase_done.set_drain_time(this,1000);
phase.raise_objection(this);
......
phase.drop_objection(this);
endtask
验证过程中,dut的输出相对于输出总会有一定的延迟,如果立刻停止UVM验证平台,可能一部分dut输出还没有结束,所以需要进行延迟结束停止平台。set_drain_time,用来定义在phase结束后延时大小。当UVM在某个task phase中检测到所有的objection被撤销后,接下来会检查有没有设置drain_time。如果没有设置,则马上进入下一个phase,否则延迟drain_time后再进入下一个phase。一个phase对应一个drain_time,并不是所有的phase共享一个drain_time。在没有设置的情况下,drain_time默认值是0。
6. transaction
定义transaction时,用如下方式以及field_automation机制来实现:
class mac_transaction extends uvm_sequence_item;
rand bit[47:0] dmac;
rand bit[47:0] smac;
rand bit[15:0] eth_type;
rand byte pload[];
rand bit[31:0] crc;
`uvm_object_utils_begin(mac_transaction)
`uvm_field_int(dmac,UVM_ALL_ON)
`uvm_field_int(smac,UVM_ALL_ON)
`uvm_field_int(eth_type,UVM_ALL_ON)
`uvm_field_array_int(pload,UVM_ALL_ON)
`uvm_field_int(crc,UVM_ALL_ON)
endclass
uvm_field_*系列宏共有如下几种:
name | description |
---|---|
uvm_field_array_A | 表示动态数组,A表示动态数组中存放的内容类型 |
uvm_field_sarray_A | 表示静态数组,A表述此静态数组中存放的内容的类型 |
uvm_field_queue_A | 表示队列,A表示此队列存放的内容的类型 |
uvm_field_aa_A_B | 表示联合数组,其中B表示联合数组的索引类型,A表示联合数组中存放的内容的类型 |
7. sequence
7.1 sequence定义如下:
class my_sequence extends uvm_sequence #(mac_transaction);
mac_transaction m_trans;
extern function new(string name ="my_sequence");
virtual task body();
repeat(10) begin
`uvm_do(m_trans);
end
#100;
endtask
`uvm_object_utils(my_sequence)
endclass
function my_sequence::new(string name="my_sequence");
super.new(name);
endfunction
启动一个sequence:
my_sequence my_seq;
my_seq = my_sequence::type_id::create("my_seq");
my_seq.start(sequencer);
要启动一个sequence,第一步就是把这个sequence实例化,第二步就是调用sequence的start任务,调用时传入一个sequencer参数。
当sequence启动起来之后,这个sequence中的body就会自动运行。
7.2 uvm_do宏
一个transaction的产生:
task body();
mac_transaction tr;
tr = mac_transaction::type_id::create("tr"); \\第一步实例化
start_item(tr); //第二步调用start_item
assert(tr.randomize()); //第三步执行randomize过程
finish_item(tr); //调用finish_item
endtask
上述四步,由于是牵扯到sequencer和driver的通信,因此这里有一个主动和被动的过程。当driver中使用seq_item_port.get_next_item主动请求一个item(transaction)时,sequencer才会要求sequence产生一个item(transaction);产生完成后,sequence就要等待driver把item(transaction)取走,这需要driver显式调用seq_item_port.item_done()。当此函数被调用后,finish_item才会返回,一个transaction的产生真正的完成。
上述四步中,只有第三步才会有一些差异,而另外三步则永远是固定不变的。可以使用uvm_do系列宏来简化transaction的过程。
//uvm_do的使用
task body();
mac_transaction tr;
`uvm_do(tr)
endtask
//uvm_do_with的使用
task body();
mac_transaction tr;
`uvm_do_with(tr,{tr.crc_err == 1;})
endtask
sequence可以作为uvm_do宏的参数
class crc_seq extends uvm_sequence#(mac_transaction);
......
`uvm_object_utils(crc_seq)
endclass
class logn_seq extends uvm_sequence#(mac_transaction);
......
`uvm_object_utils(long_seq)
endclass
class new_seq extends uvm_sequence#(mac_transaction);
task body();
crc_seq cseq;
long_seq lseq;
`uvm_do(cseq)
`uvm_do(lseq)
endtask
`uvm_object_utils(new_seq)
endclass
7.3 virutal sequence
实现sequence之间同步的最好的方式就是使用virtual sequence,本身并不发送transaction,只是控制其他sequence,起到统一调度的作用。
首先需要一个virtualsequencer,里面包含指向其他实际sequencer的指针。
class vsequencer extends uvm_sequencer;
cpu_sequencer cpu_sqr;
mac_sequencer man_sqr;
`uvm_component_utils(vsequencer)
endclass
在test中,例化vsqr,并把相应的sequencer赋值给vsqr中的指针。
class base_test extends uvm_test;
env env_inst;
vsequencer vsqr;
function build_phase(uvm_phase phase);
...
vsqr = vsequencer::type_id::create("vsqr",this);
...
endfunction
function connect_phase(uvm_phase phase);
...
vsqr.cpu_sqr = env_inst.cpu_agent.cpu_sqr;
vsqr.mac_sqr = env_inst.mac_agent.mac_sqr;
...
endfunction
endclass
在virtual sequence里面使用uvm_do宏来发送transaction:
class vseq extends uvm_sequence;
task body();
cpu_seq cseq;
mac_seq mseq;
`uvm_do_on(cseq,p_sequencer.cpu_sqr)
`uvm_do_on(mseq,p_sequencer.mac_sqr)
`uvm_object_utils(vseq)
`uvm_declare_p_sequencer(vsequencer)
endtask
endclass
这里应该使用uvm_do_on宏来指定sequencer,而不能用uvm_do或者uvm_do_with宏。
在vseq的定义中,还出现了p_sequencer的变量,它是指向sequencer的指针,这个sequencer就是启动这个vseq的sequencer。要使用p_sequencer这个变量,那么就要使用宏显式的声明:
uvm_declare_p_sequencer(vsequencer)
8. config机制
8.1 config基本机制
config机制在UVM平台中都是成对出现的,例如在某个case的build_phase中如下设置:
uvm_confgi_db#(int)::set(this,"env.agent.driver","pre_num_max",100);
那么在driver的build_phase中这么做:
uvm_config_db#(int)::get(this,"pre_num_max",pre_num_max);
uvm_config_db中的set和get都是静态函数,所以可以用冒号的形式调用。
set | get |
---|---|
第一个参数说明是哪个component对pre_num_max进行了设置,一般使用填写this | 第一个参数一般是this即可 |
第二个参数表示从调用uvm_config_db::set的地方看下去,要设置的变量所在的component的路径 | 第二个参数填写空的字符串 |
第三个参数表示一个记号,用以说明这个值是传给driver中的哪个变量的 | 第三个参数就是set中的第三个参数 |
第四个参数则是要设置的变量 | 第四个参数则是要设置的变量 |
注:必须保持set和get中第三个参数一致 |
8.2 省略get的config
在一些特定的情况下,get是可以省略的:
class mac_driver extends uvm_driver#(mac_transaction);
...
int pre_num;
int pre_num_max;
int pre_num_min;
`uvm_component_utils_begin(mac_driver)
`uvm_field_int(pre_num_min, UVM_ALL_ON)
`uvm_field_int(pre_num_max, UVM_ALL_ON)
`uvm_component_utils_end
function void build_phase(uvm_phase phase);
super.build_phase(uvm_phase phase);
//uvm_config_db#(int)::get(this,"","pre_num_max",pre_num_max);
//uvm_config_db#(int)::get(this,"","pre_num_min",pre_num_min);
endfunction
endclass
只要在把mac_driver注册到factory时,顺便使用field_automaction机制把get的变量注册,那么当UVM执行到driver的super。build_phase(phase)这句话时,就会自动执行那两get的语句。这是相当便利的。在前面介绍field_automation的类(如transaction)和uvm_component的类(如uvm_driver)中,是都可以使用field_automation机制的。
8.3 多重set
- UVM规定层次越高,那么它set的优先级越高。在整个UVM树中,uvm_test_top位置是高于env的,所以uvm_test_top中的set优先级高。因为层次越高,越接近用户。
- 当处于同一层次时,则是时间优先。
8.4 聚合config变量
在验证过程,需要set的变量太多,可以将这些变量放到一个专门的类里面。
class iconfig extends uvm_object;
rand int var1;
...
rand int var1000;
constraint default_cons{
var1 = 7;
...
var1000 = 999;
}
`uvm_object_utils_begin(iconfig)
`uvm_field_int(var1,UVM_ALL_ON)
...
`uvm_field_int(var1000,UVM_ALL_ON)
`uvm_object_utils_end
endclass
经过上述定义之后,可以在base_test中这样写:
class base_test extends uvm_test;
iconfig cfg;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cfg = iconfig::type_id::create("cfg");
uvm_config_db#(iconfig)::set(this,"env.agent.driver","cfg",cfg);
uvm_confgi_db#(iconfig)::set(this,"env.agent.monitor","cfg",cfg);
...
endfunction
endclass
实时改变config值
有时候,可能当DUT运转到某一时刻时,需要改变验证平台的某些配置参数,这种情况可以通过virtual sequence的方式实现。
class vsequencer extends uvm_sequencer;
iconfig cfg;
...
enclass
class base_test extends uvm_test;
iconfig cfg;
vsequencer vsqr;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cfg = iconfgi::type_id::create("cfg");
vsqr = vsequencer::type_id::create("vsqr",this);
vsqr.cfg = this.cfg;
...
endfunction
endclass
class vseq extends uvm_sequence;
`uvm_object_utils(vseq)
`uvm_declare_p_sequencer(vsequencer)
task body();
...//send some transaction
p_sequencer.cfg.pre_num_max = 99;
...//send other transaction
endtask
enclass
通过这种方法。可以在sequence中实时的改变验证平台的配置参数。
9. UVM中的各种port
9.1 TLM级别通信
- TLM:Transaction Level Modeling的缩写
- TLM级别的通信中的几个常用术语:
- put操作:发起者A把一个transaction发送B。A称为“发起者”(PORT),B称为“目标”(EXPORT)。数据流是A->B。
- get操作:A向B索取一个transaction。A依然是“发起者”,B依然是“目标”
- PORT和EXPORT体现的是控制流而不是数据流。无论get还是put,其发起者都是PORT端口,而不是EXPORT。作为一个EXPORT来说,只能被动的接收PORT的命令。put和get操作只是数据流不一样。
- UVM中常见的port
- PORT
uvm_blocking_put_port#(T);
uvm_noblocking_put_port#(T);
uvm_put_port#(T);
uvm_blocking_get_port#(T);
uvm_noblocking_get_port#(T);
uvm_get_port#(T);
uvm_blocking_transport_port#(REQ,ESP);
uvm_noblocking_transport_port#(REQ,REP);
uvm_transport_port#(REQ,RSP);
前6个定义中的参数就是这个PORT中的数据流类型,而后3个定义中参数则表示transport操作(request-response操作)中发起请求时传输的数据类型和返回的数据类型。- EXPORT
uvm_blocking_put_export#(T);
uvm_noblocking_put_export#(T);
uvm_put_export#(T);
uvm_blocking_get_export#(T);
uvm_noblocking_get_export#(T);
uvm_get_export#(T);
uvm_blocking_transport_export#(REQ,ESP);
uvm_noblocking_transport_export#(REQ,REP);
uvm_transport_export#(REQ,RSP);
前6个定义中的参数就是这个EXPORT中的数据流类型,而后3个定义中参数则表示transport操作(request-response操作)中发起请求时传输的数据类型和返回的数据类型。
- IMP
uvm_blocking_put_imp#(T,IMP);
uvm_nonblocking_put_imp#(T,IMP);
uvm_put_imp#(T,IMP);
uvm_blocking_get_imp#(T,IMP);
uvm_nonblocking_get_imp(T,IMP);
uvm_get_imp#(T,IMP);
uvm_blocking_transport_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP);
uvm_nonblocking_transport_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP);
uvm_transport_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP);
前六个定义中的第一个T是这个IMP传输的数据类型。第二个参数IMP指的是实现这个接口的一个component。
注:一定使用IMP来终结连接关系。PORT和EXPORT都不能作为连接关系的终点。
9.2 UVM中的analysis port和analysis export
analysis port 和analysis export与port和export类似,主要区别是:
第一,默认情况下,一个analysis port(analysis export)可以连接多个IMP,也就是说,analysis port(analysis export)与IMP之间的通信是一种一对多的通信,而PORT和EXPORT与IMP的通信是一种一对一的通信。analysis port(analysis export)更像是一个广播。
第二,作为PORT和EXPORT,有put,get,transport操作,虽然如前面所示,一个PORT要么是put_port,要么是get_port,要么是transport_port,不可能是三者兼有,但是毕竟是有这三种操作。但是对于analysis port(analysis export)来说,它只有一种操作write。write的意思就是广播一下,剩下的事情就与他无关了。
第三,作为PORT和EXPORT,都有阻塞和非阻塞的区分。响应的put,get,transport操作也分成了阻塞和非阻塞的。但是对于analysis port和analysis export来说,没有阻塞和非阻塞的概念。因为本身就是广播,不必等待与其相连的其他port的响应。所以不存在阻塞和非阻塞。
9.3用analysis实现monitor和scoreboard的通信
用uvm_analysis_port实现monitor和scoreboard的通信,这种方式是最常见。
在monitor中,需要定义一个analysis port:
class monitor extends uvm_monitor;
uvm_analysis_port#(mac_transaction) ap;
task main_phase(uvm_phase phase);
super.main_phase(phase);
mac_transaction tr;
...
ap.write(tr);
...
endtask
endclass
在scoreboard中定义一个名字位write的task。
class scoreboard extends uvm_scoreboard;
uvm_analysis_imp#(mac_transaction,scoreboard) scb_imp;
task write(mac_transaction tr);
//do something on tr
endtask
endclass
在agent中,定义一个analysis port,并把其指向monitor的ap:
class agent extends uvm_agent;
uvm_analysis_port#(mac_transaction) ap;
...
function void connect_phase(uvm_phase phase);
super.connect(phase);
this.ap = monitor.ap;
endfuction
endclass
env的connnect_phase如下:
function void env::connect_phase(uvm_phase phase);
super.connect(phase);
agent.ap.connect(scoreboard.scb.imp);
endfunction
9.4 有多个uvm_analysis_imp存在的情况
上面的monitor和scoreboard之间的通信,采用一个analysis port和一个analysis imp相连的方式实现的。对于一个analysis imp来说,必须在其实例化的uvm_component定义一个write的task。在上面的例子中,scoreboard只接收一路数据,但在实际情况中,scoreboard除了接收monitor的数据之外,还有接收reference model的数据。响应的scoreboard就要再添加一个uvm_analysis_imp的IMP,如model_imp。此时问题就出现了,这个新的IMP也要有一个write任务与其对应,因为很明显,我们对接收到的两路数据处理是不一样的。但是write只有一个,怎么办?
UVM考虑到了这种情况,采用如下的方式处理:
`uvm_analysis_imp_decl(_monitor)
`uvm_analysis_imp_decl(_model)
class scoreboard extens uvm_scoreboard;
uvm_anaslysis_imp_monitor#(mac_transaction,scoreboard) monitor_imp;
uvm_analysis_imp_model#(mac_transaction,scoreboard) model_imp;
task write_monitor(mac_transaction tr);
//do something on tr
endtask
task write_model(mac_transaciton tr);
//do something on tr
endtask
endclass
通过宏uvm_analysis_imp_decl,声明了两个后缀_monitor和_model。UVM会根据这两个后缀内建两个新的imp:uvm_analysis_imp_moniort和uvm_analysis_imp_model。当与uvm_analysis_imp_monitor相连接的analysis port执行write任务时,会自动调用write_monitor任务,而与uvm_analysis_imp_model相连的则会自动调用write_model任务。所以,只要把后缀声明了,把write后面添加上相应的后缀就可以正常工作了。
9.5 用fifo实现monitor和scoreboard
上节中要声明两个后缀,然后再写响应的task,这种方法看起相当的麻烦。那么有没有简单的方法呢?另外上面的monitor和scoreboard的通信,monitor占据主动地位,而scoreboard只能被动接收。那么有没有方法也让scoreboard实现主动的接收呢?这两个问题的答案是肯定的,那就是使用fifo来实现monitor和scoreboard的通信。
在agent和scoreboard之间添加一个uvm_analysis_fifo,相应的,scoreboard里面的端口要改成这样:
class scoreboard extends uvm_scoreboard;
uvm_blockint_get_port#(mac_transaction) get_port;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
task main_phase(uvm_phase phase);
mac_transaction tr;
while(1) begin
get_port.get(tr);
//do something about tr
end
endtask
endclass
在env里面应该这样连接:
class env extends uvm_env;
uvm_tlm_analysis_fifo#(mac_transaction) agent_scb_fifo;
...
function void build_phase(uvm_phase phase);
super.build_phase(uvm_phase phase);
agent_scb_fifo = new("agent_scb_fifo",this);
...
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
agent.ap.connect(agent_scb_fifo.analysis_export):
scoreboard.get_port.connect(agent_scb_fifo.blocking_get_export);
...
endfunction
endclass
而monitor和agent里面的相关代码跟上节中的完全相同。可以看到,这样连接之后,不必在scoreboard中再写一个名字位write的任务了。scoreboard可以按照自己的节奏工作,而不必按照monitor的节奏来。
10. 静态函数
在systemverilog中,函数分为两类,一类是静态的,一类是非静态的。
定义一个非静态函数:
class A;
function void non_static_function();
...
endfunction
endclass
//对于一个非静态函数,只有当A实例化之后才能被调用:
class B;
A a;
funciton void test();
a = new();
a.non_static_function();
endfunction
endclass
而对于静态函数,并不需要一个类实例化就能调用,而且其调用的方式与非静态函数不一样。非静态函数是用一个点号(.)来调用,而非静态函数是用双冒号来调用(::):
class C;
static function void static_function();
...
endfunction
endclass
class D;
function void test();
C::static_function();
endfunction
endclass
参考资料:UVM1.1应用指南及源码分析