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

代码参考自第一篇文章,如有不妥,望告知!!!

posted @ 2021-03-27 19:07  耐心的小黑  阅读(537)  评论(0编辑  收藏  举报