关注微信公众号摸鱼范式
,后台回复COOKBOOK
获取COOKBOOK原本和译本
PDF度盘链接
Testbench Architecture
UVM Testbench Architecture
UVM testbench 是使用SystemVerilog(动态)类对象与SystemVerilog(静态)接口和结构化层次结构中的模块交互构建的。层次结构由功能层组成,testbench 的中心是被测设计(DUT)。
事务处理器和testbench层次通常完全由SystemVerilog类构建。然而,这种构造风格只针对SystemVerilog仿真器,从而限制了可移植性。使用SystemVerilog类和SystemVerilog接口的另一种风格架构,可以提高执行引擎之间的可移植性。利用这两个SystemVerilog构造,可以在事务器层中进行自然的分离,将一端分组的事务级通信与另一端分组的时控、信号级通信分开。
SV作为验证语言,其最大优势就是利用interface将软件世界(仿真环境)和硬件世界(RTL代码)剥离开来。我们可以利用SV中高级语言属性的优势构造环境,又可以利用其全面继承于verilog的属性与硬件世界交互。利用SV的这一特点,来剥离环境中的事务级通信与RTL中的信号级通信,这就是验证方法学思考的事情。
事务器分区
将不同的抽象级别划分为类似的功能,就形成了一个Dual Top分区的testbench体系结构,其中一个顶层包含所谓的HDL域中所有的时控的、信号级代码,而另一个所谓的HVL/TB顶层包含所有的事务级testbench域代码。由于事务处理器通常位于两个域,因此它们也必须被划分为两个部分。这两部分是一个BFM接口和一个代理类。BFM接口处理信号级代码,而代理类处理常规事务器将执行的任何其他操作。BFM和代理之间通过函数和任务调用进行通信。
虽然这种双顶层测试平台架构可移植性好,但它也在一定程度上降低了建模灵活性。这主要是因为信号级代码被放置到SystemVerilog接口而不是类中。SystemVerilog类提供了强大的面向对象功能,包括SystemVerilog接口所忽略的继承和多态性。
关于双顶层测试平台架构的更多内容可以参考 https://verificationacademy.com/patterns-library/implementation-patterns/environment-patterns/dual-domain-hierarchy-pattern
UVM testbench构建和连接过程
关于构建UVM testbench的文章描述了配置和构建双顶层可移植测试台的所有层的过程。本文提供了一些示例来说明如何构建块级testbench,以及如何将多个块级testbench集成到更高级别的testbench中。
构建UVM Testbench
UVM testbench的首个phase是build phase。在此phase,组成testbench层次结构的uvm_component类被实例化为对象。例化过程自顶向下,在下一层之前构造和配置层次结构的每一层(延迟构造)。
当在HVL顶层模块的initial
块中调用run_test()
方法时,UVM testbench将被激活。这个UVM静态方法有一个字符串参数,该参数根据名称定义要运行的test,并通过UVM工厂构造它。然后,UVM通过调用test类的build方法开始build phase。在执行test的build phase期间,将准备各种testbench组件配置对象,并将这些配置对象中的虚接口分配给相关的testbench接口,然后将配置对象放入UVM配置数据库中。随后,构建下一层次结构。
这里简要提一下,UVM工厂机制的两大属性,一个是override,而另一个就是根据字符串实例化一个类,也就是上面说到的根据输入的test name,run_test去实例化验证环境开始仿真。再重复一点,所谓UVM配置数据库,就是放置所有config_db传递的属性的地方,而config_db的机制在UVM_basics中已经介绍过了。
在层次结构的下一层,将检索上一层准备的相应配置对象,并可能进行进一步的配置。在使用此配置对象来指导下一层次结构的构造和配置之前,可以在当前层次结构中修改它。(这也就是说控制是逐级的,上一层可以修改再往上的层级给以下层级的配置)这样的条件构造就会影响testbench的层次结构。
build phase是自顶向下工作的,因此对testbench层次结构的每个后续级别执行该过程,直到达到最底层级为止。
在build phase完成后,connect phase开始进行所有组件间的连接。与build phase相反,connect phase自底向上工作,从最底层到testbench层次结构的顶部。在connect phase之后,UVM的其余phase将运行到完成(也是自下而上的),在此基础上将控制传递回testbench模块。
注:UVM_basics中我已经提到过,final_phase其实也是自上而下的。
test是构建过程的起点
UVM testbench的构建过程从test类开始,并自顶向下工作。test类构建方法是在build phase第一个被调用的方法,它(即方法实现)决定在UVM testbench上构建什么。其功能是:
- 设置工厂覆盖,以便根据需要将配置对象或组件对象创建为其派生类型
- 创建并配置各个子组件所需的配置对象
- 通过HDL testbench模块给放入配置空间的虚接口句柄赋值
- 构建封装的env配置对象,并将其包含到配置空间中
- 在testbench层次结构中构建test的下层组件,通常是顶层env
对于所有test来说,对于给定的验证环境,在build方法中完成的大部分工作都是相同的,因此建议创建一个test base class,每个test case可以由其扩展。
下面显示的是一个模块级验证环境,用来帮助具体解释test的build过程是如何工作的。这是SPI主机接口DUT的环境,包含两个agent,一个用于APB总线接口,另一个用于SPI接口。关于此示例的build和connect phase的详细描述,请参见“Block Level Testbench Example ”。
Factory Overrides
UVM工厂允许在build过程中用另一个派生类替换其基类。这对于专门化(即定制或扩展)组件行为或配置对象非常有用。必须在构造目标对象之前指定Factory Override,因此在build过程开始时进行Override比较方便。
子组件配置对象
每个容器类组件(如agent或env)都应该有一个配置对象来定义其结构和行为。这些配置对象应该在test的build方法中创建并实现以适应test case的需求。如果子组件的配置很复杂或者很可能更改,那么建议添加一个实现基本(或默认)配置处理的虚函数,然后可以通过在base test类扩展的test case中重写该虚函数来更改配置。
//
// Class Description:
//
//
class spi_test_base extends uvm_test;
// UVM Factory Registration Macro
//
`uvm_component_utils(spi_test_base)
//------------------------------------------
// Data Members
//------------------------------------------
//------------------------------------------
// Component Members
//------------------------------------------
// The environment class
spi_env m_env;
// Configuration objects
spi_env_config m_env_cfg;
apb_agent_config m_apb_cfg;
spi_agent_config m_spi_cfg;
//------------------------------------------
// Methods
//------------------------------------------
// Standard UVM Methods:
extern function new(string name = "spi_test_base", uvm_component parent = null);
extern function void build_phase( uvm_phase phase );
extern virtual function void configure_apb_agent(apb_agent_config cfg);
extern function void set_seqs(spi_vseq_base seq);
endclass: spi_test_base
function spi_test_base::new(string name = "spi_test_base", uvm_component parent = null);
super.new(name, parent);
endfunction
// Build the env, create the env configuration
// including any sub configurations
function void spi_test_base::build_phase(uvm_phase phase);
// env configuration
m_env_cfg = spi_env_config::type_id::create("m_env_cfg");
// APB configuration
m_apb_cfg = apb_agent_config::type_id::create("m_apb_cfg");
configure_apb_agent(m_apb_cfg);
m_env_cfg.m_apb_agent_cfg = m_apb_cfg;
// The SPI is not configured as such
m_spi_cfg.has_functional_coverage = 0;
m_env_cfg.m_spi_agent_cfg = m_spi_cfg;
uvm_config_db #(spi_env_config)::set(this, "*", "spi_env_config", m_env_cfg);
m_env = spi_env::type_id::create("m_env", this);
endfunction: build_phase
function void spi_test_base::set_seqs(spi_vseq_base seq);
seq.m_cfg = m_env_cfg;
seq.spi = m_env.m_spi_agent.m_sequencer;
endfunction
从配置空间赋值虚接口
在调用UVM run_test()
方法之前,必须通过将 DUT 的顶层 I/O 上的信号连接到 SystemVerilog interface类型的的pin接口上来建立信号的连接。然后pin接口被连接到(driver和monitor)BFM接口,每个BFM接口的句柄由一个虚接口句柄赋值,这个虚接口句柄通过uvm_config_db::set调用传递给test。详细信息请参阅虚接口的文章。
在test的build()
方法中,这些虚接口句柄将被分配给相关组件配置对象中的虚接口句柄。然后,各个组件访问其配置对象中的虚接口句柄,通过方法调用来驱动或监视DUT。为了保持组件的模块化和可重用性,driver和monitor不应该直接从配置空间检索它们的虚接口句柄,而只从它们的配置对象中检索。test类中是确保通过配置对象将虚接口赋值给相应验证组件的正确位置。
以下代码显示了在 SPI testbench示例中使用 uvm_config_db::get 方法对 apb_agent 配置对象中的虚拟接口句柄进行赋值:
// The build method from earlier, adding the apb agent virtual interface assignment
// Build the env, create the env configuration including any sub configurations and
// assign virtual interfaces
function void spi_test_base::build_phase( uvm_phase phase );
// Create env configuration object
m_env_cfg = spi_env_config::type_id::create("m_env_cfg");
// Call function to configure the env
configure_env(m_env_cfg);
// Create apb agent configuration object
m_apb_cfg = apb_agent_config::type_id::create("m_apb_cfg");
// Call function to configure the apb_agent
configure_apb_agent(m_apb_cfg);
// Add the APB driver BFM virtual interface
if ( !uvm_config_db #(virtual apb_driver_bfm)::get(this, "", "APB_drv_bfm", m_apb_cfg.drv_bfm ) ) `uvm_error(...)
// Add the APB monitor BFM virtual interface
if ( !uvm_config_db #(virtual apb_monitor_bfm)::get(this, "", "APB_mon_bfm",
m_apb_cfg.mon_bfm ) ) `uvm_error(...)
...
endfunction: build_phase
嵌入子组件配置对象
配置对象从test通过UVM组件配置空间传递给子组件。它们可以单独传递,使用uvm_config_db::set方法中的path参数来控制哪些组件可以访问这些对象。然而,一个常见的需求是中间组件也需要做一些本地配置。
因此,通过testbench层次结构传递配置对象的一种有效方法是将配置对象以反映层次结构本身的方式嵌入到另一个配置对象中。在testbench上的每个中间级别,该级别的配置对象都被“展开”,以产生它的子配置对象,这些子配置对象被重新配置(如果有必要的话),然后使用uvm_config_db::set传递给相关的子组件。
依据层级嵌套配置对象是一种不错的整合方式,但是笔者觉得逐级传递可以选择直接逐级赋值配置对象句柄的方式,而没有必要调用uvm_config_db::set方法。对于封装好有复用需求的UVC,上文的这种方式无疑是最好的传递方式,所有配置的选择修改都放到test层进行。但是注意:若你的容器类组件并非是复用的最小单元,那么最好不要选用逐级传递的方式来配置以免影响复用。仍然可以依据层级嵌套配置对象,但是均在test层分别set到相应层级,相应层级组件内分别get即可。我觉得这可能是是比较适合复用的方式,灵活性极高。(也可与根据个人需求来在顶层组件中来进行底层组件配置对象的非直线获取)
按照SPI模块级环境示例,每个agent都有一个单独的配置对象。env配置对象有每个agent配置对象的句柄。在测试中,从test case的角度构造和配置所有三个配置对象,并将agent配置对象赋值给env配置对象中的相应agent配置对象句柄。随后,将env配置对象添加到配置空间中,以便在稍后构建env时检索。
对于更复杂的环境,则需要额外的封装级别。
//
// Configuration object for the spi_env:
//
//
// Class Description:
//
//
class spi_env_config extends uvm_object;
// UVM Factory Registration Macro
//
`uvm_object_utils(spi_env_config)
//------------------------------------------
// Data Members
//------------------------------------------
// Whether env analysis components are used:
bit has_functional_coverage = 0;
bit has_spi_functional_coverage = 1;
bit has_reg_scoreboard = 0;
bit has_spi_scoreboard = 1;
// Configurations for the sub_components
apb_config m_apb_agent_cfg;
spi_agent_config m_spi_agent_cfg;
//------------------------------------------
// Methods
//------------------------------------------
extern function new(string name = "spi_env_config");
endclass: spi_env_config
function spi_env_config::new(string name = "spi_env_config");
super.new(name);
endfunction
//
/