zoukankan      html  css  js  c++  java
  • 14FPGA综设之图像边沿检测的sobel算法

    连续学习FPGA基础课程接近一个月了,迎来第一个有难度的综合设计,图像的边沿检测算法sobel,用verilog代码实现算法功能。

    一设计功能

    (一设计要求)

    (二系统框图)

     根据上面的系统,Verilog代码如下:注意的是,VGA模块的时钟输入有两个,一是50M,二是25M。PLL的IP核的输入时钟连接顶层时钟,产生的输出时钟连接各个功能模块,有两个一是50M,二是25M。50M连接串口接收,sobel_ctrl控制模块。25M连接VGA_ram的vga显示部分和RAM的读地址的时钟,50M连接VGA_ram的RAM的写地址时钟。

    module top_sobel(
    input s_clk,
    input wire rst_n,
    input wire rx,

    output wire hsync,
    output wire vsync,
    output wire [7:0] rgb
    );

    wire clk_25out;
    wire clk_50M;
    gen_clk25M gen_clk_ins
    (// Clock in ports
    .CLK(s_clk), // IN
    // Clock out ports
    .CLK_25MOUT1(clk_25out), // OUT
    .CLK_50M(clk_50M)); // OUT

    wire uart_flag;
    wire [7:0]uart_data;

    wire sobel_flag;
    wire [7:0]sobel_data;

    uart_rx uart_rx_m0(
    .sclk(clk_50M),
    .rst_n(rst_n),
    .rx(rx),
    .po_data(uart_data),
    .po_flag(uart_flag)
    );

    sobel_ctrl inst_sobel_ctrl (
    .clk (clk_50M),
    .rst_n (rst_n),
    .pi_flag (uart_flag),
    .pi_data (uart_data),
    .po_flag (sobel_flag),
    .po_sum (sobel_data)
    );

    vga_ram inst_vga_ram (
    .clk (clk_25out),
    .clks (clk_50M),
    .rst_n (rst_n),
    .pi_flag (sobel_flag),
    .pi_data (sobel_data),
    .hsync (hsync),
    .vsync (vsync),
    .vga_rgb (rgb)
    );

     

    endmodule

    二设计思路

               在原有基础上,自己动手设计,仿真验证,调试直到成功。

    (一)我自己觉得这次的功能和上次的双FIFO流水线很紧密,所以这次,应该是先把双FIFO的逻辑弄懂,即把他们的时序图自己大致画一下,然后我觉得关键是怎么采集三行三列的9个数,我的想法是,比如第零行用FIFO1存储,然后再加三个寄存器,打拍操作。每来一个串口接收标志信号就送FIFO1的数据到如寄存器1中,然后是寄存器2,再是寄存器3。这样不断循环,就可以采集九个数据,最后再按照步骤进行相应的加法和乘法,绝对值操作。

    刚开始要循序渐进,可以弄20X20的数据,而且只是实现到DX这一步,等完成了再往下继续加功能。

    (二)设计知识点

    1.打拍操作:同步复位,没有复位信号

    第二点,我的FIFO1和FIFO2数据是在rd_en读使能信号控制下进行寄存器的打拍操作,而对这就个数进行运算则是在add_flag控制下,赋值输出(原因是rd_en  提前了add_flag一个时钟周期)

    尤老师,讲为啥有wr_en_pre1和wr_en_pre12.主要是让FIFO的写使能信号和读使能信号同步。

    sobel算法实现过程介绍

    第三点,仔细看了几遍sobel算法的介绍,明白只需要采集到9个点,然后再按照步骤实现功能即可。

    三所遇问题及解决办法

    由于这次编写的综合设计模块,众多,而且代码量有上千行,我先暂时不详解每个模块的设计代码,而是我在亲自动手设计图像边沿检测的收获。

    问题一:我直接在v3的源码中修改,想直接仿真运行,却发现modelsim报错:显示路径错误

    * Error: (vopt-1933) Unable to create temporary directory D:/netclass/firstlevel/net19_double_fifo/double_fifo/work/_tempmsg

    # No such file or directory. (errno = ENOENT)

    # Error loading design

    原因:一是可能路径太长,二是原工程的路径和当前不符合。所以要么把工程直接放在更目录或者自己重新建立一个。

    我的解决办法,自己重新建立工程,如IP核等,或者路径更改,可以移除工程在添加进去。

    问题二:仿真时找不到文本,提示如下

    ** Warning: (vsim-7) Failed to open readmem file "./data.txt" in read mode.

    # No such file or directory. (errno = ENOENT)    : sim/tb_top_uart.v(45)

    #    Time: 0 ps  Iteration: 0  Instance: /tb_top_uart

    下面是错误目录下

    解决办法:我是问了尤老师才知道应该放在modelsim的目录下即和ISE工程同一目录下

    问题三:怎么对86X4的数据进行仿真

    答案是并转串。即本来是86X4的矩阵,但我可以先在TXT文档中用344X1的数据替代,直接在仿真中把86改成344即可。

    `timescale 1ns / 1ps

    module tb_top_uart;

           // Inputs

           reg sclk;

           reg rst_n;

           reg rx;

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

           // Outputs

           wire tx;

           // Instantiate the Unit Under Test (UUT)

           top_dfifo uut (

                  .clk(sclk),

                  .rst_n(rst_n),

                  .rx(rx),

                  .tx(tx)

           );

           initial begin

                  // Initialize Inputs

                  sclk = 0;

                  rst_n = 0;

                  rx = 1;

                  // 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;

           initial begin

                  #200;

                  rx_byte();

           end

           task rx_byte();

                  integer i;

                  integer j;

                  begin

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

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

                                       rx_bit(mem[i]);

                                end

                         end

                  end

           endtask

       

        task rx_bit(input [7:0] data);

               integer i;

               begin

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

                             case (i)

                                        0:rx =0;

                                        1:rx =data[i-1];

                                        2:rx =data[i-1];

                                        3:rx =data[i-1];

                                        4:rx =data[i-1];

                                        5:rx =data[i-1];

                                        6:rx =data[i-1];

                                        7:rx =data[i-1];

                                        8:rx =data[i-1];

                                        9:rx =1;

                             endcase

                             #104160;

                      end

               end

        endtask 

    endmodule

    问题三,怎么进行绝对值的计算

    方法:先自己网上搜了下,大概是利用原码反码,补码的关系。即最高位为符号位,1表示负数,绝对值为取反加一。0表示正数,绝对值等于本身。

    //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)

           abs_dx<=~dx+1;

    else if(flag_abs & dx[7]==0)

           abs_dx<=dx;

    问题四,怎么写VGA控制程序,在里面调用一个RAM。用来存储198X198个数据,VGA模块负责RAM的读写,让RAM里写入sobel_ctrl模块处理好数据,读出来的数据需要给rgb进行显示。

    我的想法是:要做一个新东西,就首先学会联系已学过的东西(基础),既然用RAM读写这198X198数据,那么首先得搞明白RAM。再自己适当修改一下读写逻辑,如数据的读写地址等等就欧克。

    我选择的RAM类型为:Simple Dual Port RAM,该ram包含两个地址总线,一个写地址和一个读地址,分别控制两个地址总线可以控制该ram的读和写。还有一个关键信号:wr_en,控制读写逻辑。本RAM位宽为8深度为256的ram.

    下面的代码是根据上面的RAM的读写时序和设定的位宽深度设计的:

    module ctrl_ram(

           input      wire                    clk,

           input      wire                    rst_n,

           input      wire      [7:0]       pi_data,

           output   wire      [7:0]       po_data

           );

    reg               wr_en;

    reg [7:0]       wr_addr;

    reg [7:0]       rd_addr;

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  wr_en <= 1'b1;

           end

           else if(rd_addr == 'd255) begin

                  wr_en <= 1'b1;

           end

           else if (wr_addr == 'd255) begin

                  wr_en <= 1'b0;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  wr_addr <= 'd0;

           end

           else if (wr_en == 1'b1) begin

                  wr_addr <= wr_addr + 1'b1;

           end

           else begin

                  wr_addr <= 'd0;

           end

    end

    always @(posedge clk or negedge rst_n) begin

           if (rst_n == 1'b0) begin

                  rd_addr <= 'd0;

           end

           else if (wr_en == 1'b0) begin

                  rd_addr <= rd_addr + 1'b1;

           end

           else begin

                  rd_addr <= 'd0;

           end

    end

    ram_256x8 ram_256x8_inst (

      .clka(clk), // input clka

      // .wea(wr_en), // input [0 : 0] wea

      .wea(1'b0),

      .addra(wr_addr), // input [7 : 0] addra

      .dina(pi_data), // input [7 : 0] dina

      .clkb(clk), // input clkb

      .addrb(rd_addr), // input [7 : 0] addrb

      .doutb(po_data) // output [7 : 0] doutb

    );

    endmodule

    而这次的写使能信号是由 sobel_ctrl的pi_flag控制的,还有一个关键点是写地址是50M的系统时钟,而读地址是25M的时钟。所以需要在顶层加一个PLL输出两个时钟,一个是50M,一个是25M,PLL的输入时钟接顶层时钟50M,然后把PLL输出的时钟分别连在串口模块,sobel模块,VGA模块。

    关键点:在VGA里调用一个40K的ram存储198X198的数据,怎么设计写使能信号和读写地址值得注意。先将它的代码展示如下

    module vga_ram(
    input wire clk,
    input wire clks,
    input wire rst_n,
    input wire pi_flag,
    input wire [7:0] pi_data,
    output reg hsync,
    output reg vsync,
    output reg [7:0] vga_rgb
    );

    reg [15:0] addrb,addra;
    wire [7:0] doutb;

    parameter MAX_value = 16'd39203;

    reg [8:0]x; //行移动计数器最大439
    reg [8:0]y; //场移动计数器最大279

    reg dec_x;//行计数器减一切换标志信号
    reg dec_y;//场计数器减一切换标志信号

    reg [9:0]cnt_h;
    reg [9:0]cnt_v;

    parameter h_max =10'd799;
    parameter v_max = 10'd524;

    //行计数器
    always@(posedge clk or negedge rst_n)
    if(!rst_n)
    cnt_h<=10'd0;
    else if(cnt_h==h_max)
    cnt_h<=10'd0;
    else
    cnt_h<=cnt_h+1'b1;
    //场计数器
    always@(posedge clk or negedge rst_n)
    if(!rst_n)
    cnt_v<=10'd0;
    else if(cnt_v==v_max & cnt_h==h_max)
    cnt_v<=10'd0;
    else if(cnt_h==h_max)
    cnt_v<=cnt_v+1'b1;
    //hsync 行同步信号
    always@(posedge clk or negedge rst_n)
    if(!rst_n)
    hsync<=1'b1;
    else if(cnt_h==10'd95)
    hsync<=1'b0;
    else if(cnt_h==h_max)
    hsync<=1'b1;

    //vsync场同步信号
    always@(posedge clk or negedge rst_n)
    if(!rst_n)
    vsync<=1'b1;
    else if(cnt_v=='d1 & cnt_h==h_max)
    vsync<=1'b0;
    else if(cnt_v==v_max & cnt_h==h_max)
    vsync<=1'b1;


    parameter T100MS = 23'd2_599_999;
    //div counter
    reg [22:0]div_cnt;
    always@(posedge clk or negedge rst_n)begin
    if(rst_n==0)
    div_cnt<=23'd0;
    else if(div_cnt==T100MS)
    div_cnt<=23'd0;
    else
    div_cnt<=div_cnt+1'b1;
    end

    //the flag of one_s_flag
    reg one_s_flag;
    always@(posedge clk or negedge rst_n)begin
    if(rst_n==0)
    one_s_flag<=1'b0;
    else if(div_cnt==(T100MS-1))begin
    one_s_flag<=1'b1;
    end
    else begin
    one_s_flag<=1'b0;
    end
    end
    always@(posedge clk or negedge rst_n)
    if(!rst_n)begin
    x<=9'd0;
    dec_x<=1'b0;
    end
    else begin
    case(dec_x)
    0:begin
    if(x==9'd439)begin
    x<=x;
    dec_x<=1'b1;
    end
    else if(one_s_flag) begin
    x<=x+1'b1;
    dec_x<=1'b0;
    end
    end
    1:begin
    if(x==9'd0)begin
    x<=x;
    dec_x<=1'b0;
    end
    else if(one_s_flag) begin
    x<=x-1'b1;
    dec_x<=1'b1;
    end
    end
    default: ;
    endcase
    end


    always@(posedge clk or negedge rst_n)
    if(!rst_n)begin
    y<=9'd0;
    dec_y<=1'b0;
    end
    else begin
    case(dec_y)
    0:begin
    if(y==9'd279)begin
    y<=y;
    dec_y<=1'b1;
    end
    else if(one_s_flag)begin
    y<=y+1'b1;
    dec_y<=1'b0;
    end
    end
    1:begin
    if(y==9'd0)begin
    y<=y;
    dec_y<=1'b0;
    end
    else if(one_s_flag)begin
    y<=y-1'b1;
    dec_y<=1'b1;
    end
    end
    default: ;
    endcase
    end

    always@(posedge clk or negedge rst_n)
    if(!rst_n)
    vga_rgb<=8'b0;
    else if(cnt_h>10'd144+x &cnt_h<=10'd343+x & cnt_v>10'd35+y & cnt_v<=10'd234+y)
    vga_rgb<=doutb;
    else if(cnt_h>10'd144 &cnt_h<=10'd783 & cnt_v>10'd35 & cnt_v<=10'd194)
    vga_rgb<=8'b111_000_00;
    else if(cnt_h>10'd144 &cnt_h<=10'd783 & cnt_v>10'd194 & cnt_v<=10'd354)
    vga_rgb<=8'b000_111_00;
    else if(cnt_h>10'd144 &cnt_h<=10'd783 & cnt_v>10'd354 & cnt_v<=10'd514)
    vga_rgb<=8'b000_000_11;
    else
    vga_rgb<=8'b0;


    always @(posedge clk or negedge rst_n) begin
    if (rst_n == 1'b0) begin
    // reset
    addrb <= 'd0;
    end
    else if(cnt_h >=143+x && cnt_h <=340+x && cnt_v >=35+y && cnt_v <=232+y && addrb=='d39203)begin
    addrb <= 'd0;
    end
    else if (cnt_h >=143+x && cnt_h <=340+x && cnt_v >=35+y && cnt_v <=232+y) begin
    addrb <= addrb + 1'b1;
    end
    end

    always @(posedge clks or negedge rst_n) begin
    if (rst_n == 1'b0) begin
    // reset
    addra <= 'd0;
    end
    else if (pi_flag == 1'b1 && addra == 'd39203) begin
    addra <= 'd0;
    end
    else if (pi_flag == 1'b1) begin
    addra <= addra + 1'b1;
    end
    end


    RAM40K ram_inst(
    .clka(clks),
    .wea(pi_flag),
    .addra(addra),
    .dina(pi_data),
    .clkb(clk),
    .addrb(addrb),
    .doutb(doutb)
    );
    endmodule

    问题五,怎么产生200X200的数据,即把一个200X200像素的图片转换为200X200的数据阵列?

    以前的经验是,直接弄一个txt文本储存200X200的数据,不过是并转串,而且是用在仿真中。

    尤老师的经验是,用MATLAB处理,即给一个200X200的像素图片,用MATLAB的相应语句转换产生一个200X200的数据阵列,再复制到友善串口助手发送,之后发现显示器还是黑色的,他推测可能是阈值过大。

     

    我觉得遇到这种完全新的,还是先记录问题,再看哈视频,然后自己动手做。

    在图像边沿检测的视频二50分钟,我看到了建完所有的模块。

    下面是MATLAB的图片转阵列的代码,第一行是读取图片(直接把图片 粘贴在当前文件夹下,再v3edu修改成图片相应的名字)第二行是转换的图片的阵列大小,这个是根据MATLAB相应的工作路径下显示可以转换的范围,而不是胡乱搞的。

    clc;

    clear all;

    rgbimage=imread('./v3edu.jpg','jpg');%读取rgb图像

    grayimage=rgbimage(1:200,1:200,1);%去其中r分量作为传递图像

    grayimage=bitshift(grayimage,-5);%右移5位取R分量的高三位

    fid=fopen('imagedata.txt','w+');%打开文件返回句柄

    fprintf(fid,'%02x ',grayimage');%将图像转置行列对换(默认matlab

    imshow(rgbimage);

     

     操作示意图如上

    解决办法:即出现多沿触发时,要同一个信号如CLK都是上升沿,或rst_n都为下降沿有效。

    问题五。怎么debug?

    首先在顶层,看看各个模块的连接有没有错。第二步,检查控制模块sobel_ctrl的各个模块和信号的逻辑有没有错。第三步,逻辑分析仪:在ise14.7中建立了ICON和ILA这两个IP核(弄懂这)

    问题六:分成两部分一是没有图像显示,原来时没有管脚约束文件。。

    第二个问题是,RAM的数据没有读出来,即移动的方框一直显示黑色,即RAM输出的po_data一直为零。(rgb信号全1为白,全零为黑)

    故解决办法,明天好好改哈RAM的读写逻辑。

    通过综合器的警告,我发现,在顶层模块中,每个功能模块没有和顶层的时钟信号连接,即只连接的自己模块的,这没有时钟驱动源。我用pll模块产生了两个时钟输出:一个50M和25M,然后对应连接各个模块就欧克勒。

    最终结论确实是RAM模块的读写地址时钟不一样,还有就是顶层模块,除了PLL的输入时钟连接系统时钟,其他模块的时钟信号都是连接的PLL的输出时钟,50M 或25M。

    最终显示效果如下,在看到图像那一刻很开心,比较自己亲自调了一周程序,还好没放弃:

    皮卡丘原图:

    经过sobel算法处理后的图片:

    知识点:一是怎么采集3X3的9个数,直接用2个FIFO,在每个FIFO用三个寄存器,在标志信号的控制下进行打拍 操作。二是怎么把一个图片转换为一个如200X200的矩阵数列,直接用MATLAB转换即可。

    我的收获是:一是做一个新东西,在原有基础上想办法。二是,要拆分设计验证,不能一把搞完所有模块,直接去上板验证。

  • 相关阅读:
    k8s云集群混搭模式落地分享
    开发人员需要掌握的日常Linux命令集
    k8s云集群混搭模式,可能帮你节省50%以上的服务成本
    PyQt5Day27--展示控件QLCDNumber(LCD屏幕显示)
    PyQt5Day26--展示控件QLabel
    PyQt5Day25--输入控件QClendarWidget(日期控件)
    python学习Day40--复习+初始协程
    python学习Day39--复习(前期+进程和线程相关)
    PyQt5Day24--输入控件QDialog(对话框控件)
    python学习Day38--进程与线程
  • 原文地址:https://www.cnblogs.com/Xwangzi66/p/12966206.html
Copyright © 2011-2022 走看看