在芯片验证领域,I2C总线验证是一个经典但充满技术细节的课题。最近我在验证Synopsys的DW_apb_i2c IP时,遇到了一些值得分享的技术问题和解决方案。不同于常见的I2C接口验证,DW_apb_i2c的端口设计有其特殊性,这给验证环境的搭建带来了独特挑战。
传统I2C接口通常直接暴露SCL(时钟)和SDA(数据)两根双向信号线,但DW_apb_i2c采用了更底层的接口设计:
这种设计直接反映了I2C总线的电气特性,但也导致与标准VIP接口的兼容性问题。SVT_I2C_VIP提供的interface是传统的SCL和SDA双向信号,如何正确连接这两种接口成为首要问题。
要解决这个问题,必须深入理解开漏输出(Open-Drain)的工作原理。开漏输出具有两个关键特性:
在Verilog中的典型实现方式:
verilog复制assign SCL = ic_clk_oe ? 1'b0 : 1'bz;
assign SDA = ic_data_oe ? 1'b0 : 1'bz;
这种设计确保了多个设备可以安全地共享总线,实现"线与"逻辑。当任何设备拉低总线时,总线即为低电平;只有当所有设备都释放总线时,上拉电阻才能将总线拉高。
基于上述理解,我们构建的验证环境如下图所示:

环境包含以下关键组件:
具体的信号连接代码如下:
verilog复制// 输入信号连接
assign Mtop.u_DW_apb_i2c.ic_clk_in_a = i2c_if.SCL;
assign Mtop.u_DW_apb_i2c.ic_data_in_a = i2c_if.SDA;
// 开漏输出连接
assign i2c_if.SCL = Mtop.u_DW_apb_i2c.ic_clk_oe ? 1'b0 : 1'bz;
assign i2c_if.SDA = Mtop.u_DW_apb_i2c.ic_data_oe ? 1'b0 : 1'bz;
这种连接方式完美模拟了I2C总线的实际工作场景,其中:
根据主从关系和数据流向,我们设计了四种基本验证场景:
| 模式 | DUT角色 | VIP角色 | 数据流向 | 关键验证点 |
|---|---|---|---|---|
| 1 | Master | Slave | DUT→VIP | 写时序、ACK处理 |
| 2 | Master | Slave | VIP→DUT | 读时序、数据采样 |
| 3 | Slave | Master | VIP→DUT | 地址识别、数据接收 |
| 4 | Slave | Master | DUT→VIP | 数据响应、时钟同步 |
以DUT作为Master发送数据为例,具体实现步骤:
verilog复制// 设置I2C时钟频率
apb_write(IC_CON, 0x45); // 标准模式(100kHz)
// 配置目标Slave地址
apb_write(IC_TAR, 0x55);
// 写入待发送数据
apb_write(IC_DATA_CMD, 0xAA);
systemverilog复制// 创建Slave响应事务
i2c_slave_transaction resp = new();
resp.data = new[1];
resp.data[0] = 8'hAA; // 预期接收数据
// 将事务发送给Slave Driver
slave_sequencer.send_response(resp);
systemverilog复制// 在记分板中检查事务匹配
if (dut_trans.data == vip_trans.data)
test_pass = 1;
else
$error("Data mismatch!");
当DUT和VIP配置不同的速度模式时,会出现时钟同步问题。解决方案:
systemverilog复制// 获取DUT配置的速度模式
dut_speed = apb_read(IC_CON) & 0x3;
// 获取VIP配置
vip_speed = vip_cfg.speed_mode;
// 一致性检查
assert (dut_speed == vip_speed)
else $error("Speed mode mismatch!");
systemverilog复制// 根据DUT配置动态调整VIP
case(dut_speed)
2'b00: vip_cfg.speed_mode = STANDARD;
2'b01: vip_cfg.speed_mode = FAST;
2'b10: vip_cfg.speed_mode = FAST_PLUS;
default: vip_cfg.speed_mode = STANDARD;
endcase
在实际验证中,我们发现Slave VIP的响应时序与预期不符:DUT Master发送STOP条件后,Slave Driver才会执行put_response。经过分析,这是Slave VIP在阻塞模式下的预期行为:
systemverilog复制// 在Slave Sequence中添加调试信息
task body();
`uvm_info("SLAVE_SEQ", "Sending response config", UVM_MEDIUM)
send_response_config();
`uvm_info("SLAVE_SEQ", "Waiting for get_response", UVM_MEDIUM)
get_response(rsp);
`uvm_info("SLAVE_SEQ", "Response received", UVM_MEDIUM)
endtask
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 总线死锁 | 多个Master同时驱动 | 检查仲裁逻辑 |
| 数据错误 | 时钟不同步 | 验证速度模式配置 |
| 无响应 | 地址不匹配 | 检查IC_TAR寄存器 |
| ACK丢失 | 时序违规 | 调整时钟拉伸配置 |
建议在环境中添加以下自动检查:
systemverilog复制// 总线冲突检查
always @(posedge i2c_if.SCL) begin
if ($countdrivers(i2c_if.SDA) > 1)
$error("Bus contention detected!");
end
// 时序检查
property setup_time_check;
@(posedge i2c_if.SCL) !$isunknown(i2c_if.SDA) |-> ##[1:10] $stable(i2c_if.SDA);
endproperty
systemverilog复制covergroup i2c_cg;
speed_mode: coverpoint apb_read(IC_CON)[1:0];
address_range: coverpoint apb_read(IC_TAR)[6:0];
data_length: coverpoint trans_length;
endgroup
在实际项目中,我们发现这种验证架构不仅能验证IP基本功能,还能有效模拟SoC集成后的真实场景。特别是在验证时钟同步和总线仲裁等复杂场景时,这种环境展现了强大的调试能力。