UVM

UVM-sequence和sequencer的使用
转载自https://blog.csdn.net/wonder_coole/article/details/90665876

  1. UVM sequence机制的意义
    UVM的sequence机制最大的作用就是将test case和testbench分离开来。 对一个项目而言,testbench是相对稳定的框架,而针对各个module要有不同的测试内容,所以具体的test case 的差异非常大。在UVM中, test和sequence类总是成对出现,实现了testbench和具体的test case的结合。test类中可以针对具体的测试内容对testbench做一些差异化配置,在sequence类中则是实现test case的具体细节。

所以,大的项目大的团队中,做testbench(test 类以及以下的uvm树结构等)的是一个团队,对具体module做test case的是另一团队。UVM sequence机制很好的支持不同团队任务的的分割和结合。

transaction,sequence,sequencer,driver的相互关系参考下图

Sequence_Transactions

  1. UVM sequence细节要点
    2.1 sequence内包含的成员函数
    sequence启动后,会根据参数设置情况,自动执行pre_start(), pre_body(), parent_seq.pre_do(),parent_seq.mid_do(), body(), parent_seq.post_do(), post_body, post_start()等函数/任务。

seq.start (m_sequencer, null, , 1);

// The following methods will be called in start()
seq.pre_start(); (task)
seq.pre_body(); (task) if call_pre_post == 1
parent_seq.pre_do() (task) if parent_seq != null
parent_seq.mid_do(this) (func) if parent_seq != null
seq.body() (task) your code
parent_seq.post_do(this) (func) if parent_seq != null
seq.post_body() (task) if call_pre_post == 1
sub_seq.post_start() (task)
2.2 sequence的三种启动方式
(1)使用start任务.

task my_case0::main_phase(uvm_phase phase);
case0_sequence cseq;
cseq = new(“cseq”);
cseq.starting_phase = phase;
cseq.start(env.i_agt.sqr);
endtask
(2)使用uvm_config_db#(uvm_object_wrapper)配置default_sequence

uvm_config_db#(uvm_object_wrapper)::set(this,
“env.i_agt.sqr.main_phase”,
“default_sequence”,
case0_sequence::type_id::get());
(3)使用uvm_config_db#(uvm_sequence_base)配置default_sequence

function void my_case0::build_phase(uvm_phase phase);
case0_sequence cseq;
super.build_phase(phase);

cseq = new("cseq");
uvm_config_db#(uvm_sequence_base)::set(this,
                                       "env.i_agt.sqr.main_phase",
                                       "default_sequence",
                                       cseq);

endfunction
该示例在uvm1.1可以正常运行,uvm1.2中有问题。

default_sequence最终也是调用sequence的start任务。在uvm_sequence_base基类中的start任务原型是:

virtual task start (
uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1
)
parent_sequence控制这三个任务/函数会不会执行: parent_seq.pre_do() (task),parent_seq.mid_do(this) (func),parent_seq.post_do(this) (func) 。

call_pre_post控制着pre_body,post_body任务的执行与否。

this_priority是发送给sequencer的transaction的优先级值。只能是大于-1的整数。

2.3 sequence的仲裁机制
在同一个sequencer上可以启动多个sequence,每个sequence在启动时可以指定一个priority值,priority值越大优先级越高。设置方法:

uvm_do_pri(m_trans, 100)uvm_do_pri_with(m_trans, 200, {m_trans.pload.size < 500;})
sequencer默认的仲裁算法是SEQ_ARB_FIFO。所有仲裁算法有6种:

UVM_SEQ_ARB_FIFO : 按transaction的到达sequencer的顺序,先收先处理(FIFO),不考虑优先级(default)
UVM_SEQ_ARB_RANDOM : 完全从待处理的仲裁队列中随机选择,而无视它们的抵达顺序和优先级。
UVM_SEQ_ARB_STRICT_FIFO : 严格按照优先级顺序,从仲裁队列中选择优先级最高的transaction执行。如果仲裁队列中有多个相同优先级的,则按FIFO原则选择其中最先到达的。
UVM_SEQ_ARB_STRICT_RANDOM : 严格按照优先级顺序,从仲裁队列中选择优先级最高的transaction执行。如果仲裁队列中有多个相同优先级的,则从中随机选择。
UVM_SEQ_ARB_WEIGHTED : 这是最难理解的一个,其结果就是高优先级的sequence有更大可能获得sequencer的仲裁权。具体的仲裁过程是:
a.将仲裁队列中所有transaction的priority值求和得到sum值
b.之后随机产生一个在0到sum之间的一个threshold值
c.从仲裁队列的最前面开始,依次对每个transaction计算priority的加权值(将所有该transaction之前的priority值求和)
d.取加权值最先大于threshold的那个transaction

