基于FPGA的 图像边沿检测

目录

一  项目结构

1.1 设计思路

 1.2 设计流程

 二  接口设计

2.1 摄像头配置模块

2.2 IIC_master 模块

之后就进行数据采集

2.3 采集数据模块

2.4 灰度转化 

2.5 高斯滤波

2.7 二值化

 2.8 Sobel边缘检测

2.9 SDRAM乒乓缓存

 2.10 VGA显示

三 代码设计


一  项目结构

1.1 设计思路

基于 OV5640的 图像边沿检测,采集的图像大小是 1280 * 720 ,采用VGA接口进行显示

项目模块设计:

基于FPGA的 图像边沿检测_第1张图片

 1.2 设计流程

  •  本次实验做的是基于OV5640的摄像头数据采集实验,在上电等待20ms后,利用SCCB协议(这里我用的IIC协议)进行摄像头的配置,配置完254个寄存器后,会输出一个配置完成有效信号给摄像头采集模块。
  • 需要接收摄像头配置完成的信号,当场同步信号拉低后,且行参考信号有效时进行数据的采集。但是摄像头的数据是把16位RGB拆分为高八位和低八位发送的,我们需要通过移位+位拼接的方式把两个8bit数据合并成16bit数据输出,同时为了SDRAM模块更好的识别帧头和帧尾,在图像的第一个像素点以及最后一个像素点的时候分别拉高sopeop信号,其余像素点拉低。

  • 数据处理,包括图像的灰度转化、高斯滤波、二值化,Sobel边沿检测等。具体实现后续讲解。

  • 乒乓操作主要⽤于控制数据流,在此项⽬中主要体现为先写SDRAM bank1的数据,同时读SDRAM bank3的数据,当两块bank的数据读写完毕后,切换操作为读bank1的数据,写bank3的数据,这样可以保持数据为完整的⼀帧,使显⽰屏帧与帧之间切换瞬间完成。

  • 显示,利用VGA接口将数据显示到显示屏上。

 二  接口设计

2.1 摄像头配置模块

module cmos_config(
    input               clk         ,
    input               rst_n       ,
    //i2c_master
    output              req         ,//请求
    output      [3:0]   cmd         ,//命令
    output      [7:0]   dout        ,//数据
    input               done        ,//应答
    
    output              config_done     //配置完成信号
);

摄像头配置模块比较简单,之前设计过利用IIC协议来读写EEPROM,摄像头的控制模块还比EEPROM控制模块简单,只涉及到了向摄像头写入数据,即配置它的功能。

配置流程:

  • 主要采用状态机计数器的方式来设计配置模块;
  • 当上电之后计数20ms,之后就可以进行摄像头的配置,有一个配置完成信号,当配置完254个寄存器后,配置信号有效。
  • 配置模块主要就是通过IIC_master模块向摄像头里面写入数据,完成配置。
  • 发送数据是以任务的方式发请求、命令和数据。

2.2 IIC_master 模块

接口模块的作用就只是用来进行发数据或读数据,这里没有用到读数据。

module i2c_master(
    input               clk         ,
    input               rst_n       ,

    input               req         ,
    input       [3:0]   cmd         ,
    input       [7:0]   din         ,

    output      [7:0]   dout        ,
    output              done        ,
    output              slave_ack   ,

    output              i2c_scl     ,
    input               i2c_sda_i   ,
    output              i2c_sda_o   ,
    output              i2c_sda_oe     
    );

设计接口模块

基于FPGA的 图像边沿检测_第2张图片

  •  也是用状态机加计数器来设计接口模块,状态机的设计就是根据IIC协议的读写时序来设计的,具体时序之前的博客有写过。
  • 里面很重要的一点就是时钟设计,我用的传输速率是 200k bit/s,因此一个IIC时钟周期    SCL = 50 M / 200 K = 250 次系统时钟周期,需要一个计数器,来记250个系统时钟周期,通过IIC协议收发数据,都是根据SCL来进行的。

 SCL设计:

