不想错过我的推送,记得右上角-查看公众号-设为星标,摘下星星送给我
欢迎大家加入2022届数字IC交流群,QQ群号 1060380138
考虑构建一个用于验证SPI主机DUT的testbench作为模块级testbench的一个例子。在这种情况下,UVM环境有两个agent—APB agent在其APB从机端口上处理总线传输,以及SPI agent在其SPI端口上处理SPI协议传输。整个UVM验证环境的结构在框图中进行了说明。让我们穿过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
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
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
由于UVM build过程是自顶向下的,所以接下来构建SPI和APB agent。UVM_basics关于agent构建过程的部分描述了如何配置和构建APB agent,SPI agent也遵循相同的过程。
agent中的组件位于testbench层次结构的底部,因此build过程终止于此。
这个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。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 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 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
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配置数据库可以方便地实现这一点。
对于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 查找键。
如下所示,顶层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
END