UVM_SEQ_ARB_USER : 使用用户自定义的仲裁方法.
注意: 在UVM 1.2, 带这些宏“UVM_”前缀; 在 UVM 1.1,不带“UVM_“前缀.

使用sequencer的成员函数set_arbitration来配置优先级算法:

env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);
仲裁算法详细讲解可参考:UVM Tutorial for Candy Lovers – 26. Sequence Arbitration

2.4 sequence对sequencer的占用或失效

  1. 用lock()和grab()排他性占用sequencer

在sequence的body()中使用lock()…unlock()或grab()…ungrab(), 可以让在其中间发送的所有transaction连续的获取sequencer的仲裁,从而连续的发送到driver而不被别是sequence打断。

二者的区别仅在仲裁机制不一样:
lock会放在仲裁队列的末尾,sequencer将原来就在仲裁队列里的transaction发送完后才执行lock的transaction,一旦lock占用sequencer后,只有unlock后才释放。
grab会放在仲裁队列的最前面,sequencer会立即执行grab的transaction,同样一旦grab占用sequencer后,只有ungrab后才释放。

 grab实时性强,是插队操作。unlock则不是。
  1. 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的无效状态。否则系统会进入死循环。
2.5 sequence里的宏及start/finish_item任务
1.uvm_do 相关宏

在sequence的body中使用uvm_do系列宏,可以自动完成transaction的创建,随机化和发送。
uvm_do系列宏对transaction和sequence都能支持。
如参数是transaction时它会调用start_item&finish_item任务。
如果是sequence,它会调用start任务。
所有uvm_do宏均由uvm_do_on_pri_with而来,如下图示:
uvm_do(SEQ_OR_ITEM)uvm_do_pri(SEQ_OR_ITEM, PRIORITY)
uvm_do_with(SEQ_OR_ITEM, CONSTRAINTS)uvm_do_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS)

//uvm_do_on 发到指定的sequencer
uvm_do_on(SEQ_OR_ITEM, SEQR)uvm_do_on_pri(SEQ_OR_ITEM, SEQR, PRIORITY)
uvm_do_on_with(SEQ_OR_ITEM, SEQR, CONSTRAINTS)uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS)
uvm_do macros

  1. uvm_create及uvm_send系列宏

如果需要在sequence中灵活可控的产生及发送transaction,可以uvm_create及uvm_send系列宏。和uvm_do相比更灵活。
uvm_create(SEQ_OR_ITEM): 工厂模式创建一个transaction或sequence,只后用户应该随机化,或指定某个值给创建的trans。uvm_create(SEQ_OR_ITEM,SEQR): 和uvm_create宏一样,并且指定了对应的sequencer。
uvm_send(SEQ_OR_ITEM): 将有uvm_create创建的tr/seq发送出去,其他send宏如下:uvm_send_pri(SEQ_OR_ITEM, PRIORITY)

uvm_rand_send(SEQ_OR_ITEM)uvm_rand_send_pri(SEQ_OR_ITEM, PRIORITY)
uvm_rand_send_with(SEQ_OR_ITEM, CONSTRAINTS)uvm_rand_send_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS
3. start_item和finish_item任务。

