UVM学习笔记



1. 常见的uvm_component

  • uvm_driver: 所有的driver都要派生自uvm_driver。
  • uvm_monitor: 所有的monitor都要派生自uvm_monitor。
  • uvm_sequencer: 所有的sequencer都要派生自uvm_sequencer。
  • uvm_scoreboard: 一般的scoreboard都要派生自uvm_scoreboard。
  • reference model: reference model直接派生自uvm_component。
  • uvm_agent: 所有的env要派生自uvm_agent。
  • uvm_env: 所有的env要派生自uvm_env。
  • uvm_test: 所有的case要派生自uvm_test。

2. 常见的uvm_object

  • uvm_sequence_item: 所有的自定义的transaction要从uvm_sequence_item派生。
  • uvm_sequence: 所有的sequence要从uvm_sequence派生。
  • config: 所有的config一般直接从uvm_object派生。
  • uvm_reg_item: 派生自uvm_sequence_item。
  • uvm_reg_map,uvm_mem,uvm_reg_field,uvm_reg,uvm_reg_file,uvm_reg_block等与寄存器有关的众多类都是派生自uvm_object。
  • uvm_phase: 派生自uvm_object,其用处主要是控制uvm_component的行为,使得uvm_component平滑的在各个不同的phase之间一次运转。

3. override功能

假设自己定义一个my_driver,在跑80%的case的时候,driver是足够的,但是剩余的20%的case中,我们需要对my_driver的行为行为作出某些改动,这个时候可以用到override功能。使用override功能的第一步是要先从my_driver派生出一个类,把这个类的行为定义好:

