3. 接口驱动与监视:Driver 与 Monitor 的数据通道
在 UVM 验证环境中,接口(interface)是验证组件与 DUT 之间的物理连接桥梁。Driver 通过接口向 DUT 发送激励,Monitor 通过接口采集 DUT 的输出响应。本章节深入剖析 Driver 与 Monitor 如何通过接口完成数据驱动与采集,并给出典型实现与调试要点。
3.1 接口的声明与连接
接口封装了 DUT 的时序信号,并提供时钟块(clocking block)和驱动控制。典型接口结构如下:
interface axi_if (input clk, input rst_n);
// 信号声明
logic [31:0] awaddr;
logic awvalid;
logic awready;
logic [31:0] wdata;
logic wvalid;
logic wready;
logic [31:0] rdata;
logic rvalid;
logic rready;
// 时钟块定义
clocking drv_cb @(posedge clk);
default input #1step output #1step;
output awaddr, awvalid, wdata, wvalid;
input awready, wready;
endclocking
clocking mon_cb @(posedge clk);
default input #1step;
input awaddr, awvalid, awready, wdata, wvalid, wready, rdata, rvalid;
endclocking
// 驱动 modport
modport DRV (clocking drv_cb, input clk, rst_n);
modport MON (clocking mon_cb, input clk, rst_n);
endinterface
接口设计要点:时钟块中的
#1step 确保采样发生在时钟沿前的稳定区域,避免竞争。Driver 使用 output 方向信号驱动 DUT,Monitor 使用 input 方向信号采样。
3.2 Driver 通过接口驱动 DUT
Driver 从 sequencer 获取事务(transaction),通过接口的时钟块驱动到 DUT 引脚。核心流程如下:
- 获取事务:调用
seq_item_port.get_next_item(req)从 sequencer 获取待驱动的事务。 - 时序驱动:在时钟块同步下,将事务字段赋值给接口信号。
- 握手等待:等待 DUT 返回 ready/valid 握手信号,确保数据被正确接收。
- 完成通知:调用
seq_item_port.item_done()通知 sequencer 当前事务已完成。
class axi_driver extends uvm_driver #(axi_transaction);
virtual axi_if vif;
function void build_phase(uvm_phase phase);
if(!uvm_config_db#(virtual axi_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "vif not set")
endfunction
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
drive_transaction(req);
seq_item_port.item_done();
end
endtask
task drive_transaction(axi_transaction tr);
@(posedge vif.clk);
vif.drv_cb.awaddr <= tr.addr;
vif.drv_cb.awvalid <= 1'b1;
vif.drv_cb.wdata <= tr.data;
vif.drv_cb.wvalid <= 1'b1;
wait(vif.drv_cb.awready && vif.drv_cb.wready);
@(posedge vif.clk);
vif.drv_cb.awvalid <= 1'b0;
vif.drv_cb.wvalid <= 1'b0;
endtask
endclass
驱动时序常见错误:Driver 在非阻塞赋值后未等待一个时钟周期就释放 valid,导致 DUT 采样到无效数据。务必在握手完成后延迟一个时钟再撤销 valid。
3.3 Monitor 通过接口采集数据
Monitor 被动监听接口信号,在时钟边沿采样有效数据,并封装成事务发送给 scoreboard 等分析组件。采集流程如下:
- 等待有效条件:检测接口上的 valid 与 ready 同时为高,表示一次有效传输。
- 采样信号:在时钟块同步下读取地址、数据、控制信号。
- 构造事务:将采样值填入事务对象。
- 发送事务:通过
analysis_port.write(tr)发送给订阅组件。
class axi_monitor extends uvm_monitor;
virtual axi_if vif;
uvm_analysis_port #(axi_transaction) mon_ap;
function void build_phase(uvm_phase phase);
if(!uvm_config_db#(virtual axi_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "vif not set")
mon_ap = new("mon_ap", this);
endfunction
task run_phase(uvm_phase phase);
forever begin
collect_transaction();
end
endtask
task collect_transaction();
axi_transaction tr = axi_transaction::type_id::create("tr");
@(posedge vif.clk);
wait(vif.mon_cb.awvalid && vif.mon_cb.awready);
tr.addr = vif.mon_cb.awaddr;
wait(vif.mon_cb.wvalid && vif.mon_cb.wready);
tr.data = vif.mon_cb.wdata;
mon_ap.write(tr);
endtask
endclass
采集时序对比:Driver 使用
output #1step 驱动,Monitor 使用 input #1step 采样。两者在时钟边沿的时序窗口不同,确保驱动与采集互不干扰。
3.4 Driver 与 Monitor 的接口时序关系
下表总结了 Driver 和 Monitor 在接口使用上的关键差异:
| 对比维度 | Driver | Monitor |
|---|---|---|
| 方向 | 输出激励到 DUT | 输入采样 DUT 响应 |
| 时钟块方向 | output | input |
| 时序控制 | 主动驱动,等待握手 | 被动等待有效条件 |
| 事务来源 | 从 sequencer 获取 | 从接口采样构造 |
| 输出端口 | 无(直接驱动接口) | analysis_port 发送 |
| 常见错误 | 时序违规、握手遗漏 | 采样窗口错误、漏采数据 |
3.5 接口驱动与监视的典型结构
下图展示了 Driver 和 Monitor 通过同一接口与 DUT 连接的典型架构:
3.6 调试与故障排查要点
在接口驱动与监视的调试过程中,以下问题最为常见:
- 驱动时序偏移:Driver 的
output #1step与 Monitor 的input #1step在仿真精度不足时可能产生 delta 周期偏差,导致 Monitor 采样到旧值。建议在时钟块中显式指定#1step并检查仿真精度设置。 - 握手信号遗漏:Driver 在驱动 valid 后未等待 ready 就撤销,造成 DUT 丢失数据。应在握手条件满足后再进入下一状态。
- Monitor 采样窗口错误:Monitor 在 valid 有效但 ready 未有效时采样,导致数据不完整。必须同时检查 valid 与 ready。
- 接口配置未传递:Driver 或 Monitor 未通过
uvm_config_db获取 virtual interface,导致空指针访问。建议在 build_phase 中检查并打印接口句柄。
调试技巧:在 Driver 和 Monitor 的 run_phase 中添加
$display 打印关键信号值,配合波形查看器定位时序问题。使用 UVM 的 uvm_info 分级打印,避免信息过载。
3.7 驱动与监视的协同工作流
下图展示了 Driver 与 Monitor 在时间轴上的协同工作流程: