PDF获取
后台回复COOKBOOK
,即可获取PDF笔记以及原版COOKBOOK
后台回复即可获取
testbench分析部分的第一个任务是监测DUT上的活动。和driver一样,monitor也是agent的组成部分。类似于driver组件,执行的也是实际信号活动和该活动的抽象表示之间的转换(接口上的信号变化翻译成环境中的transaction)。Monitor和Driver之间的关键区别是Monitor总是被动的,不驱动接口上的任何信号。当agent处于passive模式时,Monitor仍将执行。
Monitor通过虚接口与DUT信号通信,并且包含识别信号活动中的协议模式的代码。一旦识别出协议模式,Monitor将构建表示该信号活动的transaction模型,并将transaction广播给任何感兴趣的组件。
monitor由从uvm_monitor扩展而来的代理类和BFM (SystemVerilog接口)组成。这个代理应该有一个analysis port 和一个指向BFM接口的虚接口句柄。
class wb_bus_monitor extends uvm_monitor;
`uvm_component_utils(wb_bus_monitor)
uvm_analysis_port #(wb_txn) wb_mon_ap;
virtual wb_bus_monitor_bfm m_bfm; //BFM handle
wb_config m_config;
// Standard component constructor
function new(string name, uvm_component parent);
super.new(name,parent);
endfunction
function void build_phase( uvm_phase phase );
wb_mon_ap = new("wb_mon_ap", this);
m_config = wb_config::get_config(this); // get config object
m_bfm = m_config.WB_mon_bfm; // set local virtual if property
m_bfm.proxy = this; //Set BFM proxy handle
endfunction
task run_phase(uvm_phase phase);
m_bfm.run(); //Don't start the BFM until we get to the run_phase
endtask
function void notify_transaction(wb_txn item); //Used by BFM to
return transactions
wb_mon_ap.write(item);
endfunction : notify_transaction
endclass
interface wb_bus_monitor_bfm (wishbone_bus_syscon_if wb_bus_if);
import wishbone_pkg::*;
//------------------------------------------
// Data Members
//------------------------------------------
wb_bus_monitor proxy;
//------------------------------------------
// Methods
//------------------------------------------
task run();
wb_txn txn;
forever @ (posedge wb_bus_if.clk) begin
//Capture protocol pin activity into txn
proxy.notify_transaction(txn);
end
endtask
endinterface
正如在科学实验中,观察的行为不应该影响所观察的活动,监测组件应该是passive的。它不应该向DUT注入任何激励。这也就意味着monitor代码在与DUT信号交互时应该是完全只读的(只采样DUT的信号)。此外,BFM除非被调用,否则将不会观察数据,以确保BFM与UVM phasing一致。
简单来说,这段话映射的还是UVM的组件分类哲学,monitor就只monitor,不进行除此之外的工作(比如驱动DUT,对比结果等)。另外就是其行为完全要由phase机制调度,来保证在特定的时间段进行特定的工作。
monitor必须了解协议才能检测信号活动中的可识别模式。可以通过在monitor BFM 的 run() task中编写特定于协议的状态机代码来完成检测。这部分代码通过监测虚接口来等待目标信号活动。
例如,最简单的,检测SoC验证中外设的中断信号,或是识别系统总线上的一笔读写操作。
一旦识别出接口上的特定操作,monitor将构建一个或多个抽象地表示信号活动的transaction。这可以通过调用一个函数并将特定于transaction的属性(例如数据和地址)作为参数传递给函数来实现,或者通过在检测到现有transaction时在其上设置属性来实现。
这也就是说,事务级对象的构建通过建立与特定信号活动之间的映射来实现。
由于SystemVerilog中的对象是基于句柄的,所以当Monitor从其分析端口写入transaction句柄时,只有该句柄被复制并广播给接收的组件。每次 Monitor 在run()
task中运行其正在进行的协议识别循环时,都会发生此写入操作。为了防止在循环的下一次迭代中覆盖相同的transaction对象内存,广播的句柄应该指向Monitor创建的transaction对象的单独副本。
这可以通过两种方式实现:
笔者关于randc用法的tips:之前我在sequence中使用`uvm_do来例化、随机、发送transaction,发现randc不生效,并不是所有值都出现之后才开始重复下一轮。后来意识到,`uvm_do每次都会重新例化transaction,这样对于单个的transaction来说,相当于只随机了一次,而randc是只针对于同一transaction对象的(随机调用的是对象的randomize函数)。我当时采用的方式是只实例化transaction一次,但是并没有出现上文提到的transaction被覆盖的问题,显然这是因为driver未索取transaction时,sequence不会发新的transaction给driver,所以不存在会被覆盖的问题。但若是那种会缓存多笔transaction的通信场景(例如使用analysis fifo),那就会出现原文中的问题。如果此时我们还想randc生效,那显然应该采取上文中提到的第二种方式。每次随机都使用的是同个对象,但是采用clone对象的方式来发送transaction。
一旦构建了一个新的或克隆的transaction,就应该通过写入analysis port的方式广播给所有需要接收该transaction的组件。
//Full run task from monitor BFM
task run();
wb_txn txn;
forever @ (posedge wb_bus_if.clk) begin
if(wb_bus_if.s_cyc) begin
// Is there a valid wb cycle?
txn = wb_txn::type_id::create("txn");
// create a new wb_txn
txn.adr = wb_bus_if.s_addr;
// get address
txn.count = 1;
// set count to one read or write
if(wb_bus_if.s_we) begin
// is it a write?
txn.data[0] = wb_bus_if.s_wdata;
// get data
txn.txn_type = WRITE;
// set op type
while (!(wb_bus_if.s_ack[0] | wb_bus_if.s_ack[1]|wb_bus_if.s_ack[2]))
@ (posedge wb_bus_if.clk);
// wait for cycle to end
end
else begin
txn.txn_type = READ;
// set op type
case (1)
//Nope its a read, get data from correct slave
wb_bus_if.s_stb[0]: begin
while (!(wb_bus_if.s_ack[0])) @ (posedge wb_bus_if.clk);
//wait for ack
txn.data[0] = wb_bus_if.s_rdata[0];
// get data
end
wb_bus_if.s_stb[1]: begin
while (!(wb_bus_if.s_ack[1])) @ (posedge wb_bus_if.clk);
// wait for ack
txn.data[0] = wb_bus_if.s_rdata[1];
// get data
end
endcase
end
// else: !if(wb_bus_if.s_we)
proxy.notify_transaction(txn);
end
endtask
UVM agent是用于给定逻辑接口(如APB或USB)的验证组件“套件”。agent包括一个封装了相应的一组接口信号的interface,monitor和driver的BFM,以及一个package,其中包含组成整个agent组件的各种类。agent本身是一个包含sequencer、driver和monitor的容器,也包含其他相关的验证组件,比如functional coverage collector 以及scoreboard(一般不会放置在agent内)。代理只是提供与“普通”类的对象相同API的类的对象。driver代理和monitor代理以寻常的方式与UVM testbench的其余部分通信,同时还通过虚接口句柄分别访问driver和monitor的BFM接口。因此,一个完整的monitor是由monitor代理服务和monitor BFM成对工作组成的,对driver来说也一样。agent还有一个连接到monitor的analysis port 的analysis port ,用户不需要知道agent的内部结构,便可以将外部分析组件连接到agent。agent是testbench上最低级的层次结构块,其确切结构取决于其配置,每个agent的配置(config object)可能因test而异。这些类和接口共同构成一个可移植可重用的agent。
这也就是说agent是与DUT的一组接口强相关的,一般对应的是DUT的一类功能或是协议接口。显然,对于有相同接口的DUT,agent是可重用的。关于代理器和相应BFM的更多内容可参考https://verificationacademy.com/patterns-library/implementation-patterns/environment-patterns/bfm-proxy-pair-pattern
agent-active
接下来我们研究一下APB agent是如何组成、配置、构建和连接的。APB agent的pin接口apb_if编写在apb_if.sv文件中。命名为apb_monitor_bfm的monitor BFM接口有一个apb_if端口。同样地,命名为apb_driver_bfm的driver BFM接口也有一个apb_if端口。BFM定义了与apb_if引脚接口中信号交互的任务和函数。driver和monitor代理不直接访问BFM局域的pin。文件apb_agent_pkg.sv包含package和APB agent的各种类文件。任何使用这个package中文件的组件,比如env,都要import这个package。
package apb_agent_pkg;
import uvm_pkg::*;
ìnclude "uvm_macros.svh"
ìnclude "config_macro.svh"
ìnclude "apb_seq_item.svh"
ìnclude "apb_agent_config.svh"
ìnclude "apb_driver.svh"
ìnclude "apb_coverage_monitor.svh"
ìnclude "apb_monitor.svh"
typedef uvm_sequencer#(apb_seq_item) apb_sequencer;
ìnclude "apb_agent.svh"
//Reg Adapter for UVM Register Model
ìnclude "reg2apb_adapter.svh"
// Utility Sequences
ìnclude "apb_seq.svh"
ìnclude "apb_read_seq.svh"
ìnclude "apb_write_seq.svh"
endpackage: apb_agent_pkg
注意, 这里的apb_sequencer 类型实际上是一个“typedef”,它使用“sequence_item”类型简单地参数化了“uvm_sequencer”基础组件。
agent的config object定义了:
按照约定,UVM agent config类有一个类型为uvm_active_passive_enum的枚举类型变量,该变量定义了agent是构造了sequencer和driver的UVM_ACTIVE还是二者均未构造的UVM_PASSIVE。这个参数称为active,默认值为UVM_ACTIVE。
而是否构建其他子组件则应该由附加的配置属性来控制,这些属性应该具有描述性名称。例如,如果有一个 functional coverage collector,那么应该有一些控制位来控制是否构建它,比如适当地将其命名为has_functional_coverage。
config object包含driver和monitor使用的BFM虚接口句柄。config object是在test中构造和配置的,在这个顶层,把从testbench module传入的虚接口赋值给虚接口句柄。
agent config object还可以包含其他数据成员,来控制agent的配置方式或行为。例如,APB agent的config object具有数据成员,用于设置内存映射并确定通过关联地址映射拉高的APB PSEL信号。
config类应该将所有配置数据成员默认为通用值。
下面的代码示例展示了APB agent的config object。
// // Class Description: // // class apb_agent_config extends uvm_object; // UVM Factory Registration Macro // `uvm_object_utils(apb_agent_config) // BFM Virtual Interfaces virtual apb_monitor_bfm mon_bfm; virtual apb_driver_bfm drv_bfm; //------------------------------------------ // Data Members //------------------------------------------ // Is the agent active or passive uvm_active_passive_enum active = UVM_ACTIVE; // Include the APB functional coverage collector bit has_functional_coverage = 0; // Include the APB RAM based scoreboard bit has_scoreboard = 0; // // Address decode for the select lines: // Address decode for the select lines: int no_select_lines = 1; int apb_index = 0;// Which PSEL is the monitor connected to logic[31:0] start_address[15:0]; logic[31:0] range[15:0]; //------------------------------------------ // Methods //------------------------------------------ // Standard UVM Methods: extern function new(string name = "apb_agent_config");endclass: apb_agent_config function apb_agent_config::new(string name = "apb_agent_config"); super.new(name); endfunction
在agent的build phase发生的操作由其config object的内容决定。第一个操作是获取config object的句柄,然后,当要构造子组件时,相关配置字段的值来决定是否构造。
agent-passive
该规则的例外是monitor代理和monitor BFM,它们总是存在的,因为不管agent是active还是passive,都会使用它们。driver仅在agent处于active模式时构造。driver BFM则需要额外的思考,根据重用目标可以采取如下不同的操作。
作为interface,driver BFM是在构造硬件部分代码时实例化和创建的。因此为了防止信号在RTL及testbench的其余部分准备就绪之前被驱动或采样,BFM状态机通常不应该自启动。相反,应该使用来自相应代理的启动指示作为BFM开始其活动的trigger。这就允许BFM被同步到标准的UVM Phasing机制中,并且如果agent被配置为passive,driver还会保持静默。除了保持静默,driver在passive模式下也不能驱动信号。这意味着要么driver必须使所有输出默认处于三态('hz)模式,要么driver在passive模式下不能被实例化。
要决定是否实例化driver,需要考虑所需的agent重用的级别。对于简单的block-to-top重用(RTL代码代替了driver的功能),driver在任何情况下都不需要,也不应该被实例化。如果driver BFM还可能需要驱动信号,那么必须实例化,以及使用三态信号值。要控制实例化,参数可以与生成语句一起使用。
构建了agent的子组件后,就需要将它们连接起来。通常需要的agent连接有:
注意:
APB agent的以下代码说明了config object如何确定在build phase和connect phase进行的工作:
//
// Class Description:
//
//
class apb_agent extends uvm_component;
// UVM Factory Registration Macro
//
`uvm_component_utils(apb_agent)
//------------------------------------------
// Data Members
//------------------------------------------
apb_agent_config m_cfg;
//------------------------------------------
// Component Members
//------------------------------------------
uvm_analysis_port #(apb_seq_item) ap;
apb_monitor m_monitor;
apb_sequencer m_sequencer;
apb_driver m_driver;
apb_coverage_monitor m_fcov_monitor;
//------------------------------------------
// Methods
//------------------------------------------
// Standard UVM Methods:
extern function new(string name = "apb_agent", uvm_component parent = null);
extern function void build_phase( uvm_phase phase );
extern function void connect_phase( uvm_phase phase );
endclass: apb_agent
function apb_agent::new(string name = "apb_agent", uvm_component parent = null);
super.new(name, parent);
endfunction
function void apb_agent::build_phase( uvm_phase phase );
if (m_cfg == null)
if( !uvm_config_db #( apb_agent_config )::get(this, "",
"apb_agent_config",m_cfg) )
`uvm_fatal(...)
// Monitor is always present
m_monitor = apb_monitor::type_id::create("m_monitor", this);
// Only build the driver and sequencer if active
if(m_cfg.active == UVM_ACTIVE) begin
m_driver = apb_driver::type_id::create("m_driver", this);
m_sequencer = apb_sequencer::type_id::create("m_sequencer", this);
end
if(m_cfg.has_functional_coverage) begin
m_fcov_monitor = apb_coverage_monitor::type_id::create("m_fcov_monitor", this);
end
endfunction: build_phase
function void apb_agent::connect_phase(uvm_phase phase);
ap = m_monitor.ap;
// Only connect the driver and the sequencer if active
if(m_cfg.active == UVM_ACTIVE) begin
m_driver.seq_item_port.connect(m_sequencer.seq_item_export);
end
if(m_cfg.has_functional_coverage) begin
m_monitor.ap.connect(m_fcov_monitor.analysis_export);
end
endfunction: connect_phase
传统的Verilog或VHDL编写的testbench很难支持有约束的随机激励,test case通常都是'hard-wired' ,很难更改并运行一个新的test case,一般来说是要重新编译的。而UVM Sequences则带来了一种面向对象的激励生成的方式,十分灵活,为生成激励提供了新的选择。
stimulus_gen
sequence是一个被用作方法的对象。UVM sequence包含一个叫做body的task。当使用sequence时,实例化它,之后执行body方法,然后就可以丢弃该sequence。与uvm_component不同,sequence具有有限的仿真生命周期,因此可以称为瞬态对象。sequence的body方法可以用来实例化和执行其他sequence,或者用来生成sequence_item对象(这些对象通过sequencer发送到driver,用于驱动DUT。),当然也可以同时包含这两种使用方式。sequence_item对象也是瞬态对象,它们包含了driver与DUT进行交互所需要的信息。当DUT生成响应时,driver使用sequence_item将响应信息通过sequencer回传给原sequence。实例化和执行其他sequence实际上与能够调用常规子线程相同,因此可以通过将简单sequence链接在一起来构建复杂的功能。
在类的继承关系方面,uvm_sequence继承于uvm_sequence_item, uvm_sequence_item继承于uvm_object。这两个基类都被称为对象而并非组件。UVM testbench组件层次结构是由具有不同属性的uvm_component构建的,这些组件主要是在实例化时将它们绑定到静态组件层次结构中,并且组件层次结构在仿真的整个生命周期中都保持不变。
上文注释中也提到过,uvm_sequence_item实际上是派生于uvm_transaction的,而uvm_transaction才是直接由uvm_object派生。
uvm_inheritance_diagram
sequence是UVM中产生激励的主要手段。事实上,sequence和sequence_items都是对象,这意味着它们可以很容易地随机化来产生有趣的激励。其面向对象的特性也意味着可以像其他任何对象一样被操作。UVM架构也使这些类与testbench组件层次结构分开,优点是通过调用和执行来自库package的不同sequence组合,可以轻松定义新的test case,而不会被锁定到组件范围内可用的方法。但缺点是,sequence不能直接访问在组件层次结构中可用的testbench资源,比如配置信息,或是寄存器模型的句柄。故sequence使用sequencer作为进入组件层次结构的媒介来对testbench资源进行访问。
在UVM sequence体系结构中,sequence负责激励产生流程,并通过sequencer将sequence_items发送给driver。driver负责将sequence_items中包含的信息转换为pin级活动。sequencer是实现通信通道和仲裁机制的中间组件,来促进sequence和driver之间的交互。数据流是双向的,请求项通常会从序列路由到驱动程序,而响应项将从驱动程序返回到序列。通信接口的sequencer端在connect phase与driver端连接。
sequence中的UVM激励生成程序通过生成 sequence_items 并经由sequencer将它们发送给driver来控制driver的行为。激励生成的框架是围绕sequence结构构建的,但是数据的生成则是使用sequence_items作为数据对象。由于 sequence_items 是构建sequence的基础,因此在设计时需要注意一些问题。sequence_item内容由driver驱动DUT所需的信息决定;要易于生成新的数据对象内容,通常是通过支持随机约束生成;以及其他因素如analysis hooks。
sequence_item的内容与driver的需求密切相关。driver依赖它接收到的sequence_items的内容来确定要执行哪种类型的驱动。sequence items属性成员将由表示以下信息类型的数据域组成:
sequence_items在sequence中随机化,以生成数据流。因此,激励的数据属性通常应该声明为rand,并且sequence_item应该包含任何需要的约束,以确保默认生成的值是合法的,或者在合理的范围内。在sequence中,sequence_items通常使用内嵌约束进行随机化,内嵌约束便于对这些基本约束做扩展。
由于sequence_items用于驱动和响应DUT,所以我们最好约定驱动DUT的属性应该是rand,响应DUT的属性不是rand。这将随机化过程进行优化,来确保所收集到的响应信息不会被可能发生的随机化破坏。例如,考虑以下总线协议sequence_item:
class bus_seq_item extends uvm_sequence_item;
// Request data properties are rand
rand logic[31:0] addr;
rand logic[31:0] write_data;
rand bit read_not_write;
rand int delay;
// Response data properties are NOT rand
bit error;
logic[31:0] read_data;
`uvm_object_utils(bus_seq_item)
function new(string name = "bus_seq_item");
super.new(name);
endfunction
// Delay between bus cycles is in a sensible range
constraint at_least_1 { delay inside {[1:20]};}
// 32 bit aligned transfers
constraint align_32 {addr[1:0] == 0;}
// etc
endclass: bus_seq_item
uvm_sequence_item通过uvm_transaction类继承了uvm_object。uvm_object有许多虚方法,用于实现常见的数据对象函数(copy、clone、compare、print、transaction recording),应该实现这些方法以使sequence_item更具通用性。sequence_item经常用于analysis traffic ,它可能有助于添加实用函数来帮助实现功能覆盖率或analysis。
uvm_config_db类是访问资源数据库的推荐方式。资源是两个或多个组件/对象之间共享的任何信息片段。使用uvm_config_db::set将信息放入数据库,使用uvm_config_db::get从数据库检索信息。uvm_config_db类是一个参数化类,因此这就好像它被划分为许多特定类型的“迷你数据库”。参数类型没有限制,可以是类、uvm_object、bit、byte或虚接口等内置类型。
uvm_config_db有两种典型用法。第一个是将虚接口从HDL/DUT域传递给环境,第二个是通过testbench层次结构向下传递配置对象。
这两种用法基本就是最常用的两种,config_db机制放置资源的位置是全局的,只要通过set,get方法匹配数据类型和资源名就可以完成信息传递。
set方法的完整签名为void uvm_config_db #( type T = int )::set( uvm_component cntxt , stringinst_name , string field_name , T value );
如下示例,是将虚接口放入UVM配置数据库:
interface ahb_if data_port_if( clk , reset );
interface ahb_if control_port_if( clk , reset );
...
uvm_config_db #( virtual ahb_if )::set( null , "uvm_test_top" , "data_port" , data_port_if );
uvm_config_db #( virtual ahb_if )::set( null , "uvm_test_top" ,"control_port" , control_port_if );
这段代码将两个AHB接口放入配置数据库的层次位置“uvm_test_top”,这是由run_test()创建的顶层test组件的默认位置。这两个不同的接口在数据库中使用两个不同的域名“data_port”和“control_port”。
注意:
下面是在env中配置agent的示例:
class env extends uvm_env;
ahb_agent_config m_ahb_agent_config;
function void build_phase( uvm_phase phase );
...
m_ahb_agent = ahb_agent::type_id::create("m_ahb_agent" , this );
...
uvm_config_db #( ahb_agent_config )::set( this , "m_ahb_agent*" , "ahb_agent_config" , m_ahb_agent_config );
...
endfunction
endclass
这段代码设置AHB agent及其所有子组件的配置。有两点需要注意:
这里的原理是这样的,get配置生效的前提是第一个组件节点的字符串与第二个参数中的字符串用‘ . ’拼接后与set中设置的相同。这里使用了通配符使得driver、monitor、sequencer中使用(以driver为例)get( this , " " , "ahb_agent_config" , m_ahb_agent_config_drv );就可以直接匹配成功。其实就算这里只set到了m_ahb_agent,在driver中照样可以使用get( null , "uvm_test_top.env.m_ahb_agent" , "ahb_agent_config" , m_ahb_agent_config_drv );来获取成功。原因就是,config_db机制是全局的,实际上它依赖的只是第一和第二个参数组成的字符串能否匹配成功而已。而UVM中做这样的设定,UVM cookbook中这样引导,都是为了让使用者对自己set的目标有一个清晰的把握,避免同类型组件误用了并非自己的config_db的配置。
get方法的完整签名是bit uvm_config_db #( type T = int )::get( uvm_component cntxt , stringinst_name , string field_name , ref T value );
从配置数据库中获取虚接口的示例如下:
class test extends uvm_test;
...
function void build_phase( uvm_phase phase );
...
if( !uvm_config_db #( virtual ahb_if )::get( this , "" , "data_port" ,
m_cfg.m_data_port_config.m_ahb_if ) ) begin
`uvm_error("Config Error", "uvm_config_db #( virtual ahb_if )::get cannot find resource data_port" ) )
end
...
endfunction
...
endclass
上面的代码试图获取AHB数据端口的虚接口,并将其分配到正确的agent的配置对象中。当数据库查找失败时,将提供一个有意义的错误消息。
下面是一个为事务器检索配置的示例:
class ahb_monitor extends uvm_monitor;
ahb_agent_config m_cfg;
function void build_phase( uvm_phase phase );
...
if( !uvm_config_db #( ahb_agent_config )::get( this , "" , "ahb_agent_config" , m_cfg ) ) begin
`uvm_error("Config Error" , "uvm_config_db #( ahb_agent_config )::get cannot find resource ahb_agent_config" )
end
...
endfunction
endclass
还有几点需要注意:
两组优先规则适用于uvm_config_db。首先,在build_phase,组件层次结构上层的context中的set()调用优先于层次结构下层的set()调用。其次,在相同的context中或在build_phase之后,最后的set()调用优先于前一个。有关这些规则的更多细节,可以参阅UVM参考手册或直接查看UVM源代码。
package是一种SystemVerilog语言构造,它支持将相关的声明和定义组合在其名称空间中。package可以包含类型定义、常量声明、函数和类模板。要在作用域内使用package,必须导入,然后才能引用package的内容。
SystemVerilog package是组织代码以及确保对类型、类等的引用一致的有用方法。UVM基类库包含在一个名为“uvm_pkg”的package中。应该使用package来开发UVM testbench,以收集和组织为实现agent、env、sequence库、test库等而开发的各种类定义。
package应该以_pkg后缀命名。包含package的文件的名称应该反映package的名称,并具有.sv后缀名。例如,文件spi_env_pkg.sv将包含package spi_env_pkg。
正当性: .sv扩展名是一种约定,表示package文件是一个独立的编译单元。后缀_pkg表示该文件包含一个package。这两种约定对人和机器解析脚本都很有用。
在package的作用域内声明的类模板应该被分隔成具有.svh后缀名的单独文件。这些文件应该按照需要编译的顺序在包中`include。package文件是唯一应该使用`include的地方,也就是说,在被`include的文件本身中不应该再有`include语句。
正当性:在单独的文件中声明类更容易维护,也能更清楚地传达package的内容。
package的内容可以引用由package导入启用的其他package的内容。这样的外部package导入应该在package的顶部作为package“主体”的第一个语句声明。可能包含的类模板等单独文件不应该单独导入。
正当性:将所有package导入本地化到一个地方可以使package依赖关系更加清晰。将package导入放在package的其他部分或被`include的文件中是不太可见的,而且更有可能导致排序问题和潜在的类型冲突。
一个package中包含的所有文件都应该放在一个目录中。这对于agent来说尤其重要,因为agent目录结构需要是一个完整独立的package。
正当性:一个package文件的包含目录有助于编译流程的设置,也有助于重用,这是因为一个package的所有文件都可以很容易地收集在一起。
下面是UVM env的package文件示例。这个env包含两个agent(SPI和APB)和一个寄存器模型,它们作为sub-package导入。与env相关的类模板是被`include的:
// Note that this code is contained in a file called spi_env_pkg.sv
//
// In Questa it would be compiled using:
// vlog +incdir+$UVM_HOME/src+<path_to_spi_env> <path_to_spi_env>/spi_env_pkg.sv
//
//
// Package Description:
//
package spi_env_pkg;
// Standard UVM import & include:
import uvm_pkg::*;
`include "uvm_macros.svh"
// Any further package imports:
import apb_agent_pkg::*;
import spi_agent_pkg::*;
import spi_register_pkg::*;
// Includes:
`include "spi_env_config.svh"
`include "spi_virtual_sequencer.svh"
`include "spi_env.svh"
endpackage: spi_env_pkg
SystemVerilog package定义了一个作用域,这一事实常常让用户感到困惑。这意味着在package中声明的所有内容,以及导入到package中的其他package的元素,都只在该package的范围内可见。如果一个package被导入到另一个作用域(例如,另一个package或一个模块),则只有被导入package的元素是可见的,而被导入package本身所导入的任何package的元素都是不可见的。因此,如果在新范围中需要这些其他package的元素,则需要分别导入它们。
//
// Package Scope Example
// ----------------------------------------------------
//
package spi_test_pkg;
// The UVM package has to be imported, even though it is imported in the spi_env package.
// This is because the import of the uvm_pkg is only visible within the current scope
import uvm_pkg::*;
// The same is true of the ìnclude of the uvm_macros
`include "uvm_macros.svh"
// Import of uvm_pkg inside the spi_env package is not
// visible within the current spi_test_pkg scope
import spi_env_pkg::*;
// Other imports and ìncludes
`include spi_test_base.svh
endpackage: spi_test_pkg
这也就是说package本身就是一个作用域,而导入外层package中的package只对外层package内可见。关于package的这部分内容,主要是帮助我们组织文件结构的。
END