UVM -- COOKBOOK学习

UVM Testbench Architecture

UVM testbench 是使用SystemVerilog(动态)类对象与SystemVerilog(静态)接口和结构化层次结构中的模块交互构建的。层次结构由功能层组成,testbench 的中心是被测设计(DUT)。

img

事务处理器和testbench层次通常完全由SystemVerilog类构建。然而,这种构造风格只针对SystemVerilog仿真器,从而限制了可移植性。使用SystemVerilog类和SystemVerilog接口的另一种风格架构,可以提高执行引擎之间的可移植性。利用这两个SystemVerilog构造,可以在事务器层中进行自然的分离,将一端分组的事务级通信与另一端分组的时控、信号级通信分开。

SV作为验证语言,其最大优势就是利用interface将软件世界(仿真环境)和硬件世界(RTL代码)剥离开来。我们可以利用SV中高级语言属性的优势构造环境,又可以利用其全面继承于verilog的属性与硬件世界交互。利用SV的这一特点,来剥离环境中的事务级通信与RTL中的信号级通信,这就是验证方法学思考的事情。

img

事务器分区

将不同的抽象级别划分为类似的功能,就形成了一个Dual Top分区的testbench体系结构,其中一个顶层包含所谓的HDL域中所有的时控的、信号级代码,而另一个所谓的HVL/TB顶层包含所有的事务级testbench域代码。由于事务处理器通常位于两个域,因此它们也必须被划分为两个部分。这两部分是一个BFM接口和一个代理类。BFM接口处理信号级代码,而代理类处理常规事务器将执行的任何其他操作。BFM和代理之间通过函数和任务调用进行通信。

img

虽然这种双顶层测试平台架构可移植性好,但它也在一定程度上降低了建模灵活性。这主要是因为信号级代码被放置到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类被实例化为对象。例化过程自顶向下,在下一层之前构造和配置层次结构的每一层(延迟构造)。

img

当在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 ”。

img

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时检索。

img

对于更复杂的环境,则需要额外的封装级别。

// 
// 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 
// 
// Inside the spi_test_base class, the agent config handles are assigned:
// 
// 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); 
// Adding 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(...) 
// Adding 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(...) 
// Assign the apb_agent config handle inside the env_config: 
	m_env_cfg.m_apb_agent_cfg = m_apb_cfg; 
// Repeated for the spi configuration object 
	m_spi_cfg = spi_agent_config::type_id::create("m_spi_cfg"); 
	configure_spi_agent(m_spi_cfg); 
