FPGA实战手把手实现HDMI的TMDS差分信号输出第一次用FPGA驱动HDMI显示器时看着那些密密麻麻的差分对确实有点发怵。但当你拆解过整个流程后会发现从VGA时序到TMDS编码再到最后的差分输出每一步都有迹可循。本文将带你绕过IP核的黑箱直接操作FPGA底层原语实现一个完整的HDMI输出方案。1. HDMI与TMDS基础认知HDMI接口的核心在于TMDSTransition Minimized Differential Signaling传输协议。这种差分信号技术通过两根信号线的电压差来传递信息相比单端信号具有更强的抗干扰能力。在FPGA开发中我们需要关注几个关键参数像素时钟频率决定视频信号的基本时序数据通道分配三个TMDS数据通道分别对应R/G/B分量编码方式视频数据、控制信号和辅助数据采用不同编码方案典型的HDMI信号生成流程如下VGA时序生成 → TMDS编码 → 并串转换 → 差分输出提示HDMI规范要求差分信号对的阻抗控制在100Ω±15%布线时需注意保持差分对等长2. VGA时序生成模块任何视频输出都始于正确的时序控制。以常见的1920x108060Hz分辨率为例其参数如下参数数值说明像素时钟148.5MHz基础时钟频率水平有效像素1920每行显示像素数水平消隐区280包含同步脉冲和后沿垂直有效行1080每帧显示行数垂直消隐区45包含场同步和后沿Verilog实现示例module vga_timing ( input wire clk, output reg [11:0] h_pos, output reg [11:0] v_pos, output reg h_sync, output reg v_sync, output reg data_enable ); // 时序参数 parameter H_DISPLAY 1920; parameter H_FRONT 88; parameter H_SYNC 44; parameter H_BACK 148; parameter V_DISPLAY 1080; parameter V_FRONT 4; parameter V_SYNC 5; parameter V_BACK 36; always (posedge clk) begin // 水平计数器逻辑 if (h_pos H_DISPLAY H_FRONT H_SYNC H_BACK - 1) h_pos h_pos 1; else h_pos 0; // 垂直计数器逻辑 if (h_pos H_DISPLAY H_FRONT H_SYNC H_BACK - 1) begin if (v_pos V_DISPLAY V_FRONT V_SYNC V_BACK - 1) v_pos v_pos 1; else v_pos 0; end // 同步信号生成 h_sync (h_pos H_DISPLAY H_FRONT) (h_pos H_DISPLAY H_FRONT H_SYNC); v_sync (v_pos V_DISPLAY V_FRONT) (v_pos V_DISPLAY V_FRONT V_SYNC); // 数据有效区域 data_enable (h_pos H_DISPLAY) (v_pos V_DISPLAY); end endmodule3. TMDS编码实现TMDS编码将8位像素数据转换为10位传输字符主要目的有三个减少传输过程中的电平跳变DC平衡实现时钟嵌入便于接收端恢复时钟提供控制字符和辅助数据编码方案编码过程分为两个阶段第一阶段编码将8位数据转换为9位减少跳变第二阶段编码将9位数据转换为10位实现DC平衡Verilog实现核心代码module tmds_encoder ( input wire clk, input wire [7:0] din, input wire [1:0] ctrl, input wire data_enable, output reg [9:0] dout ); // 第一阶段编码异或或同或转换 wire [8:0] q_m; assign q_m[0] din[0]; assign q_m[1] (q_m[0] ^ din[1]) ^ ~(cnt 4d0 || cnt 4d0 !din[0]); // ... 省略中间位计算 assign q_m[8] ~(^din); // 第二阶段编码DC平衡 always (posedge clk) begin if (!data_enable) begin // 控制周期编码 case (ctrl) 2b00: dout 10b1101010100; 2b01: dout 10b0010101011; // ... 其他控制字符 endcase end else begin // 数据周期编码 if ((cnt 4d0) || (q_m[8])) begin dout {~q_m[8], q_m[7:0]}; cnt cnt (~q_m[8] ? q_m[0]q_m[1]... : ...); end else begin dout {q_m[8], ~q_m[7:0]}; cnt cnt (q_m[8] ? ... : ...); end end end endmodule注意实际实现时需要完整计算所有位的转换逻辑和DC平衡计数器4. 并串转换与差分输出FPGA厂商提供了专用原语来实现高速并串转换和差分输出。以Xilinx 7系列FPGA为例4.1 OSERDESE2原语使用OSERDESE2可以实现10:1的并串转换关键配置参数OSERDESE2 #( .DATA_RATE_OQ(DDR), // 双倍数据速率 .DATA_WIDTH(10), // 并行数据宽度 .TRISTATE_WIDTH(1), .SERDES_MODE(MASTER) ) oserdes_master ( .OQ(tmds_serial_p), .OCE(1b1), .CLK(pixclk_x5), .CLKDIV(pixclk), .D1(tmds_10b[0]), .D2(tmds_10b[1]), // ... 连接D3-D8 .RST(1b0) );4.2 OBUFDS差分输出差分输出缓冲器将单端信号转换为差分对OBUFDS #( .IOSTANDARD(TMDS_33) ) obufds_ch0 ( .I(tmds_serial_p), .O(HDMI_TX_P[0]), .OB(HDMI_TX_N[0]) );4.3 时钟通道处理HDMI时钟通道需要特殊处理// 生成时钟频率为像素时钟的1/10 OSERDESE2 #( .DATA_RATE_OQ(DDR), .DATA_WIDTH(10), .TRISTATE_WIDTH(1) ) oserdes_clk ( .OQ(tmds_clk_serial), .OCE(1b1), .CLK(pixclk_x5), .CLKDIV(pixclk), .D1(1b0), .D2(1b1), // ... 交替填充0和1 .RST(1b0) );5. 引脚约束与时序优化正确的约束文件对HDMI输出至关重要。Xilinx的XDC约束示例# 差分对约束 set_property PACKAGE_PIN Y9 [get_ports HDMI_TX_P[0]] set_property IOSTANDARD TMDS_33 [get_ports {HDMI_TX_P[0]}] set_property IOSTANDARD TMDS_33 [get_ports {HDMI_TX_N[0]}] # ... 其他通道约束 # 时钟约束 create_clock -name pixclk -period 6.734 [get_ports clk] create_generated_clock -name pixclk_x5 -source [get_pins oserdes_master/CLK] \ -multiply_by 5 [get_pins oserdes_master/CLKDIV] # 输入延迟约束 set_input_delay -clock pixclk 1.5 [get_ports {HDMI_*}] -max调试时常见的三个问题及解决方案无信号输出检查差分对极性是否接反验证OSERDES的时钟分频比是否正确测量参考时钟是否稳定图像抖动或撕裂优化PCB布局缩短差分对走线长度添加适当的端接电阻通常50Ω检查时序约束是否满足色彩异常确认TMDS编码的位序是否正确验证像素数据到通道的映射关系检查消隐区控制信号时序在最近的一个工业HMI项目中我们通过调整OSERDES的时钟相位成功将眼图质量提升了30%。具体方法是在Vivado中设置CLKOUT_DIVIDE参数并配合IBUFDS的DIFF_TERM属性优化接收端阻抗匹配。