3. 开发可重用的验证组件
- 3.1 Data Items 建模
- 3.2 Transaction 级组件
- 3.3 创建 Driver
- 3.4 创建 Sequencer
- 3.5 连接 Driver 和 Sequencer
- 3.6 创建 Monitor
- 3.7 实例化组件
- 3.8 创建 Agent
- 3.9 创建 Environment
- 3.10 Enabling Scenario Creation
- 3.11 仿真结束控制
- 3.12 实现检查和覆盖
3.1 Data Items 建模
Data Items:
- 作为DUT激励的transaction对象
- 验证环境处理的transaction
- 用户定义的类的实例
- 进行transaction级覆盖率收集和检查
注:UVM 类库提供了 uvm_sequence_item 基类,每个用户定义的 data item 都应该直接或间接派生自该基类。
创建一个用户定义 data item:
- 参考 DUT 的 transaction 说明,确定相关特性、约束、任务和函数
- 从 uvm_sequence_item 继承出一个 data item 类
- 定义构造函数
- 添加控制域
- 使用 UVM field 宏使能 print、copy、comparing 等
- 按照需要定义 do_* 函数
UVM 有许多内建的自动化方法:
- print()
- copy()
- compare()
为了帮助 debug 和追踪 transaction,uvm_transaction 基类提供了访问transaction ID的方法 get_transaction_id()。uvm_sequence_item 基类继承自 uvm_transaction,同样有一个 get_transaction_id() 成员函数,可以将 sequence item 与其对应的 sequence 相关联。
下面的例子中,定义了几个随机变量和约束,UVM 宏使能了copy、compare、print等方法。
class simple_item extends uvm_sequence_item;
rand int unsigned addr;
rand int unsigned data;
rand int unsigned delay;
constraint c1 { addr < 16'h2000; }
constraint c2 { data < 16'h1000; }
// UVM automation macros for general objects
`uvm_object_utils_begin(simple_item)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_int(delay, UVM_ALL_ON)
`uvm_object_utils_end
// Constructor
function new (string name = "simple_item");
super.new(name);
endfunction : new
endclass : simple_item
3.1.1 继承和约束
为了达到验证目标,用户需要在data item的生成中添加更多的约束。在Systemverilog中,通过继承来进一步添加约束。下面的例子为添加了额外约束的继承后的data item。
class word_aligned_item extends simple_item;
constraint word_aligned_addr { addr[1:0] == 2'b00; }
`uvm_object_utils(word_aligned_item)
// Constructor
function new (string name = "word_aligned_item");
super.new(name);
endfunction : new
endclass : word_aligned_item
3.1.2 定义控制域
通常可能不需要生成输入空间内的所有可能数值,在 simple_item 的例子中,delay 属性可能被随机化为0到最大值之间的任意值。覆盖整个范围是没有必要的,但是覆盖小、中、大的delay属性是必要的。要完成这个目的,需要定义控制域,这些控制域同样可以用于覆盖率收集。为了增强可读性,使用枚举类型来代表不同的类别。
typedef enum {ZERO, SHORT, MEDIUM, LARGE, MAX} simple_item_delay_e;
class simple_item extends uvm_sequence_item;
rand int unsigned addr;
rand int unsigned data;
rand int unsigned delay;
rand simple_item_delay_e delay_kind; // Control field
// UVM automation macros for general objects
`uvm_object_utils_begin(simple_item)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_enum(simple_item_delay_e, delay_kind, UVM_ALL_ON)
`uvm_object_utils_end
constraint delay_order_c { solve delay_kind before delay; }
constraint delay_c {
(delay_kind == ZERO) -> delay == 0;
(delay_kind == SHORT) -> delay inside { [1:10] };
(delay_kind == MEDIUM) -> delay inside { [11:99] };
(delay_kind == LARGE) -> delay inside { [100:999] };
(delay_kind == MAX ) -> delay == 1000;
delay >=0; delay <= 1000; }
endclass : simple_item
通过这种方法可以创建更多抽象测试,例如可以为delay_kind 的各个取值分配权重:
constraint delay_kind_d {
delay_kind dist {ZERO:=2, SHORT:=1, MEDIUM:=1, LONG:=1, MAX:=2};
}
当创建data item时,要考虑范围内哪些取值是需要关注的,然后添加控制域来控制这些取值的覆盖率收集以及随机化过程。
3.2 Transaction 级组件
一个简单的 transaction 级验证环境的基本组件包括:
- 一个激励生成器(sequencer)来向DUT产生transaction
- 一个driver将transaction转换为信号级的激励,驱动到DUT接口
- 一个monitor来识别DUT接口上的信号级活动并转换为transaction
- 一个analysis组件,如覆盖率收集或scoreboard,来分析transaction
UVM 中的 TLM 接口的一致性和模块化提高了组件的复用性,每个模块可以通过接口相互连接,而不用考虑各自的内部实现。
上图展示了将各个组件封装到可复用接口级验证组件 agent 的建议结构。
3.3 创建 Driver
所有的driver都应该直接或者间接继承自uvm_driver基类。driver有一个可以和sequencer通信的TLM接口。driver也可以实现一个或者多个 run-time phase来管理操作过程。
创建一个driver:
- 从 uvm_driver 继承一个类
- 如果需要,添加 UVM 域的自动化相关宏来使能内建方法
- 从 sequencer 获取下一个 data item 并驱动
- 声明一个 virtual interface 来连接 DUT
下面的例子中,simple_driver 继承自 uvm_driver,参数为 transaction 类型,使用了 seq_item_port 与 sequencer 进行通信。
class simple_driver extends uvm_driver #(simple_item);
simple_item s_item;
virtual dut_if vif;
// UVM automation macros for general components
`uvm_component_utils(simple_driver)
// Constructor
function new (string name = "simple_driver", uvm_component parent);
super.new(name, parent);
endfunction : new
function void build_phase(uvm_phase phase);
string inst_name;
super.build_phase(phase);
if(!uvm_config_db#(virtual dut_if)::get(this, “”,"vif",vif))
`uvm_fatal("NOVIF", {"virtual interface must be set for: ", get_full_name(),".vif"});
endfunction : build_phase
task run_phase(uvm_phase phase);
forever begin
// Get the next data item from sequencer (may block).
seq_item_port.get_next_item(s_item);
// Execute the item.
drive_item(s_item);
seq_item_port.item_done(); // Consume the request.
end
endtask:drive_item
endclass:simple_driver
3.4 创建 Sequencer
sequencer 生成激励数据并传递到 driver 进行驱动。UVM 类库提供了 uvm_sequencer 基类,参数为 request 和 response item 类型。uvm_sequencer 基类包含了所有 sequence 与 driver 进行通信所需要的基础功能。sequencer 的声明方式如下,默认的 response 类型和 request 类型相同,如果需要不同的 response 类型,可选的第二个参数必须指定为 uvm_sequencer 基类型。
uvm_sequence #(simple_item, simple_rsp) sequencer;
3.5 连接 Driver 和 Sequencer
driver 和 sequencer 通过 TLM 连接,driver 的 seq_item_port 连接到 sequencer 的 seq_item_export。sequencer 通过 export 提供 data item,driver 通过 seq_item_port 进行接收,并可选地提供 response。对 driver 和 sequencer 进行实例化的上层组件完成两者的连接。
uvm_driver 内的 seq_item_port 定义了driver 从 sequence获取下一个 item的方法。
3.5.1 Basic Sequencer and Driver Interaction
driver 和 sequencer 中的相互操作通过 get_next_item() 和 item_done() 两个任务完成。driver 使用 get_next_item() 来获取下一个要发送的随机化后的 item,在发送给 DUT 之后,使用 item_done() 通知 sequencer item 处理完成。
forever begin
get_next_item(req);
// Send item following the protocol.
item_done();
end
注:get_next_item() 是阻塞的,在得到 item 之前会一直等待
3.5.2 Querying for the Randomized Item
除了 get_next_item() 任务,uvm_seq_item_pull_port 类提供了另一个任务 try_next_item()。该任务为非阻塞,可以使用该任务使 driver 可以执行一些 idle transaction:
task run_phase(uvm_phase phase);
forever begin
// Try the next data item from sequencer (does not block).
seq_item_port.try_next_item(s_item);
if (s_item == null) begin
// No data item to execute, send an idle transaction.
...
end
else begin
// Got a valid item from the sequencer, execute it.
...
// Signal the sequencer; we are done.
seq_item_port.item_done();
end
end
endtask: run
3.5.3 连续获取随机化 item
在一些协议中,如流水线协议,driver 可能需要同时处理多个 transaction。sequencer-driver的连接中,一个单一的 item 握手应该在下一次 item 开始之前完成。在这种机制下,driver 可以通过调用 item_done() 来完成握手,随后通过调用 put_response® 来提供响应。
3.5.4 将处理后的数据送回 Sequencer
在一些 sequence 中,当前生成的数据需要依赖之前生成数据的响应结果。默认情况下,driver 和 sequencer 之间的 data item 是通过引用来复制的,这种方式下 driver 对 data item 做出的改变是对 sequencer 可见的。当 driver 和 sequencer 之间的 data item 是通过数值拷贝的,driver 需要将处理的响应返回到 sequencer,通过向 item_done() 添加可选的参数:
seq_item_port.item_done(rsp);
或者使用 put_response() 方法:
seq_item_port.put_response(rsp);
或者使用 uvm_driver 内建的 analysis port:
rsp_port.write(rsp);
注:在提供响应之前,响应的 sequence 和 transaction id 必须设置为与请求 transaction 的 id 匹配:rsp.set_id_info(req)
注:put_response() 是阻塞方法,所以 sequence 必须调用对应的 get_response(rsp)
3.5.5 使用基于 TLM 的 Driver
uvm_driver 中内建的 seq_item_port 是一个双向的端口,包含了标准的 TLM 方法 get() 和 peek() 用于从 sequencer 请求 item,同样包含了 put() 方法来提供响应。因此,其他并没有继承自 uvm_driver 的组件仍然可以连接到 sequencer 并与之通信。实际使用哪个方法由需要进行的操作确定。
// Pause sequencer operation while the driver operates on the transaction.
peek(req);
// Process req operation.
get(req);
// Allow sequencer to proceed immediately upon driver receiving transaction.
get(req);
// Process req operation.
- peek() 是阻塞方法
- get() 操作通知 sequencer 进行下一次 transaction,返回与 peek() 相同的 transaction,所以该 transaction 可以被忽略。
如果要通过 blocking_slave_port 提供响应,driver 需要调用:
seq_item_port.put(rsp);
该响应同样可以通过 analysis port 送出。
3.6 创建 Monitor
monitor 负责从总线提取信号信息,并将其翻译为事件、数据和状态信息,这些信息可以通过标准 TLM 接口提供给其他组件。monitor 不能依赖其他组件手机的状态信息,如 driver。但是 monitor 可能需要依靠请求中指定的 id 信息来正确设置响应中的 sequence 和 transaction id 信息。
monitor 的功能应该限制为基本的监控,可以包含可配置开启与关闭的协议检查功能以及覆盖率收集功能。额外的更高级别功能,如 scoreboard,应该分开实现。
如果需要验证一个抽象的模型或加速 pin-level 的功能,应该将信号级抽象、覆盖率、检查和 transaction 级活动分隔开。analysis port 可以用于 sub-monitor 组件之间的通信。
下面的例子为具有以下功能的monitor:
- 通过 virtual interface(xmi) 收集总线信息
- 收集到的数据用于覆盖率收集和检查
- 收集到的数据通过 analysis port(item_collected_port) 发送出去
class master_monitor extends uvm_monitor;
virtual bus_if xmi; // SystemVerilog virtual interface
bit checks_enable = 1; // Control checking in monitor and interface.
bit coverage_enable = 1; // Control coverage in monitor and interface.
uvm_analysis_port #(simple_item) item_collected_port;
event cov_transaction; // Events needed to trigger covergroups
protected simple_item trans_collected;
`uvm_component_utils_begin(master_monitor)
`uvm_field_int(checks_enable, UVM_ALL_ON)
`uvm_field_int(coverage_enable, UVM_ALL_ON)
`uvm_component_utils_end
covergroup cov_trans @cov_transaction;
option.per_instance = 1;
... // Coverage bins definition
endgroup : cov_trans
function new (string name, uvm_component parent);
super.new(name, parent);
cov_trans = new();
cov_trans.set_inst_name({get_full_name(), ".cov_trans"});
trans_collected = new();
item_collected_port = new("item_collected_port", this);
endfunction : new
virtual task run_phase(uvm_phase phase);
collect_transactions(); // collector task.
endtask : run
virtual protected task collect_transactions();
forever begin
@(posedge xmi.sig_clock);
...// Collect the data from the bus into trans_collected.
if (checks_enable)
perform_transfer_checks();
if (coverage_enable)
perform_transfer_coverage();
item_collected_port.write(trans_collected);
end
endtask : collect_transactions
virtual protected function void perform_transfer_coverage();
-> cov_transaction;
endfunction : perform_transfer_coverage
virtual protected function void perform_transfer_checks();
... // Perform data checks on trans_collected.
endfunction : perform_transfer_checks
endclass : master_monitor
数据的收集由 collect_transactions 任务完成,该任务在 run_phase 中的开始被调用。该任务是一个 forever 循环,只要总线上出现可获取的数据就进行收集。
收集到数据之后立即通过 analysis port 送给需要的组件。
覆盖率收集和检查功能可以被关闭,以加快仿真过程,可以通过 config 机制进行配置,例如:
uvm_config_int::set(this,“*.master0.monitor”, “checks_enable”, 0);
注:SystemVerilog 不允许在类里声明并行断言,所以协议检查也可以在 SystemVerilog interface 里完成
3.7 实例化组件
当在 UVM 中实例化组件时,应该使用 create() 方法:
class my_component extends uvm_component;
my_driver driver;
...
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
driver = my_driver::type_id::create("driver",this);
...
endfunction
endclass
type_id::create() 是一个类型指定的静态方法,返回 factory 中的实例化类型,其参数为实例化名称和父类组件句柄。factory 允许使用者从 my_driver 继承一个新的类来进行覆盖,因此不需要修改父类就可以使用新的类型。
例如,对于特定的 test,环境中可以通过以下方式对 driver 进行修改:
- 声明一个继承自原组件的新组件,并添加需要的功能
class new_driver extends my_driver;
... // Add more functionality here.
endclass: new_driver
- 在 test、environment 或 testbench 中覆盖原有的类型
virtual function void build_phase(uvm_phase phase);
set_type_override_by_type(my_driver::get_type());
super.build_phase(phase);
new_driver::get_type());
endfunction
factory 也支持将一个新的类型覆盖原有的类型。在上面的例子中,因为 new_driver 继承自 my_driver,TLM 接口是相同的,父类组件中定义的连接不需要改变。
3.8 创建 Agent
UVM 建议创建的 agent 提供协议指定的激励创建、检查和覆盖率收集功能。在基于总线的环境中,agent 通常包含一个 master、一个 slave 或者一个 arbiter 组件。
3.8.1 操作模式
agent 有两种基本的操作模式:
- active 模式,agent 在系统中实例化 driver 来驱动 DUT 信号。该模式需要 agent 实例化 driver 和 sequencer,同样需要实例化 monitor 来进行检查和收集覆盖率。
- passive 模式,agent 不实例化 driver 或 sequencer。只有 monitor 被实例化并配置,该模式用于只需要进行检查和收集覆盖率的情况。
下面例子中的 simple_agent 按照建议的方式实例化了一个 sequencer、一个 driver、一个 monitor。在 build_phase 中对 agent 中的子组件进行了创建和配置。
class simple_agent extends uvm_agent;
... // Constructor and UVM automation macros
uvm_sequencer #(simple_item) sequencer;
simple_driver driver;
simple_monitor monitor;
// Use build_phase to create agents's subcomponents.
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase)
monitor = simple_monitor::type_id::create("monitor",this);
if (is_active == UVM_ACTIVE) begin
// Build the sequencer and driver.
sequencer = uvm_sequencer#(simple_item)::type_id::create("sequencer",this);
driver = simple_driver::type_id::create("driver",this);
end
endfunction : build_phase
virtual function void connect_phase(uvm_phase phase);
if(is_active == UVM_ACTIVE) begin
driver.seq_item_port.connect(sequencer.seq_item_export);
end
endfunction : connect_phase
endclass : simple_agent
注:调用 super.build_phase() 使得 uvm_field_* 宏声明的 UVM fields 可以在 build_phase 自动配置
3.8.2 连接组件
connect_phase() 在 build_phase() 执行完成后开始,用于连接 agent 内的组件。
3.9 创建 Environment
3.9.1 Environment 类
Environment 类是可复用组件的顶层容器,实例化并配置所有的子组件。大部分的验证复用是对 environment 级别的复用,开发者实例化一个 environment 类并进行配置。例如,可能需要更改环境中的 master 和 slave 数量:
class ahb_env extends uvm_env;
int num_masters;
ahb_master_agent masters[];
`uvm_component_utils_begin(ahb_env)
`uvm_field_int(num_masters, UVM_ALL_ON)
`uvm_component_utils_end
virtual function void build_phase(phase);
string inst_name;
super.build_phase(phase);
if(num_masters ==0))
`uvm_fatal("NONUM",{"'num_masters' must be set";
masters = new[num_masters];
for(int i = 0; i < num_masters; i++) begin
$sformat(inst_name, "masters[%0d]", i);
masters[i] = ahb_master_agent::type_id::create(inst_name,this);
end
// Build slaves and other components.
endfunction
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new
endclass
3.10 Enabling Scenario Creation
环境使用者会需要创建许多test来验证一个DUT,由于验证组件的开发者对DUT的协议更加熟悉,因此组件开发者需要通过以下方式来加速test的编写:
- 在data item类放置控制域来简化test
- 为可重用的sequence创建一个库
环境使用者可以:
- 定义新的sequence生成新的transaction
- 定义新的sequence启动存在的sequence
- 覆盖data item中的默认控制域来改变driver和整体环境行为
- 使能新的行为或sequence
本节描述如何创建可重用的sequence库并进行使用。
3.10.1 声明自定义sequence
sequence由多个data item组成,验证组件可以包含一个basic sequence的库,test可以调用该sequence库。这种方式可以增强通用激励的复用性并减少test的长度。
创建一个自定义sequence:
- 从uvm_sequence基类继承一个sequence,指定request 和 response类型。下面的例子中仅指定了request类型,因此response类型与其相同
- 使用 `uvm_object_utils 宏注册sequence
- 如果sequence内需要访问sequencer的变量或方法,使用 `uvm_declare_p_sequencer 宏指定sequencer句柄
- 实现sequence的body任务,可以执行data item或启动其他sequence
class simple_seq_do extends uvm_sequence #(simple_item);
rand int count;
constraint c1 { count >0; count <50; }
// Constructor
function new(string name="simple_seq_do");
super.new(name);
endfunction
//Register with the factory
`uvm_object_utils(simple_seq_do)
// The body() task is the actual logic of the sequence.
virtual task body();
repeat(count)
// Example of using convenience macro to execute the item
`uvm_do(req)
endtask : body
endclass : simple_seq_do
class simple_sequencer extends uvm_sequencer #(simple_item) ;
// same parameter as simple_seq_do
`uvm_component_utils(simple_sequencer)
function new (string name=”simple_sequencer”,uvm_component parent) ;
super.new(name,parent) ;
endfunction
endclass
3.10.2 发送子sequence和sequence item
sequence中可以定义:
- 发送到DUT的data item流
- 在DUT接口实现的操作流
也可以在sequence中生成不连接到DUT接口的静态data item 列表
3.10.2.1 sequence 和 sequence item 基本flow
要发送sequence item,sequence的body任务需要create()该item,调用start_item(),确定是否对其随机化,最后调用finish_item()。
要发送一个subsequence,父类sequence的body中需要创建subsequence,可选择对其进行随机化,然后调用start()方法。如果subsequence有相关联的response,父类sequence可以调用get_response()方法。
下图为使用 uvm_do 宏实现的完成的sequence item和sequence flow。完整的flow包括对象的创建,类属性的初始化。sequence的pre_do(), mid_do(), post_do() callbacks以及对象的随机化发生在下图中不同的时间点。subsequence的pre_body()和post_body()方法不会被调用。
3.10.2.2 sequence和sequence item 宏
`uvm_do 系列宏可以在sequence中自动进行transaction item的创建、随机化和发送。uvm_do 宏直到driver准备好接收数据且pre_do方法调用之后才将item随机化。uvm_do_with宏可以对随机化添加额外的约束。
3.10.2.2.1 `uvm_do
此宏的参数为 uvm_sequence 或 uvm_sequence_item 类型,当driver向sequencer请求item时,item被随机化并提供给driver。
下面的sequence使用uvm_do宏发送了一个item:
class simple_seq_do extends uvm_sequence #(simple_item);
... // Constructor and UVM automation macros
// See Section 4.7.2
virtual task body();
`uvm_do(req)
endtask : body
endclass : simple_seq_do
也可以将sequence类型作为uvm_do的参数:
class simple_seq_sub_seqs extends uvm_sequence #(simple_item);
... // Constructor and UVM automation macros
// See Section 4.7.2
simple_seq_do seq_do;
virtual task body();
`uvm_do(seq_do)
endtask : body
endclass : simple_seq_sub_seqs
3.10.2.2.2 `uvm_do_with
与 uvm_do类似,uvm_do_with的第一个参数为sequence或sequence item类型,第二个参数为合法的约束。
class simple_seq_do_with extends uvm_sequence #(simple_item);
... // Constructor and UVM automation macros
// See Section 4.7.2
virtual task body();
`uvm_do_with(req, { req.addr == 16'h0120; req.data == 16'h0444; } )
`uvm_do_with(req, { req.addr == 16'h0124; req.data == 16'h0666; } )
endtask : body
endclass : simple_seq_do_with
如果约束只是设置一些特定的参数,该宏可以用一个自定义的任务替代:
class simple_seq_do_with extends uvm_sequence #(simple_item);
task do_rw(int addr, int data);
item= simple_item::type_id::create("item",,get_full_name());
item.addr.rand_mode(0);
item.data.rand_mode(0);
item.addr = addr;
item.data = data;
start_item(item);
randomize(item);
finish_item(item);
endtask
virtual task body();
repeat (num_trans)
do_rw($urandom(),$urandom());
endtask
...
endclass : simple_seq_do_with
3.10.3 在sequencer启动sequence
sequencer不会默认执行任何sequence,需要手动调用start() 方法。用户也可以通过config_db方法来指定一个sequence自动执行。
3.10.3.1 手动启动
可以在任意位置调用start()方法来启动并随机化一个sequence实例。
3.10.3.2 使用基于phase的自动启动方式
在每个run-time phase开始时,sequencer会检查是否有sequence自动启动。一般在test中设置自动启动sequence,例如下面的例子中指定了特定sequencer的main_phase,并创建一个sequence的实例,然后对其随机化并开始执行。
uvm_config_db#(uvm_object_wrapper)::set(this,
".ubus_example_tb0.ubus0.masters[0].sequencer.main_phase",
"default_sequence",
loop_read_modify_write_seq::type_id::get());
也可以启动某个sequence实例:
lrmw_seq = loop_read_modify_write_seq::type_id::create(“lrmw”,,get_full_name());
// set parameters in lrmw_seq, if desired
uvm_config_db#(uvm_sequence_base)::set(this,
".ubus_example_tb0.ubus0.masters[0].sequencer.main_phase",
"default_sequence", lrmw_seq);
通过创建特定的sequence实例,可以在启动之前做一些需要的操作,如设置参数或随机化。在进入指定的phase之后,sequence实例会被启动,sequencer不会对其进行随机化。
3.10.4 覆盖sequence item和sequence
要覆盖特定sequence或sequence item:
- 声明一个继承自特定基类的自定义sequence或sequence item
- 调用factory覆盖方法
// Affect all factory requests for type simple_item.
set_type_override_by_type(simple_item::get_type(), word_aligned_item::get_type());
// Affect requests for type simple_item only on a given sequencer.
set_inst_override_by_type("env0.agent0.sequencer.*", simple_item::get_type(), word_aligned_item::get_type());
// Alternatively, affect requests for type simple_item for all
// sequencers of a specific env.
set_inst_override_by_type("env0.*.sequencer.*", simple_item::get_type(), word_aligned_item::get_type());
3.11 仿真结束控制
UVM使用objection机制控制仿真结束。
三种方式:
- non-phase aware sequence
handle phase objection around sequence invocation,sequence itself is not phase aware
class test extends ovm_test;
task run_phase(uvm_phase phase);
phase.raise_objection(this);
seq.start(seqr);
phase.drop_objection(this);
endtask
endclass
- phase aware sequence(explicit)
在启动sequence之前传递 starting phase,由sequence控制objection
class test extends ovm_test;
task run_phase (uvm_phase phase);
seq.set_starting_phase(phase);
seq.start(seqr);
endtask
endclass
class seq extends uvm_sequence #(data_item);
task body();
uvm_phase p = get_starting_phase();
if(p) p.raise_objection(this);
//some critical logic
if(p) p.drop_objection(this);
endtask
endclass
- phase aware sequence(implicit)
在启动sequence之前传递starting phase,sequence内调用 set_automatic_phase_objection(1)
class test extends ovm_test;
task run_phase (uvm_phase phase);
seq.set_starting_phase(phase);
seq.start(seqr);
endtask
endclass
class seq extends uvm_sequence #(data_item);
function new(string name = "seq");
super.new(name);
set_automatic_phase_objection(1);
endfunction
task body();
// Sequence logic with no objection
// as it is already handled in the base class
endtask
endclass
注意,当使用 default_sequence 启动sequence时,不能使用第一种方式,只能使用后面两种方式。
3.12 实现检查和覆盖
3.12.1 在类里实现检查和覆盖
类的检查和覆盖应该在继承自uvm_monitor的类里实现,bus monitor在env中默认创建,如果使能了覆盖率收集和检查,bus monitor则会执行这些功能。
可以使用程序代码或者立即断言来实现检查。
Tips:使用可以用几行代码实现的立即断言进行简单检查,使用函数进行复杂检查。
注:类里不能实现并行断言
下面是一个简单断言检查的例子,检查transfer的大小是否为1,2,4,8:
function void ubus_master_monitor::check_transfer_size();
check_transfer_size : assert(trans_collected.size == 1 ||
trans_collected.size == 2 || trans_collected.size == 4 ||
trans_collected.size == 8) else begin
// Call DUT error: Invalid transfer size!
end
endfunction : check_transfer_size
下面的例子是用函数实现的检查:
function void ubus_master_monitor::check_transfer_data_size();
if (trans_collected.size != trans_collected.data.size())
// Call DUT error: Transfer size field / data size mismatch.
endfunction : check_transfer_data_size
在实现中应在正确的时刻执行这些检查,下面的例子为在monitor收集到transaction时立即进行检查:
function void ubus_master_monitor::perform_transfer_checks();
check_transfer_size();
check_transfer_data_size();
endfunction : perform_transfer_checks
功能覆盖率使用SystemVerilog covergroup实现:
// Transfer collected beat covergroup.
covergroup cov_trans_beat @cov_transaction_beat;
option.per_instance = 1;
beat_addr : coverpoint addr { option.auto_bin_max = 16; }
beat_dir : coverpoint trans_collected.read_write;
beat_data : coverpoint data { option.auto_bin_max = 8; }
beat_wait : coverpoint wait_state {
bins waits[] = { [0:9] };
bins others = { [10:$] }; }
beat_addrXdir : cross beat_addr, beat_dir;
beat_addrXdata : cross beat_addr, beat_data;
endgroup : cov_trans_beat
嵌入的covergroup在继承自uvm_monitor的类里定义并被显式采样。
// perform_transfer_coverage
virtual protected function void perform_transfer_coverage();
cov_trans.sample(); // another covergroup
for (int unsigned i = 0; i < trans_collected.size; i++) begin
addr = trans_collected.addr + i;
data = trans_collected.data[i];
wait_state = trans_collected.wait_state[i];
cov_trans_beat.sample();
end
endfunction : perform_transfer_coverage
systemverilog不能覆盖动态数组,因此需要单独访问每个元素进行覆盖。
3.12.2 在接口中实现检查和覆盖
接口检查用断言实现,用于检查协议的信号活动。与物理接口相关的断言放置在env的接口中,例如断言可以检查在传输有效时地址不能为x或z。
3.12.3 控制检查和覆盖
应该提供控制检查和覆盖率是否执行的方法,可以使用bit field来实现,该field可以通过config_db进行配置。
if (checks_enable)
perform_transfer_checks();
uvm_config_db#(int)::set(this,"masters[0].monitor", "checks_enable", 0);