UVM(三)—— Sequence机制

1. Sequenece机制基础

1.1 概念

控制和产生一系列的激励,并通过sequencer将激励发送给driver。
是一个产生和发送数据的过程,需要消耗仿真时间,要用在task phase中启动。
将激励的产生与驱动分离,提高了测试平台中组件的可重用性,使得事务的产生更加灵活。

1.2 原理

1.3 相关宏

  • uvm_do系列宏
    uvm_do系列的其他七个宏其实都是用uvm_do_on_pri_with宏来实现的。

  • uvm_create与uvm_send
    uvm_create:实例化transaction。
    uvm_send:将处理完的transaction发送出去。
    uvm_send_pri:将transaction交给sequencer时设定优先级。
  • uvm_rand_send系列宏
    前提:transaction已经被分配了空间。
    意义:如果一个transaction占用的内存比较大,那么很可能希望前后两次发送的transaction都使用同一块内存,只是其中的内容可以不同,这样比较节省内存。
  • start_item与finish_item
    必须要先实例化transaction后才可以调用这两个任务。
    可以在transaction实例化后、finish_item调用前对其进行随机化。
    指定transaction优先级,要在调用start_item和finish_item时都要加入优先级参数。不指定默认为-1。
  • pre_do、mid_do与post_do

    pre_do任务的参数用于表明uvm_do宏是在对一个transaction还是在对一个sequence进行操作。
    mid_do和post_do的两个参数是正在操作的sequence或者item的指针,但是其类型是uvm_sequence_item类型。通过cast可以转换成目标类型。
    意义:增加uvm_do系列宏的功能。

1.4 三种启动方式

(1)直接使用start方式启动sequence
(2)配置sequencer中的default_sequence(test中的build_phase完成)
(3)使用`uvm_do系列宏、`uvm_send系列宏、`uvm_rand_send系列宏启动sequence

当一个sequence启动后会自动执行sequence的body任务。
前两种方式除了执行body外,还会自动调用sequence的pre_body与post_body任务。第三种则不会。

1.5 sequencer仲裁算法

算法参数说明
SEQ_ARB_FIFO默认算法。先入先出,不考虑优先级。
SEQ_ARB_WEIGHTED优先执行高权值的sequence
SEQ_ARB_RANDOM随机执行sequence
SEQ_ARB_STRICT_FIFO按照优先级,同一优先级,先入先出
SEQ_ARB_STRICT_RANDOM按照优先级,同一优先级,随机
SEQ_ARB_USER用户自定义。需扩展uvm_sequencer并且重载user_priority_arbitration

1.6 sequence对sequencer的占用或失效

1.6.1 用lock()和grab()排他性占用sequencer
sequence的body()中 使用lock()…unlock()或grab()…ungrab(), 可以让在其中间发送(假设中间的sequence具有最高级)的所有transaction连续地获取sequencer的仲裁,从而连续地发送到driver而不被别的sequence打断。
两者区别——仲裁机制不一样:
(1)lock()会放在仲裁队列的末尾,sequencer将原来就在仲裁队列里的transaction发送完后才执行lock的transaction,一旦lock占用sequencer后,只有unlock后才释放。
(2)grab()会放在仲裁队列的最前面,grab操作比lock操作优先级更高,sequencer会立即执行grab的transaction,同样一旦grab占用sequencer后,只有ungrab后才释放。
(3)grab实时性强,是插队操作。unlock则不是。

1.6.2 sequence的is_revelant函数和wait_for_relevant任务
sequencer在仲裁时, 会查看sequence的is_relevant函数的返回结果, 为1说明此sequence有效并参加仲裁, 否则无效。
可以通过重载is_relevant函数来使sequence失效。
当sequencer将所有有效transaction发送完毕后,它会调用处于无效状态的sequence的wait_for_relevant任务。当wait_for_relevant返回后,sequencer还会继续调用is_relevant函数。
在wait_for_relevant任务中,user一定要将使sequence的无效状态的条件清除。否则系统会进入死循环。

1.7 在sequence中获得my_sequencer中变量的值

假设我们需要在 sequence 中使用 my_sequencer 类型中的 dmac和smac 变量。
两种方法:
(1)通过cast转换将m_sequencer转换成my_sequencer类型,并引用其中的dmac和smac。
(2)使用`uvm_declare_p_sequencer(my_sequencer),会自动将m_sequencer通过cast转换成p_sequencer。这个过程在pre_body()之前就完成了。因此在sequence中可以直接使用成员变量p_sequencer来引用dmac和smac。

