DDR3(8):添加UART发送和HDMI显示

社会主义新天地 提交于 2020-12-19 18:54:17

  前期工作完成后,就要设计发送和显示了,本次设计采用 UART进行发送和 HDMI 进行显示。

 一、总体框架

 

二、PC端UART

  用串口助手即可,网上很多这种软件,发送时注意一下波特率、停止位和 hex 格式。图片数据的产生采用 Matlab 软件,将图片转换成 RGB565 的 16bit 像素数据,每个像素数据用两个 8bit 数据表示,串口发送两个数据表示一个像素点。

 

三、uart_rx

  之前的博客已经详细介绍了串口的发送和接收,此处不再赘述。

 

四、8转128bit

1、方法1

本来的设想是串口过来的 8bit 数据直接转成 128bit 的数据,后来发现 FIFO 不给力,写 8bit,读只能最多选 64bit,因此先代码设计一下 8bit 转 16bit,再用 FIFO实现 16bit 转 128 bit。这里 FIFO 除了转bit位数,还一个功能是跨时钟域。此外设计读使能时需注意,因为 user_wr_ctrl 模块里设计的突发长度是64,因此设计 FIFO 时需要注意两点:一是FIFO内的数据个数必须大于64才能启动读,也就是用户写;二是读使能64次后关闭读使能,又回到第一步判断是否能启动读。如此反复。

//==========================================================================
//==    16bit转128bit的FIFO读使能
//==========================================================================
always @(posedge sclk) begin
    if(rst) begin
        fifo_rd_cnt <= 0;
    end
    else if(add_fifo_rd_cnt) begin
        if(end_fifo_rd_cnt)
            fifo_rd_cnt <= 0;
        else
            fifo_rd_cnt <= fifo_rd_cnt + 1;
    end
end

assign add_fifo_rd_cnt = fifo_rd_en;
assign end_fifo_rd_cnt = add_fifo_rd_cnt && fifo_rd_cnt== BURST_LEN-1; //64

always @(posedge sclk) begin
    if(rst) begin
        fifo_rd_en <= 1'b0;
    end
    else if(rd_data_count>=BURST_LEN) begin //FIFO内数据个数大于64突发长度则启动读
        fifo_rd_en <= 1'b1;
    end
    else if(end_fifo_rd_cnt) begin          //读完64突发个数则先暂停
        fifo_rd_en <= 1'b0;
    end
end

//==========================================================================
//==    输出
//==========================================================================
assign user_wr_en   = fifo_rd_en;
assign user_wr_data = fifo_rd_data;

 

2、方法2

  如果左右时钟相同,那就可以不做时钟域处理,直接用移位寄存器做 8bit 转 128bit 即可,用移位寄存器写的代码,输出使能和 128bit 数据都是一个个的,每个都会让 user_wr_ctrl 模块启动读命令,这样的话,之前的 user_wr_ctrl 的突发长度就得改成 1。

//==========================================================================
//==    方式二:直接拼接128bit,后面user_wr_ctrl的突发长度 BURST_LEN 须改成1
//==========================================================================
reg   [ 5:0]                cnt                 ;
wire                        add_cnt             ;
wire                        end_cnt             ;
always @(posedge sclk) begin
    if(rst)
        cnt <= 0;
    else if(add_cnt) begin
        if(end_cnt)
            cnt <= 0;
        else
            cnt <= cnt + 1;
    end
end

assign add_cnt = rx_data_vld;
assign end_cnt = add_cnt && cnt== 16-1;

always @(posedge sclk) begin 
    if(rst) begin
        user_wr_en <= 'd0;
    end
    else if(end_cnt) begin
        user_wr_en <= 'd1;
    end
    else begin
        user_wr_en <= 'd0;
    end
end

always @(posedge sclk) begin
    if(rst) begin
        user_wr_data <= 'd0;
    end
    else if(add_cnt) begin
        user_wr_data <= {user_wr_data[119:0],rx_data};
    end
end

 

五、缓存处理

1、缓存介绍

  在做FPGA设计中,经常会遇到不同频率、相位的时钟需要控制同一组数据,此类不同频率、相位的时钟控制的数据,我们称为跨时钟域处理,如果跨时钟域的数据不经过处理,可能会出现数据部分丢失和增多的可能性。

  如图所示,clk2 和 clk3 采集由时钟 clk1 产生的数据 data1,分别得到 data2 和 data3,可以看出由于产生 data2 的时钟 clk2 比 clk1 快,因此 data2 会比 data1 的每个数据保持多一个始终周期,而产生 data3 的时钟 clk3 比 clk1 慢,所以 data3 会比 data1 的数据少。
  我们把 DDR3 中原始的数据读出,并通过 HDMI 接头显示在显示器上,由于 DDR3 读出的数据所在的时钟域和 HDMI 的时钟域不
