3. 色彩校正模块架构设计:顶层接口、数据流与流水线

好,咱们进入第三章。前面两章我们把色彩校正的数学原理和算法定点化聊透了。这一章,得真刀真枪地画架构图、定接口了。

我个人习惯,设计一个模块之前,先想清楚三件事:跟谁说话(顶层接口)、数据怎么走(数据流)、怎么跑得快(流水线)。这三件事定下来,RTL 代码基本就是填空了。

3.1 顶层接口定义

色彩校正模块,说白了就是一个三入三出的流水线处理单元。输入是 RAW 域的 R、G、B 像素值,输出是校正后的 R'、G'、B'。

我在一个 4K@60fps 的项目里吃过亏——接口定义时没留够余量,导致后端集成时不得不加一级寄存器打拍,时序差点崩了。从那以后,我定接口都遵循一个原则:能宽则宽,能带 valid-ready 就别用单握手。

信号名 方向 位宽 描述
clk I 1 主时钟,通常为像素时钟
rst_n I 1 异步复位,低有效
pixel_in_valid I 1 输入像素有效标志
pixel_in_ready O 1 模块可接收新像素
pixel_in_r I 12 输入 R 分量
pixel_in_g I 12 输入 G 分量
pixel_in_b I 12 输入 B 分量
pixel_out_valid O 1 输出像素有效标志
pixel_out_ready I 1 下游模块可接收
pixel_out_r O 12 校正后 R' 分量
pixel_out_g O 12 校正后 G' 分量
pixel_out_b O 12 校正后 B' 分量
ccm_coeff[8:0] I 9×16 3×3 校正矩阵系数,s1.14 格式
接口设计小贴士:valid-ready 握手协议虽然多两根线,但能天然处理背压。我见过只用 valid 的项目,下游一堵就丢数据,调试起来欲哭无泪。

3.2 数据流设计

数据流怎么走?你想想看,一个像素进来,先要跟矩阵系数做乘法累加。三个通道并行算,最后再拼回去。

我习惯把数据流画成一张图,这样写代码时思路特别清晰。下面这张图就是咱们模块的核心数据流:

色彩校正模块数据流与流水线架构 色彩校正模块数据流与流水线架构 输入像素 R/G/B (12bit) Stage 1 输入寄存器 打拍对齐 Stage 2 乘法器阵列 R×C00, G×C01, B×C02 三通道并行 Stage 3 加法器树 三级加法 结果累加 输出像素 R'/G'/B' (12bit) 数据流说明: 每个像素的 R/G/B 三通道并行进入 3 级流水线, 经过乘法累加后输出校正结果。整条路径无反馈,天然适合流水线。 CCM 系数 系数通过 APB/AXI-Lite 配置, 乘法器直接读取寄存器值 Stage 1: 输入寄存 Stage 2: 乘法 Stage 3: 加法 流水线深度 = 3 级,延迟 = 3 个时钟周期 每个时钟周期可处理 1 个像素,无气泡
核心思路:三通道并行 + 三级流水线。每个时钟周期进来一个像素,三个时钟周期后出去一个校正结果。吞吐率 = 1 像素/周期,延迟 = 3 周期。

3.3 流水线架构设计

为什么要用三级流水线?说白了,就是要把组合逻辑拆短。你看那个 3×3 矩阵乘法:

R' = R × C00 + G × C01 + B × C02
G' = R × C10 + G × C11 + B × C12
B' = R × C20 + G × C21 + B × C22

每个通道需要 3 个乘法 + 2 个加法。如果全部用组合逻辑,路径长度至少是:乘法器延迟(~2ns)+ 加法器树延迟(~1.5ns),在 500MHz 以下还能扛,但到了 1GHz 以上就必崩。

我记得有一次做 8K 芯片,像素时钟跑到 1.2GHz。第一版 RTL 把乘加全放一个周期里,综合后时序报告一片红。后来拆成三级流水线,每级只放一个乘法或一个加法,时序立马收敛。

3.4 三级流水线拆法

具体怎么拆?我建议这样分:

  • Stage 1:输入寄存 + 系数准备
    • 把 pixel_in_r/g/b 打进寄存器
    • 从系数寄存器组读出对应的 C00~C22
    • 这一级不做任何运算,纯打拍
  • Stage 2:乘法器阵列
    • 并行计算 9 个乘法:R×C00, R×C10, R×C20, G×C01, ...
    • 每个乘法器输出位宽:12bit × 16bit = 28bit
    • 结果寄存到下一级
  • Stage 3:加法器树 + 输出寄存
    • 每个通道的 3 个乘法结果做两级加法
    • 第一级:加前两个,第二级:加第三个
    • 最终结果截位到 12bit,输出
曾经踩过的坑:Stage 2 的乘法器输出位宽一定要留够。我早期一个项目偷懒用了 24bit,结果饱和截位导致高亮区域出现色斑。后来老老实实扩到 28bit,问题解决。宁可多花点面积,别丢精度。

3.5 握手与背压处理

流水线架构里,valid-ready 握手怎么穿插?我习惯在每个 stage 之间都插一级 valid-ready 寄存器。这样每个 stage 都可以独立 stall。

具体实现上,用一组 shift register 把 valid 信号逐级传递。ready 信号则从后级往前级反压:

// 伪代码示意
always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    stage1_valid <= 1'b0;
    stage2_valid <= 1'b0;
    stage3_valid <= 1'b0;
  end else begin
    // Stage 1: 输入握手
    if (pixel_in_valid && stage1_ready)
      stage1_valid <= 1'b1;
    else if (stage2_ready)
      stage1_valid <= 1'b0;
    
    // Stage 2: 流水线传递
    if (stage1_valid && stage2_ready)
      stage2_valid <= 1'b1;
    else if (stage3_ready)
      stage2_valid <= 1'b0;
    
    // Stage 3: 输出握手
    if (stage2_valid && stage3_ready)
      stage3_valid <= 1'b1;
    else if (pixel_out_ready)
      stage3_valid <= 1'b0;
  end
end

你想想看,这种结构的好处是什么?每个 stage 的 ready 信号只取决于下一级是否空闲。只要下一级能收,本级就能放。整条流水线天然无锁,不会死锁。

3.6 面积与性能权衡

三级流水线听起来面积大,其实不然。我算过一笔账:

资源项 组合逻辑实现 三级流水线 差异
乘法器 (18×18) 9 个 9 个 相同
加法器 6 个 6 个 相同
寄存器 (FF) ~200 个 ~600 个 多 400 个
最大频率 ~400 MHz ~1.2 GHz 3 倍提升

多 400 个寄存器,换来 3 倍的频率提升。这笔买卖,我觉得值。

个人建议:如果你的目标频率低于 300MHz,其实两级流水线就够了。但如果你像我一样做高性能设计,直接上三级,别犹豫。多出来的几百个寄存器,在整体芯片面积里连 0.1% 都不到。

3.7 本章小结

嗯,这一章我们把色彩校正模块的架构定下来了。顶层接口用了 valid-ready 握手,数据流是三通道并行,流水线拆成三级。这些设计决策,都是我在多个项目里反复验证过的。