Post

UVM

Study about UVM

API

  • new(string name = “”, uvm_component parent = null)

    这里的name是字符串类型,为什么这里面需要有name以及parent,这是因为是uvm_component的要求,这里的new继承自uvm_component

  • uvm_info(“my_driver”,”data is drived”,UVM_LOW)

    这里的uvm_info功能类似于display不过功能更加强大。”my_driver” - 用于打印归类 “data is drived” - 是打印的信息, UVM_LOW是打印的冗余级别,有些信息非常关键可以设置为UVM_LOW,有些信息可有可无可以设置为UVM_HIGH,介于两者之间的就可以设置为UVM_MEDIUM,UVM默认只显示UVM_MEDIUM和UVM_LOW的信息

  • `uvm_fatal(“my_driver”, “virtual interface must be set for vif!!!”)

    `uvm_fatal在uvm中行使着警告的作用,在触发uvm_fatal后会直接结束仿真

  • `uvm_component_utils(my_driver)

    工厂机制的实现被集中在这个宏之中,这个宏能做的事情非常多,其中一件事情就是把my_driver登记在UVM内部的一张表中,所有派生自uvm_component及其派生类都应使用`uvm_component_utils注册

  • run_test(“my_driver”)

    创建一个my_driver的实例,并调用其中main_phase

  • raise_objection(this)

    开启仿真平台,raise_objection必须在第一个消耗仿真时间的语句之前

  • drop_objection(this)

    关闭仿真平台,类似于$finish的作用

  • uvm_config_db#(virtual my_if)::set(null,”uvm_test_top”,”vif”,input_if)
  • uvm_config_db#(virtual my_if)::get(this,””,”vif”,vif)

uvm_config_db中set和get主要行使着类似寄信和收信的作用,其中virtual my_if是寄信的类型,null是绝对路径,“uvm_test_top”是相对路径,“vif”是寄信的名称,“input_if”是寄信的内容

  • type_name::type_id::create(name,parent)

通过这个函数对于组件进行实例化,只有factory注册过的类才能用这种方式进行实例化,只有使用这种实例化的方法才能在后面使用factory机制中最为强大的重载功能

  • uvm_tim_analysis_fifo #(my_transaction) agt_mdl_fifo

初始化transaction级的fifo,用于组件间通信

  • uvm_analysis_port#(my_transaction) ap

创建写入端口用于写入数据

  • uvm_blocking_get_port#(my_transaction)

创建接收端口用于接收数据

  • uvm_object_utils_begin(my_transaction)与uvm_object_utils_end(my_transaction)

用于实现my_transaction的factory注册

  • uvm_field_int

用于实现UVM的field_automation机制

  • `uvm_do(m_trans)

其主要作用:1.创建一个my_transaction的实例m_trans;2.将其随机化;3.最终将其送给sequencer。

  • drv.seq_item_port.connect(sqr.seq_item_export)

建立driver和sequencer的连接

  • seq.start(i_agt.sqr)

在env中启动sequence,向sequencer发出请求

基础知识

UVM_tree

objection机制

UVM中通过objection机制控制验证平台的关闭

field_automation 机制

通过一些UVM的宏可以自动实现print,compare,copy等函数

phase

main_phase

在其中进行一些主要的操作,消耗仿真时间,是一个task型的phase

build_phase

build_phase 是UVM中内建的一个phase,当UVM启动后,会自动执行build_phase。build_phase在new函数之后main_phase之前执行,在build_phase中主要通过config_db的get和set传递一些数据。一般在build_phase中会调用super.build_phase,这是因为其父类的build_phase执行了一些必要的操作这里必须要显示调用并执行他。build_phase与main_phase不同的是,他是一个函数型的phase不需要消耗仿真时间

build_phase在UVM中的执行顺序一般是从树根到树叶

connect_phase

connect_phase 与 build_phase类似,其执行的时间是在build_phase执行之后执行,用于组件端口的连接。

与build_phase不同的是,connect_phase的执行顺序是由树叶到树根

transaction

传统的testbench的操作一般都是信号级的,但是在uvm中信息的传递是基于transaction,即事务级。在uvm中所有transaction都是从uvm_sequence_item派生而来,这里没有用uvm_component_utils注册factory,而使用uvm_object_utils注册factory,因为其类的祖先是object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class my_transaction extends uvm_sequence_item;

    rand bit [47:0] dmac;
    rand bit [47:0] smac;
    rand bit [15:0] ether_type;
    rand byte pload[];
    rand bit [31:0] crc;

    constraint pload_cons(
        pload.size >= 46;
        pload.size <= 1500;
    )
function bit[31:0] calc_crc();
    return 32'h0;
endfunction

function void post_randomize();
    crc = calc_crc;
endfunction

`uvm_object_utils(my_transaction)

