3. SPI验证测试用例设计:读写寄存器测试,DMA传输测试,异常时序注入测试,边界条件测试

SPI 验证,说白了就是要把这个协议从里到外翻个底朝天。我做了这么多年验证,发现很多人上来就写驱动,结果一上硬件就抓瞎——时序不对、DMA 卡死、边界没测到。嗯,今天咱们就把这四类测试用例掰开揉碎了讲。

核心思路: 验证不是跑通就完事,而是要证明你的设计在所有合法和非法条件下都能正确工作。

3.1 读写寄存器测试

这是最基础的,但也是最容易翻车的。我见过有人 SPI 寄存器读回来全是 0xFF,还以为是芯片没焊好,结果发现是地址线接反了。

3.1.1 寄存器回读验证

写一个值进去,再读出来,看是不是同一个数。听起来简单?你得考虑:

  • 全地址遍历: 每个寄存器地址都要测,别偷懒只测几个
  • 数据线完整性: 写 0xAA、0x55 这种交替位模式,检查有没有短路
  • 写保护位: 有些寄存器是只读的,写进去应该被忽略
我的习惯: 先写 0xAAAAAAAA,再写 0x55555555,来回读三次。如果两次结果不一样,大概率是时序问题。

3.1.2 寄存器域位测试

寄存器里经常有多个域位,比如控制寄存器的 bit[3:0] 是分频系数,bit[7] 是使能位。你得单独测每个域位:

// 伪代码示例:域位测试
for (bit_pos = 0; bit_pos < 32; bit_pos++) {
    write_reg(addr, 1 << bit_pos);
    read_val = read_reg(addr);
    if (read_val != (1 << bit_pos)) {
        printf("Bit %d 读写失败\n", bit_pos);
    }
}

为什么要这么测?我曾经遇到过一个芯片,它的 bit[15] 和 bit[16] 在内部是短路的,写一个另一个也跟着变。要不是做了逐位测试,这 bug 根本抓不到。

3.2 DMA传输测试

DMA 传输是 SPI 性能的关键。但 DMA 一旦出问题,数据流就全乱了。我调试过最头疼的一个问题,就是 DMA 传输到一半突然停掉,查了三天才发现是 DMA 描述符链表的地址没对齐。

3.2.1 单次DMA传输

先测最简单的:一次 DMA 传输 1 字节、4 字节、64 字节、1024 字节。检查:

  • 传输完成后 SPI 状态寄存器是否正确
  • 接收缓冲区数据是否完整
  • DMA 传输完成中断是否触发

3.2.2 循环DMA传输

很多场景需要连续传输,比如音频数据流。循环 DMA 测试要关注:

  1. 设置 DMA 为循环模式,缓冲区大小 256 字节
  2. 启动传输,持续 10 秒
  3. 检查是否有数据丢失或重复
  4. 检查 DMA 地址是否回绕正确
注意: 循环 DMA 最容易出问题的是回绕点。当 DMA 地址从缓冲区末尾跳回开头时,如果 SPI 时钟还在跑,可能会多采或少采一个数据。

3.2.3 DMA与CPU并发访问

DMA 在后台跑,CPU 同时读写同一个缓冲区——这就是经典的数据一致性问题。我建议这样测:

测试场景 操作 预期结果
CPU读,DMA写 CPU 每 1ms 读取缓冲区,DMA 持续写入 CPU 读到完整数据,无撕裂
CPU写,DMA读 CPU 更新缓冲区头部,DMA 持续发送 DMA 发送数据不包含半更新状态
Cache一致性 使能 CPU Cache,DMA 传输 4KB Cache 刷新/无效化操作正确

3.3 异常时序注入测试

这是验证中最有意思的部分。正常时序谁都会测,但异常时序才是真正考验设计鲁棒性的地方。说白了,就是故意给 SPI 接口喂「坏数据」,看它会不会崩溃。

3.3.1 时钟异常

  • 时钟抖动: 在 SPI 时钟上叠加 ±5% 的随机抖动,看数据采样是否稳定
  • 时钟毛刺: 在时钟上升沿附近插入一个窄脉冲,检查 SPI 状态机是否误触发
  • 时钟暂停: 传输过程中暂停时钟 10μs 再恢复,看能否继续
我曾经: 遇到过 SPI 时钟线上有个 2ns 的毛刺,导致每传输 100 个字节就错一个。用示波器抓了整整一天才找到。后来我在验证用例里加上了毛刺注入,再也没出过类似问题。

3.3.2 片选异常

