3. Verilog HDL图像处理基础

在Quartus工程中做视频图像处理,Verilog代码的写法与普通逻辑设计有明显区别。视频流是连续、有序、带时序约束的数据流,一旦某一级处理模块不能在每个时钟周期稳定接收并输出一个像素,整条链路就会出现丢帧、错位或撕裂。因此,本节关注的不是“能不能综合”,而是“能否在像素时钟下持续、稳定、可预测地吞吐”。核心围绕三件事展开:流水线设计、行缓存与窗缓存实现、时序收敛技巧,并给出可综合图像处理模块的编写规范。

一、视频流的节拍:像素时钟与有效区间

视频流处理的第一约定是节拍。以常见的1280×720@60Hz为例,像素时钟约74.25MHz,每行包含有效像素1280个,加上行消隐共1650个时钟;每帧有效行720行,加上场消隐共750行。数据有效由data_en或de信号标记,行首由hsync标记,帧首由vsync标记。所有处理模块必须在这套节拍下工作:每个像素时钟最多处理一个像素,且处理延迟必须是固定整数个时钟周期。如果某模块延迟随数据内容变化,后续模块就无法用固定偏移对齐,图像会出现水平错位。

因此,面向视频流的Verilog模块应遵循“同步、单时钟、固定延迟”的编码风格:所有寄存器用同一个像素时钟驱动,不使用异步复位以外的异步逻辑,不引入随数据变化的等待状态。下面这张信号链图展示了从输入像素到输出像素的固定延迟流水结构,每一级都在像素时钟下推进,延迟以寄存器级数计量。

视频流固定延迟流水线信号链(像素时钟域) 输入像素 din / de / hsync 第1级:寄存 din_r1 第2级:运算 sum = a+b 第3级:截位 dout_r 输出像素 dout / de_d3 clk clk clk clk 延迟对齐:de 与数据必须同步打拍 de_d1 <= de; de_d2 <= de_d1; de_d3 <= de_d2; // 与数据同拍数 若数据延迟3拍,de 也必须延迟3拍,否则有效区间错位 吞吐要求 每 clk 接收 1 像素 不允许等待/握手停顿 禁止写法 组合环、异步复位释放 随数据变化的可变延迟 推荐写法 同步复位、单时钟域 固定级数、参数化位宽

二、流水线设计:把组合逻辑切成寄存器级

流水线的本质是用寄存器把长组合路径切开,使每级逻辑延迟小于像素时钟周期。以3×3均值滤波为例,若把9个像素相加、再除以9全部放在一个时钟周期内,组合路径可能超过10ns,在74.25MHz下无法收敛。正确做法是分级:第一级寄存9个窗口像素;第二级做两两相加得到部分和;第三级把部分和再相加;第四级完成乘系数与截位。每级之间插入寄存器,关键路径被压缩到单级加法器,时序余量充足。

流水线设计要遵守三条规则。第一,级数固定:模块一旦确定延迟为4拍,就不能在某些条件下变成3拍或5拍。第二,控制信号同步打拍:de、hsync、vsync必须与数据经过相同级数的寄存器,否则有效区间与数据错位。第三,位宽逐级扩展:8位像素相加会得到9位,9个相加可能到12位,中间级必须保留足够位宽,最后再截位回8位,避免中间溢出。

// 3x3 均值滤波:固定4级流水
reg [7:0] p00,p01,p02,p10,p11,p12,p20,p21,p22;
reg [8:0] s0,s1,s2;      // 两两相加,9位
reg [9:0] s01,s012;      // 部分和,10位
reg [7:0] dout_r;
reg de_d1,de_d2,de_d3,de_d4;

always @(posedge clk) begin
    // 第1级:窗口像素寄存
    {p00,p01,p02,p10,p11,p12,p20,p21,p22} <= {p00,p01,p02,p10,p11,p12,p20,p21,p22};
    // 第2级:两两相加
    s0 <= p00 + p01;
    s1 <= p02 + p10;
    s2 <= p11 + p12;
    // 第3级:部分和
    s01  <= s0 + s1;
    s012 <= s01 + s2;
    // 第4级:加p20,p21,p22后除9(近似乘0.111)
    dout_r <= (s012 + p20 + p21 + p22) / 9;
    // 控制信号同步打拍
    de_d1 <= de; de_d2 <= de_d1; de_d3 <= de_d2; de_d4 <= de_d3;
end