function new(string name = "my_transaction");
    super.new(name);
endfunction

uvm_env

为了方便对于各个组件进行实例化,故引入了env这个容器类,在其中通过type_name::type_id::create这种方式对于组件进行实例化(一般在build_phase中进行)。

agent

因为driver和monitor处理的是同一种协议,在同一套既定的规则下做着不同的事情,由于二者的这种相似性,UVM通常将二者封装在一起,成为一个agent,因此不同的agent就代表了不同的协议。

is_active是uvm_agent的变量,其默认值是UVM_ACTIVE代表需要实例化driver的,若不需要driver的情况下可以设置为UVM_PASSIVE。

一般可以通过uvm_config_db#(uvm_active_passive_enum)::set(this, "i_agt", "is_active", UVM_ACTIVE)进行传递

reference model

reference model用于完成和dut一样的功能,reference model的输出被scoreboard获取用于与dut作比较。ref model 与 i_agt 的通信是值得注意的,在UVM中经常使用TLM(Transaction Level Model)来进行组件间通信,在UVM的transaction级别的通信中,数据发送的方式有很多种,其中一种是使用uvm_analysis_port。UVM中接受数据的方法也有很多种,其中一种是使用uvm_blocking_get_port。这也是一个参数化的类,其中传递的transaction的类型,在my_model进行声明,build_phase中进行初始化,main_phase中使用port.get进行数据传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 在monitor中声明此变量
 uvm_analysis_port#(my_transaction) ap;
//2. 在build_phase阶段对于uvm_analysis_port进行实例化
function build_phase
 ap = new("ap",ap);
endfunction
// 3. 在main_phase中对于数据进行收集与传递
task main_phase
my_transaction tr
    while(1)begin
        tr = new("tr");
        collect_one_pkt(tr);
        ap.write(tr);
    end
endtask

在my_monitor和my_model之间实现了各自的端口后,通信的功能并没有实现,还需要在my_env中使用fifo将两个端口联系在一起,fifo的类型是uvm_tim_analysis_fifo,其本质是一个参数化的类,其中参数存储的是transaction类型。

1
2
    uvm_tim_analysis_fifo #(my_transaction) agt_mdl_fifo;
    agt_mdl_fifo = new("agt_mdl_fifo",this);

在connect_phase将两者相连

1
2
3
4
5
function void my_env::connect_phase(uvm_phase phase)
    super.connect_phase(phase);
    i_agt.ap.connect(agt_mdl_fifo.analysis_export);
    mdl.port.connect(agt_mdl_fifo.blocking_get_export);
endfunction

scoreboard

scoreboard的作用收集dut的数据和ref model的数据进行比较,一般通过fork建立起两个线程,一个线程获取ref model的数据并将其push到expect_queue中去,另一个线程从o_agt获取数据并将获取到的数据与expect_queue中的数据进行比较。

sequencer

一般transaction激励都是由sequencer产生并由driver驱动,如果说sequencer是手枪,那么sequence就是弹夹用来为sequencer提供子弹。sequence与sequencer的区别是sequencer是一个uvm_component,而sequence是一个uvm_object,与transaction一样,它也有生命周期,不过它的生命周期略长于transaction,需要等所有transaction发射完毕,sequence的生命才会结束。

一个典型的sequence如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class my_sequence extends uvm_sequence #(my_transaction);
    my_transaction my_trans;

    function new(string name = "my sequence")
        super.new(name);
    endfunction

    virtual task body();
        repeat(10)begin
            `uvm_do(my_trans);
        end
        #1000
    endtask

    `uvm_object_utils(my_sequence);

