FPGA丨设计行缓存(linebuffer)生成像素矩阵
一、行缓存介绍
一幅图像是由一个个像素点构成的,对于一幅480*272大小的图片来说,其宽度是480,高度是272。在使用FPGA进行图像处理时,最关键的就是使用FPGA内部的存储资源对像素行进行缓存与变换。
由于在图像处理过程中,经常需要对图像进行开窗。如在进行卷积操作时,我们需要将开窗得到的如3X3大小的局部图像数据与卷积核进行卷积运算,从而完成处理。而开窗操作需要使用行缓存来实现,因此下面就介绍一下行缓存是什么。
假如我们需要3X3的窗口,那么我们就需要设计3行行缓存。因为正常情况下,大多图像数据都是一行一行的,先从左到右,然后从上到下将每一个像素数据输出。如果不加处理,那么我们是不能得到3X3的图像窗口的,我们的最终目的应该是让一帧图像的三行数据对齐之后同时输出,这样我们才能得到3X3的图像窗口!!!
为了实现3行行缓存,由上图可知,我们就需要3个fifo。整体的思路就是,第一行数据依次输入进来写入fifo1,当写到第一行最后一个数据时,开始从fifo1依次读出数据然后写入fifo2,依次类推!!!就这样,当第四行数据到来的时候,此时三个fifo会同时输出数据,而输出的数据正是前三行数据且是对齐的。
二、程序设计
1、line_buffer
`timescale 1ns/1ps
module line_buffer (
rst_n,
clk,
din,
dout,
valid_in,
valid_out
);
parameter WIDTH = 10;//数据位宽
parameter IMG_WIDTH = 480;//图像宽度
input rst_n;
input clk;
input [WIDTH-1:0] din;
output [WIDTH-1:0] dout;
input valid_in;//输入数据有效,写使能
output valid_out;//输出给下一级的valid_in,也即上一级开始读的同时下一级就可以开始写入
wire rd_en;//读使能
reg [8:0] cnt;//这里的宽度注意要根据IMG_WIDTH的值来设置,需要满足cnt的范围≥图像宽度
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= {9{1'b0}};
else if(valid_in)
if(cnt == IMG_WIDTH)
cnt <= IMG_WIDTH;
else
cnt <= cnt +1'b1;
else
cnt <= cnt;
end
//一行数据写完之后,该级fifo就可以开始读出,下一级也可以开始写入了
assign rd_en = ((cnt == IMG_WIDTH) && (valid_in)) ? 1'b1:1'b0;
assign valid_out = rd_en;
line_fifo u_line_fifo(
.clk (clk),
.rst (!rst_n),
.din (din),
.wr_en (valid_in),
.rd_en (rd_en),
.dout(dout),
.empty(),
.full(),
.data_count(),
.wr_rst_busy(),
.rd_rst_busy()
);
/*
.clk(clk), // input wire clk
.rst(rst), // input wire rst
.din(din), // input wire [9 : 0] din
.wr_en(wr_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(dout), // output wire [9 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.data_count(data_count), // output wire [8 : 0] data_count
.wr_rst_busy(wr_rst_busy), // output wire wr_rst_busy
.rd_rst_busy(rd_rst_busy) // output wire rd_rst_busy
*/
endmodule
这里的FIFO IP核使用的是Vivado中的IP核,参数方面需要注意的是:
- 同步FIFO
- depth至少要≥图像一行的像素点的个数
- 勾选了
First Word Fall Through
,这样read latency = 0,否则的话三行数据不能对齐
2、matrix_3x3
`timescale 1ns/1ps
module matrix_3x3 (
clk,
rst_n,
valid_in,//输入数据有效信号
din,//输入的图像数据,将一帧的数据从左到右,然后从上到下依次输入
dout_r0,//第一行的输出数据
dout_r1,//第二行的输出数据
dout_r2,//第三行的输出数据
dout,//最后一行的输出数据
mat_flag//当第四行数据到来,前三行数据才开始同时输出,此时该信号拉高
);
parameter WIDTH = 10;//数据位宽
parameter COL_NUM = 480 ;
parameter ROW_NUM = 272 ;
parameter LINE_NUM = 3;//行缓存的行数
input clk;
input rst_n;
input valid_in;
input [WIDTH-1:0] din;
output[WIDTH-1:0] dout;
output[WIDTH-1:0] dout_r0;
output[WIDTH-1:0] dout_r1;
output[WIDTH-1:0] dout_r2;
output mat_flag;
reg [WIDTH-1:0] line[2:0];//保存每个line_buffer的输入数据
reg valid_in_r [2:0];
wire valid_out_r [2:0];
wire [WIDTH-1:0] dout_r[2:0];//保存每个line_buffer的输出数据
assign dout_r0 = dout_r[0];
assign dout_r1 = dout_r[1];
assign dout_r2 = dout_r[2];
assign dout = dout_r[2];
genvar i;
generate
begin:HDL1
for (i = 0;i < LINE_NUM;i = i +1)
begin : buffer_inst
// line 1
if(i == 0) begin: MAPO
always @(*)begin
line[i]<=din;
valid_in_r[i]<= valid_in;//第一个line_fifo的din和valid_in由顶层直接提供
end
end
// line 2 3 ...
if(~(i == 0)) begin: MAP1
always @(*) begin
//将上一个line_fifo的输出连接到下一个line_fifo的输入
line[i] <= dout_r[i-1];
//当上一个line_fifo写入480个数据之后拉高rd_en,表示开始读出数据;
//valid_out和rd_en同步,valid_out赋值给下一个line_fifo的
//valid_in,表示可以开始写入了
valid_in_r[i] <= valid_out_r[i-1];
end
end
line_buffer #(WIDTH,COL_NUM)
line_buffer_inst(
.rst_n (rst_n),
.clk (clk),
.din (line[i]),
.dout (dout_r[i]),
.valid_in(valid_in_r[i]),
.valid_out (valid_out_r[i])
);
end
end
endgenerate
reg [10:0] col_cnt ;
reg [10:0] row_cnt ;
assign mat_flag = row_cnt >= 11'd3 ? valid_in : 1'b0;
always @(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
col_cnt <= 11'd0;
else if(col_cnt == COL_NUM-1 && valid_in == 1'b1)
col_cnt <= 11'd0;
else if(valid_in == 1'b1)
col_cnt <= col_cnt + 1'b1;
else
col_cnt <= col_cnt;
always @(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
row_cnt <= 11'd0;
else if(row_cnt == ROW_NUM-1 && col_cnt == COL_NUM-1 && valid_in == 1'b1)
row_cnt <= 11'd0;
else if(col_cnt == COL_NUM-1 && valid_in == 1'b1)
row_cnt <= row_cnt + 1'b1;
endmodule
3、仿真程序matrix_3x3_tb
`timescale 1ns/1ps
`define CLK_PERIOD 20//50MHZ
module matrix_3x3_tb ();
reg clk;
reg [9:0] din;
reg rst_n;
reg valid_in;
//wires
wire [9:0] dout;
wire [9:0] dout_r0;
wire [9:0] dout_r1;
wire [9:0] dout_r2;
wire mat_flag;
matrix_3x3 matrix_3x3_inst(
.clk (clk),
.din (din),
.dout(dout),
.dout_r0(dout_r0),
.dout_r1(dout_r1),
.dout_r2(dout_r2),
.rst_n(rst_n),
.valid_in(valid_in),
.mat_flag(mat_flag)
);
initial begin
clk = 0;
rst_n = 0;
valid_in = 0;
#(`CLK_PERIOD * 10);
rst_n=1;
#(`CLK_PERIOD*10);
valid_in = 1;
#(`CLK_PERIOD*480*5);
valid_in = 0;
#(`CLK_PERIOD*20);
$stop;
end
always #(`CLK_PERIOD/2) clk = ~clk;
/*
这里din会在0-479之后,返回0,再次从0-479;
所以,模拟的每一行数据都是从0-479,因此在仿真时三行数据对齐时它们的数据才会是一样的。
如果输入的din是真实的图像数据,那么由于一帧图像数据每一行是不一样的,所以对齐后三行数据也不相同。
*/
always @ (posedge clk or negedge rst_n)begin
if(!rst_n)
din <= 0;
else if(din == 479)
din <= 0;
else if (valid_in == 1'b1)
din <= din + 1'b1;
end
endmodule
三、仿真结果
可以看到,第四行数据到来的时候,mat_flag信号拉高,此时三个fifo会同时输出数据,而输出的数据正是前三行数据且是对齐的。因为这里din一直在0-479之间循环,所以三行数据是一样的;如果是真实的图像数据,那么这三行数据是不一样的。
参考文章:
https://my.oschina.net/u/4579395/blog/4636457
https://blog.csdn.net/qq_41332806/article/details/107385545
代码参考自第一篇文章,如有不妥,望告知!!!