
很多刚接触FPGA的朋友都会在板卡资源列表里看到HDMI接口但真要把一条HDMI线插到FPGA板上让画面在监视器上亮起来中间的坑远比你想象得多。这次黑金云课堂的“FPGA基础HDMI视频输入与环路输出实验”正好把FPGA图像处理里最常用的一条链路串了起来HDMI信号进来FPGA完成解码再原样或处理后送出去。它能帮你搞清楚HDMI接口怎么工作、TMDS编码是什么、像素时钟从哪来、画面上为什么会出现花屏和雪花以及怎么用Vivado的ILA去定位这类问题。不管你是刚开始学FPGA还是已经做过几个小项目想往图像处理方向走这个实验都值得认真做一遍。1. 别小看这个基础实验它在带你走通一条完整视频链路1.1 实验内容和目标拆解单看“HDMI视频输入与环路输出”这几个字你可能觉得这不就是把一条HDMI线接进来、再接出去吗实际操作起来完全不是这个量级。这个实验的核心目标是三件事让FPGA正确接收外部HDMI源送来的视频信号把串行的TMDS差分信号恢复成并行的RGB像素数据和行场同步信号然后再把这组数据重新编码成HDMI输出让下一台显示器正常显示。环路输出Loop Out这个词玩过视频矩阵或者显示器级联的朋友应该不陌生。它就是把输入信号复制一份再吐出去常见于专业监视器、视频切换台这类设备。放到FPGA实验里它的意义在于给你提供了一条“回环检查”的通路你可以先不接真实HDMI源直接把FPGA生成的测试彩条从HDMI输出再用一根线连回输入验证接收通路是不是好的也可以接上真实信号源让FPGA把处理过的画面再输出给显示器对比。这个实验的难点不在“输出”因为在很多开发板上HDMI输出基本上就是调用一个现成的编码器IP把并行的RGB和同步信号填进去设置好参数就行。真正的硬骨头在“输入”HDMI输入信号是3路串行差分数据加1路差分时钟频率在几十到上百兆赫兹要用FPGA内部的IO资源去做串并转换、字节对齐、通道对齐还要做时钟恢复和跨时钟域处理。1.2 环路输出到底有什么用为什么值得学如果你只是想在FPGA上把HDMI接口点亮很多人会选择只做输出。我在实际项目里见过不少工程师板的HDMI输出能出图但一遇到“视频进来处理完再显示”的需求就卡住了因为他们从来没做过接收端。环路输出实验强就强在它逼着你把“接收-处理-发送”整条链路全部打通。真实项目里的视频采集、图像增强、OSD叠加、画中画本质上都是这条路子只是把中间的“环路直通”换成了各种处理算法而已。做嵌入式或者FPGA开发的人应该都能理解接口通信这东西最怕的就是“单端调试”。环路输出给了一个自测手段你在办公室没有HDMI信号源的时候用FPGA自己产生彩条信号从输出端发出去再从输入端接收回来把两幅画面做对比甚至可以在中间故意加一个颜色翻转的模块用肉眼就能确认接收和发送是否都正常。这种自测闭环的设计思路我认为比实验本身更值得学习。1.3 实验环境和整体通路概述以常用的黑金Artix-7系列开发板为例板卡上通常有HDMI的输入座和输出座引脚一般接在FPGA的HRHigh Range或者HPHigh PerformanceBANK上支持LVDS或者TMDS电平标准。Vivado工程里需要做三部分逻辑输入端的TMDS解串模块、中间的缓存和处理模块、输出端的TMDS编码模块。从时钟域来看整条链路至少涉及三个时钟来自HDMI输入解出来的像素时钟、FPGA内部处理用的时钟通常和输入像素时钟同频同相或者经过MMCM/PLL调整、输出端编码器需要的像素时钟。很多新手在第一个实验里就栽在时钟上要么是输入时钟没有做BUFG接入全局时钟网络要么是跨时钟域的FIFO深度不够导致画面撕裂这些问题在后面的调试章节我会单独展开。2. HDMI接口与信号原理先看清链路再写代码2.1 TMDS编码与那四对差分线HDMI 1.4和之前的版本本质上就是4对差分信号线其中3对传数据1对传像素时钟。注意这里传的不是传统的二进制电平而是TMDSTransition Minimized Differential Signaling最小化传输差分信号编码后的数据。TMDS编码技术有两层目的一是减少信号跳变次数降低电磁干扰二是通过直流平衡让接收端能更好地恢复信号。每对差分数据线在每个像素时钟周期里传10 bit其中8 bit是有效数据另外2 bit是编码扩展和直流平衡位。3对线加起来每个时钟周期正好传24 bit对应RGB三种颜色各8 bit。所以你看HDMI设计里像素时钟和分辨率是强绑定的1080p60的像素时钟是148.5 MHz数据率就是148.5乘以10再乘以3接近4.5 Gbps这是信道容量的概念也是为什么HDMI布线对等长、阻抗要求那么严的原因。TMDS编码的基本规则你可以简单理解成低位5 bit用XOR/XNOR逻辑编码高位3 bit查表编码再加上2 bit的控制/平衡位。对于FPGA开发来说底层编码的具体算法其实不用你手工实现因为输出端有现成的编码器Xilinx的HDMI IP核或开源代码都有输入端也只要做串并转换不需要去逆编码。但你要明白一个关键点接收端必须知道每一对线上的“位边界”在哪才能把10 bit一组正确解出来这就引出了字对齐的问题。2.2 EDID、HPD和DDC链路握手的三件套很多做FPGA的人第一次调HDMI输入时特别困惑我明明把HPD引脚拉到高电平了为什么PC显卡就是不肯输出信号因为HDMI的链路建立不是一个“上电就出画面”的过程它有一套完整的握手协议。Source输出端和Sink接收端之间通过DDC通道本质是I2C交换EDID数据通过HPD引脚变化来感知热插拔事件。下面是HDMI握手的基本流程HDMI源设备上电后首先检测Sink设备的HPD引脚是否有高电平当HPD有效时源设备通过DDC读取Sink的EDIDEDID里详细记录了Sink支持哪些分辨率、什么像素时钟范围、色彩空间等等源设备从EDID里挑一个最合适的分辨率然后才开始在TMDS通道上发送视频信号。在FPGA实验板上输入端口的EDID通常存放在板载的EEPROM里HPD引脚一般直接上拉到电源或者通过FPGA控制。这里有个非常典型的开发板坑如果EEPROM里没写EDID或者HDMI座的HPD和FPGA引脚没接对你的FPGA接收模块做得再完美也收不到数据因为显卡压根不认为你插了一个显示器。2.3 像素时钟和分辨率的计算关系像素时钟不是拍脑袋定的它是视频时序里最重要的一个参数。对于某一分辨率像素时钟等于有效像素数、消隐区像素数、行数、帧率几项相乘的结果。以1080p60为例一行总共有2200个像素其中有效像素1920个前面有88个消隐后面有148个前沿总行数是1125行其中有效行1080行。2200乘以1125再乘以60算出来正好是148.5 MHz。我做实验时经常要用的几个典型值整理成了一张表方便你对照分辨率刷新率像素时钟典型应用720p60 Hz74.25 MHz监控、低成本HDMI源1080p60 Hz148.5 MHz高清视频、PC输出1080p30 Hz74.25 MHz低带宽场景768pXGA60 Hz65 MHzVGA转HDMI的老设备4K3030 Hz297 MHz4K显示入门记住这些数不是为了背表格而是为了后续调试。如果你发现FPGA接收端解出的像素时钟和预期值差了好远那大概率是EDID没有正确上报分辨率源设备用了低分辨率输出或者你所用的FPGA开发板输入的时钟路径上有问题。时钟对了后边的像素数据才有意义。3. FPGA接收端实现从差分串行数据到并行像素流3.1 硬件方案选择直接用FPGA IO还是外接PHY芯片HDMI输入的接收方案在FPGA项目里一般有三种做法。第一种是拿FPGA的高速收发器比如Artix-7的GTP去接收HDMI信号这种方式适合做4K或者深色深信号但GTX差分管脚并不直接兼容HDMI座子的引脚分配一般要经过电平转换或者专门设计。第二种是在FPGA外部放一颗HDMI接收PHY芯片比如ADI的ADV7611、TI的TFP401这颗芯片负责把TMDS信号解成24位并行RGB和行场同步、像素时钟FPGA只做后续处理。第三种是黑金开发板最常用的做法HDMI座子直接连到FPGA的HP BANK引脚上用FPGA的IO逻辑做差分解串。第三种方案看起来“省芯片”但对FPGA管脚的电平标准和逻辑资源有一定要求。Xilinx的7系列FPGA里为了支持HDMI这类接口专门在IO Bank里集成了IDDR、ODDR、ISERDES、OSERDES这些资源。ISERDES可以把一对差分信号按1:4、1:7甚至1:8的比率串转并正好匹配TMDS 10 bit对不上2的幂次方的问题。3.2 串行解串、字对齐和通道对齐TMDS每通道在一个像素时钟周期内传10 bitFPGA的ISERDES通常只能按2、4、8这种位数解串所以常用做法是先把10 bit拆成几段再做“滑动窗口”对齐。这一段是接收端最容易出问题的环节理解它你才能真正理解HDMI接收为什么比发送难。字对齐的核心思路是数据在传输前TMDS编码会在控制周期插入特定的控制字符比如VSYNC、HSYNC的前导有固定的编码序列接收端只要在串行数据流里找到这些特殊符号就知道当前bit组的边界了。具体到Verilog逻辑上你会用类似“移位寄存器模式匹配”的方式不断调整解串的起始bit位置直到检测到连续多个正确的控制字。通道对齐比字对齐稍微简单一点但也经常被忽略。3个数据通道的串行数据在物理链路上可能因为走线长度差异产生几bit的相位差所以需要一个通道对齐逻辑把3个通道解出来的像素数据在时间上拉到同一拍。实际工程中我会在CH0、CH1、CH2解串完成之后各加一个可配置延迟链然后通过比较同一行的像素数据是否一致来判断是否对齐。下面给一个纯粹的思路示意代码说明IDDR解串和字对齐的基本形态实际项目中你会在这个基础上做大量工程化处理// 示意代码使用IDDR把TMDS数据通道解成2bit wire tmds_clk_bufg; wire [1:0] ch0_odd, ch0_even; IDDR #( .DDR_CLK_EDGE(SAME_EDGE_PIPELINED), .INIT_Q1(1b0), .INIT_Q2(1b0), .SRTYPE(SYNC) ) iddr_ch0 ( .Q1(ch0_odd), .Q2(ch0_even), .C(tmds_clk_bufg), // TMDS时钟已通过BUFG接入全局时钟网络 .CE(1b1), .D(tmds_data_p_ch0), .R(1b0), .S(1b0) ); // 之后需要把2bit再累计成10bit并做滑动窗口字对齐这里特别强调一下时钟TMDS差分时钟到达FPGA引脚后必须先经过IBUFDS转换成单端再经过BUFG接入全局时钟网络否则逻辑根本跑不稳。另外HDMI信号的差分引脚在约束文件里要正确设置为LVDS或者TMDS电平标准我习惯同时在XDC里加上PACKAGE_PIN和IOSTANDARD两个属性并且确保一对差分信号的正负极性没有接反。3.3 行场同步提取与去抖处理TMDS数据通道里并不单独传行场同步信号而是在数据通道0的数据包头部通过控制字符来表示。具体来说在每行有效像素开始之前数据通道会插入一个“控制前导”窗口这时通道0上的4个控制信号HSYNC、VSYNC、CTL0、CTL1就编码在TMDS控制字符里。也就是说你需要从解串后的数据流里识别出控制字符并把HSYNC、VSYNC还原成标准的行场同步脉冲。这里的去抖非常关键。真实的HDMI信号在分辨率切换或者信号源不稳定的瞬间会出现HSYNC/VSYNC毛刺、丢失或加倍。如果不加处理直接把这些信号拿去做帧缓存读取地址画面会瞬间撕裂甚至黑屏。稳妥的做法是在FPGA内部做一个“同步信号健康检测”模块连续统计几个帧周期内行场同步的个数如果异常就暂停输出直到恢复正常再继续。我实测的经验是用计数器统计一场一帧内HSYNC的个数是最简单的健康检测方法。比如1080p60一帧有1125行那么一帧内应该检测到1125个HSYNC脉冲偏差在几行以内是正常的。如果统计结果变成了1100或者1200说明信号源在切换模式这时候要么动态调整接收参数要么报警提示而不是让显示画面花掉。3.4 像素数据缓存与跨时钟域FIFO接收端恢复出的像素时钟是由HDMI源设备的TMDS时钟决定的而输出端的像素时钟如果走的是独立锁相环两者之间存在天然的频率偏差即使都是148.5 MHz也不可能做到完全同频同相。这时候如果不加缓存直接把数据从输入端搬到输出端一段时间后必然会因为FIFO上溢或者下溢而画面撕裂。解决这个问题的常规做法是全链路用一个FIFO做帧级或行级缓存。简单场景下输入端把写使能和像素数据写入FIFO输出端用本地时钟读FIFOFIFO的深度至少能容纳一两行数据。但如果你后面要做帧率转换、图像缩放或者帧缓存那就要用DDR3/4加帧缓存控制器配合乒乓缓存结构。跨时钟域处理要注意一个细节写入FIFO的像素时钟来自HDMI输入读时钟来自输出端这两个时钟即便频率相同相位也不一定同步FIFO的读写指针计数必须使用格雷码做异步比较。Xilinx的FIFO IP核会自动处理这个问题所以我不太建议新手自己写异步FIFO直接用Vivado里的FIFO Generator配置成“Independent Clocks”模式读写位宽都设为24位深度按一行像素数取2的幂次方就够了。4. 环路输出让画面从FPGA再走出去4.1 直通环路先跑通最简单的那条路环路输出最简单的形态就是把接收端解出来的像素数据和同步信号不做任何处理直接送给输出编码器由编码器重新生成TMDS信号送出去。这个直通模式的价值在于快速验证链路把接收端问题和输出端问题剥离开来。具体到工程实现你需要在接收端解出数据之后不要直接接输出编码器而是加一级寄存器打拍最好再把数据经过一个带同步下降沿处理的模块。原因是接收端的像素数据和输出端的编码器虽然理论上都是同一个像素时钟域但中间经过的FIFO、寄存器链会导致数据相位发生变化输出端编码器对行场同步信号和有效数据之间的时序关系要求很严格稍微错一两个时钟周期画面就会出现偏移或者滚动。直通模式跑通后你可以做一个很直观的验证在FPGA内部把R和B两个通道互换再编码输出。如果显示器上的红蓝颜色对调了说明整条接收和发送链路都是通的你只是改了数据而已这种“故意搞破坏再验证”的调试方法比单纯看彩条有用得多。4.2 处理后环路给数据加一点“私货”直通没问题之后就可以尝试在中间插入处理模块体验一下FPGA图像处理的乐趣。最简单的处理是色彩转换把RGB转成YUV再转回RGB虽然肉眼看起来差不多但这算是真正摸到了视频处理的门槛。再往上可以做的有小区域放大、亮度调整、边缘检测、OSD叠加每一步都是在接收数据流上做像素级的变换。我个人比较推荐的第一个处理模块是“区域裁剪放大”把输入1920x1080的画面只取中央一块640x480的区域放大到全屏输出。这个模块用到双线性插值设计起来不复杂但很有代表性它逼着你去想清楚行缓存的深度、插值系数的计算、像素坐标的生成这些图像处理的基本问题。做完这个你对图像数据流的掌控力会有质的提升。需要注意的是一旦在中间加入了处理模块输出端的时序约束就要重新梳理。比如你做了裁剪放大输出像素时钟可能就不再等于输入像素时钟了这时候输出端要单独产生新的像素时钟并且要把时序约束同步更新到XDC文件里否则Vivado综合实现后时序报告一片红。4.3 输出端编码器的一些配置要点HDMI输出编码器不管用官方IP还是开源代码需要配置的东西都差不多像素时钟频率、分辨率时序参数、数据通道映射、是否使能音频等。我重点说三个容易踩坑的点。第一个是像素时钟的PLL配置。输出端一般由一个MMCM/PLL把板载时钟常见50 MHz或者200 MHz倍频到目标像素时钟。你要根据具体输入时钟频率去计算倍频和分频系数并在XDC约束里给MMCM的输入输出时钟都加上时序约束不让工具给你乱推。第二个是数据通道的RGB映射。HDMI标准的TMDS通道0对应蓝色、通道1对应绿色、通道2对应红色。如果你发现输出画面颜色不对先查通道映射而不是查像素数据。很多开源代码把通道名写成D0/D1/D2你得仔细看对应关系。第三个是HDMI热插拔检测。输出端作为Source要主动检测Sink设备的HPD引脚只有在HPD有效时才把TMDS信号真正发出去否则会莫名消耗功耗还可能因为信号源与Sink设备握手异常导致黑屏。有些开发板会把HPD直接做死拉高但这在真实产品里是绝对不行的。5. 调试实录黑屏、花屏、闪屏一步步怎么查5.1 先查物理链路再谈逻辑问题我在带人做这个实验时遇到“屏幕上没画面”的第一反应一定不是打开Vivado抓波形而是先检查物理链路。这个所谓“物理链路”包含好几层HDMI线缆本身是否完好、线缆是否弯折严重、开发板上HDMI座子有没有虚焊、输入源有没有真的切到正确分辨率输出。举个例子有些HDMI线是1.4版本只支持到2K用在1080p60可能是正常但如果你强制设置为4K或者高刷新率信号质量下降会非常明显画面要么直接不亮要么是马赛克一样的持续性噪点。很多开发板用户在京东买一根十几块的“高清线”来调FPGA遇到花屏第一反应是代码有问题查了一晚上最后换线就好了这种教训我见得太多。HPD信号的检查也属于物理链路范畴。你可以用万用表量一下HDMI座子的HPD引脚电平正常情况下应该为高。如果为低那就是HPD电路没工作FPGA代码写得再好也没用。黑金开发板的原理图上一般会把HPD引脚标注出来调试前先找到它。5.2 黑屏的排查顺序从外到内不慌黑屏分两种完整的黑屏以及背光亮但没画面的黑屏。前者基本是链路未建立后者相当于信号有了但你没有正确解码。下面是我的排查顺序你可以直接抄确认输入源设备是否识别到了显示设备。用PC当源时看显卡驱动里是不是多了一个显示器如果识别到了1080p说明EDID和HPD没有问题。用示波器或逻辑分析仪测TMDS时钟引脚看是否有正常的148.5 MHz或者74.25 MHz差分时钟。如果没有说明源没有发送信号。如果时钟有但画面黑检查FPGA内部时钟有没有进BUFG、MMCM锁定没有。很多时序问题会导致内部时钟不稳定画面上表现就是黑屏。然后用ILA抓接收端的VSYNC、HSYNC和DE信号如果DE数据有效完全没有脉冲说明字对齐有问题TMDS数据没有被正确解析出来。最后检查FIFO的读写水位如果写端持续有数据但读端一直是空的说明跨时钟域FIFO配置有问题或者输出端时钟未启动。很多人的问题其实就出在第二步到第三步之间TMDS时钟测得到但MMCM配置输入时钟频率不对锁相环根本没锁定数据当然解不出来。5.3 花屏和画面抖动常见原因速查花屏的原因相对集中我列了一个速查表现象最可能的原因下一步操作满屏随机雪花噪点TMDS差分线极性接反或阻抗不匹配检查XDC约束、PACKAGE_PIN是否与原理图一致画面有规律的斜纹字对齐没找到正确边界检查字对齐模块的检测窗口和移位逻辑颜色通道错乱通道映射或RGB顺序不对对照通道对应关系调整数据位映射图像周期性跳动撕裂跨时钟域FIFO溢满或读空加大FIFO深度或改用帧缓存方案画面顶部有彩色条纹行场同步信号时序不对检查控制字符提取逻辑确认HSYNC/VSYNC与DE的相对时序画面抖动这一点我多说一句。如果只是偶尔轻微抖动大概率是FIFO读写指针的异步比较没有做格雷码或者FIFO深度设置得刚好卡在临界点上。如果抖动很频繁那就是输入和输出像素时钟的频率差太大这种情况下不要试图用FIFO去“磨平”它你需要重新设计时钟方案比如用同一个MMCM同时产生输入输出像素时钟。5.4 用ILA抓内部波形定位问题等到物理链路和基本逻辑都排查完了还找不出问题就该上Vivado的ILAIntegrated Logic Analyzer了。ILA相当于FPGA内部的示波器你可以把想看的信号添加到探针列表里设置触发条件然后在板上运行。抓HDMI接收端信号时我通常先抓这几个信号MMCM的locked信号、ISERDES解串后的字节数据、检测到的VSYNC/HSYNC/DE、FIFO的wr_rst_busy和rd_rst_busy。触发条件设置为VSYNC上升沿存储深度设为16384采样频率就是像素时钟。这里有个ILA使用技巧在调试接收端时把采样时钟直接选成输入像素时钟这样抓到的波形和实际时序是对齐的。不要图省事用系统时钟去采样那样看到的信号边沿都被异步化了很多细节根本判断不了。另外ILA的探针加到关键信号上会增加布局布线压力调试完一定要记得移除探针再综合最终版本否则可能因为时序不过而误判代码有问题。6. 学完之后这个基础实验还能扩展出什么6.1 从环路输出到真正的视频处理链路环路输出实验做完后你已经掌握了FPGA处理视频接口的基本功时钟恢复、串并转换、FIFO缓存、跨时钟域处理。这些能力是通用的不只在HDMI上有效。很多做图像处理的工程师第一次接触MIPI CSI-2、LVDS、SDI这些接口时发现他们本质上都在解决同样的问题怎么把高速串行数据变成并行像素数据怎么保证处理过程中数据不丢不重。我个人觉得下一步最值得做的扩展是帧缓存加图像叠加。在你的环路输出链路上加一块DDR3把输入视频存入DDR3然后从DDR3读出来叠加一个FPGA生成的字符信息再输出。这个项目做好之后你就相当于自己实现了一个简易的视频处理盒子后面无论是做汽车电子里的倒车影像叠加还是工业相机里的实时图像注释都是同一个套路。6.2 和其他接口比对着学理解会更深HDMI做完了你带着对TMDS、时钟、同步信号的理解再去看LVDS、MIPI、SDI这类接口会特别快。就拿MIPI来说它的D-PHY也是差分串行但分组方式、协议层、控制命令完全不一样你在HDMI实验里练出来的“先物理链路再字对齐再协议解析”的调试思路完全可以直接迁移过去。再往深走就是高速收发器比如Xilinx的GTP/GTX。7系列里这些收发器原生支持多种协议但底层概念和HDMI实验是相通的都是串行解串、时钟数据恢复、字节对齐、通道绑定。你手里这块开发板如果带着GTP引脚完全可以尝试把HDMI信号转接到GTP上收一遍体会一下专用高速收发器和IO逻辑解串的差异。这一步做完PCIE、千兆网这些高速接口对你来说都只是协议层的学习了。最后再分享一个我个人的小技巧调试这类视频链路时一定要养成“边改边验证”的习惯每改一处逻辑就重新跑一次ILA不要一口气改好几处再上板。因为视频链路的问题经常是连环的你同时动了字对齐和FIFO深度就算画面恢复了你也不知道到底是哪一步起作用出了问题更是无从下手。稳一点慢慢来这个实验做完你对FPGA的感觉会和之前完全不一样。