UVM--Sequencer和Sequence

1 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; 
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"); //创建uvm_sequence_item
//send child sequence via start () 
cseq.start(m_sequencer, this);  
//将child_sequence 挂载到sequencer上 
//执行child_sequence_body 实际上执行的是里边的最小颗粒度item
//m_sequencer表示要挂载到m_sequencer
//this表示parent sequence,此处表示top_sequence,
//给出parent sequence表示与顶层的优先级一致,自动为100,用于仲裁
// send sequence item 
void'($cast (req, tmp));
start_item(req);  //item挂载                
req.randomize with {data == 20;}; 
finish_item(req); //item挂载
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", $sforrnatf ("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 testl extends uvm_test;
env e;
`uvm_component_utils(testl) 
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 挂载到 sequencer 上的应用。

uvm_sequence: :start(uvm_sequencer_base sequencer, 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, 用户也可以指定优先级数值。第四个参数建议使用默认值, 这样的话 uvm_ sequence::pre _ body()和 uvm_sequence::post_ body()两个方法会在 uvm_sequence::body()的前后执行。 在上面的例子中,child_seq 被嵌套到 top_seq 中, 继而在挂载时需要指定 parent_sequence; 而在 test 一层调用 top_seq 时, 由于它是 root sequence, 则不需要再指定 parent sequence, 这一点大家需要注意。另外,在调用挂载 sequence 时, 需要对这些 sequence 进行例化

第二种发送方法是针对将 item 挂载到 sequencer 上的应用。

uvm _sequence: : start_item (uvm _sequence_ item item, int set_priority = -1, 
uvm_sequencer_base sequencer=null); 
uvm_sequence: :finish_item (uvm_sequence_item item, int set_priority = -1);

对于 start_item()的使用, 第三个参数用户需要注意是否要将 item 挂载到 “ 非当前 parent sequence 挂载的 sequencer"上面,有点绕口是吗?简单来说,如果你想将 item 和其 parent sequence 挂载到不同的 sequencer 上面,你就需要指定这个参数。默认情况下,”父"(sequence) 与"子"(item) 都是走的一条道路 (virtual sequence 除外)。 在使用这一对方法时, 用户除了需要记得创建 item, 例如通过 uvm_ object: :create()或 uvm_sequence::create _item(), 还需要在它们之间完成 item 的随机化处理。 从这一点建议来看, 需要读者了解到, 对于一个 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_ i tem_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, 那么就不会这样执行, 所以在一些场景中, UVM 用户会奇怪为什么pre_body()和 post_body () 没有被执行。 在这里, pre_body()和 post_body()并不是一定会被执行,这一点同 UVM 的 phase 顺序执行是有区别的。建议是,用户可以在 base_sequence 中自定义一些方法,确保它们会按照顺序执行,比如下面这段例码,用户可以分别在 user_pre_ body()、user_post_ body()和 user_body0中填充代码,确保这些方法会被顺序执行,或也可以考虑使用 pre_start()和 post_start()这两个预定义的方法。

virtual task user_pre_body ();
endtask 
virtual task user_post_body(); 
endtask 
virtual task user_body (); 
endtask 
virtual task body();
user_pre_body(); 
user_body (); 
user_post_body(); 
endtask

下面一段代码是对 start()方法执行过程的自然代码描述, 大家可以看到它们执行的顺序关系和条件: 

对于 pre_do()、mid_do()、post_do()而言,子 一级的sequence/item在被发送过程中会间接调用parent sequence的pre_do()等方法。

  •    正是通过几个sequence/item宏来打天下的方式,用户可以通过'uvm_ do/'uvm _do_ with 来发送sequence或item。这种不区分对象是sequence还是 item的方式, 带来了不少便捷,但容易引起验证师的惰性。在使用之前,需先了解它们背后的sequence和 item 各自发送的方法。
•    不同的宏可能包含创建对象的过程也可能不会创建对象。例如'uvm_do/、uvm _do_ with 会创建对象, 而'uvm_send 则不会创建对象, 也不会将对象做随机处理, 因此要了解 它们各自包含的执行内容和顺序。
•    此外还有其他的宏, 可以在 UVM 用户手册关于 sequence 的宏部分深入了解。例如, 将优先级作为参数传递的 'uvm_do_pri/'uvm_do_on_prio等, 还有专门针对 sequence 的uvm_create_ seq/'uvm _do_ seq/、uvm_do_ seq_ with等宏。 不过, 我们在列表中给出的宏已经可以满足大多数的场景应用, 而且整齐统一,便于用户记忆和使用, 所以我们在这里不再对其他一些宏做额外说明。

class child_seq extends uvm_sequence;
`uvm_object_utils(child_seq)
...
task body();
bus_trans req; 
`uvm_create(req)
`uvm_rand_send_with(req, {data == 10;})
// =`uvm_do _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。所以如果用户想实现类似 fork-join_any 或fork-join_none 的发送顺序, 还应节在使用 disable 前, 对各个 sequence 线程的后台运行保持关注, 尽量在发送完 item 完成握手之后再终止 sequence, 这样才能避免 sequencer 被死锁的问题。

