UVM入门进阶7:Item_Sequence_Sequencer_Driver

参考文档链接:https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/

本节目录:

  • Item, Sequence, Sequencer, Driver介绍
  • Item, Sequence, Sequencer, Driver两两之间的关系与常见操作
  • 如何构建一个测试序列(sequence)

背景:

小白第一年可能不会直接写验证环境,而是写一些seq和test

对于小白来讲第一年工作基本就两件事:

  • (第一年)构建seq,使用这些宏
  • (第一年)定义功能覆盖率,对他们进行收敛

第二年创建环境:

  • (第二年)创建环境,你可以把实验代码照搬过工作中去修改

1 新手上路

1.1 前言

run_phase

  • 到我们的run_phase了

  • driver,monitor,checker,reference_model的run_phase都是些forever,和sv里面的run任务相同,它们不会停下来,sv里面只有generator会停下来

  • 这节课我们会讲,在run_phase我们的激励怎么一步一步把item通过sqr发送到dri一侧

1.2 四个核心词介绍

本章主要讲四个核心词的作用、分类以及之间的互动关系

  • sequence item
  • sequence
  • sequencer
  • driver

1.2.1 trans驱动作用

整个事物的驱动力就是transaction的传送

1.2.2 四者关系打比方

graph TB
sequence --> 道路
sequence_item --> 货车
sequencer --> 目的地关卡
driver --> 目的_进行扫描与分解

通路结构图:

  • sqr到driver是通过req与rsp

1.2.3 sequencer item

sequence item是driver与DUT每一次互动的最小颗粒度

sequence item是一个类

  • 内部可定义成员方法或变量,成员变量应考虑是否需要随机化

1.2.4 driver:一个例子

一个例子:假设DUT是slave端,driver扮演master访问DUT寄存器

  • item定义的数据信息包括:地址、命令码、数据和状态值(访问是否成功,V1V2的MCDF不涉及状态码,UVM实战会完善)
  • driver任务:
    • driver取得后通过时序方式在initerface一侧发起激励至DUT
      • 这种时序需要保持几个时钟(按照总线做寄存去访问的习惯)

1.2.5 sequence 的层次化

sequence内部包括item可以包括sequence,从而sequence一层一层实现层次化

1.2.6 sequencer

  • sequencer是sequence与driver之间的桥梁,使用TLM进行通信,通信参数为sequence_item类
  • sequencer的相关控制方法都在uvm_sequence里面

1.2.7 driver

driver特点:

  • 永远喂不饱,forever驱动
  • 不会修改item值

1.2.8 sequence的互动

1.3 继承关系

1.3.1 uvm_sequence是obj

uvm_sequence_item和uvm_sequence都继承于uvm_object,不同于comp只在build_phase里创建和配置,obj可以在任何时候穿几件

uvm_sequence与uvm_comp继承树

1.3.2 提示:sequence继承obj而带来的特点

(1)你无法预测run阶段什么时候会创建sequence并挂在到sqr上,也无法通过phase机制识别sequence 的运行阶段

(2)obj独立于build阶段外,所以可以动态挂在

(3)seq无法在顶层配置的时候按照层次关系直接进行配置

(4)seq必须挂载到sqr,并依赖于sqr的结构关系来间接获取顶层配置信息

1.3.3 提示

(1)新手:将时序控制的权力给seq,即seq产生item,seq交给driver

(2)明确职责的情况:seq应该只负责生成item

(3)item生成和传送并不代表最终的接口时序,决定接口时序的是sqr和dri。进而引申出一个问题,为什么要有sqr在seq和dri之间

  • sqr既是关卡也是路由
  • sqr是一个组件,可以通过TLM端口传送item
  • sqr有仲裁机制,处理多个并行seq

1.4 总结:数据传送机制

(1)数据传送机制是get模式(关于put和ge模式参考TLM)

  • sv里面是generator产生item,stimulator/driver去get
  • uvm也是get模式,driver是initiator,是get模式

如图:

通信模式

数据传输方向:

(2)为什么是get模式:

  • 效率较高:seq产生item可以直接到达sqr,穿过sqr到达dri即可结束传输;put还需要收到返回值才可以发起下一次的传输
  • seq的仲裁特性:仲裁特性更符合设计思维:

2 Sequence和Item

2.1 概述(引入)

(3)

  • seq指的是:uvm_sequence类

  • item指的是:uvm_sequence_item类

(4)环境中至少有一个seq,不能仅仅一个item就可以想当然的发送到sqr

2.2 sequence item 介绍

item继承于uvm_object,具备obj核心方法:copy()clone()compare()record()

item内部数据成员可分为以下几类:

  • 控制类:如总线协议上的读写类型、数据长度、传送模式
  • 负载类:一般指数据总线上的数据包
  • 配置类:用来控制driver的驱动行为,例如命令driver的发送间隔或者有无错误插入
    • config_db只能配置一次driver
    • item控制
  • 调试类:用来标记一些额外信息方便调试,例如该对象的实例序号、创建时间、被driver解析的时间始末等

2.3 item的简单例子

sqr没有接dri与seq,仅仅看item是怎么回事

item定义

test

这里用new创建obj,复习一下create的好处:

  • override
  • 建立层次化关系
  • comp不可以new?

输出结果

t1没有随机化,t2随机化了

2.4 item使用特点

(1)数据成员为rand形

(2)使用`uvm_field_xxx宏进行数据成员声明,便于域自动化

(3)seq的body()很重要,和run类似

(4)item生命周期:始于seq的body(),穿越sqr,最终到达dri并被笑话

  • item要善用copy(),clone()等数据方法

2.5 item与seq的关系

(1)seq控制item实例的随机化,因此内部应预留可供外部随机化的变量,通过层级传递进seq进而控制item,控制item的内容有

  • item内部随机变量
  • item之间的组织和时序控制

2.6 三类常见的seq定义方式:

  • 扁平类(flat sequence):最简单的,只组织最小颗粒度即item

  • 层次类(hierarchical sequence):更高层seq包括底层seq

  • 虚拟类(virtual sequence):工作后经常用的,控制整个测试场景中不同的seq

2.7 三类seq定义:Flat Sequence 介绍

2.7.1 介绍

2.7.2 代码示例一

seq

  • body就像组件中的run一样,因为obj不参与phase,定义了body你不需要手动运行就和你不需要手动run一样
  • 数据的随机化:

test

这里为什么手动执行body(),因为这不是个完整的代码,完整的应该是由挂载到的sqr自动运行

示例总结

(1)暂时没有用seq宏

(4)示例一颗粒度较小没有使用item

2.7.3 代码示例二:加大颗粒度(使用item)

trans

seq更多的去做一些控制的任务

  • seq更加简洁

  • 多的去做一些控制的任务,粒度由trans控制

  • 只需要例化一个trans实例

test没有什么大的变化

输出结果

2.7.4 如何去定义一个item,粒度多少合适?

简单来讲:seq里面包含多个item

2.8 三类seq定义:Hierarchical Sequence 介绍

2.8.1 介绍

  • 嵌套seq好处是可以创建更丰富的激励场景
  • hierarchical seq可以嵌套:hierarchical seq,flat seq,item

2.8.2 代码示例:trans与flatseq基于2.7.3

hier_seq

宏做了三件事:

  • 创建了seq和item
  • 完成了随机化
  • 传送到sqr上

2.8.3 示例总结

(1)示例代码宏的三个作用

(3)示例中既有串行的激励关系,也有并行的激励关系

更复杂的场景中可以加入seq/item之间的时序关系:

  • 事件同步
  • 一定的延迟关系

3 Sequencer和Driver

3.1 概述

3.1.1 数据传输概述


  • seq与dri是组件,组件之间的通信依赖于TLM端口

  • 数据传送机制是get模式(关于put和ge模式参考TLM)

    • sv里面是generator产生item,stimulator/driver去get

    • uvm也是get模式,driver是initiator,是get模式


seq与dri的端口结构图:双向的数据传输

3.1.2 端口

(1)端口:

UVM专门提供便于item传输的端口,供seq与driv使用:

1
2
3
uvm seq_item pull_port #(type REQ=int, type RSP=REQ)
uvm_seq_item_pull_export #(type REQ=int, type RSP=REQ)
uvm_seq_item_pull_imp #(type REQ=int, type RSP=REQ, type imp=int)

(2)端口分类:

由于driver是请求发起端,所以在driver一侧例化了下面两种端口,一般我们只会使用其中一种

1
2
uvm_seq_item_pull_port #(REP, RSP) seq_item_port//一般只使用这个,这个端口既可以get req又可以put rsp
uvm_analysis_port #(RSP) rsp_port//广播模式端口,一段对多段,专门广播rsp,一般Monitor会用

sequencer一侧则为请求的响应端,在sequencer一侧例化了 对应的两种端口:

1
2
uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export//名字export实际上是imp
uvm_analysis_export #(RSP) rsp_export
  • 为什么是export实际上是imp?:
    • 因为sqr里面有fifo,对外显示的是export但已经是最后一个组件了,对内还需要连接
    • 如图所示:
    • 补充sqr只有rsp的fifo没有req的fifo

回忆三种port

3.1.3 端口的连接

dri与sqr的端口成对组合,一共两对,进行连接:

  • 第一对:
    • uvm_seq_item_pull_port #(REP, RSP) seq_item_port
    • uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export
  • 第二对:
    • uvm_analysis_port #(RSP) rsp_port
    • uvm_analysis_export #(RSP) rsp_export

两组端口都可以实现rsp发送,但方式不一样


(1)通常使用第一对就够了,即:driver::seq_item_port.connect(sequencer::seq_item_export)

3.1.4 第一对端口的方法(需要反复听)

在driver一侧,通过seq_item_port调用这一种类型的TLM端口的方法:

  • task get_next_item(output REQ req_arg):采取blocking的方式等待从sequence获 取下一个item
    • 常用,blocking即阻塞的方式
  • task try_next_item(output REQ req_arg):采取nonblocking的方式从sequencer获 取item,如果立即返回的结果req_arg为null,则表示sequence还没有准备好
    • nonblocking即非阻塞
  • function void item_done(input RSP rsp_arg=null):用来通知sequence当前的 sequence item已经消化完毕,可以选播性地传递RSP参数,返回状态值
    • 不可缺少,完成一个完整的握手
  • task wait_for_sequences():等待当前的sequence直到产生下一个有效的item
    • 用的较少
  • function bit has_do_available():如果当前的sequence准备好而且可以获取下一个有 效的item,则返向1,否则返回0
  • function void put_response(input RSP rsp_arg):采取nonblocking方式发送 response,如果丽]返回1,否则返回0

  • 也可以用传统的:

    • task get(output REQ req_arg):采用get方式获取item

    • task peek(output REQ req_arg):采用peek方式获取item

    • task put(input RSP rsp_arg):采取blocking方式将response发送回sequence

    • 为什么有这三个port?

      • uvm seq_item pull_port,uvm seq_item pull_export,uvm seq_item pull_imp这三个port继承于之前的tlm_port,这些port保留下来

经常成对出现的方法:

  • get_next_item与item_done

put rsp独立出现


3.1.5 sqr与dri参数类的REQ与RSP的类型参数

参数类声明原型:

参数类内部的类型一致,缺省值为uvm_sequence_item,即自定义的Item的父类类型


类型转换:

(1)如上所说缺省时拿到的是父类类型,需要动态进行类型转换

(2)类型参数为自定义类时不用在意


类型保持一致的作用:

把rsp反馈到sqr的四种方法:

  • put(rsp)//put rsp
  • put()//put方法
  • rsp_port::write(RSP)//rsp_port进行write写入
  • item_done

这四种方法最终都会写入到一个fifo里面

3.2 事务传递实例

(1)item(trans) 和 flat_seq

flat_seq:

  • body
    • 调用seq里面的函数create_item创建了一个item,创建时参数包括item的类型,被挂载到的sqr, (补充:用new也能)
    • 父类句柄转换:void`($cast(req, tmp)),记住这种类型转换的方法(补充:如果只做randomize可以把子类也随机了,但这里要调用子类内的成员变量所以需要转换类型)

补充内容:

  • 为什么item也被挂载到sqr上
    • 实际上seq与Item都挂载到sqr上
    • 而自己定义的sqr又是uvm_sequence的成员变量
  • 一个完成的传输:
    • seq收到item_done才算结束,这里使用get_response(tmp)

(2)sqr:最简单的一个

继承于uvm_sequencer

只要进行一个注册就ok,最简单的一个,更多的握手关系在seq和dri一侧,sqr做路由作用

(3)driver

  • 关于REQ是哪来的,你忘了自己是个参数类了吗?这不是参数类的类型参数值吗?

    • 这里没有进行参数指明,所以直接用默认了,就是没有下图红字部分
  • run_phase

    • 通过port拿到item:seq_item_port.get_next_item(tmp)

    • item父类转子类

    • 子类克隆方法(返回父类句柄)

    • 克隆的对象的父类句柄转子类

    • req的id交给rsp的id:rsp.set_sequence_id(req.get_sequence_id())

      • seq_id是什么时候产生的?item经过sqr时被打上

      • seq_id有什么用?用于识别不同seq

      • 为什么要手动set?id没有进行域的自动化,克隆的时候不会复制id,克隆只关心数据部分而id不是数据部分

      • 传输结构图:

    • item_done(rsp句柄),让seq结束

      • 即便没有rsp句柄(即没有rsp消息)也是一个完整的数据传输过程
      • 如果没有rsp句柄,seq能否等到?
        • finish_item可以等到,因为等到的item_done
        • get_response(tmp)会卡住,因为fifo为空没有数据
          • 因此get_response一定要成对不然会卡住

(4)env

  • 成员区域

    • 对sqr和drv进行例化
  • build

    • 对sqr和drv进行创建
  • connect

    • 对第一个端口类进行连接,这就够了

(5)test

build:

  • sqr的创建

run_phase控制各个run_phase:

  • 挂起objection防止退出

  • 例化flat_seq

    • flat是Obj是在运行过程中动态产生的所以不在build里面例化
  • flat_sep挂载到sqr上

  • 结束挂起结束运行

(6)输出结果

3.3 【重要】各个模块的动作总结

flat_seq:

1
2
3
4
5
create_item()
start_item()
randomize()
finish_item()
*get_response()

driver以及之后的逻辑:

高层次端口的连接(env和test)

(1)env端口连接,使用一种端口进行成对连接就行

(2)

  • 记得在test中挂起objection防止提前退出

  • 挂载seq到sqr使用uvm_sequence::start(SEQUENCER)完成

3.4 【重要】通信时序

3.4.1 通信时序

3.4.2 seq与sqr的细节补充

(1)seq起点是create

(2)sqr的仲裁机制根据dri的get_next_item来的

(3)sqr在获取item之前item应该完成随机化


3.4.3 握手建议、类型问题

  • 主要讲克隆和ID一部分的内容,可以参考3.2driver的run_phase

  • 以及rep和rsp使用一个item所带来的危害


  • seq与driver类型完全保持一致

  • 如何修改底层的sequence item类型?override

4 Sequencer和Sequence:初步

4.1 概述

4.1.1 主要学习内容

主要讲几种常见的仲裁方式,一般可能用不太到

4.1.2 sequence宏概述:区分两种挂载seq到sqr方式

  • start()
    • 在test对top_seq挂载(参考第三章),在top_sep对child_sep和item挂载,在child_sep对item挂载
  • `uvm_do
    • 一种宏:参考第五章

4.2 sequence和item发送示例

bus_trans

child_seq

top_seq

包含child_seq与item

sqr

driver

driver里面get的全是最小颗粒度item,seq在sqr处被拆解

env

test与输出结果

4.3 示例总结:两种挂载方式

两种挂载方式,分别把seq和item挂载到sqr上

4.3.1 把seq挂载到sqr上:这种挂载发生在top_seq上

top_seq中的uvm_sequence::start()

  • start内部参数解释
    • uvm_sequencer_base sequencer:
      • 挂载到的sqr实例句柄
    • uvm_sequence_base parent_sequence:
      • 上层对象句柄,示例中top_seq调用该方法时代入”this”代表child_seq的上层对象为自己
      • 这样做的好处是保持子对象优先级一致,进而对仲裁有帮助仲裁表现一致
    • int this_priority:
      • 优先级,默认值-1,此时实际优先级为100
    • call_pre_post:
      • 建议使用默认值
        • 补充:body的pre和post回调函数不建议实现

4.3.2 把item挂载到sqr上

第二个参数不需要特别关心,往往知道挂载哪个item就够了

4.3.3 item创建发送过程

pre_do(),mid_do(),post_do():不建议实现,会变复杂


4.3.4 seq/item的start()挂载内部逻辑

sequence挂载到sequencer

为什么可以不关心:有时候call_pre_post=1时这些post和pre都不会执行

item挂载到sequencer

start_item立即返回,因为只有一个sqr进行仲裁可以立刻返回结果

5 Sequencer和Sequence:更简单的创建挂载传输方式:宏

图中宏省略了`

图中宏省略了`

这些宏封装了好多方法函数,x表示包括,空表示不包括

Item:

  • 宏解释:

    • `uvm_do:是一个完整的过程,但直接randomiztion
    • `uvm_do_with:做一个带constraints的randomiztion
    • `uvm_create:就是一个创建
    • `uvm_send():就是发送
  • 执行顺序解释:

    • body为空,因为item没有

Sequence:

  • 宏解释:

    • `uvm_do:是一个完整的过程
      +
    • `uvm_do_with:做一个constraints的randomiztion
    • `uvm_create:就是一个创建
    • `uvm_send():就是发送
  • 执行顺序解释:

    • 没有做sync和post-sync
      • 因为sync和post-sync需要在body里面拿到优先级,`uvm_do本身不会拿到优先级,只有在seq_body里面,只有在发送item时候才会拿到(即每次发送一个item的时候都会申请一次优先级)

宏的调用位置

  • 这些宏调用的都是 sequence方法,因此只有sequence才可以调用这些宏

宏带来的怠惰

5.2 其他的宏

  • 将优先级作为参数传递的`uvm_do_pri/`uvm_do_on_prio等宏
    • 并行发送多个seq时,伴随优先级发送
    • 形参优先级,默认为100
    • `uvm_do_on:这个on是什么意思,表示把这个特定的seq挂载到某一个sqr上面
      • 对于item来说,使用`uvm_do则会被同样挂载到seq挂载到的sqr上
      • `uvm_do_on则可以进行指定
  • 专门针对seq的`uvm_create_seq/`uvm_do_seq/`uvm_do_seq_with等宏

5.3 序列宏的示例

child_seq

怎样利用宏发送的:

  • `uvm_create(req):创建item
  • `uvm_rand_send_with:添加constraint

上述宏可以合并为:

  • `uvm_do_with

top_seq

  • `uvm_do:这里发送了child_seq
  • `uvm_do_with:这里发送了item

5.4 序列宏的建议

(1)不管seq在哪个层次,都应在test结束前执行完毕

(2)可以用fork_join完成seq发送。而不建议用fork_join_any和fork_join_none,可能会导致seq没结束就立即退出,使得后台仍然有一些seq像挂载到sqr上面;进一步如果你针对fork调用disable可能会所锁sqr,因为你来不及释放seq线程权限。

  • 补充:你在代码中使用fork_join_any和fork_join_none一定要使用同步,如此处假如你使用了fork_join_any和fork_join_none你应该完成即便fork退出seq运行在后台时,仍然能与后台运行的seq进行同步,使得所有seq结束以后才进行后续的操作

(3)fork_join其中一个seq线程无法结束时,考虑在合适时间点使用disbale

5 Sequencer和Sequence:仲裁特性

5.1 Sequencer和Sequence的仲裁特性

5.1.1 仲裁特性介绍

仲裁场景结构图:

仲裁场景结构图


(1)sqr已经内建了仲裁机制,只需要在top_seq设置仲裁的模式就足够

(2)

如何设置:uvm_sequencer::set_arbitration(UVM_SEQ_ARB_TYPE val)

模式介绍:

  • UVM_SEQ_ARB_FIFO:默认模式,竞争优先
  • UVM_SEQ_ARB_WEIGHTED:优先级模式
  • UVM_SEQ_ARB_RANDOM:随机模式
  • UVM_SEQ_ARB_STRICT_FIFO:按照优先和抵达顺序授权
  • UVM_SEQ_ARB_STRICT_RANDOM:最高优先级并随机授权
  • UVM_SEQ_ARB_USER:自定义仲裁,很少用

5.1.2 Sequencer的仲裁示例

item和child_seq

top_seq和sqr

top_seq:

  • body:
    • 设置优先级模式
    • fork_join进行调度:
      • 三个同一时间申请的,seq1和seq2优先级更高
      • base是constraint
      • 此时赋予了优先级之后child_seq中的`uvm_do_with就可以进行挂在了,因为它们等待的就是这个顶层的优先级,使用wait_for_guant等待授权

driver

没有太多新意

env

test和输出结果

test:

  • 为什么test里面不用宏:因为用不了只能在seq里面用

为什么是seq1seq2seq1seq2seq3seq3,而且还是0时刻?:

  • 发送item没有耗时

示例分析

5.2 Sequencer的锁定机制介绍

(1)锁定机制

什么是锁定机制:seq拿到权限后进行锁定,

什么是锁定:seq拿到一个item后锁定下来,后续item都可以拿到

(2)两种锁定方式:两个函数都可以

  • lock()与unlock
    • 一定记得unlock
  • grab()与ungrab()
    • 优先级比lock高,下一次授权周期可以无条件获取授权

5.2.2 Sequencer的锁定示例

item、child_seq、lock_seq

child_seq:内部重复发送两次,间隔10ns,参考之前一节的示例

lock_seq:

  • 先等了10ns,试图达到等待授权把sqr锁住

  • m_sequencer.lock(this);

    • 什么时候才能lock住:下一次等待授权的时候,即大家都在等待的时候
  • 重复发送三次

grab_seq

grab_seq:

  • 等了20ns

  • m_sequencer.grab(this);

    • 什么时候才能锁住:只要重新做仲裁就能锁住
  • 重复发送三次

top_seq

seq1,seq2,seq3

lock_seq:优先级为300

grab_seq:没有优先级

输出结果


注释版:

结果解释

10ns:

  • seq1,seq2优先级500先发送sep3优先级300再发送(发送完等待10纳秒,重新排队伍)
  • lock_seq开始要权限

10ns-20ns:

  • seq发送

20ns:

  • seq1,2,3,lock都开始等待(lock因为优先级低所以也要等待)

20ns-40ns:

  • 被lock锁住

40ns:

  • grabs拿到权限(实际上20nsgrab就开始试图拿权限,但被lock控制,只能等到重新仲裁,届时即可直接锁住无需)

40ns-70ns:

  • 被grabs锁住

70ns:

  • seq1,seq2优先级500先发送sep3优先级300再发送

结果补充

seq优先级如果使用默认,不给定,则默认100

即直接:

  • `uvm_do_pri_with(seq)
  • `uvm_do_pri_with(lock)
  • `uvm_do_pri_with(grab)

5.2.3 结论

无论是lock或者grab都是在重新仲裁时才能锁住

6 Sequence的层次化

对于小白我们可能更关心如何让你的sequence层次化,如何理解别人写的层次化的sequence,以及把它们作为更顶层的sequence如何一层一层的调用

6.1 概述:水平复用和垂直复用

  • hierarchical sequence:层次序列,seq都挂载在一个sqr上
  • virtual sequence:seq可挂载在多个sqr上
  • layering sequence

6.2 Hierarchical Sequence层次序列

6.2.1 Hierarchical Sequence介绍

6.2.2 Hierarchical Sequence代码示例

cmd 和 item

clk_rst_seq

reg_test_seq

top_seq

reg_master_sequencer

driver

agent

  • 例化与连接

6.2.3 示例解析

6.2.4 总结

6.3 Virtual Sequence

6.3.1 Virtual Sequence介绍


把所有子系统的sqr都放在virtual_sequencer里面

  • virtual_sequencer包含着很多实例句柄
  • 它就像一个路由一样,找到他就能找到各个子系统的sqr
  • 起到一个统筹的作用
  • virtual sequencer往往与virtual sequence有联系的


virtual sequencer就是个简单的路由器,包含句柄而已,没有任何item经过,也不需要与driver连接

6.3.2 Virtual Sequence示例

vritual sequencer结构图

vritual sequencer结构图

【重要】有关挂载

mcdf_normal_seq(virtual sequence)要挂载到virutal sequencer

其他的seq间接的通过virutal sequencer挂载到不同的sqr上面,挂载的目的地不一样所以用uvm_do_on


virtual sequence

定义了一个sequence:实际上就是我们的virtual sequence

包括了好多seq句柄

sqr与vsqr区别:

  • 用了很多`uvmdoon
  • 有别于Hierarchical Sequence使用的`uvm_do

p_sequencer和m_sequencer的区别:

  • m_sequencer:是父类句柄,类型是uvm_sequencer
  • p_sequencer:是子类句柄,类型是`uvm_declare_p_sequencer(mcdf_virtual_sequencer)
    • 自定义出来的,不是与定义好的,定义语句就来源于`uvm_declare_p_sequencer(mcdf_virtual_sequencer)
    • 定义语句完成了两个步骤:
      • mcdf_virtual_sequencer p_sequencer;
      • $cast(p_sequencer, m_sequencer);//把父类句柄转为子类句柄
    • 为什么一定要子类句柄:
      • 因为父类的访问不到这些子系统sqr句柄
      • 通过这个子类句柄p_sequencer我们可以访问到vsqr里面所有成员变量

子一级的sqr,agent,和virutal sequencer

virutal sequencer 包含各个句柄就完事了

mcdf_env

千万不要忘了再connect里进行句柄传递,避免悬空

test

6.3.3 示例解析

6.3.4 总结

中心化协调

6.3.5 Virutal Sequencer建议

  • vseq与普通seq区分
  • vsqr同底层负责传输对象的sqr区分
  • vseq中使用`uvm_declare_p_sqr来创建p_sequencer变量方便之后索引
  • 顶层环境中创建vsqr并完成内部各个sqr句柄与底层sqr实例连接

