FPGA将图像从BRAM传送至DDR3再输出到HDMI接口的测试(附GitHub源码)

本项目在FPGA中将BRAM中存储的图像数据输入到片外DDR3芯片中,再从DDR3中将数据取出输送到FPGA的HDMI模块中进行显示,可为DDR3、HDMI相关设计提供参考。

GitHub源码:https://github.com/CY0807/BRAM_DDR3_HDMI

芯片型号:xc7a35tfgg484-2L

开发板:ALINX AX7035

开发环境:vivado 2019.1

1 概要

(1)组成:在顶层模块bram_ddr3_hdmi_test中包含了3个主要的模块:负责产生图像数据的img_data_generator、封装好的DDR3模块ddr3_top、HDMI接口模块hdmi_top。

(2)时钟域:工程主要涉及三方面的时钟域,与上述顶层模块包含的3个主要模块相对应:数据生成模块img_data_generator直接采用板载晶振的50MHz时钟、DDR3接口采用了Vivado官方给的MIG IP核进行初步封装,用户时钟为100MHz、HDMI接口按照1080P协议要求采用148.5MHz时钟,为行场同步信号时钟、742.5MHz为TMDS传输所需时钟。DDR3模块和HDMI模块的时钟都来源于锁相环IP核。

(3)数据流:数据产生模块img_data_generator每个时钟周期向DDR3模块发送一个像素的图像数据;HDMI模块在行场同步信号都为高时从DDR3模块中获取一个像素的图像数据;其中DDR3模块和数据产生模块之间、DDR3模块和HDMI模块之间都采用了异步不等位宽的FIFO进行跨时钟域数据传输。

(4)控制:由于DDR3的带宽远大于数据产生模块和HDMI模块的带宽之和,在DDR3模块内部采用了状态机来对DDR3的读写进行控制,采用分时复用的方法完成将图像数据写入DDR3的同时又能将数据输出到HDMI接口上,其中异步FIFO还起到着缓存的作用。

2. img_data_generator——数据生成模块

2.1 简要分析

模块头代码如下

module img_data_generator
(
    input sys_clk,
    input sys_reset_n,
    input data_req,
    output [15:0] data_img
);

上述代码中:

(1)时钟sys_clk与hdmi中行场信号的频率保持一致;采用1080P格式,其频率为148.5MHz,来源于锁相环IP核;

(2)复位sys_reset_n与顶层模块的复位一致,并与物理按键连接;

(3)data_req为从hdmi_top中接受到的数据请求信号;

(4)data_img为16位的RGB565的图像数据,在data_req的后一拍有效;其数据来源于img_data_generator模块中提前例化的BRAM模块;

由于BRAM大小的限制,没有足够空间存储1080P格式的图像(1920宽 * 1080高 * 16位宽),因此在实际操作中,将图像缩放5倍,即384宽 * 216高 * 16位大小的图像数据存储在BRAM中,并在屏幕中显示时,在宽和高两个方向将图像重复5次,即显示25个重复的图像。为实现这一目标需对BRAM的操作地址进行计算,具体见img_data_generator.v文件。

2.2 仿真波形

为验证数据生成模块有效性,工程中可见tb_img_data_generator.v仿真文件来检测img_data_generator的波形。

在仿真文件中,模拟了1080P的hdmi时序的信号,包括行场同步信号、行场有效信号、数据有效信号等,具体先见如下波形图,再作分析。

2

1

第一张图为仿真波形的整体图,可见:

(1)h_cnt和v_cnt分别为行列计数器;h_active和v_active分别为行列有效信号;

(2)数据有效信号video_active为h_active和v_active的逻辑与,data_req等效于video_active

在黄线处,数据首次有效,将波形图放大得到第二张图,其中data_img是在data_req后一拍有效的,其数据的正确性可以自行进行检验,在工程的cy_files文件夹中,存放着图像数据与相关处理代码:

(1)origin_img:为1366 * 768 的JPG原图像;

