• 基于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接口进行显示

    项目模块设计:

     1.2 设计流程

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

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

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

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

     二  接口设计

    2.1 摄像头配置模块

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

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

    配置流程:

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

    2.2 IIC_master 模块

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

    1. module i2c_master(
    2. input clk ,
    3. input rst_n ,
    4. input req ,
    5. input [3:0] cmd ,
    6. input [7:0] din ,
    7. output [7:0] dout ,
    8. output done ,
    9. output slave_ack ,
    10. output i2c_scl ,
    11. input i2c_sda_i ,
    12. output i2c_sda_o ,
    13. output i2c_sda_oe
    14. );

    设计接口模块

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

     SCL设计:

    1. //scl
    2. always @(posedge clk or negedge rst_n)begin
    3. if(~rst_n)begin
    4. scl <= 1'b1;
    5. end
    6. else if(idle2start | idle2write | idle2read)begin//开始发送时,拉低
    7. scl <= 1'b0;
    8. end
    9. else if(add_cnt_scl && cnt_scl == `SCL_HALF-1)begin
    10. scl <= 1'b1;
    11. end
    12. else if(end_cnt_scl && ~stop2idle)begin
    13. scl <= 1'b0;
    14. end
    15. end

    数据发送:

    1. //sda_out
    2. always @(posedge clk or negedge rst_n)begin
    3. if(~rst_n)begin
    4. sda_out <= 1'b1;
    5. end
    6. else if(state_c == START)begin //发起始位
    7. if(cnt_scl == `LOW_HLAF)begin //时钟低电平时拉高sda总线
    8. sda_out <= 1'b1;
    9. end
    10. else if(cnt_scl == `HIGH_HALF)begin //时钟高电平时拉低sda总线
    11. sda_out <= 1'b0; //保证从机能检测到起始位
    12. end
    13. end
    14. else if(state_c == WRITE && cnt_scl == `LOW_HLAF)begin //scl低电平时发送数据 并串转换
    15. sda_out <= tx_data[7-cnt_bit];
    16. end
    17. else if(state_c == SACK && cnt_scl == `LOW_HLAF)begin //发应答位
    18. sda_out <= (command&`CMD_STOP)?1'b1:1'b0;
    19. end
    20. else if(state_c == STOP)begin //发停止位
    21. if(cnt_scl == `LOW_HLAF)begin //时钟低电平时拉低sda总线
    22. sda_out <= 1'b0;
    23. end
    24. else if(cnt_scl == `HIGH_HALF)begin //时钟高电平时拉高sda总线
    25. sda_out <= 1'b1; //保证从机能检测到停止位
    26. end
    27. end
    28. end
    29. //sda_out_en 总线输出数据使能
    30. always @(posedge clk or negedge rst_n)begin
    31. if(~rst_n)begin
    32. sda_out_en <= 1'b0;
    33. end
    34. else if(idle2start | idle2write | read2sack | rack2stop)begin
    35. sda_out_en <= 1'b1;
    36. end
    37. else if(idle2read | start2read | write2rack | stop2idle)begin
    38. sda_out_en <= 1'b0;
    39. end
    40. end

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

    配置的顶层模块接口:

    1. module cmos_top(
    2. input clk , //xclk 24M
    3. input rst_n ,
    4. output scl , //scl
    5. inout sda , //sda
    6. output pwdn ,
    7. output reset ,
    8. output cfg_done //配置完成信号
    9. );

    之后就进行数据采集

    2.3 采集数据模块

    接口设计:

    1. module capture(
    2. input clk ,//像素时钟 摄像头输出的pclk
    3. input rst_n ,
    4. input enable , //采集使能 配置完成
    5. input vsync ,//摄像头场同步信号
    6. input href ,//摄像头行参考信号
    7. input [7:0] din ,//摄像头像素字节
    8. output [15:0] dout ,//像素数据
    9. output dout_sop,//包文头 一帧图像第一个像素点
    10. output dout_eop,//包文尾 一帧图像最后一个像素点
    11. output dout_vld //像素数据有效
    12. );

    设计思路:

    • 先对场同步信号进行同步打拍,然后检测下降沿
    1. //vsync同步打拍
    2. always @(posedge clk or negedge rst_n)begin
    3. if(~rst_n)begin
    4. vsync_r <= 2'b00;
    5. end
    6. else begin
    7. vsync_r <= {vsync_r[0],vsync};
    8. end
    9. end
    10. assign vsync_nedge = vsync_r[1] & ~vsync_r[0]; //检测下降沿

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

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

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

    2.4 灰度转化 

    接口设计:

    1. module rgb2gray(
    2. input clk ,
    3. input rst_n ,
    4. input din_sop ,
    5. input din_eop ,
    6. input din_vld ,
    7. input [15:0] din ,//RGB565
    8. output dout_sop ,
    9. output dout_eop ,
    10. output dout_vld ,
    11. output [7:0] dout //灰度输出
    12. );

    原理 :

    • 人眼对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格式的图像,这里有两种方式的扩展位宽,第一种是采用带补偿的拓展位宽;第二种是采用不带补偿的拓展位宽。
    1. //扩展 RGB565-->RGB888
    2. always @(posedge clk or negedge rst_n)begin
    3. if(~rst_n)begin
    4. data_r <= 0;
    5. data_g <= 0;
    6. data_b <= 0;
    7. end
    8. else if(din_vld)begin
    9. data_r <= {din[15:11],din[13:11]}; //带补偿的 r5,r4,r3,r2,r1, r3,r2,r1
    10. data_g <= {din[10:5],din[6:5]} ; //补偿低三位
    11. data_b <= {din[4:0],din[2:0]} ;
    12. /*
    13. data_r <= {din[15:11],3'd0};
    14. data_g <= {din[10:5],2'd0} ;
    15. data_b <= {din[4:0],3'd0} ;
    16. */
    17. end
    18. end

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

    • 这里用到的是10位精度的扩大,人眼对绿色的敏感度最高,权重为 0.587,换成整数是10位的位宽。
    1. //加权
    2. //第一拍
    3. always @(posedge clk or negedge rst_n)begin
    4. if(~rst_n)begin
    5. pixel_r <= 0;
    6. pixel_g <= 0;
    7. pixel_b <= 0;
    8. end
    9. else if(vld[0])begin
    10. pixel_r <= data_r * 306;
    11. pixel_g <= data_g * 601;
    12. pixel_b <= data_b * 117;
    13. end
    14. end
    15. //第二拍
    16. always @(posedge clk or negedge rst_n)begin
    17. if(~rst_n)begin
    18. pixel <= 0;
    19. end
    20. else if(vld[1])begin
    21. pixel <= pixel_r + pixel_g + pixel_b;
    22. end
    23. end
    24. assign dout = pixel[10 +:8]; //从第十位开始取数据,取八位。

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

    2.5 高斯滤波

    接口设计:

    1. module gauss_filter(
    2. input clk ,
    3. input rst_n ,
    4. input din_sop ,
    5. input din_eop ,
    6. input din_vld ,
    7. input [7:0] din ,//灰度输入
    8. output dout_sop ,
    9. output dout_eop ,
    10. output dout_vld ,
    11. output [7:0] dout //灰度输出
    12. );

    设计思路:

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

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

    1. wire [7:0] taps0 ;
    2. wire [7:0] taps1 ;
    3. wire [7:0] taps2 ;
    4. //缓存3行数据
    5. gs_line_buf gs_line_buf_inst (
    6. .aclr (~rst_n ),
    7. .clken (din_vld ),
    8. .clock (clk ),
    9. /*input*/
    10. .shiftin (din ),
    11. .shiftout ( ),
    12. /*output*/
    13. .taps0x (taps0 ),
    14. .taps1x (taps1 ),
    15. .taps2x (taps2 )
    16. );

    卷积:

    1. reg [7:0] line0_0 ;
    2. reg [7:0] line0_1 ;
    3. reg [7:0] line0_2 ;
    4. reg [7:0] line1_0 ;
    5. reg [7:0] line1_1 ;
    6. reg [7:0] line1_2 ;
    7. reg [7:0] line2_0 ;
    8. reg [7:0] line2_1 ;
    9. reg [7:0] line2_2 ;
    10. reg [9:0] sum_0 ;//第0行加权和
    11. reg [9:0] sum_1 ;//第1行加权和
    12. reg [9:0] sum_2 ;//第2行加权和
    13. reg [7:0] sum ;//三行的加权和
    • 首先是参数,lineX_Y表示第X第Y行,我们需要创造一个3*3的矩阵寄存器,所以这里共需要9个寄存器,并且需要求的加权和,所以每一行再分配一个sum,最后再分配一个总sum;
    1. /*
    2. 高斯滤波系数,加权平均
    3. 1 2 1
    4. 2 4 2
    5. 1 2 1
    6. */
    7. always @(posedge clk or negedge rst_n)begin
    8. if(~rst_n)begin
    9. line0_0 <= 0;line0_1 <= 0;line0_2 <= 0;
    10. line1_0 <= 0;line1_1 <= 0;line1_2 <= 0;
    11. line2_0 <= 0;line2_1 <= 0;line2_2 <= 0;
    12. end
    13. else if(vld[0])begin
    14. line0_0 <= taps0;line0_1 <= line0_0;line0_2 <= line0_1;
    15. line1_0 <= taps1;line1_1 <= line1_0;line1_2 <= line1_1;
    16. line2_0 <= taps2;line2_1 <= line2_0;line2_2 <= line2_1;
    17. end
    18. end
    19. always @(posedge clk or negedge rst_n)begin
    20. if(~rst_n)begin
    21. sum_0 <= 0;
    22. sum_1 <= 0;
    23. sum_2 <= 0;
    24. end
    25. else if(vld[1])begin
    26. sum_0 <= {2'd0,line0_0} + {1'd0,line0_1,1'd0} + {2'd0,line0_2};
    27. sum_1 <= {1'd0,line1_0,1'd0} + {line1_1,2'd0} + {1'd0,line1_2,1'd0};
    28. sum_2 <= {2'd0,line2_0} + {1'd0,line2_1,1'd0} + {2'd0,line2_2};
    29. end
    30. end
    31. always @(posedge clk or negedge rst_n)begin
    32. if(~rst_n)begin
    33. sum <= 0;
    34. end
    35. else if(vld[2])begin
    36. sum <= (sum_0 + sum_1 + sum_2)>>4;
    37. end
    38. end
    1. 第一个Always完成的是9个八位的寄存器对图像数据的存储;
    2. 第二个Always通过位拼接来实现与卷积核的乘法(直接乘也没问题,只是这样写能显示出老练的技法)与每行的求和;
    3. 第三个Always就是求总和,这里也和灰度寄存器一样使用了移位运算符,这也是因为Verilog不能进行小数运算,实际运算的卷积核内的参数都是扩大了16倍(2^4)。

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

    2.7 二值化

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

    接口设计:

    1. module gray2bin(
    2. input clk ,
    3. input rst_n ,
    4. input din_sop ,
    5. input din_eop ,
    6. input din_vld ,
    7. input [7:0] din ,//灰度输入
    8. output dout_sop ,
    9. output dout_eop ,
    10. output dout_vld ,
    11. output dout //二值输出
    12. );

    设计思路:

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

    1. always @(posedge clk or negedge rst_n)begin
    2. if(~rst_n)begin
    3. binary <= 0 ;
    4. binary_sop <= 0 ;
    5. binary_eop <= 0 ;
    6. binary_vld <= 0 ;
    7. end
    8. else begin
    9. binary <= din>100 ;//二值化阈值可自定义
    10. binary_sop <= din_sop ;
    11. binary_eop <= din_eop ;
    12. binary_vld <= din_vld ;
    13. end
    14. end

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

     2.8 Sobel边缘检测

    接口设计:

    1. module sobel(
    2. input clk ,
    3. input rst_n ,
    4. input din ,//输入二值图像
    5. input din_sop ,
    6. input din_eop ,
    7. input din_vld ,
    8. output dout ,
    9. output dout_sop,
    10. output dout_eop,
    11. output dout_vld
    12. );

    设计思路:

     

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

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

     

    • Gx 和 Gy 如下

     梯度计算:

     

     

    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设计,慢时钟域写到快时钟域读
      1. wrfifo wrfifo_inst (
      2. .aclr (~rst_n ),
      3. .data (wfifo_data ),
      4. .rdclk (clk ),
      5. .rdreq (wfifo_rdreq),
      6. .wrclk (clk_in ),
      7. .wrreq (wfifo_wrreq),
      8. .q (wfifo_q ),
      9. .rdempty(wfifo_empty),
      10. .rdusedw(wfifo_usedw),
      11. .wrfull (wfifo_full )
      12. );
      13. assign wfifo_data = {din_eop,din_sop,din};
      14. assign wfifo_wrreq = ~wfifo_full & din_vld & ((~wr_finish_r[1] & din_sop) ||wr_data_flag);
      15. assign wfifo_rdreq = state_c == WRITE && ~avs_waitrequest;
      数据进行拼接,将eop,sop和 数据拼接到一起,用于判断能不能写入
    2. 读写仲裁优先级
      1. /************************读写优先级仲裁*****************************/
      2. //rd_flag ;//读请求标志
      3. always @(posedge clk or negedge rst_n)begin
      4. if(!rst_n)begin
      5. rd_flag <= 0;
      6. end
      7. else if(rfifo_usedw <= `RD_LT)begin
      8. rd_flag <= 1'b1;
      9. end
      10. else if(rfifo_usedw > `RD_UT)begin
      11. rd_flag <= 1'b0;
      12. end
      13. end
      14. //wr_flag ;//写请求标志
      15. always @(posedge clk or negedge rst_n)begin
      16. if(!rst_n)begin
      17. wr_flag <= 0;
      18. end
      19. else if(wfifo_usedw >= `USER_BL)begin
      20. wr_flag <= 1'b1;
      21. end
      22. else begin
      23. wr_flag <= 1'b0;
      24. end
      25. end
      26. //flag_sel ;//标记上一次操作
      27. always @(posedge clk or negedge rst_n)begin
      28. if(!rst_n)begin
      29. flag_sel <= 0;
      30. end
      31. else if(read2done)begin
      32. flag_sel <= 1;
      33. end
      34. else if(write2done)begin
      35. flag_sel <= 0;
      36. end
      37. end
      38. //prior_flag ;//优先级标志 0:写优先级高 1:读优先级高 仲裁读、写的优先级
      39. always @(posedge clk or negedge rst_n)begin
      40. if(!rst_n)begin
      41. prior_flag <= 0;
      42. end
      43. else if(wr_flag && (flag_sel || (~flag_sel && ~rd_flag)))begin //突发写优先级高
      44. prior_flag <= 1'b0;
      45. end
      46. else if(rd_flag && (~flag_sel || (flag_sel && ~wr_flag)))begin //突发读优先级高
      47. prior_flag <= 1'b1;
      48. end
      49. end
      50. /******************************************************************/

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

    3. 地址设计
      1. // wr_addr rd_addr
      2. always @(posedge clk or negedge rst_n) begin
      3. if (rst_n==0) begin
      4. wr_addr <= 0;
      5. end
      6. else if(add_wr_addr) begin
      7. if(end_wr_addr)
      8. wr_addr <= 0;
      9. else
      10. wr_addr <= wr_addr+1 ;
      11. end
      12. end
      13. assign add_wr_addr = (state_c == WRITE) && ~avs_waitrequest;
      14. assign end_wr_addr = add_wr_addr && wr_addr == `BURST_MAX-1 ;
      15. always @(posedge clk or negedge rst_n) begin
      16. if (rst_n==0) begin
      17. rd_addr <= 0;
      18. end
      19. else if(add_rd_addr) begin
      20. if(end_rd_addr)
      21. rd_addr <= 0;
      22. else
      23. rd_addr <= rd_addr+1 ;
      24. end
      25. end
      26. assign add_rd_addr = (state_c == READ) && ~avs_waitrequest;
      27. assign end_rd_addr = add_rd_addr && rd_addr == `BURST_MAX-1;

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

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

      1. //change bank
      2. always @(posedge clk or negedge rst_n)begin
      3. if(~rst_n)begin
      4. wr_bank <= 2'b00;
      5. rd_bank <= 2'b11;
      6. end
      7. else if(change_bank)begin
      8. wr_bank <= ~wr_bank;
      9. rd_bank <= ~rd_bank;
      10. end
      11. end
      12. //wr_finish 一帧数据全部写到SDRAM
      13. always @(posedge clk or negedge rst_n)begin
      14. if(~rst_n)begin
      15. wr_finish <= 1'b0;
      16. end
      17. else if(~wr_finish & wfifo_q[17])begin //写完 从wrfifo读出eop
      18. wr_finish <= 1'b1;
      19. end
      20. else if(wr_finish && end_rd_addr)begin //读完
      21. wr_finish <= 1'b0;
      22. end
      23. end
      24. //change_bank ;//切换bank
      25. always @(posedge clk or negedge rst_n)begin
      26. if(~rst_n)begin
      27. change_bank <= 1'b0;
      28. end
      29. else begin
      30. change_bank <= wr_finish && end_rd_addr;
      31. end
      32. end
      33. /*********************** wrfifo 写数据 ************************/
      34. //控制像素数据帧 写入 或 丢帧
      35. always @(posedge clk_in or negedge rst_n)begin
      36. if(~rst_n)begin
      37. wr_data_flag <= 1'b0;
      38. end
      39. else if(~wr_data_flag & ~wr_finish_r[1] & din_sop)begin//可以向wrfifo写数据
      40. wr_data_flag <= 1'b1;
      41. end
      42. else if(/*wr_finish_r[1] && din_sop*/wr_data_flag & din_eop)begin//不可以向wrfifo写入数据
      43. wr_data_flag <= 1'b0;
      44. end
      45. end
      46. always @(posedge clk_in or negedge rst_n)begin //把wr_finish从wrfifo的读侧同步到写侧
      47. if(~rst_n)begin
      48. wr_finish_r <= 0;
      49. end
      50. else begin
      51. wr_finish_r <= {wr_finish_r[0],wr_finish};
      52. end
      53. end
      54. /****************************************************************/

    5. rdfifo设计 ,快时钟写到慢时钟读
      1. rdfifo u_rdfifo(
      2. .aclr (~rst_n ),
      3. .data (rfifo_data ),
      4. .rdclk (clk_out ),
      5. .rdreq (rfifo_rdreq),
      6. .wrclk (clk ),
      7. .wrreq (rfifo_wrreq),
      8. .q (rfifo_q ),
      9. .rdempty (rfifo_empty),
      10. .wrfull (rfifo_full ),
      11. .wrusedw (rfifo_usedw)
      12. );
      13. assign rfifo_data = avs_rddata;
      14. assign rfifo_wrreq = ~rfifo_full & avs_rddata_vld;
      15. 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显示

    接口设计:

    1. module vga_interface ( //1280*720
    2. input clk ,//75MHz
    3. input rst_n ,
    4. input [15:0] din ,
    5. input din_vld ,
    6. output rdy ,
    7. output [15:0] vga_rgb ,
    8. output vga_hsync,
    9. output vga_vsync
    10. );

    设计思路:

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

      用一个fifo来缓存数据

      1. //FIFO例化
      2. vga_buf u_buf(
      3. .aclr (~rst_n ),
      4. .clock (clk ),
      5. .data (din ),
      6. .rdreq (rdreq ),
      7. .wrreq (wrreq ),
      8. .empty (empty ),
      9. .full (full ),
      10. .q (q_out ),
      11. .usedw (usedw )
      12. );
      13. assign wrreq = ~full && din_vld;
      14. assign rdreq = ~empty && display_vld;

    三 代码设计

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

  • 相关阅读:
    Qt 10进制和16进制转换
    k8s集群配置
    ArcGIS模型构建器ModelBuilder的使用方法
    飞书公式总结
    饲料化肥经营商城小程序的作用是什么
    【Kotlin 协程】协程简介 ( 协程概念 | 协程作用 | 创建 Android 工程并进行协程相关配置开发 | 异步任务与协程对比 )
    Vert.x中LocalDateTime类型转化报错问题
    【Linux】环境变量
    springboot 整合使用redis发布订阅功能
    BadNets: Identifying Vulnerabilities in the Machine Learning Model Supply Chain
  • 原文地址:https://blog.csdn.net/qq_52445967/article/details/126317509