4. 图像数据流与AXI总线设计

图像数据在AI ISP里怎么跑?说白了,就是像素从传感器进来,经过各种处理模块,最后写进DDR或者送显示。这个过程中,AXI总线是的主角。我个人习惯把图像数据流比作水管系统——协议是接头标准,DMA是水泵,帧缓存是水池,带宽就是水管的粗细。哪个环节出问题,整个系统都得堵。

4.1 AXI4-Stream协议详解

AXI4-Stream是ARM公司定义的点对点高速数据流协议。它没有地址阶段,只有数据阶段,非常适合图像这种连续流数据。我在项目中遇到过不少团队把Full AXI和Stream混用,结果资源浪费严重——其实Stream接口比Full AXI省将近一半的逻辑资源。

4.1.1 信号定义与握手规则

信号 方向 说明
TVALID Master→Slave 主设备指示数据有效
TREADY Slave→Master 从设备准备好接收
TDATA Master→Slave 数据总线,宽度可配(8/16/32/64/128位)
TKEEP Master→Slave 字节使能,指示TDATA中哪些字节有效
TLAST Master→Slave 帧/包结束标志
TUSER Master→Slave 用户自定义边带信息(如帧同步、行号)

握手规则只有一条:TVALID和TREADY同时为高时,数据在时钟上升沿传输。这个规则简单,但坑不少。我曾经调试一个ISP管线,发现中间模块丢像素,查了三天才发现是TVALID在复位后没拉低,导致从设备误采样。

设计技巧:我建议所有Stream接口的TVALID都做成寄存器输出,不要用组合逻辑。组合逻辑的TVALID在时序紧张时容易出glitch,一旦采样错位,整帧图像就花了。

4.1.2 图像数据在Stream上的组织方式

图像数据在AXI4-Stream上通常按行传输。举个例子,1920x1080的RGB888图像,每像素3字节,一行就是5760字节。如果TDATA宽度是64位(8字节),一行需要720个beat。TLAST在每行的最后一个beat拉高,TUSER在每帧的第一个像素处拉高作为帧同步。

// 典型的图像Stream时序(Verilog伪代码)
always @(posedge clk) begin
  if (tvalid & tready) begin
    if (tuser)          // 帧起始
      frame_cnt <= frame_cnt + 1;
    if (tlast)          // 行结束
      line_cnt <= line_cnt + 1;
  end
end
注意:不同IP对TUSER的定义可能不同。有的用TUSER[0]做帧同步,有的用TUSER[7:0]传行号。集成前一定要确认好协议映射表,否则数据对齐会出大问题。

4.2 Video DMA控制器设计

Video DMA(VDMA)是图像数据进出DDR的桥梁。它的核心功能是:从Stream接口收数据,按帧写入DDR;或者从DDR读数据,按帧输出到Stream。我设计VDMA时,最关注三个点:地址生成、行缓存管理、中断控制。

4.2.1 地址生成逻辑

图像在DDR里是二维存储的。每帧图像对应一个基地址,每行有固定的跨度(stride)。地址计算公式很简单:

addr = base_addr + row * stride + col * pixel_size

但实际设计中,stride不一定等于图像宽度。比如图像宽度1920像素,但为了DDR burst对齐,stride可能设成2048像素。多出来的部分叫padding,不存储有效数据。

参数 典型值 说明
图像宽度 1920 有效像素数
图像高度 1080 有效行数
像素深度 4字节(RGBA) 每像素字节数
Stride 2048×4 = 8192 行跨度(对齐到64字节)
帧大小 8192×1080 ≈ 8.8MB 实际占用DDR空间
关键点:VDMA的地址生成器必须支持帧级循环。比如三帧缓存(triple buffering),地址要在三个base_addr之间轮转。我习惯用状态机控制地址切换,每收到一帧TLAST就切到下一帧地址。

4.2.2 行缓存与带宽匹配

Stream接口的速率和DDR的读写速率往往不匹配。比如Stream跑300MHz,DDR跑800MHz,但DDR有刷新、bank冲突等开销。这时候需要行缓存做速率匹配。

我常用的方案是双行缓存(double line buffer):一行在写,一行在读。当写行满了,就发起一次DDR burst写请求。burst长度一般设成8或16,充分利用DDR的带宽。

// 行缓存写控制(简化版)
always @(posedge clk) begin
  if (wr_en) begin
    line_buf[wr_ptr] <= data_in;
    if (wr_ptr == LINE_LEN-1) begin
      wr_done <= 1;   // 一行写满,触发DDR写请求
      wr_ptr <= 0;
    end else begin
      wr_ptr <= wr_ptr + 1;
    end
  end
