The UVM Primer – Chapter 13 UVM Environments
- 前面两节介绍了使用factory机制创建顶层对象uvm_test,并在uvm_test中例化uvm_componnets;UVM会自动的调用uvm_components中的各个phase,并且自为每个componnet的run_phase开启独立的进程.这样的方式易于理解,却难以复用。
- 采用以上方式,每次定义新的uvm_test都需要声明对应类型的tester;每个uvm_test类都会例化所有的uvm_components;
- UVM通过uvm_env将测试平台的组件和行为分离,uvm_env专注于构造测试平台的框架。
13.1 Architecting Adaptable Code
- 对于tester而言,我们希望tester可以实现的功能:
- random_tester:发送1000个随机operation with different oprands;
- add_tester: 发送1000次add operation with different oprands;
(1) 实现方式-A
rando_tester和add_tester都分别扩展自uvm_component,每个tester都需要定义get_op,get_data,run_phase方法;无论是random operation还是add operation,在run_phase都以同样的方式送往DUT,这样run_phase就难以维护。一旦我们需要对run_phase做出修改,就需要同时修改各个tester中的run_phase函数。
(2) 实现方式-B
首先从uvm_component中扩展一个类,base_tester,在其中定义虚函数virtual get_op(),get_data()和非虚函数run_phase,在此类中定义run_phase的行为。random_tester和add_tester均扩展自base_tester,此时如果需要对run_phase中的行为进行修改,就只需要修改base_tester中的方法即可。(PS:为了防止声明base_tester的变量,导致仿真报错,可以将base_tester定义为虚类,将get_op和get_data定义为纯虚方法) 这样base_tester就变成了,我们可以使用的资源,便于扩展。
(3) 实现方式-C
将random_tester也变成可使用资源,继承关系:uvm_component -> base_tester -> random_tester -> add_tester; 因为get_data()在random_tester和add_tester中的机制是一致的,因此add_tester可以继承random_tester中的get_data函数,类似的mul_tester,xor_tester都可以扩展自random_tester,并且只需要重构get_op函数即可,其他功能可以通过继承获得。
13.2 Separating Structure from stimulus
- base_tester时测试平台架构中关键的一环,通过base_tester可以派生出一系列的tester,然后我么可以像前两节所述,在uvm_test中例化这些tester开始仿真,这样可以正常工作但是却不利于代码维护。
- 原因: testbench中的stimulus和structure都被封装在uvm_test中,每一个扩展自uvm_tester的都包含了,tester,coverage,scoreboard的对象,如果我们需要修改测试平台的结构,此时工作就会变得相当复杂。
- 这样的测试平台,违背了“每个类只做一件事情”的原则,因此我们需要将平台中的structure(uvm_componnet及其子类组件)和stimulus(uvm_test及其子类)分成两个不同的功能类。UVM定义了uvm_env(扩展自uvm_component),用于存放测试平台的各个组件,uvm_env通常只包含build_phase和connect_phase两个方法。
- 使用uvm_component将测试平台的各个组件封装起来,然后使用uvm_test将各个组件填充起来。我们首先在uvm_env中例化各个uvm_component,然后在uvm_test中例化uvm_env,此时测试平台的结构如下所示:
13.3 The Env Class
- ENV定义了测试平台的结构,例化了测试平台中的component,并且在connect_phase将个组件连接起来(后续专门讲解)
class env extends uvm_env;
`uvm_component_utils(env)
base_tester tester_h;
coverage coverage_h;
scoreboard scoreboard_h;
function void build_phase(uvm_phase phase);
tester_h = base_tester::type_id::create("tester_h",this);
coverage_h = coverage::type_id::create("coverage_h",this);
scoreboard_h = scoreboard::type_id::create("scoreboard_h",this);
endfuction : build_phase
function new(string name,uvm_component parent)
super.new(name,parent);
encfunction : new
enclass
- uvm_env类证明了一件事情,面向对象编程时,把问题分的越细,每一个小块都是single-used class,这样代码就越是简单,便于debug。class env中包含三个句柄**_h,并且在build_phase例化这些组件。但是,env的build_phase好像变得更加复杂了:
- UVM OOP篇中的factory,我们需要传递进去一个string类型的变量,然后返回一个animal类型的对象句柄,我们还需要使用case函数,将其转换至我们需要的类型,如lion;UVM的工厂机制做了更多的功能:
- 使用`uvm_componnet_utils或`uvm_object_utils宏将,新类注册到uvm工厂;
- 工厂会返回正确的object类型,不需要使用cast转换;
- 如上图,UVM使用静态方法,静态成员的技术实现component的例化,这样的方式有以下优势:
- 不需要使用cast拷贝factory的输出,这是UVM自动完成的;
- 编译器会捕获未定义或者拼写错误的类型;
- 编译器会捕获未使用`uvm_component_utils注册的类;
- 我们定义的env中包含三个component的例化,但是其中base_tester是虚类,是不能够声明对象并进行例化的,只能例化该类的子类;为什么这里可以工作?因为class env中,只是用base_tester占位(placeholder),代码假定facory会返回base_tester类型的子类,但是env并不控制返回的子类类型,可能是random_tester,也可能是add_tester,这需要在其他代码中,在build_phase之前重载(overridden)base_tester到factory,这样在env中就可以返回正确的类型;
13.4 Overriding the Factory
在使用factory之前,我们从random_test中继承得到add_test,在add_test中用add_tester替换原来的tester,这样可以正常工作但是却违反了OOP变成的规则:如果我们把代码来回拷贝,那么就说明代码写的不合理(很烂)。不断地拷贝代码意味着,修改的时候需要修改很多地方,有任何遗漏都会导致bug,使用UVM的factory overridden就可以解决这个问题;
- 由于add_tester是base_tester的子类,所以我们用了base_tester的地方都可以替换为add_tester,这样我们告诉UVM所有遇到base_tester的地方都用add_tester代替,这样代码仍可以正常编译和运行,这就称作factory override。
如下图: - 静态方法:set_type_override告诉UVM,在所有使用base_class_name的地方用child_class_name代替;
- 这样我们就可以将测试平台分为两个部分:structure - 由uvm_env控制; stimulus - 由uvm_test控制;random_test例子:
class random_test extends uvm_test;
`uvm_component_utils(random_test);
env env_h;
function void build_phase(uvm_phase phase);
base_tester::type_id::set_type_override(random_tester::get_type());
env_h = env::type_id::create("env_h",this);
endfunction : build_phase
function new(string name, uvm_component parent);
super.new(name,parent);
endfunction : new
endclass
在任何的test中可以根据需求重载覆盖base_tester,env则负责例化重载后类型的对象,现在uvm_test和uvm_env分工明确,这样的代码适应性更强。
- 这一节我们介绍了uvm_env,env将测试平台分割成structure和stimulus两个功能模块;
- 通过定义虚类base_tester和factory override使得uvm_env (stucture)和uvm_test (stimulus) 可以通信;
- 本节,各components分别访问BFM,这在较小的环境中还可以实现,但是在很大的测试平台中就会变得难以控制,因此UVM提供了inter-object communication的机制,可以将不同的uvm_component连接在一起,各component之间可以直接通信,不一定要经过BFM,让测试环境更加智能。