HDMI

HDMI是数字视频接口,因此很容易从现代FPGA驱动。
让我们看看它是如何工作的。

连接器

标准的HDMI连接器称为“ A型”,有19个针脚。在19个引脚中,有8个需要特别关注,因为它们形成4个TMDS差分对来传输实际的高速视频信息。

  • TMDS时钟+和时钟-
  • TMDS的data0 +和data0-
  • TMDS数据1+和数据1
  • TMDS数据2+和数据2

我们从FPGA到HDMI连接器的连接几乎不那么简单…我们使用8个FPGA引脚配置为4个差分TMDS输出。

视频信号

让我们创建一个640×480 RGB 24bpp @ 60Hz视频信号。那就是每帧307200像素,并且由于每个像素具有24位(红色,绿色和蓝色为8位),频率为60Hz,因此HDMI链路传输0.44Gbps的“有用”数据。

但是视频信号通常也具有“屏幕外”区域,HDMI接收器(电视或监视器)将其用于一些内部管理。实际上,我们的640×480帧是作为800×525帧发送的。

考虑到这一点,我们需要24.5MHz的像素时钟来实现每秒60帧的速度,但是HDMI指定了25MHz的最小像素时钟,因此我们使用它(获得61Hz的帧频)。

TMDS信号

FPGA具有4个TMDS差分对来驱动。
首先,TMDS时钟只是像素时钟,因此它以25MHz运行。其他三对传输红色,绿色和蓝色信号,因此我们得到了类似的结果。

实际上事情要复杂一些。HDMI要求我们对数据进行加密并在每个色带中添加2位,因此我们只有10位而不是8位,并且链路最终每像素传输30位。HDMI接收器需要加扰和多余的位才能正确同步并获取每个通道-有关DVI和HDMI规范的更多详细信息。

源代码

首先是视频生成器。我们使用了两个计数器,它们经过800×525像素区域…

reg [9:0] CounterX;  // counts from 0 to 799
always @(posedge pixclk) CounterX <= (CounterX==799) ? 0 : CounterX+1;
 
reg [9:0] CounterY;  // counts from 0 to 524
always @(posedge pixclk) if(CounterX==799) CounterY <= (CounterY==524) ? 0 : CounterY+1;


并创建水平同步和垂直同步信号…

wire hSync = (CounterX>=656) && (CounterX<752);
wire vSync = (CounterY>=490) && (CounterY<492);
wire DrawArea = (CounterX<640) && (CounterY<480);


并生成一些红色,绿色和蓝色信号(每个8位)…

wire [7:0] red = {CounterX[5:0] & {6{CounterY[4:3]==~CounterX[4:3]}}, 2'b00};
wire [7:0] green = CounterX[7:0] & {8{CounterY[6]}};
wire [7:0] blue = CounterY[7:0];


通过三个“ TMDS_encoder”实例将其扩展为10位。

wire [9:0] TMDS_red, TMDS_green, TMDS_blue;
TMDS_encoder encode_R(.clk(pixclk), .VD(red  ), .TMDS(TMDS_red)  , .CD(2'b00)        , .VDE(DrawArea));
TMDS_encoder encode_G(.clk(pixclk), .VD(green), .TMDS(TMDS_green), .CD(2'b00)        , .VDE(DrawArea));
TMDS_encoder encode_B(.clk(pixclk), .VD(blue ), .TMDS(TMDS_blue) , .CD({vSync,hSync}), .VDE(DrawArea));


现在,对于每个像素时钟周期,我们将发送三个10位值。我们将25MHz时钟乘以10以生成250MHz时钟…

wire clk_TMDS, DCM_TMDS_CLKFX;
DCM_SP #(.CLKFX_MULTIPLY(10)) DCM_TMDS_inst(.CLKIN(pixclk), .CLKFX(DCM_TMDS_CLKFX), .RST(1'b0));
 
BUFG BUFG_TMDSp(.I(DCM_TMDS_CLKFX), .O(clk_TMDS));  // 250 MHz


并使用时钟频率为250MHz的三个移位寄存器…

reg [3:0] TMDS_mod10;  // modulus 10 counter
always @(posedge clk_TMDS) TMDS_mod10 <= (TMDS_mod10==9) ? 0 : TMDS_mod10+1;
 
reg TMDS_shift_load;
always @(posedge clk_TMDS) TMDS_shift_load <= (TMDS_mod10==9);
 
reg [9:0] TMDS_shift_red, TMDS_shift_green, TMDS_shift_blue;
always @(posedge clk_TMDS)
begin
    TMDS_shift_red   <= TMDS_shift_load ? TMDS_red   : TMDS_shift_red  [9:1];
    TMDS_shift_green <= TMDS_shift_load ? TMDS_green : TMDS_shift_green[9:1];
    TMDS_shift_blue  <= TMDS_shift_load ? TMDS_blue  : TMDS_shift_blue [9:1];	
end


将TMDS数据发送到FPGA外部。

OBUFDS OBUFDS_red  (.I(TMDS_shift_red  [0]), .O(TMDSp[2]), .OB(TMDSn[2]));
OBUFDS OBUFDS_green(.I(TMDS_shift_green[0]), .O(TMDSp[1]), .OB(TMDSn[1]));
OBUFDS OBUFDS_blue (.I(TMDS_shift_blue [0]), .O(TMDSp[0]), .OB(TMDSn[0]));
OBUFDS OBUFDS_clock(.I(pixclk), .O(TMDSp_clock), .OB(TMDSn_clock));


完整的资源在这里


更高的分辨率

对于640×480,我们使用250MHz时钟串行器,但是对于更高的分辨率,我们需要更高的频率,该频率可以很快超过FPGA的能力。解决方法是使用一些特殊的FPGA IO功能,例如DDR输出和IO串行器。

更高频率下的另一个问题是如何可靠地将数据从像素时钟域传输到串行器域。一种可能的技术是使用浅FIFO。查看Xilinx XAPP460(对于Spartan-3A)和XAPP495(对于Spartan-6)应用笔记,以获取一些想法。

屏幕截图

以下是使用数码相机拍摄由Pluto-IIx HDMI驱动的LCD监视器的一些照片。
我们有乒乓球比赛 …

有趣的是,经典的PacMan街机游戏…以前可以从fpgaarcade.com获得,但是该网站最近经过了重新设计。您仍然可以使用wayback machine获取原始资源。

这是我们的测试板的图片(Pluto-IIx HDMI装有可选的HDMI适配器-因此我们实际上有两个HDMI输出可用于…)。

链接