end

4.3 帧缓存管理

帧缓存管理是AI ISP系统的核心难点。为什么?因为ISP管线里多个模块同时访问DDR——去噪模块读RAW帧,3A统计模块读YUV帧,显示模块读最终帧。如果管理不好,轻则帧率下降,重则画面撕裂。

4.3.1 三帧缓存机制

我强烈推荐三帧缓存(triple buffering)。它比双帧缓存多一个缓冲帧,能有效避免写指针追上读指针的问题。

  • 写帧(Write Frame):当前VDMA正在写入的帧
  • 读帧(Read Frame):ISP后端正在处理的帧
  • 空闲帧(Free Frame):等待分配的帧

帧状态切换逻辑:写帧完成后,它变成读帧;原来的读帧变成空闲帧;空闲帧变成写帧。这个循环用状态机实现,每个帧对应一个状态寄存器。

避坑指南:我曾经在一个项目里只用双帧缓存,结果ISP处理速度波动时,读帧还没读完,写帧就覆盖上来了,画面出现水平撕裂。换成三帧缓存后问题彻底解决。代价是多占一帧DDR空间,但现在的DDR容量完全扛得住。

4.3.2 帧指针同步

帧指针在硬件里是寄存器,在软件里是内存变量。硬件写完一帧后,通过中断通知软件更新指针。软件配置下一帧地址时,必须保证硬件不在使用该帧。我习惯用原子操作来更新指针——先读状态,再写地址,最后确认状态没变。

4.4 带宽计算与优化

带宽计算是系统设计的第一步。算错了,后面所有优化都是白费。我见过太多团队,流片回来发现DDR带宽不够,只能降帧率跑。

4.4.1 带宽计算公式

图像数据流的带宽需求,核心公式就一个:

BW = 分辨率 × 帧率 × 像素深度 × 读写因子

举个例子:4K@60fps,RGBA格式(4字节/像素),读写因子取2(一读一写):

BW = 3840 × 2160 × 60 × 4 × 2 = 3.98 GB/s

这还只是单路。AI ISP里通常有RAW、RGB、YUV多路数据流,总带宽轻松超过10GB/s。

数据流 分辨率 帧率 格式 带宽需求
RAW输入 3840×2160 60 12bit RAW 1.79 GB/s
RGB处理 3840×2160 60 8bit RGB 1.19 GB/s
YUV输出 3840×2160 60 NV12 0.60 GB/s
3A统计 1920×1080 30 YUV 4:2:0 0.09 GB/s
总计 3.67 GB/s

4.4.2 优化手段

带宽不够怎么办?别急着加DDR频率,先看看能不能从架构上省带宽。

  • 压缩传输:RAW数据用DPCM/PCM压缩后再写DDR,压缩比可达2:1。我做过一个项目,压缩后带宽直接砍半。
  • 减少读写次数:能流水线处理的模块,尽量用Stream直连,不要经过DDR。比如去噪和锐化,完全可以串在一起。
  • Burst对齐:DDR burst效率最高的长度是8或16。如果VDMA每次只写4个字节,带宽利用率不到30%。确保地址和长度都对齐到burst边界。
  • 优先级仲裁:显示输出对延迟最敏感,给它最高优先级。3A统计可以放最低优先级,慢一点没关系。
经验之谈:我建议在系统设计阶段就预留30%的带宽余量。实际项目中,DDR有刷新开销、bank冲突、温度漂移,实际可用带宽通常只有理论值的70-80%。留足余量,后面少流汗。

4.5 本章知识体系

下面这张图总结了图像数据流与AXI总线设计的核心逻辑。从协议到VDMA,再到帧管理和带宽,环环相扣。

图像数据流与AXI总线设计知识体系 图像数据流与AXI总线设计知识体系 AXI4-Stream协议 握手规则(TVALID/TREADY) 图像数据组织(行/帧) TLAST/TUSER边带信号 Video DMA控制器 地址生成(基址+Stride) 行缓存(双缓冲) 中断控制(帧完成) 帧缓存管理 三帧缓存(写/读/空闲) 帧指针同步(原子操作) 撕裂避免(Triple Buffering) 核心逻辑:协议定义接口 → VDMA控制数据搬运 → 帧缓存管理避免冲突 → 带宽计算确保系统可行

嗯,这一章内容不少。从协议细节到VDMA设计,再到帧管理和带宽优化,每一步都是AI ISP系统集成中绕不开的硬骨头。我个人觉得,带宽计算是最容易被忽视的——很多人觉得DDR带宽够用,结果一上系统就崩。记住一句话:先算带宽,再画架构,最后写代码。顺序别搞反了。