class new_driver extends my_driver;
    ...
    `uvm_component_utils(new_driver)
endclass

之后, 再具体的case的build_phase中,调用override相关函数:

class case_x extends base_test;
    
    function void build_phase(uvm_phase phase);
        ...
        set_type_override_by_type(my_driver::get_type(),new_driver::get_type());
    endfunction
endclass

经过上述过程之后,这么跑的case_x的时候,系统中运行的my_driver就是new_driver类型的,其行为是new_driver的行为。不过这有个前提,那就是my_driver在agent中实例化的时候,要使用factory的方式实例化:

class my_agent extnds uvm_agent;
    my_driver drv;
    
    function void build_phase(uvm_phase phase);
    
        ...
        drv = my_driver::type_id::create("drv",this);
    endfunction 
endclass

假如不是使用上面的写法,而是使用drv=new(“drv”,this)的写法进行实例化,那么override功能是不能实现的。

4. phase

4.1 phase的分类

build_phase
connect_phase
end_ofelaboration_phase
run_phase
extract_phase
check_phase
report_phase
final_phase

uvm中的phase,按照其是否消耗仿真时间的特性,可以分为两大类,一类是function_phase,其实现是通过函数(function)来实现的;另外一类是run_phase等,它们是消耗仿真时间的,其实现是通过任务(task)来实现的。上述表格中run_phase是task phase,其他的都是function_phase。
注:所谓的仿真时间就是$time函数等到的时间,而运行时间是CPU的时间。直观上,我们觉得一个程序运行的快,就是CPU时间花费的少,即运行时间少。

4.2 phase的执行顺序

  • build_phase是自上而下执行的。build_phase是做实例化工作的,driver和monitor都是agent的成员变量,所以它们的实例化都是在agent的build_phase中执行。如果在agent的build_phase之前执行driver的build_phase,此时driver还根本没有实例化,所以调用driver.build_phase只会引发错误。
  • 在build_phase的实例化工作,仅仅指的是uvm_component及派生类的实例化,如果在其他phase实例化一个uvm_component的话,那么系统就会报错。如果是uvm_object的实例化,则可以在任何phase完成,当然也包括build_phase。
  • 除了build_phase之外,所有的不耗仿真的时间的phase(即function phase)都是自下而上执行的。如connect_phase ,先执行driver和monitor的connect_phase,再执行agent的connect_phase。
  • run_phase也是自伤而下的执行,更准确的说是自上而下的启动,同时在运行。
  • run_phase分成12个小的phase:
pre_reset_phase
reset_phase
post_reset_phase
pre_configure_phase
configure_phase
post_configure_phase
pre_main_phase
main_phase
post_main_phase
pre_shutdown_phase
shutdown_phase
post_shutdown_phase

这个12个小phase与run_phase完全相同,即自下而上的启动,同事运行
phase的跳转仅限于这12个动态phase之间相互跳转。
run_phase和12个phase之间是并列关系。
main_phase可以启动run_phase,但是run_phase不可以启动main_phase。

5. objection控制验证平台关闭

task main_phase(uvm_phase phase);
    super.main_phase(phase);
    phase.phase_done.set_drain_time(this,1000);
    phase.raise_objection(this);
        ......
    phase.drop_objection(this);
endtask

验证过程中,dut的输出相对于输出总会有一定的延迟,如果立刻停止UVM验证平台,可能一部分dut输出还没有结束,所以需要进行延迟结束停止平台。set_drain_time,用来定义在phase结束后延时大小。当UVM在某个task phase中检测到所有的objection被撤销后,接下来会检查有没有设置drain_time。如果没有设置,则马上进入下一个phase,否则延迟drain_time后再进入下一个phase。一个phase对应一个drain_time,并不是所有的phase共享一个drain_time。在没有设置的情况下,drain_time默认值是0。

6. transaction

定义transaction时,用如下方式以及field_automation机制来实现:

class mac_transaction extends uvm_sequence_item;
    rand bit[47:0] dmac;
    rand bit[47:0] smac;
    rand bit[15:0] eth_type;
    rand byte      pload[];
    rand bit[31:0] crc;
    
    `uvm_object_utils_begin(mac_transaction)
        `uvm_field_int(dmac,UVM_ALL_ON)
        `uvm_field_int(smac,UVM_ALL_ON)
        `uvm_field_int(eth_type,UVM_ALL_ON)
        `uvm_field_array_int(pload,UVM_ALL_ON)
        `uvm_field_int(crc,UVM_ALL_ON)
endclass

uvm_field_*系列宏共有如下几种:

namedescription
uvm_field_array_A表示动态数组,A表示动态数组中存放的内容类型
uvm_field_sarray_A表示静态数组,A表述此静态数组中存放的内容的类型
uvm_field_queue_A表示队列,A表示此队列存放的内容的类型
uvm_field_aa_A_B表示联合数组,其中B表示联合数组的索引类型,A表示联合数组中存放的内容的类型

7. sequence

7.1 sequence定义如下:

class my_sequence extends uvm_sequence #(mac_transaction);
    mac_transaction m_trans;
    
    extern function new(string name ="my_sequence");
    
    virtual task body();
        repeat(10) begin
            `uvm_do(m_trans);
        end
        #100;
    endtask
    
    `uvm_object_utils(my_sequence)
endclass

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

启动一个sequence:

my_sequence my_seq;
my_seq = my_sequence::type_id::create("my_seq");
my_seq.start(sequencer);

要启动一个sequence,第一步就是把这个sequence实例化,第二步就是调用sequence的start任务,调用时传入一个sequencer参数。
当sequence启动起来之后,这个sequence中的body就会自动运行。

7.2 uvm_do宏

一个transaction的产生:

task body();
    mac_transaction tr;
    tr = mac_transaction::type_id::create("tr");      \\第一步实例化
    start_item(tr);     //第二步调用start_item
    assert(tr.randomize());     //第三步执行randomize过程
    finish_item(tr);    //调用finish_item
endtask

上述四步,由于是牵扯到sequencer和driver的通信,因此这里有一个主动和被动的过程。当driver中使用seq_item_port.get_next_item主动请求一个item(transaction)时,sequencer才会要求sequence产生一个item(transaction);产生完成后,sequence就要等待driver把item(transaction)取走,这需要driver显式调用seq_item_port.item_done()。当此函数被调用后,finish_item才会返回,一个transaction的产生真正的完成。
上述四步中,只有第三步才会有一些差异,而另外三步则永远是固定不变的。可以使用uvm_do系列宏来简化transaction的过程。

//uvm_do的使用
task body();
    mac_transaction tr;
    `uvm_do(tr)
