1. 3x3卷积核的滑动窗口生成器

1. 3x3卷积核的滑动窗口生成器 学习卡片

任务目标

实现一个可综合的3x3滑动窗口生成器,从按行输入的图像像素流中,在每个时钟周期输出当前像素位置对应的3x3邻域数据,供后续卷积运算直接使用。

设计思路

图像按行逐像素输入(行优先),每个时钟周期进入一个像素。为了在任意时刻都能得到以当前输入像素为右下角的3x3窗口,需要缓存前两行数据。采用两个行缓冲(每个缓冲存储一行像素)加三个移位寄存器组(每行一个,深度为3)的结构。当第三行数据开始输入时,每个时钟周期即可输出完整的3x3窗口。

接口定义

信号方向位宽说明
clkinput1时钟
rst_ninput1异步复位,低有效
pixel_ininput8输入像素(灰度)
pixel_validinput1输入像素有效标志
frame_startinput1帧起始脉冲(高电平1周期)
row_leninput16图像宽度(每行像素数),需≥3
window_validoutput1窗口数据有效标志
window_outoutput723x3窗口,按行拼接:{row0[23:0], row1[23:0], row2[23:0]},每行内左到右为{col2, col1, col0}

核心代码

module window_3x3 #(
    parameter DATA_WIDTH = 8,
    parameter PIXEL_CNT_WIDTH = 16
)(
    input  wire                        clk,
    input  wire                        rst_n,
    input  wire [DATA_WIDTH-1:0]       pixel_in,
    input  wire                        pixel_valid,
    input  wire                        frame_start,
    input  wire [PIXEL_CNT_WIDTH-1:0]  row_len,
    output reg                         window_valid,
    output reg  [DATA_WIDTH*9-1:0]     window_out
);

    // 行缓冲:存储前两行像素
    reg [DATA_WIDTH-1:0] row_buf_0 [0:2047];  // 第0行缓存
    reg [DATA_WIDTH-1:0] row_buf_1 [0:2047];  // 第1行缓存

    // 当前行计数与列计数
    reg [PIXEL_CNT_WIDTH-1:0] row_cnt;
    reg [PIXEL_CNT_WIDTH-1:0] col_cnt;

    // 三行各自的3像素移位寄存器
    reg [DATA_WIDTH-1:0] shift_row0 [0:2];
    reg [DATA_WIDTH-1:0] shift_row1 [0:2];
    reg [DATA_WIDTH-1:0] shift_row2 [0:2];

    // 行状态:0=第0行,1=第1行,2=第2行及以后
    reg [1:0] row_state;

    // 帧内像素计数(用于判断是否已积累足够行)
    reg [PIXEL_CNT_WIDTH-1:0] pixel_cnt;

    integer i;

    // 像素计数与行列计数
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            row_cnt <= 0;
            col_cnt <= 0;
            pixel_cnt <= 0;
            row_state <= 0;
        end else if (frame_start) begin
            row_cnt <= 0;
            col_cnt <= 0;
            pixel_cnt <= 0;
            row_state <= 0;
        end else if (pixel_valid) begin
            if (col_cnt == row_len - 1) begin
                col_cnt <= 0;
                if (row_cnt == 2) begin
                    // 第2行结束后,后续行持续滚动
                    row_cnt <= row_cnt;
                end else begin
                    row_cnt <= row_cnt + 1;
                end
                if (row_state < 2)
                    row_state <= row_state + 1;
            end else begin
                col_cnt <= col_cnt + 1;
            end
            pixel_cnt <= pixel_cnt + 1;
        end
    end

    // 行缓冲写入与移位寄存器更新
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for (i = 0; i < 3; i = i + 1) begin
                shift_row0[i] <= 0;
                shift_row1[i] <= 0;
                shift_row2[i] <= 0;
            end
            window_valid <= 0;
            window_out <= 0;
        end else if (pixel_valid) begin
            // 行缓冲写入
            if (row_state == 0)
                row_buf_0[col_cnt] <= pixel_in;
            else if (row_state == 1)
                row_buf_1[col_cnt] <= pixel_in;

            // 移位寄存器更新
            case (row_state)
                0: begin
                    shift_row0[2] <= shift_row0[1];
                    shift_row0[1] <= shift_row0[0];
                    shift_row0[0] <= pixel_in;
                end
                1: begin
                    shift_row1[2] <= shift_row1[1];
                    shift_row1[1] <= shift_row1[0];
                    shift_row1[0] <= pixel_in;
                end
                default: begin
                    shift_row2[2] <= shift_row2[1];
                    shift_row2[1] <= shift_row2[0];
                    shift_row2[0] <= pixel_in;
                end
            endcase

            // 窗口输出与有效标志
            if (row_state == 2 && col_cnt >= 2) begin
                window_valid <= 1;
                window_out <= {
                    shift_row0[2], shift_row0[1], shift_row0[0],
                    shift_row1[2], shift_row1[1], shift_row1[0],
                    shift_row2[2], shift_row2[1], shift_row2[0]
                };
            end else begin
                window_valid <= 0;
            end
        end else begin
            window_valid <= 0;
        end
    end

endmodule

运行与验证

Testbench验证要点:

  1. 输入一个5x5图像(像素值0~24),row_len=5,连续25个时钟周期输入像素,frame_start在第一个像素前拉高1周期。
  2. 观察window_valid信号:从第3行第3个像素(即第13个像素)开始,每个时钟周期输出一个有效窗口。
  3. 检查第13个像素(值为12)对应的窗口输出应为:
    {row0: 0,1,2} {row1: 5,6,7} {row2: 10,11,12}
    即 window_out = 24'h000102_050607_0A0B0C(按字节拼接)。
  4. 继续检查后续像素的窗口,确认窗口内容随输入逐像素右移。

通过条件:仿真波形中,window_valid在正确位置拉高,且window_out数据与手工计算的3x3邻域完全一致,无延迟错位。

边界说明

本设计假设图像宽度≥3且连续输入无气泡(pixel_valid每个周期有效)。行缓冲深度设为2048,若图像宽度超过2048需调整参数。窗口输出以当前输入像素为右下角,适合直接接入3x3卷积运算单元。