Sequencer和Sequence
一、sequence和item发送实例
class bus_trans extends uvm_sequence_item;
rand int data;
`uvm_object_utils_begin(bus_trans)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_object_utils_end
...
endclass
class child_seq extends uvm_sequence;
`uvm_object_utils(child_seq);
...
task body();
uvm_sequence_item tmp;
bus_trans req, rsp;
tmp = create_item(bus_trans::get_type(), m_sequencer, "req");
void'($cast(req, tmp));
start_item(req);
req.randomize with {data == 10;};
finish_item(req);
endtask
endclass
class top_seq extends uvm_sequence;
`uvm_object_utils(top_seq)
...
task body();
uvm_sequence_item tmp;
child_seq cseq;
bus_trans req;
//create child sequence and items
cseq = child_seq::type_id::create("cseq");
tmp = create_item(bus_trans.get_type(), m_sequencer, "req");
//send child sequence via start()
cseq.start(m_sequencer, this);
//send sequence item
void'($cast(req, tmp));
start_item(req);
req.randomize with {data == 20;};
finish_item(req);
endtask
endclass
class sequencer extends uvm_sequencer;
`uvm_component_utils(sequencer)
...
endclass
class driver extends uvm_driver;
`uvm_component_utils(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 \n %s", req.sprint()), UVM_LOW)
seq_item_port.item_done();
end
endtask
endclass
class env extends uvm_env;
sequencer sqr;
driver drv;
`uvm_component_utils(env)
...
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;
`uvm_component_utils(test1)
...
function void build_phase(uvm_phase phase);
e = env::type_id::create("e", this);
endfunction
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
二、发送sequence/item方法解析
在这段代码中,主要使用了两种方法,第一个方法是针对将sequence挂载到sequencer上的应用。
uvm_sequence::start(uvm_sequencer_base_sequence, uvm_sequence_base_parent_sequence=null, int this_priority=-1, bit call_pre_post=1)
//在使用该方法的过程中,首先应该指明sequencer的句柄,如果该sequence是顶部的sequence,即没有更上层的sequence嵌套它,则它可以省略对第二个参数parent_sequence的指定
//第三个参数的默认值是-1,会使得该sequence如果有parent_sequence会继承其优先级值,如果它是顶部(root)sequence,则其优先级会被自动设定为100。
//第四个参数默认值为1,默认uvm_sequence::pre_body()和uvm_sequence::post_body()两个方法会在uvm_sequence::body()的前后执行
示例中child_seq被嵌套到top_seq中,继而在挂载时需要指定parent_sequence,而在test一层调用top_seq时,由于它是root sequence,则不需要再指定parent sequence。
第二种发送方法是针对item挂载到sequencer上的应用。
uvm_sequence::start_item(uvm_sequence_item item, int set_priority=-1, uvm_sequencer_base_sequence=null);
uvm_sequence::finish_item(uvm_sequence_item, int set_priority=-1);
//对于start_item(),第三个参数需要注意是否将item挂载到“非当前parent sequence挂载的sequencer”上面,即如果将item和其parent sequence挂载到不同的sequencer上面,就需要指定这个参数。
对于一个item的完整传送,sequence要在sequencer一侧获得通过权限,才可以顺利将item发送至driver。拆解这些步骤如下:
创建item。
通过start_item()方法等待获得sequencer的授权许可,其后执行parent sequence的方法pre_do()。
对item进行随机化处理。
通过finish_item()方法在对item进行了随机化处理之后,执行parent sequence的mid_do(),以及调用uvm_sequencer::send_request()和uvm_sequencer::wait_for_item_done()来将item发送至sequencer再完成与driver之间的握手。最后执行了parent_sequence的post_do()。
这些完整的细节有两个部分需要注意:
第一,sequence和item自身的优先级,可以决定什么时刻可以获取sequencer的授权。
第二,parent sequence的虚方法pre_do()、mid_do()、post_do()会发生在发送item的过程中间。
对比start()方法和start_item()/finish_item(),首先要分清它们面向的挂载对象是不同的。在执行start()过程中,默认情况下会执行sequence的pre_body()和post_body(),但是如果start()的参数call_pre_post=0,那么就不会这样执行。
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) //your 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()源代码如下:
三、发送序列的相关宏
通过这些sequence/item宏,可以使用’uvm_do、'uvm_do_with来发送无论是sequence还是item。这种不区分对象是sequence还是item方式带来了不少便捷。不同的宏,可能会包含创建对象的过程,也可能不会创建对象。例如’uvm_do、'uvm_do_with会创建对象,而’uvm_send则不会创建对象,也不会将对象做随机处理,因此要了解它们各自包含的执行内容和顺序。
四、序列宏的示例
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;
//send child sequence via start()
`uvm_do(cseq)
//send sequence item
`uvm_do_with(req, {data == 20;})
endtask
endclass
无论sequence处于什么层次,都应该让sequence在test结束前执行完毕,还应该保留出一部分时间供DUT将所有发送的激励处理完毕,进入空闲状态才可以结束测试。
尽量避免使用fork_join_any或者fork_join_none来控制sequence的发送顺序。因此如果想终止在后台运行的sequence线程而简单使用disable方式,就可能在不恰当的时间点上锁住sequencer。一旦sequencer被锁住而又无法释放,接下来也就无法发送其它sequence,尽量在发送完item完成握手之后再终止sequence。如果要使用fork_join方式,应该确保有方法可以让sequence线程在满足一些条件后停止发送item,否则只要有一个sequence线程无法停止,则整个fork_join无法退出。
五、Sequencer的仲裁
uvm_sequencer类自建了仲裁机制用来保证多个sequence在同时挂载到sequencer时,可以按照仲裁规则允许特定sequence中的item优先通过。在实际使用中,可以通过uvm_sequencer::set_arbitration(UVM_SEQ_ARB_TYPE val)函数来设置仲裁模式,这里的仲裁模式UVM_SEQ_ARB_TYPE 有下面几种值可以选择:
UVM_SEQ_ARB_FIFO:默认模式。来自于sequences的发送请求,按照FIFO先进先出的方式被依次授权,和优先级没有关系。
UVM_SEQ_ARB_WEIGHTED:不同sequence的发送请求,将按照它们的优先级权重随机授权。
UVM_SEQ_ARB_RANDOM:不同的请求会被随机授权,而无视它们抵达顺序和优先级。
UVM_SEQ_ARB_STRICT_FIFO:不同的请求,会按照它们的优先级以及抵达顺序来依次授权,与优先级和抵达时间都有关系。
UVM_SEQ_ARB_STRICT_RANDOM:不同的请求,会按照它们的最高优先级随机授权,与抵达时间无关。
UVM_SEQ_ARB_USER:可以自定义仲裁方法user_priority_arbitration()来裁定哪个sequence的请求被优先授权。
六、Sequencer的仲裁示例
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 bus_trans extends uvm_sequence_item;
rand int 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 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;
`uvm_component_utils(test1)
...
function void build_phase(uvm_phase phase);
e = env::type_id::create("e", this);
endfunction
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
输出结果:
seq1、seq2、seq3在同一时刻发起传送请求,通过’uvm_do_prio_with的宏,在发送sequence时可以传递优先级参数。由于将seq1与seq2设置为同样的高优先级,而seq3设置为较低的优先级,这样在随后的UVM_SEQ_ARB_STRICT_FIFO仲裁模式下,可以从输出结果看到,按照优先级高低和传送请求时间顺序,先将seq1和seq2中的item发送完毕,随后将seq3发送完。除了sequence遵循仲裁机制,在一些特殊情况下,有一些sequence需要有更高权限取得sequencer的授权来访问driver。例如在需要响应中断的情况下,用于处理中断的sequence应该有更高的权限来获得sequencer的授权。
七、Sequencer的锁定机制
uvm_sequencer提供了两种锁定机制,分别通过lock()和grab()方法实现,这两种的方法区别在于:
lock()与unlock()这一对方法可以为sequence提供排外的访问权限,但前提条件是,该sequence首先需要按照sequencer的仲裁机制获得授权。而一旦sequence获得授权,则无需担心权限被收回,只有该sequence主动解锁它的sequencer,才可以释放这一锁定的权限,lock()是一种阻塞任务,只有获得了权限才会返回。
grab()与ungrab()也可以为sequence提供排外的访问权限,而且它只需要在sequencer下一次授权周期时就可以无条件地获得权限。与lock方法相比,grab方法无视同一时刻内发起传送请求的其它sequence,而唯一可以阻止它的只有已经预先获得授权的其它lock或者grab的sequence。
如果sequence使用了lock()或者grab()方法,必须在sequence结束前调用unlock()或者ungrab()方法来释放权限,否则sequencer会进入死锁状态而无法继续为其余sequence授权。
示例
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("LOCK", "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
输出结果:
对于sequence locks,在10ns时它跟其它几个sequence一同向sequencer发起请求,按照仲裁模式,sequencer先后授权给seq1、seq2、seq3,最后才授权给locks。而locks在获得授权之后,就可以一直享有权限而无需担心权限被sequencer收回,locks结束前,需要通过unlock()方法返还权限。
对于sequence grabs,尽管在20ns时就发起了请求权限(实际上seq1、seq2、seq3也在同一时刻发起了权限请求),而由于权限已经被locks占用,所以它也无权收回权限。因此只有当locks在40ns结束时,grabs才可以在sequencer没有被锁定的状态下获得权限,而grabs在此条件下获取权限是无视同一时刻发起请求的其它sequence的。同样的在grabs结束前,也应当通过ungrab()方法释放权限,防止sequencer的死锁行为。
————————————————
原文链接:https://blog.youkuaiyun.com/qq_39794062/article/details/114272496