之前, 实现 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