前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【UVM COOKBOOK】Testbench Architecture【二】

【UVM COOKBOOK】Testbench Architecture【二】

作者头像
空白的贝塔
发布2021-09-08 11:40:08
1K0
发布2021-09-08 11:40:08
举报
文章被收录于专栏:摸鱼范式

不想错过我的推送,记得右上角-查看公众号-设为星标,摘下星星送给我

欢迎大家加入2022届数字IC交流群,QQ群号 1060380138

模块级Testbench

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

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接口生成一个时钟和一个复位信号。

代码语言:javascript
复制
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。

代码语言:javascript
复制
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。

代码语言:javascript
复制
// 
// 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()方法执行的。

代码语言:javascript
复制
// 
// 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类的内容如下:

代码语言:javascript
复制
// 
// 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连接。

代码语言:javascript
复制
// 
// 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及做一定的检查。

现在,我们将从两个顶层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接口:

代码语言:javascript
复制
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。

代码语言:javascript
复制
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如下:

代码语言:javascript
复制
// 
// 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方法或任何配置方法来完成。

代码语言:javascript
复制
// 
// 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)之间建立连接。

代码语言:javascript
复制
// 
// 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配置数据库可以方便地实现这一点。

HDL top level module

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

代码语言:javascript
复制
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 顶层模块下。

代码语言:javascript
复制
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::*,这是编译这个函数调用所必需的。

代码语言:javascript
复制
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

END

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 摸鱼范式 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 模块级Testbench
    • Testbench模块
      • Test
        • Environment
          • Agents
            • 集成级Testbench
              • 顶层testbench module
              • Test
                • PSS env
                  • testbench层次结构的其余部分
                    • 进一步的集成级
                    • 双顶层架构
                      • HDL top level module
                        • HVL/TB top level module
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档