ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA实战:RGB转TMDS编码原理与HDMI接口实现全解析

FPGA实战:RGB转TMDS编码原理与HDMI接口实现全解析 第一次在FPGA上调HDMI输出我对着黑屏显示器坐了三个小时。逻辑分析仪显示RGB数据和同步信号全对仿真波形也看不出问题直到我把TMDS编码器的最终输出抓下来才意识到自己对“数据编码”的理解有多粗糙——我直接把8位像素按原样送了进去根本没有做8b到10b的变换。TMDSTransition Minimized Differential Signaling过渡最小化差分信号这套数据编码算法是HDMI和DVI物理层的底层基础RGB转TMDS则是FPGA上做视频输出的必经之路。这篇文章把我踩过的坑和整套算法的细节一次说清楚适合正在调FPGA视频接口、或者想彻底搞明白HDMI底层原理的开发者。1. 为什么视频传输偏偏要用TMDS这套编码1.1 从DVI到HDMITMDS的身世TMDS来自Silicon Image公司1999年随DVI 1.0标准发布2002年HDMI 1.0直接继承了这套物理层编码。直到HDMI 2.0时代TMDS都还是绝对主力——1080p60Hz下单通道数据率1.485Gbps4K60Hz需要跑到6Gbps底层编码算法本质上没变过。只有到了HDMI 2.1才全面换用FRLFixed Rate LinkTMDS逐步退居兼容模式。所以现在市面上能买到的绝大多数HDMI设备里面跑的仍然是TMDS。你在FPGA上做一个RGB转TMDS的发送器就相当于手工实现了一个简化版HDMI发送端的核心。1.2 三个绕不开的工程问题任何高速串行传输都要面对三个基础问题TMDS的编码设计完全是围绕它们展开的。第一个是直流平衡。视频数据不是均匀的随机信号一张纯白画面的像素全是0xFF一张纯黑画面全是0x00。如果直接把这些原始数据串行发出去线路上的平均电平会长时间偏向高或低接收端没法做交流耦合也无法稳定恢复时钟。TMDS解决方式是引入“运行差异”计数器保证任意一段码流中1和0的数量长期趋于相等。第二个是电磁干扰和信号完整性问题。如果数据流中0→1和1→0的翻转非常频繁且随机高速差分线上的辐射会变大串扰也会变严重。TMDS的阶段一编码专门做了“过渡最小化”让输出码流中相邻位的翻转次数尽量少。第三个是时钟恢复。HDMI链路上除了专门传像素时钟的通道之外三路数据通道本身不带独立时钟接收端需要从数据边沿恢复出位时钟。这就要求数据流中必须有足够多的跳变沿不能出现长时间不变的电平。TMDS的编码规则保证了即使数据全0或全1输出码流也始终有节奏地翻转。1.3 一条TMDS链路长什么样标准TMDS链路有4对差分线3对数据通道加1对时钟通道。RGB三个颜色分量各占一条数据通道每个像素时钟周期传输一个10位的TMDS符号。时钟通道传输的就是像素时钟本身接收端以它为参考重建整个时序。数据通道里传输的内容分成两类DE有效时传视频像素DE无效时传控制信号比如HSYNC和VSYNC。视频像素经过完整的两阶段8b/10b编码控制信号则直接映射成固定10位模式。DE信号本身不会单独占用一根线它是通过码流中标志位的不同来区分的——这一点在FPGA实现时尤其容易混淆。2. 编码算法逐拍拆解8位数据如何变成10位符号2.1 阶段一过渡最小化Transition Minimization对每个8位像素数据D[7:0]先计算一组“前缀异或”序列N[7:0]N[0] D[0] N[i] N[i-1] ^ D[i] i 1..7然后统计N[0]到N[6]这7位中1的个数。如果1的个数大于4或者等于4且D[0]为0就判定为“需要反转”否则不反转。这个决策条件看起来有点绕实际上是统计数据中的“位分布倾向”决定q_m采用正常序还是反相序目的是让后续输出的相邻位翻转尽量少。根据决策结果生成q_m[7:0]if (decision 0) q_m[i] q_m[i-1] ^ N[i] else q_m[i] q_m[i-1] ^ ~N[i]注意这里q_m[0]始终等于D[0]逐位递推而不是整体取反。这个细节非常关键网上很多简化代码直接写q_m ~N或q_m N看起来能用实际上生成的码字不符合标准推导接收端按标准反解时可能还原出错位。我拿一个真实数据走一遍。假设D 0xB1即二进制10110001bit7到bit0依次为1、0、1、1、0、0、0、1N[0]1 N[1]1^01 N[2]1^01 N[3]1^01 N[4]1^10 N[5]0^11 N[6]1^01 N[7]1^10N[7:0] 011101110x77统计N[0]到N[6]得6个1大于4决策为1。然后递推q_m[0]1 q_m[1]1 ^ ~1 0 q_m[2]0 ^ ~1 0 q_m[3]0 ^ ~1 0 q_m[4]0 ^ ~0 1 q_m[5]1 ^ ~1 0 q_m[6]0 ^ ~1 0 q_m[7]0 ^ ~0 1得到q_m 100001110x87。整个过程必须按位递推不能用简单取反替代这是我对这个算法最深的体会之一。2.2 阶段二直流平衡DC Balancingq_m只有8位要变成10位还差2位。这2位不只是补位而是承载了直流平衡的核心逻辑。设q_m中1的个数为k则k的范围是0到8。编码器内部维护一个运行差异计数器cnt记录“我已经累计发送了多少个多余的1”。每次编码后cnt会更新目标是让cnt尽量回到0附近。规则分三种情况如果cnt为0或者k等于4本次本身平衡输出q_m或~q_m具体看cnt的符号cnt为正说明之前1多本次选~q_m来抵消cnt为负则选q_m然后cnt按本次实际差异更新如果k为4则cnt清零。如果cnt为正且k大于4或者cnt为负且k小于4说明本次的方向和累计偏差方向相同需要反转q_m输出来对冲cnt减去本次正差异。其余情况直接输出q_mcnt加上本次差异。这里的“差异”等于2k-81的个数减去0的个数每次更新后cnt理论上会向0回归长期看线路上的直流电平稳定。标准中cnt一般限制在±4范围内实际实现时建议用5位有符号数避免极端情况下溢出。接收端拿到10位符号后先看bit8判断是不是数据符号再看bit9决定是否把后8位取反恢复出q_m再反推原始数据。整个链路是双向可逆的这正是TMDS编码设计严谨的地方。2.3 控制字符DE为0时发什么DE为0时不传像素传控制信号。HDMI的每条数据通道在控制期有2位控制输入映射成固定的10位编码。常用映射如下控制输入ctrl[1:0]10位输出001101010100010010101011100101010100111010101011这些控制码本身大多接近DC平衡接收端看到这类模式就知道当前处于消隐区同步信号的边沿就通过这些码字的切换来传递。实际工程中HSYNC和VSYNC通常映射到蓝色通道的ctrl[1:0]上RGB三通道分配哪一路由设计者决定不同HDMI发送芯片的映射可能有差异。提示控制字符的具体位序和极性在不同资料里存在差异。我做项目时习惯直接以HDMI规范中的CTLx编码表为准HDL代码里用常量表形式给出调试时优先比对示波器上的波形而不是凭记忆核对码值。2.4 解码端视角怎样才能完整还原把编码算法理解透最好的检验方式是站在接收端想一遍。接收端收到10位符号后检查bit8。如果为1表示这是视频数据如果为0说明是控制符号查表还原出2位控制信号。对于视频符号根据bit9判断信号极性恢复出8位q_m。从q_m反推原始数据Dq_m[0]就是D[0]后面每一位根据阶段一的递推关系进行逆运算。接收端的反推不需要知道发送端当时decision的值因为q_m本身就携带了全部信息。这也是为什么发送端必须严格按标准递推生成q_m任何简化取反都会让接收端还原得牛头不对马嘴。3. FPGA落地RGB转TMDS的完整实现路径3.1 顶层架构FPGA上做RGB转TMDS发送器整体分四块像素源、TMDS编码器、并行转串行、时钟生成。像素源输出24位RGB、HSYNC、VSYNC、DE和像素时钟。三个颜色通道各接一个TMDS编码器每个编码器输出10位符号。然后三个10位符号同步进入串行化模块变成3对高速差分数据。时钟通道单独处理把像素时钟直接通过DDR输出原语变成一对差分时钟。我画过不少版本最核心的教训是编码器工作在像素时钟域串行化模块工作在5倍像素时钟域这两个时钟域之间的数据交接必须安排得明明白白。很多黑屏问题不是编码错而是时序跨域没处理好。3.2 核心编码器的Verilog实现下面是一个可直接综合的TMDS编码器模块。这个模块我至少用了三年从720p到1080p都跑过逻辑本身没问题module tmds_encoder ( input wire clk, input wire [7:0] data_in, input wire [1:0] ctrl_in, input wire de_in, output reg [9:0] tmds_out ); reg [7:0] n; // 前缀异或序列 reg [7:0] q_m; // 过渡最小化后的8位 reg [3:0] ones_n; // N[0]..N[6]中1的个数 reg decision; integer i; // 阶段一计算N序列 always (*) begin n[0] data_in[0]; for (i 1; i 8; i i 1) n[i] n[i-1] ^ data_in[i]; end // 统计N[0]..N[6]中1的个数注意是7位 always (*) begin ones_n 4d0; for (i 0; i 7; i i 1) ones_n ones_n n[i]; end // 决策1的个数4或4且D[0]0时反转 always (*) begin decision (ones_n 4d4) || ((ones_n 4d4) ~data_in[0]); end // 递推生成q_m always (*) begin q_m[0] data_in[0]; for (i 1; i 8; i i 1) q_m[i] q_m[i-1] ^ (decision ? ~n[i] : n[i]); end // 阶段二直流平衡 reg signed [4:0] cnt; // 运行差异计数器 reg [3:0] ones_q_m; // q_m中1的个数 wire [4:0] diff_qm; // 本次q_m对应差异 2k-8 always (*) begin ones_q_m 4d0; for (i 0; i 8; i i 1) ones_q_m ones_q_m q_m[i]; end assign diff_qm {1b0, ones_q_m, 1b0} - 5d8; // 等价于2*k-8 always (posedge clk) begin if (de_in) begin if (cnt 0 || ones_q_m 4) begin // cnt非0且本次平衡时按cnt符号选择极性 if (cnt 0) begin tmds_out[9] 1b1; tmds_out[8] 1b1; tmds_out[7:0] q_m; end else begin tmds_out[9] 1b0; tmds_out[8] 1b1; tmds_out[7:0] ~q_m; end if (cnt 0) cnt diff_qm; else cnt 0; end else if ((cnt 0 ones_q_m 4) || (cnt 0 ones_q_m 4)) begin tmds_out[9] 1b0; tmds_out[8] 1b1; tmds_out[7:0] ~q_m; cnt cnt - diff_qm; end else begin tmds_out[9] 1b1; tmds_out[8] 1b1; tmds_out[7:0] q_m; cnt cnt diff_qm; end end else begin case (ctrl_in) 2b00: tmds_out 10b1101010100; 2b01: tmds_out 10b0010101011; 2b10: tmds_out 10b0101010100; 2b11: tmds_out 10b1010101011; default: tmds_out 10b1101010100; endcase cnt 0; end end endmodule代码里有几个点必须单独说。diff_qm的计算我用移位减法的写法是为了避免综合器把它优化出问题实际等价于2*k - 8。统计ones_n时标准只统计7位不是8位这个细节曾经让我花了一晚上对比标准文档。控制字符的case里我把默认分支也写上了防止综合后产生锁存器。3.3 并行转串行DDR与OSERDES的取舍10位并行数据要变成1对高速差分信号关键是串行化怎么做。我整理过三种常见方案方案时钟需求资源占用适用场景OSERDESE2 10:1 SDR10倍像素时钟少需级联主从高分辨率速率要求高OSERDESE2 5:1 DDR5倍像素时钟少1080p附近最常用移位寄存器ODDR5倍像素时钟中等逻辑实现低端FPGA教学验证以1080p60Hz像素时钟148.5MHz为例数据率1.485Gbps。7系列FPGA的OSERDESE2在DDR模式下用742.5MHz时钟即可驱动这个频率在器件规格内。如果用10倍时钟做SDR1.485GHz的全局时钟布线压力会非常大一般不推荐。DDR模式的实现逻辑很直观10位符号拆成高5位和低5位每个5倍时钟周期内上升沿输出一位、下降沿输出一位5个时钟周期刚好把10位发完。OSERDESE2原语本身支持这种用法Xilinx的SelectIO IP也封装好了。Altera平台对应的是ALTDDIO_OUTLattice平台用DDR输出寄存器加逻辑移位。不管什么平台核心思想一致5倍时钟做DDR传输数据通道和时钟通道之间做相位对齐。3.4 时钟方案5倍像素时钟与相位对齐时钟是整个发送链路的心脏。我的经验是先确定像素时钟再用MMCM/PLL生成5倍频时钟720p60Hz像素时钟74.25MHz5倍时钟371.25MHz1080p60Hz像素时钟148.5MHz5倍时钟742.5MHz1080p50Hz像素时钟148.5MHz实际是148.5/1.001做开发板功能验证时不纠结时钟通道输出的像素时钟本身通过ODDR原语固定输出模式实现差分对一个DDR寄存器在上升沿输出1、下降沿输出0这样每两个TMDS位周期产生一个完整时钟周期正好一个像素时钟周期内有10个位周期。数据通道和时钟通道之间的相位关系是重点。我遇到过画面能点亮但偶尔闪一下的情况最后定位到是数据通道的串行边界和时钟通道边沿偏离了理想位置。解决方法是利用MMCM的fine phase shift微调串行时钟相位或者在数据通道上插入可调的IDELAY。具体调整值要靠扫相位实测没有一次到位的捷径。3.5 硬件连接电平标准与PCB注意事项FPGA的IO标准直接决定信号能不能被显示器正确识别。TMDS是电流型差分信号规范要求共模电平约3.3V、差分摆幅约±10mA电流在50欧端接上产生约500mV摆幅。老一点的FPGA方案里直接支持TMDS_33 IO标准的器件不少。Vivado里给差分端口分配TMDS_33即可这时驱动能力最接近规范。如果器件只支持LVDS_25也可以点起来但驱动幅度偏低短距离几十厘米没问题线长了就掉链子。我在实际开发板上经常碰到的情况是FPGA差分IO直接连HDMI座子IO标准配LVDS_25。这种做法做验证完全够用但不适合做产品——要么加TMDS电平转换芯片比如TFP410、SIL171要么选用原生支持TMDS电平的FPGA bank。PCB布局走线要注意三件事四对差分线尽量等长对内P/N偏差控制在50密耳以内差分阻抗控制在100欧电源去耦要做好高速切换时IO电源纹波会影响信号质量。我在一块手工焊接的板上踩过坑VCCIO波纹稍微大一点屏幕就开始出横纹。4. 调试实录从黑屏到点亮的排查链路4.1 先仿真编码器输出对不对上板之前一定先仿真这是我能给出的最诚恳的建议。仿真用例不要用随机数据直接用彩条测试图样这样可以直观判断输出码流是否合理。仿真重点看三件事。第一DE为0时输出是否是固定的控制编码表里的值且每两个像素时钟周期切换一次。第二DE为1时10位输出的bit8必须是1。第三跑几百个像素周期后看cnt是否保持在合理范围如果cnt一直往一个方向跑偏说明直流平衡逻辑有bug。我习惯在testbench里写一个简单的解码模块把TMDS输出解回RGB再和原始像素比对。只要解码比对通过编码器的问题基本就排除了。4.2 硬件层面从黑屏到点亮的排查链条上板后黑屏不要慌着改代码。按顺序查一个问题一个问题排除第一查PLL锁定。用ILA抓MMCM的locked信号没锁定说明时钟输入有问题或者配置参数不对。第二查时钟通道。用示波器看差分时钟对是否有输出频率是否符合像素时钟。没有时钟输出后面一切免谈。第三查数据通道波形。示波器看三对差分数据是否有对应频率的跳变如果有一对完全没波形检查对应的编码器输出是否卡在固定值。第四查DE和同步时序。DE的极性、HSYNC/VSYNC的极性如果和显示器要求不一致也会黑屏。有的显示器对同步信号极性和DE的时序容忍度很低。第五查串行顺序。10位并行转串行时发送顺序是从bit0开始还是从bit9开始不同规范和不同IP的约定可能不同。这个坑最隐蔽症状是画面能出来但颜色和位置错乱。4.3 常见故障对照表现象可能原因检查方向完全黑屏时钟通道没输出、PLL未锁定查MMCM配置、时钟输入黑屏但时钟通道正常编码器卡在控制字符、DE没识别查DE极性、编码器状态花屏、雪花数据通道时序对齐差、串行时钟相位偏扫相位、加IDELAY颜色错乱通道映射错误、位序不对查串行顺序、RGB通道连接颜色偏色某通道直流平衡异常查cnt复位、编码器输出间歇性黑屏/闪屏电源纹波、差分走线不等长查供电、调整I/O强度4.4 我踩过的一个坑10位符号的发送顺序有一次画面能显示但是整体颜色像是RGB通道互换后又错位了一位我排查了很久才发现是串行化模块的位序问题。TMDS规范中符号的发送顺序是固定的但FPGA的OSERDESE2原语和移位寄存器实现方式不同对“哪一位先出去”的约定可能不一样。当时我把10位符号的最终输出顺序反过来一切就正常了。这个坑几乎每个人都会踩一次。建议在串行化模块的接口处加一个位序反转的配置参数调试时可以先试默认顺序不行就翻过来能省大量时间。5. 扩展思考从8b/10b家族看TMDS的定位5.1 TMDS与8b/10b编码的异同TMDS看起来像是8b/10b编码的变体但实际上有本质区别。两者都是8位数据扩成10位、效率都是80%、都带直流平衡但设计侧重点不同维度TMDS8b/10b编码目标最小化翻转、DC平衡保证翻转密度、DC平衡控制字符固定编码2位控制K码5位控制解码方式递推逆运算查表典型场景HDMI/DVIPCIe、USB3.0、千兆以太网8b/10b的著名劣势之一是实现复杂、需要查表TMDS把控制信息压缩成更少的组合电路规模更小这也是当年DVI选择它的原因之一。5.2 Deep Color、HDR对编码算法的挑战像素位宽超过24位后TMDS编码算法本身不用改变的是数据的吞吐方式。30位或36位深色模式下要么用更高的TMDS时钟要么分时复用多路通道。HDMI 1.4的深色模式就是用提高TMDS时钟频率实现的——像素时钟变成原来的1.25倍、1.5倍或2倍。对FPGA实现的影响是编码器逻辑不变但串行化部分的速率压力变大。30位深色1080p需要1.85Gbps单通道速率7系列FPGA的普通IO已经逼近极限这时就得考虑更高级的GTX/GTH收发器或者换用HDMI 2.0的FRL方案。HDR本身不改变TMDS编码它只是改变了像素数据的格式比如宽色域、高位深。如果你在FPGA上做HDR视频输出编码器部分完全不用动处理好像素格式转换就行。5.3 什么情况下可以绕开TMDS做产品时没必要把所有场景都硬往TMDS上套。板内短距离RGB并口传输根本不需要编码传感器接口用MIPI或LVDS更合适工业相机走Camera Link或CoaXPress。只有需要接HDMI/DVI显示器的场景TMDS才是必须品。我自己的体会是把TMDS编码算法彻底搞懂最大的收获不是背下了那套递推公式而是理解了“为什么高速接口需要编码层”这个通用问题。之后再看PCIe、以太网、USB它们那些复杂的加扰、编码机制底层逻辑都是一脉相通的。
返回列表