UVM基础-Sequence仲裁机制

本文详细介绍了UVM(UniversalVerificationMethodology)中Sequence的仲裁机制,包括优先级设置、lock/grab操作以及sequence有效性管理。作者展示了如何通过uvm_do宏和start函数设置优先级,以及如何使用lock和grab操作来控制sequence执行顺序。同时,is_relevant和wait_for_relevant函数在sequence的有效性管理中起关键作用,避免了仿真挂死问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.1 多个sequence在一个sequencer启动

       seq可以通过调用其start函数在某个sequencer上启动,那么通常情况下,一个场景用例需要多个seq完成激励发送,主要包括初始化系统配置类激励和正常的场景激励,有以下test_case代码:

1.	class tc_sano_case extends base_test;
2.	       cfg_sequence      cfg_seq;
3.	       sano_sequence    sano_seq;
4.	......
5.	       `uvm_component_utils(tc_sano_case)
6.	......
7.	       virtual function void build_phase(usp_phase phase);
8.	              super.build_phase(phase);
9.	              cfg_seq = cfg_sequence::type_id::create("cfg_seq");
10.	              sano_seq = sano_sequence::type_id::create("sano_seq");
11.	......
12.	       endfunction:build_phase
13.	       virtual task main_phase(uvm_phase phase);
14.	              super.main_phase(phase);
15.	              cfg_seq.starting_phase = phase;
16.	              sano_seq.starting_phase = phase;
17.	              fork
18.	                    cfg_seq.start(env.i_agt.sqr);
19.	                    sano_seq.start(env.i_agt.sqr);
20.	              join
21.	        endtask:main_phase
22.	endclass

cfg_seq中:

1.	class cfg_sequence extends uvm_sequence#(cfg_transaction);
2.	       cfg_transaction cfg_tr;
3.	......
4.	       `uvm_object_utils(cfg_sequence);
5.	......
6.	       virtual task body();
7.	              if(starting_phase != null) 
8.	                     starting_phase.raise_objection(this);
9.	              repeat(3)
10.	                     `uvm_do(cfg_tr)
11.	              end
12.	              if (starting_phase != null)
13.	                     starting_phase.drop_objection(this);
14.	         endtask:body
15.	......
16.	endclass

sano_sequence中:

1.	class sano_sequence extends uvm_sequence#(sano_transaction);
2.	       sano_transaction sano_tr;
3.	......
4.	       `uvm_object_utils(sano_sequence);
5.	......
6.	       virtual task body();
7.	              if(starting_phase != null) 
8.	                     starting_phase.raise_objection(this);
9.	              repeat(5)
10.	                     `uvm_do_with(sano_tr, {sano_tr.data_size==100;})
11.	              end
12.	              if (starting_phase != null)
13.	                     starting_phase.drop_objection(this);
14.	         endtask:body
15.	......
16.	endclass

那么这个用例会交替产生cfg_tr和sano_tr,并且两者交替发送,事实上,如果使某个sequence先进行驱动,那么就要设置sequence的优先级。

1.2 sequence的优先级和uvm sequence宏

       实际上,uvm_do系列宏提供了改变sequence优先级的接口,`uvm_do宏,包括`uvm_do_with宏,优先级设定默认是-1,不具备优先级。uvm_do系列宏的核心其实是`uvm_do_on_pri_with(tr_or_seq, sqr, pri, {cons}),第一个参数是想要传输seq或者transaction,第二个参数是在哪个sqr上启动,第三个参数是sequence或者tr的优先级,第四个参数是约束,uvm_do系列宏的底层都是这个宏,只不过将参数进行了固化。

       使用`uvm_do_pri或者`uvm_do_on_pri这种带有pri关键字的宏,可以对sequence设定优先级,具备设定优先级功能的宏一共有四个,分别是:`uvm_do_pri,`uvm_do_pri_with,`uvm_do_on_pri,`uvm_do_on_pro_with。

例如将1.1节cfg_seq代码改成

1.	class cfg_sequence extends uvm_sequence#(cfg_transaction);
2.	       cfg_transaction cfg_tr;
3.	......
4.	       `uvm_object_utils(cfg_sequence);
5.	......
6.	       virtual task body();
7.	              if(starting_phase != null) 
8.	                     starting_phase.raise_objection(this);
9.	              repeat(3)
10.	                     `uvm_do_pri(cfg_tr, 200)
11.	              end
12.	              if (starting_phase != null)
13.	                     starting_phase.drop_objection(this);
14.	         endtask:body
15.	......
16.	endclass