2. Virtual Sequence

假设我们有如下图的需求,DUT有许多接口,不同接口需要不同的sequence产生激励,那我们要如何控制不同Sequence的执行顺序呢?

在此,我们引入virtual sequence的概念。Virtual sequence是 具有管理多个sequence,并控制它们之间关系功能 的sequence。

配套的,我们需要使用virtual sequencer来管理多个sequencer。

代码实现如下:

3. Sequence library(了解即可)

3.1 概念

– 一个sequence
– 包含了一组在其内部注册了的sequence类型
– 可通过对其进行配置,创建并执行一系列的sequence/sequence item

3.2 功能

– 其他sequence可以向sequence library注册
– 可根据配置产生并且执行已经在其内部注册过的sequence
– 具有多种内嵌的选择sequence的算法
– 支持用户自定义的sequence选择算法
– 注册方式的多样化

3.3 向sequence library中注册sequence

3.3.1 永久注册
(1)创建sequence library类。
// 从uvm_sequence派生时要指明此sequence library所产生的transaction类型
class simple_seq_library extends uvm_sequence_library#(my_transaction);
function new(string name= "simple_seq_library");
super.new(name);
// 在new函数中要调用init_sequence_library,否则其内部的候选sequence队列就是空的
init_sequence_library();
  endfunction
  `uvm_object_utils(simple_seq_library)
  // 要调用 uvm_sequence_library_utils注册
  `uvm_sequence_library_utils(simple_seq_library);
endclass


