• 14图像边缘检测的sobel_ctrl控制模块


    一设计功能

    计算得到的dxy,再通过和阈值比较大小,输出po_sum作为VGA的输入,在显示器器上显示图像的轮廓。

    二设计思路

       根据前一篇博客对sobel算法的介绍,先通过FIFO的双流水线操作采集到三行三列的九个数,再得到dx  和dy,再求dx 和dy的绝对值和给Dxy,最后把Dxy 和阈值比较大小得到输出po_sum。

    (一)双FIFO的流水线操做

    根据FIFO的时序图:写的读写控制代码如下

    注意在实际过程中加一个wr_cnt来计算数据的个数。一般先设置一个FIFOIP核,再自己写一个控制程序。根据上面的读写时序,控制程序如下

    module fifo_ctrl(

           input      wire              clk,

           input      wire              rst_n

           );

    reg                              wr_en;

    reg         [8:0]              wr_cnt;

    reg         [7:0]              wr_data;

    wire                                   full,empty;

    reg                              rd_en;

    reg         [1:0]              empty_dly;

    reg                              read_start;

    reg                [8:0]              rd_cnt;

    wire              [7:0]              rd_data;

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  wr_en <= 1'b0;

           end

           else if (wr_cnt == 'd0) begin

                  wr_en <= 1'b1;

           end

           else if (wr_cnt== 'd257) begin

                  wr_en <= 1'b0;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  wr_cnt <= 'd0;

           end

           else if (wr_cnt != 'd257) begin

                  wr_cnt <= wr_cnt + 1'b1;

           end

    end

    always @(posedge clk or  negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  wr_data <='d0;

           end

           else if (wr_en == 1'b1) begin

                  wr_data <= wr_data + 1'b1;

           end

           else begin

                  wr_data <= 'd0;

           end

    end

    always @(posedge clk) begin

           empty_dly <= {empty_dly[0],empty};//shift reg empty_dly == 2'b10 negedge

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  read_start <= 1'b0;

           end

           else if (empty_dly == 2'b10) begin

                  read_start <= 1'b1;

           end

           else begin

                  read_start <= 1'b0;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  rd_en <= 1'b0;

           end

           else if(rd_cnt == 256)begin

                  rd_en <= 1'b0;

           end

           else if (read_start == 1'b1) begin

                  rd_en <= 1'b1;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  rd_cnt <= 'd0;

           end

           else if (rd_en == 1'b1) begin

                  rd_cnt <= rd_cnt + 1'b1;

           end

    end

    asfifo_w256x8_r256x8 fifo_inst (

      .wr_clk(clk), // input wr_clk

      .rd_clk(clk), // input rd_clk

      .din(wr_data), // input [7 : 0] din

      .wr_en(wr_en), // input wr_en

      .rd_en(rd_en), // input rd_en

      .dout(rd_data), // output [7 : 0] dout

      .full(full), // output full

      .empty(empty) // output empty

    );

    endmodule

         1. sobel_ctrl控制模块的关键点一:双FIFO流水线操作

    有了上面一个FIFO的读写控制代码基础,现在来实现怎么设计实现双FIFO的流水线操作

    /根据时序图写代码时,一定得懂设计思路

    //FIFO1的写使能怎么产生,写数据来源于什么

    //FIFO2的写使能怎么产生,写数据来源于什么

    //读信号怎么产生,读出的数据给谁,读信号有效才加

    来回答上面的问题:

      第一部分:写使能信号的产生

    FIFO1的写使能信号:在0行时,来一个pi_flag 就拉高。对于2~84行的数据,利用wr_en1_pre1 和wr_en1_pre2进行打一拍操作,在2~84行,只要有pi_flag 就拉高wr_en1_pre2。

    FIFO2的写使能怎么产生:在1~84行且来一个pi_flag 就拉高wr_en2

    第二部分:写数据的产生

    FIFO1:在第0行,fifo1的数据来源于串口接收端。对于2~84行的数据,FIFO1的数据来源于FIFO2的读出端数据。

    FIFO2:在1~84行,FIFO2的数据来源于串口的数据(来一个pi_flag)

       第三部分:读信号和读出的数据输出给谁

    对于FIFO1和FIFO2的读出端数据dout1和dout2.对于2~85行的数据,在读信号的控制下,读出后直接在加标志信号flag_add控制下,将FIFO1 和FIFO2的读出端数据和串口接收端的数据加起来输出给PO_SUM

       读信号rd_en,在大于第二行,且来一个pi_flag 就拉高rd_en

    根据上面双FIFO流水线设计思路的要点,编写代码如下:

    parameter VALUE = 10 ; //阈值

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  wr_en1 <= 1'b0;

           end

           else if (cnt_row == 'd0) begin

                  wr_en1 <= pi_flag;

           end

           else begin

                  wr_en1 <= wr_en1_pre1;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  wr_en1_pre2 <= 1'b0;

           end

           else if (cnt_row >=2 && cnt_row <=198 && pi_flag == 1'b1) begin

                  wr_en1_pre2 <= 1'b1;

           end

           else begin

                  wr_en1_pre2 <= 1'b0;

           end

    end

    always @(posedge clk) begin

           wr_en1_pre1 <= wr_en1_pre2;

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  rd_en <= 1'b0;

           end

           else if (cnt_row >=2 && cnt_row <=199 && pi_flag == 1'b1) begin

                  rd_en <= 1'b1;

           end

           else begin

                  rd_en <= 1'b0;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  data_in1 <= 'd0;

           end

           else if (cnt_row == 'd0) begin

                  data_in1 <= pi_data;

           end

           else  begin//if (cnt_row >=2 && cnt_row <=84)

                  data_in1 <= dout2;

           end

    end

    always @(posedge clk or negedge rst_n ) begin

           if (rst_n  == 1'b0) begin

                  // reset

                  wr_en2 <= 1'b0;

           end

           else if (cnt_row >=1 && cnt_row <=198 && pi_flag == 1'b1) begin

                  wr_en2 <= 1'b1;

           end

           else begin

                  wr_en2 <= 1'b0;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  data_in2 <= 'd0;

           end

           else if ( cnt_row >=1 && cnt_row <=198) begin

                  data_in2 <= pi_data;

           end

    end

    always @(posedge clk) begin

           add_flag <= flag_shift;

    end

    2. sobel_ctrl控制模块的关键点二:怎么采集三行三列的9个数

    关键是怎么采集三行三列的9个数,我的想法是,比如第零行用FIFO1存储,然后再加三个寄存器,打拍操作。每来一个串口接收标志信号就送FIFO1的数据到如寄存器1中,然后是寄存器2,再是寄存器3。

    怎么采集三行三列的9个数

    //注意打拍的寄存器越往后是越早的数据

    //如pi_data是最新的数据,dout1_tt是前两个数据

    reg flag_shift;

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  // reset

                  flag_shift <= 1'b0;

           end

           else begin

                  flag_shift <= rd_en;

           end

    end

     //打拍操作,三个寄存器在标志信号下打一拍存一个数

    always@(posedge clk)begin

           if(flag_shift == 1'b1)begin

           {dout1_tt,dout1_t} <= {dout1_t,dout1};

           {dout2_tt,dout2_t} <= {dout2_t,dout2};

           {rx_data_tt,rx_data_t}<={rx_data_t,pi_data};

           end

    end

    3. sobel_ctrl控制模块的关键点三:怎么得到横向的DX和纵向的DY?

    always @(posedge clk) begin

           add_flag <= flag_shift;

    end

    //对这三行三列的数进行加减乘运算

    //((dout2-dout2_tt)<<1)实现乘2操作

    reg [7:0]dx;

    always@(posedge clk or negedge rst_n)

           if(rst_n==0)

           dx<=0;

    else if(add_flag)

           dx<=(dout1-dout1_tt)+((dout2-dout2_tt)<<1)+(pi_data-rx_data_tt);

           //对这三行三列的数进行加减乘运算

    reg [7:0]dy;

    always@(posedge clk or negedge rst_n)

           if(rst_n==0)

           dy<=0;

    else if(add_flag)

           dy<=(dout1_tt-rx_data_tt)+((dout1_t-rx_data_t)<<1)+(dout1-pi_data);

    4. sobel_ctrl控制模块的关键点四:怎么得到横向的DX和纵向的DY的绝对值和?

    利用原码反码,补码的关系。即最高位为符号位,1表示负数,绝对值为取反加一。0表示正数,绝对值等于本身。值得注意的是进行绝对值前,利用flag_d_pre,flag_d, flag_abs延迟三个时钟周期,打了三拍。再求绝对值的和,又利用flag_dxy打了一拍。

    //对dx和dy进行绝对值求和

           //补码原码反码的关系

    //flag_d_pre

    reg flag_d_pre;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           flag_d_pre<=0;

    else if(cnt_row>=2 && cnt_col>=2 && pi_flag)

           flag_d_pre<=1;

    else

           flag_d_pre<=0;

    //flag_d;

    reg flag_d;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           flag_d<=0;

    else

           flag_d<=flag_d_pre;

           //falg_abs

    reg flag_abs;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           flag_abs<=0;

    else

           flag_abs<=flag_d;

    //abs_dx

    reg [7:0]abs_dx;

    reg [7:0]abs_dy;

    always@(posedge clk or negedge rst_n)

    if(!rst_n)

           abs_dx<=0;

    else if(flag_abs & dx[7]==1'b1)

           abs_dx<=(~dx)+1;

    else if(flag_abs & dx[7]==1'b0)

           abs_dx<=dx;

    //abs_dy 

    always@(posedge clk or negedge rst_n)

    if(!rst_n)

           abs_dy<=0;

    else if(flag_abs & dy[7]==1'b1)

           abs_dy<=(~dy)+1;

    else if(flag_abs & dy[7]==1'b0)

           abs_dy<=dy;

    //取了绝对值进行打拍

    reg flag_dxy;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           flag_dxy<=0;

    else

           flag_dxy<=flag_abs;

    reg [7:0]dxy;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           dxy<=0;

    else if(flag_dxy)

           dxy<=abs_dy+abs_dx;

    5. sobel_ctrl控制模块的关键点五:怎么阈值比较得到边缘,大于阈值白色显示,否则黑色?

    //判断相对于阈值大小输出不同的图像

    reg flag_rgb;

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           flag_rgb<=0;

    else

           flag_rgb<=flag_dxy;

    //rgb

    always@(posedge clk or negedge rst_n)

    if(!rst_n)

           po_sum<=0;

    else if(flag_rgb & dxy>=VALUE)

           po_sum<=8'hff;

    else if(flag_rgb & dxy<=VALUE)

           po_sum<=8'h00;  

    always@(posedge clk or negedge rst_n)

    if(rst_n==0)

           po_flag<=0;

    else

           po_flag<=flag_rgb;

    三,仿真验证

    到这里,终于把图像边缘检测的算法关键点讲清楚了,而我的仿真是先利用80X4的数据,即用sublime的重复语句产生一个0~320的数,然后再为仿真的输入,进行波形的验证。

    在sublime中利用敲出{:08b},然后再选中这个代码,再右键选择重复代码,之后在设置起始位和停止位及步长,,敲回车即可。

    如0~320,1

    RAM一般最大为9K,即1024X9个数据。

    刚开始我写的仿真程序包含了串口发送有点复杂,所以在这里提高vga_ram模块的仿真,两者很相似,只需要改哈端口就行,设计思想相同。把上面产生的数据保存在一个txt文件里,在仿真里用mem读取这个文本,再用一个task函数把这数据赋值给我们功能模块vga_ram的输入pi_data,如程序所写。b第二个注意点,如要产生300X3的数据,不用写一个矩阵的仿真程序,直接并转串,用一个900X1的数据表示,结果是一样,还很方便。

    `timescale 1ns / 1ps

    module tb_vga_ram;

           // Inputs

           reg sclk;

           reg rst_n;

           reg pi_flag;

           reg [7:0]pi_data;

           reg [7:0] mem[899:0];

           // Outputs

           wire hsync ;

           wire vsync  ;

           wire [7:0]vga_rgb;

           vga_ram  inst_vga_ram (

                         .clk     (sclk),

                         .rst_n   (rst_n),

                         .pi_flag (pi_flag),

                         .pi_data (pi_data),

                         .hsync   (hsync),

                         .vsync   (vsync),

                         .vga_rgb (vga_rgb)

                  );

           // Instantiate the Unit Under Test (UUT)

           initial begin

                  // Initialize Inputs

                  sclk = 0;

                  rst_n = 0;

                  pi_flag=0;

                  pi_data=0;

                  // Wait 100 ns for global reset to finish

                  #100;

            rst_n =1;

                  // Add stimulus here

           end

           initial begin

                  $readmemb("./data.txt",mem);

           end

           always #10 sclk = ~sclk;

           always #100 pi_flag = ~pi_flag;

           initial begin

                  #200;

                  rx_byte();

           end

           task rx_byte;

                  integer i;

                  integer j;

                  begin

                         for(j=0;j<900;j=j+1)begin

                                for (i=0;i<900;i=i+1)begin

                                       pi_data=mem[i];

                                #199;

                                end

                         end

                  end

           endtask

       

    endmodule

    波形如下:

  • 相关阅读:
    VMware下Linux配置网络
    前端知识之查漏补缺二
    前端网络基础查漏补缺篇
    简单实现Promise
    前端知识之查漏补缺-1
    git tag
    云服务器安装node环境 mysql nginx
    js动画
    vue原理之双向绑定虚
    js的封装、继承与多态
  • 原文地址:https://www.cnblogs.com/Xwangzi66/p/12968016.html
Copyright © 2020-2023  润新知