uvm_do及uvm_send宏的最终实现均是依靠start_item和finish_item任务来实现。调这两个任务可指定优先级参数。
start_item里会调用wait_for_grant及parent_seq.pre_do任务。
finish_item里会调用mid_do,send_request,wait_for_item_down及post_do等
create_item(item)
sequencer.wait_for_grant(prior) (task) \ start_item
parent_seq.pre_do(1) (task) /
`uvm_do* macros
parent_seq.mid_do(item) (func) \ /
sequencer.send_request(item) (func) \finish_item /
sequencer.wait_for_item_done() (task) /
parent_seq.post_do(item) (func) /
2.6 sequence的嵌套/层次化,继承/派生
嵌套(nested)即在sequence的body中使用另外已经定义好的sequence。当然需要他们均有一致的transaction类型。执行顺序参考下图。seq2在seq1的body中启动,seq1.start()中参数parent_sequence为空,seq2.start()中参数parent_sequence为this。

sequence flow

常用的嵌套,也称作层次化(sequence hierarchy), 在实践中会广泛用到。 通常对简单基本的测试场景放在一些基础sequence里,复杂场景的sequence均有许多基础sequence组合而成。如下图所示:

Sequence_Hierarchy

正常一种sequence->sequencer->driver只支持一种类型的trans,如果要在同一sequence中向sequencer发出不同类型的trans,可以:

              1)sequencer和driver声明时要使用基类uvm_sequence_item

              2)在driver中,需要将从seq_item_port收到的req使用cast进行向下类型转换。

继承:一般把公用的函数或任务放在一个base_sequence,其他sequence均从它派生出来。

3.virtual sequence/sequencer
3.1 p_sequencer
sequence类里有一个uvm_sequencer_base类型m_sequencer指针,当sequence和sequencer关联后,m_sequencer会自动指向该sequencer,但通过m_sequencer不能直接使用seqr里的变量,否则会出现编译错误。只能使用cast强制向子类转换后,才能通过m_sequencer.xxx来访问该seqr内的xxx变量。

UVM引入p_sequencer,可以自动的实现上面所述的cast动作,从而可以在sequence中自由使用关联sequencer内的变量。

p_sequencer并不是UVM自动地在sequence的创建的,需要用户使用`uvm_declare_p_sequencer宏声明,之后UVM会自动实现指向seqr及cast的动作。

3.2 什么是virtual sequence/sequencer
在实际应用中,dut往往是很复杂的系统,不单单只有一种接口。而我们testbench中的driver只能驱动一种接口,对应一种transaction的sequence。如果需要对多个接口同时进行激励,就需要的virtual sequence/sequencer。

Virtual sequencer 的特点:含有sub sequencer的句柄,用以控制这些sub sequencer。它并不和任何driver相连 Virtual sequencer,本身并不处理具体的trans/seq。

Virtual sequence 的作用:和virtual sequencer相关联的就是virtual sequence,它的作用是协调不同的subsequencer中sequence的执行顺序。

Virtual 的含义: 这里的virtual 区别用 system Verilog中用在function/task/class声明前,用于修饰的virtual。virtual sequence/sequencer的virtual主要是指这种sequence/sequencer不像直接作用在具体driver上的sequence/sequencer,它不处理具体的transaction,主要是来做不同类型sequence间的控制和调度的。

3.3 实现virtual sequence/sequencer的步骤
1 :定义virtual sequencer,里面包含各个env里的子sequencer类型的指针。
2 :在base_test里实现virtual sequencer的例化,和sub sequencer的连接。
> base_test作为uvm_test_top,即uvm树形结构的最顶层,负责chip_env和virtual sequencer的规划。
> 实例化virtual sequencer。
> 将virtual sequencer与各env的sequencer连接在一起,具体实现是通过function connect_phase中将virtual sequencer的中个sub sequencer的指针,指向各个具体的sequencer。
3 :定义virtual sequence。
> 在里边对多个sequence实例化。
> 要声明指向对应的virtual sequencer的p_sequencer,用于使用virtual sequencer中的sub sequencer句柄。
> 利用uvm_do_on类型宏,在指定的sub sequencer上运行具体的sequence。 注意:uvm_do这样的宏,针对的处理对象,不仅仅是transaction,还可以处理sequence。
4 :在具体test定义中,利用uvm_config_db将virtual sequencer的default_sequence设置为具体的virtual sequence。

*良好的代码规范是只在顶层virtual sequence中raise/drop objection,避免在各底层sequence中使用raise/drop objection引起的混乱。
4. 在sequence中使用config_db
4.1 sequence的完整路径
除了在uvm树中的component中用config_db外,在sequence中也可以用,虽然sequence是一个uvm_object类。用config_db时,关键在path的参数,一个sequence的完整路径,均是有该sequence关联的sequencer和该sequence的实例对象名组成。例如如:
sequencer的路径 + sequence实例名
uvm_test_top.env.a_agent.a_sequencer.test0_sequence

