糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > FPGA综合项目——图像边缘检测系统

FPGA综合项目——图像边缘检测系统

时间:2020-09-23 11:36:58

相关推荐

FPGA综合项目——图像边缘检测系统

目录

0.此篇总结1.系统功能2.模块划分3.PLL4.SCCB模块5.摄像头配置模块6.采集模块7.灰度模块8.高斯滤波模块9.二值模块10.边缘检测模块11.存储模块12.VGA模块13.顶层模块14.管脚配置及上板实验15. 后记:资源使用情况

0.此篇总结

参考:《手把手教你学FPGA设计:基于大道至简的至简设计法》-----潘文明,易文兵编著

将会学到的东西:

①PLL分频的使用,也就是PLL IP核

②sccb通信,包括原理、写时序以及读时序,类似IIC通信

③ov7670摄像头的配置,内部164个寄存器的配置,通过一个包含关系的参数文件

④彩图转灰度图的一个常用公式,FPGA中怎么处理小数的乘法除法

⑤高斯滤波的原理,和插值像素原理相似

⑥移位寄存器IP核的使用

⑦二值化,用一个阈值区分01

⑧索贝尔两算子的边缘检测算法

⑨存储的乒乓操作、ram IP核的使用

⑩VGA时序

1.系统功能

通过摄像头采集事实图像给FPGA,经过FPGA处理后将最终结果显示在VGA设备上。效果图如下:

2.模块划分

最后的模块RTL图:

3.PLL

时钟是最基本的信号,我们要考虑到各个模块或者说外设正常工作所需要的时钟,我们的三个外设中,摄像头和VGA的时钟为25MHz,而博主用的FPGA时钟为50MHz,所以要用PLL分出25M的时钟。