(2)384x216_img_bin_file:原图像通过Img2Lcd.exe软件生成的384x216大小的RGB565的bin文件;

(3)bin_2_bram.py:将bin文件转化为BRAM初始化的coe文件;

(4)384x216_img_bram_init_file:BRAM IP核的初始化文件;

3. hdmi_top——HDMI接口顶层模块

模块头代码如下:

module hdmi_top(
    input           video_clk,        //pixel clock and 5x pixel clock required for the video
    input           video_clk_5x,
    input           HDMI_reset_n,
    
    output [0:0]    HDMI_OEN,         //HDMI out enable
    output          TMDS_clk_n,       //HDMI differential clock negative
    output          TMDS_clk_p,       //HDMI differential clock positive
    output [2:0]    TMDS_data_n,      //HDMI differential data negative
    output [2:0]    TMDS_data_p,      //HDMI differential data positive
    
    output          data_req,         //data_in慢data_req一拍 
    input  [15:0]   data_in,           //RGB565 data
    output          new_frame
);

(1)video_clk、video_clk_5x按照HDMI 1080P的时序标准分别为148.5MHz、742.5MHz,来源于vivado生成的锁相环IP核;

(2)HDMI_reset_n是物理按键reset信号和锁相环locked信号的逻辑与;

(3)HDMI_OEN、TMDS信号是输出到HDMI物理接口上的信号;

(4)data_req、data_in与数据生成模型进行通信;data_req为请求数据信号,data_in在data_req有效的下一个时钟周期有效,为RGB565数据;

(5)new_frame信号高有效,表示开始新的一桢,为脉冲信号;

hdmi_top内部的模块则满足了hdmi 1080P的时序输出要求,其形式固定且开源资料较多,在此不进行介绍了。

4 DDR3顶层模块——ddr3_top

DDR3顶层模块的结构图如下所示:

总框图

DDR3顶层模块主要由三部分组成:

(1)MIG IP核:为Vivado软件官方提供的DDR3芯片控制器,AX7035板上配有一个Micron(美光)的2Gbit(256MB)的DDR3芯片,型号为MT41J128M16HA-125;DDR的总线宽度共为16bit;数据速率为800MHz,用户时钟为100MHz。

(2)ddr3_fifo_ctrl:包含两个异步FIFO,用于缓存和跨时钟域传输,其中wr_fifo接受来自数据生成模块的16bit的像素数据,提供给ddr3 128bit的数据;rd_fifo接收来自ddr3的128bit数据,将16bit的数据传输给hdmi模块。

(3)ddr3_rw:ddr3顶层模块的总控制器,主要由状态机组成,控制MIG核对wr_fifo的读取和对rd_fifo的写入;在ddr3_rw中,采用了突发读写的方式,本项目设置了突发长度burst_len为60个128bit的数据,当wr_fifo中的数据大于burst_len,则一次性将burst_len个数据从wr_fifo中读取到ddr3中;同理,当rd_fifo中的数据少于burst_len,则一次性将burst_len个数据写入到rd_fifo中;ddr3_rw为DDR3顶层模块的核心组件,详细代码见ddr3_rw.v,其状态机核心代码如下所示,参考了正点原子的达芬奇Pro的DDR3设计。

case(state_cnt)
            IDLE:begin
                if(init_calib_complete)
                    state_cnt <= DDR3_DONE ;
                else
                    state_cnt <= IDLE;
            end
            DDR3_DONE:begin
                if(wr_rst)begin   //当帧复位到来时,对寄存器进行复位
                    state_cnt <= DDR3_DONE;
                    wr_addr_cnt  <= 24'd0;	
                    app_addr_wr <= app_addr_wr_min_a;					
			    end    //当读到结束地址对寄存器复位
                else if(app_addr_rd >= app_addr_rd_max_a - 8)begin  
                        state_cnt <= DDR3_DONE;
                        rd_addr_cnt  <= 24'd0;      
                        app_addr_rd <= app_addr_rd_min_a; 
                        rd_end <= 1'b1;
                end	   //当写到结束地址对寄存器复位
                else if(app_addr_wr >= app_addr_wr_max_a - 8)begin  
                        state_cnt <= DDR3_DONE;
                        wr_addr_cnt  <= 24'd0;      
                        app_addr_wr <= app_addr_wr_min_a; 
                        wr_end <= 1'b1;
                end	                		    
                else if(wfifo_rcount >= wr_bust_len_a)begin  
                    state_cnt <= WRITE;              //跳到写操作
                    wr_addr_cnt  <= 24'd0;                       
                    app_addr_wr <= app_addr_wr;      //写地址保持不变
                end
                else if(raddr_rst_h)begin           //当帧复位到来时,对寄存器进行复位 
                    if(raddr_rst_h_cnt >= 11'd200 && ddr3_read_valid)begin  
                        state_cnt <= READ;         //保证读fifo在复位时不回写入数据
                        rd_addr_cnt  <= 24'd0;      
                        app_addr_rd <= app_addr_rd_min_a; 
                    end
                    else begin
                        state_cnt <= DDR3_DONE;
                        rd_addr_cnt  <= 24'd0;      
                        app_addr_rd <= app_addr_rd;                                
                    end                                
                end      
                else if(rfifo_wcount <= rd_bust_len_a && ddr3_read_valid )begin  
                    state_cnt <= READ;                              //跳到读操作
                    rd_addr_cnt <= 24'd0;
                    app_addr_rd <= app_addr_rd;      //读地址保持不变
                end
                else begin
                    state_cnt <= state_cnt;   
                    wr_addr_cnt  <= 24'd0;      
                    rd_addr_cnt  <= 24'd0;  
                    rd_end <= 1'b0;   
                    wr_end <= 1'b0;                                       
                end
            end    
            WRITE:   begin 
                if((wr_addr_cnt == (wr_bust_len_a - 1)) && 
                   (app_rdy && app_wdf_rdy))begin    //写到设定的长度跳到等待状态                  
                    state_cnt    <= DDR3_DONE;       //写到设定的长度跳到等待状态               
                    app_addr_wr <= app_addr_wr + 8;  //一次性写进8个数,故加8
                end       
                else if(app_rdy && app_wdf_rdy)begin   //写条件满足
                    wr_addr_cnt  <= wr_addr_cnt + 1'd1;//写地址计数器自加
                    app_addr_wr  <= app_addr_wr + 8;   //一次性写进8个数,故加8
                end
                else begin                             //写条件不满足,保持当前值     
                    wr_addr_cnt  <= wr_addr_cnt;
                    app_addr_wr  <= app_addr_wr; 
                end
            end
            READ:begin                      //读到设定的地址长度    
                if((rd_addr_cnt == (rd_bust_len_a - 1)) && app_rdy)begin
                    state_cnt   <= DDR3_DONE;          //则跳到空闲状态 
                    app_addr_rd <= app_addr_rd + 8;

                end       
                else if(app_rdy)begin               //若MIG已经准备好,则开始读
                    rd_addr_cnt <= rd_addr_cnt + 1'd1; //用户地址计数器每次加一
                    app_addr_rd <= app_addr_rd + 8; //一次性读出8个数,DDR3地址加8
                end
                else begin                         //若MIG没准备好,则保持原值
                    rd_addr_cnt <= rd_addr_cnt;
                    app_addr_rd <= app_addr_rd;
                end
            end
            default:begin
                state_cnt    <= IDLE;
                wr_addr_cnt  <= 24'd0;
                rd_addr_cnt  <= 24'd0;
            end
        endcase

此外,DDR3顶层模块还支持读写的乒乓操作、在HDMI新的一帧到来时对rd_fifo进行复位操作、也支持对wr_fifo的复位操作等等,在此不逐一介绍了。

最后附上1080P屏幕显示效果图:

0ce0580d737b55cafccd76d0c05f290

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值