4.2 在sequence中get参数

  1. 在test中设置sequence中参数count如:
    uvm_config_db#(int)::set(this, “env.i_agt.sqr.*”, “count”, 9);
    注意: 由于sequence实例化名字不固定,路径中对应的sequence实例化名字要使用通配符。
  2. 在sequence中使用get:
    uvm_config_db#(int)::get(null,get_full_name(),“count”,count);
    注意:其中第一个参数不能使用this指针,因为sequence不是component,是能使用null/uvm_root::get();

4.3 在sequence中向component或sequence中set参数

  1. 在sequence的body中set参数:
    uvm_comfig_db#(bit)::set(uvm_root::get(),“uvm_test_top,env.scb”,“en_compare”,1);

  2. 在component/sequence中get的方法:
    由于component都是在build phase中调用get函数,如果需要在main phase中get该参数,就需要使用wait_modified任务不停检测参数的变化,然后再使用get获取参数。

    fork
    //在这个进程中不停检测参数的变化
    while(1) begin
    uvm_comfig_db#(bit)::wait_modified(this,“”,“en_compare”)
    void`(uvm_config_db#(bit)::get(this,"",“en_compare”,en_compare));
    end

    // 该component的主体内容
    begin

    end
    join

  3. sequence的response
    5.1 成对使用get_reponse/put_reponse任务:

    1. 在sequence中使用uvm_do后,使用get_reponse(rsp)获得从driver返回的reponse
    2. 在driver中,返回一个rsp的方法:

while(1) begin
seq_item_port.get_next_item(req);
drive_it_to_vif(req);
rsp = new(“rsp”) // 创建 rsp
rsp.set_id_info(req);// 将req的id等信息复制给rsp
seq_item_port.put_response(rsp); // 返回rsp
seq_item_port.item_done(); // 也可省掉put_reponse的调用,直接使用item_done返回,如:
// seq_item_port.item_done(rsp)
end
5.2 返回多个response
driver可以一次返回多个response,要在driver中多次调用put_response,在sequence中多次调用get_response.
sequencer内部为rsp维护一个队列,默认深度为8,所以多次返回超过8各rsp,有溢出的风险。

5.3 response的类型
response的类型默认是和request的类型一样的,如需返回不同类型的response,需要在声明sequence->sequencer->driver类时传入req和rsp两种参数类型。

5.4 非阻塞形式获取response
在sequence中使用get_response获取rsp,是阻塞的方式。如需非阻塞方式获取,需要使用response_handler,它会自动的在另外一进程中接受response.
1.在sequence中的pre_body()中使用use_reponse_handler()打开该功能。
2.重载response_handler函数(类似中断服务程序,需要写入对rsp处理的内容),注意:要在里面使用cast强制类型转换

  1. sequence library
    flow of sequence from library to driver

6.1 seq lib是sequence的集合,也是继承自uvm_sequence基类:
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
1.声明seq lib时要指明所产生的transaction类型,
2.seq lib 的new函数中要调用init_sequence_library
3.要调用uvm_sequence_library_utils将seq lib注册。
4.在单个sequence中要使用宏uvm_add_to_seq_lib将其加入指定seq lib中。
5.可以一对多,也可多对一
6.将seq lib设为sequencer的default sequence

其他将seq加入seq lib的方法可参考: UVM Sequence Library - Usage, Advantages, and Limitations

6.2 选择seq的算法:
UVM_SEQ_LIB_RAND: 完全随机
UVM_SEQ_LIB_RANDC: random cycle order,先随机排序,再按顺序执行,再保证每个seq执行一遍后,执行剩余次数。
UVM_SEQ_LIB_ITEM: 不执行里面的seq,而是自己产生trans,等同于一普通seq
UVM_SEQ_LIB_USER: 用户自定义,需重载select_sequence函数。
使用config_db 配置方法:

uvm_config_db#(uvm_sequence_lib_mode)::set(this,
“env.i_agt.sqr.main_phase”,
“default_sequence.selection_mode”,
UVM_SEQ_LIB_RANDC);
设置seq的执行次数:min_random_count & max_random_count, 默认都是10
6.3 sequence library的执行过程
seqence library和sequence,也是调用start任务执行,只后顺序执行seq lib的pre_start,pre_body, body,post_body,post_start。在seq lib的body中,按照配置的random的方式启动各个sequence。在调用start任务启动sequence时,给参数call_pre_post传入0值,因此每个sequence的pre_body 和 post_body不会运行。

6.4 使用uvm自带的sequence_library_cfg 统一配置lib里的参数

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值