第一次在FPGA上把RGB信号转成HDMI输出时我以为这就像UART发送一样简单把像素字节按顺序丢出去显示器就亮了。结果板子一上电示波器上波形明明在跑屏幕就是无信号或者奇奇怪怪的花屏、偏色、滚动条。后来把整套链路里里外外捋了一遍才发现核心不是“编码”而是“把并行像素流变成高速串行比特流”这个物理过程以及一堆容易被人忽略的握手时序和板级细节。这篇文章就把我实际做FPGA RGB转HDMI显示系统的完整思路、参数计算和踩坑过程写出来适合正在做视频输出、图像采集显示或者想用FPGA直连HDMI接口的开发者参考。1. 为什么自己用FPGA做RGB转HDMI而不是直接买一颗转换芯片1.1 HDMI发送器本质是“并行转串行”不是显示协议很多第一次接触的人会下意识把HDMI当成一种高级的“显示通信协议”觉得要先学懂各种帧结构、数据岛、音频包才能动手。实际上从FPGA的角度看HDMI/DVI发送链路的核心动作非常朴素把RGB三个通道的并行像素数据按TMDS算法编码成10bit符号三个通道每个通道串行输出物理线路上看到的是高速差分信号接收端显示器会自动完成时钟恢复、解码和像素重建也就是说发送端要解决的问题并不是“怎么把画面内容组织成协议包”而是“怎么在指定像素时钟下稳定地把三路10bit数据变成一路高速串行流”。这个理解一旦转过弯来整个项目的主线就清楚了。这也是为什么我看到有人纠结“HDMI 1.4和2.0的编码格式有什么不同”就卡住。对入门级别的RGB输入、8bit色深、1080p60来说你压根用不到深色、HDR、音频回传这些扩展功能核心就是三路TMDS数据和一路像素时钟。1.2 桥接芯片与纯逻辑直出的取舍市面上有专门的HDMI发送桥接芯片FPGA这边只需要输出RGBDE时钟剩下的TMDS编码和串行化全部由芯片完成。优点是一天就能点亮屏幕缺点也明显带宽被芯片型号锁死比如1080p通常没问题但4K60就要换更贵的型号而且这类芯片的寄存器初始化、EDID管理、音频配置非常繁琐有时候调I2C配置的时间比调FPGA逻辑还长。纯逻辑直出的思路是FPGA内部完成TMDS编码把串行数据从普通IO引脚输出。它的上限取决于FPGA本身的serdes能力低端FPGA也能做到720p、1080p60好处是不依赖外部芯片物料成本低带宽潜力更大还可以把帧率、分辨率、测试图生成全部集成到同一颗FPGA里。代价是板级设计、输出约束、时序收敛都得自己处理。我做这个项目时选的是纯逻辑方案因为目标只是把FPGA内部生成的彩色测试图输出到普通显示器上同时验证一套可复用的RGB转HDMI发送通路后续还能在此基础上扩展分辨率或者叠加图像处理。如果你手上有现成的桥接芯片那当然可以快速出活但如果想彻底掌握这条链路的细节、方便以后做更高分辨率或者定制输出格式建议至少自己写一次FPGA直出方案。2. 时钟树设计1080p60的像素时钟和高速位时钟是怎么算出来的2.1 从“一行多少个像素”推导出像素时钟做显示输出第一件事就是把时序参数的来龙去脉算清楚不能只看别人给的表格。以最常见的1080p60为例接收端期望的水平和垂直时序是一组固定的数值其中水平总像素是2200垂直总行数是1125。也就是说一帧实际传输的像素数不是1920×1080而是2200×1125。所以像素时钟就是像素时钟 2200 × 1125 × 60Hz 148.5MHz这个148.5MHz是整个系统的基准HDMI发送链路的每一个环节都跟它挂钩。如果你做的是720p60水平总像素是3300的话像素时钟就是74.25MHz如果做的是自定义分辨率算法一模一样先定下blanking参数再算时钟不要反过来拍脑袋定时钟。这里有个新手常犯的错误直接用1920×1080×60去算得到124.4MHz然后发现显示画面偏左、或者右边有一条黑边。原因就是没把blanking算进去。显示器在水平消隐期也需要时间来完成行同步所以总像素一定比有效像素多。2.2 五倍频还是十倍频串行数据率的两种实现路径TMDS物理层规定每通道10bit要在1个像素时钟周期内全部发完。以1080p60为例每通道数据率就是串行比特率 148.5MHz × 10 1.485Gbps问题来了FPGA内部逻辑时钟通常跑不到1.485GHz怎么把这个数据搬出去业界有两种常见路径使用五倍像素时钟742.5MHz配合DDR输出。742.5MHz时钟的上升沿和下降沿各送1bit一个像素时钟内正好送出10bit。很多FPGA的高速IO模块都能在这个频率下工作。直接使用十倍像素时钟1.485GHz单沿输出。这种方案需要FPGA的IO支持极高的单沿翻转率低端芯片基本不用想。所以绝大多数FPGA直出方案都走第一条路内部逻辑时钟用148.5MHzIO时钟用742.5MHz用DDR双沿的方式把10bit串出去。这也是接下来配置serdes模块时最重要的时钟关系。2.3 跨时钟域与FIFO设计如果你的RGB数据来自DDR3读出的图像数据或者来自摄像头接口那它的时钟大概率不是干净的148.5MHz而是某个异步时钟域。在这种情况下直接拿异步数据去驱动TMDS编码器会出现亚稳态和偶发花屏。标准做法是在进入TMDS链路之前加一个异步FIFO写入端是源时钟域读出端是148.5MHz的像素时钟域。FIFO深度不需要很大一般16到32就足够因为显示链路本质是流式的只要平均速率匹配瞬时缓冲量不会太大。关键是FIFO的读侧要用像素时钟连续读不要让读使能有间隙否则blanking期间后端就会出现像素空洞。如果你生成图像数据本身就发生在148.5MHz域内那可以不用FIFO但要保证生成逻辑的时序收敛特别是跨组合逻辑的路径不能太深。我实测下来像测试图生成、多边形绘制这类简单逻辑直接在像素时钟域做完全没有问题。3. TMDS编码与通道映射数据流在链路里到底怎么走3.1 从24bit像素到三路10bit符号TMDS编码不是简单地把8bit RGB值搬过去而是有一套保证直流平衡的编码算法。简单理解就是把8bit数据通过异或或同或逻辑展开成9bit再根据数据中0和1的数量决定是否翻转第10bit从而让线路上的直流分量尽可能平衡。这个编码器可以用FPGA的组合逻辑完成不需要查表。以红通道为例输入是8bit的R输出是10bit的TMDS符号绿蓝通道同理。编码完成后三路各10bit分别进入三个串行器。需要特别注意的是TMDS编码的输入不只有像素数据还有控制信号。在水平和垂直消隐期DE为低电平这时候三路通道要发送固定的控制符号来表示行场同步。也就是说发送器必须能在“像素数据”和“控制符号”之间无缝切换切换时机完全由DE信号决定。3.2 DE与control token的优先级很多人写代码时只关注DE有效期间的像素编码把DE无效期间随便塞个0结果显示器能识别出画面但画面上下翻滚或者行场不同步。原因就是消隐期的control token必须是特定值。TMDS标准里消隐期发送的control符号是有讲究的通道0在消隐期携带HSYNC和VSYNC相关的控制位通道1和通道2在消隐期发送固定的控制符号各通道的control输入有两根线比如c0、c1编码器会根据这两根线的值生成对应的10bit符号。如果你在DE无效期间直接把控制符号置为0而行同步、场同步的极性又配置不对就会导致显示器完全无法锁定。我在编码模块里是这么处理的一个周期内如果DE为高就按像素编码输出DE为低就按内部维护的HSYNC和VSYNC信号编码control输出。同时对外提供配置寄存器可以翻转行场同步的极性因为不同显示器对极性的要求并不一致做成可配置能省很多调试时间。3.3 通道顺序与引脚极性R、G、B三路通道和HDMI物理引脚的对应关系是这个项目里最容易被忽略又最容易翻车的地方。有些参考设计把通道0对应R通道1对应G通道2对应B但HDMI插座上的差分对顺序在不同板卡上可能有差异。我的建议是在FPGA的引脚约束阶段就明确标出三组差分对分别属于哪个通道然后在编码输出端加一个可控的通道交换。这样即使PCB上丝印标错了也不需要改板子直接在寄存器里把R和B对调一下就行。另外TMDS时钟通道的正负极性也要注意。有些FPGA的DPHY或者OBUFDS会对输出取反如果时钟极性反了接收端采到的所有数据都会错位表现就是画面完全花掉或者无信号。排查时可以用示波器看时钟通道的差分波形是否和像素时钟频率一致再确认相对数据通道的相位。4. 输出级实现从直接IO翻转到OSERDESE2的关键路径4.1 三种输出方案的演进把10bit符号变成物理差分信号有三种做法难度和性能依次递增方案一直接用Vivado里的DDR输出原语把bit0~bit9拼成两个5bit组分别在时钟上升沿和下降沿送出。这个方案逻辑简单但要求你手动处理双沿逻辑时序比较紧张。方案二使用厂商的串行器原语比如某7系列FPGA的OSERDESE2配置成10:1模式硬件自己完成并行到串行的转换。这是最推荐的方案因为原语在布局布线时会被放到靠近IO的位置时序天然更可控。方案三如果分辨率超过1080p或者需要更大位宽可以走专用高速收发器通道。但这类资源不是每个芯片都有而且配置复杂入门阶段先不做。我当时用的就是第二种OSERDESE2加上OBUFDS输出差分信号。核心配置是DATA_RATE_OQ选DDRDATA_WIDTH配10CLK接5倍像素时钟CLKDIV接像素时钟。并行侧用10bit输入注意原语的输入顺序和移位方向不同厂商定义不完全一样最好对照原语手册把每个bit的实际含义过一遍不要想当然。4.2 一个可用的10bit串行器配置片段这里给一段基于某7系列FPGA的示意配置重点是展示时钟关系不是完整工程代码OSERDESE2 #( .DATA_RATE_OQ (DDR), .DATA_RATE_TQ (DDR), .DATA_WIDTH (10), .TRISTATE_WIDTH (1), .SERDES_MODE (MASTER) ) oserdes_ch0 ( .OQ (tmds_p[0]), .OCE (1b1), .CLK (clk_5x), // 5倍像素时钟742.5MHz .CLKDIV (clk_pix), // 像素时钟148.5MHz .RST (serdes_rst), .D1 (tmds_10bit[0]), .D2 (tmds_10bit[1]), // ... D3~D8 按原语顺序填入 .T1 (1b0), .T2 (1b0), .T3 (1b0), .T4 (1b0), .TBYTEIN (1b1), .TBYTEOUT (), .TQ () );配套的差分输出是这样的OBUFDS #( .IOSTANDARD (TMDS_33) ) obufds_ch0 ( .I (tmds_p[0]), .O (hdmi_tx_p[0]), .OB (hdmi_tx_n[0]) );注意这里有个关键点如果你用的是五倍像素时钟DDR模式那么5倍时钟本身是742.5MHz。这个时钟的来源一般是PLL或者MMCM配置时要注意PLL的VCO频率范围有些低端芯片的VCO上限不够直接生成742.5MHz需要先倍频再分频或者采用其他组合方式。千万不能让PLL的输出超出规格否则即使约束通过实际抖动也会很大显示器会出现随机闪屏。4.3 输出约束与引脚分配这一节是我最想强调的因为我第一次画板子时认为只要能综合通过就行结果上板后从720p到1080p都无法稳定显示。约束要注意三件事第一TMDS输出引脚必须用差分约束让布线工具知道这是一对差分信号并尽量走同一层、保持等长。对内不等长会直接恶化时序裕量。第二时钟引脚和重时钟引脚的分配要靠近对应的输出管脚不要跨巨大的时钟区域。跨区布线会让高频率信号的时钟偏斜变大影响发送时序。第三如果FPGA支持TMDS输出电平标准就显式指定不支持的话可能需要用LVDS或DIFF_SSTL等兼容电平并注意后端是否要加电平转换电路。直接在普通LVCMOS IO上跑1.485Gbps除非信号路径极短否则大概率过不了信号完整性测试。5. 上电时序与DDC握手为什么有波形但显示器黑屏5.1 HPD与EDID显示器不认你的灵魂原因很多人做到“VS波形正常”之后卡在显示器死活不亮。这时候多半不是TMDS数据的问题而是HDMI握手没做好。HDMI源端你的FPGA板需要让显示器感知到“有设备接入”并允许它读取EDID。最核心的信号是HPDHot Plug Detect。在HDMI连接器上HPD由显示器输出源端应该监控它并做对应处理。但作为FPGA开发板如果你的发送器没有主动处理HPD显示器会认为根本没有设备插入自然不启用输出。简单可靠的模拟方法是板卡上一上电就通过一个电阻把HPD引脚拉高到3.3V。这样显示器会认为源端已经就绪然后去试图通过DDC通道I2C读取EDID。如果你的设计不打算支持EDID读取就让I2C引脚保持空闲或接上拉显示器读取失败后通常会进入“无信号”状态但至少不会因为你完全没处理握手而黑屏。如果显示器屏幕上出现“无信号进入省电模式”那说明HPD至少被正确处理了问题多半在TMDS时钟或者数据通路上。5.2 先跑blank再让DE信号有效上电瞬间PLL还没锁定FPGA内部的像素时钟是抖动的。如果这个时候就输出有效DE和像素数据显示器可能在错误的时钟边沿上建立同步然后陷入反复追踪的循环。我采用的顺序是FPGA上电复位所有模块等待PLL lock信号拉高在DE保持无效的情况下连续输出若干个完整帧的blank像素等显示器稳定后再拉高DE使能输出有效图像这个“先blank后图像”的顺序在很多参考设计里看不到但它真的能避免很大一部分“上电黑屏重启一下又好了”的诡异现象。我在实际调试中遇到过直接上电就出图第一次大概率花屏或者闪一下但热复位之后完全正常。原因就是PLL尚未稳定时就开始发送有效图像数据接收端的时钟恢复完全被带乱。改成先blank 200ms以上问题直接消失。6. 板级调试实录示波器、驱动强度与EMI问题6.1 看波形时到底在看什么调试TMDS链路时示波器不能只看“有没有波形”要看三个关键点第一时钟通道的频率是否精确等于像素时钟频率。148.5MHz不能变成147MHz或者149.5MHz频率偏差过大会导致显示器无法锁定或者出现行场不同步。第二数据通道的差分幅度和摆率是否正常。如果幅度太低可能是输出驱动强度配置过弱或者线路损耗太大如果波形边缘有过冲说明驱动强度可能过高可以尝试调低电流或者增加串联电阻。第三时钟和数据之间的相对抖动。用示波器余辉模式看时钟眼图如果眼图闭合严重优先检查PLL配置和电源噪声。我调试时最喜欢用一张黑白竖条测试图竖条图案对TMDS串行数据中的高频成分和低频成分都很有压力任何信号完整性问题都会在竖条边缘表现为拖影、噪点或者微小色偏。6.2 电源与地弹最容易忽略的隐性杀手FPGA在输出1.485Gbps串行流时瞬时功耗变化非常剧烈。如果板卡的电源网络阻抗过高或者去耦电容不足输出驱动翻转时会在电源上产生周期性纹波这些纹波会直接耦合到PLL和输出缓冲造成随机抖动。实际遇到过一次单独跑core逻辑一切正常但只要开启TMDS输出图像偶尔出现水平方向的随机噪点。排查了所有逻辑和时序都没有问题最后发现是给FPGA IO bank供电的LDO纹波偏大输出负载一上去就掉电压。换了一颗纹波更小的电源模块之后问题彻底消失。所以不管是做评估板还是集成到自己的PCB一定要保证高速输出IO bank的供电余量充足至少留30%以上的电流裕度并且靠近FPGA电源引脚放置足够容值的去耦电容。6.3 用一张测试图排查通道映射与极性讲一个很实用的调试招数生成一张带“R”、“G”、“B”三个色块同时左上角画一个特定角标的测试图。如果显示器显示出来色块位置交换那就直接确认通道映射关系需要调整如果角标位置偏了半个字符说明DE相位和像素数据没有对齐如果画面整体偏右或偏左需要调整同步位置参数。我还遇过一个“红绿蓝都正常但整体画面发紫”的情况最后发现是蓝色通道少了最低位的数据线没连接导致蓝色分量在低灰度时丢失。这种问题用纯色测试图一眼就能看出来但用彩色自然图像反而很难判断。7. 从1080p60到更高分辨率带宽升级路径7.1 2像素并行架构提高输出能力的第一步如果目标分辨率超过1080p60比如1440p60或者1080p120单个像素时钟可能达到220MHz甚至300MHz以上。这个时候内部逻辑在单像素时钟域里做复杂处理通常会遇到时序收敛困难。常见做法是把数据通路改成2像素并行一个像素时钟周期内同时处理2个像素内部逻辑时钟可以降到像素时钟的一半但输出串行器仍然按2倍并行位宽工作。比如1440p60的像素时钟约241.5MHz采用2像素并行后内部逻辑时钟只要约120MHz配合每个通道20bit并行输入给更高位宽的串行器FPGA的压力会小很多。不过要注意改成并行像素后生成测试图和做图像处理的部分也要同步改成2像素流水线代码复杂度会明显上升。如果没有强需求建议先把1080p60的链路做稳再扩展并行宽度。7.2 什么时候该换高速收发器或专用桥接芯片从技术趋势看HDMI 2.0以上引入了更高的数据率TMDS也逐步演进到FRL模式。很多通用FPGA的普通IO并不支持FRL所需的极高速度所以到这一步通常有两条路一是选用集成高速收发器的FPGA用收发器实现HDMI 2.0的物理层逻辑侧仍然可以保留RGB输入和TMDS编码思想但编码后的数据会通过并行接口送给收发器再由收发器负责串行化。二是回到第一章说的桥接芯片思路。当你评估完纯逻辑方案的带宽成本、PCB难度、开发时间之后发现专用芯片更划算那就果断切换。桥接芯片负责HDMI协议的最高速部分FPGA专心做图像生成和简单处理两边各干各擅长的事。我的建议是如果你的产品三年内只做1080p纯逻辑方案性价比很高如果明确要做4K60甚至更高直接在一开始就选带高速收发器的FPGA或者桥接芯片方案不要在普通IO上硬扛否则板级和时序成本会成倍增加。8. 那几个让我印象深刻的坑最后把我实际踩过的、值得单独说一遍的坑总结一下给准备动手的人提个醒。第一个坑是OSERDESE2的bit顺序。我一开始严格按照原语手册写的并行输入映射但输出波形完全不对后来才发现不同系列、不同封装下原语的串行输出顺序有差异。解决方法是写一个简单的循环测试模式比如并行输入0b1100000000看输出波形高电平出现在哪个位置以此校准接线。第二个坑是DE信号不能有毛刺。TMDS编码器对DE非常敏感如果DE信号上有组合逻辑产生的毛刺哪怕只有一个周期接收端就可能在消隐期和有效显示期之间误切换造成画面边缘出现一条噪声带。我用的是在像素时钟域里从寄存器直接输出的DE不在外部组合逻辑里做DE。第三个坑是热插拔场景下的复位。如果用户在使用中拔掉HDMI线再插回去FPGA端如果没有检测HPD变化并重新执行一遍“先blank后出图”的流程显示器很可能会一直黑屏直到重启。我后来在系统里加了一个简单的HPD检测逻辑一旦发现HPD从低变高就自动把输出状态机重置到blank状态再平滑地恢复图像输出。第四个坑是TTL电平的3.3V对HDMI的5V引脚。HDMI规范里源端要提供5V电源但如果你的FPGA板没有专门的5V HDMI电源引脚千万不要直接把FPGA的3.3V接到HDMI连接器的5V脚。正确的做法是单独从板卡电源生成5V再通过合适的限流和保护电路接到连接器。否则一旦插上某些带反向保护的显示器接口板卡可能直接烧掉。做这套系统最舒服的时刻是终于看到测试图上红绿蓝三色块按预期位置出现的那一次。前面所有的时序计算、约束调整、信号质量排查在这一刻都值了。如果你也在做类似的东西希望这篇文章能帮你少走我走过的那些弯路。核心就一句话协议可以慢慢学但时钟树、IO约束、上电时序和板级信号完整性是决定成败的四根柱子。