一、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
的死锁行为。
———————
作者:煎丶包
来源:CSDN
原文:https://blog.csdn.net/qq_39794062/article/details/114272496
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:
CSDN,CNBLOG博客文章一键转载插件