片选信号(CS)是 SPI 的「开关」,它出问题后果很严重:

  1. CS 提前拉高: 传输 8 个时钟后 CS 拉高,只传输了 1 个字节
  2. CS 延迟拉高: 传输完成后 CS 延迟 100ns 才拉高
  3. CS 毛刺: 传输过程中 CS 出现 5ns 的毛刺
  4. CS 悬空: CS 引脚未驱动,处于高阻态

3.3.3 数据线异常

MOSI 和 MISO 是数据命脉。我习惯这样注入异常:

// 异常注入示例:在传输第5个字节时翻转MISO
for (byte_idx = 0; byte_idx < 10; byte_idx++) {
    if (byte_idx == 5) {
        // 强制将MISO拉高一个时钟周期
        force_miso_high();
        delay_one_spi_clock();
        release_miso();
    }
    spi_transfer_byte(tx_data[byte_idx]);
    check_rx_data(rx_data[byte_idx]);
}

3.4 边界条件测试

边界条件是最容易漏掉的,但也是 bug 最密集的地方。你想想看,设计者通常只考虑正常情况,边界上的「万一」往往被忽略。

3.4.1 传输长度边界

边界类型 测试值 关注点
最小长度 0 字节、1 字节 空传输是否触发异常
FIFO 深度边界 FIFO 深度 -1、FIFO 深度、FIFO 深度 +1 FIFO 满/空标志是否正确
DMA 描述符边界 描述符链表长度 1、2、最大值 链表遍历是否正常
最大长度 65535 字节(16位长度字段最大值) 长度计数器是否溢出

3.4.2 时钟频率边界

SPI 通常支持分频系数 2、4、8、16、32、64、128、256。边界测试要覆盖:

  • 最高频率: 分频系数 2,看 setup/hold 时间是否满足
  • 最低频率: 分频系数 256,看是否有超时问题
  • 动态切换: 传输过程中切换分频系数,看状态机是否错乱
血的教训: 我遇到过一款芯片,在最高频率下 SPI 时钟占空比变成了 40:60,导致数据采样错误。原因是时钟树上的 buffer 驱动能力不够。所以边界测试不只是测功能,还要测电气特性。

3.4.3 模式切换边界

SPI 有 4 种模式(CPOL/CPHA 组合)。很多芯片支持运行时切换模式,但切换瞬间最容易出问题:

  1. 模式 0 → 模式 1:时钟极性变化,当前传输是否受影响
  2. 模式 2 → 模式 3:数据采样沿变化,已发出的数据是否有效
  3. 全模式遍历:连续切换 1000 次,看是否有累积错误

3.5 知识体系总览

下面这张图把 SPI 验证测试的四个维度串起来了。你可以把它当作测试用例设计的检查清单:

SPI 验证测试知识体系 SPI 验证测试 寄存器读写测试 DMA 传输测试 异常时序注入 边界条件测试 全地址遍历 数据线完整性 域位逐位测试 单次传输 循环传输 CPU并发访问 时钟抖动/毛刺 片选异常 数据线强制翻转 传输长度边界 时钟频率边界 模式切换边界 验证原则:先基础后异常,先单次后循环,先功能后边界 每个测试用例都要有明确的通过/失败判据

3.6 测试用例设计清单

最后,我整理了一份测试用例清单。每次做 SPI 验证时,我都会对着这个清单打勾:

  1. 寄存器测试(10个用例): 全地址遍历、数据线 0xAA/0x55、域位逐位、写保护、复位默认值、保留位检查、多字节连续读写、地址递增、随机地址、边界地址(0x00 和 0xFF)
  2. DMA测试(8个用例): 单次 1/4/64/1024 字节、循环 256 字节 10 秒、描述符链表 1/2/最大值、CPU 并发读写、Cache 一致性、DMA 暂停/恢复、DMA 超时、DMA 错误处理
  3. 异常时序测试(12个用例): 时钟抖动 ±5%、时钟毛刺 2ns、时钟暂停 10μs、CS 提前/延迟/毛刺/悬空、MOSI 毛刺、MISO 毛刺、数据线短路模拟、电源抖动、地弹噪声、多从设备冲突、模式切换毛刺、复位时序异常
  4. 边界条件测试(8个用例): 0 字节传输、1 字节传输、FIFO 深度 -1/+1、最大长度 65535、最高/最低频率、频率动态切换、模式 0→1→2→3 遍历、连续模式切换 1000 次
我的建议: 别想着一次把所有用例都跑完。先跑核心的 10 个,确保基本功能没问题。然后再逐步扩展。验证是个迭代过程,不是一锤子买卖。