3

UVM技能点(一)——sequence介绍

 1 year ago
source link: https://www.eefocus.com/fpga/518907
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文版权归本公众号所有。

sequence介绍

 熟悉UVM的朋友应该都知道,sequence的作用是将测试数据的产生从driver中分离出来,使得driver能专注于驱动测试数据的功能。在不同的测试用例中,将不同的sequence设置成sequencer的main_phase的default_sequence,当sequencer执行到main_phase时,发现有default_sequence,那么它就会启动这个sequence。之后sequencer将启动的sequence产生的测试数据交给driver,再由driver驱动进入DUT完成仿真。

当完成一个sequence的定义后,就可以使用start任务将其启动,即在my_case的main_phase中创建自定义的sequence对应的实例,然后调用sequence.start(sequencer)来启动。除了直接启动之外,还可以使用default_sequence启动。一种是通过在my_case的build_phase使用uvm_config_db设置default_sequence给sequencer,另一种方式是先实例化要启动的sequence,之后再通过default_sequence启动。两者的区别就在于config_db::set的第四个参数,即要设置的值的获取方式不同,前者通过type_id::get获取,后者则直接用实例化后的变量即可。当一个sequence启动后会自动执行sequence的body任务。除body外,它还会调用sequence的pre_body和post_body任务。

sequence中的仲裁任务

UVM支持同一时刻在一个sequencer上启动多个sequence,则此时sequencer起到仲裁的作用,即决定使用哪个sequence产生的测试用例。sequencer是根据transaction的优先级来进行仲裁的,通常来说,优先级越高越容易被选中。使用uvm_do和uvm_do_with宏时产生的transaction为默认优先级,即-1,可以通过uvm_do_pri及uvm_do_pri_with来改变其优先级。uvm_do_pri(uvm_do_pri_with同理)第二个参数为优先级,用户可以自由指定任意大于-1的整数,数字越大,优先级越高。sequencer的仲裁算法有很多种:

SEQ_ARB_FIFO,SEQ_ARB_WEIGHTED,SEQ_ARB_RANDOM,SEQ_ARB_STRICT_FIFO,SEQ_ARB_STRICT_RANDOM,SEQ_ARB_USER

若想使得优先级设置起作用,应该设置仲裁算法为SEQ_ARB_STRICT_FIFO或SEQ_ARB_STRICT_RANDOM。具体可在测试用例的main_phase中对sequencer进行设置。

此外,除transaction有优先级外,sequence本身也有优先级概念,不过其本质还是设置该sequence产生的transaction的优先级。sequence的优先级可在启动sequence时指定,其中第三个参数即为优先级,如

seq0.start(env.i_agt.sqr,null,100);

除了设置优先级外,sequencer还可以通过sequence的lock操作来改变其行为。所谓lock可以理解为一个发送transaction的请求,当该请求前面的发送请求都被仲裁执行完毕,sequencer就开始响应lock请求,之后会连续发送发出该请求的sequence的transaction,直到unlock操作被调用。lock操作在sequence的body中被调用,当两个sequence都试图调用lock时,先获得所有权的sequence在执行完毕后才会将所有权交给另一个sequence。

在lock之上,还有优先级更高的grab操作,不同于lock操作在被调用后要等待仲裁队列里的请求执行完毕才会被执行,grab操作只要一发出立马就会被响应,即“插队”到仲裁队列最前面——特殊情况是当其他sequence的lock操作正在执行中时,grab会等该lock操作执行完毕再执行。grab操作同样要ungrab来释放所有权。当两个sequence都试图使用grab操作时,与同时调用lock操作的判断规则一致。

最后,UVM还可以设置sequence,使其在一段时间内失效不参与仲裁,这个功能可以通过重载sequence的is_relevant函数实现。当此函数返回1说明sequence有效,否则无效。sequence还有个函数wait_for_relevant也与有效性有关。wait_for_relevant在sequencer发现其启动的所有sequence都无效时被调用,此时sequencer会等待sequence变有效,换言之,可以通过wait_for_relevant来实现该sequence的无效变有效受其他sequence的状态影响。

在wait_for_relevant中,必须将使sequence无效的条件清除,否则会陷入死循环,即一直没有有效的sequence可以执行。因此,is_relevant与wait_for_relevant一般应成对重载。

与sequence相关的宏

最基础的sequence宏是uvm_do及其衍生,uvm_do系列宏主要包含以下八个:

uvm_douvm_do_priuvm_do_withuvm_do_pri_withuvm_do_onuvm_do_on_priuvm_do_on_withuvm_do_on_pri_with

从其组织形式可以看出,是uvm_do及pri,with,on等关键词的排列组合,这样有助于我们去记忆。回顾一下uvm_do宏的功能,它是UVM中最常用的宏之一,它用于创建一个transaction的实例,将该实例随机化,