//scl
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            scl <= 1'b1;
        end
        else if(idle2start | idle2write | idle2read)begin//开始发送时,拉低
            scl <= 1'b0;
        end
        else if(add_cnt_scl && cnt_scl == `SCL_HALF-1)begin 
            scl <= 1'b1;
        end 
        else if(end_cnt_scl && ~stop2idle)begin 
            scl <= 1'b0;
        end 
    end

数据发送:

//sda_out
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            sda_out <= 1'b1;
        end
        else if(state_c == START)begin          //发起始位
            if(cnt_scl == `LOW_HLAF)begin       //时钟低电平时拉高sda总线
                sda_out <= 1'b1;
            end
            else if(cnt_scl == `HIGH_HALF)begin    //时钟高电平时拉低sda总线 
                sda_out <= 1'b0;                //保证从机能检测到起始位
            end 
        end 
        else if(state_c == WRITE && cnt_scl == `LOW_HLAF)begin  //scl低电平时发送数据   并串转换
            sda_out <= tx_data[7-cnt_bit];      
        end 
        else if(state_c == SACK && cnt_scl == `LOW_HLAF)begin  //发应答位
            sda_out <= (command&`CMD_STOP)?1'b1:1'b0;
        end 
        else if(state_c == STOP)begin //发停止位
            if(cnt_scl == `LOW_HLAF)begin       //时钟低电平时拉低sda总线
                sda_out <= 1'b0;
            end
            else if(cnt_scl == `HIGH_HALF)begin    //时钟高电平时拉高sda总线 
                sda_out <= 1'b1;                //保证从机能检测到停止位
            end 
        end 
    end

//sda_out_en  总线输出数据使能
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            sda_out_en <= 1'b0;
        end
        else if(idle2start | idle2write | read2sack | rack2stop)begin
            sda_out_en <= 1'b1;
        end
        else if(idle2read | start2read | write2rack | stop2idle)begin 
            sda_out_en <= 1'b0;
        end 
    end

具体实现发送就是入上图,这里不多阐述了。

配置的顶层模块接口:

module cmos_top(
    input           clk     ,        //xclk  24M
    input           rst_n   ,
    
    output          scl     ,        //scl
    inout           sda     ,        //sda
    output          pwdn    ,        
    output          reset   ,

    output          cfg_done        //配置完成信号

);

之后就进行数据采集

2.3 采集数据模块

接口设计:

module capture(
    input           clk     ,//像素时钟 摄像头输出的pclk
    input           rst_n   ,

    input           enable  ,  //采集使能 配置完成
    input           vsync   ,//摄像头场同步信号
    input           href    ,//摄像头行参考信号
    input   [7:0]   din     ,//摄像头像素字节

    output  [15:0]  dout    ,//像素数据
    output          dout_sop,//包文头 一帧图像第一个像素点
    output          dout_eop,//包文尾 一帧图像最后一个像素点
    output          dout_vld //像素数据有效
);

设计思路:

  • 先对场同步信号进行同步打拍,然后检测下降沿
//vsync同步打拍
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            vsync_r <= 2'b00;
        end
        else begin
            vsync_r <= {vsync_r[0],vsync};
        end
    end

    assign vsync_nedge = vsync_r[1] & ~vsync_r[0];  //检测下降沿

检测到下降沿,且接收到摄像头配置完成信号,采集数据标志拉高,采集完一帧图像,标志拉低。之后进行下一帧图像的采集。

  • 采集数据,采集数据标志拉高行参考信号有效时,进行数据采集,这里进行了拼接。摄像头的数据是把16位RGB拆分为高八位和低八位发送的,我们需要通过移位+位拼接的方式把两个8bit数据合并成16bit数据输出。
//data
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            data <= 0;
        end
        else begin
            data <= {data[7:0],din};//左移
            //data <= 16'b1101_1010_1111_0111;//16'hdaf7
        end
    end
  • SOP、EOP、数据有效信号
//data_sop
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            data_sop <= 1'b0;
            data_eop <= 1'b0;
            data_vld <= 1'b0;
        end
        else begin
            data_sop <= add_cnt_h && cnt_h == 2-1 && cnt_v == 0;
            data_eop <= end_cnt_v;
            data_vld <= add_cnt_h && cnt_h[0] == 1'b1;
        end
    end