(2)创建sequence并注册进simple_seq_library中。
// 第一个是此sequence的名字,第二个是要加入的sequence library的名字
`uvm_add_to_seq_lib(seq0, simple_seq_library)


(3)将simple_seq_library配置给sequencer的default_sequence,以启动sequence lib。

3.3.2 临时添加——使用类配置
(1)创建sequence library类和sequence类。
(2)在test中根据实际需求将sequence添加到sequence library中,simple_seq_library为注册目标的sequence library。
simple_seq_library::add_typewide_sequence(my_sequence::get_type());
(3)将simple_seq_library配置给sequencer的default_sequence,以启动sequence lib。

3.3.3 在某一个单独的sequence library实例中添加sequence
(1)创建sequence library类和sequence类。
(2)在test中声明sequence library句柄,在build_phase中实例化sequence对象,向sequence library中注册sequence。
simple_seq_library m_seqlib;
virtual function void build_phase(uvm_phase phase);
    ...
    m_seqlib = simple_seq_library::type_id::create(“m_seqlib”, this);
    m_seqlib.add_sequence(my_sequence::get_type());
    ...
endfunction

(3)将simple_seq_library配置给sequencer的default_sequence,以启动sequence lib。

3.4 相关参数及配置

selection_mode参数可选配置如下:

参数配置两种方法如下:



代码示例仓库
https://gitee.com/mala_wong/uvm_sequence_test

参考资料:
• 白皮书《UVM实战》第6章 UVM中的sequence
• B站教程 《UVM基础》P32-P37 P52-P57
• CSDN博文
https://blog.csdn.net/weixin_43830240/article/details/111193810
https://blog.csdn.net/weixin_43830240/article/details/111220598?spm=1001.2014.3001.5501

UVM(二)——TLM通信

1. 什么是TLM

TLM是Transaction Level Modeling(事务级建模),为组件之间的通信建立专门的通信信道。

2. TLM原理

3. 常用TLM port/imp/export

portexportimport
putuvm_blocking_put_port#(T);
uvm_nonblocking_put_port#(T);
uvm_put_port#(T);
uvm_blocking_put_export#(T); uvm_nonblocking_put_export#(T);
uvm_put_export#(T);
uvm_blocking_put_imp#(T, IMP); uvm_nonblocking_put_imp#(T, IMP);
uvm_put_imp#(T, IMP);
getuvm_blocking_get_port#(T);
uvm_nonblocking_get_port#(T);
uvm_get_port#(T);
uvm_blocking_get_export#(T); uvm_nonblocking_get_export#(T);
uvm_get_export#(T);
uvm_blocking_get_imp#(T, IMP); uvm_nonblocking_get_imp#(T, IMP);
 uvm_get_imp#(T, IMP);
peekuvm_blocking_peek_port#(T);
uvm_nonblocking_peek_port#(T);
uvm_peek_port#(T);
uvm_blocking_peek_export#(T); uvm_nonblocking_peek_export#(T);
uvm_peek_export#(T);

uvm_blocking_peek_imp#(T, IMP); uvm_nonblocking_peek_imp#(T, IMP);
uvm_peek_imp#(T, IMP);
get peekuvm_blocking_get_peek_port#(T); uvm_nonblocking_get_peek_port#(T);
uvm_get_peek_port#(T);
uvm_blocking_get_peek_export#(T);  uvm_nonblocking_get_peek_export#(T);
uvm_get_peek_export#(T);
uvm_blocking_get_peek_imp#(T, IMP); uvm_nonblocking_get_peek_imp#(T, IMP);
uvm_get_peek_imp#(T, IMP);
transportuvm_blocking_transport_port#(REQ, RSP); uvm_nonblocking_transport_port#(REQ, RSP);
uvm_transport_port#(REQ, RSP);
uvm_blocking_transport_export#(REQ, RSP); uvm_nonblocking_transport_export#(REQ, RSP);
uvm_transport_export#(REQ, RSP);
uvm_blocking_transport_imp#(REQ, RSP, IMP); uvm_nonblocking_transport_imp#(REQ, RSP, IMP); uvm_transport_imp#(REQ, RSP, IMP);

每一个IMP要和一个component相对应。
按照控制流的优先级排序,UVM中 三种端口顺序为:PORT > EXPORT  > IMP。

4. 常用TLM端口使用方法

端口类型实现方法
uvm_put_port #(T)put()
try_put()
can_put()
uvm_blocking_put_port #(T)put()
uvm_nonblocking_put_port #(T)try_put()
can_put()
uvm_get_peek_portget()、try_get()、can_get()
peek()、try_peek()、can_peek()
uvm_blocking_get_peek_portget()
peek()
uvm_nonblocking_get_peek_porttry_get()、can_get()
try_peek()、can_peek()
uvm_transport_porttransport()
nb_transport()
uvm_blocking_transport_porttransport()
uvm_nonblocking_transport_portnb_transport()

上表中省略了get、peek port,与get_peek_port类似。

put() 是一个task,可能会阻塞当前进程直到transaction传输成功。
try_put() 是一个function,不会阻塞当前进程,不管transaction传输是否完成都会返回。
can_put() 是一个function,不会阻塞当前进程,不发生transaction传输,只是检验对方是否准备好了接受。
get()、peek()与put()类似;try_get()、try_peek()与try_put()类似;can_get()、can_peek()与can_put()类似。

对于analysis_port和analysis_export来说,没有阻塞和非阻塞的概念。只有一种操作:write。

从功能上来看 export和port的功能是一样的,通常export作为中间端口使用。
在import中重载实现表格中的对应方法,并通过发送方的 port名.方法 调用。
因此只有import才能作为连接关系的终点。

4. FIFO

peek端口与get相似,其数据流、控制流都相似,唯一的区别在于当get任务被调用时,FIFO内部缓存中会少一个transaction,而 peek被调用时,FIFO会把transaction复制一份发送出去,其内部缓存中的transaction数量并不会减少。

FIFO的类型有两种,一种是上图中的uvm_tlm_analysis_fifo,另外一种是uvm_tlm_fifo。这两者的唯一差别在于前者有一个 analysis_export端口,并且有一个write函数,而后者没有。

fifo的调试函数

函数名用途
used()用于查询FIFO缓存中有多少transaction。
is_empty()用于判断当前FIFO缓存是否为空。
is_full()用于判断当前FIFO缓存是否已经满了。
flush()用于清空FIFO的缓存。
new
fifo本质上是个component,前两个参数与componet new函数中参数一致。第三个参数用于设定FIFO缓存的上限,默认为1。若要把缓存设置为无限大,则将传入的size参数设置为0。
size()返回FIFO缓存的上限。

一般来说,使用 uvm_analysis_port、uvm_tlm_analysis_fifo、uvm_analysis_imp 即可。

UVM(一)—— field automation、config_db、factory机制

1. field_automation机制

1.1 介绍
利用`uvm_field_* 宏,将类中的成员变量注册到UVM中,UVM可自动为其实现compare、copy、print、pack/unpack等方法,不需要自己去定义。解决 逐字段对transaction进行操作 的问题,极大地简化了验证平台的搭建,提高了效率。

1.2 用法
使用uvm_object_utils_begin和uvm_object_utils_end来实现my_transaction的factory注册,在这两个宏中间,使用uvm_field宏注册所有字段。uvm_field系列宏随着transaction成员变量的不同而不同。

1.3 相关宏

简单的uvm_field系列宏
`define uvm_field_int(ARG,FLAG)
`define uvm_field_real(ARG,FLAG)
`define uvm_field_enum(T,ARG,FLAG)
`define uvm_field_object(ARG,FLAG)
`define uvm_field_event(ARG,FLAG)
`define uvm_field_string(ARG,FLAG)
动态数组:
`define uvm_field_array_enum(ARG,FLAG)
`define uvm_field_array_int(ARG,FLAG)
`define uvm_field_array_object(ARG,FLAG)
`define uvm_field_array_string(ARG,FLAG)
静态数组:
`define uvm_field_sarray_int(ARG,FLAG)
`define uvm_field_sarray_enum(ARG,FLAG)
`define uvm_field_sarray_object(ARG,FLAG)
`define uvm_field_sarray_string(ARG,FLAG)
队列:
`define uvm_field_queue_enum(ARG,FLAG)
`define uvm_field_queue_int(ARG,FLAG)
`define uvm_field_queue_object(ARG,FLAG)
`define uvm_field_queue_string(ARG,FLAG)
联合数组:(第一个类型是存储数据类型,第二个类型是索引类型)
`define uvm_field_aa_int_string(ARG, FLAG)
`define uvm_field_aa_string_string(ARG, FLAG)
`define uvm_field_aa_object_string(ARG, FLAG)
`define uvm_field_aa_int_int(ARG, FLAG)
`define uvm_field_aa_int_int_unsigned(ARG, FLAG)
`define uvm_field_aa_int_integer(ARG, FLAG)
`define uvm_field_aa_int_integer_unsigned(ARG, FLAG)
`define uvm_field_aa_int_byte(ARG, FLAG)
`define uvm_field_aa_int_byte_unsigned(ARG, FLAG)
`define uvm_field_aa_int_shortint(ARG, FLAG)
`define uvm_field_aa_int_shortint_unsigned(ARG, FLAG)
`define uvm_field_aa_int_longint(ARG, FLAG)
`define uvm_field_aa_int_longint_unsigned(ARG, FLAG)
`define uvm_field_aa_string_int(ARG, FLAG)
`define uvm_field_aa_object_int(ARG, FLAG)

2. config_db机制

2.1 介绍
用来传递参数,一般有以下三种应用场景:
(1)传递virtual interface到环境中。
(2)设置单一变量值,例如int、string、enum等。
(3)传递配置对象到环境。

2.2 用法
set函数是寄信,而get函数是收信。如在某个测试用例的build_phase中可以使用如下方式寄信:

uvm_config_db#(int)::set(this,"env.i_agt.drv","pre_num",100);

其中第一个和第二个参数联合起来组成目标路径,与此路径符合的目标才能收信。第一个参数必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。第三个参数表示一个记号,用以说明这个值是传给目标中的哪个成员的,第四个参数是要设置的值

在driver中的build_phase使用如下方式收信:

uvm_config_db#(int)::get(this,"","pre num",pre num);

get函数中的第一个参数和第二个参数联合起来组成路径。第一个参数也必须是一个uvm_component实例的指针,第二个参数是相对此实例的路径。一般的,如果第一个参数被设置为this,那么第二个参数可以是一个空的字符串。第三个参数就是set函数中的第三个参数,这两个参必须严格匹配,第四个参数则是要设置的变量

2.3 使用实例
2.3.1 interface传递
在实现接口传递的过程中需要注意:
(1)在传递过程中的类型应当为virtual interface,即实际接口的句柄。
(2)接口传递应该发生在run_test()之前。这保证了在进入build phase之前,virtual interface已经被传递到uvm_config_db中。

interface intf1; 
    logic enable=0; 
endinterface 

class compl extends uvm component;
    `uvm_component_utils(comp1)
    virtual intf1 vif;

    function void build_phase(uvm phase phase); 
        if(!uvm config db#(virtual intf1):: get(this,"","vif", vif)) begin 
            uvm_error("GETVIF","no virtual interface is assigned")
        end 
        `uvm_info("SETVAL",$sformatf("vif. enable is %b before set", vif. enable), UVM LOW)
        vif.enable=1;
        `uvm info("SETVAL",$sformatf("vif. enable is b after set", vif. enable), UVM LOW)
    endfunction 
endclass

class testl extends uvm_test; 
    `uvm_component_utils(test1)
    comp1(c1) 

endclass 

intf1 intf(); 
initial begin 
    uvm_config_db#(virtual intf1)::set(uvm root::get(),"uvm test top. cl","vif", intf); 
    run test("test1"); 
end

//输出结果:
UVM_INFO @ 0:reporter [RNTST] Running test test1...
UVM_INFO @ 0:uvm_test_top.cl [SETVAL] vif.enable is 0 before set 
UVM_INFO @ 0:uvm_test_top.cl [SETVAL] vif.enable is 1 after set

2.3.2 变量传递
在各个test中, 可以在build phase对底层组件的变量加以配置, 进而在环境例化之前完成配置, 使得环境可以按照预期运行。
代码与2.3.1类似。

2.3.3 object传递
在test配置中,需要配置的参数不只是数量多,而且可能还分属于不同的组件,如果每个变量单独传递,容易出错,还不能复用。因此我们可以将每个组件中的变量加以整合,首先放置到一个uvm_object中,再对中心化的配置对象进行传递,会更有利于整体环境的修改维护。

class configl extends uvm object;
    int val1=1; 
    int str1="null"; 
    uvm_object_uti1s(config1)
    ...
endclass 

class comp1 extends uvm_component;
    `uvm_component_uti1s(comp1)
    configl cfg; 
    ...
    function void build_phase(uvm phase phase); 
        uvm_object_tmp; 
        uvm_config_db#(uvm_object):: get(this,"","cfg", tmp); 
        void'($cast(cfg, tmp)); 
        uvm_info("SETVAL",$sformatf("cfg.vall is %d after get", cfg. val1), UVM_LOW)
        uvm_info("SETVAL",$sformatf("cfg.str1 is %s after get", cfg. str1), UVM_LOW)
    endfunction
endclass

class test1 extends uvm_test;
    `uvm component utils(test1)
    comp1 c1,c2;
    config1 cfgl,cfg2; 
    ...
    function void build_phase(uvm_phase phase); 
        cfg1=config1::type id::create("cfg1"); 
        cfg2=configl::type_id::create("cfg2"); 
        cfg1.val1=30; 
        Cfg1.strl="c1"; 
        Cfg2.val1=50; 
        cfg2.strl="c2"; 
        uvm_config_db#(uvm_object):set(this,"cl","cfg", cfg1); 
        uvm_config_db#(uvm object):set(this,"c2","cfg", cfg2); 
        c1=comp1::type id::create("c1", this);
        c2=comp1::type_id::create("c2", this); 
    endfunction 
endclass

3. factory机制

3.1 介绍
工厂模式的主要解决的问题是,将原来分布在各个地方的对象创建过程单独抽离出来,交给工厂类负责创建。其他地方想要使用对象直接找工厂(即调用工厂的方法)获取对象。
对象由工厂生产是利用工厂生产模具可灵活替代的好处,在不修改原有验证环境层次和验证包的同时,实现对环境内部组件类型或者对象的覆盖(override,或译成重载)

3.2 用法
3.2.1 定义->注册->构建 缺一不可
component 构建函数
function new (string name = " name", uvm_component parent = null)
创建对象
comp_type::type_id::create(string name,uvm_component parent);

object 构建函数
function new (string name = “name”)
创建对象
object_type::type_id::create(string name);

class comp1 extends uvm_component;//1.定义
    `uvm_component_utils(comp1)//2.注册
    //3.构建函数
    function new (string name = "comp1", uvm_component parent = null);
        super.new(name,parent);
    endfunction:new
endclass
class obj1 extends uvm_object;//1.定义
    `uvm_object_utils(obj1)//2.注册
    function new(string name = "obj1");//3.构建函数
        super.new(name);
    endfunction:new
endclass

//创建对象
comp1 c1,c2;
obj1 o1,o2;
initial begin
    c1 = new("c1");
    o1 = new("o1");
    //建议使用下面方法创建对象
    c2 = comp1::type_id::create("c2",null);
    o2 = obj1::type_id::create("o2");
end

3.2.2 覆盖方法
覆盖机制可以将其原来所属类型替换为另一个新类型
在覆盖之后,原本用来创建原型类型的请求,将由工厂来创建新的替换类型。
(1)无需修改原始代码,保证了原有代码的封装性
(2)新的替换类型必须与被替换的类型相兼容,否则稍后的句柄赋值将失败,所以使用继承。
(3)做顶层修改时,非常方便

使用create()来创建对象时:
(1)工厂会检查,是否有类型被覆盖,如果是,它会创建一个新类型的对象,如果不是,它会创建一个原有类型的对象

覆盖发生时,可以使用“类型覆盖”或者“实例覆盖”:
(1)类型覆盖(set_type_override())指,UVM层次结构下的所有原有类型都被覆盖类型所替换。
(2)实例覆盖(set_inst_override())指,某些位置中原有类型会被覆盖类型所替换