最终将其送个sequencer with关键词给uvm_do宏增加了随机化的约束条件,pri关键词则设置了发送的transaction的优先级(在上一节有提到)。uvm_do_on宏用于显式地指定使用哪个sequencer发送此transaction,其参数形式为`uvm_do_on(SEQ_OR_ITEM,SEQR),第一个参数是transaction的指针,第二个是sequencer的指针。当使用uvm_do时,它实际等价于将uvm_do_on的第二个参数设置为了默认的sequencer,即此sequence启动时为其指定的sequencer。uvm_do_on_pri等在此基础上加上各关键词的功能,与uvm_do_pri与uvm_do之间的关系类似,因此uvm_do系列的宏本质上都是uvm_do_on_pri_with的特殊形式。

除了使用uvm_do宏自动产生,随机化并发送transaction之外,还可以通过uvm_create宏与uvm_send宏来实现这个过程。uvm_create宏的作用是实例化transaction,当一个transaction被实例化后,可以对其做更多的处理(如随机化),处理完毕后使用uvm_send宏发送出去。当然,也可以不用uvm_create而直接使用new函数来进行实例化。uvm_send也可以增加pri关键词以设置其优先级。

此外,还可以将随机化处理与发送合并为uvm_rand_send系列宏来实现。这个宏的使用前提是transaction已经被分配了空间,即已经实例化了。uvm_rand_send宏可增加pri,with关键字。设计uvm_rand_send系列宏的意义在于,如果一个transaction占用的内存很大,那么很可能希望前后两次发送的transaction都使用同一块内存,只是内容不同,这样子比较节约内存空间。

以上都是使用宏来完成transaction发送相关的工作,但这样子隐藏了具体的实现细节。不使用宏产生transaction的方式主要依赖于两个任务,start_item与finish_item。在使用这两个任务之前,必须要先实例化transaction后才可以调用。完整使用如上两个任务构建一个sequence的代码如下:

virtual task body(); repeat(10) begin tr = new("tr"); start_item(tr); finish_item(tr); endendtask

对transaction进行随机化的操作可放在实例化之后,finish_item之前的任意位置。start_item和finish_item都可以在调用时指定优先级。

了解了uvm_do宏实现的细节之后,为了增加uvm_do系列宏的灵活度,UVM提供了三个接口:pre_do,mid_do与post_do。

pre_do是一个任务,在start_item中被调用,是start_item返回前执行的最后一行代码,在它执行完成后才开始对transaction进行随机化。pre_do有一个1bit参数,用于表明uvm_do是对一个transaction还是一个sequence进行操作(详见下一节sequence的嵌套)。

mid_do是一个函数,在finish_item最开始被调用,在执行完此函数后才会进行finish_item中的其他操作。mid_do有一个参数,表示正在操作的sequence或transaction的指针,但其类型是uvm_sequence_item,需要通过cast转换成目标类型。

post_do也是一个函数,在finish_item中最后一行代码被调用。post_do也有一个参数,与mid_do类似。

sequence进阶应用

在一个sequence的body中,除了可以使用uvm_do产生transaction外,还可以启动其他的sequence,这就是sequence的嵌套。嵌套的方式也非常简单,直接在新的sequence的body任务中调用定义好的sequence即可,如下所示:

virtual task body();    SEQ0 seq0;\\定义化seq0    SEQ1 seq1;\\定义化seq1
    repeat(10) begin        `uvm_do(seq0);        `uvm_do(seq1);    endendtask

在上述代码中,使用了uvm_do宏。uvm_do宏的第一个参数可以是transaction的指针,此时其调用start_item和finish_item;该参数也可以是sequence的指针,此时其调用该sequence的start任务。除了uvm_do外,前述介绍的uvm_send,uvm_rand_send,uvm_create宏等,其第一个参数均可以是sequence的指针。

与transaction类似,sequence中也可以加入rand修饰的变量,用以进行对其产生的transaction进行约束。加入了rand变量的sequence可以通过uvm_do_with等宏添加约束条件。不过有一个需要注意的地方,在sequence中定义rand类型变量以向产生的transaction传递约束时,变量的名字一定要与transaction中相应字段的名字不同。这是因为如果名字一样,编译器就无法正确识别该变量究竟在sequence中还是在transaction中,会产生bug。

一般来说,嵌套sequence能正确的启动的条件之一是嵌套的所有sequence产生的所有transaction类型需与sequencer能发送的transaction类型一致。不过有一种方法将两个不同的transaction交给同一个sequencer处理,那就是将sequencer和driver能够接受的数据类型设置为uvm_sequence_item。

class my_sequencer extends uvm_sequencer #(uvm_sequence_item);class my_driver extends uvm_driver #(uvm_sequence_item);

这样子,在driver接收数据时,通过cast将其转换为不同的transaction类型,即可实现一个sequencer/driver发送多种类型transaction的功能了。

考虑一种特殊情况,即sequence中产生的transaction受对应的sequencer中变量的约束,此时该如何在sequence中获取该变量值呢?UVM内建了一个宏uvm_declare_p_sequencer(SEQUENCER),这个宏声明了一个SEQUENCER类型的成员变量,在定义sequence时,使用这个宏声明对应sequencer的类型如下

class case0_sequence extends uvm_sequence #(my_transaction); my_transaction my_trans; `uvm_object_utils(case0_sequence) `uvm_declare_p_sequencer(my_sequencer)endclass

这样UVM就会自动把m_sequencer(sequence默认sequencer变量,为uvm_sequencer_base类型)转换为my_sequencer类型,这个过程在pre_body()之前就完成了。这样在sequence中可以直接使用成员变量p_sequencer,从而获取my_sequencer中设置的变量值。这个概念比较难理解,读者可以参考《UVM实战》6.4.4节,结合具体代码理解。

由于在同一个项目中各sequence通常都是类似的,因此可以将很多公用的函数或者任务写在base_sequence中,其他sequence都从此类派生。sequence是支持派生与继承的。同时对于使用了uvm_declare_p_sequencer的base_sequence,在派生的sequence中不需要再次声明,p_sequencer直接成为新的sequence的成员变量。

参考文献:https://zhuanlan.zhihu.com/p/349791759


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK