当我开始使用 UVM RAL 时,我无法理解 UVM 基类库对更新所需值和镜像值寄存器的值有什么看法。我还认为,所使用的术语没有准确反映其意图。花了一些时间后,我想出了一个表,帮助我了解寄存器模型 API 的行为,以及如何最好地调用它们。
在介绍该表之前,让我们看一下创建寄存器模型的过程:
创建寄存器格式规范
将规范转换为 UVM 寄存器模型
使用寄存器模型
创建寄存器格式规范:有许多寄存器格式可用于描述设计人员的寄存器规范。您可能熟悉广泛使用的 Synopsys RALF 格式。下图说明了使用 Synopsys Ralgen 工具将 RALF 格式转换为寄存器模型的流程。虚线表示您可以为不同的方法生成寄存器模型:
使用寄存器模型:寄存器模型具有一组用于所需寄存器值和镜像寄存器值的变量。该文档使用术语“所需”和“镜像”,但为了避免混淆,我在下面将它们称为“主”和“镜像”。镜像变量的目的是始终保存或表示 RTL 的值,以便它可以用作记分板。有一堆 API 可用于对这些变量进行操作。这里的目的是阐明在模拟期间调用任何这些 API 时主变量和镜像变量会发生什么情况。
让我们看一下可用的 API。我将它们分为三类:主动、被动和间接。
积极:物理事务在总线上执行读取和写入操作。Read()、write()、update() 和 mirror() 是使用物理接口在 DUT 上运行的活动 API。您可以选择使用后门机制,在这种情况下,它不会消耗模拟周期。您可以期待与使用前门访问时发生的相同的 RTL 寄存器行为.
被动:仅使用寄存器模型运行。set()、get() 和 predict() 是直接在模型上运行的被动 API。我也称 peek() 为被动,因为这不会在读取过程中更改寄存器值。例如,读取以清除寄存器 – 在执行 peek() 时不会被清除。
间接:有一组API间接地在DUT上运行,它们是peek()和poke()。请注意,peek() 和 poke() API 只是后门访问。虽然 poke 可以更新 RTL 寄存器,但它无法模拟物理读取期间可能发生的实际寄存器行为。例如,写一个要清除。
让我们简要介绍一下广泛使用的 API 定义。您可以在 UVM 类参考指南中找到更多详细信息。
读取():使用前门或后门访问从 DUT 寄存器读取值。
写():使用前门或后门访问更新 DUT 寄存器。
更新():如果使用 set() 更改了主寄存器变量中的任何值,则可以使用此方法(批量更新)在 DUT 中写入所有这些寄存器。您可以调用单独的 write() 方法来获得相同的结果。
镜像(): 镜像维护 DUT 寄存器值的副本。Mirror() 方法读取寄存器,如果启用了检查,则可以选择将读回值与当前镜像值进行比较。 镜像可以使用物理接口(前门)或 peek()(后门)机制执行。
躲猫猫():使用后门访问机制从 DUT 寄存器读取值。
Poke():使用后门访问机制将 DUT 寄存器写入指定值。
预测(): 可以使用此方法将镜像变量值更改为预期值。
我运行了一些实验,下表显示了从测试台执行任何这些 API 时寄存器模型和 DUT 中会发生什么。
缩写
UMV – 更新主变量, UMrV – 更新镜像变量, AP – 自动预测
RDR – 读取 DUT 寄存器, UDR – 更新 DUT 寄存器, RMV – 读取主变量
FD – 前门, BD – 后门, * – 检查是否使用了UVM_CHEK, NA – 不适用
要记住的几点
我没想到 peek() 和 poke() 方法会无条件更新镜像值。在查看了 UVM 源代码后,我发现 do_predit() 方法在 peek() 和 poke() 方法中被无条件调用。我还注意到,使用后门机制的 write() 和 read() 方法会在调用 do_predict() 时更新镜像寄存器,而无需检查此 get_auto_predict() 方法的输出。我看到唯一有条件调用的地方是具有前门访问权限的 write () 和 read() 方法。
在与专家讨论后,我了解到预期功能是确保镜像变量具有最新的寄存器值。类似地,使用后门访问的read()/write()更新镜像寄存器 — 这也是有意为之的。由于使用了后门程序,因此物理接口上不会观察到(当自动预测关闭时)来更新寄存器模型。因此,在所有情况下都必须对其进行更新。
(文章来源:Bernie DeLay)