情景

尽管存在大容量快速的存储器DDR3等,但图像处理中很多地方都需要相关运算,相关运算和卷积运动是类似的,在神经网络中的卷积实际就是相关操作,其原因是因为图像数据是对称的。FPGA在传统图像预处理也比较多,基于卷积核的变量名的命名规则图像处理方法有很多。常见的有中值滤波,高斯滤波,均值滤波,等等。这些作为图像预处理 应用很多,其本质就是核函数不同,即3×3,5×5模板不同。 基于上述所述,fpga做模板运算变量名,以3×3模板为例,需要同时获得3行3列的数据,但其实只需要缓存2行数据,第三行的数据直接获取进行。 实现数据的缓存方式有很多种,fifo,类似于c++中的队列,有先进先出的数据结构。 ram也可以作为数据的缓存。 基于上述,我们需要实神经网络引擎现对数据的缓存。变量类型有哪些

目的

实现行数据的缓存,为后续的图像预处理做铺垫。 本文实现三行数据的缓存,并在第四行数据到来的时刻,即三行数据写入完成时,完成同时输出三行缓存的变量泵数据。

链式存储实现流水线 数据的传递为乒乓操作 输出的同时写入

FPGA 图像篇(数据处理-行缓存)
基于运动鞋上述所示链式存储结构,我们使用三个fifo作为三行数据的缓存,当第一行数神经网络是什么据写入满line_buffer0后将第一行数据读出作为line_buffer1的输入,同时line_buffer变量泵0继续写入。 当line_buffer0,line_buffer1,line_buffer2,等三个fifo 写满目的数据后,第四行数据变量来临神经网络英文时,同时输出三行数据实现并行操作。

我们的思路为:1行缓存模块,2三行数据输出模变量名块(通过对行缓存模块的模块复用实现)

1 行缓存模块

基于fifo实现对行数据的缓运动健康存,我们神经网络算法以480的一行数据为例,数据位宽为10位。 首先调用ip核fifo。步骤如下: 1 打开fifo界面

FPGA 图像篇(数据处理-行缓存)

2 参数设置 我们选择同步时钟的块ram

FPGA 图像篇(数据处理-行缓存)
3 r变量英语ead mode选择fisrt word fall through 这样在独信号有效时就可以读出数据。没有一拍的延迟, 然后数据位宽选10,数据深度选512,因为我们数据个卷积数为480. 然后就是复位我们选择asynchronous r数据结构与算法eset 异步复位。

FPGA 图像篇(数据处理-行缓存)

然后就是其他一些快写满 读空卷积层信号我们都没有用到。

FPGA 图像篇(数据处理-行缓存)
这里也没有用到。

FPGA 图像篇(数据处理-行缓存)

以上为fifoip核的选取。神经网络是什么下面为fifo的例化

`    fifo_0 fifo_0(
  .clk(clk),                  // input wire clk
  .rst(!rst_n),                  // input wire rst
  .din(din),                  // input wire [7 : 0] din
  .wr_en(vaild_in),              // input wire wr_en
  .rd_en(rd_en),              // input wire rd_en
  .dout(dout), 
             // output wire [7 : 0] dout
  .full(),                // output wire full
  .empty(),              // output wire empty
  .wr_rst_busy(),  // output wire wr_rst_busy
  .rd_rst_busy()  // output wire rd_rst_busy
);`

然后就是行缓存模块的设计,我们的输入信号有clk,rst_n; din为输入数据流,vaild_in为输入数变量名据使能信号。dout为输运动鞋出数据流,rd_en为读使能信号,控制数据的读出。 rd_en信号为我数据结构们实现乒乓操作的关键,我们的目的的是缓存一行数据,故 assign rd_en = ((cnt == Img_WIDTH)&&vaild_in)?1’b1:1’b0; Img_WIDTH为图像的宽度在本例中为480,则rd_en在计数满480个数据后,并且写入信号有效时,赋值为1,进行立刻读出。 行缓存模块设计思路为: cnt计数计数满480,开始读出。 代码如下:

    `
module l_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;
fifo_generator_0 u_line_fifo(
.clk (clk),
.rst (!rst_n),
.din (din),
.wr_en (valid_in),
.rd_en (rd_en),
.empty(),
.full(),
.data_count(),    
.wr_rst_busy(),  
.rd_rst_busy()
);
endmodule
`

2 三行数据输出模块

在此模块中我们需要用到genvar i,generate语法来实现模块的重复例化。 我们以模板中以下程序来说说明此语法。 定义genvar,作为gen运动鞋er卷积层ate种的循环变量。 generate语句中定义的for语句,必须要有begin且begin后需要有标签。 此处的逻辑为当i等于0时候,输入运动会作文数据为din即外部输入,写入使能信号为外部信号vaild_in,此处为第一个行缓存模块的信号赋值。 i等于1和2时候,输入数据为上一个模块的输出数据,写入使能信号为上一个模块的读出使能信号。

    `
    genvar i;
    generate
        for (i = 0;i < LINE_NUM;i = i +1)
            begin : buffer
            if(i == 0) begin
                   always @(*)begin
                    line[i]<=din;
                    valid_in_r[i]<= valid_in;
                end
            end
            if(~(i == 0)) begin
                always @(* ) begin
                line[i] <= dout_r[i-1];
                valid_in_r[i] <= valid_out_r[i-1];
                end
            end
        l_buffer
             line_buffer(
            .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
endgenerate
`

基于上述所述完成了三个行缓存模数据结构知识点总结块的例化和连接。

`reg   [WIDTH-1:0] line[2:0];//保存每个模块的输入数据
reg   valid_in_r  [2:0];     //保存每个模块的写入使能信号
wire  valid_out_r [2:0]      //保存每个模块的读出使能信号
    ;`

