本文
芯片验证系列文章主要分为三部分:验证工程师与芯片验证、SV学习笔记、UVM学习笔记。此为 UVM学习笔记 部分第三篇,主要介绍UVM的组件,包括:uvm_driver, uvm_monitor, uvm_sequencer, uvm_agent, uvm_scoreboard, uvm_env, uvm_test。
参考
名称 |
作者 |
来源 |
《芯片验证漫游指南》 |
刘斌 |
书籍 |
《UVM实战》 |
张强 |
书籍 |
专业术语与缩略语
组件家族
- SV环境中的验证组件按照功能需要,被称之为激励器(stimulator)、监测器(monitor)和检查器(checker)。
- 这三个核心组件与验证环境的三个关键特性对应,即激励、监测和检查。在过往那么多验证方法学中,都有与其对应的组件(component)。
- UVM组件家族是从UVM基类继承的一个核心分支即uvm_component类。
uvm_driver
概述
- 从该类会从uvm_sequencer中获取事务(transaction),经过转化进而在接口中对DUT进行时序激励。
- 任何继承于uvm_driver的类需要注意的是,该类是参数化类,因此在定义时需要声明参数的类型。首先看uvm_driver类的定义:
1
|
class uvm_driver#(type REQ=uvm_sequence_item,type RSP=REQ) extends uvm_component;
|
- 用户在定义新的driver类时,应当声明该类所需要获取的事务参数REQ类型,默认情况下,RSP参数类型同REQ类型保持一致。
- uvm_driver在uvm_component基础上没有扩展新的函数,而只是扩展了一些用来通信的端口和变量:
1
2
3
4
|
uvm_seq_item_pull_port#(REQ, RSP)seq_item_port;
uvm_analysis_port#(RSP)rsp_port;
REQ req;
RSP rsp;
|
- driver类与sequencer类之间的通信就是为了获取新的事务对象,这一操作是通过pull的方式实现的:
1
2
|
driver.seq_item_port.connect(sequencer.seq_item_export) ;
driver.rsp_port.connect(sequencer.rsp_export) ;
|
示例
1
2
3
4
5
6
7
8
9
10
|
class dut_driver extends uvm_driver#(basic_transaction) ;//sequence item类型(默认req和rsq相同,这里都是basic_transaction)
virtual chip_if vif; //virtual interface
bit[7:0] addr,data;
`uvm_component_utils(dut_driver) //在factory中注册dut_driver
function new(string name, uvm_component parent) ;
super.new(name, parent) ;
endfunction:new
extern task run_phase(uvm_phase phase) ; //在运行过程中自动执行的run_phase
endclass:dut_driver
|
uvm_monitor
概述
- 从名字来看,这个类是为了监测接口数据,而任何需要用户自定义数据监测行为的monitor都应当继承于该类。
- 虽然uvm_monitor与它的父类相比, 并没有增添新的成员和方法,但将新定义的monitor类继承于uvm_monitor类会有助于实现父类uvm_monitor的方法和特性。
- 通常所执行的功能包括:
- 观测DUT的interface,并且收集总线信息
- 永远保持PASSIVE模式,即永远不会驱动DUT
- 在总线协议或者内部信号协议观察时,可以做一些功能和时序的检查
- 对于更加复杂的检查要求,它们可以将数据发送至其它验证组件,例如scoreboard、reference model或者coverage collector。
示例
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
26
27
28
29
30
31
32
33
|
class serial_monitor extends uvm_monitor;
virtual serial_if.monitor mi; //virtual interface + modport
`uvm_component_utils(serial_monitor)
function new(string name,uvm_component parent) ;
super.new(name,parent) ;
endfunction:new
function void build_phase(uvm_phase phase) ;
super.build_phase(phase) ;
endfunction:build_phase
extern task run_phase(uvm_phase) ;
endclass:serial_monitor
task serial_monitor::run_phase(uvm_phase) ;
serial_transaction tr;
forever begin
tr=new; //每次发送都创建新的实例
wait(mi.rts) ;
@(neg edge mi.line) ;
#(bit period/2) ;
for(inti=0; i<=7; i++) begin
#(bit_period) ;
tr.parity_error ^= mi.line;
tr.data[il = mi.line; //监测数据
end
#(bit_period) assert(mi.line==1'b1) else //检查协议
`uvm_warning(*MON”,W Framing error”)
...
end
endtask:run phase
|
uvm_sequencer
概述
- 从名uvm_sequencer就如同一个管道,从这个管道中会产生连续的激励事务,并最终通过TLM端口送至driver一侧。
- 如果需要的话,uvm_sequencer也可以从uvm_driver那里获取随后的RSP对象来得知数据通信是否正常。
- uvm_sequencer恐怕是这些组件中技能最超凡的一个成员了,单从它的继承层级就可见一斑。
- 从uvm_sequencer类的定义来看,它也同uvm_driver一样是个参数类,需要在定义sequencer时声明REQ的类型。
- 从名uvm_sequencer与uvm_component之间还多了两个中间类uvm_sequencer_base类和uvm_sequencer_param_base类。
- sequencer既管理着sequence,同时也将sequence中产生的transaction item传送到driver一侧,可以说是整个激励环节中的“路由器”
- 而就sequence、sequencer与driver之间的缠绵悱恻的故事,我们将在序列的部分中详细分析。
示例
1
2
3
4
5
6
7
|
class my_sequencer extends uvm_sequencer#(basic_transaction) ;//sequence_item类型
`uvm_component_utils(uvm_sequencer) //在factory中注册my_sequencer
function new(string name,uvm_component parent) ;
super.new(name,parent) ;
endfunction:new
endclass:my_sequencer
|
- 可以看得出来, uvm_sequencer的注册和构建函数new()与其它的uvm_component在定义时没有什么区别。
uvm_agent
概述
- uvm_agent本身不具备什么神技,但它却是一个标准的验证环境“单位”。这样的一个标准单位通常包含一个driver、一个monitor以及一个sequencer。这三个小伙伴经常是聚在一起,组成一个小团伙agent。
- 同时为了复用,有的时候uvm_agent中只需要包含一个monitor,而不需要driver和sequencer,这就需要通过一个变量来进行有条件的例化。
1
|
uvm_active_passive_enum is_active=UVM_ACTIVE;
|
- is_active是agent的一个成员,缺省值是UVM_ACTIVE,这表示处在active模式的agent需要例化driver、monitor和sequencer;而如果is_active的值是UVM_PASSIVE,这表示agent是passive模式,只可以例化monitor。active模式的agent既有激励功能也有监测功能,passive模式的agent只具有监测功能。
- active模式对应着DUT的接口暴露给agent且需要激励的场景,而passive模式对应着DUT的接口已经与其它设计连接而只需要监测的场景。
- 通过is_active变量,agent需要在build_phase()和connect_phase()等函数中通过选择语句来对driver和sequencer进行有条件的例化和连接,下面这段例码是如何对agent内三个组件进行有条件例化的参考。
- Agent的存在是为了验证环境的复用。
- 按照总线的传输方向划分,可以分为master和slave。Master agent是用来向DUT发起transaction。Slave agent是用来响应DUT的events。
示例
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
26
27
28
29
30
|
class my_agent extends uvm_agent;
//agent中所包含的常见组件
my_sequencer m_sqr;
my_driver m_drv;
my_monitor m_mon;
dut_if vif;
`uvm_component_utils(my_agent)
//决定agent内部结构的变量is_active
uvm_active_passive_enum is_active=UVM_ACTIVE;
...
extern function void build_phase(uvm_phase phase) ;
extern function void connect_phase(uvm_phasephase) ;
endclass:my_agent
function void template_master_agent::build() ;
super.build() ;
monitor=template_master_monitor::type_id::create("monitor", this) ;
if(is_active==UVM_ACTIVE) begin
sequencer=template_master_sequencer::type_id::create("sequencer", this) ;
driver=template_master_driver::type_id::create("driver", this) ;
end
endfunction:build
function void template_master_agent::connect() ;
if(is_active==UVM_ACTIVE) begin
driver.seq_item_port.connect(sequencer.seq_item_export) ;
end
endfunction:connect
|
uvm_scoreboard
概述
- 从名字来看,uvm_scoreboard担任着同SV中checker一样的功能,即进行数据比对和报告。
- uvm_scoreboard本身也没有添加额外的成员变量和方法,但UVM建议用户将自定义的scoreboard类继承于uvm_scoreboard类,这便于子类在日后可以自动继承于可能被扩充到uvm_scoreboard类中的成员。
- 在实际环境中,uvm_scoreboard会接收来自于多个monitor的监测数据,继而进行比对和报告。
- 正由于uvm_scoreboard通用的比较数据特性,UVM自带的其它两个用来做数据比较的类其实很少被使用到:uvm_in_order_comparator#(type T)和uvm_algorithm_comparator#(type BEFORE,type AFTER,type TRANSFORMER)
- 【作为了解】uvm_in_order_comparator是一个参数类,并且它有两个端口before_export和after_export分别从DUT的输入端monitor和输出端monitor获取观测到的数据事务。这些数据事务是将多个时钟周期内的数据整合为更高抽象级的数据对象,而且要求前后端监测到数据事务类型应该相同。
- 【作为了解】uvm_algorithm_comparator也是一个参数类,它的参数数目要更多,这是为了贴合在更多实际场景中,DUT的输入端监测数据格式不同于输出端数据格式,因此type BEFORE与type AFTER两个事务类可以不相同,同时用户也应该提供用来将BEFORE类转换为AFTER类的转换类type TRANSFORMER。
示例
- 在scoreboard中通常会声明TLM端口以供monitor传输数据。
- 简易比较的方法, 可以采用UVM预定义的comparator。
- 对于复杂的设计, 参考SV模块中的做法, 可以在scoreboard中分别创建reference model和comparator。
- 以此为例,也需要注意的是,如果一个组件中有子一级的组件,应该考虑它们的创建、连接和通信问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class cpu_scoreboard extends uvm scoreboard;
uvm_analysis_export #(bus_xact) in_export;
uvm_analysis_export #(bus_xact) out_export;
typedef uvm_in_order_comparator #(bus_xact) comp_t; //预先定义一个类型
comp_t m_comp; //使用预先定义的类型进行声明
...
function void build_phase(uvm_phase phase) ;
super.build_phase(phase) ;
in_export=new("in_export", this) ; //端口也要例化
out_export=new("out_export", this) ;
m_comp=comp_t::type_id::create("m_comp", this) ;
endfunction
function void connect_phase(uvm_phase phase) ;
super.connect_phase(phase) ;
in_export.connect(m_comp.before_export) ;
out_export.connect(m_comp.after_export) ;
endfunction
endclass:cpu_scoreboard
|
uvm_env
概述
- 从环境层次结构而言,uvm_env可能包含多个uvm_agent和其它component。
- 这些不同组件共同构成一个完整的验证环境,而这个环境在将来复用中可以作为子环境被进一步集成到更高的环境中。
- 下面的验证结构中,就定义了一个高层的环境,它里面包含sub_env、agent、scoreboard。
- uvm_env的角色就是一个结构化的容器, 它可以容纳其它组件同时它也可以作为子环境在更高层的集成中被嵌入。
- 在实际使用中, 用户容易混淆uvm_env与uvm_agent之间的嵌套关系,而且容易在创建对象阶段出现错误,建议的嵌套关系是:
- uvm_agent作为一个标准单元, 在更上层的集成中应该被例化到uvm_env。
- uvm_env在更高层的复用中, 可以被其它uvm_env所嵌套。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class top_env extends uvm_env;
sub_env m_se;
my_agent m_agt;
my_scoreboard m_sb;
`uvm_component_utils(top_env)
extern function new(string name, uvm_component parent) ;
function void build_phase(uvm_phase) ;
m_se = sub_env::type_id::create("m_se",this) ;
m_agt=my_agent::type_id::create("m_agt",this) ;
m_sb=my_scoreboard::type_id::create("m_sb",this) ;
endfunction
...
endclass:top_env
|
uvm_test
概述
- 从uvm_test类本身没有什么新成员,但是作为测试用例的“代言人”它不但决定着环境的结构和连接关系,也决定着使用哪一个测试序列。
- 如果没有这个代言人,整个环境都无从建立, 所以uvm_test是验证环境建立的唯一入口,只有通过它才能正常运转UVM的phase机制。
- 我们从下面的示例看到,在一个顶层test中可以例化多个组件,譬如uvm_env或者uvm_agent,而在仿真时通过uvm_test可以实现验证环境的运转。
- 我们推荐在uvm_test中只例化一个顶层uvm_env,这便于提供一个唯一环境节点以形成树状的拓扑结构,而这种树状环境结构也会对应着一种树状配置结构。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class env extends uvm env;
`uvm_component_utils(env)
...
endclass
class agent extends uvm_agent;
`uvm_component_utils(agent)
...
endclass
class test1 extends uvm_test;
`uvm_component_utils(test1)
env e1, e2;
agent a1;
...
function void build phase(uvm phase phase) ;
e1 = env::type_id::create("e1",this) ;
e2 = env::type_id::create("e2",this) ;
a1 = agent::type_id::create("al",this) ;
endfunction
endclass
|
文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。