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 要在最后一笔数据传输时拉高,不能早也不能晚。
3.1.2 数据完整性检查
除了握手,还要验证数据内容。我常用的方法是:
- 发送端生成递增数据(0, 1, 2, ...)
- 接收端检查数据顺序和值是否正确
- 特别关注跨时钟域时的数据对齐
// 简单的 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 状态保持
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 的核心特性。验证时要覆盖:
- 固定地址突发 (FIXED):所有数据写同一个地址,常用于 FIFO
- 递增地址突发 (INCR):地址依次递增,最常用
- 回绕地址突发 (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 事务。验证时要构造:
- 多个读请求同时发出,返回顺序可以乱
- 写数据可以乱序到达,但写响应必须按地址顺序返回
- 读和写可以交叉进行
3.4 协议检查器编写
协议检查器,就是自动盯着总线看有没有违规的“监工”。我建议每个项目都写一个,放在 testbench 里一直跑着。
3.4.1 检查器架构
我常用的检查器分三层:
- 信号层:检查每个时钟周期信号是否合法(如 TVALID 不能为 X)
- 事务层:检查整个传输过程是否合规(如突发长度是否匹配)
- 协议层:检查跨事务的规则(如写响应顺序)
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 超时");
好了,接口协议验证就讲到这里。记住:协议验证不是跑通就完事,要刻意构造异常场景,把协议检查器写扎实。这样你的 IP 核才能在各种恶劣环境下稳定工作。