6.4 Layering Sequence:层次化

6.4.1 Layering Sequence介绍

  • 普通协议,构建协议总线时通过:sequencer item包含与约束关系

  • 复杂协议,网络传输协议,需要更深层次一层一层解析,通过:若干个抽象层次去做

由高抽象级到低抽象级(传输层>>链路层>>物理层)的构建,我们称为Layering Sequence

接下来学习,一定考虑三个层次:高抽象级的item,低抽象级的item,中间做转化的sequence

Layering Sequence层次结构图:

Layering Sequence层次结构图


寄存器模型下的item传递结构图与layering sequence具体作用内容:

寄存器模型下的item传递结构图

  • 对寄存器的reg.write()和reg.read()指令,通过寄存器模型的Adaption Layer转化层次,进而转化为总线的一个item。即把reg_item转为bug_item
  • 得出layering sequence包括三者:高抽象级的item,低抽象级的item,中间做转化的sequence
  • 高抽象级的item与低抽象级的item没有直接的继承关系所以需要做一个映射
  • 高抽象级不包括具体的数据,只有类似命令、长度、idle等信息

6.4.2 Layering Sequence代码示例

Layering Sequence层次结构图


cmd和bus_trans(item)

比较底层

packet_seq

layer_trans(item)

比较抽象:

  • 没有任何与数据有关的传输内用,只有cmd,len,idle
  • 与底层的bus_trans没有任何继承关系所以要做一个映射

adapter_seq:转化层

  • p_sequencer通过`ucm_declare获得,通过p_sqr拿到up_sqr更上层的layersqr句柄
  • 拿到子类句柄void($cast(trans,req))

  • 转化为一个底层个seq,把高抽象级的长度转化为低抽象级的pkt_seq,`uvm_do挂载到了phy_driver上

  • 做一个握手,告诉高层,我已经把高抽象层的trans消化掉了

top_seq , layering_sqr , phy_sqr

layering_sqr , phy_sqr定义不讲了

phy_sqr里面有个句柄up_sqr,需要顶层把相关实例传递进来

top_seq:连续发送了两个layer_trans

phy_driver

phy_agent

包含phy_drv和phy_sqr的例化与连接

test,重点在这里,查看连接关系

  • 例化了layer_sqr与phy_agt
  • sqr句柄的传递,layer_seq/top_seq,adapter_seq,phy_seq的挂载
    • up_sqr句柄的传递
    • adapter_seq的挂载
    • top_seq的挂载
    • phy_seq的挂载实际上不是个挂载而是产生的过程,在adapter_seq用uvm_do出来好多pkg_seq,这些pkg_seq时加上就属于phy到drv的seq
  • 为什么用fork_join_none
    • 用fork_join_none直接挂载上去就可以了
    • 用fork_join不行,这个start会自动帮我们执行body(),adapter.body()是个forever会卡在这里

6.4.3 如何实现sequencer layer协议转换的方法:

(1)layer_trans,bus_trans完全两个不同的抽象级

(2)需要adapter_seq完成从获取layer_trans再去生成phy_trans和seq,再从physqr发送出去

(2)fork_join_none

(4)至于多少个层次取决于

6.4.4 总结

(2)只有高抽象级到低抽象级,低到高需要有回路,通过response_item实现,两者的思想实际上一样

(3)也可以不通过高到低的回路,而是外部通过monitor采集response trans最终实现返回,避开adapter