2. 5x5卷积核的滑动窗口生成器

任务目标

在图像卷积加速器中,滑动窗口生成器负责从行缓存(Line Buffer)中按像素时钟节拍拼装出卷积核所需的二维数据块。本案例实现5x5窗口生成器,相比3x3版本支持更大感受野,每个时钟周期输出一组完整的5x5窗口数据,供后续卷积运算单元直接使用。

设计规格

  • 输入:单像素灰度数据流 pixel_in(8bit),伴随有效标志 pixel_valid
  • 输出:25路并行数据 win_data[24:0],每路8bit,对应5x5窗口
  • 输出有效:win_valid,当窗口内所有像素均为有效图像数据时拉高
  • 行缓存深度:图像宽度 IMG_WIDTH 可配置,本设计以64为例
  • 边界处理:窗口覆盖图像边界时输出无效,不产生窗口数据

核心实现

采用4条行缓存(Line Buffer)存储前4行数据,当前行数据直接参与拼接。每个像素时钟,将新像素写入4条行缓存,同时从行缓存中读出对应列位置的旧像素,与当前像素共同组成5x5窗口。行缓存使用移位寄存器实现,避免复杂读写地址控制。

module window_5x5 #(
    parameter DATA_WIDTH = 8,
    parameter IMG_WIDTH  = 64
)(
    input  wire                     clk,
    input  wire                     rst_n,
    input  wire [DATA_WIDTH-1:0]    pixel_in,
    input  wire                     pixel_valid,
    output wire [DATA_WIDTH-1:0]    win_data [24:0],
    output wire                     win_valid
);

    // 行缓存:4条,每条存储IMG_WIDTH个像素
    reg [DATA_WIDTH-1:0] line_buf [3:0][IMG_WIDTH-1:0];
    reg [DATA_WIDTH-1:0] cur_row  [IMG_WIDTH-1:0];  // 当前行数据

    // 列计数器
    reg [$clog2(IMG_WIDTH)-1:0] col_cnt;
    reg [1:0] row_cnt;  // 已缓存行数(0~4)

    // 窗口输出寄存器
    reg [DATA_WIDTH-1:0] win_reg [24:0];
    reg win_valid_reg;

    // 列计数与行计数
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            col_cnt <= 0;
            row_cnt <= 0;
        end else if (pixel_valid) begin
            if (col_cnt == IMG_WIDTH-1) begin
                col_cnt <= 0;
                if (row_cnt < 4)
                    row_cnt <= row_cnt + 1;
            end else begin
                col_cnt <= col_cnt + 1;
            end
        end
    end

    // 写入行缓存与当前行
    integer i;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for (i = 0; i < IMG_WIDTH; i = i + 1) begin
                cur_row[i] <= 0;
                line_buf[0][i] <= 0;
                line_buf[1][i] <= 0;
                line_buf[2][i] <= 0;
                line_buf[3][i] <= 0;
            end
        end else if (pixel_valid) begin
            // 当前行写入
            cur_row[col_cnt] <= pixel_in;
            // 行缓存移位:line_buf[0]接收上一行,line_buf[1]接收上上行...
            line_buf[0][col_cnt] <= cur_row[col_cnt];
            line_buf[1][col_cnt] <= line_buf[0][col_cnt];
            line_buf[2][col_cnt] <= line_buf[1][col_cnt];
            line_buf[3][col_cnt] <= line_buf[2][col_cnt];
        end
    end

    // 窗口拼装:当前列col_cnt,窗口列范围[col_cnt-4, col_cnt]
    // 需要从行缓存中读取前4列数据
    wire [DATA_WIDTH-1:0] buf_rd [3:0][4:0];  // 行缓存读出数据

    genvar g;
    generate
        for (g = 0; g < 4; g = g + 1) begin : rd_loop
            // 读取col_cnt-4到col_cnt位置的数据
            // 使用移位寄存器实现列延迟
            reg [DATA_WIDTH-1:0] delay_line [4:0];
            always @(posedge clk or negedge rst_n) begin
                if (!rst_n) begin
                    for (i = 0; i < 5; i = i + 1)
                        delay_line[i] <= 0;
                end else if (pixel_valid) begin
                    delay_line[0] <= line_buf[g][col_cnt];
                    delay_line[1] <= delay_line[0];
                    delay_line[2] <= delay_line[1];
                    delay_line[3] <= delay_line[2];
                    delay_line[4] <= delay_line[3];
                end
            end
            assign buf_rd[g][0] = delay_line[4];
            assign buf_rd[g][1] = delay_line[3];
            assign buf_rd[g][2] = delay_line[2];
            assign buf_rd[g][3] = delay_line[1];
            assign buf_rd[g][4] = delay_line[0];
        end
    endgenerate

    // 当前行延迟线
    reg [DATA_WIDTH-1:0] cur_delay [4:0];
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for (i = 0; i < 5; i = i + 1)
                cur_delay[i] <= 0;
        end else if (pixel_valid) begin
            cur_delay[0] <= pixel_in;
            cur_delay[1] <= cur_delay[0];
            cur_delay[2] <= cur_delay[1];
            cur_delay[3] <= cur_delay[2];
            cur_delay[4] <= cur_delay[3];
        end
    end

    // 窗口输出组合逻辑
    always @(*) begin
        // 行0:line_buf[3](最旧行)
        win_reg[0]  = buf_rd[3][0]; win_reg[1]  = buf_rd[3][1];
        win_reg[2]  = buf_rd[3][2]; win_reg[3]  = buf_rd[3][3];
        win_reg[4]  = buf_rd[3][4];
        // 行1:line_buf[2]
        win_reg[5]  = buf_rd[2][0]; win_reg[6]  = buf_rd[2][1];
        win_reg[7]  = buf_rd[2][2]; win_reg[8]  = buf_rd[2][3];
        win_reg[9]  = buf_rd[2][4];
        // 行2:line_buf[1]
        win_reg[10] = buf_rd[1][0]; win_reg[11] = buf_rd[1][1];
        win_reg[12] = buf_rd[1][2]; win_reg[13] = buf_rd[1][3];
        win_reg[14] = buf_rd[1][4];
        // 行3:line_buf[0]
        win_reg[15] = buf_rd[0][0]; win_reg[16] = buf_rd[0][1];
        win_reg[17] = buf_rd[0][2]; win_reg[18] = buf_rd[0][3];
        win_reg[19] = buf_rd[0][4];
        // 行4:当前行
        win_reg[20] = cur_delay[0]; win_reg[21] = cur_delay[1];
        win_reg[22] = cur_delay[2]; win_reg[23] = cur_delay[3];
        win_reg[24] = cur_delay[4];
    end

    // 窗口有效:需要至少5行数据且列位置>=4
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            win_valid_reg <= 0;
        else if (pixel_valid)
            win_valid_reg <= (row_cnt >= 4) && (col_cnt >= 4);
        else
            win_valid_reg <= 0;
    end

    assign win_valid = win_valid_reg;

    // 输出赋值
    genvar w;
    generate
        for (w = 0; w < 25; w = w + 1) begin : out_assign
            assign win_data[w] = win_reg[w];
        end
    endgenerate

endmodule

运行与验证

Testbench要点:

  • 输入一幅8x8灰度图像(像素值0~255递增),按行逐像素送入
  • 观察 win_valid 拉高时的窗口数据,与预期窗口对比
  • 预期:第5行第5列像素到达后,win_valid 首次拉高,此时窗口覆盖图像左上角5x5区域
// 验证核心代码片段
reg [7:0] img [0:63];  // 8x8图像
initial begin
    // 初始化图像数据
    for (int i = 0; i < 64; i++) img[i] = i;
    // 送入像素
    for (int r = 0; r < 8; r++) begin
        for (int c = 0; c < 8; c++) begin
            @(posedge clk);
            pixel_valid = 1;
            pixel_in = img[r*8 + c];
        end
    end
    pixel_valid = 0;
end

// 检查第一个有效窗口
always @(posedge clk) begin
    if (win_valid) begin
        // 窗口中心在(4,4),左上角像素应为img[0]=0
        if (win_data[0] !== 0) $error("窗口数据错误");
        // 中心像素应为img[4*8+4]=36
        if (win_data[12] !== 36) $error("中心像素错误");
    end
end

通过条件:仿真中 win_valid 在正确时序拉高,且窗口数据与输入图像对应位置像素值完全一致。使用Vivado或ModelSim运行仿真,观察波形确认窗口数据排列正确。

关键说明

  • 行缓存数量 = 卷积核高度 - 1 = 4,当前行直接参与窗口拼装
  • 列方向通过移位寄存器实现5拍延迟,保证窗口列对齐
  • 窗口数据索引约定:win_data[0] 为窗口左上角,win_data[4] 为右上角,win_data[20] 为左下角,win_data[24] 为右下角
  • 本设计为最小可综合实现,实际系统中行缓存通常用Block RAM实现以节省寄存器资源