然后将sano_sequence代码改为:

1.	class sano_sequence extends uvm_sequence#(sano_transaction);
2.	       sano_transaction sano_tr;
3.	......
4.	       `uvm_object_utils(sano_sequence);
5.	......
6.	       virtual task body();
7.	              if(starting_phase != null) 
8.	                     starting_phase.raise_objection(this);
9.	              repeat(8)
10.	                     `uvm_do_pri_with(sano_tr, 100, {sano_tr.data_size==500;})
11.	              end
12.	              if (starting_phase != null)
13.	                     starting_phase.drop_objection(this);
14.	         endtask:body
15.	......
16.	endclass

这样做的目的是设定sequence的优先级,但是设定过后仿真,发现sequence并没有按照优先级的顺序发送,还是交替产生,感觉sequence的优先级没有“生效”。

实际上,UVM对于sequence的仲裁,在sequencer中实现,而sequencer对sequence的仲裁是需要进行模式设定的,sequencer存在多种仲裁算法:

类型

含义

SEQ_ARB_FIFO

先入先出原则,无优先级仲裁(sqr的默认模式)

SEQ_ARB_WEIGHTED

加权仲裁模式,考虑权重

SEQ_ARB_RANDOM

随机发送模式,不考虑权重

SEQ_ARB_STRICT_FIFO

依据优先级发送,如果优先级相同,按照先入先出模式发送

SEQ_ARB_STRICT_RANDOM

依据优先级发送,如果优先级相同,按照随机发送

SEQ_ARB_USER

按照用户自定义的方式发送

 sqr的默认仲裁模式是SEQ_ARB_FIFO,会按照先进先出的原则进行仲裁,那么如果要按照优先级发送,那么需要把sqr的模式设置为SEQ_ARB_STRICT_FIFO或者SEQ_ARB_STRICT_RANDOM,可以通过sqr.set_arbitration(mod)进行设定,如果需要设定,改写tc代码:

1.	class tc_sano_case extends base_test;
2.	       cfg_sequence      cfg_seq;
3.	       sano_sequence    sano_seq;
4.	......
5.	       `uvm_component_utils(tc_sano_case)
6.	......
7.	       virtual function void build_phase(usp_phase phase);
8.	              super.build_phase(phase);
9.	              cfg_seq = cfg_sequence::type_id::create("cfg_seq");
10.	              sano_seq = sano_sequence::type_id::create("sano_seq");
11.	......
12.	       endfunction:build_phase
13.	
14.	       virtual function void connect_phase(uvm_phase phase);
15.	               super.connect_phase(phase);
16.	......
17.	               env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);
18.	               //env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_RANDOM);
19.	       endfunction:connect_phase
20.	       virtual task main_phase(uvm_phase phase);
21.	              super.main_phase(phase);
22.	              cfg_seq.starting_phase = phase;
23.	              sano_seq.starting_phase = phase;
24.	              fork
25.	                    cfg_seq.start(env.i_agt.sqr);
26.	                    sano_seq.start(env.i_agt.sqr);
27.	              join
28.	        endtask:main_phase
29.	endclass

经过设定,sequence的发送会优先发送cfg_sequence,因为cfg_sequence的优先级更高,发送完cfg_sequence之后,在发送sano_sequence。

       除了通过uvm_do系列宏设定sequence的优先级之外,通过start函数同样可以设定优先级,实际上,start函数有三个参数,将上述代码25行和26行改写为:

cfg_seq.start(env.i_agt.sqr, null, 300);
sano_seq.start(env.i_agt.sqr, null, 100);

可以起到同样的效果(此时sequence中uvm_do要用无权重的设定),start函数的三个参数含义分别是:第一个,表示在哪个sqr上启动;第二个,表示当前sequence的父节点sequence,涉及到sequence的继承概念,没有就些null;第三个参数就是sequence的权重。实际上,通过start设定权重,最终也是设定在了transaction上。注意无论是通过start方式设定优先级,还是通过uvm_do宏设定,优先级默认为-1,不具备优先级,优先级的数值必须大于-1,小于-1会报错。

这里还需要提一下,sequencer的仲裁算法中有一个设定是用户自定义优先级:SEQ_ARB_USER,当设定这个参数后,my_sequencer中需要重载user_priority_arbitration函数,函数原型是:

       这里需要传输有效的sequence的权重序号,将用户认为最先传输的sequence先压入队列,函数会返回第0个队列,也就是优先级最高的队列。

2.1 lock操作和grab操作

除了通过设定sequence或者tr的优先级之外,uvm还提供了锁定sequence的方法,即lock操作和grab操作。假设有seq0代码:

1.	class sequence_0 extends uvm_sequence#(uvm_sequence_item);
2.	       my_transaction tr;
3.	......
4.	       `uvm_object_utils(sequence_0);
5.	......
6.	       virtual task body();
7.	              if(starting_phase != null) 
8.	                     starting_phase.raise_objection(this);
9.	              repeat(3) begin
10.	                     `uvm_do(tr)
11.	              end
12.	              lock();
13.	              repeat(10) begin
14.	                    `uvm_do(tr)
15.	              end
16.	              unlock();
17.	              repeat(3) begin
18.	                    `uvm_do_with(tr, {tr.data_size=200;})
19.	              end              
20.	              if (starting_phase != null)
21.	                     starting_phase.drop_objection(this);
22.	         endtask:body
23.	......
24.	endclass

sequence1中代码:

1.	class sequence_1 extends uvm_sequence#(uvm_sequence_item);
2.	       my_transaction tr;
3.	......
4.	       `uvm_object_utils(sequence_1);
5.	......
6.	       virtual task body();
7.	              if(starting_phase != null) 
8.	                     starting_phase.raise_objection(this);
9.	              repeat(20) begin
10.	                     `uvm_do_with(tr,{tr.payload == 32'hffff_ffff;})
11.	              end            
12.	              if (starting_phase != null)
13.	                     starting_phase.drop_objection(this);
14.	         endtask:body
15.	......
16.	endclass

此时,sequence0和sequence1会先交替发送,当执行到sequence0的lock函数后,sequence1会停止发送,sequencer将仲裁逻辑完全锁定在sequence0上,直到sequence0的unlock函数被调用,然后sequence0剩余的uvm_do和sequence1交替发送。

grab操作和lock操作的用法一样,只不过grab操作比lock操作拥有更高的优先级,lock请求会被插入到sequencer仲裁队列的最后面,而grab操作会插入到sequencer仲裁队列的最前面,一发出就会获得sequencer的所有权,也就是说,如果同时执行到lock和grab,优先grab操作获得sequencer的所有权。将上述sequence0代码改为:

1.	class sequence_0 extends uvm_sequence#(uvm_sequence_item);
2.	       my_transaction tr;
3.	......
4.	       `uvm_object_utils(sequence_0);
5.	......
6.	       virtual task body();
7.	              if(starting_phase != null) 
8.	                     starting_phase.raise_objection(this);
9.	              repeat(3) begin
10.	                     `uvm_do(tr)
11.	              end
12.	              grab();
13.	              repeat(8) begin
14.	                    `uvm_do(tr)
15.	              end
16.	              ungrab();
17.	              repeat(3) begin
18.	                    `uvm_do_with(tr, {tr.data_size=200;})
19.	              end              
20.	              if (starting_phase != null)
21.	                     starting_phase.drop_objection(this);
22.	         endtask:body
23.	......
24.	endclass

如果两个sequence同时在一个sequencer上启动,且两个sequence中同时都有lock操作,或者同时都有grab操作,且两者不是同时发生,那么先运行到的操作先执行,一直到sequence的unlock(ungrab)操作调用,然后再释放所有权给另外一个sequence。

如果两个sequence,其中一个有lock操作,另外一个有grab操作,会怎么样?如果lock操作先运行到,那么grab操作也不会生效,直到unlock调用,返回sequencer的所有权,再执行grab,也就是说,grab不会打断lock操作。

2.2 设置sequence的有效性

       uvm sequence机制还提供了sequence对于其自身有效性的管理,通过两个函数实现:第一个是is_relevant;另一个是wait_for_relevant。sequence可以通过重载这两个函数,实现对自身有效性的控制,被设定为无效的sequence,sequencer会将其优先级放置为最低。这两个函数的原型:

这个函数的返回值,会说明当前sequence是否有效,如果返回0,则证明当前sequence无效,失去sequencer的所有权。

       这个任务指等待当前sequence变有效,可以看到源码中带有了阻塞语句,也就是说,如果在某个时刻,所有的sequence都无效,那么sequencer会调用这个任务,一直等到有效的sequence,所以该方式要谨慎使用。

       只用is_relevant来控制sequence的有效性(sequence的主动控制):

1.	class sequence_0 extends uvm_sequence#(uvm_sequence_item);
2.	       my_transaction tr;
3.	       int       seq_num;
4.	       bit       need_delay = 1'b1;
5.	......
6.	       `uvm_object_utils(sequence_0);
7.	......
8.	       virtual function bit is_relevant();
9.	               if seq_num >= 3 && need_delay:
10.	                       return 0;
11.	               else
12.	                       return 1;
13.	       endfunction:is_relevant
14.	       virtual task body();
15.	              if(starting_phase != null) 
16.	                     starting_phase.raise_objection(this);
17.	              fork
18.	                  repeat(10) begin
19.	                      `uvm_do(tr)
20.	                      seq_num++;
21.	                  end
22.	                  while (1) begin
23.	                      if (need_delay == 1'b1) begin
24.	                            if(seq_num >= 3) begin
25.	                                  #15000ns;
26.	                                  need_delay = 1'b0;
27.	                                  break;
28.	                             end
29.	                             else begin
30.	                                  #100ns;
31.	                             end
32.	                       end
33.	                  end
34.	              join
35.	              if (starting_phase != null)
36.	                     starting_phase.drop_objection(this);
37.	         endtask:body
38.	......
39.	endclass

这段代码实现的是,在发送完三个tr后,将sequence设定为无效,然后等待15us后,然后将sequence重新设定为有效,sequence是否有效,主动权在本身。实际上,这种代码是会有挂死风险的,因为一旦在15us之内,所有的sequence都运行完毕,那么此时sequencer会调用这个无效sequence的wait_for_relevant函数,而不会执行该sequence的body,因此会出现退出条件一直无法执行而挂死仿真的情况。只有在有sequence有效,且过了15us后,还有sequence有效,无效sequence的退出无效条件执行到,才不会出现仿真挂死情况。

如果结合wait_for_relevant:

1.	class sequence_0 extends uvm_sequence#(uvm_sequence_item);
2.	       my_transaction tr;
3.	       int       seq_num;
4.	       bit       need_delay = 1'b1;
5.	......
6.	       `uvm_object_utils(sequence_0);
7.	......
8.	       virtual function bit is_relevant();
9.	               if seq_num >= 3 && need_delay:
10.	                       return 0;
11.	               else
12.	                       return 1;
13.	       endfunction:is_relevant
14.	
15.	       virtual task wait_for_relevant();
16.	               #1000;
17.	               need_delay = 1'b0;
18.	       endtask
19.	       virtual task body();
20.	              if(starting_phase != null) 
21.	                     starting_phase.raise_objection(this);
22.	              repeat(10) begin
23.	                     `uvm_do(tr)
24.	                     seq_num++;
25.	              end
26.	              if (starting_phase != null)
27.	                     starting_phase.drop_objection(this);
28.	         endtask:body
29.	......
30.	endclass

这个时候,sequence在无效后,是否有效取决于其他sequence,如果所有的sequence都发送完,sequencer会调用无效sequence的wait_for_relevant任务,这个任务如果设定了重新使能sequence有效的条件,那么sequence变有效正常发送。如果将上述代码15-18行改写为:

virtual task wait_for_relevant();
     #1000;
endtask

这样,sequencer调用wait_for_relevant,然后再调用is_relevant,发现is_relevant还是返回0,那么继续调用wait_for_relevant,这样仿真会最后挂死。

结论:

  • is_relevant函数和wait_for_relevant任务要成对调用,is_relevant函数中设定无效条件,wait_for_relevant任务中声明退出无效条件。

给出可能出现仿真挂死的情况:

  • 只有is_relevant函数,没有重载wait_for_relevant任务,且is_relevant的无效退出条件在其他sequence执行完,之后执行到,sequencer已经调用了无效sequence的wait_for_relevant函数,仿真挂死
  • 重载了is_relevant和wait_for_relevant,在重载wait_for_relevant的时候,未声明退出无效条件
  • 只重载了is_relevant,没有重载wait_for_relevant,且没有机制退出无效条件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值