不想错过我的推送,记得右上角-查看公众号-设为星标,摘下星星送给我
欢迎大家加入2022届数字IC交流群,QQ群号 1060380138
后台回复COOKBOOK
,即可获取PDF笔记以及原版COOKBOOK
后台回复即可获取
设计可重用testbench的关键原则之一是使其尽可能可配。这就意味着testbench及其组成部分可以很容易地重用和快速修改(即重新配置)。在testbench中,有任意数量的值通常可以写成文本值,如for循环次数、字符串名称、随机权重、其他约束表达式值和coverage bin值。这些值可以用SystemVerilog变量表示,可以在运行时设置(和更改),也可以用SystemVerilog参数表示,但必须在elaboration时设置。由于它们提供的灵活性,应始终在可能的情况下构建存放这些属性的配置对象并使用 uvm_config_db API 访问。
另一方面,像总线宽度这样的,必须在细化时确定,因此就不能通过动态配置对象实现。有许多关于在UVM中处理静态参数的文章:
配置对象是组织配置变量的一种有效的、可重用的方法。在一个典型的testbench中,通常会有几个配置对象,每个对象都绑定到一个组件。配置对象被创建为uvm_object的子类,以封装testbench层次结构的给定分支的所有相关配置变量。也可能有一个单独的、附加的配置对象来保存全局配置变量。配置对象中的每个配置变量都可以声明为rand,因此配置对象可以被随机化。
UVM配置数据库可以有效地处理用户定义的配置对象的范围和存储。下面是典型agent配置对象的代码。它具有指向driver和monitor与agent关联的BFM接口的虚接口,以及描述和控制这些BFM的许多变量。
// configuration class
class wb_config extends uvm_object;
`uvm_object_utils( wb_config );
// Configuration Parameters
virtual wb_m_bus_driver_bfm wb_drv_bfm; // virtual driver BFM
virtual wb bus monitor bfm wb mon bfm; // virtual monitor BFM
int m_wb_id; // Wishbone bus ID
int m wb master id; // Wishbone bus master id for wishone agent
int m mac id; // id of MAC WB master
int unsigned m_mac_wb_base_addr; // Wishbone base address of MAC
bit [47:0] m_mac_eth_addr; // Ethernet address of MAC
bit [47:0] m_tb_eth_addr; // Ethernet address of testbench for sends/receives
int m mem slave size; // Size of slave memory in bytes
int unsigned m_s_mem_wb_base_addr; // base address of wb memory for MAC frame buffers
int m_mem_slave_wb_id; // Wishbone ID of slave memory
int m_wb_verbosity; // verbosity level for wishbone messages
function new( string name = "" );
super.new( name );
endfunction
endclass
任何使用配置对象的组件都应该执行以下步骤:
test组件作为顶层组件,从test参数package或UVM配置数据库(例如虚接口句柄)获取配置值。然后为环境中的组件设置特定于test的配置变量。
class test_mac_simple_duplex extends uvm_test;
...
wb_config wb_config_0; // config object for WISHBONE BUS
function void set_wishbone_config_params();
// set configuration info
// NOTE The MAC is WISHBONE slave 0, mem_slave_0 is WISHBONE slave 1
// MAC is WISHBONE master 0, wb_master is WISHBONE master 1
wb_config_0 = new();
if (!uvm_config_db #(virtual wb_m_bus_driver_bfm)::get(this, "", "WB_DRV_BFM", wb_config_0.wb_drv_bfm))
`uvm_fatal(...)
if (!uvm_config_db #(virtual wb_bus_monitor_bfm)::get(this, "", "WB_MON_BFM", wb_config_0.wb_mon_bfm))
`uvm_fatal(...)
wb_config_0.m_wb_id = 0; // WISHBONE 0
wb_config_0.m_mac_id = 0; // the ID of the MAC master
wb_config_0.m_mac_eth_addr = 48'h000BC0D0EF00;
wb_config_0.m_mac_wb_base_addr = 32'h00100000;
wb_config_0.m_wb_master_id = 1; // the ID of the wb master
wb_config_0.m_tb_eth_addr = 48'h000203040506;
wb_config_0.m_s_mem_wb_base_addr = 32'h00000000;
wb_config_0.m_mem_slave_size = 32'h00100000; // 1 Mbyte
wb_config_0.m_mem_slave_wb_id = 0; // the ID of slave mem
wb_config_0.m_wb_verbosity = 350;
uvm_config_db #(wb_config)::set(this, "*", "wb_config", wb_config_0);
endfunction
...
function void build_phase(uvm_phase);
set_wishbone_config_params();
...
endfunction
...
endclass
注意,如果在UVM配置数据库中没有找到虚接口,则使用`uvm_fatal()。这将立即停止test,并将给定的消息传递给`uvm_fatal()调用。可以选择将这些`uvm_fatal()消息转换为`uvm_error()消息,以便在停止之前使testbench运行更久。
要么直接将配置对象传递给使用配置对象的组件,要么使用uvm_config_db::get获取配置对象。在本例中,driver从配置对象获取虚接口句柄、ID和详细信息。注意BFM不是uvm_components。因此,driver和/或monitor代理可能还需要通过函数调用将相关的配置数据传递给BFM。一次函数调用通常足以一次传递所有数据,从而最小化调用开销。
class wb_m_bus_driver extends uvm_driver #(wb_txn, wb_txn);
...
local virtual wb_m_bus_driver_bfm m_bfm; // Virtual Interface
wb_config m_cfg;
function void build_phase( uvm_phase phase );
if (m_config == null)
if( !uvm_config_db #( wb_config )::get( this , "" , "wb_config" , m_cfg ) ) begin
`uvm_fatal(...)
end
m_bfm = m_cfg.wb_drv_bfm; // set local virtual if property
...
endfunction
function void connect_phase( uvm_phase phase );
super.connect_phase( phase );
m_bfm.set_m_id(m_config.m_wb_master_id); //Set config value in BFM
endfunction
function void end_of_elaboration();
set_report_verbosity_level_hier(m_config.m_wb_wb_verbosity);
endfunction
...
endclass
下文有关于配置sequence的单独章节。
建立HDL-to-Testbench连接始终是一种必需的配置活动。SystemVerilog模块(通常是HDL端顶层模块,有时是更精细的模块封装级别)必须将虚接口添加到配置空间中。在HVL Testbench端,test组件从UVM配置数据库中检索相关的虚接口句柄,并将它们应用到适当的配置对象中:
class test_mac_simple_duplex extends uvm_test;
...
function void set_wishbone_config_params();
wb_config_0 = new();
// Get the virtual interface handle that was set in the top module or protocol module
if (!uvm_config_db #(virtual wb_m_bus_driver_bfm)::get(this, "", "WB_DRV_BFM", wb_config_0.wb_drv_bfm))
`uvm_fatal(...)
if (!uvm_config_db #(virtual wb_bus_monitor_bfm)::get(this, "", "WB_MON_BFM", wb_config_0.wb_mon_bfm))
`uvm_fatal(...)
...
uvm_config_db #( wb_config )::set(this , "*", "wb_config", wb_config_0, 0); // put in config
endfunction
...
endclass
配置sequence最通用的方法是默认使用其完整的层次名称,但允许根据需要设置任何其他名称:
class my_bus_seq extends uvm_sequence #( my_bus_sequence_item );
string scope_name = "";
task body();
my_bus_config m_config;
if( scope_name == "" ) begin
scope_name = get_full_name(); // this is {sequencer.get_full_name() , get_name() }
end
if( !uvm_config_db #( my_bus_config )::get( null , scope_name , "my_bus_config" , m_config ) ) begin
`uvm_error(...)
end
endtask
endclass
考虑一个名为“initialization_sequence”的sequence运行在sequencer“uvm_test_top.env.sub_env.agent1.sequencer”上。上面代码中的scope_name默认设置为"uvm_test_top.env.sub_env.agent1.sequencer.initialization_sequence"。
sequence配置最常见的用例是为agent及其组成组件(sequencer、driver、monitor……)获取agent的配置对象集。
class sub_env extends uvm_env;
...
function void build_phase( uvm_phase phase );
...
my_bus_config agent1_config;
...
uvm_config_db #( my_bus_config )::set( this , "agent1*" , "my_bus_config" , agent1_config );
...
endfunction
task main_phase( uvm_phase phase );
my_bus_sequence seq = my_bus_sequence::type_id::create("my_bus_sequence");
seq.start( agent1.sequencer );
endtask
...
endclass
使用sub_env作为context,可配置sequence中的set()调用和默认get()调用将匹配并致使sequence能够访问agent的配置对象。
上面可配置sequence类的缺省完整层次作用域名称可以用来唯一地标识多个sequence实例,从而使它们服从不同的单独配置,而所需要的只是sequence的给定实例名不同。
例如,环境类可能像这样:
class sub_env extends uvm_env;
...
function void build_phase( uvm_phase phase );
...
my_bus_config agent1_config, agent1_error_config;
...
agent1_config.enable_error_injection = 0;
agent1_error_config.enable_error_injection = 10;
// most sequences do not enable error injection
uvm_config_db #( my_bus_config )::set( this , "agent1*" , "my_bus_config" , agent1_config );
// sequences with "error" in their name will enable error injection
uvm_config_db #( my_bus_config )::set( this , "agent1.sequencer.error*" , "my_bus_config" , agent1_error_config );
...
endfunction
task main_phase( uvm_phase phase );
my_bus_sequence normal_seq = my_bus_sequence::type_id::create("normal_seq");
my_bus_sequence error_seq = my_bus_sequence::type_id::create("error_seq");
normal_seq.start( agent1.sequencer );
error_seq.start( agent1.sequencer );
endtask
...
endclass
由于可配置sequence类使用给定的sequence名执行config_db get()调用,正常sequence将选择禁用错误注入的配置对象,而error_sequence将选择error配置对象。
也可以完全忽略sequence配置的组件层次结构。这样做的好处是,实际上可以定义仅用于配置sequence的行为作用域,并使这些行为作用域与组件层次结构完全分离。上面描述的可配置sequence在这个场景中也可以使用。
例如,在virtual sequence 中:
class my_virtual_sequence extends uvm_sequence #( uvm_sequence_item_base );
...
task body();
my_bus_sequence normal_seq = my_bus_sequence::type_id::create("normal_seq");
my_bus_sequence error_seq = my_bus_sequence::type_id::create("error_seq");
normal_seq.scope_name = "sequences::my_bus_config.no_error_injection";
error_seq.scope_name = "sequences::my_bus_config.enable_error_injection";
normal_seq.start( agent1.sequencer );
error_seq.start( agent1.sequencer );
endtask
...
endclass
这种增加灵活性的使用模型缺点是,testbench上的每个sequence和组件必须就命名方案达成一致,或者至少能够处理这种任意命名方案。由于不再保证作用域名称的唯一性,当从模块级转换到集成级testbench时,可能会面临一些重用挑战。
当参数化DUT或接口时,参数值几乎总是在testbench上使用。由于这个原因,我们不应该用实例声明的直接文本值特定化这些公共参数。而是在一个Package中定义相应的命名参数和相关的值,由环境的HDL/DUT端和testbench端共享。这极大地帮助我们避免了这样的错误,即参数值在一边发生了改变,而在另一边却没有发生改变,或者test配置参数是DUT参数的某个函数,而在进行改变时可能会导致计算错误。
请注意,此“共享package”不必是放置所有test参数的地方。不适用和被DUT使用的test参数可以直接在test中特定化。共享参数package应仅包含在 HDL/DUT 和 HVL/TB 域之间共享的参数。
下面的WISHBONE示例涉及两个WISHBONE总线设备、从机存储器和一个以太网MAC(媒体访问控制器)。参数被放在一个包test_params_pkg中,并在实例化HDL顶层模块中的WISHBONE设备和testbench端的test类中使用。
// MAC WISHBONE parameters
parameter mac m wb id = 0; // WISHBONE bus master id of MAC
parameter mac_slave_wb_id = 1; // WISHBONE bus slave id of MAC
endpackage
下面展示了在HDL top模块中使用参数mem_slave_size和mem_slave_wb_id实例化WISHBONE总线从机内存模块。注意,在top_mac_hdl模块中导入了test_params_pkg:
module hdl_top_mac;
...
import test_params_pkg::*;
// WISHBONE interface instance
// Supports up to 8 masters and up to 8 slaves
wishbone_master_bfm wb_mstr_bfm(wb_bus_if);
wishbone_bus_syscon_if wb_bus_if();
//-----------------------------------
// WISHBONE 0, slave 0: 000000 - 0fffff
// this is 1 Mbytes of memory
wb_slave_mem #(mem_slave_size) wb_s_0 (
// inputs
.clk ( wb_bus_if.clk ),
.rst ( wb_bus_if.rst ),
.adr ( wb_bus_if.s_addr ),
.din ( wb_bus_if.s_wdata ),
.cyc ( wb_bus_if.s_cyc ),
.stb ( wb_bus_if.s_stb[mem_slave_wb_id] ),
.sel ( wb_bus_if.s_sel[3:0] ),
.we ( wb_bus_if.s_we ),
// outputs
.dout( wb_bus_if.s_rdata[mem_slave_wb_id] ),
.ack ( wb_bus_if.s_ack[mem_slave_wb_id] ),
.err ( wb_bus_if.s_err[mem_slave_wb_id] ),
.rty ( wb_bus_if.s_rty[mem_slave_wb_id] )
);
...
endmodule
testbench的test类中用于设置WISHBONE总线从机内存配置对象值的参数使用如下所示。注意,不是使用数字字面值32'h00100000,而是使用包含命名DUT参数mem_slave_size的表达式来赋值地址值。
package tests_pkg;
...
import test_params_pkg::*;
...
`include "test_mac_simple_duplex.svh"
endpackage
//-----------------------------------------------------------------
class test_mac_simple_duplex extends uvm_test;
...
wb_config wb_config_0; // config object for WISHBONE BUS
...
function void set_wishbone_config_params();
//set configuration info
wb_config_0 = new();
wb_config_0.m_s_mem_wb_base_addr = mem_slave_wb_id * slave_addr_space_sz; // base address of slave mem
wb_config_0.m_mem_slave_size = 2**(mem_slave_size+2); // defaultis 1 Mbyte
wb_config_0.m_mem_slave_wb_id = mem_slave_wb_id; // WISHBONE bus slave id of slave mem
...
endfunction
...
endclass
在参数集有多个实例的情况下,可以使用基于实例助记符的命名约定来区分实例,或者使用基于参数化类的方法来通过参数特定化区分参数集。
注意,现在存在两个Wishbone从机mem实例。每个都有自己的特定化参数。这是通过指定参数及其默认值的参数化类来处理的。然后,通过使用typedef创建特定化的参数化类,为每个实例设置实际的参数值。
package test_params_pkg;
import uvm_pkg::*;
// WISHBONE general slave parameters
parameter slave_addr_space_sz = 32'h00100000;
// WISHBONE slave memory parameters
class WISHBONE_SLAVE #(int mem_slave_size = 18, int mem_slave_wb_id = 0);
endclass
// Specializations for each slave memory instance
typedef WISHBONE_SLAVE #(18, 0) WISHBONE_SLAVE_0;
typedef WISHBONE SLAVE #(18, 1) WISHBONE SLAVE 1;
// MAC WISHBONE parameters
parameter mac m wb id = 0; // WISHBONE bus master id of MAC
parameter mac_slave_wb_id = 2; // WISHBONE bus slave id of MAC
endpackage
要在上述代码中使用特定化 WISHBONE_SLAVE_0 或 WISHBONE_SLAVE_1 的参数 mem_slave_size 和 mem_slave_wb_id,请使用以下语法:name_of_specialization::parameter_name,如下图所示。
module hdl_top_mac;
...
import test_params_pkg::*;
// WISHBONE interface instance
// Supports up to 8 masters and up to 8 slaves
wishbone_master_bfm wb_mstr_bfm(wb_bus_if);
wishbone_bus_syscon_if wb_bus_if();
//-----------------------------------
// WISHBONE 0, slave 0: 000000 - 0fffff
// this is 1 Mbytes of memory
wb_slave_mem #(WISHBONE_SLAVE_0::mem_slave_size) wb_s_0 (
// inputs
.clk ( wb_bus_if.clk ),
.rst ( wb_bus_if.rst ),
.adr ( wb_bus_if.s_addr ),
.din ( wb_bus_if.s_wdata ),
.cyc ( wb_bus_if.s_cyc ),
.stb ( wb_bus_if.s_stb [WISHBONE_SLAVE_0::mem_slave_wb_id] ),
.sel ( wb_bus_if.s_sel[3:0] ),
.we ( wb_bus_if.s_we ),
// outputs
.dout( wb_bus_if.s_rdata[WISHBONE_SLAVE_0::mem_slave_wb_id] ),
.ack ( wb_bus_if.s_ack [WISHBONE_SLAVE_0::mem_slave_wb_id] ),
.err ( wb_bus_if.s_err [WISHBONE_SLAVE_0::mem_slave_wb_id] ),
.rty ( wb_bus_if.s_rty [WISHBONE_SLAVE_0::mem_slave_wb_id] )
);
...
endmodule
sequence通常需要访问testbench资源,如寄存器模型或配置对象。这最好使用uvm_config_db来检索资源,作为被其他sequence扩展的sequence基类的body()方法的第一个操作。
uvm_config_db可以通过几种方式访问资源:
// Resource access using m_sequencer:
spi_env_config m_cfg;
task body();
if(!uvm_config_db #(spi_env_config)::get(m_sequencer, "", "spi_env_config", m_cfg)) begin
`uvm_error("BODY", "spi_env_config config_db lookup failed")
end
endtask: body
// Resource access using get_full_name():
spi_env_config m_cfg;
task body();
if(!uvm_config_db #(spi_env_config)::get(null, get_full_name(), "spi_env_config", m_cfg)) begin
`uvm_error("BODY", "spi_env_config config_db lookup failed")
end
endtask: body
// Resource access using pre-assigned lookup:
spi_env_config m_cfg;
task body();
if(!uvm_config_db #(spi_env_config)::get(null, "SPI_ENV::", "spi_env_config", m_cfg))
begin
`uvm_error("BODY", "spi_env_config config_db lookup failed")
end
endtask: body
前两个方法基本上是等价的,因为它们都是基于testbench层次结构中的m_sequencer位置或testbench层次结构中的sequence "伪"位置创建作用域字符串。这两种方法之间的细微差别如下。对于第一个方法,m_sequencer.get_full_name()在m_sequencer作为参数传递给get()调用时被调用,生成testbench层次结构中该sequencer的路径。一个例子是“uvm_test_top.env.my_agent.sequencer”。对于第二个方法,get_full_name()是在sequence上调用的,而不是在sequencer上。如果sequence是在sequencer上启动的(即 m_sequencer 句柄不为空),则此 get_full_name() 调用返回sequencer的路径,并附加sequence名称。这方面的一个例子可能是“uvm_test_top.env.my_agent.sequencer.my_seq”。这对于针对在特定sequencer上运行的特定sequence的特定配置信息很有用。
第三种方法依赖于用户商定的不同配置域的命名约定,该约定可以在特定的项目或工作组中很好地工作,但可能由于名称冲突而导致重用问题。
base sequence实现的完整示例如下所示。派生sequence必须调用base sequence body()方法,以确保在启动激励程序之前设置了资源句柄。
//
// Sequence that needs to access a testbench resource via the configuration space
//
// Note that this is a base class; any class extending it must call super.body()
// at the start of its body task to get set up
//
class register_base_seq extends uvm_sequence #(bus_seq_item);
`uvm_object_utils(register_base_seq)
// Handle for the actual sequencer to be used:
bus_sequencer BUS;
// Handle for the environment configuration object:
bus_env_config env_cfg;
// Handle for the register model
dut_reg_model RM;
function new(string name = "register_base_seq");
super.new(name);
endfunction
task body;
// Get the env configuration object - using get_full_name()
if(!uvm_config_db #(bus_env_config)::get(null, get_full_name(), "bus_config", env_cfg)) begin
`uvm_error("BODY", "Failed to find bus_env_config in the config_db")
end
// Assign a pointer to the register model which is inside the env config object:
RM = env_cfg.register_model;
endtask: body
endclass: register_base_seq
//
// A derived sequence:
//
class initialization_seq extends register_base_seq;
`uvm_object_utils(initialization_seq)
task body;
super.body(); // assign the resource handles
... // Sequence body code
endtask: body
endclass: initialization_seq
使用此技术的另一个示例是访问配置对象中的虚接口以等待硬件事件。
宏在减少小的类似模式的代码段的重复键入、隐藏来自不同供应商的仿真器之间的实现差异或限制、或使关键代码段更不容易出错以便重用等方面都很有用。UVM中的许多宏满足这些条件,但并非全部。虽然宏的好处通常是显而易见且直接的,但与其使用相关的成本通常是不透明的,并且在以后的代码更改变得越来越突发性时可能会出现问题。
在DVCon 2011的一篇题为OVM-UVM Macros-Costs vs Benefits的论文中,对宏的使用进行了详细的探讨。
这些建议摘要如下:
关于这篇论文的更多内容可以参考 https://verificationacademy.com/resource/6646
END