1. 为什么需要模拟中断与异常场景?
在实际的I2C总线通信中,各种异常情况时有发生。比如从设备突然掉电导致无应答(NACK),或者主设备在发送数据时遭遇干扰导致传输中断。这些场景如果不在验证阶段充分覆盖,就可能给产品埋下隐患。
我记得去年参与一个智能家居项目时,就遇到过类似问题。当时设备在实验室测试一切正常,但量产后发现部分产品在高温环境下会偶发通信失败。后来排查发现是I2C从设备在极端温度下响应变慢,导致主设备误判为NACK。如果当初验证时能模拟这种极端场景,就能提前发现问题。
通过virtual sequence构建这些异常场景,可以系统性地验证DUT的中断处理能力。具体来说,我们需要关注:
- 7-bit和10-bit地址模式下的NACK处理
- 数据传输过程中的异常终止
- RESTART功能禁用时的错误恢复
- 各种异常触发的TX_ABRT中断是否正确产生
2. 搭建验证环境的关键组件
2.1 I2C VIP配置要点
I2C验证IP(VIP)是构建虚拟场景的核心。在搭建环境时,需要特别注意Slave端的响应配置。比如要模拟地址NACK,就需要将Slave配置为对特定地址不响应。
i2c_slave_cfg.addr_mode = I2C_10BIT_ADDR; // 设置10位地址模式 i2c_slave_cfg.ignore_addr_list = '{10'h123}; // 忽略地址0x123对于数据NACK场景,可以通过控制Slave的ACK行为来实现:
task i2c_slave_driver::send_nack_after_byte(bit[7:0] target_byte); // 当收到特定数据字节时返回NACK endtask2.2 APB总线寄存器配置
中断相关的寄存器配置主要通过APB总线完成。关键寄存器包括:
| 寄存器名称 | 地址偏移 | 功能描述 |
|---|---|---|
| IC_CON | 0x00 | 控制寄存器,设置工作模式 |
| IC_TX_ABRT_SOURCE | 0x80 | 记录传输终止原因 |
| IC_INTR_MASK | 0x2C | 中断屏蔽控制 |
配置示例:
// 使能TX_ABRT中断 apb_write(IC_INTR_MASK, 32'h0000_0004); // 设置7位地址模式 apb_write(IC_CON, 32'h0000_0040);3. Virtual Sequence实战技巧
3.1 构建基础传输场景
在开始异常场景前,先要确保基础传输功能正常。这里分享一个我常用的基础sequence模板:
class i2c_basic_seq extends uvm_sequence; virtual task body(); // 1. 配置寄存器 setup_registers(); // 2. 启动传输 i2c_start_seq.start(p_sequencer); // 3. 发送地址+数据 send_address(); send_data(); // 4. 结束传输 i2c_stop_seq.start(p_sequencer); endtask endclass3.2 模拟地址NACK场景
地址NACK是最常见的异常之一。在10-bit地址模式下,模拟过程需要特别注意地址分两次发送的特性。
class i2c_addr_nack_seq extends i2c_basic_seq; virtual task send_address(); // 发送地址第一部分 i2c_byte_seq.byte_data = 8'hF0 | (target_addr >> 8); i2c_byte_seq.start(p_sequencer); // 从设备返回NACK i2c_slave_seq.respond_with_nack(); // 检查中断状态 check_interrupt(TX_ABRT); endtask endclass实际项目中,我发现很多工程师会忽略检查IC_TX_ABRT_SOURCE寄存器的值。这个寄存器能明确告诉我们中断原因,对调试非常重要。
3.3 数据NACK与传输终止
数据NACK的模拟相对简单,但要注意时序控制。太早或太晚发送NACK都可能导致DUT行为不同。
class i2c_data_nack_seq extends i2c_basic_seq; virtual task send_data(); // 发送第一个字节(正常ACK) i2c_byte_seq.byte_data = data_queue.pop_front(); i2c_byte_seq.start(p_sequencer); // 发送第二个字节(触发NACK) i2c_byte_seq.byte_data = data_queue.pop_front(); fork begin i2c_byte_seq.start(p_sequencer); end begin // 在第9个时钟周期拉高SDA(NACK) #(8*i2c_clk_period); i2c_slave_seq.force_sda_high(); end join // 检查中断和状态寄存器 check_interrupt(TX_ABRT); check_abort_source(ABRT_SLAVE_DISABLED); endtask endclass4. 波形分析与调试技巧
4.1 关键信号捕获
在验证中断场景时,建议在波形中标记以下关键点:
- START条件生成时刻
- 地址/数据传输的每个字节
- ACK/NACK响应位置
- 中断信号assertion时间
我通常会在测试中插入一些标记事件,方便后期分析:
// 在sequence中插入波形标记 $display("TX_ABRT中断触发时刻:%t", $time); uvm_hdl_force("tb.dut.intr", 1'b1); #10ns; uvm_hdl_release("tb.dut.intr");4.2 常见问题排查
在实际项目中,经常遇到中断未能正确触发的情况。根据我的经验,可以按以下步骤排查:
- 确认IC_INTR_MASK寄存器配置正确
- 检查IC_ENABLE寄存器是否已使能
- 确认APB总线时钟与I2C时钟的相位关系
- 检查TX_ABRT中断是否被其他条件屏蔽
有个特别容易忽略的点是时钟域交叉问题。如果APB时钟和I2C时钟不同源,可能需要额外的同步处理时间。我曾经遇到过一个案例,中断信号因为跨时钟域延迟了3个周期才被APB侧捕获,导致软件误判为中断丢失。