ARTICLE · 1231671
写 UVM driver,卡住的往往不是模板,而是「怎么驱动接口」
写 UVM driver,卡住的往往不是模板,而是「怎么驱动接口」写 UVM driver 时,最容易踩进一个坑: 模板能搭出来,真正驱动接口的部分却不知道怎么写。 vif 声明好了,get_next_item() 也写好了,轮到 drive_one(),只能留一个空任务。 问题出在哪?一句话概括: 事务描述的是「要做什么」,接口协议规定的是「每一拍怎么做」。 driver 的职责,就是把这两部分连接起来。 这一篇不堆完整模板,我们先从一个最小的数据发送接口出发,把动手前该想清楚的事理顺。
假设 DUT 有一个简单的输入接口:
只有信号名称,还不足以写出驱动逻辑。我们先为这个例子约定几条规则: 时钟上升沿,valid 和 ready 同时为高,表示一次传输被接收; 发送方不能以 ready 已经为高作为拉高 valid 的前提; 发出有效数据后,如果握手尚未完成,发送方需保持 valid,并保持 data 不变; 复位期间,driver 将 valid 保持为低。 后面的讨论,都基于这个简单、非流水的驱动方案。
假设 sequence 提交了一笔事务: 这句话没有描述信号何时变化,也没说遇到反压怎么办。这些内容,需要 driver 补齐。 在本例中,一笔事务对应的处理过程是: 取得事务 → 驱动有效数据 → 等待握手 → 结束本次驱动 → 通知事务完成。 其中最需要注意的是「等待握手」。 举个例子:driver 已经送出 8'hA5,但 DUT 的 ready 连续两拍为低:
前两拍虽然「数据已经放到线上」,但 DUT 并没有接收。 因此,driver 不能因为执行过一次信号赋值,就立即修改数据或撤销请求。 信号已驱动 和 传输已完成,需要分别判断。
driver 里常见这样的声明: virtual stream_if vif; 它提供了访问接口实例的句柄。但声明之后,vif 并不会自动连到顶层接口。 采用常见配置方式时,还需要通过 uvm_config_db 获取实际接口实例,并检查获取结果。这部分可以理解为两步: 先准备句柄,再建立连接。 连接建立以后,还有一个问题:driver 在什么时刻驱动信号?又在什么时刻观察 ready? 这就需要明确采样和驱动的时序约定。采用 clocking block 时,应结合它的时钟事件、输入采样偏移和输出驱动偏移来理解代码。 不要只看到代码里出现了 drv_cb,就以为时序问题全解决了。
明确接口以后,再回头写 UVM 结构,就很容易对应到具体职责: 明确事务类型 —— driver 从 sequencer 接收哪一种事务?本例至少需要知道待发送的数据; 声明并获取 vif —— 保存接口句柄,配置阶段获取,失败时及时报错; 建立初始状态 —— 按协议处理复位和空闲,避免一开始就产生意外请求; 获取事务 —— 用 get_next_item() 取得待处理请求; 执行协议驱动 —— 把事务字段转换成接口信号,并等待约定的完成条件; 通知完成 —— 用与 get_next_item() 配对的 item_done() 完成事务握手。 结构写齐以后,再逐项检查是否真正实现——尤其是协议驱动部分,不能只留一个任务名。
item_done() 完成的是 driver 与 sequencer 之间的事务握手。 它本身不会检查 DUT 的运算结果,也不代表 scoreboard 已经比较通过。 在本文的简单驱动方案里,我们把「输入握手完成,并结束本次驱动」作为通知事务完成的条件。但在其他方案里,完成条件可能不同。 比如,DUT 接收了一笔输入请求后,还要很多拍才能输出结果。driver 可以按约定完成输入侧事务,后续结果由 monitor 观察,再交给 scoreboard 检查。 所以,要分清三个时刻:
它们可能先后发生。 写 driver 时,要先明确事务的完成条件;判断 DUT 是否正确,还需要后续观察和检查。
对这个最小接口,先检查三种情况: 场景一:ready 始终为高 —— driver 能否完成一次传输?握手后是否正确结束本次请求? 场景二:ready 连续几拍为低 —— 等待期间,valid 是否保持?data 是否稳定? 场景三:连续提交两笔不同数据 —— 第一笔是否被重复接收?第二笔是否被遗漏?两次事务通知是否与处理过程对应? 这些场景不复杂,却能直接暴露驱动时序里的问题。 测试通过后,再增加复位打断、随机反压等情况。复位发生在传输中途时如何处理,也需要事先定义清楚。
理解了上面的过程,再看 UART TX 的输入侧,思路是一样的:先读接口约定,再决定怎样把事务交给 DUT。 但不能直接把 valid/ready 的驱动逻辑,套到 send/busy 上。如果 UART TX 用 send 和 busy,还需要确认: send 是单拍脉冲,还是需要保持? DUT 在哪个时刻接收 data? busy 为高时,新的请求如何处理? driver 允许什么时候提交下一笔事务? 信号名称不同,协议细节也可能不同。需要迁移的是分析方法,而不是那几行代码。
面对空白文件时,可以先写下三句话: 这笔事务描述什么? 接口怎样接收它? 什么条件下,我认为这笔请求已经处理完成? 把这三个问题答清楚,再补 UVM 的结构和方法,driver 就有了可以落地的编写顺序。 后续我们会继续沿着这条路径,把接口上的传输还原成 transaction,聊聊 monitor 应该在什么时候采样,以及怎样避免漏采和重复采集。
**帧间验证|从接口时序到验证实现,把每一步讲清楚。
一、先看接口,再写 driver
先确定协议边界
同样叫 valid、ready,不代表所有接口都采用完全相同的约定。实际工程应以规格说明为准。
二、一笔事务,怎样变成一次传输?
发送数据 8'hA5。
三、vif 解决连接,clocking block 约定访问时刻
检查接口时,至少看两件事
① driver 是否拿到了正确的接口实例?
② driver 的驱动与采样时刻,是否符合接口和 DUT 的约定?















































































