FPGA学习——带axi_stream 接口的HDMI显示控制设计

之前, 实现 HDMI 一般使用的是 HDMI 发送芯片, 典型的例如 ADV7513、 sil9022、 CH7301, 使用这些芯片实现 HDMI 发送,本质上还是将 FPGA 输出的 24 位像素数据+3 位的控制信号(HSYNC、 VSYNC、 DE) 接入这些芯片,然后由这些芯片完成数据的编码和串行发送。

这样不仅增加了硬件复杂度,而且占用了较多FPGA的IO资源。为了在节约 IO 资源的同时实现 HDMI 发送, 采用 FPGA 实现HDMI 发送所需的 TMDS 编码和串行发送器。这种设计方案在 FPGA 内部实现了 HDMI 发送芯片的核心功能, 并最终直接使用 FPGA 管脚输出符合 HDMI 协议规范的 HDMI 链路信号。

本文先是介绍说明了常规HDMI接口模块的设计和使用,最后根据实际情况将其升级成带有AXI_stream 接口的hdmi模块,可与VDMA接口直接连接,便于使用。

Hdmi IP v1.0使用说明

1.设计概述

• 用于hdmi接口,驱动显示24位的RGB视频图像

• 基于vivado18.3软件设计

2.端口说明

图1.Hdmi IP端口示意图

注:

(1)输入时钟信号。

pixelclk根据显示分辨率确定,不同的分辨率使用不同的时钟频率,pixelclk5x的值则是pixelclk的5倍。例如显示分辨率为1280*720时,pixelclk为74.25MHz,pixelclkx5为371.25MHz;显示分辨率为800*600时,pixelclk为40MHz,pixelclkx5为200MHz。根据显示需求配备相应的驱动时钟,同时需注意显示设备是否支持所设置的分辨率。

(2)输入使能信号。

vid_pVDE为外部输入的使能控制信号,使能为高平时,编码器将8 位像素数据编码成 10 数据并转换 TMDS 序列输出。在本设计中,使能信号由外部视频图像时序控制逻辑产生。

3.模块划分

此HDMI驱动模块实质是一个DVI编码传输模块,包含2个逻辑部分,一个是编码模块,一个是串行发送模块。

编码模块采用直流平衡编码方式,将输入信号编码成一个连续的 10bit TMDS 字符流再经过由5倍时钟速率驱动的串行化发送模块发送至显示端口。在顶层中,编码模块被例化3次,分别对RGB的red、green、blue分量进行编码。下图为整个设计的框架图。

 图2.Hdmi IP框架图

在串行化发送模块中使用了双数据速率发送模式(DDR),这需要使用vivado的原语来实现。原语查找如下图。

 图3.vivado中的ODDR原语

在模块中有如下形式的调用。将编码好的待发送的数据的高低位分别加载到ODDR的上升沿数据输入端和下降沿数据输入端,在5倍的时钟驱动下既能将10bit数据同步输出。 要注意的是,此次ODDR原语的引用是基于Xilinx开发工具的,在其他FPGA开发工具中将是不同的形式。   

ODDR #(

    .DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"

    .INIT(1'b0),    // Initial value of Q: 1'b0 or 1'b1

    .SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"

  ) ODDR_1 (

    .Q (dataout_1        ),// 1-bit DDR output

    .C (clkx5            ),// 1-bit clock input

    .CE(1'b1             ),// 1-bit clock enable input

    .D1(TMDS_shift_1h[0] ),// 1-bit data input (positive edge)

    .D2(TMDS_shift_1l[0] ),// 1-bit data input (negative edge)

    .R (1'b0             ),// 1-bit reset

.S (1'b0             ) // 1-bit set 

);

另外, 对于 TMDS,其传输时使用的是差分传输方式,既对每一个通道都使用 2 根信号线, 两根信号线传输的电平刚好相反,所以在最终输出时,需要再使用 Xilinx 的 OBUFDS 原语,用来将上述 ODDR 模块输出信号转成差分信号输出。 

OBUFDS #(

    .IOSTANDARD("DEFAULT"), // Specify the output I/O standard

    .SLEW("SLOW")           // Specify the output slew rate

  ) OBUFDS_0 (

    .O  (dataout_0_p ),// Diff_p output (connect directly to top-level port)

    .OB (dataout_0_n ),// Diff_n output (connect directly to top-level port)

.I  (dataout_0   ) // Buffer input

);

4.使用说明

(1)在纯FPGA开发中,需要结合VGA控制器来产生视频图像时序。这种情况下,只将传统的直接输出到 FPGA 管脚的VGA控制器的输出信号接到了此设计的对应输入接口,即可实现 HDMI 接口的图像发送这样不会改变原有图像显示系统的框架结构,只是对最终输出的信号多加入了一级变换处理。 

图4.VGA控制逻辑与Hdmi IP的连接

(2)在进行vivado的 IP INTEGRATOR 开发时,可使用vivdao自带的 Video Timing Controller IP 产生视频图像控制时序,完成视频图像的显示。如下图所示,可以在Video Timing Controller IP的GUI中可以选择需要的显示分辨率。 

图5.Video Timing Controller IP的参数设置

下图是基于HDMI接口的图像显示系统。其中,AXI4_Stream to Video Out ip核是vivado ip库自带的,其作用是将相关的AXI4_Stream形式的数据与时序信号转化成视频信号输出。将Hdmi IP核的输入端口和AXI4_Stream to Video Out ip核的对应输出端口连接,最后再引出输出端口连接硬件。

 图6.Hdmi IP在基于HDMI接口的图像显示系统中的连接

上述是常规的hdmi模块,在zynq中结合VDMA使用时需要增加Video Timing Controller IP、AXI4_Stream to Video Out ip这两个IP核。如下文,对此进行了升级,将VGA时序及axis接口一同封装在hdmi模块中,使其可以和VDMA直连,简化了整体设计。

在顶层中添加了axis相关的端口,例化了disp模块用于产生VGA时序。

module hdmi_top #(  parameter RGB_select=24,
                    parameter Dout_width=3,
                    parameter BUFFER_DEPTH = 4096,
                    parameter VGA_select="1280x720"
)
(
//******************axis端口有**************************************
    input                                      s_axis_aclk,     // AXI4-Stream clock
	input                                      s_axis_aresetn,  // AXI4-Stream reset, active low 
	input [23:0]                               s_axis_video_tdata,    // AXI4-Stream data
	input                                      s_axis_video_tvalid,   // AXI4-Stream valid 
	output                                     s_axis_video_tready,   // AXI4-Stream ready 
	input                                      s_axis_video_tuser,    // AXI4-Stream tuser (SOF)
	input                                      s_axis_video_tlast,    // AXI4-Stream tlast (EOL)
	//input[1:0]                               s_axis_video_tkeep,     // AXI4-Stream tkeep

//*******************内部逻辑端口***********************************
	input  clk_vga,                   //vga控制模块驱动时钟
//	input  rst_p,             
	input  clk_vgax5,                 //5倍时钟输入
		   //显示数据输入
	output DataReq,
	output Disp_PCLK,                      //vga控制模块驱动时钟输出
	output [11:0] H_Addr,
    output [11:0] V_Addr,
	//hdmi output
	output tmds_clk_p,                //hdmi时钟输出
	output tmds_clk_n,                //hdmi时钟输出
	output [Dout_width-1:0]tmds_data_p,    //rgb输出
	output [Dout_width-1:0]tmds_data_n     //rgb输出
);
   (* mark_debug="true" *)  wire [RGB_select-1:0]disp_data;
	wire        video_hs;
	wire        video_vs;
	wire        video_de;
	wire [7:0]  video_r;
	wire [7:0]  video_g;
	wire [7:0]  video_b;
	wire        Frame_Begin;
	wire tlast;
	wire tuser;
	wire full;
	wire empty;
	wire rst_p=~s_axis_aresetn;
	//wire fifo_ready;
	//assign fifo_ready=~full;
	assign s_axis_video_tready=~full;
 reg fifo_en;
  always@(negedge s_axis_video_tuser )
  fifo_en<=1;
  //always@(posedge s_axis_video_tuser )
  //fifo_en<=0;
  
	xpm_fifo_async # (

  .FIFO_MEMORY_TYPE          ("auto"),           //string; "auto", "block", or "distributed";
  .ECC_MODE                  ("no_ecc"),         //string; "no_ecc" or "en_ecc";
  .RELATED_CLOCKS            (0),                //positive integer; 0 or 1
  .FIFO_WRITE_DEPTH          (BUFFER_DEPTH),     //positive integer
  .WRITE_DATA_WIDTH          (26),               //positive integer
  .WR_DATA_COUNT_WIDTH       (12),               //positive integer
  .PROG_FULL_THRESH          (10),               //positive integer
  .FULL_RESET_VALUE          (0),                //positive integer; 0 or 1
  .USE_ADV_FEATURES          ("0707"),           //string; "0000" to "1F1F"; 
  .READ_MODE                 ("fwft"),            //string; "std" or "fwft";
  .FIFO_READ_LATENCY         (0),                //positive integer;
  .READ_DATA_WIDTH           (26),               //positive integer
  .RD_DATA_COUNT_WIDTH       (12),               //positive integer
  .PROG_EMPTY_THRESH         (10),               //positive integer
  .DOUT_RESET_VALUE          ("0"),              //string
  .CDC_SYNC_STAGES           (2),                //positive integer
  .WAKEUP_TIME               (0)                 //positive integer; 0 or 2;

) xpm_fifo_async_inst (

      .rst              (~s_axis_aresetn),
      .wr_clk           (s_axis_aclk),
      .wr_en            (~full&fifo_en&s_axis_video_tvalid),                                                          //  仔细阅读ov5640发现, tuser和第一个有效数据联系紧密,计划利用tuser控制fifo写使能
      .din              ({s_axis_video_tdata,s_axis_video_tlast,s_axis_video_tuser}),
      .full             (full),
      .overflow         (),
      .prog_full        (),
      .wr_data_count    (),
      .almost_full      (),
      .wr_ack           (),
      .wr_rst_busy      (),
      .rd_clk           (clk_vga),
      .rd_en            (~empty&video_de),
      .dout             ({disp_data,tlast,tuser}),
      .empty            (empty),
      .underflow        (),
      .rd_rst_busy      (),
      .prog_empty       (),
      .rd_data_count    (),
      .almost_empty     (),
      .data_valid       (),
      .sleep            (1'b0),
      .injectsbiterr    (1'b0),
      .injectdbiterr    (1'b0),
      .sbiterr          (),
      .dbiterr          ()

);
	
//检测tuser脉冲信号
 /*(* mark_debug="true" *)wire tuser_plus;
 //reg tuser_plus_de=0;
reg tuser0;
always@(posedge clk_vga or posedge rst_p)
 if(rst_p)
    tuser0<=0;
  else
    tuser0<=tuser;
    always@(posedge tuser_plus )
    tuser_plus_de<=1;
assign tuser_plus=!tuser&tuser0;*/

	disp_driver #(RGB_select,VGA_select   //通过顶层模块中的RGB_select/VDA_select参数改写disp_driver模块中的参数
	)disp_driver0 
	( 
	    .ClkDisp (clk_vga),                                                                                                                                                                                                                                  
		.Rst_p(rst_p),
		.Sweep_de(fifo_en),
		.Data(disp_data),
		.DataReq(DataReq),
		.H_Addr(H_Addr),
		.V_Addr(V_Addr),
		.Disp_HS(video_hs),
		.Disp_VS(video_vs),
		.Disp_Red(video_r),
		.Disp_Green(video_g),
		.Disp_Blue(video_b),
		.Disp_DE(video_de),
		.Disp_PCLK(Disp_PCLK),
		.Frame_Begin(Frame_Begin)
	);

	dvi_encoder u_dvi_encoder (
		.pixelclk      	(clk_vga),// system clock
		.pixelclk5x    	(clk_vgax5),// system clock x5
		.Rst_n         	(!rst_p),// reset
		.vid_pData ({video_r[7:0],video_g[7:0],video_b[7:0]}),
		.vid_pHSync     (video_hs),// hsync data
		.vid_pVSync     (video_vs),// vsync data
		.vid_pVDE       (video_de),// data enable
		.TMDS_CLK_p   	(tmds_clk_p),
		.TMDS_CLK_n   	(tmds_clk_n),
		.TMDS_DATA_p   	(tmds_data_p),//rgb
		.TMDS_DATA_n   	(tmds_data_n) //rgb
	);
	
endmodule

猜你喜欢

转载自blog.csdn.net/weixin_41895751/article/details/122636283
今日推荐