- 一、UVM Sequence仲裁机制用法
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个队列,也就是优先级最高的队列。
- 二、Sequence仲裁机制进阶
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,且没有机制退出无效条件。