java sequencer_UVM学习笔记--sequence和sequencer(转)

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的相互关系参考下图

image.png

2. 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则不是。

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的无效状态。否则系统会进入死循环。

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)

2. 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 hierarchy), 在实践中会广泛用到。 通常对简单基本的测试场景放在一些基础sequence里,复杂场景的sequence均有许多基础sequence组合而成。如下图所示:

正常一种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

5. 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强制类型转换

6. sequence library

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里的参数

参考:

版权声明:本文为CSDN博主「wonder_coole」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/wonder_coole/java/article/details/90665876

你可能感兴趣的:(java,sequencer)