要想输出三行缓存好的数据还需要一个标志位,用来同时输出:

`assign  flag = r_cnt >= 11'd3 ? valid_in : 1'b0;`

row_cnt为行计数,当计数大于三行时。flag等于vaild_in,因为此时v运动会加油稿aild_in信号有说明有数据在写入,flag有效时候数据开始同步,实现 输出doutr0 、doutr1,doutr2实现三行数据的循环输出。

   `assign dout_0 = dout_r[0];
    assign dout_1 = dout_r[1];
    assign dout_2 = dout_r[2];
    assign dout = dout_r[2];

`

再下来就是行列的计数: c_cnt神经网络的基本原理: 计数列数, c_cnt == Col_M-1 && vali卷积的物理意义d_i神经网络控制n == 1’b1 计数满480个数神经网络引擎据后并且还在写入,列计数信号置为0,否则写入时加一。 r_cn运动的好处t: 计神经网络分类数行数,当计数满一行且行数计满时候 清零,即一帧图像计满了。 否则当行数计满,列数加一。

`always @(posedge clk or negedge rst_n)
    if(rst_n == 1'b0)
        c_cnt <=  11'd0;
    else if(c_cnt == Col_M-1 && valid_in == 1'b1)
        c_cnt <=  11'd0;
    else if(valid_in == 1'b1)
        c_cnt <= c_cnt + 1'b1;
    else
        c_cnt <= c_cnt;
always @(posedge clk or negedge rst_n)
    if(rst_n == 1'b0)
        r_cnt  <=     11'd0;
    else if(row_cnt == ROW_NUM-1 && col_cnt == COL_NUM-1 && valid_in == 1'b1)
        r_cnt  <=     11'd0;
    else if(col_cnt == COL_NUM-1 && valid_in == 1'b1) 
        r_cnt    <=     r_cnt + 1'b1;`
综上所述可实现三行数据的缓存,当三行数据缓存完成时候开始输出,并不断输出三行。

三行数据数据结构题库输出模块 程序如下:

    clk,
    rst_n,
    valid_in,
    din,
    dout_0,
    dout_1,
    dout_2,
    dout,
    flag
);
parameter WIDTH = 10;
parameter  Col_M     =   480    ;
parameter Row_M     =   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_0;
output[WIDTH-1:0] dout_1;
output[WIDTH-1:0] dout_2;
//标志位 开始流水线操作的开始
output flag;
// 寄存器 行缓存数据的传递   
reg   [WIDTH-1:0] line[2:0];
reg   valid_in_r  [2:0];
wire  valid_out_r [2:0];
//行缓存数据的输出寄存
wire  [WIDTH-1:0] dout_r[2:0];
assign dout_0 = dout_r[0];
assign dout_1 = dout_r[1];
assign dout_2 = dout_r[2];
assign dout = dout_r[2];
//  行和列的计数器
reg      [10:0]  c_cnt ;
reg      [10:0]  r_cnt ;
// 例化行缓存模块,并将模块之间的信号进行关联。通过generate简化代码
genvar i;
generate
    begin:HDL1
    for (i = 0;i < LINE_NUM;i = i +1)
        begin : buffer           
            if(i == 0) begin
                always @(*)begin
                    line[i]<=din;
                    valid_in_r[i]<= valid_in;
                end
            end      
            if(~(i == 0)) begin
                always @(* ) begin                
                    line[i] <= dout_r[i-1];               
                    valid_in_r[i] <= valid_out_r[i-1];
                end
            end
         //  例化行缓存模块
        l_buffer
            line_buffe(
                .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
assign  flag  =r_cnt >= 11'd3 ? valid_in : 1'b0;
//行和列信号的计数更新
always @(posedge clk or negedge rst_n)
    if(rst_n == 1'b0)
        c_cnt <=  11'd0;
    else if(c_cnt == COL_NUM-1 && valid_in == 1'b1)
        c_cnt <=  11'd0;
    else if(valid_in == 1'b1)
        c_cnt <= c_cnt + 1'b1;
    else
        c_cnt <= c_cnt;
always @(posedge clk or negedge rst_n)
    if(rst_n == 1'b0)
        r_cnt  <=     11'd0;
    else if(r_cnt == ROW_NUM-1 && c_cnt == COL_NUM-1 && valid_in == 1'b1)
        r_cnt  <=     11'd0;
    else if(c_cnt == COL_NUM-1 && valid_in == 1'b1) 
        r_cnt    <=     r_cnt + 1'b1;
endmodule

testbench程序:

`define clk_period 20
module matrix_3x3_tb ();
reg clk;
reg [9:0] din;
reg rst_n;
reg valid_in;
wire [9:0] dout;
wire [9:0] dout_r0;
wire [9:0] dout_r1;
wire [9:0] dout_r2;
wire flag;
mat_3x3 mat(
    .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),
    .flag(flag)
);
initial begin
    rst_n = 0;
    valid_in = 0;
    #(`clk_period * 10);
    rst_n=1;
    #(`clk_period*10);
    valid_in = 1;
    #(`clk_period*480*10);
    valid_in = 0;
    #(`clk_period*50);
    $stop;
end
initial begin
    clk = 0;
end
always #(`clk_period/2) clk = ~clk;
always @ (posedge clk or negedge rst_n)begin
    if(!rst_n)
        din <= 1;
    else if(din == 480)
        din <= 1;
    else if (valid_in == 1'b1)
        din <= din + 1'b1;
end
endmodule

仿真结果

如下图所示在flag信号高电平的时刻同时输出三组数据 。

FPGA 图像篇(数据处理-行缓存)

flag低电平时刻也是不再写入数据的时刻。

FPGA 图像篇(数据处理-行缓存)