// Adding the SPI driver BFM virtual interface 
	if ( !uvm_config_db #(virtual spi_driver_bfm)::get(this, "", "SPI_drv_bfm", m_spi_cfg.drv_bfm ) ) `uvm_error(...) 
// Adding the SPI monitor BFM virtual interface 
	if ( !uvm_config_db #(virtual spi_monitor_bfm)::get(this, "", "SPI_mon_bfm", m_spi_cfg.mon_bfm ) ) `uvm_error(...)
	m_env_cfg.m_spi_agent_cfg = m_spi_cfg; 
// Now env config is complete set it into config space 
	uvm_config_db #( spi_env_config )::set( this , "*", "spi_env_config",m_env_cfg) );
// Now we are ready to build the spi_env
	m_env = spi_env::type_id::create("m_env", this);
endfunction: build_phase

构建下一层次结构

test构建过程的最后一个阶段是使用UVM工厂实例化下一级别的testbench层次结构。这通常意味着构建顶级env,但可能有多个env,或者可能存在条件构建,可以在几个env之间进行选择。

img

编码规范-工厂实例化方法的名称参数应匹配local句柄

create()方法有两个参数,一个是名称字符串,另一个是指向父uvm_component类对象的指针。这些参数的值用于在链表中创建一个条目,UVM使用该条目在伪层次结构中定位uvm_component。此列表用于消息传递和配置机制。按照约定,name参数字符串应该与组件的声明句柄相同,parent参数应该是关键字“this”,以便它引用创建它的uvm_component。使用与句柄相同的名称有助于交叉引用路径和句柄。例如,在前面的代码片段中,使用声明句柄m_env在test中创建了spi_env,因此在build过程中,spi_env的UVM“动态路径”名称是“spi_test.m_env”。

分层build过程

UVM中的build phase自顶向下工作。一旦构造了test类,其 build()方法将被调用,然后是它的每个子组件的 build()方法,以此类推,直到构造了完整的环境层次结构。这种延迟的构造方法使每个 build()方法能够影响层次结构中较低层次的组件的bulid过程中执行的动作。例如,如果将agent配置为passive,则agent的build过程将省略实例化agent的sequencer和driver。

img

分层连接过程

一旦build_phase完成,UVM testbench组件层次结构就就位了,单个组件已经被构造并链接到组件层次结构链表中。UVM connect phase在build phase之后,从层次结构的底部向上工作。其目的是在组件之间建立TLM连接,赋值虚接口句柄,并为寄存器模型等资源进行其他赋值。

配置对象再次在连接过程中发挥作用,因为它们可能包含对虚接口或其他引导连接过程的信息的引用。例如,在一个agent内部,只有在agent是active的情况下,才会将虚接口赋给一个driver,以及driver与其sequencer之间的TLM连接。

例子

UVM build phase可以通过一些示例来很好地说明,这些示例说明了不同的组件层次结构是如何构建的:

  • 包含agent的模块级testbench
  • 集成级testbench

Sequencer-Driver连接|连接Sequencer和Driver

通过在sequencer中实现的双向TLM通信机制,可以促进sequence及其目标driver之间的请求和响应item的传输。uvm_driver类包含一个uvm_seq_item_pull_port,它应该被连接到与driver相关联的sequencer中的uvm_seq_item_pull_export。port和export类使用将用于请求和响应transaction的sequence_item的类型进行参数化。一旦建立了port-export连接,driver代码就可以使用export中实现的API从sequence中获取req sequence_item,并向其返回响应。

img

在 connect phase,使用TLM连接方法连接driver port和sequencer export:

// Driver parameterized with the same sequence_item for request & response 
// response defaults to request 
class adpcm_driver extends uvm_driver #(adpcm_seq_item); 
.... 
endclass: adpcm_driver 
// Agent containing a driver and a sequencer - uninteresting bits left out 
class adpcm_agent extends uvm_agent;
	adpcm_driver m_driver; 
	adpcm_agent_config m_cfg; 
// uvm_sequencer parameterized with the adpcm_seq_item for request & response 
	uvm_sequencer #(adpcm_seq_item) m_sequencer;
// Sequencer-Driver connection: 
function void connect_phase(uvm_phase phase); 
	if(m_cfg.active == UVM_ACTIVE) begin
// The agent is actively driving stimulus
// Driver-Sequencer TLM connection
	m_driver.seq_item_port.connect(m_sequencer.seq_item_export); 
	m_driver.vif = cfg.vif;
// Virtual interface assignment
end 
endfunction: connect_phase

driver和sequencer之间的连接通常是在agent的 connect_phase()方法中进行的。使用标准的UVM driver和sequencer基类,driver和sequencer之间的TLM连接是一对一的连接——多个driver不会连接到一个sequencer,多个sequencer也不会连接到一个driver。

除了这个双向TLM端口外,driver中还有一个analysis_port,可以连接到sequencer中的analysis_export,实现driver和sequencer之间的单向响应通信路径。这是一个历史工件,并提供了通常不使用的冗余功能。双向TLM接口提供了所需的所有功能。如果使用该analysis_port,则连接方式如下:

// Same agent as in the previous bidirectional example: 
class adpcm_agent extends uvm_agent; 
	adpcm_driver m_driver; 
	uvm_sequencer #(adpcm_seq_item) m_sequencer; 
	adpcm_agent_config m_cfg; 
// Connect method: 
	function void connect_phase(uvm_phase phase ); 
		if(m_cfg.active == UVM_ACTIVE) begin 
// Always need the driver-sequencer TLM connection
			m_driver.seq_item_port.connect(m_sequencer.seq_item_export);
 // Response analysis port connection
			m_driver.rsp_port.connect(m_sequencer.rsp_export);
			m_driver.vif = cfg.vif;
		end 
//... 
	endfunction: connect_phase 
endclass: adpcm_agent

请注意,必须始终建立双向TLM连接才能影响req的通信。rsp_port一个可能的使用模型是在driver返回响应时通知其他组件,否则就不需要它了。

模块级Testbench

考虑构建一个用于验证SPI主机DUT的testbench作为模块级testbench的一个例子。在这种情况下,UVM环境有两个agent—APB agent在其APB从机端口上处理总线传输,以及SPI agent在其SPI端口上处理SPI协议传输。整个UVM验证环境的结构在框图中进行了说明。让我们穿过testbench的每一层,并描述它是如何从上到下组合在一起的。

img

Testbench模块

在SPI模块级testbench中使用了两个顶层testbench模块。hdl_top模块包含SPI Master DUT、APB和SPI BFM以及apb_if、spi_if和intr_if接口。SPI Master DUT连接apb_if、spi_if和intr_if, apb_if、spi_if和intr_if分别连接APB从机和SPI主机BFM。两个 initial块也封装在hdl_top中。第一个 initial块使用uvm_config_db::set将BFM接口的虚接口句柄放置到UVM配置空间中。第二个 initial块为APB接口生成一个时钟和一个复位信号。

module hdl_top; 
	ìnclude "timescale.v" 
// PCLK and PRESETn 
// 
	logic PCLK; 
	logic PRESETn; 
// 
// Instantiate the pin interfaces: 
// 
	apb_if APB(PCLK, PRESETn); 
	spi_if SPI(); 
	intr_if INTR();
// 
// Instantiate the BFM interfaces:
// 
	apb_monitor_bfm APB_mon_bfm(
 	.PCLK    (APB.PCLK),
 	.PRESETn (APB.PRESETn),
 	.PADDR   (APB.PADDR),
 	.PRDATA  (APB.PRDATA),
 	.PWDATA  (APB.PWDATA),
 	.PSEL   (APB.PSEL),
 	.PENABLE (APB.PENABLE),
 	.PWRITE  (APB.PWRITE),
 	.PREADY  (APB.PREADY)
	);
	apb_driver_bfm APB_drv_bfm(
 	.PCLK    (APB.PCLK),
 	.PRESETn (APB.PRESETn),
 	.PADDR   (APB.PADDR),
 	.PRDATA  (APB.PRDATA),
 	.PWDATA  (APB.PWDATA),
 	.PSEL   (APB.PSEL),
 	.PENABLE (APB.PENABLE),
 	.PWRITE  (APB.PWRITE),
 	.PREADY  (APB.PREADY)
	);
	spi_monitor_bfm SPI_mon_bfm(
 	.clk  (SPI.clk),
	.cs   (SPI.cs),
 	.miso (SPI.miso),
 	.mosi (SPI.mosi)
	);
	spi_driver_bfm SPI_drv_bfm(
 	.clk  (SPI.clk),
 	.cs   (SPI.cs),
 	.miso (SPI.miso),
 	.mosi (SPI.mosi)
	);
	intr_bfm INTR_bfm(
 	.IRQ  (INTR.IRQ),
 	.IREQ (INTR.IREQ)
	);
// DUT
	spi_top DUT(
 // APB Interface:
 	.PCLK(PCLK),
 	.PRESETN(PRESETn),
 	.PSEL(APB.PSEL[0]),
 	.PADDR(APB.PADDR[4:0]),
 	.PWDATA(APB.PWDATA),
 	.PRDATA(APB.PRDATA),
 	.PENABLE(APB.PENABLE),
 	.PREADY(APB.PREADY),
 	.PSLVERR(),
 	.PWRITE(APB.PWRITE),
 // Interrupt output
 	.IRQ(INTR.IRQ),
 // SPI signals
 	.ss_pad_o(SPI.cs),
 	.sclk_pad_o(SPI.clk),
 	.mosi_pad_o(SPI.mosi),
 	.miso_pad_i(SPI.miso)
	);
// Initial block for virtual interface wrapping: 
	initial begin 
		import uvm_pkg::uvm_config_db; 
 		uvm_config_db #(virtual apb_monitor_bfm)::set(null, "uvm_test_top", "APB_mon_bfm", APB_mon_bfm); 
		uvm_config_db #(virtual apb_driver_bfm) ::set(null, "uvm_test_top", "APB_drv_bfm", APB_drv_bfm); 
		uvm_config_db #(virtual spi_monitor_bfm)::set(null, "uvm_test_top", "SPI_mon_bfm", SPI_mon_bfm); 
		uvm_config_db #(virtual spi_driver_bfm) ::set(null, "uvm_test_top", "SPI_drv_bfm", SPI_drv_bfm); 
		uvm_config_db #(virtual intr_bfm)       ::set(null, "uvm_test_top", "INTR_bfm", INTR_bfm); 
	end 
// 
// Initial blocks for clock and reset generation: 
// 
	initial begin 
		PCLK = 0; 
  		forever #10ns PCLK = ~PCLK; 
	end 
	initial begin 
		PRESETn = 0; 
		repeat(4) @(posedge PCLK); 
		PRESETn = 1; 
	end
endmodule: hdl_top

hvl_top模块只导入uvm_pkg和spi_test_lib_pkg,其中包含可以运行的test的定义。它还包含调用 run_test()方法来构造和启动指定的test的 initial块,从而实现UVM phasing。

module hvl_top; 
	ìnclude "timescale.v" 
	import uvm_pkg::*; 
	import spi_test_lib_pkg::*; 
// UVM initial block: 
	initial begin 
		run_test(); 
	end 
endmodule: hvl_top

Test

UVM构建过程中的下一个phase是build phase。对于SPI模块级示例,这意味着在首先创建和准备环境使用的所有相关配置对象之后构建spi_env组件。配置和构建过程对于大多数test case来说是很通用的,所以设计一个可扩展以创建特定test的test基类通常是很好的做法。

在SPI示例中,spi_env的配置对象包含SPI和APB配置对象的句柄。这就允许使用env配置对象将所有所需的子配置对象传递给env,来作为spi_env build方法的一部分。这种嵌套配置的“俄罗斯套娃”方法可用于多层次结构级别。

在env配置块中将agent的配置对象赋值给它们的句柄之前,先实例化,并且使用uvm_config_db::get方法赋值其虚接口,然后再进行配置。虚接口赋值给在hdl_top中设置的virtual BFM接口句柄。APB agent可以根据不同的test case进行不同的配置,所以它的配置过程被专用于基类中的特定虚方法。这就使得派生的test类可以重载此方法并根据需要来配置APB agent。

// 
// 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; 
// Register map 
	spi_register_map spi_rm; 
//Interrupt Utility 
	intr_util INTR; 
//------------------------------------------ 
// Methods 
//------------------------------------------ 
	extern virtual function void configure_apb_agent(apb_agent_config cfg); 
// Standard UVM Methods: 
	extern function new(string name = "spi_test_base", uvm_component parent = null); 
	extern function void build_phase( uvm_phase phase );
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 and assigning virtural interfaces 
function void spi_test_base::build_phase( uvm_phase phase ); 
	virtual intr_bfm temp_intr_bfm; 
// env configuration 
	m_env_cfg = spi_env_config::type_id::create("m_env_cfg"); 
// Register map - Keep reg_map a generic name for vertical reuse reasons
	spi_rm = new("reg_map", null); 
	m_env_cfg.spi_rm = spi_rm; 
	m_apb_cfg = apb_agent_config::type_id::create("m_apb_cfg"); 
	configure_apb_agent(m_apb_cfg); 
	if (!uvm_config_db #(virtual apb_monitor_bfm)::get(this, "", "APB_mon_bfm", m_apb_cfg.mon_bfm)) ùvm_fatal(...) 
	if (!uvm_config_db #(virtual apb_driver_bfm) ::get(this, "", "APB_drv_bfm", m_apb_cfg.drv_bfm)) ùvm_fatal(...)
 	m_spi_cfg.has_functional_coverage = 0;
 	m_env_cfg.m_spi_agent_cfg = m_spi_cfg;
// Insert the interrupt virtual interface into the env_config: 
	INTR = intr_util::type_id::create("INTR"); 
    if (!uvm_config_db #(virtual intr_bfm)::get(this, "", "INTR_bfm", temp_intr_bfm) ) `uvm_fatal(...) 
	INTR.set_bfm(temp_intr_bfm); 
	m_env_cfg.INTR = INTR; 
	uvm_config_db #( spi_env_config )::set( this ,"*", "spi_env_config", m_env_cfg); 
	m_env = spi_env::type_id::create("m_env", this); 
// Override for register adapter: 
	register_adapter_base::type_id::set_inst_override(apb_register_adapter::get_type(), 
"spi_bus.adapter");
endfunction: build_phase 
// 
// Convenience function to configure the apb agent 
// 
// This can be overloaded by extensions to this base class 
function void spi_test_base::configure_apb_agent(apb_agent_config cfg);
 	cfg.active = UVM_ACTIVE; 
	cfg.has_functional_coverage = 0; 
	cfg.has_scoreboard = 0; 
// SPI is on select line 0 for address range 0-18h 
	cfg.no_select_lines = 1; 
	cfg.start_address[0] = 32'h0;
	cfg.range[0] = 32'h18; 
endfunction: configure_apb_agent

如果要创建一个特定的test case,就需要扩展spi_test_base类,test编写人员可以利用在父类中定义的配置和build过程。因此,test编写人员只需要添加一个run_phase方法。在下面的示例中(简单且需要更新),run_phase方法实例化sequence,并在env中的相应的sequencer上启动它们。所有的配置过程都是由build_phase方法中调用的super.build_phase()方法执行的。

// 
// Class Description: 
// 
// 
class spi_poll_test extends spi_test_base; 
// UVM Factory Registration Macro 
// 
	`uvm_component_utils(spi_poll_test) 
//------------------------------------------ 
// Methods 
//------------------------------------------ 
// Standard UVM Methods: 
	extern function new(string name = "spi_poll_test", uvm_component parent = null);
	extern function void build_phase(uvm_phase phase); 
	extern task run_phase(uvm_phase phase); 
endclass: spi_poll_test 

function spi_poll_test::new(string name = "spi_poll_test", uvm_component parent = null); 
	super.new(name, parent); 
endfunction 
// Build the env, create the env configuration 
// including any sub configurations and assigning virtural interfaces 
function void spi_poll_test::build_phase(uvm_phase phase); 
	super.build_phase(phase); 
endfunction: build_phase 

task spi_poll_test::run_phase(uvm_phase phase); 
	config_polling_test t_seq = config_polling_test::type_id::create("t_seq"); 
	set_seqs(t_seq); 
	phase.raise_objection(this, "Test Started");
	t_seq.start(null);
	#100; 
	phase.drop_objection(this, "Test Finished"); 
endtask: run_phase

Environment

SPI UVM环境中的下一层是spi_env。这个类包含许多子组件,即SPI和APB agent、scoreboard和functional coverage collector。实例化哪个子组件由spi_env配置对象中的变量决定。

在本例中,spi_env配置对象还包含一个实用程序,该实用程序包含一个检测中断的方法,可以被序列使用。spi_env_config类的内容如下:

// 
// Class Description: 
// 
// 
class spi_env_config extends uvm_object; 
	const string s_my_config_id = "spi_env_config"; 
	const string s_no_config_id = "no config"; 
	const string s_my_config_type_error_id = "config type error"; 
// UVM Factory Registration Macro 
// 
	`uvm_object_utils(spi_env_config) 
// Interrupt Utility - used in the wait for interrupt task 
// 
	intr_util INTR; 
//------------------------------------------ 
// 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; 
// Whether the various agents are used: 
	bit has_apb_agent = 1; 
	bit has_spi_agent = 1; 
// Configurations for the sub_components
	apb_agent_config m_apb_agent_cfg;
	spi_agent_config m_spi_agent_cfg; 
// SPI Register model 
	uvm_register_map spi_rm; 
//------------------------------------------ 
// Methods 
//------------------------------------------ 
	extern task wait_for_interrupt; 
	extern function bit is_interrupt_cleared; 
// Standard UVM 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 
// This task is a convenience method for sequences waiting for the interrupt
// signal 
task spi_env_config::wait_for_interrupt;
 	INTR.wait_for_interrupt();
endtask: wait_for_interrupt
// Check that interrupt has cleared: 
function bit spi_env_config::is_interrupt_cleared; 
	return INTR.is_interrupt_cleared(); 
endfunction: is_interrupt_cleared

在本例中,每个子组件都有构建配置的控制位。这为env的重用提供了终极灵活性。

在spi_env的build phase,使用uvm_config_db get()从配置空间检索spi_env_config的句柄。然后,build过程检查配置对象中的各种has_字段,以确定是否构建子组件。在APB和SPI agent的情况下,还有一个额外的步骤,即从env配置对象中解包每个agent的配置对象,然后在任何本地修改之后在env配置表中设置agent配置对象。

在connect phase阶段,再次使用spi_env配置对象来确定要建立哪个TLM连接。

// 
// Class Description: 
// 
// 
class spi_env extends uvm_env; 
// UVM Factory Registration Macro 
// 
	`uvm_component_utils(spi_env) 
//------------------------------------------ 
// Data Members 
//------------------------------------------ 
	apb_agent m_apb_agent; 
	spi_agent m_spi_agent; 
	spi_env_config m_cfg; 
	spi_scoreboard m_scoreboard; 
// Register layer adapter
	reg2apb_adapter m_reg2apb;
// Register predictor 
	uvm_reg_predictor#(apb_seq_item) m_apb2reg_predictor; 
//------------------------------------------ 
// Constraints 
//------------------------------------------ 
//------------------------------------------ 
// Methods 
//------------------------------------------ 
// Standard UVM Methods: 
	extern function new(string name = "spi_env", uvm_component parent = null); 
	extern function void build_phase(uvm_phase phase);
	extern function void connect_phase(uvm_phase phase); 
endclass:spi_env 
function spi_env::new(string name = "spi_env", uvm_component parent = null); 
	super.new(name, parent); 
endfunction 
function void spi_env::build_phase(uvm_phase phase); 
	if (!uvm_config_db #(spi_env_config)::get(this, "", "spi_env_config", m_cfg)) 
`uvm_fatal("CONFIG_LOAD", "Cannot get() configuration spi_env_config from uvm_config_db. Have you set() it?") 
	uvm_config_db #(apb_agent_config)::set(this, "m_apb_agent*", "apb_agent_config", m_cfg.m_apb_agent_cfg); 
	m_apb_agent = apb_agent::type_id::create("m_apb_agent", this);
// Build the register model predictor 
	m_apb2reg_predictor = 
	uvm_reg_predictor#(apb_seq_item)::type_id::create("m_apb2reg_predictor", this); 
	m_reg2apb = reg2apb_adapter::type_id::create("m_reg2apb"); 
	uvm_config_db #(spi_agent_config)::set(this, "m_spi_agent*","spi_agent_config", m_cfg.m_spi_agent_cfg); 
	m_spi_agent = spi_agent::type_id::create("m_spi_agent", this);
	if(m_cfg.has_spi_scoreboard) begin
 		m_scoreboard = spi_scoreboard::type_id::create("m_scoreboard", this); 
	end
endfunction:build_phase
function void spi_env::connect_phase(uvm_phase phase); 
// Only set up register sequencer layering if the spi_rb is the top block 
// If it isn't, then the top level environment will set up the correct sequencer 
// and predictor 
	if(m_cfg.spi_rb.get_parent() == null) begin 
 		if(m_cfg.m_apb_agent_cfg.active == UVM_ACTIVE) begin 
			m_cfg.spi_rb.spi_reg_block_map.set_sequencer(m_apb_agent.m_sequencer, m_reg2apb); 
end 
// 
// Register prediction part: 
//
// Replacing implicit register model prediction with explicit prediction 
// based on APB bus activity observed by the APB agent monitor
// Set the predictor map: 
m_apb2reg_predictor.map = m_cfg.spi_rb.spi_reg_block_map;
// Set the predictor adapter: 
m_apb2reg_predictor.adapter = m_reg2apb; 
// Disable the register models auto-prediction 
m_cfg.spi_rb.spi_reg_block_map.set_auto_predict(0); 
// Connect the predictor to the bus agent monitor analysis port 
m_apb_agent.ap.connect(m_apb2reg_predictor.bus_in); 
end 
if(m_cfg.has_spi_scoreboard) begin 
	m_spi_agent.ap.connect(m_scoreboard.spi.analysis_export);
  	m_scoreboard.spi_rb = m_cfg.spi_rb; 
end 
endfunction: connect_phase

Agents

由于UVM build过程是自顶向下的,所以接下来构建SPI和APB agent。UVM_basics关于agent构建过程的部分描述了如何配置和构建APB agent,SPI agent也遵循相同的过程。

agent中的组件位于testbench层次结构的底部,因此build过程终止于此。

集成级Testbench

这个testbench示例采用了两个模块级别的验证环境,并展示了如何在更高的集成级别重用它们。示例中所说明的原则适用于重复垂直重用。

该示例采用SPI模块级示例,并将其与GPIO DUT的另一个模块级验证环境集成。这两个模块的硬件已经集成到外设子系统(PSS)中,该子系统使用“AHB2APB 总线桥”与 SPI 和 GPIO 模块上的 APB 接口连接。来自模块级别的环境由pss_env封装,它还包含一个AHB agent来驱动AHB总线接口。在此配置中,模块级APB总线接口不再出现,APB agent进入passive,监测APB传输。该激励需要驱动AHB接口,寄存器分层允许在集成级重用模块级激励。

如上文所述,这种级别下,模块环境的APB接口工作在passive模式,DUT对外的直接接口变成了AHB,虽然我们可以通过AHB接口的monitor获取信息,但是我们仍然选择保留APB monitor,可以辅助debug及做一定的检查。

img

现在,我们将从两个顶层testbench模块开始,从上到下依次介绍testbench和构建过程。

顶层testbench module

与模块级testbench示例一样,这里也使用了两个顶层module。hdl_top实例化DUT,实例化BFM接口,并将pin接口连接到DUT和BFM接口。引用BFM接口的虚接口句柄会被放入配置空间,并生成时钟和复位。此代码与模块级testbench代码之间的主要区别是有更多的接口,并且需要绑定到一些内部信号来监视APB总线。另一个区别是,如果要在passive模式下使用agent,便不会实例化driver BFM。DUT由一个module封装,该module将其I/O信号连接到UVM testbench使用的接口。内部信号使用binder module绑定到APB接口:

module top_tb; 
	import uvm_pkg::*; 
	import pss_test_lib_pkg::*; 
// PCLK and PRESETn 
// 
	logic HCLK; 
	logic HRESETn; 
// 
// Instantiate the pin interfaces: 
// 
	apb_if APB(HCLK, HRESETn);
// APB interface - shared between passive agents 
	ahb_if AHB(HCLK, HRESETn);
// AHB interface
	spi_if SPI();
// SPI Interface
	...
// Additional pin interfaces
// 
// Instantiate the BFM interfaces: 
// 
apb_monitor_bfm APB_SPI_mon_bfm( 
.PCLK    (APB.PCLK),
.PRESETn (APB.PRESETn), 
.PADDR  (APB.PADDR), 
.PRDATA  (APB.PRDATA), 
.PWDATA  (APB.PWDATA), 
.PSEL  (APB.PSEL), 
.PENABLE (APB.PENABLE), 
.PWRITE  (APB.PWRITE), 
.PREADY  (APB.PREADY) 
); 
apb_monitor_bfm APB_GPIO_mon_bfm( 
.PCLK  (APB.PCLK),
.PRESETn (APB.PRESETn), 
.PADDR  (APB.PADDR), 
.PRDATA (APB.PRDATA), 
.PWDATA  (APB.PWDATA),
.PSEL  (APB.PSEL), 
.PENABLE  (APB.PENABLE), 
.PWRITE  (APB.PWRITE), 
.PREADY 
(APB.PREADY) 
); 
apb_driver_bfm APB_GPIO_drv_bfm(
  .PCLK  (APB_dummy.PCLK),
  .PRESETn (APB_dummy.PRESETn), 
.PADDR  (APB_dummy.PADDR), 
.PRDATA  (APB_dummy.PRDATA), 
(APB_dummy.PWDATA),  .PWDATA
.PSEL 
(APB_dummy.PSEL), 
.PENABLE  (APB_dummy.PENABLE), 
.PWRITE  (APB_dummy.PWRITE), 
.PREADY  (APB_dummy.PREADY) 
); 
... 
// Additional BFM interfaces 
// Binder 
binder probe(); 
// DUT Wrapper: 
pss_wrapper wrapper(.ahb(AHB), 
.spi(SPI), 
.gpi(GPI), 
.gpo(GPO), 
.gpoe(GPOE), 
.icpit(ICPIT), 
.uart_rx(UART_RX),
.uart_tx(UART_TX),
.modem(MODEM)); 
// UVM initial block: 
// Virtual interface wrapping
initial begin 
 	import uvm_pkg::uvm_config_db;
    uvm_config_db  #(virtual apb_monitor_bfm)::set(null, "uvm_test_top", "APB_SPI_mon_bfm", APB_SPI_mon_bfm); 
    uvm_config_db  #(virtual apb_monitor_bfm)::set((null, "uvm_test_top", "APB_GPIO_mon_bfm", APB_GPIO_mon_bfm); 
	uvm_config_db #(virtual ahb_monitor_bfm)::set(null, "uvm_test_top", "AHB_mon_bfm",AHB_mon_bfm); 
	uvm_config_db #(virtual ahb_driver_bfm)  ::set(null, "uvm_test_top", "AHB_drv_bfm",AHB_drv_bfm); 
	uvm_config_db #(virtual spi_monitor_bfm) ::set(null, "uvm_test_top", "SPI_mon_bfm",SPI_mon_bfm); 
	uvm_config_db #(virtual spi_driver_bfm)  ::set(null, "uvm_test_top", "SPI_drv_bfm",SPI_drv_bfm);
...
//Additional uvm_config_db::set() calls
end 
// 
// Clock and reset initial block: 
// 
initial begin 
	HCLK = 1; 
	forever #10ns HCLK = ~HCLK; 
end 
initial begin 
	HRESETn = 0; 
	repeat(4) @(posedge HCLK); 
	HRESETn = 1; 
end 
// Clock assignments: 
	assign GPO.clk = HCLK; 
	assign GPOE.clk = HCLK; 
	assign GPI.clk = HCLK; 
endmodule: hdl_top

hvl_top module与模块级别的示例基本相同。不过它现在导入的是pss_test_lib_pkg。

module hvl_top; 
	import uvm_pkg::*; 
	import pss_test_lib_pkg::*; 
// UVM initial block: 
	initial begin 
		run_test(); 
	end 
endmodule: hvl_top

Test

与模块级test一样,集成级test应该在基类中实现公共的构建和配置过程,随后的test case可以由基类派生。从示例中可以看出,这里需要进行更多的配置,所以这种需求变得更加迫切。

pss_env的配置对象包含spi_env和gpio_env的配置对象的句柄。依照顺序,子env配置对象包含其agent子组件配置对象的句柄。pss_env负责取消嵌套spi_env和gpio_env配置对象,并在其配置表中设置它们,从而进行任何必要的本地更改。接着,spi_env和gpio_env将它们的agent配置放入它们的配置表中。

pss base test如下:

// 
// Class Description: 
// 
// 
class pss_test_base extends uvm_test; 
// UVM Factory Registration Macro 
// 
	`uvm_component_utils(pss_test_base) 
//------------------------------------------ 
// Data Members 
//------------------------------------------ 
//------------------------------------------ 
// Component Members 
//------------------------------------------ 
// The environment class 
	pss_env m_env; 
// Configuration objects 
	pss_env_config m_env_cfg; 
	spi_env_config m_spi_env_cfg; 
	gpio_env_config m_gpio_env_cfg; 
	apb_agent_config m_spi_apb_agent_cfg; 
	apb_agent_config m_gpio_apb_agent_cfg; 
	ahb_agent_config m_ahb_agent_cfg; 
	spi_agent_config m_spi_agent_cfg; 
	...
// Additional configuration object handles
// Register map 
	pss_register_map pss_rm; 
// Interrupt Utility 
	intr_util ICPIT; 
//------------------------------------------ 
// 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, int index, 
	logic[31:0] start_address, logic[31:0] range); 
	extern task run_phase( uvm_phase phase ); 
endclass: pss_test_base 
function pss_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 and assigning virtural interfaces 
function void pss_test_base::build_phase(uvm_phase phase); 
	virtual intr_bfm temp_intr_bfm;
	m_env_cfg = pss_env_config::type_id::create("m_env_cfg");
// Register model 
// Enable all types of coverage available in the register model 
	uvm_reg::include_coverage("*", UVM_CVR_ALL);
// Register map - Keep reg_map a generic name for vertical reuse reasons 
	pss_rb = pss_reg_block::type_id::create("pss_rb");
	pss_rb.build(); 
	m_env_cfg.pss_rb = pss_rb;
// SPI Sub-env configuration: 
	m_spi_env_cfg = spi_env_config::type_id::create("m_spi_env_cfg"); 
	m_spi_env_cfg.spi_rb = pss_rb.spi_rb;
// apb agent in the SPI env:
	m_spi_apb_agent_cfg = apb_agent_config::type_id::create("m_spi_apb_agent_cfg"); 
	configure_apb_agent(m_spi_apb_agent_cfg, 0, 32'h0, 32'h18); 
	if (!uvm_config_db #(virtual apb_monitor_bfm)::get(this, "", "APB_SPI_mon_bfm", m_spi_apb_agent_cfg.mon_bfm)) 
  	`uvm_fatal("VIF CONFIG", "Cannot get() BFM interface APB_SPI_mon_bfm from      uvm_config_db. Have you set() it?") 
// if (!uvm_config_db #(virtual apb_driver_bfm) ::get(this, "", "APB_SPI_drv_bfm", m_spi_apb_agent_cfg.drv_bfm)) 
// `uvm_fatal("VIF CONFIG", "Cannot get() BFM interface APB_SPI_drv_bfm from uvm_config_db. Have you set() it?")
	m_spi_apb_agent_cfg.active = UVM_PASSIVE; 
	m_spi_env_cfg.m_apb_agent_cfg = m_spi_apb_agent_cfg; 
// SPI agent: 
	m_spi_agent_cfg = spi_agent_config::type_id::create("m_spi_agent_cfg"); 
	if (!uvm_config_db #(virtual spi_monitor_bfm)::get(this, "", "SPI_mon_bfm", m_spi_agent_cfg.mon_bfm))
  	`uvm_fatal("VIF CONFIG", "Cannot get() BFM interface SPI_mon_bfm from uvm_config_db. Have you set() it?") 
	if (!uvm_config_db #(virtual spi_driver_bfm) ::get(this, "", "SPI_drv_bfm", m_spi_agent_cfg.drv_bfm)) 
  	`uvm_fatal("VIF CONFIG", "Cannot get() BFM interface SPI_drv_bfm from uvm_config_db. Have you set() it?") 
	m_spi_env_cfg.m_spi_agent_cfg = m_spi_agent_cfg; 
	m_env_cfg.m_spi_env_cfg = m_spi_env_cfg; 
	uvm_config_db #(spi_env_config)::set(this, "*", "spi_env_config", m_spi_env_cfg); 
// GPIO env configuration: 
	m_gpio_env_cfg = gpio_env_config::type_id::create("m_gpio_env_cfg"); 
	m_gpio_env_cfg.gpio_rb = pss_rb.gpio_rb; 
	m_gpio_apb_agent_cfg = apb_agent_config::type_id::create("m_gpio_apb_agent_cfg"); 
	configure_apb_agent(m_gpio_apb_agent_cfg, 1, 32'h100, 32'h124); 
	if (!uvm_config_db #(virtual apb_monitor_bfm)::get(this, "", "APB_GPIO_mon_bfm", m_gpio_apb_agent_cfg.mon_bfm)) 
  	`uvm_fatal("VIF CONFIG", "Cannot get() BFM interface APB_GPIO_mon_bfm from uvm_config_db. Have you set() it?") 
// if (!uvm_config_db #(virtual apb_driver_bfm) ::get(this, "", "APB_GPIO_drv_bfm", 
	m_gpio_apb_agent_cfg.drv_bfm)) 
// `uvm_fatal("VIF CONFIG", "Cannot get() BFM interface APB_drv_bfm from uvm_config_db. Have you set() it?")
	m_gpio_apb_agent_cfg.active = UVM_PASSIVE; 
	m_gpio_env_cfg.m_apb_agent_cfg = m_gpio_apb_agent_cfg; 
	m_gpio_env_cfg.has_functional_coverage = 1;
// Register coverage no longer valid 
// GPO agent
	m_GPO_agent_cfg = gpio_agent_config::type_id::create("m_GPO_agent_cfg"); 
	if (!uvm_config_db #(virtual gpio_monitor_bfm)::get(this, "", "GPO_mon_bfm", m_GPO_agent_cfg.mon_bfm)) 
	`uvm_fatal("VIF CONFIG", "Cannot get() BFM interface GPO_mon_bfm from uvm_config_db. Have you set() it?") 
// if (!uvm_config_db #(virtual gpio_driver_bfm) ::get(this, "", "GPO_drv_bfm", m_GPO_agent_cfg.drv_bfm)) 
// `uvm_fatal("VIF CONFIG", "Cannot get() BFM interface SPI_drv_bfm from uvm_config_db. Have you set() it?")
	m_GPO_agent_cfg.active = UVM_PASSIVE;
// Only monitors
	m_gpio_env_cfg.m_GPO_agent_cfg = m_GPO_agent_cfg; 
// GPOE agent
	m_GPOE_agent_cfg = gpio_agent_config::type_id::create("m_GPOE_agent_cfg"); 
	if (!uvm_config_db #(virtual gpio_monitor_bfm)::get(this, "", "GPOE_mon_bfm", m_GPOE_agent_cfg.mon_bfm))
  	`uvm_fatal("VIF CONFIG", "Cannot get() BFM interface GPOE_mon_bfm from uvm_config_db. Have you set() it?")
// if (!uvm_config_db #(virtual gpio_driver_bfm) ::get(this, "", "GPOE_drv_bfm", m_GPOE_agent_cfg.drv_bfm))
// `uvm_fatal("VIF CONFIG", "Cannot get() BFM interface SPI_drv_bfm from uvm_config_db. Have you set() it?")
	m_GPOE_agent_cfg.active = UVM_PASSIVE;
// Only monitors
	m_gpio_env_cfg.m_GPOE_agent_cfg = m_GPOE_agent_cfg; 
// GPI agent - active (default)
	m_GPI_agent_cfg = gpio_agent_config::type_id::create("m_GPI_agent_cfg"); 
	if (!uvm_config_db #(virtual gpio_monitor_bfm)::get(this, "", "GPI_mon_bfm", m_GPI_agent_cfg.mon_bfm)) 
  	`uvm_fatal("VIF CONFIG", "Cannot get() BFM interface GPI_mon_bfm from uvm_config_db. Have you set() it?") 
	if (!uvm_config_db #(virtual gpio_driver_bfm) ::get(this, "", "GPI_drv_bfm", m_GPI_agent_cfg.drv_bfm)) 
  	`uvm_fatal("VIF CONFIG", "Cannot get() BFM interface SPI_drv_bfm from uvm_config_db. Have you set() it?")
	m_gpio_env_cfg.m_GPI_agent_cfg = m_GPI_agent_cfg; 
// GPIO Aux agent not present 
	m_gpio_env_cfg.has_AUX_agent = 0; 
	m_gpio_env_cfg.has_functional_coverage = 1; 
	m_gpio_env_cfg.has_out_scoreboard = 1; 
	m_gpio_env_cfg.has_in_scoreboard = 1; 
	m_env_cfg.m_gpio_env_cfg = m_gpio_env_cfg; 
	uvm_config_db #(gpio_env_config)::set(this, "*", "gpio_env_config", m_gpio_env_cfg); 
// AHB Agent 
	m_ahb_agent_cfg = ahb_agent_config::type_id::create("m_ahb_agent_cfg"); 
	if (!uvm_config_db #(virtual ahb_monitor_bfm)::get(this, "", "AHB_mon_bfm", m_ahb_agent_cfg.mon_bfm)) 
  	`uvm_fatal("VIF CONFIG", "Cannot get() BFM interface AHB_mon_bfm from uvm_config_db. Have you set() it?") 
	if (!uvm_config_db #(virtual ahb_driver_bfm) ::get(this, "", "AHB_drv_bfm", m_ahb_agent_cfg.drv_bfm)) 
  	`uvm_fatal("VIF CONFIG", "Cannot get() BFM interface AHB_drv_bfm from uvm_config_db. Have you set() it?")
	m_env_cfg.m_ahb_agent_cfg = m_ahb_agent_cfg;
// Add in interrupt line 
	ICPIT = intr_util::type_id::create("ICPIT"); 
	if (!uvm_config_db #(virtual intr_bfm)::get(this, "", "ICPIT_bfm", temp_intr_bfm)) 
  	`uvm_fatal("VIF CONFIG", "Cannot get() interface ICPIT_bfm from uvm_config_db. Have you set() it?") 
	ICPIT.set_bfm(temp_intr_bfm); 
	m_env_cfg.ICPIT = ICPIT; 
	m_spi_env_cfg.INTR = ICPIT; 
	uvm_config_db #(pss_env_config)::set(this, "*", "pss_env_config", m_env_cfg); 
	m_env = pss_env::type_id::create("m_env", this);
endfunction: build_phase
// 
// Convenience function to configure the apb agent 
// 
// This can be overloaded by extensions to this base class 
function void pss_test_base::configure_apb_agent(apb_agent_config cfg, int index, 
logic[31:0] start_address, logic[31:0] range);
	cfg.active = UVM_PASSIVE; 
	cfg.has_functional_coverage = 0; 
	cfg.has_scoreboard = 0; 
	cfg.no_select_lines = 1;
	cfg.apb_index = index;
	cfg.start_address[0] = start_address;
	cfg.range[0] = range;
endfunction: configure_apb_agent

task pss_test_base::run_phase( uvm_phase phase );
endtask: run_phase

同样,扩展这个基类的test case将填充它的run方法,以定义将在env中的virtual sequencer上运行的virtual sequence。如果要执行非默认配置,那么可以通过填充或重载build方法或任何配置方法来完成。

// 
// Class Description: 
// 
// 
class pss_spi_polling_test extends pss_test_base; 
// UVM Factory Registration Macro 
// 
	`uvm_component_utils(pss_spi_polling_test) 
//------------------------------------------ 
// Methods
//------------------------------------------ 
// Standard UVM Methods: 
	extern function new(string name = "pss_spi_polling_test", uvm_component parent = null); 
	extern function void build_phase(uvm_phase phase);
	extern task run_phase(uvm_phase phase); 
endclass: pss_spi_polling_test 

function pss_spi_polling_test::new(string name = "pss_spi_polling_test", uvm_component parent = null); 
	super.new(name, parent); 
endfunction 
// Build the env, create the env configuration 
// including any sub configurations and assigning virtural interfaces
function void pss_spi_polling_test::build_phase(uvm_phase phase); 
	super.build_phase(phase); 
endfunction: build_phase 
task pss_spi_polling_test::run_phase(uvm_phase phase); 
  	config_polling_test t_seq = config_polling_test::type_id::create("t_seq"); 
	t_seq.m_cfg = m_spi_env_cfg; 
	t_seq.spi = m_env.m_spi_env.m_spi_agent.m_sequencer;
	phase.raise_objection(this, "Starting PSS SPI polling test"); 
	repeat(10) begin 
		t_seq.start(null); 
	end 
	phase.drop_objection(this, "Finishing PSS SPI polling test");
endtask: run_phase

PSS env

PSS env build过程在检查各种 has_<sub-component> 字段以确定test case是否需要 env 后,检索配置对象并构建各种sub-env。 如果要存在 sub-envs,则在 PSS envs 配置表中设置 sub-envs 配置对象。 connect 方法用于在 TLM 端口之间建立连接,并在monitor和analysis组件(如scoreboard)之间建立连接。

// 
// Class Description: 
// 
// 
class pss_env extends uvm_env; 
// UVM Factory Registration Macro 
// 
	`uvm_component_utils(pss_env) 
//------------------------------------------ 
// Data Members 
//------------------------------------------ 
	pss_env_config m_cfg;
//------------------------------------------ 
// Sub Components 
//------------------------------------------ 
	spi_env m_spi_env; gpio_env 
	m_gpio_env; ahb_agent 
	m_ahb_agent; 
// Register layer adapter 
	reg2ahb_adapter m_reg2ahb; 
// Register predictor 
	uvm_reg_predictor#(ahb_seq_item) m_ahb2reg_predictor; 
//------------------------------------------ 
// Methods 
//------------------------------------------ 
// Standard UVM Methods: 
	extern function new(string name = "pss_env", uvm_component parent = null); 
// Only required if you have sub-components 
	extern function void build_phase(uvm_phase phase); 
// Only required if you have sub-components which are connected
	extern function void connect_phase(uvm_phase phase); 
endclass: pss_env 

function pss_env::new(string name = "pss_env", uvm_component parent = null); 
	super.new(name, parent); 
endfunction 
// Only required if you have sub-components 
function void pss_env::build_phase(uvm_phase phase); 
	if (!uvm_config_db #(pss_env_config)::get(this, "", "pss_env_config", m_cfg) ) 
  	`uvm_fatal("CONFIG_LOAD", "Cannot get() configuration pss_env_config from uvm_config_db. Have you set() it?") 
	uvm_config_db #(spi_env_config)::set(this, "m_spi_env*", "spi_env_config", m_cfg.m_spi_env_cfg); m_spi_env = spi_env::type_id::create("m_spi_env", this); 
	uvm_config_db #(gpio_env_config)::set(this, "m_gpio_env*", "gpio_env_config", m_cfg.m_gpio_env_cfg); m_gpio_env = gpio_env::type_id::create("m_gpio_env", this);

	uvm_config_db #(ahb_agent_config)::set(this, "m_ahb_agent*", "ahb_agent_config", m_cfg.m_ahb_agent_cfg); m_ahb_agent = ahb_agent::type_id::create("m_ahb_agent", this); 
  // Build the register model predictor 
  	m_ahb2reg_predictor = uvm_reg_predictor#(ahb_seq_item)::type_id::create
  ("m_ahb2reg_predictor", this); m_reg2ahb =
  	reg2ahb_adapter::type_id::create("m_reg2ahb");
endfunction: build_phase 
// Only required if you have sub-components which are connected
function void pss_env::connect_phase(uvm_phase phase); 
// Only set up register sequencer layering if the pss_rb is the top block 
// If it isn't, then the top level environment will set up the correct sequencer
// and predictor 
	if(m_cfg.pss_rb.get_parent() == null) begin
  		if(m_cfg.m_ahb_agent_cfg.active == UVM_ACTIVE) begin 
  		m_cfg.pss_rb.pss_map.set_sequencer(m_ahb_agent.m_sequencer, m_reg2ahb);
		end 
// 
// Register prediction part:
// 
// Replacing implicit register model prediction with explicit prediction
// based on APB bus activity observed by the APB agent monitor 
// Set the predictor map: 
		m_ahb2reg_predictor.map = m_cfg.pss_rb.pss_map; 
// Set the predictor adapter: 
		m_ahb2reg_predictor.adapter = m_reg2ahb; 
// Disable the register models auto-prediction 
		m_cfg.pss_rb.pss_map.set_auto_predict(0); 
// Connect the predictor to the bus agent monitor analysis port 
		m_ahb_agent.ap.connect(m_ahb2reg_predictor.bus_in); 
	end 
endfunction: connect_phase

testbench层次结构的其余部分

build过程自顶向下继续,如模块级testbench示例中所示,有条件地构建sub-env,并如agent示例中所述构建sub-env中包含的agent。

进一步的集成级

进一步集成级别的垂直重用可以通过扩展PSS示例描述的流程来实现。每个集成级别都会添加另一层,因此,第2级集成环境将包含两个或多个第1级环境,而第2级env配置对象将包含第1级env配置对象的嵌套句柄。显然,在层次结构的test级别上,每一轮垂直重用的代码量都会增加,但层次结构进一步向下,配置和构建过程已经在上一代垂直分层中实现了。

双顶层架构

本书中提倡的双顶层架构可实现平台可移植性——这是使用模拟或其他硬件辅助平台进行testbench加速的基础。 HDL 顶层封装了与 RTL DUT 的基于时钟周期的信号级活动直接相关的所有内容,这些活动可以在仿真中运行或映射(即合成)到硬件仿真加速器上。 HVL/TB 顶层封装了面向对象的testbench,并且总是像往常一样在仿真器上运行。

除了硬件辅助的testbench加速,当然也还有其他使用双顶层架构的好理由。具体来说,它可以方便地使用多处理器平台进行仿真,使用编译和运行时优化技术,或者应用良好的软件工程实践来创建可移植的、可配置的VIP。在一个仿真中拥有多个顶层module是符合(System)Verilog标准,这是非常普遍的。所有顶层(即未实例化的)module都被有效地视为模块层次结构顶层的隐式实例。

显然,双HDL和testbench顶层模块层次结构必须相互连接,或“绑定”在一起,以实现TB - HDL域间通信。使用如下所示的UVM配置数据库可以方便地实现这一点。

img

HDL top level module

对于MAC示例,top_mac_hdl模块包含MAC DUT及其相关(信号和BFM)接口、MAC MII wrapper模块和带有WISHBONE总线逻辑的WISHBONE从机存储器:

module top_mac_hdl; 
	import test_params_pkg::*; 
// WISHBONE interface instance 
// Supports up to 8 masters and up to 8 slaves 
	wishbone_bus_syscon_if wb_bus_if(); 
// BFM interface instances 
	wb_m_bus_driver_bfm wb_drv_bfm(wb_bus_if); 
	wb_bus_monitor_bfm wb_mon_bfm(wb_bus_if); 
//----------------------------------- 
// WISHBONE 0, slave 0: 000000 - 0fffff 
// this is 1 Mbytes of memory 
	wb_slave_mem #(MEM_SLAVE_SIZE) wb_s_0 ( 
	... 
	); 
	... 
//----------------------------------- 
// MAC 0 
// It is WISHBONE slave 1: address range 100000 - 100fff 
// It is WISHBONE Master 0 
	eth_top mac_0 ( 
	... 
	); 
// Wrapper module for MAC MII interface 
	mac_mii_protocol_module #(.INTERFACE_NAME("MIIM_IF")) mii_pm( 
	... 
	); 
	initial begin 
// Set BFM interfaces in config space 
		import uvm_pkg::uvm_config_db; 
		uvm_config_db #(virtual wb_m_bus_driver_bfm):: set(null, "uvm_test_top", "WB_DRV_BFM", wb_drv_bfm); 
		uvm_config_db #(virtual wb_bus_monitor_bfm):: set(null, "uvm_test_top", "WB_MON_BFM", wb_mon_bfm); 
		end 
endmodule

请注意,在 initial块中使用了显式命名的uvm_config_db 导入到本地,以便将BFM接口放到UVM配置空间中,这与top_mac_hdl顶层模块开头的test参数包通配符导入有适当的区别。另一方面,不加选择的通配符导入对于test参数包是合适的,因为该包的多个元素通常会在top_mac_hdl中使用。

更要注意的是,不是像上面的代码将 BFM 接口的 uvm_config_db 注册放在 HDL 顶层模块下,而是可以类似地使用完整的跨域分层实例路径放在 HVL 顶层模块下。

initial begin 
// Set BFM interfaces in config space 
	import uvm_pkg::uvm_config_db; 
		uvm_config_db #(virtual wb_m_bus_driver_bfm):: set(null, "uvm_test_top", "WB_DRV_BFM", top_mac_hdl.wb_drv_bfm); 
		uvm_config_db #(virtual wb_bus_monitor_bfm):: set(null, "uvm_test_top", "WB_MON_BFM", top_mac_hdl.wb_mon_bfm); 
end

将它放在 HDL 端更加优雅,因为 uvm_config_db::set 调用使用 BFM 接口本地化,因此可以使用相对实例路径。 此外,完整的实例路径可以使用 SystemVerilog %m 字符串格式化程序计算为字符串。 例如,可以使用 $sformatf("%m.wb_drv_bfm") 而不是 "WB_DRV_BFM" 作为驱动程序 BFM 虚接口的(保证唯一的)uvm_config_db 查找键。

HVL/TB top level module

如下所示,顶层testbench模块包含一个 initial块,用于调用UVM run_test()函数来启动test。注意,前面的包导入了uvm_pkg::run_test和tests_pkg:😗,这是编译这个函数调用所必需的。

module top_mac_hvl; 
	import uvm_pkg::run_test; 
	import tests_pkg::*; 
	initial 
		run_test(); // create and start running test
// Optionally the initial block from above for uvm_config_db 
// registration of the HDL side BFM interfaces 
endmodule
posted @ 2022-06-16 22:18  Thisway2014  阅读(452)  评论(0编辑  收藏  举报