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