3、接口协议验证:AXI4-Stream、APB、AXI4-Full 与协议检查器

接口协议验证,说白了就是确保你的 IP 核跟外界“说同一种语言”。

我做了这么多年验证,见过太多因为握手信号时序差一拍、地址边界没对齐导致整块芯片挂掉的案例。协议验证不过,后端的仿真跑得再漂亮也是白搭。

这一章,我们聚焦三个最常用的总线协议:AXI4-Stream、APB 和 AXI4-Full。我会结合实战经验,讲讲怎么验证它们,以及怎么写一个靠谱的协议检查器。

3.1 AXI4-Stream 协议验证

AXI4-Stream 是点对点的高速数据流协议。没有地址线,只有数据、有效和就绪信号。我刚开始接触时觉得它简单,后来发现坑全在握手细节里。

3.1.1 核心握手规则

TVALID 和 TREADY 必须同时为高,数据才被传输。这个规则看似简单,但验证时要注意:

  • TVALID 不能依赖 TREADY:发送方一旦拉高 TVALID,必须保持到握手完成。我见过一个设计,TVALID 在 TREADY 没拉高时就降下去了,结果数据丢失。
  • TREADY 可以随时拉高或拉低:接收方可以“忙一下再收”,但发送方不能因此丢掉数据。
  • TLAST 必须与最后一拍数据同时有效:这个很多人会忘。TLAST 要在最后一笔数据传输时拉高,不能早也不能晚。
我的习惯:在验证 AXI4-Stream 时,我会专门构造“TVALID 早于 TREADY”、“TREADY 频繁拉低”、“TLAST 提前/延迟”三种异常场景。大部分 bug 都藏在这里。

3.1.2 数据完整性检查

除了握手,还要验证数据内容。我常用的方法是:

  1. 发送端生成递增数据(0, 1, 2, ...)
  2. 接收端检查数据顺序和值是否正确
  3. 特别关注跨时钟域时的数据对齐
// 简单的 AXI4-Stream 数据检查器片段
always @(posedge clk) begin
  if (tvalid && tready) begin
    if (tdata !== expected_data) begin
      $error("数据不匹配: 期望 %h, 实际 %h", expected_data, tdata);
    end
    expected_data <= expected_data + 1;
  end
end

3.2 APB 协议验证

APB 是低带宽控制总线,常用于配置寄存器。它没有流水线,每个传输至少需要两个周期。嗯,这里要注意:APB 的状态机跳转很容易出错。

3.2.1 状态机验证

APB 有三个状态:IDLE、SETUP、ACCESS。验证时要覆盖:

  • IDLE → SETUP:PSEL 拉高,PENABLE 保持低
  • SETUP → ACCESS:PENABLE 拉高,保持一个周期
  • ACCESS → IDLE:PSEL 和 PENABLE 同时拉低
  • 等待状态:PREADY 为低时,ACCESS 状态保持
我曾经踩过的坑:有一次验证 APB 从机,发现 PREADY 在 ACCESS 状态一直为低,导致总线死锁。原因是从机内部状态机没处理好“忙”信号。所以验证时一定要加超时检查——如果 PREADY 超过 N 个周期还不拉高,直接报错。

3.2.2 地址与数据对齐

传输类型 地址要求 数据宽度 常见错误
32-bit 写 地址按 4 字节对齐 32-bit 地址未对齐导致数据错位
16-bit 写 地址按 2 字节对齐 16-bit 高 16 位被意外覆盖
8-bit 写 任意地址 8-bit 字节使能信号错误

3.3 AXI4-Full 协议验证

AXI4-Full 是高性能总线,支持乱序传输和突发。验证复杂度比前两个高一个量级。我个人习惯把验证分成三层:

3.3.1 通道独立性验证

AXI4-Full 有五个通道:读地址、读数据、写地址、写数据、写响应。每个通道独立握手。验证时要确保:

  • 一个通道的阻塞不影响其他通道
  • 地址通道和数据通道可以同时传输
  • 写响应通道可以延迟到所有数据传完后再返回

3.3.2 突发传输验证

突发是 AXI4-Full 的核心特性。验证时要覆盖:

  1. 固定地址突发 (FIXED):所有数据写同一个地址,常用于 FIFO
  2. 递增地址突发 (INCR):地址依次递增,最常用
  3. 回绕地址突发 (WRAP):地址到边界后回绕,常用于 Cache Line
// AXI4-Full 突发长度检查
always @(posedge clk) begin
  if (awvalid && awready) begin
    case (awburst)
      FIXED: check_fixed_burst(awaddr, awlen);
      INCR:  check_incr_burst(awaddr, awlen);
      WRAP:  check_wrap_burst(awaddr, awlen, awsize);
    endcase
  end
end

3.3.3 乱序传输验证

AXI4-Full 支持多个 outstanding 事务。验证时要构造:

  • 多个读请求同时发出,返回顺序可以乱
  • 写数据可以乱序到达,但写响应必须按地址顺序返回
  • 读和写可以交叉进行
关键指标:验证 AXI4-Full 时,我至少会构造 16 个 outstanding 事务的场景。很多 IP 在 4-8 个事务时没问题,一上 16 个就暴露了内部 buffer 管理的问题。

3.4 协议检查器编写

协议检查器,就是自动盯着总线看有没有违规的“监工”。我建议每个项目都写一个,放在 testbench 里一直跑着。

3.4.1 检查器架构

我常用的检查器分三层:

  • 信号层:检查每个时钟周期信号是否合法(如 TVALID 不能为 X)
  • 事务层:检查整个传输过程是否合规(如突发长度是否匹配)
  • 协议层:检查跨事务的规则(如写响应顺序)
协议检查器三层架构 协议层检查器 事务层检查器 信号层检查器 跨事务规则检查 写响应顺序 突发长度/地址对齐 数据完整性 信号 X/Z 态检查 握手时序检查

3.4.2 常见检查项

写检查器时,我建议至少覆盖以下规则:

协议 检查项 违规示例
AXI4-Stream TVALID 不能为 X 未初始化的 TVALID 导致仿真错误
AXI4-Stream TLAST 必须与最后一拍对齐 TLAST 提前一拍拉高,接收方提前结束
APB PENABLE 只能在 SETUP 后拉高 PENABLE 与 PSEL 同时拉高,违反协议
APB PREADY 超时检查 从机死锁,PREADY 永远为低
AXI4-Full 写响应必须按地址顺序返回 乱序写响应导致数据一致性错误
AXI4-Full 突发地址不能跨越 4KB 边界 跨越边界导致内存访问错误

3.4.3 检查器实现技巧

写检查器时,有几个实用技巧:

  • 用断言 (Assertion):SystemVerilog 的 assert 语句是最直接的检查方式
  • 加超时机制:每个传输启动后,启动一个计时器,超时则报错
  • 日志分级:ERROR 用于协议违规,WARNING 用于性能问题,INFO 用于调试
// AXI4-Stream 超时检查示例
property p_tvalid_timeout;
  @(posedge clk)
  $rose(tvalid) |-> ##[1:100] tready;
endproperty

assert property (p_tvalid_timeout)
  else $error("TVALID 等待 TREADY 超时");
我的建议:协议检查器最好在项目一开始就写好,而不是等验证后期再补。早期发现协议问题,改起来成本低得多。我见过一个项目,因为 AXI4-Full 的写响应顺序检查没做,到后仿才发现问题,改了一个月。

好了,接口协议验证就讲到这里。记住:协议验证不是跑通就完事,要刻意构造异常场景,把协议检查器写扎实。这样你的 IP 核才能在各种恶劣环境下稳定工作。