•    如果用户要使用 fork-join 方式, 那么应当确保有方法可以让 sequence 线程在满足 一些条件后停止发送 item。否则只要有一个 sequence 线程无法停止, 则整个 fork-join 无法退出。面对这种情况, 仍然需要用户考虑监测合适的事件或时间点,才能够使用 disable 来关闭线程。

sequencer的仲裁特性及应用

uvm_ sequencer 类自建了仲裁机制用来保证多个 sequence 在同时挂载到 sequencer 时, 可以按照仲裁规则允许特定 sequence 中的 item 优先通过。在实际使用中, 我们可以通过uvm_sequencer: :set_ arbitration(UVM _ SEQ_ ARB_TYPE val)函数来设置仲裁模式,这里的仲裁模式UVM_SEQ_ ARB_ TYPE有下面几种值可以选择:

•    UVM _ SEQ_ ARB _FIFO: 默认模式。 来自于 sequence 的发送请求, 按照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的请求被优先授权。

在上面的仲裁模式中,与priority有关的模式有UVM_SEQ_ARB_WEIGHTED、 UVM_SEQ_ARB_STRJCT_FIFO和UVM_ SEQ_ ARB_ STRICT_ RANDOM。这三种模式的区别在于,UVM_SEQ_ARB_WEIGHTED的授权可能会落到各个优先级 sequence的请求上面,
而UVM_ SEQ_ARB _ STRJCT _ RANDOM则只会将授权随机安排到最高优先级的请求上面, UVM_ SEQ_ARB _ STRJCT _FIFO则不会随机授权,而是严格按照优先级以及抵达顺序来依次
授权。没有特别的要求,用户不需要再额外自定义授权机制,因此使用UVM_SEQ_ARB _ USER这一模式的情况不多见, 其他模式可以满足绝大多数的仲裁需求。

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 top_seq extends uvm_sequence;
...
task body();
child_seq seql, seq2, seq3; 
m_sequencer.set_arbitration (UVM_SEQ_ARB_STRICT_FIFO); 
fork 
`uvm_do_pri_with(seql, 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",
          reg.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   

//输出结果:
UVM_INFO@ 0: uvm_test_top.e.drv [DRV] got a item 16 from parent sequence seql 
UVM_INFO@ 0: uvm_test_top.e.drv [DRV] got a item 22 from parent sequence seq2 
UVM_INFO@ 0: uvm_test_top.e.drv [DRV] got a item 19 from parent sequence seql 
UVM_INFO@ 0: uvm_test_top.e.drv [DRV) got a item 23 from parent sequence seq2 
UVM_INFO@ 0: uvm_test_top.e.drv [DRV] got a item 33 from parent sequence seq3 
UVM_INFO@ 0: uvm_test_top.e.drv [DRV] got a item 32 from parent sequence seq3

上面的例码中 , seq1、seq2、seq3在同一时刻发起传送请求 ,通过uvm_ do_prio_with的宏, 在发送sequence时可以传递优先级参数。 由于将seq1与seq2设置为同样的高优先级, 而seq3设置为较低的优先级 , 这样在随后的UVM_SEQ_ARB_STRlCT_FIFO仲裁模式下, 可以从输出结果看到, 按照优先级高低和传送请求时间顺序, 先将seq1 和seq2中的 item发送完毕,随后将seq3发送完。除了sequence遵循仲裁机制,在一些特殊情形下,有 一些sequence需要有更高权限取得sequencer的授权来访问driver。例如, 在需要响应中断的情形下, 用于处理中断的sequence应该有更高的权限来获得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授权。 下面给出一段例码 , 展示如何使用上述方法实现锁定的 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;
#l0ns; 
m_sequencer.lock (this) ;
`uvm_info ("LOCK", "get exclusive access by lock()", UVM LOW)
repeat (3) 
#l0ns
`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) 
#l0ns
`uvm_do_with(req, {data inside { (200: 210)};})
m_sequencer.ungrab(this); 
endtask 
endclass 