module pll_ip(inclk0, //FPGA的50M时钟输入c0,//25M分频,用于摄像头和VGA);

4.SCCB模块

SCCB模块用于FPGA和摄像头的通讯,FPGA要先给摄像头做好配置,就要通过SCCB通讯来实现。该模块的作用就是:

当配置模块给出写命令时,产生写时序;当配置模块给出读命令时,产生读时序;模块中读到的数据rdata并没有用到;

先来看看SCCB的时序:

在sio_c为1,sio_d拉低时,数据传输开始

在sio_c为1,sio_d拉高时,数据传输结束

数据转换要在sio_c为0时完成

再来看看写时序:

起始位0,表示开始,然后写入‘从机地址(8位)’、‘x’(等待应答)、‘寄存器地址’、‘x’(等待应答)、写入的数据、‘x’等待应答、最后拉高表示传输结束,x全部取1就行,当写入为8位时,共30位数据。

再来看看读时序:

和写时序不一样的,读时序分为两个阶段,起始位、从机地址、x、寄存器地址、x、终止位。这是第一阶段; 起始位、从机地址+1(表示读)、x、读数据、x、终止位。这是第二阶段,若读数据8位,则每个阶段都是21位数据。

代码如下:

module sccb(clk , //输入,25M时钟rst_n, //输入,复位ren , //输入,读使能,由配置模块给出wen , //输入,写使能,由配置模块给出sub_addr , //输入,寄存器地址,由配置模块给出rdata, //输出,读到的数据rdata_vld , //输出,读到数据有效wdata, //输入,要写入的数据rdy , //输出,准备信号sio_c, //输出,SCCB的传输时钟sio_d_r , //输入,读到的数据en_sio_d_w, //输出,写数据传输使能sio_d_w//输出,写数据输出);//参数定义parameterSIO_C = 120 ; //配置4.8us的sccb时钟 sio_c//输入信号定义inputclk;//25minputrst_n ;inputren;inputwen;input [7:0] sub_addr ;input [7:0] wdata ;//输出信号定义output[7:0] rdata ;output rdata_vld;output sio_c ;//208kHz output rdy;inputsio_d_r ;output en_sio_d_w;output sio_d_w ;reg en_sio_d_w;reg sio_d_w ;//输出信号reg定义reg [7:0] rdata ;reg rdata_vld;reg sio_c ;reg rdy;//中间信号定义reg [7:0]count_sck;reg [4:0]count_bit;reg [1:0]count_duan ;reg flag_r ;reg flag_w ;reg [4:0]bit_num ;reg [1:0]duan_num;reg [29:0] out_data;wireadd_count_sck ;wireend_count_sck ;wireadd_count_bit ;wireend_count_bit ;wireadd_count_duan;wireend_count_duan;wiresio_c_h2l;wiresio_c_l2h;wireen_sio_d_w_h2l;wireen_sio_d_w_l2h;wireout_data_time ;wirerdata_time ;wire [7:0]rd_com ;//sccb 时钟周期 4.8us //在读或者写状态下加一always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begincount_sck <= 0;endelse if(add_count_sck)beginif(end_count_sck)begincount_sck <= 0;endelse begincount_sck <= count_sck + 1;endendendassign add_count_sck = flag_r || flag_w;assign end_count_sck = add_count_sck && count_sck == SIO_C-1;//区分读写输出的个数//读21个,写30个always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begincount_bit <= 0;endelse if(add_count_bit)beginif(end_count_bit)begincount_bit <= 0;endelse begincount_bit <= count_bit + 1;endendendassign add_count_bit = end_count_sck; assign end_count_bit = add_count_bit && count_bit == bit_num+2-1; //区分读写的阶段数//读两个阶段,写一个阶段always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begincount_duan <= 0;endelse if(add_count_duan)beginif(end_count_duan)begincount_duan <= 0;endelse begincount_duan <= count_duan + 1;endendendassign add_count_duan = end_count_bit;assign end_count_duan = add_count_duan && count_duan == duan_num-1;//读工作标志//当收到读使能时,flag_r置1always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag_r <= 0;endelse if(ren)beginflag_r <= 1;endelse if(end_count_duan)beginflag_r <= 0;endend//写工作标志//当收到写使能时,flag_w置1always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag_w <= 0;endelse if(wen)beginflag_w <= 1;endelse if(end_count_duan)begin flag_w <= 0;endend//读工作时(收到读使能时),sio_d输出 21 bit数据//SCCB时序中,读分为两个阶段,因此bit_num = 21、duan_num = 2;//写工作时(收到写使能时),sio_d输出 30 bit数据//SCCB时序中,写只有一个阶段,因此bit_num = 30、duan_num = 1;always @(*)beginif(flag_r)beginbit_num = 21;duan_num = 2;endelse if(flag_w)beginbit_num = 30;duan_num = 1;endelse beginbit_num = 1;duan_num = 1;endend//SCCB时钟的高低变化always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginsio_c <= 1;endelse if(sio_c_h2l)beginsio_c <= 0;endelse if(sio_c_l2h)beginsio_c <= 1;endend//一定要结合波形分析,开始和结束占用了两个“0”数据,因此在SCCB时钟高低变化时应排除掉assign sio_c_h2l = count_bit >= 0 && count_bit < (bit_num-2) && add_count_sck && count_sck == SIO_C-1;assign sio_c_l2h = count_bit >= 1 && count_bit < bit_num && add_count_sck && count_sck == SIO_C/2-1;//写和读数据always @ (*)beginif(flag_r)beginout_data <= {1'h0,rd_com,1'h1,sub_addr,1'h1,1'h0,1'h1,9'h0};//最后9位都为0 也就是有效21位endelse if(flag_w)beginout_data <= {1'h0,8'h42,1'h1,sub_addr,1'h1,wdata,1'h1,1'h0,1'h1};//有效30位endelse beginout_data <= 0;endend//区分写地址和读地址assign rd_com = (flag_r && count_duan == 0)? 8'h42 : 8'h43;//写数据使能,发往摄像头//在写数据的时候为1//除了读数据阶段以及复位,其他时间写数据使能都为1//读数据前也要进行写操作,要写进去读的地址等信息always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginen_sio_d_w <= 0;endelse if(ren || wen)beginen_sio_d_w <= 1;endelse if(end_count_duan)beginen_sio_d_w <= 0;endelse if(en_sio_d_w_h2l)beginen_sio_d_w <= 0;endelse if(en_sio_d_w_l2h)beginen_sio_d_w <= 1;endend//设置读数据时,给摄像头写数据的使能为0assign en_sio_d_w_h2l = flag_r && count_duan == 1 && count_bit == 10 && add_count_sck && count_sck == 1-1;assign en_sio_d_w_l2h = flag_r && count_duan == 1 && count_bit == 18 && add_count_sck && count_sck == 1-1;//写入数据,在sccb时钟周期的1/4处开始写always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginsio_d_w <= 1;endelse if(out_data_time)beginsio_d_w <= out_data[30-count_bit-1]; //一位一位赋值,共30位endendassign out_data_time = count_bit >= 0 && count_bit < bit_num && add_count_sck && count_sck == SIO_C/4-1;//读出数据,在sccb时钟周期的3/4处开始读always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrdata <= 0;endelse if(rdata_time)beginrdata[17-count_bit] <= sio_d_r; //一位一位赋值,共8位endendassign rdata_time = flag_r && count_duan==1 && count_bit>=10 && count_bit<18 && add_count_sck && count_sck==SIO_C/4*3-1;//读出数据有效//在读操作下完成最后的阶段置1always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrdata_vld <= 0;endelse if(flag_r && end_count_duan)beginrdata_vld <= 1;endelse beginrdata_vld <= 0;endend//准备信号//不读不写,置1always @(*)beginif(ren || wen || flag_r || flag_w)beginrdy = 0;endelse beginrdy = 1;endendendmodule

5.摄像头配置模块

摄像头用到ov7670,共有164个寄存器需要配置。配置信号为操作码+地址+配置值

本模块的作用:管理ov7670的寄存器配置信号,产生相应的读写命令

配置模块代码如下:

module ov7670_config(clk , //输入,25M时钟rst_n, //输入,复位config_en , //输入,配置使能rdy , //输入,准备信号rdata, //输入,读到的数据rdata_vld , //输入,读到数据有效信号wdata, //输出,写入摄像头寄存器的数据addr , //输出,寄存器地址wr_en , //输出,写使能rd_en, //输出,读使能cmos_en , //输出,配置完成后的采集使能 ,给到采集模块pwdn);parameterDATA_W = 8; //位宽参数parameterRW_NUM = 2; //先写后读,计数为0则写,为1则读 //输入信号定义inputclk; //50Mhzinputrst_n ;inputconfig_en;inputrdy;input [DATA_W-1:0] rdata ;inputrdata_vld;//输出信号定义output[DATA_W-1:0] wdata ;output[DATA_W-1:0] addr;output cmos_en ;output wr_en ;output rd_en ;output pwdn;//输出信号reg定义reg [DATA_W-1:0] wdata ;reg [DATA_W-1:0] addr;reg cmos_en ;reg wr_en ;reg rd_en ;//中间信号定义reg [8 :0] reg_cnt ;wireadd_reg_cnt;wireend_reg_cnt;reg flag;reg [17:0] add_wdata; //配置表信号 “操作码2比特”+“地址8比特”+“配置值8比特”reg [ 1:0] rw_cnt;wireadd_rw_cnt ;assign pwdn = 0;`include "ov7670_para.v" //寄存器地址及数据//配置寄存器个数计数,当配置完164个寄存器后给出采集使能//结束一个寄存器的设置时,+1,继续下一个寄存器的设置always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginreg_cnt <= 0;endelse if(add_reg_cnt)beginif(end_reg_cnt)reg_cnt <= 0;elsereg_cnt <= reg_cnt + 1;endendassign add_reg_cnt = end_rw_cnt; assign end_reg_cnt = add_reg_cnt && reg_cnt==REG_NUM-1;//写读计数器 0写1读 单独的配置一个寄存器的操作方式always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrw_cnt <= 0;endelse if(add_rw_cnt) beginif(end_rw_cnt)rw_cnt <= 0;elserw_cnt <= rw_cnt + 1;endendassign add_rw_cnt = flag && rdy;assign end_rw_cnt = add_rw_cnt && rw_cnt==RW_NUM-1;//配置使能过来到配置结束,flag为1always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag <= 1'b0;endelse if(config_en)beginflag <= 1'b1;endelse if(end_reg_cnt)beginflag <= 1'b0;endend//cmos_en ,采集使能,配置完为1always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begincmos_en <= 1'b0;endelse if(end_reg_cnt)begincmos_en <= 1'b1;endend//配置值为add_wdata的低八位always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginwdata <= 8'b0;endelse beginwdata <= add_wdata[7:0];endend//地址为add_wdata的高八位always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginaddr <= 8'b0;endelse beginaddr <= add_wdata[15:8];endend//写使能always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginwr_en <= 1'b0;endelse if(add_rw_cnt && rw_cnt==0 && add_wdata[16])beginwr_en <= 1'b1;endelse beginwr_en <= 1'b0;endend//读使能always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrd_en <= 1'b0;endelse if(add_rw_cnt && rw_cnt==1 && add_wdata[17])beginrd_en <= 1'b1;endelse beginrd_en <= 1'b0;endendendmodule

此模块有一个输入config_en需要完善,因此根据配置的速度来写一个产生配置使能的小模块key_en,这里用key_vld的第二位来作配置使能信号。

代码如下:

module key_en(clk ,rst_n ,key_in ,key_vld );parameter DATA_W = 20;parameter KEY_W = 4 ;parameterTIME_20MS = 500_000 ;inputclk ;input rst_n ;input[KEY_W-1 :0]key_in ;output[KEY_W-1 :0]key_vld ;reg [KEY_W-1 :0]key_vld ;reg [DATA_W-1:0]cnt ;wire add_cnt ;wire end_cnt ;reg flag;reg[KEY_W-1 :0] key_in_ff1 ;reg[KEY_W-1 :0] key_in_ff0 ;always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begincnt <= 20'b0;endelse if(add_cnt)beginif(end_cnt)cnt <= 20'b0;elsecnt <= cnt + 1'b1;endelse begincnt <= 0;endendassign add_cnt = flag==1'b0 && (key_in_ff1!=0);assign end_cnt = add_cnt && cnt == TIME_20MS - 1;always @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag <= 1'b0;endelse if(end_cnt)beginflag <= 1'b1;endelse if(key_in_ff1==0)beginflag <= 1'b0;endendalways @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginkey_in_ff0 <= 0;key_in_ff1 <= 0;endelse beginkey_in_ff0 <= key_in ;key_in_ff1 <= key_in_ff0;endendalways @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginkey_vld <= 0;endelse if(end_cnt)beginkey_vld <= key_in_ff1;endelse beginkey_vld <= 0;endendendmodule

6.采集模块

采集要求:分辨率640*480、RGB565格式图像;

注意:①因为采集的时RGB565的数据,所以一个像素点共16位数据,但是摄像头采集一次的数据为8位,所以需要采集两次才为一个像素点。②当行信号为高时,数据才有效。

代码如下:

module cmos_capture(clk , //输入,25M时钟rst_n , //输入,复位en_capture , //输入,采集使能,由配置模块给出vsync , //输入,帧信号,由FPGA给出href , //输入,行信号,由FPGA给出din , //输入,摄像头的8位数据输入dout , //输出,采集到的16位图像数据dout_vld , //输出,数据输出有效信号dout_sop , //输出,一帧的起始信号,即一帧开始传输dout_eop //输出,一帧的结束信号,即一帧传输结束);parameterCOL = 640; //一行的像素点个数parameterROW = 480; //行inputclk; inputrst_n ;inputen_capture ; inputvsync ; inputhref ; input [7:0] din; output [15:0] dout ; output dout_vld; output dout_sop; output dout_eop; reg [15:0] dout ;reg dout_vld;reg dout_sop;reg dout_eop;reg [10:0] count_x; reg [9:0] count_y;reg flag_capture ; reg vsync_ff0 ; wire add_count_x ;wire end_count_x ;wire add_count_y ;wire end_count_y ;wire vsync_l2h ; wire din_vld; wire flag_dout_vld;//一行的像素计数,共640个像素点//两个时钟一个像素点always @ (posedge clk or negedge rst_n)beginif(!rst_n)begincount_x <= 0;endelse if(add_count_x)beginif(end_count_x)begincount_x <= 0;endelse begincount_x <= count_x + 1;endendend//当数据输入有效且采集的时候+1assign add_count_x = flag_capture && din_vld;assign end_count_x = add_count_x && count_x == COL*2-1;//当行信号href为1时,数据有效assign din_vld = flag_capture && href; //行计数器,共480行always @ (posedge clk or negedge rst_n)beginif(!rst_n)begincount_y <= 0;endelse if(add_count_y)beginif(end_count_y)begincount_y <= 0;endelse begincount_y <= count_y + 1;endendend//完成一行像素采集要转到下一行时+1assign add_count_y = end_count_x;assign end_count_y = add_count_y && count_y == ROW-1; //开始采集指示信号,在vsync上升沿时置1always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginflag_capture <= 0;endelse if(flag_capture == 0 && vsync_l2h && en_capture)beginflag_capture <= 1;endelse if(end_count_y)beginflag_capture <= 0;endend//监沿器:鉴定vsync的上升沿,而这个上升沿表示新的一帧图像要开始传输了assign vsync_l2h = vsync_ff0 == 0 && vsync == 1; //将vsync过一个D触发器打拍//vsync_ff0 为前一时刻//vsync为当前时刻always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginvsync_ff0 <= 0;endelse beginvsync_ff0 <= vsync;endend//采集数据输出,将采到的din给dout的低8位,下一次always @ (posedge clk or negedge rst_n)begin if(!rst_n)begindout <= 0;endelse if(din_vld)begindout <= {dout[7:0],din};endend//当前像素点输出有效指示信号always @ (posedge clk or negedge rst_n)begin if(!rst_n)begindout_vld <= 0;endelse if(flag_dout_vld)begindout_vld <= 1;endelse begindout_vld <= 0;endend//第二个时钟来的时候为第一个像素点assign flag_dout_vld = add_count_x && count_x[0] == 1;//一帧数据开始always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_sop <= 0;endelse if(flag_dout_vld && count_x[10:1] == 0 && count_y == 0)begindout_sop <= 1;endelse begindout_sop <= 0;endend//一帧数据读完always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_eop <= 0;endelse if(flag_dout_vld && count_x[10:1] == COL-1 && count_y == ROW-1)begindout_eop <= 1;endelse begindout_eop <= 0;endendendmodule

7.灰度模块

图像处理的第一步,灰度处理:即色彩图转为灰度图,我们用到一个比较出名的色彩公式:

F= RED * 0.299 + GREEN * 0.587 + BLUE * 0.114

那么问题又来了,怎么做小数的乘法呢?事实上,在实际工程中我们都会避免效率低下的浮点运算,所以第一步就是让它变成整数运算。①因为公式里的浮点运算是三位精度,因此我们可以先乘以1000以后再除以1000。换句话说就是用299、587、114除以1000。②对于除法,在FPGA中采用除法器是相当耗费资源的事,所以我们采用不消耗资源的移位法进行除法计算,但是移位法的缺点在于只能除以2的倍数的值,因此通过右移10位来除以1024。③当然考虑到精度问题,我们也可以用乘以2^8再右移8位,即0.299 * 256 右移八位,依此类推。

最终的公式为:F = (RED * 76 + GREEN * 150 + BLUE * 30)>>8

具体代码如下:

module rgb565_gray(clk , //输入,25Mrst_n , //输入,复位din , //输入,采集到的图像数据,一次一个像素点din_vld, //输入,输入有效信号din_sop, //输入,一帧图像传输的开始din_eop, //输入,一帧图像传输的结束dout , //输出,灰度转化后的图像数据,一次一个像素点dout_vld , //输出,输出有效信号dout_sop , //输出,一帧图像传输的开始dout_eop //输出,一帧图像传输的结束);//输入输出定义input clk;input rst_n ;input [15:0] din; //采集的数据16位input din_vld ;input din_sop ;input din_eop ;output [7:0] dout;output dout_vld ;output dout_sop ;output dout_eop ;//信号类型定义reg [7:0] dout;reg dout_vld ;reg dout_sop ;reg dout_eop ;wire [7:0] red;wire [7:0] green ;wire [7:0] blue ;//补齐8位数据,用原数据的低位补齐assign red = {din[15:11],din[13:11]};assign green = {din[10:5],din[6:5]};assign blue = {din[4:0],din[2:0]}; //当输入有效时,实现灰度公式always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout <= 0;endelse if(din_vld)begindout <= (red*76 + green*150 + blue*30) >> 8;endend//输出有效跟随输入有效,即有一个输入有效就有一个输出有效always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_vld <= 0;endelse begindout_vld <= din_vld;endend//输出开始跟随输入开始,即一帧的第一个像素输入就开始输出一帧第一个像素always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_sop <= 0;endelse begindout_sop <= din_sop;endend输出结束跟随输入结束,即一帧的最后一个像素输入就开始输出一帧最后一个像素always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_eop <= 0;endelse begindout_eop <= din_eop;endendendmodule

8.高斯滤波模块

高斯滤波采用3 * 3 的掩膜,即三行三列的像素点,我们要依次将一帧图像的所有3 * 3 掩膜的像素点进行滤波。下图的掩模具体的计算公式如下:

其中f(x,y)为灰度像素值,g(x,y)为高斯滤波后的像素值。由公式可知,这与我们在优化显示AMG8833模块中的原理类似(具体可以看此链接AMG8833优化显示),都是以附近像素为原始数据的带有权重的数据处理方法。

我们知道方法之后,怎么在640 * 480 的一帧像素中构建3 * 3的掩膜呢?这里我们需要用到移位寄存器的IP核。

如上图所示为移位寄存器的端口设置,taps是可以设置的在指定位置输出的抽头,将抽头数设置为3,即可输出3行;将抽头间的距离设置为640,则意味着第一个抽头的输出是在寄存器链的第640位,第二个在640 * 2 后输出;而shiftout是寄存器末尾的输出,与最后一个抽头的输出一致,这里我们用不到。

现在我们只是可以做到输出3行,那么怎么输出三列呢?可以用D触发器进行打拍寄存。信号经过D触发器后后慢上一拍(参考D触发器打拍)所以打两拍之后就得到了前两排、前一拍、现在的、共3个数据,所以完成了3列的数据寄存。由此3 * 3的掩模便做出来了。

然后就是高斯算法:

①计算每一行的代数和②每一列的代数和相加除以16,并输出

模块如图所示:

代码如下:

module gs_filter(clk , //输入,25M时钟rst_n , //输入,复位din , //输入,经过灰度处理的8位数据din_vld, //输入,输入数据有效din_sop, //输入,一帧图像传输的开始din_eop, //输入,一帧图像传输的结束dout , //输出,数据输出dout_vld , //输出,数据有效dout_sop , //输出,一帧图像传输的开始dout_eop //输出,一帧图像传输的结束);//输入输出定义input clk;input rst_n ;input [7:0] din;input din_vld ;input din_sop ;input din_eop ;output [7:0] dout ;output dout_vld;output dout_sop;output dout_eop;//型号类型定义reg [7:0] dout ;reg dout_vld;reg dout_sop;reg dout_eop;reg din_vld_ff0 ;reg din_vld_ff1 ;reg din_vld_ff2 ;reg din_sop_ff0 ;reg din_sop_ff1 ;reg din_sop_ff2 ;reg din_eop_ff0 ;reg din_eop_ff1 ;reg din_eop_ff2 ;reg [7:0] taps0_ff0 ;reg [7:0] taps0_ff1 ;reg [7:0] taps1_ff0 ;reg [7:0] taps1_ff1 ;reg [7:0] taps2_ff0 ;reg [7:0] taps2_ff1 ;reg [15:0] gs_0 ;reg [15:0] gs_1 ;reg [15:0] gs_2 ;wire [7:0] taps0 ;wire [7:0] taps1 ;wire [7:0] taps2 ;//例化移位寄存器IP核shift_ipcore u1(.clken(din_vld ),.clock(clk ),.shiftin (din ),.shiftout ( ),//悬空.taps0x(taps0),.taps1x(taps1),.taps2x(taps2) );//异步处理消除亚稳态always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindin_vld_ff0 <= 0;din_vld_ff1 <= 0;din_vld_ff2 <= 0;din_sop_ff0 <= 0;din_sop_ff1 <= 0;din_sop_ff2 <= 0;din_eop_ff0 <= 0;din_eop_ff1 <= 0;din_eop_ff2 <= 0;endelse begindin_vld_ff0 <= din_vld;din_vld_ff1 <= din_vld_ff0;din_vld_ff2 <= din_vld_ff1;din_sop_ff0 <= din_sop;din_sop_ff1 <= din_sop_ff0;din_sop_ff2 <= din_sop_ff1;din_eop_ff0 <= din_eop;din_eop_ff1 <= din_eop_ff0;din_eop_ff2 <= din_eop_ff1;endend//D触发器打拍寄存前两拍、前一拍的数据always @ (posedge clk or negedge rst_n)beginif(!rst_n)begintaps0_ff0 <= 0;taps0_ff1 <= 0;taps1_ff0 <= 0;taps1_ff1 <= 0;taps2_ff0 <= 0;taps2_ff1 <= 0;endelse if(din_vld_ff0)begintaps0_ff0 <= taps0;taps0_ff1 <= taps0_ff0;taps1_ff0 <= taps1;taps1_ff1 <= taps1_ff0;taps2_ff0 <= taps2;taps2_ff1 <= taps2_ff0;endend//计算3 * 3 掩模第一行的和always @ (posedge clk or negedge rst_n)beginif(!rst_n)begings_0 <= 0;endelse if(din_vld_ff1)begings_0 <= taps0_ff1 + 2*taps1_ff1 + taps2_ff1;endend//计算3 * 3 掩模第二行的和always @ (posedge clk or negedge rst_n)beginif(!rst_n)begings_1 <= 0;endelse if(din_vld_ff1)begings_1 <= 2*taps0_ff0 + 4*taps1_ff0 + 2*taps2_ff0;endend//计算3 * 3 掩模第三行的和always @ (posedge clk or negedge rst_n)beginif(!rst_n)begings_2 <= 0;endelse if(din_vld_ff1)begings_2 <= taps0 + 2*taps1 + taps2;endend//三行之和除以16,即右移4位always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout <= 0;endelse if(din_vld_ff2)begindout <= (gs_0 + gs_1 + gs_2) >> 4;endend//输出有效,当输出有效时输出有效always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_vld <= 0;endelse if(din_vld_ff2)begindout_vld <= 1;endelse begindout_vld <= 0;endend//一帧输出开始,输入开始时输出开始always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_sop <= 0;endelse if(din_sop_ff2)begindout_sop <= 1;endelse begindout_sop <= 0;endend//一帧输出结束,输入结束时输出结束always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_eop <= 0;endelse if(din_eop_ff2)begindout_eop <= 1;endelse begindout_eop <= 0;endendendmodule

9.二值模块

此模块较为简单,顾名思义就是变为2值数据,也就是0和1。具体的做法为设定一个阈值,大于阈值为1,否则为0。

代码如下:

module gray_bit(clk , //输入,25M时钟rst_n , //输入,复位din , //输入,经过高斯处理的8位数据 din_vld, //输入,输入数据有效din_sop, //输入,一帧图像传输的开始din_eop, //输入,一帧图像传输的结束dout , //输出,数据输出dout_vld , //输出,数据有效dout_sop , //输出,一帧图像传输的开始dout_eop , //输出,一帧图像传输的结束value //输入,设定的阈值);input clk;input rst_n ;input [7:0] value ;input [7:0] din;input din_vld ;input din_sop ;input din_eop ;output dout ;output dout_vld;output dout_sop;output dout_eop;reg dout ;reg dout_vld;reg dout_sop;reg dout_eop;//大于阈值输出1,否则为0always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout <= 0;endelse if(din >= value)begindout <= 1;endelse begindout <= 0;endend//输出有效跟随输入有效always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_vld <= 0;endelse begindout_vld <= din_vld;endend//输出开始跟随输入开始always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_sop <= 0;endelse begindout_sop <= din_sop;endend//输出结束跟随输入结束always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_eop <= 0;endelse begindout_eop <= din_eop;endendendmodule

10.边缘检测模块

与高斯滤波模块一样,此模块同样需要用到3 * 3 的掩模。只不过算法不一样。

具体的算法参考(索贝尔边缘检测算法)

A代表原始图像,Gx、Gy表示索贝尔算子。具体计算式为:

其中其中f(x,y), 表示图像(x,y)点的灰度值,换为模块中的步骤则为:

①按公式计算一行或一列的代数和②求出3 * 3 矩阵的行或者列的差值,用绝对值表示③行的绝对值与列的绝对值相加,判断是否为边缘点,输出

模块图:

代码中的打拍顺序图:

代码如下:

module sobel(clk , //输入,25M时钟rst_n , //输入,复位din , //输入,经过灰度处理的8位数据 din_vld, //输入,输入数据有效din_sop, //输入,一帧图像传输的开始din_eop, //输入,一帧图像传输的结束dout , //输出,数据输出dout_vld , //输出,数据有效dout_sop , //输出,一帧图像传输的开始dout_eop //输出,一帧图像传输的结束 );input clk;input rst_n ;input din;input din_vld ;input din_sop ;input din_eop ;output dout ;output dout_vld;output dout_sop;output dout_eop;reg dout ;reg dout_vld;reg dout_sop;reg dout_eop;reg din_vld_ff0 ;reg din_vld_ff1 ;reg din_vld_ff2 ;reg din_vld_ff3 ;reg din_sop_ff0 ;reg din_sop_ff1 ;reg din_sop_ff2 ;reg din_sop_ff3 ;reg din_eop_ff0 ;reg din_eop_ff1 ;reg din_eop_ff2 ;reg din_eop_ff3 ;reg taps0_ff0 ;reg taps0_ff1 ;reg taps1_ff0 ;reg taps1_ff1 ;reg taps2_ff0 ;reg taps2_ff1 ;reg [7:0] gx_0 ;reg [7:0] gx_2 ;reg [7:0] gy_0 ;reg [7:0] gy_2 ;reg [7:0] gx;reg [7:0] gy;reg [7:0] g ;wiretaps0_tmp ;wiretaps1_tmp ;wiretaps2_tmp ;wiretaps0 ;wiretaps1 ;wiretaps2 ;//例化移位寄存器IP 用来获得3 * 3 掩模shift2_ipcore u1(.clken(din_vld ),.clock(clk ),.shiftin (din ),.shiftout ( ),.taps0x(taps0_tmp ),.taps1x(taps1_tmp ),.taps2x(taps2_tmp ) );assign taps0 = taps0_tmp;assign taps1 = taps1_tmp;assign taps2 = taps2_tmp;//异步处理always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindin_vld_ff0 <= 0;din_vld_ff1 <= 0;din_vld_ff2 <= 0;din_vld_ff3 <= 0;din_sop_ff0 <= 0;din_sop_ff1 <= 0;din_sop_ff2 <= 0;din_sop_ff3 <= 0;din_eop_ff0 <= 0;din_eop_ff1 <= 0;din_eop_ff2 <= 0;din_eop_ff3 <= 0;endelse begindin_vld_ff0 <= din_vld;din_vld_ff1 <= din_vld_ff0;din_vld_ff2 <= din_vld_ff1;din_vld_ff3 <= din_vld_ff2;din_sop_ff0 <= din_sop;din_sop_ff1 <= din_sop_ff0;din_sop_ff2 <= din_sop_ff1;din_sop_ff3 <= din_sop_ff2;din_eop_ff0 <= din_eop;din_eop_ff1 <= din_eop_ff0;din_eop_ff2 <= din_eop_ff1;din_eop_ff3 <= din_eop_ff2;endend//用D触发器打拍获得前两拍、前一拍的数据always @ (posedge clk or negedge rst_n)beginif(!rst_n)begintaps0_ff0 <= 0;taps0_ff1 <= 0;taps1_ff0 <= 0;taps1_ff1 <= 0;taps2_ff0 <= 0;taps2_ff1 <= 0;endelse if(din_vld_ff0)begintaps0_ff0 <= taps0;taps0_ff1 <= taps0_ff0;taps1_ff0 <= taps1;taps1_ff1 <= taps1_ff0;taps2_ff0 <= taps2;taps2_ff1 <= taps2_ff0;endend//gx按列算always @ (posedge clk or negedge rst_n)beginif(!rst_n)begingx_0 <= 0;endelse if(din_vld_ff1)begingx_0 <= taps0_ff1 + 2*taps1_ff1 + taps2_ff1;endendalways @ (posedge clk or negedge rst_n)beginif(!rst_n)begingx_2 <= 0;endelse if(din_vld_ff1)begingx_2 <= taps0 + 2*taps1 + taps2;endend//gy按行算always @ (posedge clk or negedge rst_n)beginif(!rst_n)begingy_0 <= 0;endelse if(din_vld_ff1)begingy_0 <= taps0_ff1 + 2*taps0_ff0 + taps0;endendalways @ (posedge clk or negedge rst_n)beginif(!rst_n)begingy_2 <= 0;endelse if(din_vld_ff1)begingy_2 <= taps2_ff1 + 2*taps2_ff0 + taps2;endend//gx用绝对值表示always @ (posedge clk or negedge rst_n)beginif(!rst_n)begingx <= 0;endelse if(din_vld_ff2)begingx <= (gx_0>gx_2) ? (gx_0-gx_2) : (gx_2-gx_0);endend//gy用绝对值表示always @ (posedge clk or negedge rst_n)beginif(!rst_n)begingy <= 0;endelse if(din_vld_ff2)begingy <= (gy_0>gy_2) ? (gy_0-gy_2) : (gy_2-gy_0);endend//相加得到Galways @ (posedge clk or negedge rst_n)beginif(!rst_n)beging <= 0;endelse if(din_vld_ff3)beging <= gx + gy;endend//判断边缘并输出always @ (*)begindout = (g>=1) ? 1 : 0;end//输出有效always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_vld <= 0;endelse if(din_vld_ff3)begindout_vld <= 1;endelse begindout_vld <= 0;endend//输出开始always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_sop <= 0;endelse if(din_sop_ff3)begindout_sop <= 1;endelse begindout_sop <= 0;endend//输出结束always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_eop <= 0;endelse if(din_eop_ff3)begindout_eop <= 1;endelse begindout_eop <= 0;endendendmodule

11.存储模块

本项目需要存储的数据量其实是不大的,因而使用FPGA片内的两个RAM就能完成存储功能。,其工作方式为:

每个RAM可以保存1幅320*200的图像①图像数据开始时保存到RAM0,同时VGA从RAM1中读取图像数据进行显示。②读取RAM0的数据进行显示。同时模块准备将新的图像数据写到RAM1当中。③ 需要注意的是:当写完一幅图像并且读完一幅图像时,RAM才开始切换。

ram的ip核:

模块图:

代码如下:

module ram(clk , //输入,25M时钟rst_n , //输入,复位din , //输入,输入1位数据din_vld, //输入,输入数据有效信号din_sop, //输入,一帧数据的开始din_eop, //输入,一帧数据的结束rd_addr, //输入,要读RAM的地址rd_en , //输入,读RAM的使能rd_end, //输入,读结束信号rd_addr_sel , //输入,读切换信号,用来切换RAMdout , //输出,输出1位数据wr_end//输出,写结束信号 );input clk ;input rst_n;input din ;input din_vld ;input din_sop ;input din_eop ;input [15:0] rd_addr ;input rd_en;input rd_end;input rd_addr_sel;output dout;output wr_end ;reg dout;reg wr_end ;reg [9:0] cnt_col ;reg [9:0] cnt_row ;reg wr_data ;reg [15:0] wr_addr ;reg wr_addr_sel;reg wr_en0;reg wr_en1;reg rd_en0;reg rd_en1;reg flag_wr ;wireadd_cnt_col;wireend_cnt_col;wireadd_cnt_row;wireend_cnt_row;wiredisplay_area ;wireq0 ;wireq1 ;//1行640个像素点always @ (posedge clk or negedge rst_n)beginif(!rst_n)begincnt_col <= 0;endelse if(add_cnt_col)beginif(end_cnt_col)cnt_col <= 0;elsecnt_col <= cnt_col + 1;endendassign add_cnt_col = (flag_wr || (wr_end==0 && din_sop)) && din_vld;assign end_cnt_col = add_cnt_col && cnt_col == 640-1;//480行像素点,即一帧为640*480always @ (posedge clk or negedge rst_n)beginif(!rst_n)begincnt_row <= 0;endelse if(add_cnt_row)beginif(end_cnt_row)cnt_row <= 0;elsecnt_row <= cnt_row + 1;endendassign add_cnt_row = end_cnt_col;assign end_cnt_row = add_cnt_row && cnt_row == 480-1;//显示区域为 (160--480)*(140--340) 320*200的一帧图像always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_data <= 0;endelse if(display_area)beginwr_data <= din;endendassign display_area = cnt_col >= 160 && cnt_col < 480 && cnt_row >= 140 && cnt_row <= 340;//写到ram的地址always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_addr <= 0;endelse if(display_area)beginwr_addr <= (cnt_col-160) + 320*(cnt_row-140);endend//当读和写都完成时,写切换标志信号wr_addr_sel信号取反always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_addr_sel <= 0;endelse if(wr_end && rd_end)beginwr_addr_sel <= ~wr_addr_sel;endend//一帧数据传完,写结束always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_end <= 0;endelse if(end_cnt_row)beginwr_end <= 1;endelse if(wr_end && rd_end)beginwr_end <= 0;endend//写指示区域信号,在写结束且一帧图像又要开始时置1always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginflag_wr <= 0;endelse if(wr_end == 0 && din_sop)beginflag_wr <= 1;endelse if(end_cnt_row)beginflag_wr <= 0;endend//写到RAM0的使能,在显示区域且切换标志为0且数据有效时开始使能always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_en0 <= 0;endelse if(display_area && wr_addr_sel==0 && din_vld)beginwr_en0 <= 1;endelse beginwr_en0 <= 0;endend//写到RAM1的使能,在显示区域且切换标志为1且数据有效时开始使能always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_en1 <= 0;endelse if(display_area && wr_addr_sel==1 && din_vld)beginwr_en1 <= 1;endelse beginwr_en1 <= 0;endend//例化//说明一下,因为rd_addr最多给到16位,也就是RAM最多能存2^16 = 65536个像素点//所以对于640* 480的一帧图像,我们只能选择中间的320*200的区域来进行显示ram_ipcore u0(.clock(clk ),.data (wr_data ),.rdaddress (rd_addr ),.rden (rd_en0 ),.wraddress (wr_addr ),.wren (wr_en0 ),.q(q0 ) );ram_ipcore u1(.clock(clk ),.data (wr_data ),.rdaddress (rd_addr ),.rden (rd_en1 ),.wraddress (wr_addr ),.wren (wr_en1 ),.q(q1 ) );//RAM0读使能在读切换信号为0且读使能有效时置1always @ (*)beginif(rd_en && rd_addr_sel == 0)rd_en0 = 1;else rd_en0 = 0;end//RAM1读使能在读切换信号为1且读使能有效时置1always @ (*)beginif(rd_en && rd_addr_sel == 1)rd_en1 = 1;else rd_en1 = 0;end//输出也得看从哪个RAM读always @ (*)beginif(rd_addr_sel == 0)begindout = q0;endelse if(rd_addr_sel == 1)begindout = q1;endelse begindout = 0;endendendmodule

12.VGA模块

此模块的作用为读取存储模块的数据并驱动到外部显示器进行显示。具体的VGA时序可以看VGA接口时序。

具体代码如下:

module vga_driver(clk , //输入,25m时钟rst_n , //输入,复位din , //输入,输入数据wr_end, //输入,写数据结束vga_hys, //输出,行同步信号vga_vys, //输出,场同步信号vga_rgb, //输出,16位的颜色信号rd_addr, //输出,16位读地址rd_en , //输出,读使能信号rd_end, //输出,读结束信号rd_addr_sel//输出,地址改变信号);parameterDATA_W = 16;parameterCOL = 320;parameterROW = 200;parameterCOL_2 = 160;parameterROW_2 = 100;input clk;input rst_n ;input din;input wr_end ;output vga_hys ;output vga_vys ;output [DATA_W-1:0] vga_rgb ;output [15:0] rd_addr ;output rd_en;output rd_end;output rd_addr_sel;reg vga_hys ;reg vga_vys ;reg [DATA_W-1:0] vga_rgb ;reg [15:0] rd_addr ;reg rd_en;reg rd_end;reg rd_addr_sel;reg [9:0] cnt_hys ;reg [9:0] cnt_vys ;reg vga_hys_tmp;reg vga_vys_tmp;reg vga_hys_tmp_ff0 ;reg vga_vys_tmp_ff0 ;reg display_area ;reg e_area;reg display_area_ff0;reg e_area_ff0;reg display_area_ff1;reg e_area_ff1;reg [9:0] x;reg [9:0] y;wireadd_cnt_hys;wireend_cnt_hys;wireadd_cnt_vys;wireend_cnt_vys;wire [9:0] x0 ;wire [9:0] x1 ;wire [9:0] y0 ;wire [9:0] y1 ;//一行的时钟数 在一定的时钟输出像素点always @ (posedge clk or negedge rst_n)beginif(!rst_n)begincnt_hys <= 0;endelse if(add_cnt_hys)beginif(end_cnt_hys)cnt_hys <= 0;elsecnt_hys <= cnt_hys + 1;endendassign add_cnt_hys = 1;assign end_cnt_hys = add_cnt_hys && cnt_hys == 800-1;//525行数据,一行的像素点显示完后+1到下一行always @ (posedge clk or negedge rst_n)beginif(!rst_n)begincnt_vys <= 0;endelse if(add_cnt_vys)beginif(end_cnt_vys)cnt_vys <= 0;elsecnt_vys <= cnt_vys + 1;endendassign add_cnt_vys = end_cnt_hys;assign end_cnt_vys = add_cnt_vys && cnt_vys == 525-1;//一行的时钟数到达96 即vga的同步脉冲数96个后,将vga_hys_tmp拉高always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginvga_hys_tmp <= 0;endelse if(add_cnt_hys && cnt_hys == 95)beginvga_hys_tmp <= 1;endelse if(end_cnt_hys)beginvga_hys_tmp <= 0;endend//场同步信号在第二个vys时钟拉高always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginvga_vys_tmp <= 0;endelse if(add_cnt_vys && cnt_vys == 1)beginvga_vys_tmp <= 1;endelse if(end_cnt_vys)beginvga_vys_tmp <= 0;endend//vga_hys异步处理//vga_vys异步处理//打两拍always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginvga_hys_tmp_ff0 <= 0;vga_vys_tmp_ff0 <= 0;vga_hys <= 0;vga_vys <= 0;endelse beginvga_hys_tmp_ff0 <= vga_hys_tmp;vga_vys_tmp_ff0 <= vga_vys_tmp;vga_hys <= vga_hys_tmp_ff0;vga_vys <= vga_vys_tmp_ff0;endend//显示区域always @ (*)begindisplay_area = cnt_hys >= 141 && cnt_hys < (141+646) && cnt_vys >= 32 && cnt_vys < (32+484);end//有效边缘区域always @ (*)begine_area = cnt_hys >= x0 && cnt_hys < x1 && cnt_vys >= y0 && cnt_vys < y1;end//对显示区域信号进行打拍always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindisplay_area_ff0 <= 0;e_area_ff0 <= 0;display_area_ff1 <= 0;e_area_ff1 <= 0;endelse begindisplay_area_ff0 <= display_area;e_area_ff0 <= e_area;display_area_ff1 <= display_area_ff0;e_area_ff1 <= e_area_ff0;endend//有效区域 320*200 在正中间assign x0 = 141 + (323 - COL_2); //304assign x1 = 141 + (323 + COL_2); //624assign y0 = 32 + (242 - ROW_2);//174assign y1 = 32 + (242 + ROW_2);//374//读地址的计算//RAM中地址按位数一位一位存储//地址信号16位,能存65536个always @ (*)beginx = cnt_hys - x0;endalways @ (*)beginy = cnt_vys - y0;endalways @ (*)beginrd_addr = COL*y + x;end//读完写完一帧,换RAM继续读写always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginrd_addr_sel <= 1;endelse if(rd_end && wr_end)beginrd_addr_sel <= ~rd_addr_sel;endend//当最后一行显示完,读结束always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginrd_end <= 0;endelse if(end_cnt_vys)beginrd_end <= 1;endelse beginrd_end <= 0;endend//在有效显示区域 320*200 时 读开始always @ (*)beginrd_en = e_area;end//rgb565 全1为白 全0为黑always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginvga_rgb <= 0;endelse if(display_area_ff1)beginif(e_area_ff1)beginvga_rgb <= ~{DATA_W{din}};endelse beginvga_rgb <= {DATA_W{1'b1}};endendelse beginvga_rgb <= 0;endendendmodule

13.顶层模块

顶层模块的作用就是将各个模块连接在一起。

代码如下:

module byjc(clk ,rst_n ,key_in,vsync ,href ,din ,xclk ,pwdn ,sio_c ,sio_d , vga_hys,vga_vys,vga_rgb);input clk;input rst_n ;input [3:0] key_in ;input vsync ;input href ;input [7:0] din;output xclk ;output pwdn ;output vga_hys ;output vga_vys ;output [15:0] vga_rgb ;output sio_c;inout sio_d;wireen_sio_d_w;wiresio_d_w ;wiresio_d_r ;assign sio_d = en_sio_d_w ? sio_d_w : 1'dz;assign sio_d_r = sio_d;wire clk_25m ;wire locked ;wire [3:0] key_num ;wire en_coms ;wire [7:0] value_gray ;wire rdy ;wire wen ;wire ren ;wire [7:0] addr;wire [7:0] wdata ;wire capture_en ;wire [7:0] rdata ;wire rdata_vld;wire [15:0] cmos_dout;wire cmos_dout_vld ;wire cmos_dout_sop ;wire cmos_dout_eop ;wire [7:0] gray_dout;wire gray_dout_vld ;wire gray_dout_sop ;wire gray_dout_eop ;wire [7:0] gs_dout ;wire gs_dout_vld ;wire gs_dout_sop ;wire gs_dout_eop ;wire bit_dout;wire bit_dout_vld ;wire bit_dout_sop ;wire bit_dout_eop ;wire sobel_dout ;wire sobel_dout_vld;wire sobel_dout_sop;wire sobel_dout_eop;wire [15:0] rd_addr ;wire rd_en ;wire vga_data;wire rd_end ;wire wr_end ;wire rd_addr_sel ;wire[3:0]key_vld ;wire en_vld;wire [7:0] sub_addr;pll_ipcore u0(.inclk0 (clk ),.c0(xclk ) );key_en u1(.clk (xclk ),.rst_n (rst_n ),.key_in (key_in ),.key_vld(key_vld ) );ov7670_config u2(.clk (xclk ),.rst_n (rst_n ),.config_en (key_vld[1] /*en_vld*/ ),.rdy (rdy ),.rdata (rdata ),.rdata_vld (rdata_vld ),.wdata (wdata ),.addr (sub_addr ),.wr_en (wen ),.rd_en (ren ),.cmos_en(en_capture ),.pwdn (pwdn ) );sccb u3(.clk (xclk ),.rst_n(rst_n ),.ren (ren),.wen (wen),.sub_addr (sub_addr),.rdata(rdata ),.rdata_vld (rdata_vld ),.wdata(wdata ),.rdy (rdy),.sio_c(sio_c ),.sio_d_r (sio_d_r),.en_sio_d_w (en_sio_d_w ),.sio_d_w (sio_d_w) );cmos_capture u4(.clk (xclk ),.rst_n (rst_n ),.en_capture (en_capture ),.vsync (vsync ),.href (href ),.din (din ),.dout (cmos_dout ),.dout_vld (cmos_dout_vld ),.dout_sop (cmos_dout_sop ),.dout_eop (cmos_dout_eop ) );rgb565_gray u5(.clk (xclk ),.rst_n (rst_n ),.din (cmos_dout ),.din_vld(cmos_dout_vld ),.din_sop(cmos_dout_sop ),.din_eop(cmos_dout_eop ),.dout (gray_dout ),.dout_vld (gray_dout_vld ),.dout_sop (gray_dout_sop ),.dout_eop (gray_dout_eop ) );gs_filter u6(.clk (xclk ),.rst_n (rst_n ),.din (gray_dout ),.din_vld(gray_dout_vld ),.din_sop(gray_dout_sop ),.din_eop(gray_dout_eop ),.dout (gs_dout),.dout_vld (gs_dout_vld),.dout_sop (gs_dout_sop),.dout_eop (gs_dout_eop) );gray_bit u7(.clk (xclk ),.rst_n (rst_n ),.value ( 150 ),.din (gs_dout),.din_vld(gs_dout_vld),.din_sop(gs_dout_sop),.din_eop(gs_dout_eop),.dout (bit_dout ),.dout_vld (bit_dout_vld),.dout_sop (bit_dout_sop),.dout_eop (bit_dout_eop) );sobel u8(.clk (xclk ),.rst_n (rst_n ),.din (bit_dout ),.din_vld(bit_dout_vld),.din_sop(bit_dout_sop),.din_eop(bit_dout_eop),.dout (sobel_dout ),.dout_vld (sobel_dout_vld ),.dout_sop (sobel_dout_sop ),.dout_eop (sobel_dout_eop ));ram_sel u9(.clk (xclk ),.rst_n (rst_n ),.din (sobel_dout ),.din_vld(sobel_dout_vld ),.din_sop(sobel_dout_sop ),.din_eop(sobel_dout_eop ),.rd_addr(rd_addr),.rd_en (rd_en ),.rd_end(rd_end ),.rd_addr_sel (rd_addr_sel),.dout (vga_data ),.wr_end(wr_end ) );vga_driver u10(.clk (xclk ),.rst_n (rst_n ),.din (vga_data ),.wr_end(wr_end ),.vga_hys(vga_hys),.vga_vys(vga_vys),.vga_rgb(vga_rgb),.rd_addr(rd_addr),.rd_en (rd_en ),.rd_end(rd_end ),.rd_addr_sel (rd_addr_sel) );endmodule

整个项目编译后,RTL连接如图所示:

14.管脚配置及上板实验

编译成功后,还要进行管脚的配置,再搭上硬件,整个边缘检测系统就算成功了,当然中间省略了关键的一步就是波形的验证仿真,因为工作量太大就不一一陈述了。下面分析一下有哪些管脚需要配置,也就是那些是直接连到FPGA上的信号。

配置完管脚之后再次编译,上板实验得到如下结果:

15. 后记:资源使用情况

片上的内存用的比较多,可以尝试采用sdram来进行存储

总的基本逻辑单元用了1071个,其中组合逻辑占973个,时序逻辑占395个

纯组合逻辑676个基本逻辑单元

纯时序逻辑98个基本逻辑单元

组合时序联合297个基本逻辑单元

组合/时序 = 937/395 = 2.37

如果觉得《FPGA综合项目——图像边缘检测系统》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。