(19)UVM 史上最强sequence相关宏操作总结
作者:互联网
史上最强sequence相关宏操作总结
文章目录
一、序列宏
下面一段代码是对start()
方法执行过程的自然代码描述,可以看出它们执行的顺序关系和条件:
sub_seq.pre_start() (task)
sub_seq.pre_body() (task) if call_pre_post==1
parent_seq.pre_do(0) (task) if parent_sequence!=null
parent_seq.mid_do(this) (func) if parent_sequence!=null
sub_seq.body (task) YPUR STIMULUS CODE
parent_seq.post_do(this) (func) if parent_sequence!=null
sub_seq.post_body() (task) if_call_pre_post==1
sub_seq.post_start() (task)
下面是start_item()/finish_item()
的自然代码描述,来表示执行发送item时的相关方法执行顺序:
sequencer.wait_for_grant(prior) (task)
parent_seq.pre_do(1) (task)
parent_seq.mid_do(item) (func)
sequencer.send_request(item) (func)
sequencer.wait_for_item_done() (task)
parent_seq.post_do(item) (func)
- sync是wait_for_grant
- sync和post_sync实际上是要拿到优先级,优先级是在seq的body里面拿到的。seq每次发送item的时候,每次都会拿到优先级。
- 只有sequence可以调用上面这些宏
- 如果没有on,它挂载的sqr和顶层seq挂载的sqr是同一个
正是通过几个sequence/item宏来打天下的方式,用户可以通过
`uvm_do/`uvm_do_with
来发送无论是sequence还是item。这种不区分对象是sequence还是item的方式,带来了不少便捷,但也容易引起verifier们的惰性。所以在使用它们之前,需要先了解它们背后的sequence和item各自发送的方法。
不同的宏,可能会包含创建对象的过程也可能不会创捷对象。例如
`uvm_do/`uvm_do_with
会创建对象,而`uvm_send则不会创建对象,也不会将对象做随机处理,因此要了解它们各自包含的执行内容和顺序。
此外还有其他的宏,例如,将优先级作为参数传递的
`uvm_do_pri/`uvm_do_on_prio
等,还有专门针对sequence的
`uvm_create_seq/`uvm_do_seq/`uvm_do_seq_with
等宏。
on作用,表示我们要特定地把当前这个seq挂载到某一个sqr上面
二、序列宏的实例
class child_seq extends uvm_sequence;
...
task body();
bus_trans req;
`uvm_create(req)
`uvm_rand_send_with(req,{data==10;})
endtask
endclass
class top_seq extends uvm_sequence;
...
task body();
child_seq cseq;
bus_trans req;
`uvm_do(cseq)
`uvm_do_with(req,{data==20;})
endtask
endclass
最后给出的关于发送sequence/item的几点建议:
- 无论sequence处于什么层次,都应当让sequence在test结束前执行完毕。但这不是充分条件,一般而言,还应当保留出一部分时间供DUT将所有发送的激励处理完毕,进入空闲状态才可以结束测试。
尽量避免使用for-join_any或者fork-join_none
来控制sequence的发送顺序。因为这背后隐藏的风险是,如果用户想终止在后台运行的sequence线程而简单使用disable方式,那么就可能在不恰当的时间点上锁住sequencer。- 一旦sequencer被锁住而又无法释放,接下来也就无法发送其它sequence。所以如果用户想实现类似fork-join_any或者fork-join_none的发送顺序,还应当在使用disable前,对各个sequence线程的后台运行保持关注,尽量在发送完item完成握手之后再终止sequence,这样才能避免sequencer被死锁的问题。
- 如果用户要使用fork-join方式,那么应当确保有方法可以让sequence线程在满足一些条件后停止发送item。否则只要有一个sequence线程无法停止,则整个fork-join无法退出。面对这种情况,仍然需要用户考虑监测合适的事件或者时间点,才能够使用disable来关闭线程。
三、为什么会锁住sqr?
因为start_item的时候,都会wait_for_grant,一旦你拿到权限,而没有返回,在中间暴力的disable整个seq,那么可能来不及给sqr释放权限,那么可能sqr被死锁。
四、sequencer的仲裁机制
-
uvm_sequencer类自建了仲裁机制用来保证多个sequence在同时挂载到sequencer时,可以按照仲裁规则允许特定sequence中的item优先通过。
-
在实际使用中,我们可以通过uvm_sequencer::set_arbitration(UVM_SEQ_ARB_TYPE val)函数来设置仲裁模式,这里的仲裁模式UVM_SEQ_ARB_TYPE有下面几种值可以选择。
1.UVM_SEQ_ARB_FIFO:默认模式。来自于sequences的发送请求,按照FIFO先进先出的方式被依次授权,和优先级没有关系。
2.UVM_SEQ_ARB_WEIGHTED:不同sequence的发送请求,将按照它们的优先级权重随机授权。
3.UVM_SEQ_ARB_RANDOM:不同的请求会被随机授权,而无视它们的抵达顺序和优先级。
4.UVM_SEQ_ARB_STRICT_FIFO:不同的请求,会按照它们的优先级以及抵达顺序来依次授权,因此与优先级和抵达时间都有关。
5.UVM_SEQ_ARB_STRICT_RANDOM:不同的请求,会按照它们的最高优先级随机授权,与抵达时间无关。
6.UVM_SEQ_ARB_USER:用户可以自定义仲裁方法user_priority_arbitration()来裁定那个sequence的请求被优先授权。 -
在上面的仲裁模式中,与priority有关的模式有UVM_SEQ_ARB_WEIGHTED、UVM_SEQ_ARB_STRICT_FIFO和UVM_SEQ_ARB_STRICT_RANDOM。
-
这三种模式的区别在于,UVM_SEQ_ARB_WEIGHTED的授权可能会落到各个优先级sequence的请求上面,而UVM_SEQ_ARB_STRICT_RANDOM则只会将授权随机安排到最高优先级的请求上面,UVM_SEQ_ARB_STRICT_FIFO则不会随机授权,而是严格按照优先级以及抵达顺序来依次授权。
-
没有特别的要求,用户不需要再额外自定义授权机制,因此使用UVM_SEQ_ARB_USER这一模式的情况不多见,其他模式可以满足绝大多数的仲裁需求。
-
鉴于sequence传送的优先级可以影响sequencer的仲裁授权,我们有必要结合sequencer的仲裁模式和sequence的优先级给出一段例码。
class bus_trans extends uvm_sequence_item;
rand bit data;
...
endclass
class child_seq extends uvm_sequence;
rand int base;
...
task body();
bus_trans req;
repeat(2) `uvm_do_with(req,{data inside {[base:base+9]};})
endtask
endclass
class top_seq extends uvm_sequence;
...
task body();
child_seq seq1,seq2,seq3;
m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
fork
`uvm_do_pri_with(seq1,500,{base==10;})
`uvm_do_pri_with(seq2,500,{base==20;})
`uvm_do_pri_with(seq3,300,{base==30;})
join
endtask
endclass
class sequencer extends uvm_sequencer;
...
endclass
class driver extends uvm_driver;
...
task run_phase(uvm_phase phase);
REQ tmp;
bus_trans req;
forever begin
seq_item_port.get_next_item(tmp);
void'($cast(req,tmp));
`uvm_info("DRV",$sformatf("got a item %0d from parent sequence %s",req.data,req.get_parent_sequence().get_name()),UVM_LOW)
seq_item_port.item_done();
end
endtask
endclass
class env extends uvm_env;
sequencer sqr;
driver drv;
...
function void build_phase(uvm_phase phase);
sqr=sequencer::type_id::create("sqr",this);
drv=driver::type_id::create("drv",this);
endfunction
function void connect_phase(uvm_phase phase);
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
endclass
class test1 extends uvm_test;
env e;
...
task run_phase(uvm_phase phase);
top_seq seq;
phase.raise_objection(phase);
seq=new();
seq.start(e.sqr);
phase.drop_objection(phase);
endtask
endclass
五、sequencer的锁定机制
uvm_sequencer提供了两种锁定机制,分别通过lock()和grab()方法来实现,这两种方法的区别在于:
- lock()与unlock()这一对方法可以为sequence提供排外的访问权限,但前提条件是,该sequence首先需要按照sequencer的仲裁机制获得授权。而一旦sequence获得授权,则无需担心权限被收回,只有该sequence主动解锁(unlock)它的sequencer,才可以释放这一锁定的权限。lock()是一种阻塞任务,只有获得了权限,它才会返回。
- grab与ungrab()也可以为sequence提供排外的访问权限,而且它只需要在sequencer下一次授权周期时就可以无条件地获得授权。与lock方法相比,grab方法无视同一时刻内发起传送请求的其他sequence,而唯一可以阻值它的只有已经预先获得授权的其他lock或者grab的sequence。
- 这里需要注意的是,由于"解铃还须系铃人",如果sequence使用了lock()或者grab()方法,必须再sequence结束前调用unlock()或者ungrab()方法来释放权限,否则sequencer会进入死锁状态而无法继续为其余sequence授权。
六、sequencer的锁定示例
class bus_trans extends uvm_sequence_item;
...
endclass
class child_seq extends uvm_sequence;
...
endclass
class lock_seq extends uvm_sequence;
...
task body();
bus_trans req;
#10ns;
m_sequencer.lock(this);
`uvm_info("LOCK",get exclusive access by lock()",UVM_LOW)
repeat(3) #10ns `uvm_do_with(req,{data inside {[100:110]};})
m_sequencer.unlock(this);
endtask
endclass
class grab_seq extends uvm_sequence;
...
task body();
bus_trans req;
#20ns;
m_sequencer.grab(this);
`uvm_info("GRAB","get exclusive access by grab()",UVM_LOW)
repeat(3) #10ns `uvm_do_with(req,{data inside {[200:210]};})
m_sequencer.ungrab(this);
endtask
endclass
class top_seq extends uvm_sequence;
...
task body();
child_seq seq1,seq2,seq3;
lock_seq locks;
grab_Seq grabs;
m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
fork
`uvm_do_pri_with(seq1,500,{base==10;})
`uvm_do_pri_with(seq2,500,{base==20;})
`uvm_do_pri_with(seq3,300,{base==30;})
`uvm_do_pri(locks,300)
`uvm_do(grabs)
join
endtask
endclass
这里的child_seq中,虽然和上面一样也发送2个item,但每个item之间有10ns的间隔。发送第一个item也延迟10ns
七、sequencer的锁定示例解析
结合例码和输出结果,我们从中可以发现如下几点:
1.对于sequence locks,在10ns时跟其它几个sequence一同向sequencer发起请求,按照仲裁模式,sequencer先后授权给seq1,seq2,seq3,最后才授权给locks。
2.而locks在获得授权之后,就可以一直享有权限而无需担心权限被sequencer收回,locks结束前,用户需要通过unlock()方法返回权限。
3.对于sequence grabs,尽管它的20ns时就发起了请求权限(实际上seq1、seq2、seq3也在同一时刻发起了权限请求),而由于权限已经被locks占用,所以它也无权收回权限。
4.因此只有当locks在40ns结束时,grabs才可以在sequencer没有被锁定的状态下获得权限,而grabs在此条件下获取权限是无视同一时刻发起请求的其他sequence的。
5.同样地,在grabs结束前,也应当通过ungrab()方法释放权限,防止sequencer的死锁行为。
关注作者
- 自述
作者是一位中科大数字设计专业的研究生,水平有限,如有错误,请大家指正,想要与大家一同进步。 - 经历
曾获得国家奖学金,“高教社杯”数学建模国家二等奖等 - 陆续更新:
1.与UVM验证相关的system verilog后续内容;
2.与verilog数字设计相关的一些基础模块设计,例如FIFO,UART,I2C等的书写。
3.保研与竞赛经历等 - 微信公众号
欢迎大家关注公众号“数字IC小白的日常修炼”,期待与大家一同仗剑遨游数字IC世界。
标签:do,seq,sequence,19,item,uvm,sequencer,UVM 来源: https://blog.csdn.net/qq_42419590/article/details/121393762