本系列文章从秋招高频问题入手,深度剖析 UVM 寄存器模型,如有错误之处,欢迎批评指正!
联系邮箱:zhangshaopu@ufl.edu
高频问题 1:讲讲你理解的寄存器模型?
从本质上讲,UVM 通过一系列复杂的机制,在验证环境中 构建了一个【和 DUT 侧寄存器组完全等价】的寄存器模型
- 对于验证环境的 开发者 而言,集成寄存器模型是比较复杂的:不仅涉及到寄存器模型本身的开发,还要设计配套的 adapter 和 predictor 和 coverage model
- 对于验证环境的 使用者 而言,使用寄存器模型是十分便捷的:使用者只需要和验证环境中的寄存器模型交互即可,不需要关心总线层面是如何通信的;与此同时,UVM 还提供了一系列 内建的 sequence 用于寄存器的各项检查
因此,在实际的验证项目中,需要考虑到开发模型的成本:如果模块的寄存器组 非常简单,并且后续 没有迭代 的需求,那么就没有开发的必要
高频问题 2:讲讲寄存器模型的结构?
从寄存器模型内部看:
- 最基本的单元是 uvm_reg_field,多个 uvm_reg_field 组成一个 uvm_reg,多个 uvm_reg 组成一个 uvm_reg_block
- 其中,uvm_reg_block 通常作为寄存器模型的 顶层,它还可以包含其他的 uvm_reg_block
- 每个 uvm_reg_block 还包括一个 addresss map
从寄存器模型外部的配套组件看:
- adapter 是【map】和【DUT 总线 sequencer】之间 的桥梁
- predictor 是【map】和【DUT 总线 monitor】之间 的桥梁
高频问题 3:如何集成寄存器模型?
寄存器模型的集成包含三个步骤: 集成寄存器模型本身、集成 adapter 、集成 predictor
集成寄存器模型本身:
- 在 env 和 vsqr 中 包含寄存器模型的指针
- 在 base_test 中 例化寄存器模型
- 在 base_test 的 connect_phase 中 将指针赋值
这样,无论是验证环境的组件还是 sequence 都可以和寄存器模型交互
集成 adapter:
- 在 base_test 中 例化 adapter
- 在 base_test 的 connect_phase 中,通过寄存器模型的 set_sequencer 函数将 adapter 和 map、DUT 总线的 sequencer 联系起来
集成 predictor:
- 在 base_test 中 例化 predictor
- 在 base_test 的 connect_phase 中
- 给 predictor 的成员变量 map 和 adapter 赋值
- 将 DUT 总线的 monitor 的 TLM 端口 连接到 predictor 的 TLM 端口
高频问题 4:讲讲 adapter?两个重要函数?
adapter 充当的是 map 和 总线 sequencer 之间的桥梁:
寄存器模型无论读写,都会产生一个 uvm_reg_bus_op 类型的事务,这个 trans 如果想要被 总线 sequener 接收,势必要转换为总线 sequencer 对应的事务类型,这就是 reg2bus 的作用
反之,如果寄存器模型希望从总线 monitor 读回一些数据,需要将总线事务转换为 uvm_reg_bus_op 类型,因为寄存器模型同样无法直接处理总线事务,这就是 bus2reg 的作用
所以,adapter 需要通过 set_sequencer 关联特定的 map 和 sequencer,保证自己是它们中间的通信桥梁
高频问题 5:前门访问和后门访问的区别?
前门访问:在寄存器模型上做的读写操作,最终会 通过实际的总线 访问 DUT 的寄存器
后门访问:绕过实际总线,利用 DPI+VPI 的层次结构索引 直接操作 DUT 内的寄存器
区别 | 前门访问 | 后门访问 |
---|---|---|
时间上 | 由于是真实的物理操作,因此会 消耗仿真时间 | 不消耗仿真时间 |
调试上 | 任何前门访问都会 有波形文件,方便调试 | 没有波形文件 的记录,调试难度增加 |
应用上 | 能验证 总线协议本身 是否正确 | 大规模寄存器的 快速初始化 能够操作 只读寄存器 注入故障 |
高频问题 6:后门访问路径如何配置?
某个寄存器的后门访问路径通常拆分为两个部分:该寄存器所在的 block 的路径、该 reg 相对于这个 block 的路径
- 针对第一个部分,需要配置 uvm_reg_block 的路径,可以使用 configure、add_hdl_path;两者没有区别
- 针对第二个部分,需要配置 uvm_reg 的路径,可以使用 configure、add_hdl_path_slice;两者的区别在于,如果一个寄存器的域支持单独存取,则不能使用 configure 配置 hdl 路径
当然,针对 uvm_reg_block,还有一种配置 hdl 的方法:set_hdl_path_root,这个方法配置的 hdl 会 覆盖其他方法配置的 hdl
高频问题 7:有哪些前门访问和后门访问的方法?
先说最常见的四类:read、write、peek、poke
其中,read 和 write 既可以前门访问,又可以后门访问,只需要在使用的之后指定访问类型即可
// 前门访问
p_sequencer.p_reg_model.FLOW_CFG.read(status, data, UVM_FRONTDOOR);
p_sequencer.p_reg_model.FLOW_CFG.write(status, 0xffff_ffff, UVM_FRONTDOOR);
// 后门访问
p_sequencer.p_reg_model.FLOW_CFG.read(status, data, UVM_BACKDOOR);
p_sequencer.p_reg_model.FLOW_CFG.write(status, 0xffff_ffff, UVM_BACKDOOR);
peek 和 poke 是 UVM 针对后门访问内建的函数:相比于 read 和 write,无视寄存器的读写属性,更贴合后门访问的特性
p_sequencer.p_reg_model.FLOW_CFG.peek(status, data);
p_sequencer.p_reg_model.FLOW_CFG.poke(status, 0xffff_ffff);
还有两个特殊的函数:mirror 和 update,它们也可以读取或修改硬件寄存器
mirror() 会调用 read() 读取 硬件寄存器 的值,并更新 镜像值、期望值;它和 read 的区别在于,它不会返回读取的数值,只会返回读取的状态
update() 首先会检查 镜像值 和 期望值 是否一致,如果不一致,则调用 write() 修改 硬件寄存器 的值,并更新 镜像值;它和 write 的区别在于,它不需要人为指定写入的数据,因为写入的数据是 期望值
因此在实际使用中,经常和 set() 配套使用:首先通过 set() 修改 期望值,再通过 update() 将这个新的 期望值 更新到 DUT 和 镜像值 上
高频问题 8:讲讲镜像值、期望值?
UVM 源代码规定了每一个 uvm_reg_field 都有四个成员变量:复位值、随机值、镜像值、期望值
复位值
复位值通过 configure() 配置;在配置好寄存器模型之后,通常使用 reset() 对寄存器模型进行复位,实际上就是将【剩余三个值】全部更新为【复位值】
如果后续想修改复位值,可以使用 set_reset();如果想读取复位值,可以使用 get_reset() 函数
随机值
随机值是 唯一使用 rand 修饰 的成员变量;我们通常在 configure() 中配置 field 的随机属性
- 如果允许随机:调用 randomize 函数 就会更新 随机值 和 期望值
- 如果不允许随机:调用 randomize 函数 就会将 期望值 赋值给 随机值
镜像值和期望值
镜像值表示的是 当前硬件寄存器值的映射;期望值表示的是 user 期望硬件寄存器拥有的值;UVM 提供了 predict() 函数来修改这两个值,目的是 保证【寄存器模型中的镜像值和期望值】最大程度的等价于【硬件寄存器组】;这个函数通常不需要 user 去调用
- 如果 user 使用 后门访问,那么这个函数会在每一次后门访问之后 自动调用
- 如果 user 使用 前门访问,必须要打开 set_auto_predict(),这个函数才会在每一次前门访问之后 自动调用
高频问题 9:什么是预测?两种预测方式?
讲到预测,就要说一下每一个 uvm_reg_field 包含的两个值:镜像值 和 期望值;镜像值表示的是 当前硬件寄存器值的映射;期望值表示的是 user 期望硬件寄存器拥有的值
预测的目的就是:保证【寄存器模型中的镜像值和期望值】最大程度的等价于【硬件寄存器组】;预测的方式有两种:自动预测 和 显示预测
自动预测
自动预测是通过 UVM 内建的 predict() 完成的,这个函数的作用是 更新寄存器模型的【镜像值】和【期望值】:如果 user 使用 后门访问,那么这个函数会在每一次后门访问之后 自动调用;如果 user 使用 前门访问,必须要打开 set_auto_predict(),这个函数才会在每一次前门访问之后 自动调用
自动预测的缺点显而易见:如果 user 不通过寄存器模型指定的访问方式(前门、后门)去访问硬件寄存器,那么寄存器模型的值将无法更新;
显示预测
使用显示预测可以弥补自动预测的不足:只要寄存器配置总线上有活动,就可以通过 monitor 捕捉,再通过 predictor 更新到寄存器模型中;缺点就是工作量大,需要实现 monitor 并集成 predictor
一旦集成了 predictor,就可以关闭 set_auto_predict(),因为寄存器模型的更新行为 只和 monitor 有关,和 predict() 脱钩了;但仅限于前门访问,因为 monitor 本质上还是在监测总线上的行为;对于后门访问,仍然需要 predict() 来更新寄存器模型