一个sequence向sequencer发送transaction前,需要向sequencer发送一个请求,sequencer把请求放在一个仲裁队列中,他需要完成两件事,1.检测是否有sequence发起transaction请求 2.检测driver是否有申请transaction。

随之而来的问题是driver如何向sequencer发起请求,在uvm_driver中有成员变量seq_item_port,而在uvm_sequencer中有成员变量seq_item_export,这两者之间可以建立一个“通道”。

接下来的问题是sequence如何向sequencer送出transaction呢?一般只需要在env的main_phase启动sequence即可,当sequence里的子弹打完即可关闭仿真平台

1
2
3
4
5
6
7
task my_env::main_phase(uvm_phase phase);
    my_sequence seq;    
    phase.raise_objection(this);
    seq = my_sequence::type_id::create("seq");
    seq.start(i_agt.sqr);
    phase.dtop_objection(this);
endtask

前面是在env中手动启动sequense,但是在实际应用中更多还是通过default_sequence的方式启动sequence,具体方式如下,其中i_agt.sqr.main_phase可以确定在哪个phase启动sequence,第三个参数和第四个参数为UVM默认。

1
2
3
4
5
virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    uvm_config_db#(uvm_object_wrapper)::set(this,"i_agt.sqr.main_phase","default_sequence",my_sequence::type_id::get())

前文中启动sequence时需要raise objectiondrop objection,在default_sequence中通过seq.starting_phase = phaseseq.start(this)实现,然后在sequence的main_phase中通过starting_phase.raise_objection(this)starting_phase.drop_objection(this)使用objection机制

base_test

通常来说树根是一个基于uvm_test派生的类,其主要的作用是实例化my_env,并设置default_sequence以及整个验证平台的超时退出时间,并在report_phase阶段打印UVM_ERROR的信息,其在main_phase结束之后执行,对于不同的测试用例可以通过run_test(case0)run_test(case1)来实现,也可以使用UVM_TEST_NAME通过命令行传入。下图是UVM的启动流程,

启动流程

一些细节

UVM打印信息的设置

  • set_report_verbosity_level() 设置打印信息冗余度阈值以及get_report_verbosity_level()得到冗余度阈值,若涉及层次调用则需要在connect_phase及之后phase进行调用,若不涉及层次调用则可以在connect_phase之前进行调用。
  • set_report_verbosity_level_hier() 则是设置在某个ID之下的所有子类的冗余度阈值
  • set_report_id_verbosity() 对于不同ID的冗余度阈值进行设置,ID是第一个UVM_INFO的第一个参数
  • +UVM_VERBOSITY=UVM_HIGH 可以通过命令行对于整个环境的冗余度进行设置
  • set_report_severity_override(UVM_WARNING, UVM_ERROR) 对于信息严重性进行重载,将driver中的所有UVM_WARNING重载后UVM_ERROR
  • set_report_max_quit_count(5) ERROR达到某一数量结束仿真,不必等到所有objection被撤销,get_max_quit_count()可以查询当前退出阈值

  • set_report_severity_action(UVM_WARNING, UVM_DISPLAY|UVM_COUNT)可以将警告加入计数,第二个参数中可以使用UVM_STOP来加入断点调试

寄信的优先级

在UVM中通常使用uvm_config_db::set以及uvm_config_db::get来进行跨层次组件的变量传递,若有多个组件向同一个组件发送邮件,其优先级是越接近根节点的优先级越高。若优先级相同则比较寄信的时间,以最后一个为准。

uvm_config_db对于通配符是支持的,所以对于下面两个代码可以合并为一句,但是不建议使用通配符。

1
2
3
4
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv","vif",input_if);
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.mon","vif",input_if);

uvm_config_db#(virtual my_if)::set(null, "*.i_agt.*","vif",input_if);

由于uvm_config_db的第二个参数是字符串类型,即使路径设置错误也不会报错,只会不调用,可以用check_config_usage()检测此函数调用时,有哪些参数设置过,哪些参数没有设置过,config_db的set和get语句一般在build_phase中实现,所以check_config_usage()可以放在connect_phase阶段

对于类而言要先定义后实例化,这两部分缺一不可

1
2
3
4
5
class A;
endclass
A a_inst // 定义    
a_inst = new() // 实例化

This post is licensed under CC BY 4.0 by the author.