sop就是一帧图像的第一个像素点,eop是一帧图像的最后一个像素点,数据有效是cnt_h[0] == 1'b1,由于进行了拼接,低位始终是1,因此数据有效。

2.4 灰度转化 

接口设计:

module rgb2gray(

    input           clk         ,
    input           rst_n       ,
    
    input           din_sop     ,
    input           din_eop     ,
    input           din_vld     ,
    input   [15:0]  din         ,//RGB565

    output          dout_sop    ,
    output          dout_eop    ,
    output          dout_vld    ,
    output  [7:0]   dout         //灰度输出
);

原理 :

  • 人眼对RGB颜色的敏感度不同:对绿色最敏感,所以权值最高。对蓝色不敏感,权值最低。在C语言或者Python等高级语言中,权值是0.2990.5870.114,也就是说都是小数,而Verilog不支持小数运算,所以只能先消除小数点来得到乘积,最后再通过移位缩小至近似原来的大小。

  • 由于我们摄像头采集的数据时RGB565格式的,需要转化为RGB888格式的图像,进行带补偿的低三位拓展位宽,然后采用加权法进行彩色图片转灰度,用了一个心理学公式。

  • 心理学公式:将三通道的彩色图像转化位单通道的八位输出

Gray = R*0.299 + G*0.587 + B*0.114
  • 我采用的是将权值扩大1024倍之后再进行加权求和,最后再右移10位.
Gray = (R*306 + G*601 + B*117) >> 10

设计思路:

  • 首先将输入的16位 的彩色图像,转化位24位的图像,即将RGB565 格式转化为 RGB888格式的图像,这里有两种方式的扩展位宽,第一种是采用带补偿的拓展位宽;第二种是采用不带补偿的拓展位宽。
//扩展    RGB565-->RGB888
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            data_r <= 0;
            data_g <= 0;
            data_b <= 0;
        end
        else if(din_vld)begin
            data_r <= {din[15:11],din[13:11]};      //带补偿的  r5,r4,r3,r2,r1, r3,r2,r1
            data_g <= {din[10:5],din[6:5]}   ;      //补偿低三位
            data_b <= {din[4:0],din[2:0]}    ;
            /*
            data_r <= {din[15:11],3'd0};
            data_g <= {din[10:5],2'd0} ;
            data_b <= {din[4:0],3'd0}  ;
            */
        end
    end

转化完之后就可以进行加权求和

  • 这里用到的是10位精度的扩大,人眼对绿色的敏感度最高,权重为 0.587,换成整数是10位的位宽。
//加权   
    //第一拍
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            pixel_r <= 0;
            pixel_g <= 0;
            pixel_b <= 0;
        end
        else if(vld[0])begin
            pixel_r <= data_r * 306;
            pixel_g <= data_g * 601;
            pixel_b <= data_b * 117;
        end
    end
    
    //第二拍
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            pixel <= 0;
        end
        else if(vld[1])begin
            pixel <= pixel_r + pixel_g + pixel_b;
        end
    end


    assign dout = pixel[10 +:8];    //从第十位开始取数据,取八位。

 到这里就已经灰度转化完毕,之后进行高斯滤波。

2.5 高斯滤波

接口设计:

module gauss_filter(
    input           clk         ,
    input           rst_n       ,
    
    input           din_sop     ,
    input           din_eop     ,
    input           din_vld     ,
    input   [7:0]   din         ,//灰度输入

    output          dout_sop    ,
    output          dout_eop    ,
    output          dout_vld    ,
    output  [7:0]   dout         //灰度输出     

);

设计思路:

  • 原理: 高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。

  • 基于FPGA的 图像边沿检测_第3张图片

    因为高斯滤波涉及到卷积核对二维图像的一个卷积,所以我们首先需要一个移位寄存器来将串行数据变为并行数据。这里调用一个IP核即可:  

    wire    [7:0]   taps0       ; 
    wire    [7:0]   taps1       ; 
    wire    [7:0]   taps2       ; 
//缓存3行数据
    gs_line_buf	gs_line_buf_inst (
	.aclr       (~rst_n     ),
	.clken      (din_vld    ),
	.clock      (clk        ),
    /*input*/
	.shiftin    (din        ),
	.shiftout   (           ),
    /*output*/
	.taps0x     (taps0      ),
	.taps1x     (taps1      ),
	.taps2x     (taps2      )
	);

卷积:

    reg     [7:0]   line0_0     ;
    reg     [7:0]   line0_1     ;
    reg     [7:0]   line0_2     ;

    reg     [7:0]   line1_0     ;
    reg     [7:0]   line1_1     ;
    reg     [7:0]   line1_2     ;

    reg     [7:0]   line2_0     ;
    reg     [7:0]   line2_1     ;
    reg     [7:0]   line2_2     ;

    reg     [9:0]   sum_0       ;//第0行加权和 
    reg     [9:0]   sum_1       ;//第1行加权和
    reg     [9:0]   sum_2       ;//第2行加权和
    
	reg     [7:0]  sum         ;//三行的加权和
  • 首先是参数,lineX_Y表示第X第Y行,我们需要创造一个3*3的矩阵寄存器,所以这里共需要9个寄存器,并且需要求的加权和,所以每一行再分配一个sum,最后再分配一个总sum;