class top_seq extends uvm_sequence; 
...
task body();
child_seq seql, seq2, seq3;
lock_seq locks; 
grab_seq grabs;
m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO); 
fork 
`uvm_do_pri_with(seql, 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  

//输出结果
UVM_INFO@ 10000: uvm_test_top.e.drv [DRV] got a item 16 from parent sequence seql 
UVM_INFO@ 10000: uvm_test_top.e.drv [ORV] got a item 22 from parent sequence seq2 
UVM_INFO @ 10000: uvm_七est_top.e. sqr@@ top _seq. locks [LOCK] get exclusive access by lock () 
UVM_INFO@ 10000: uvm_test_top.e.drv [DRV] got a item 33 from parent sequence seq3 
UVM_INFO@ 20000: uvm_test_top.e.drv [ORV] got a item 108 from parent sequence locks 
UVM INFO@ 30000: uvm_test_top.e.drv [DRV] got a item 110 from parent sequence locks 
UVM_INFO@ 40000: uvm_test_top.e.drv [DRV] got a item 101 from parent sequence locks 
UVM_INFO @ 40000: uvm_test_top.e.sqr@@top_seq.grabs [GRAB] get exclusive access by grab () 
UVM_INFO@ 50000: uvm_test_top.e.drv [DRV] got a item 203 from parent sequence grabs 
UVM_INFO@ 60000: uvm_test_top.e.drv [DRV] got a item 202 from parent sequence grabs 
UVM_INFO@ 70000: uvm_test_top.e.drv[DRV] got a 工tern 204 from parent sequence grabs 
UVM_INFO@ 70000: uvm_test_top.e.drv [DRV] got a item 19 from parent sequence seql 
UVM_INFO@ 70000: uvm_test_top.e.drv [DRV] got a item 23 from parent sequence seq2 
UVM_INFO@ 70000: uvm_test_top.e.drv [DRV] got a item 32 from parent  sequence seq3

 结合例码和输出结果,我们从中可以发现如下几点:

•    sequence locks在10 ns时与其他几个sequence 一同向sequencer发起请求, 按照仲裁模式,sequencer授权给seq1、seq2、seq3, 最后才授权给locks。locks在获得授权后,就可以 一直享有权限而无须担心权限被sequencer收回,locks结束前, 用户需要通过 unlock()方法返还权限 。
•    对于sequence grabs, 尽管它在20 ns时就发起了请求权限(实际上seq1、seq2、seq3 也在同 一时刻发起了权限请求), 而由于权限已经被locks占用, 所以它也无权收回权限。 因此只有当locks在40ns结束时, grabs才可以在sequencer没有被锁定的状态下获得权限,而grabs 在此条件下获取权限是无视同 一时刻发起请求的其他 sequence 的。 同样地,在 grabs 结束前,也应当通过ungrab()方法释放权限, 防止 sequencer的死锁行为。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

创芯人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值