0
点赞
收藏
分享

微信扫一扫

uvm_user_guide_1.2 -- ch3 开发可重用的验证组件

ZGtheGreat 2022-04-07 阅读 73
开发语言

3. 开发可重用的验证组件

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机制控制仿真结束。

三种方式:

  1. 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
  1. 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
  1. 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);
举报

相关推荐

0 条评论