/*
高斯滤波系数,加权平均
	1	2	1
	2	4	2
	1	2	1
*/
	always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            line0_0 <= 0;line0_1 <= 0;line0_2 <= 0;      
            line1_0 <= 0;line1_1 <= 0;line1_2 <= 0;         
            line2_0 <= 0;line2_1 <= 0;line2_2 <= 0;
        end
        else if(vld[0])begin
            line0_0 <= taps0;line0_1 <= line0_0;line0_2 <= line0_1;           
            line1_0 <= taps1;line1_1 <= line1_0;line1_2 <= line1_1;       
            line2_0 <= taps2;line2_1 <= line2_0;line2_2 <= line2_1;
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            sum_0 <= 0;
            sum_1 <= 0;
            sum_2 <= 0;
        end
        else if(vld[1])begin
            sum_0 <= {2'd0,line0_0} + {1'd0,line0_1,1'd0} + {2'd0,line0_2};
            sum_1 <= {1'd0,line1_0,1'd0} + {line1_1,2'd0} + {1'd0,line1_2,1'd0};
            sum_2 <= {2'd0,line2_0} + {1'd0,line2_1,1'd0} + {2'd0,line2_2};
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            sum <= 0;
        end
        else if(vld[2])begin
            sum <= (sum_0 + sum_1 + sum_2)>>4;
        end
    end
  1. 第一个Always完成的是9个八位的寄存器对图像数据的存储;
  2. 第二个Always通过位拼接来实现与卷积核的乘法(直接乘也没问题,只是这样写能显示出老练的技法)与每行的求和;
  3. 第三个Always就是求总和,这里也和灰度寄存器一样使用了移位运算符,这也是因为Verilog不能进行小数运算,实际运算的卷积核内的参数都是扩大了16倍(2^4)。

 克服了边界效应,相对于均值滤波平滑效果更柔和,边缘保留的更好。用一个值相邻像素的加权平均灰度值代替原来的值 。

2.7 二值化

  • 是指将图像上的每一个像素只有两种可能的取值或灰度等级状态,人们经常用黑白、 B&W、单色图像表示二值图像。二值图像是指在图像中,灰度等级只有两种,也就是说,图像中的任何像素不是0就是1,再无其他过渡的灰度值。
  • 因为此时图像已经是灰度单通道的(0,255),所以只需要设定一个阈值,当图像数据<它时,就设置为0(黑色),>大于它时就设置为1(白色),根据阈值的不同,最终得到的结果也不一样。

接口设计:

module gray2bin(
    input           clk         ,
    input           rst_n       ,
    
    input           din_sop     ,
    input           din_eop     ,
    input           din_vld     ,
    input   [7:0]   din         ,//灰度输入

    output          dout_sop    ,
    output          dout_eop    ,
    output          dout_vld    ,
    output          dout         //二值输出  

);

设计思路:

将经过高斯滤波后的灰度数据输入,进行和设定的 阈值 进行比较,阈值是我们自己设定的,如果大于这个阈值就是 1,即为白色,小于这个阈值为 0 ,即为黑色,

always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            binary     <= 0 ;
            binary_sop <= 0 ;
            binary_eop <= 0 ;
            binary_vld <= 0 ;
        end
        else begin
            binary     <= din>100 ;//二值化阈值可自定义
            binary_sop <= din_sop ;
            binary_eop <= din_eop ;
            binary_vld <= din_vld ;
        end
    end

之后就可以进行Sobel边缘像素点检测。

 2.8 Sobel边缘检测

接口设计:

module sobel(
    input           clk     ,
    input           rst_n   ,
    input           din     ,//输入二值图像
    input           din_sop ,
    input           din_eop ,
    input           din_vld ,

    output          dout    ,
    output          dout_sop,
    output          dout_eop,
    output          dout_vld 
);

设计思路:

 

  • 边缘检测是是特征提取中的一个研究领域,它能边缘检测出数字图像中亮度变化明显的点,减少数据量,并剔除不相 关的信息,最终保留图像重要的结构属性。同时,Sobel 边缘检测通常带有方向性,可以只检测竖直边缘或垂直边缘 或都检测。

  • Sobel算子提供了水平方向核垂直方向的滤波模板。

 

  • Gx 和 Gy 如下基于FPGA的 图像边沿检测_第4张图片

 梯度计算:基于FPGA的 图像边沿检测_第5张图片

 基于FPGA的 图像边沿检测_第6张图片

 

assign dout = g >= 3;//阈值假设为3 当某一个像素点的梯度值大于3,认为其是一个边缘点

 之后要将输出数据扩展成16位的数据,再存到SDRAM里面。

2.9 SDRAM乒乓缓存

这里SDRAM接口模块我是直接调用的IP核,写了一个控制模块,用来向SDRAM里卖弄突发写或者突发读,由于涉及到跨时钟域的数据传输,从摄像头采集到的数据写到SDRAM里面是慢时钟域到快时钟域,从SDRAM里面读取数据显示到屏幕上是快时钟域到慢时钟域。因此设计了两个异步FIFO用来缓存数据。

  • 乒乓操作主要⽤于控制数据流,在此项⽬中主要体现为先写SDRAM bank1的数据,同时读SDRAM bank3的数据,当两块bank的数据读写完毕后,切换操作为读bank1的数据,写bank3的数据,这样可以保持数据为完整的⼀帧,使显⽰屏帧与帧之间切换瞬间完成。

保证读取到的数据是一帧的效果,如果一帧数据没有读完,就开始写入会丢帧。

设计思路:

  1. wfifo设计,慢时钟域写到快时钟域读
    wrfifo	wrfifo_inst (
    	.aclr   (~rst_n     ),
    	.data   (wfifo_data ),
    	.rdclk  (clk        ),
    	.rdreq  (wfifo_rdreq),
    	.wrclk  (clk_in     ),
    	.wrreq  (wfifo_wrreq),
    	.q      (wfifo_q    ),
    	.rdempty(wfifo_empty),
    	.rdusedw(wfifo_usedw),
    	.wrfull (wfifo_full )
    	);
    
        assign wfifo_data = {din_eop,din_sop,din};
        assign wfifo_wrreq = ~wfifo_full & din_vld & ((~wr_finish_r[1] & din_sop) ||wr_data_flag);
        assign wfifo_rdreq = state_c == WRITE && ~avs_waitrequest;
    
    数据进行拼接,将eop,sop和 数据拼接到一起,用于判断能不能写入
  2. 读写仲裁优先级
    /************************读写优先级仲裁*****************************/
    //rd_flag     ;//读请求标志
        always @(posedge clk or negedge rst_n)begin 
            if(!rst_n)begin
                rd_flag <= 0;
            end 
            else if(rfifo_usedw <= `RD_LT)begin   
                rd_flag <= 1'b1;
            end 
            else if(rfifo_usedw > `RD_UT)begin 
                rd_flag <= 1'b0;
            end 
        end
    
    //wr_flag     ;//写请求标志
        always @(posedge clk or negedge rst_n)begin 
            if(!rst_n)begin
                wr_flag <= 0;
            end 
            else if(wfifo_usedw >= `USER_BL)begin 
                wr_flag <= 1'b1;
            end 
            else begin 
                wr_flag <= 1'b0;
            end 
        end
    
    //flag_sel    ;//标记上一次操作
        always @(posedge clk or negedge rst_n)begin 
            if(!rst_n)begin
                flag_sel <= 0;
            end 
            else if(read2done)begin 
                flag_sel <= 1;
            end 
            else if(write2done)begin 
                flag_sel <= 0;
            end 
        end
    
    //prior_flag  ;//优先级标志 0:写优先级高   1:读优先级高     仲裁读、写的优先级
        always @(posedge clk or negedge rst_n)begin 
            if(!rst_n)begin
                prior_flag <= 0;
            end 
            else if(wr_flag && (flag_sel || (~flag_sel && ~rd_flag)))begin   //突发写优先级高
                prior_flag <= 1'b0;
            end 
            else if(rd_flag && (~flag_sel || (flag_sel && ~wr_flag)))begin   //突发读优先级高
                prior_flag <= 1'b1;
            end 
        end
    
    /******************************************************************/    
    

    读写不能同时进行,只有dq一组数据线

  3. 地址设计
    // wr_addr   rd_addr
        always @(posedge clk or negedge rst_n) begin 
            if (rst_n==0) begin
                wr_addr <= 0; 
            end
            else if(add_wr_addr) begin
                if(end_wr_addr)
                    wr_addr <= 0; 
                else
                    wr_addr <= wr_addr+1 ;
           end
        end
        assign add_wr_addr = (state_c == WRITE) && ~avs_waitrequest;
        assign end_wr_addr = add_wr_addr  && wr_addr == `BURST_MAX-1 ;
        
        always @(posedge clk or negedge rst_n) begin 
            if (rst_n==0) begin
                rd_addr <= 0; 
            end
            else if(add_rd_addr) begin
                if(end_rd_addr)
                    rd_addr <= 0; 
                else
                    rd_addr <= rd_addr+1 ;
           end
        end
        assign add_rd_addr = (state_c == READ) && ~avs_waitrequest;
        assign end_rd_addr = add_rd_addr  && rd_addr == `BURST_MAX-1;

    地址设计是根据读写状态来改变的,突发写或突发读都是一次突发多少个数据。

  4. 乒乓操作,写第一块bank,读第三块bank,这样子能保证读出来的是连续的一帧图像

    //change bank
        always  @(posedge clk or negedge rst_n)begin
            if(~rst_n)begin
                wr_bank <= 2'b00;
                rd_bank <= 2'b11;
            end
            else if(change_bank)begin
                wr_bank <= ~wr_bank;
                rd_bank <= ~rd_bank;
            end
        end
    
    //wr_finish     一帧数据全部写到SDRAM
        always  @(posedge clk or negedge rst_n)begin
            if(~rst_n)begin
                wr_finish <= 1'b0;
            end
            else if(~wr_finish & wfifo_q[17])begin  //写完  从wrfifo读出eop
                wr_finish <= 1'b1;
            end
            else if(wr_finish && end_rd_addr)begin  //读完
                wr_finish <= 1'b0;
            end
        end
    
    //change_bank ;//切换bank 
        always  @(posedge clk or negedge rst_n)begin
            if(~rst_n)begin
                change_bank <= 1'b0;
            end
            else begin
                change_bank <= wr_finish && end_rd_addr;
            end
        end
    
    /*********************** wrfifo 写数据   ************************/
    //控制像素数据帧 写入 或 丢帧
    
        always  @(posedge clk_in or negedge rst_n)begin
            if(~rst_n)begin
                wr_data_flag <= 1'b0;
            end 
            else if(~wr_data_flag & ~wr_finish_r[1] & din_sop)begin//可以向wrfifo写数据
                wr_data_flag <= 1'b1;
            end
            else if(/*wr_finish_r[1] && din_sop*/wr_data_flag & din_eop)begin//不可以向wrfifo写入数据
                wr_data_flag <= 1'b0;
            end
        end
    
        always  @(posedge clk_in or negedge rst_n)begin //把wr_finish从wrfifo的读侧同步到写侧
            if(~rst_n)begin
                wr_finish_r <= 0;
            end
            else begin
                wr_finish_r <= {wr_finish_r[0],wr_finish};
            end
        end
    
    /****************************************************************/

  5. rdfifo设计 ,快时钟写到慢时钟读
    rdfifo u_rdfifo(
    	.aclr       (~rst_n     ),
    	.data       (rfifo_data ),
    	.rdclk      (clk_out    ),
    	.rdreq      (rfifo_rdreq),
    	.wrclk      (clk        ),
    	.wrreq      (rfifo_wrreq),
    	.q          (rfifo_q    ), 
    	.rdempty    (rfifo_empty),
    	.wrfull     (rfifo_full ),
    	.wrusedw    (rfifo_usedw)
    );
    
        assign rfifo_data = avs_rddata;
        assign rfifo_wrreq = ~rfifo_full & avs_rddata_vld;
        assign rfifo_rdreq = ~rfifo_empty & rdreq;      //rfifo非空且vga显示是行有效和场有效

输出数据:

 assign dout       = rd_data;

    assign dout_vld   = rd_data_vld;

    assign avm_wrdata = wfifo_q[15:0];

    assign avm_write  = ~(state_c == WRITE && ~avs_waitrequest);

    assign avm_read   = ~(state_c == READ && ~avs_waitrequest);

    assign avm_addr   = (state_c == WRITE)?{wr_bank[1],wr_addr[21:9],wr_bank[0],wr_addr[8:0]}

                       :((state_c == READ)?{rd_bank[1],rd_addr[21:9],rd_bank[0],rd_addr[8:0]}

                       :0);

 2.10 VGA显示

接口设计:

module   vga_interface (    //1280*720
    input           clk      ,//75MHz
    input           rst_n    ,
    input   [15:0]  din      ,
    input           din_vld  ,
    output          rdy      ,
    output  [15:0]  vga_rgb  ,
    output          vga_hsync,
    output          vga_vsync
);

设计思路:

  1. 计数器来设计行有效和场有效区域
  2. 在行场有效显示区域进行输出数据
    //数据输出
        always @(posedge clk or negedge rst_n) begin
            if (!rst_n) begin
                rgb <= 0;
            end
            else if (display_vld) begin
                rgb <= vga_data ;
            end
            else begin
                rgb <= rgb;
            end
        end
        assign display_vld = (cnt_h_addr > H_SYNC + H_BLACK - 1) && (cnt_h_addr < H_TOTAL - H_FRONT -1)
        && (add_v_addr > V_SYNC + V_BLACK - 1) && (add_v_addr < V_TOTAL - V_FRONT - 1);
    
        assign  rgb_r = rgb[15:11];
        assign  rgb_g = rgb[10:05];
        assign  rgb_b = rgb[04:00];
    

    用一个fifo来缓存数据

    //FIFO例化
        vga_buf u_buf(
    	.aclr       (~rst_n     ),
    	.clock      (clk        ),
    	.data       (din        ),
    	.rdreq      (rdreq      ),
    	.wrreq      (wrreq      ),
    	.empty      (empty      ),
    	.full       (full       ),
    	.q          (q_out      ),
    	.usedw      (usedw      )
    );
    
        assign wrreq = ~full && din_vld;
        assign rdreq = ~empty && display_vld;

三 代码设计

链接:https://pan.baidu.com/s/1scHW8ilQwdOKbUqN1aCuPg?pwd=gvj0
提取码:gvj0
--来自百度网盘超级会员V1的分享

你可能感兴趣的:(基于FPGA的 图像边沿检测)