一、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
的死锁行为。