同,因此我们需要在 DDR3 和 HDMI 之间加入一个缓存器,进行数据的
跨时钟域处理,如图所示。
 
 
 
 
 
 

   由于缓存器的存储容量有限,因此我们不能无限制的将 DDR3 内的数据传输到缓存器内,但是也需要保证 HDMI 向缓存器要数据时,不能出现缓存器内数据量无法满足 HDMI 所需数据的问题,因此我们需要合理的使用该缓存器,使其能为 DDR3 和 HDMI 的数据解决跨时钟域的问题。可以举一个现实生活的例子解释该状况,我们可以将 DDR3 理解成水库,缓存器理解成蓄水池,HDMI 理解成用水的市民,由于蓄水池内部存储容量有限,因此如果将水库的水大量注入蓄水池,就会出现蓄水池溢出的情况,但是考虑到市民的用水,因此蓄水池内的水量又不能过少,最好的办法就是设定一个阈值,当蓄水池的水量低于阈值时,就开闸将水库的水注入蓄水池,当高于阈值时,就关闭水库的水闸,这样就能保证蓄水池的水量不会偏差阈值过多。根据市民的用水情况,设定一个合适的阈值,这样蓄水池就能很好的解决水库与市民用水之间的缓存。

 

2、缓存FIFO解读

  我们可以调取一个 FIFO 作为缓存器,将 DDR3 中的数据输入到该缓存 fifo 中,当 HDMI 端需要数据时可以从该 FIFO 中取即可。由于 HDMI 端每次至少会连续的需求一行的数据量,为了 HDMI 能够从 FIFO 中取出足够的数据,我们需要保证该缓存 FIFO 中存有的数据量不能少于 HDMI 所需求的一行数据,因此每当缓存器 FIFO 中的数据量低于 HDMI 所需的一行数据时,我们就应该启动 DDR3 的读,并将读出的数据输入到缓存器中。为了保证 HDMI 端能稳定的接收到像素信息,我们第一次读取缓存器 FIFO 时,需要在 FIFO 内存够至少一行数据的前提下进行,在此我们设定存够 1.5 行才开始读取 FIFO。FIFO 存够 1.5 行像素数据,并且 VGA 扫描到显示的有效区域,这两个条件是读取 FIFO 的必要条件。
  我们在此调取一个异步 FIFO,其中写入缓存 FIFO 的时钟应该与读取 DDR3 的时钟一致,读出缓存 FIFO的时钟应该与 VGA 所用的时钟一致。在调取该缓存 FIFO 时,FIFO 的写数据位宽应该与读出的 DDR3 的数据位宽一致,因此写数据位宽我们设定为 128bit.读出的数据需要传输到 VGA 端,因此读数据的位宽应该与 VGA 的像素位宽一致,即 24bit。由于之前所用的 VGA控制器的像素数据是 16bit 数据,可以直接输出 16bit 像素,再把 RGB 分开分别赋值给HDMI:  HDMI_R[7:0] = {R[4:0],3’d0}; HDMI_G[7:0]={G[5:0],2’d0};HDMI_B[7:0]={B[4:0],3’d0}; 这样就转换成 24bit 的 HDMI 像素。缓存 FIFO 我们设置成至少存储 1.5 行的像素点个数,而每个像素点 16bit,本次显示的分辨率为 1024*768,  (1024+512)*16 /128  ,因此我们调取的 FIFO 深度对于写端来说是 192 左右。在此我们设定 FIFO 的深度为 256 或 512 以及更大,我们这里设置 512,对于读端来说深度为 4096,为了知道 FIFO 内的数据量,还需要调取 FIFO 的写端 count。当 FIFO 内部数据量低于 1.5 行的 VGA 像素点时,我们就启动 DDR3 的读取,将读出的数据写入到 FIFO 内,在此给出一个使用状态机实现该功能的方式,如图所示。
  可以看到,当 FIFO 的内部数据 <192 时,状态会从 JUDGE 跳转到 RD,并启动 DDR3 的读,当用户端读结束时会产生 rd_end 信号, state 又从 RD 状态返回 JUDGE,为下一次判断 FIFO 内数据是否 <192 做准备。 当 FIFO 去读取 DDR3 时,可以将 user_rd_ctrl 模块的 user_rd_data_valid 作为 FIFO 的写使能,user_rd_data 作为 FIFO 的写数据。当读取 FIFO 数据时,则可以引入 VGA 模块的读数据和读使能,VGA模块之前博客说过,就不仔细说了。示意图如下所示:

  由于本次设计采用 HDMI,在 FIFO 缓存1.5 行像素数据前,如果 HDMI 就工作,有可能出现一些意想不到的错误,为严谨期间,也需将 HDMI 延时一段时间再启动,可以利用 FIFO 的 rd_data_count 来控制 HDMI 的复位信号,即可达到延时的目的。

 

六、HDMI部分

  HDMI 模块在另一篇博客《协议——HDMI》中已经详细叙述,此处不再赘述。HDMI 模块可以和 hdmi_buffer 模块封装成一个 hdmi_top 模块,方便后面的使用。如图所示:

 

七、上板查看现象

  在顶层端口出也不要忘了添加 HDMI 的接口,我一开始忘了这,怎么检查都差不多错误,无语了。既然要上板,那就得加上引脚约束,因为 DDR3 的引脚已经在设置 IP 时就填进去校验了,所以这里引脚约束只需要设置时钟、复位、 HDMI 和 UART 即可。

  最后生成 bit 文件,连接好各种接口线,bit 文件并下载到板卡中即可得到如下图像,HDMI 显示器亮了,并出现乱序的彩条。

   由 Matlab 软件将一副 1280x768 分辨率的图片生成TXT格式的图像数据,打开串口助手将数据发送出去,得到如下图像:

   成功!完结撒花!

 

参考资料:威三学院FPGA教程

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!