endtask
//uvm_do_with的使用
task body();
    mac_transaction tr;
    `uvm_do_with(tr,{tr.crc_err == 1;})
endtask

sequence可以作为uvm_do宏的参数

class crc_seq extends uvm_sequence#(mac_transaction);
    ......
    `uvm_object_utils(crc_seq)
endclass

class logn_seq extends uvm_sequence#(mac_transaction);
    ......
    `uvm_object_utils(long_seq)
endclass

class new_seq extends uvm_sequence#(mac_transaction);

    task body();
        crc_seq cseq;
        long_seq lseq;
        `uvm_do(cseq)
        `uvm_do(lseq)
    endtask
    
    `uvm_object_utils(new_seq)
endclass

7.3 virutal sequence

实现sequence之间同步的最好的方式就是使用virtual sequence,本身并不发送transaction,只是控制其他sequence,起到统一调度的作用。
首先需要一个virtualsequencer,里面包含指向其他实际sequencer的指针。

class vsequencer extends uvm_sequencer;
    cpu_sequencer cpu_sqr;
    mac_sequencer man_sqr;
    `uvm_component_utils(vsequencer)
endclass

在test中,例化vsqr,并把相应的sequencer赋值给vsqr中的指针。

class base_test extends uvm_test;
    env env_inst;
    vsequencer vsqr;
    function build_phase(uvm_phase phase);
        ...
        vsqr = vsequencer::type_id::create("vsqr",this);
        ...
    endfunction
    
    function connect_phase(uvm_phase phase);
        ...
        vsqr.cpu_sqr = env_inst.cpu_agent.cpu_sqr;
        vsqr.mac_sqr = env_inst.mac_agent.mac_sqr;
        ...
    endfunction
endclass

在virtual sequence里面使用uvm_do宏来发送transaction:

class vseq extends uvm_sequence;
    
    task body();
        cpu_seq cseq;
        mac_seq mseq;
        `uvm_do_on(cseq,p_sequencer.cpu_sqr)
        `uvm_do_on(mseq,p_sequencer.mac_sqr)
        `uvm_object_utils(vseq)
        `uvm_declare_p_sequencer(vsequencer)
    endtask
endclass

这里应该使用uvm_do_on宏来指定sequencer,而不能用uvm_do或者uvm_do_with宏。
在vseq的定义中,还出现了p_sequencer的变量,它是指向sequencer的指针,这个sequencer就是启动这个vseq的sequencer。要使用p_sequencer这个变量,那么就要使用宏显式的声明:
uvm_declare_p_sequencer(vsequencer)

8. config机制

8.1 config基本机制

config机制在UVM平台中都是成对出现的,例如在某个case的build_phase中如下设置:

uvm_confgi_db#(int)::set(this,"env.agent.driver","pre_num_max",100);

那么在driver的build_phase中这么做:

uvm_config_db#(int)::get(this,"pre_num_max",pre_num_max);

uvm_config_db中的set和get都是静态函数,所以可以用冒号的形式调用。

setget
第一个参数说明是哪个component对pre_num_max进行了设置,一般使用填写this第一个参数一般是this即可
第二个参数表示从调用uvm_config_db::set的地方看下去,要设置的变量所在的component的路径第二个参数填写空的字符串
第三个参数表示一个记号,用以说明这个值是传给driver中的哪个变量的第三个参数就是set中的第三个参数
第四个参数则是要设置的变量第四个参数则是要设置的变量
注:必须保持set和get中第三个参数一致

8.2 省略get的config

在一些特定的情况下,get是可以省略的:

class mac_driver extends uvm_driver#(mac_transaction);
    ...
    int pre_num;
    int pre_num_max;
    int pre_num_min;
    `uvm_component_utils_begin(mac_driver)
        `uvm_field_int(pre_num_min, UVM_ALL_ON)
        `uvm_field_int(pre_num_max, UVM_ALL_ON)
    `uvm_component_utils_end
    function void build_phase(uvm_phase phase);
        super.build_phase(uvm_phase phase);
        //uvm_config_db#(int)::get(this,"","pre_num_max",pre_num_max);
        //uvm_config_db#(int)::get(this,"","pre_num_min",pre_num_min);
    endfunction
endclass

只要在把mac_driver注册到factory时,顺便使用field_automaction机制把get的变量注册,那么当UVM执行到driver的super。build_phase(phase)这句话时,就会自动执行那两get的语句。这是相当便利的。在前面介绍field_automation的类(如transaction)和uvm_component的类(如uvm_driver)中,是都可以使用field_automation机制的。

8.3 多重set

  • UVM规定层次越高,那么它set的优先级越高。在整个UVM树中,uvm_test_top位置是高于env的,所以uvm_test_top中的set优先级高。因为层次越高,越接近用户。
  • 当处于同一层次时,则是时间优先。

8.4 聚合config变量

在验证过程,需要set的变量太多,可以将这些变量放到一个专门的类里面。

class iconfig extends uvm_object;
    rand int var1;
    ...
    rand int var1000;
    constraint default_cons{
        var1 = 7;
        ...
        var1000 = 999;
    }
    `uvm_object_utils_begin(iconfig)
        `uvm_field_int(var1,UVM_ALL_ON)
        ...
        `uvm_field_int(var1000,UVM_ALL_ON)
    `uvm_object_utils_end
endclass

经过上述定义之后,可以在base_test中这样写:

class base_test extends uvm_test;
    iconfig cfg;
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        cfg = iconfig::type_id::create("cfg");
        uvm_config_db#(iconfig)::set(this,"env.agent.driver","cfg",cfg);
        uvm_confgi_db#(iconfig)::set(this,"env.agent.monitor","cfg",cfg);
        ...
    endfunction
endclass

实时改变config值
有时候,可能当DUT运转到某一时刻时,需要改变验证平台的某些配置参数,这种情况可以通过virtual sequence的方式实现。

class vsequencer extends uvm_sequencer;
    iconfig cfg;
    ...
enclass

class base_test extends uvm_test;
    iconfig cfg;
    vsequencer vsqr;
    
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        cfg = iconfgi::type_id::create("cfg");
        vsqr = vsequencer::type_id::create("vsqr",this);
        vsqr.cfg = this.cfg;
        ...
    endfunction
endclass

class vseq extends uvm_sequence;
    `uvm_object_utils(vseq)
    `uvm_declare_p_sequencer(vsequencer)
    
    task body();
        ...//send some transaction
        p_sequencer.cfg.pre_num_max = 99;
        ...//send other transaction
    endtask
enclass

通过这种方法。可以在sequence中实时的改变验证平台的配置参数。

9. UVM中的各种port

9.1 TLM级别通信

  • TLM:Transaction Level Modeling的缩写
  • TLM级别的通信中的几个常用术语:
  • put操作:发起者A把一个transaction发送B。A称为“发起者”(PORT),B称为“目标”(EXPORT)。数据流是A->B。
  • get操作:A向B索取一个transaction。A依然是“发起者”,B依然是“目标”
  • PORT和EXPORT体现的是控制流而不是数据流。无论get还是put,其发起者都是PORT端口,而不是EXPORT。作为一个EXPORT来说,只能被动的接收PORT的命令。put和get操作只是数据流不一样。
  • UVM中常见的port
  • PORT
    uvm_blocking_put_port#(T);
    uvm_noblocking_put_port#(T);
    uvm_put_port#(T);
    uvm_blocking_get_port#(T);
    uvm_noblocking_get_port#(T);
    uvm_get_port#(T);
    uvm_blocking_transport_port#(REQ,ESP);
    uvm_noblocking_transport_port#(REQ,REP);
    uvm_transport_port#(REQ,RSP);
    前6个定义中的参数就是这个PORT中的数据流类型,而后3个定义中参数则表示transport操作(request-response操作)中发起请求时传输的数据类型和返回的数据类型。
  • EXPORT
    uvm_blocking_put_export#(T);
    uvm_noblocking_put_export#(T);
    uvm_put_export#(T);
    uvm_blocking_get_export#(T);
    uvm_noblocking_get_export#(T);
    uvm_get_export#(T);
    uvm_blocking_transport_export#(REQ,ESP);
    uvm_noblocking_transport_export#(REQ,REP);
    uvm_transport_export#(REQ,RSP);
    前6个定义中的参数就是这个EXPORT中的数据流类型,而后3个定义中参数则表示transport操作(request-response操作)中发起请求时传输的数据类型和返回的数据类型。
  • IMP
    uvm_blocking_put_imp#(T,IMP);
    uvm_nonblocking_put_imp#(T,IMP);
    uvm_put_imp#(T,IMP);
    uvm_blocking_get_imp#(T,IMP);
    uvm_nonblocking_get_imp(T,IMP);
    uvm_get_imp#(T,IMP);
    uvm_blocking_transport_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP);
    uvm_nonblocking_transport_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP);
    uvm_transport_imp#(REQ,RSP,IMP,REQ_IMP,RSP_IMP);
    前六个定义中的第一个T是这个IMP传输的数据类型。第二个参数IMP指的是实现这个接口的一个component。
    注:一定使用IMP来终结连接关系。PORT和EXPORT都不能作为连接关系的终点。

9.2 UVM中的analysis port和analysis export

analysis port 和analysis export与port和export类似,主要区别是:
第一,默认情况下,一个analysis port(analysis export)可以连接多个IMP,也就是说,analysis port(analysis export)与IMP之间的通信是一种一对多的通信,而PORT和EXPORT与IMP的通信是一种一对一的通信。analysis port(analysis export)更像是一个广播。
第二,作为PORT和EXPORT,有put,get,transport操作,虽然如前面所示,一个PORT要么是put_port,要么是get_port,要么是transport_port,不可能是三者兼有,但是毕竟是有这三种操作。但是对于analysis port(analysis export)来说,它只有一种操作write。write的意思就是广播一下,剩下的事情就与他无关了。
第三,作为PORT和EXPORT,都有阻塞和非阻塞的区分。响应的put,get,transport操作也分成了阻塞和非阻塞的。但是对于analysis port和analysis export来说,没有阻塞和非阻塞的概念。因为本身就是广播,不必等待与其相连的其他port的响应。所以不存在阻塞和非阻塞。

9.3用analysis实现monitor和scoreboard的通信

用uvm_analysis_port实现monitor和scoreboard的通信,这种方式是最常见。
在monitor中,需要定义一个analysis port:

class monitor extends uvm_monitor;
    uvm_analysis_port#(mac_transaction) ap;
    task main_phase(uvm_phase phase);
        super.main_phase(phase);
        mac_transaction tr;
        ...
        ap.write(tr);
        ...
    endtask
endclass

在scoreboard中定义一个名字位write的task。

class scoreboard extends uvm_scoreboard;
    uvm_analysis_imp#(mac_transaction,scoreboard) scb_imp;
    task write(mac_transaction tr);
        //do something on tr
    endtask
endclass

在agent中,定义一个analysis port,并把其指向monitor的ap:

class agent extends uvm_agent;
    uvm_analysis_port#(mac_transaction) ap;
    ...
    function void connect_phase(uvm_phase phase);
        super.connect(phase);
        this.ap = monitor.ap;
    endfuction
endclass

env的connnect_phase如下:

function void env::connect_phase(uvm_phase  phase);
    super.connect(phase);
    agent.ap.connect(scoreboard.scb.imp);
endfunction

9.4 有多个uvm_analysis_imp存在的情况

上面的monitor和scoreboard之间的通信,采用一个analysis port和一个analysis imp相连的方式实现的。对于一个analysis imp来说,必须在其实例化的uvm_component定义一个write的task。在上面的例子中,scoreboard只接收一路数据,但在实际情况中,scoreboard除了接收monitor的数据之外,还有接收reference model的数据。响应的scoreboard就要再添加一个uvm_analysis_imp的IMP,如model_imp。此时问题就出现了,这个新的IMP也要有一个write任务与其对应,因为很明显,我们对接收到的两路数据处理是不一样的。但是write只有一个,怎么办?
UVM考虑到了这种情况,采用如下的方式处理:

`uvm_analysis_imp_decl(_monitor)
`uvm_analysis_imp_decl(_model)
class scoreboard extens uvm_scoreboard;
    uvm_anaslysis_imp_monitor#(mac_transaction,scoreboard) monitor_imp;
    uvm_analysis_imp_model#(mac_transaction,scoreboard) model_imp;
    task write_monitor(mac_transaction tr);
        //do something on tr
    endtask
    task write_model(mac_transaciton tr);
        //do something on tr
    endtask
endclass

通过宏uvm_analysis_imp_decl,声明了两个后缀_monitor和_model。UVM会根据这两个后缀内建两个新的imp:uvm_analysis_imp_moniort和uvm_analysis_imp_model。当与uvm_analysis_imp_monitor相连接的analysis port执行write任务时,会自动调用write_monitor任务,而与uvm_analysis_imp_model相连的则会自动调用write_model任务。所以,只要把后缀声明了,把write后面添加上相应的后缀就可以正常工作了。

9.5 用fifo实现monitor和scoreboard

上节中要声明两个后缀,然后再写响应的task,这种方法看起相当的麻烦。那么有没有简单的方法呢?另外上面的monitor和scoreboard的通信,monitor占据主动地位,而scoreboard只能被动接收。那么有没有方法也让scoreboard实现主动的接收呢?这两个问题的答案是肯定的,那就是使用fifo来实现monitor和scoreboard的通信。
在agent和scoreboard之间添加一个uvm_analysis_fifo,相应的,scoreboard里面的端口要改成这样:

class scoreboard extends uvm_scoreboard;
    uvm_blockint_get_port#(mac_transaction) get_port;
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
    endfunction
    task main_phase(uvm_phase phase);
        mac_transaction tr;
        while(1) begin
            get_port.get(tr);
            //do something about tr
        end
    endtask
endclass

在env里面应该这样连接:

class env extends uvm_env;
    uvm_tlm_analysis_fifo#(mac_transaction) agent_scb_fifo;
    ...
    function void build_phase(uvm_phase phase);
        super.build_phase(uvm_phase phase);
        agent_scb_fifo = new("agent_scb_fifo",this);
        ...
    endfunction
    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        agent.ap.connect(agent_scb_fifo.analysis_export):
        scoreboard.get_port.connect(agent_scb_fifo.blocking_get_export);
        ...
    endfunction
endclass

而monitor和agent里面的相关代码跟上节中的完全相同。可以看到,这样连接之后,不必在scoreboard中再写一个名字位write的任务了。scoreboard可以按照自己的节奏工作,而不必按照monitor的节奏来。

10. 静态函数

在systemverilog中,函数分为两类,一类是静态的,一类是非静态的。
定义一个非静态函数:

class A;
    function void non_static_function();
        ...
    endfunction
endclass
//对于一个非静态函数,只有当A实例化之后才能被调用:
class B;
    A a;
    funciton void test();
        a = new();
        a.non_static_function();
    endfunction
endclass

而对于静态函数,并不需要一个类实例化就能调用,而且其调用的方式与非静态函数不一样。非静态函数是用一个点号(.)来调用,而非静态函数是用双冒号来调用(::):

class C;
    static function void static_function();
        ...
    endfunction
endclass
class D;
    function void test();
        C::static_function();
    endfunction
endclass

参考资料:UVM1.1应用指南及源码分析

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值