IC验证学霸笔记4—UVM--Sequencer
1 Sequencer和Sequence
发送sequence及item的方法和宏 在这段例码中, 主要使用两种方法。 第一种方法是针对将 sequence 挂载到 sequencer 上的应用。
在使用该方法的过程中, 用户首先应该指明 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 上的应用。
第二种发送方法是针对将 item 挂载到 sequencer 上的应用。
对于 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。我们可以通过拆解这些步骤得到更多的细节:
这些完整的细节有两个部分需要注意。 第一, 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()这两个预定义的方法。
下面一段代码是对 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等宏。 不过, 我们在列表中给出的宏已经可以满足大多数的场景应用, 而且整齐统一,便于用户记忆和使用, 所以我们在这里不再对其他一些宏做额外说明。
• 不同的宏可能包含创建对象的过程也可能不会创建对象。例如'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等宏。 不过, 我们在列表中给出的宏已经可以满足大多数的场景应用, 而且整齐统一,便于用户记忆和使用, 所以我们在这里不再对其他一些宏做额外说明。
几点建议:
• 无论 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的仲裁特性及应用
• 尽朵避免使用 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这一模式的情况不多见, 其他模式可以满足绝大多数的仲裁需求。
• 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这一模式的情况不多见, 其他模式可以满足绝大多数的仲裁需求。
上面的例码中 , 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传送方式。
• 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传送方式。
结合例码和输出结果,我们从中可以发现如下几点:
• 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的死锁行为。
• 对于sequence grabs, 尽管它在20 ns时就发起了请求权限(实际上seq1、seq2、seq3 也在同 一时刻发起了权限请求), 而由于权限已经被locks占用, 所以它也无权收回权限。 因此只有当locks在40ns结束时, grabs才可以在sequencer没有被锁定的状态下获得权限,而grabs 在此条件下获取权限是无视同 一时刻发起请求的其他 sequence 的。 同样地,在 grabs 结束前,也应当通过ungrab()方法释放权限, 防止 sequencer的死锁行为。
注:优秀验证学员随堂笔记,已经征求到学生的同意,会持续给牛友们分享!
大家看完记得 一键三连!多多支持
查看11道真题和解析