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实现以节省寄存器资源