1. 3x3卷积核的滑动窗口生成器
任务目标
实现一个可综合的3x3滑动窗口生成器,从按行输入的图像像素流中,在每个时钟周期输出当前像素位置对应的3x3邻域数据,供后续卷积运算直接使用。
设计思路
图像按行逐像素输入(行优先),每个时钟周期进入一个像素。为了在任意时刻都能得到以当前输入像素为右下角的3x3窗口,需要缓存前两行数据。采用两个行缓冲(每个缓冲存储一行像素)加三个移位寄存器组(每行一个,深度为3)的结构。当第三行数据开始输入时,每个时钟周期即可输出完整的3x3窗口。
接口定义
| 信号 | 方向 | 位宽 | 说明 |
|---|---|---|---|
| clk | input | 1 | 时钟 |
| rst_n | input | 1 | 异步复位,低有效 |
| pixel_in | input | 8 | 输入像素(灰度) |
| pixel_valid | input | 1 | 输入像素有效标志 |
| frame_start | input | 1 | 帧起始脉冲(高电平1周期) |
| row_len | input | 16 | 图像宽度(每行像素数),需≥3 |
| window_valid | output | 1 | 窗口数据有效标志 |
| window_out | output | 72 | 3x3窗口,按行拼接:{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验证要点:
- 输入一个5x5图像(像素值0~24),row_len=5,连续25个时钟周期输入像素,frame_start在第一个像素前拉高1周期。
- 观察window_valid信号:从第3行第3个像素(即第13个像素)开始,每个时钟周期输出一个有效窗口。
- 检查第13个像素(值为12)对应的窗口输出应为:
{row0: 0,1,2} {row1: 5,6,7} {row2: 10,11,12}
即window_out = 24'h000102_050607_0A0B0C(按字节拼接)。 - 继续检查后续像素的窗口,确认窗口内容随输入逐像素右移。
通过条件:仿真波形中,window_valid在正确位置拉高,且window_out数据与手工计算的3x3邻域完全一致,无延迟错位。
边界说明
本设计假设图像宽度≥3且连续输入无气泡(pixel_valid每个周期有效)。行缓冲深度设为2048,若图像宽度超过2048需调整参数。窗口输出以当前输入像素为右下角,适合直接接入3x3卷积运算单元。