夜雨聆风学习资料网

ARTICLE · 1231671

写 UVM driver,卡住的往往不是模板,而是「怎么驱动接口」

写 UVM driver,卡住的往往不是模板,而是「怎么驱动接口」
写 UVM driver 时,最容易踩进一个坑:
模板能搭出来,真正驱动接口的部分却不知道怎么写。
vif 声明好了,get_next_item() 也写好了,轮到 drive_one(),只能留一个空任务。
问题出在哪?一句话概括:
事务描述的是「要做什么」,接口协议规定的是「每一拍怎么做」。
driver 的职责,就是把这两部分连接起来。
这一篇不堆完整模板,我们先从一个最小的数据发送接口出发,把动手前该想清楚的事理顺。

一、先看接口,再写 driver

假设 DUT 有一个简单的输入接口:
信号
方向(相对 DUT)
含义
clk
输入
时钟
rst_n
输入
低有效复位
valid
输入
当前输入数据有效
data
输入
待接收的数据
ready
输出
DUT 可以接收数据
只有信号名称,还不足以写出驱动逻辑。我们先为这个例子约定几条规则:
时钟上升沿,valid 和 ready 同时为高,表示一次传输被接收;
发送方不能以 ready 已经为高作为拉高 valid 的前提;
发出有效数据后,如果握手尚未完成,发送方需保持 valid,并保持 data 不变;
复位期间,driver 将 valid 保持为低。
后面的讨论,都基于这个简单、非流水的驱动方案。

先确定协议边界

同样叫 valid、ready,不代表所有接口都采用完全相同的约定。实际工程应以规格说明为准。


二、一笔事务,怎样变成一次传输?

假设 sequence 提交了一笔事务:

发送数据 8'hA5。

这句话没有描述信号何时变化,也没说遇到反压怎么办。这些内容,需要 driver 补齐。
在本例中,一笔事务对应的处理过程是:
取得事务 → 驱动有效数据 → 等待握手 → 结束本次驱动 → 通知事务完成。
其中最需要注意的是「等待握手」。
举个例子:driver 已经送出 8'hA5,但 DUT 的 ready 连续两拍为低:
采样上升沿
valid
ready
data
结果
第 1 拍
1
0
A5
未接收,继续保持
第 2 拍
1
0
A5
未接收,继续保持
第 3 拍
1
1
A5
完成本次握手
前两拍虽然「数据已经放到线上」,但 DUT 并没有接收。
因此,driver 不能因为执行过一次信号赋值,就立即修改数据或撤销请求。
信号已驱动 和 传输已完成,需要分别判断。

三、vif 解决连接,clocking block 约定访问时刻

driver 里常见这样的声明:
virtual stream_if vif;
它提供了访问接口实例的句柄。但声明之后,vif 并不会自动连到顶层接口。
采用常见配置方式时,还需要通过 uvm_config_db 获取实际接口实例,并检查获取结果。这部分可以理解为两步:
先准备句柄,再建立连接。
连接建立以后,还有一个问题:driver 在什么时刻驱动信号?又在什么时刻观察 ready?
这就需要明确采样和驱动的时序约定。采用 clocking block 时,应结合它的时钟事件、输入采样偏移和输出驱动偏移来理解代码。
不要只看到代码里出现了 drv_cb,就以为时序问题全解决了。

检查接口时,至少看两件事

① driver 是否拿到了正确的接口实例?

② driver 的驱动与采样时刻,是否符合接口和 DUT 的约定?


四、driver 骨架里,哪些事不能漏?

明确接口以后,再回头写 UVM 结构,就很容易对应到具体职责:
明确事务类型
—— driver 从 sequencer 接收哪一种事务?本例至少需要知道待发送的数据;
声明并获取 vif
—— 保存接口句柄,配置阶段获取,失败时及时报错;
建立初始状态
—— 按协议处理复位和空闲,避免一开始就产生意外请求;
获取事务
—— 用 get_next_item() 取得待处理请求;
执行协议驱动
—— 把事务字段转换成接口信号,并等待约定的完成条件;
通知完成
—— 用与 get_next_item() 配对的 item_done() 完成事务握手。
结构写齐以后,再逐项检查是否真正实现——尤其是协议驱动部分,不能只留一个任务名。

五、item_done() 到底在通知谁?

item_done() 完成的是 driver 与 sequencer 之间的事务握手。
它本身不会检查 DUT 的运算结果,也不代表 scoreboard 已经比较通过。
在本文的简单驱动方案里,我们把「输入握手完成,并结束本次驱动」作为通知事务完成的条件。但在其他方案里,完成条件可能不同。
比如,DUT 接收了一笔输入请求后,还要很多拍才能输出结果。driver 可以按约定完成输入侧事务,后续结果由 monitor 观察,再交给 scoreboard 检查。
所以,要分清三个时刻:
时刻
表示什么
输入握手完成
DUT 已接收这次输入
driver 通知事务完成
driver 已完成这笔请求约定的处理
输出检查通过
实际结果满足参考模型或检查规则
它们可能先后发生。
写 driver 时,要先明确事务的完成条件;判断 DUT 是否正确,还需要后续观察和检查。

六、写完以后,先用三个场景检查

对这个最小接口,先检查三种情况:
场景一:ready 始终为高
—— driver 能否完成一次传输?握手后是否正确结束本次请求?
场景二:ready 连续几拍为低
—— 等待期间,valid 是否保持?data 是否稳定?
场景三:连续提交两笔不同数据
—— 第一笔是否被重复接收?第二笔是否被遗漏?两次事务通知是否与处理过程对应?
这些场景不复杂,却能直接暴露驱动时序里的问题。
测试通过后,再增加复位打断、随机反压等情况。复位发生在传输中途时如何处理,也需要事先定义清楚。

七、从这个接口,再回到 UART

理解了上面的过程,再看 UART TX 的输入侧,思路是一样的:先读接口约定,再决定怎样把事务交给 DUT。
但不能直接把 valid/ready 的驱动逻辑,套到 send/busy 上。如果 UART TX 用 send 和 busy,还需要确认:
send
是单拍脉冲,还是需要保持?
DUT 在哪个时刻接收 data?
busy
为高时,新的请求如何处理?
driver 允许什么时候提交下一笔事务?
信号名称不同,协议细节也可能不同。需要迁移的是分析方法,而不是那几行代码。

写在最后

面对空白文件时,可以先写下三句话:
这笔事务描述什么?
接口怎样接收它?
什么条件下,我认为这笔请求已经处理完成?
把这三个问题答清楚,再补 UVM 的结构和方法,driver 就有了可以落地的编写顺序。
后续我们会继续沿着这条路径,把接口上的传输还原成 transaction,聊聊 monitor 应该在什么时候采样,以及怎样避免漏采和重复采集。

**帧间验证|从接口时序到验证实现,把每一步讲清楚。

相关学习资料