三、行缓存与窗缓存:3×3窗口的工程实现

图像处理离不开邻域窗口。要得到3×3窗口,必须先缓存前两行,再配合当前行组成三行数据。行缓存通常用双口RAM或移位寄存器实现,深度等于图像宽度。以1280宽为例,每行缓存1280个8位像素,两行共需2×1280×8=20480 bit,约2.5KB,Quartus中可用M9K块RAM实现,每块9Kbit,两行只需3块M9K。

行缓存的读写策略是:当前行像素写入行缓存1,行缓存1的旧数据移入行缓存2,行缓存2的旧数据移入行缓存3。这样在任意时刻,三个行缓存的输出分别对应第n、n-1、n-2行的同一列像素。再对每行输出做两级移位寄存,就得到3×3窗口的9个像素。窗缓存的实现要注意:行缓存深度必须等于有效行像素数,不能包含消隐区,否则窗口会跨行错位。同时,行缓存的写使能应受de控制,只在有效像素期间写入。

// 行缓存与3x3窗口生成(简化)
reg [7:0] line_buf1 [0:1279];
reg [7:0] line_buf2 [0:1279];
reg [7:0] line_buf3 [0:1279];
reg [10:0] col_cnt;
reg [7:0] r1_d1,r1_d2, r2_d1,r2_d2, r3_d1,r3_d2;

always @(posedge clk) begin
    if (de) begin
        line_buf1[col_cnt] <= din;
        line_buf2[col_cnt] <= line_buf1[col_cnt];
        line_buf3[col_cnt] <= line_buf2[col_cnt];
        col_cnt <= col_cnt + 1'b1;
    end else begin
        col_cnt <= 0;
    end
    // 每行输出做两级移位,形成3列
    r1_d1 <= line_buf1[col_cnt]; r1_d2 <= r1_d1;
    r2_d1 <= line_buf2[col_cnt]; r2_d2 <= r2_d1;
    r3_d1 <= line_buf3[col_cnt]; r3_d2 <= r3_d1;
end
// 窗口:{r3_d2,r3_d1,r3}, {r2_d2,r2_d1,r2}, {r1_d2,r1_d1,r1}

四、时序收敛技巧

视频处理模块在Quartus中常见的时序问题是建立时间违例,表现为关键路径过长。处理思路有三条。第一,插入流水寄存器:把大位宽加法、乘法、比较拆成多级,每级只做一次运算。第二,使用专用资源:乘法用DSP块,RAM用M9K,避免用LUT堆出大存储器。第三,控制扇出:de、hsync等控制信号扇出很大时,可复制寄存器或加流水,减少单根网络的负载。在Quartus的TimeQuest中查看“Worst-Case Timing Path”,若路径起点和终点都是寄存器,且中间只有一级运算,通常就能收敛。

判断流水线是否正确的实用方法:在Testbench中给输入加一段已知的递增像素序列,观察输出是否在固定拍数后出现对应结果,并检查de与dout是否同时有效。若de比dout早或晚一拍,说明控制信号打拍级数不匹配。

五、可综合图像处理模块的编写规范

面向视频流的模块应遵循统一规范,便于在Quartus中集成和复用。接口上,统一使用像素时钟clk、同步复位rst_n、数据din、数据有效de、行同步hsync、场同步vsync,输出dout、de_out及同步信号。参数上,用parameter定义图像宽度、高度、数据位宽,避免硬编码。结构上,一个模块只做一件事,窗口生成、滤波、缩放分开,通过固定延迟级联。复位采用同步复位,避免异步复位释放时的亚稳态。所有寄存器初值在复位时明确赋值,不依赖上电随机值。

规范项推荐做法常见错误
时钟单像素时钟,所有寄存器同域跨时钟域直接采样
复位同步复位,rst_n低有效异步复位释放无同步
延迟固定整数拍,控制信号同步打拍de与数据拍数不一致
位宽中间级扩展,末级截位中间溢出导致图像异常
存储器行缓存用M9K,乘法用DSP用LUT堆大RAM
参数parameter定义宽高与位宽硬编码1280、720

把以上规范落到一个具体模块上,可以形成如下可复用的模板结构:输入级寄存、窗口生成、运算流水、输出截位、控制同步。只要每个模块都按这个骨架编写,整条视频处理链在Quartus中就能获得稳定的时序和可预测的像素对齐,后续的缩放、边缘检测、色彩空间转换都可以在此基础上逐级叠加。