HDMI Transmitter Subsystem IP 这个方向我在过去几年里陆陆续续做过几个项目从纯 FPGA 原型验证到最终 SoC 流片都有涉及。说实话第一次接触这套 IP 的时候我以为无非就是把并行视频数据串行化然后按差分对发出去能有多复杂结果真正啃下来才发现从 TMDS 编码到音频采样包插入、从 CEC 仲裁到 HDCP 握手、从 1.4 的 340MHz 到 2.0 的 600MHz 时序收敛每一个环节都有足够多的细节能把人卡住。这篇文章我打算把整个 Transmitter Subsystem 的设计思路从头到尾拆一遍包括架构划分、关键模块的 RTL 实现要点、FPGA 验证阶段容易踩的坑以及 1.4 和 2.0 两个版本在实现上的核心差异。不管你是刚接触 HDMI 协议的 FPGA 工程师还是正在做 SoC 显示子系统集成的设计者应该都能从中找到对自己有用的东西。1. 先搞清楚 HDMI Transmitter Subsystem 到底包含哪些东西1.1 从系统视角看信号链路很多人一上来就盯着 TMDS 编码器看这没错但不够。一个完整的 HDMI Transmitter Subsystem 远不止一个编码器加几个串行器那么简单。从系统层面看它至少包含以下几个功能域视频处理通路接收来自显示控制器或视频管线的像素数据完成色彩空间转换如果需要、像素打包、时序缓冲。TMDS 编码与串行化对每个色彩通道进行 8b/10b 风格的 TMDS 编码然后通过 OSERDES 或专用 SerDes 以 10 倍像素时钟速率串行输出。音频子系统采集音频采样数据按照 HDMI 规范打包成 Audio Sample Packet在视频消隐期间插入到数据流中。辅助数据通道包括 InfoFrame 的生成与发送、EDID 读取通过 DDC、CEC 通信、以及 HDCP 加密引擎的集成。时钟管理像素时钟、TMDS 位时钟、音频主时钟之间的域交叉和相位关系管理。这五个功能域不是孤立的它们之间有明确的耦合关系。比如音频包的插入时机必须和视频时序严格对齐InfoFrame 的更新必须和 VSYNC 同步HDCP 的加密必须在 TMDS 编码之前完成。理解这些耦合关系是做好架构划分的前提。1.2 为什么 Subsystem 的边界划分很关键在实际项目中HDMI Transmitter 很少作为一个孤立的 IP 存在它通常要挂到 AXI 或 AHB 总线上由处理器配置寄存器同时和显示控制器、音频控制器、DMA 等模块协同工作。所以 Subsystem 的边界划分直接影响到集成的难易程度和后续的复用性。我的经验是把 Subsystem 划分为三个层次比较合理层次职责典型接口寄存器与配置层寄存器读写、中断管理、时钟复位控制APB/AXI-Lite数据处理层视频采集、音频采集、InfoFrame 组装、HDCP 加密AXI-Stream / Native Video物理编码层TMDS 编码、串行化、差分输出并行 DDR 接口到 PHY这样划分的好处是物理编码层可以针对不同的工艺节点或 FPGA 平台做替换而数据处理层和寄存器层保持稳定。我在一个 28nm SoC 项目里就是这么做的FPGA 验证阶段用的是 Xilinx 的 OSERDES流片时换成了自研的 SerDes PHY上层 RTL 几乎没动。1.3 HDMI 1.4 和 2.0 在 Subsystem 层面的核心差异这是很多人容易混淆的地方。HDMI 1.4 和 2.0 在协议层面的差异看起来只是带宽从 10.2Gbps 提升到 18Gbps但在 Subsystem 设计上影响是全方位的TMDS 位时钟1.4 最高 340MHz每通道 3.4Gbps2.0 最高 600MHz每通道 6Gbps。这意味着 2.0 对时序收敛的要求高了一个档次。色彩深度与格式1.4 支持 8/10/12-bit 色深2.0 增加了 YCbCr 4:2:0 的支持这对视频通路的像素打包逻辑有影响。Scrambling2.0 引入了 TMDS Scrambling仅针对 340MHz 以上这是 1.4 完全没有的机制需要在编码器之前增加扰码模块。通道速率与时钟比1.4 通常用 10:1 的串行化比2.0 在 600MHz 时可能需要 20:1 或更高的串行化比取决于 PHY 的能力。所以如果你要设计一个同时兼容 1.4 和 2.0 的 SubsystemScrambling 模块和更宽的串行化通路是必须提前规划的。2. TMDS 编码器的 RTL 实现从算法到可综合代码2.1 TMDS 编码算法回顾与硬件映射TMDS 编码的本质是把 8-bit 像素数据转换成 10-bit 的直流平衡码流。算法分两个阶段第一阶段根据输入数据的 1 的个数决定是否做异或/异或非变换第二阶段根据运行中的直流平衡状态决定是否翻转。这个算法在软件里实现很简单但在硬件里要做成流水线需要仔细考虑。我见过不少实现是把两个阶段放在一个时钟周期里完成结果关键路径太长在 600MHz 下根本跑不动。正确的做法是至少拆成两级流水// 第一级计算变换后的数据 always (posedge clk) begin ones_count count_ones(din); if (ones_count 4 || (ones_count 4 ~din[0])) dout_stage1 din ^ 8hFF; // 异或非变换 else dout_stage1 din; // 异或变换 // 同时计算变换后数据的 1 的个数和 0 的个数 ones_stage1 count_ones(dout_stage1); zeros_stage1 8 - count_ones(dout_stage1); end // 第二级直流平衡调整 always (posedge clk) begin if (balance_counter 0 || ones_stage1 zeros_stage1) begin // 根据上一轮的状态决定是否翻转 dout_stage2 (ones_stage1 4) ? {~dout_stage1[9], dout_stage1[8:0]} : {1b0, dout_stage1}; // 更新 balance_counter end else begin // 根据 balance_counter 的正负和当前数据的不平衡度做调整 end end这里的关键点是count_ones的实现。在 FPGA 里可以用 LUT 直接查表在 ASIC 里可以用加法树。但要注意8-bit 的 popcount 在 600MHz 下可能需要再打一拍否则组合逻辑延迟会成为瓶颈。2.2 控制周期与数据周期的切换逻辑TMDS 不是一直传数据的。在视频消隐期间通道 0 要传控制信号HSYNC、VSYNC、CTL0-3通道 1 和 2 要传前导码或保护带。这个切换逻辑看起来简单但实际做的时候有几个坑第一个坑是前导码的生成。在数据周期开始之前通道 1 和 2 需要发送 2-bit 的前导码通道 0 需要发送 4-bit 的前导码。这些前导码的值取决于即将发送的数据类型视频数据、音频包、InfoFrame 等。如果前导码错了接收端可能无法正确识别数据类型。第二个坑是保护带的插入。在控制周期和数据周期之间需要插入保护带确保接收端的时钟恢复电路不会失锁。保护带的长度和内容在规范里有明确定义但实际实现时容易和消隐期的其他信号混淆。我的做法是单独做一个period_fsm模块用状态机明确区分 Video Data Period、Data Island Period、Control Period 三种状态每种状态下的通道输出用多路选择器切换。这样逻辑清晰也方便后续调试。2.3 编码器流水线深度与 600MHz 时序收敛到了 HDMI 2.0 的 600MHz时序收敛是最大的挑战。TMDS 编码器的组合逻辑路径包括 popcount、比较、异或、多路选择如果不做流水线在 28nm 工艺下大概只能跑到 300-400MHz。我的经验是至少做三级流水第一级输入寄存 初步的 1 计数第二级变换逻辑 精确的 1/0 计数第三级直流平衡调整 输出寄存这样每级的组合逻辑深度控制在 6-8 个 LUT 以内在 28nm 下跑到 600MHz 是比较稳妥的。在 FPGA 里Xilinx 的 UltraScale 系列因为 LUT 结构不同可能需要调整流水线划分但总体思路一样。还有一个技巧是把 popcount 拆成两级先用 LUT 算 4-bit 的 popcount再用加法器合并。这样比直接用一个 8-input 的 LUT 查表要快因为 8-input LUT 在大多数 FPGA 架构里是不存在的。3. 音频与辅助数据的插入机制3.1 Audio Sample Packet 的组装时机HDMI 的音频不是独立通道传输的而是打包成 Audio Sample Packet在视频的 Data Island Period 插入到 TMDS 数据流中。这就带来一个问题音频采样率比如 48kHz和视频帧率比如 60Hz是异步的怎么保证音频包在正确的时机插入答案是使用音频 FIFO 帧同步的机制。音频采样先写入一个异步 FIFOFIFO 的读侧由视频时序控制。每一帧的 Data Island Period 到来时根据当前 FIFO 里的采样数和音频包的容量决定这一帧插入多少个音频包。这里的关键参数是N 值每帧的音频采样数和CTS 值音频时钟与像素时钟的比值。这两个值需要通过 ACRAudio Clock Regeneration包发送给接收端接收端用它们来恢复音频时钟。如果 N 和 CTS 算错了接收端恢复出来的音频时钟就会有偏差表现为音频断续或变调。我踩过的一个坑是在 48kHz/60Hz 的场景下N6144、CTS148500 是标准值但如果像素时钟不是标准的 148.5MHz而是 148.35MHz比如 1080p59.94CTS 需要相应调整。这个细节在规范里有说明但很容易被忽略。3.2 InfoFrame 的生成与更新策略InfoFrame 是 HDMI 用来传递辅助信息的包包括 AVI InfoFrame视频格式信息、Audio InfoFrame音频格式信息、Vendor Specific InfoFrame厂商自定义信息等。这些 InfoFrame 不是每帧都变但一旦视频格式或音频格式发生变化就需要及时更新。我的实现方式是为每种 InfoFrame 维护一个双缓冲Double Buffer处理器通过 APB 总线写入新的 InfoFrame 内容硬件在 VSYNC 到来时自动切换缓冲区。这样避免了在帧中间更新 InfoFrame 导致的撕裂问题。还有一个细节是InfoFrame 的校验和。HDMI 规范要求 InfoFrame 包的最后一个字节是校验和接收端会验证。如果校验和错了接收端可能直接丢弃这个包。所以硬件在组装 InfoFrame 时必须自动计算校验和而不是让软件去算。3.3 DDC 通道与 EDID 读取的硬件实现DDCDisplay Data Channel是 HDMI 用来读取接收端 EDID 的 I2C 通道。虽然它本质上就是一个 I2C 接口但在 HDMI Transmitter Subsystem 里它的实现有几个特殊之处时钟拉伸HDMI 接收端可能会拉伸 DDC 时钟硬件必须支持。EDID 分段读取当 EDID 超过 256 字节时需要通过 Segment Pointer 切换分段。热插拔检测HPD 信号的变化需要触发中断通知软件重新读取 EDID。我通常会在 Subsystem 里集成一个简单的 I2C Master专门用于 DDC 通信而不是复用系统里的通用 I2C 控制器。这样做的好处是时序可控而且不会和其他 I2C 设备产生仲裁冲突。4. HDCP 加密引擎的集成要点4.1 HDCP 在数据通路中的位置HDCP 加密是在 TMDS 编码之前进行的。也就是说视频数据先经过 HDCP 加密异或运算然后再做 TMDS 编码。这个顺序不能反否则接收端解密后会得到错误的数据。在 Subsystem 里HDCP 引擎通常是一个独立的模块通过 AXI-Stream 接口插入到视频通路中。它需要和处理器交互来完成认证握手但认证完成后加密运算是逐像素实时进行的不能有停顿。4.2 认证状态机与密钥更新HDCP 的认证过程比较复杂涉及多个步骤发送 Aksv、读取 Bksv、验证、计算 Km、生成 Ks、启动加密。这个过程由软件驱动但硬件需要提供寄存器接口和中断支持。一个容易忽略的点是密钥更新。HDCP 规范要求每 2 秒至少更新一次密钥或者每 128 帧如果更新失败必须重新认证。硬件需要提供一个帧计数器在达到阈值时触发中断通知软件执行密钥更新。我在一个项目里遇到过因为密钥更新不及时导致接收端每隔几秒黑屏一次的问题。排查了很久才发现是软件的中断响应延迟太大后来把密钥更新的优先级提高问题就解决了。4.3 HDCP 2.2 与 1.4 的兼容性设计如果你的 Subsystem 需要同时支持 HDCP 2.2 和 1.4设计复杂度会显著增加。HDCP 2.2 使用 AES-128 加密和 1.4 的流加密完全不同。通常的做法是集成两个独立的加密引擎通过多路选择器切换。但这里有一个坑HDCP 2.2 的认证过程需要和接收端协商版本如果接收端只支持 1.4Transmitter 必须回退到 1.4。这个回退逻辑需要在软件和硬件之间做好协调否则可能出现认证死锁。5. FPGA 验证阶段的实战经验5.1 用 OSERDES 实现 TMDS 串行化在 FPGA 上验证 HDMI Transmitter最常用的方案是用 Xilinx 的 OSERDESE2 或 Intel 的 SERDES。以 Xilinx 7 系列为例OSERDESE2 支持最高 10:1 的串行化比配合 5 倍或 10 倍的位时钟可以实现 1.4 的 3.4Gbps 每通道。具体配置时需要注意DDR 模式OSERDESE2 必须配置为 DDR 模式否则串行化比要翻倍。时钟关系像素时钟和位时钟必须是 1:10 的关系对于 10:1 串行化且相位要调好。Output BufferTMDS 是电流驱动模式FPGA 的普通 LVDS 输出可能不兼容需要外接电平转换或使用专用的 HDMI 发送芯片。我在一个 Artix-7 项目里直接用 OSERDESE2 驱动 HDMI 接口发现信号质量不太理想后来加了一级 TMDS 驱动器芯片比如 TI 的 SN75DP159才稳定下来。所以如果你的 FPGA 板子上没有专用的 HDMI 发送芯片这一点要提前考虑。5.2 眼图测试与预加重配置HDMI 2.0 的 6Gbps 速率对信号完整性要求很高。在 FPGA 上通常可以通过调整 Output Buffer 的预加重Pre-emphasis和摆幅Swing来优化眼图。我的经验是先用默认配置跑一遍用示波器看眼图。如果眼图闭合逐步增加预加重等级直到眼图张开。但预加重也不是越大越好过大的预加重会导致 EMI 超标。一般在实际项目中我会把预加重设在中间档位然后通过 PCB 走线优化来补偿。5.3 用 ILA 抓取 TMDS 编码前后的数据调试 HDMI Transmitter 时最有用的工具是 ILAIntegrated Logic Analyzer。我会在 TMDS 编码器的输入和输出各挂一个 ILA抓取几帧数据然后和预期值对比。这里有一个技巧用已知的测试图案。比如发送纯红色R0xFF, G0x00, B0x00然后看编码后的 10-bit 数据是否符合 TMDS 规范。如果不符合说明编码器有问题如果符合但接收端显示不对说明问题在串行化或物理层。我还遇到过因为 ILA 采样时钟和像素时钟不同源导致的抓取数据错位问题。后来改成用像素时钟作为 ILA 的采样时钟问题就解决了。6. SoC 集成中的时钟与复位设计6.1 像素时钟、TMDS 位时钟与音频时钟的域交叉在 SoC 集成时HDMI Transmitter Subsystem 通常涉及三个时钟域像素时钟域、TMDS 位时钟域、音频时钟域。这三个时钟来自不同的 PLL相位和频率关系复杂。我的做法是视频通路像素时钟域到 TMDS 位时钟域的跨接用异步 FIFO 或握手信号。音频通路音频时钟域到像素时钟域的跨接用异步 FIFO深度要足够容纳一帧的音频采样。寄存器接口APB 时钟域到像素时钟域的跨接用同步器或异步桥。这里的关键是FIFO 深度的计算。以音频 FIFO 为例如果音频采样率是 192kHz视频帧率是 60Hz每帧的音频采样数是 3200。FIFO 深度至少要能容纳两帧的采样数即 6400 个采样才能保证不会溢出或下溢。6.2 复位序列与亚稳态防护HDMI Transmitter 的复位序列很重要。如果复位顺序不对可能导致 TMDS 输出在复位期间出现毛刺接收端可能误判为有效信号。我通常的复位顺序是先复位寄存器接口和配置逻辑再复位视频和音频通路最后复位 TMDS 编码器和串行器释放复位的顺序相反。同时所有跨时钟域的信号都要加两级同步器防止亚稳态传播。还有一个细节是热插拔检测HPD的处理。当 HPD 信号变化时需要复位整个 Subsystem 并重新读取 EDID。这个逻辑要在硬件里做好否则软件可能来不及响应。6.3 低功耗设计考虑在移动 SoC 里HDMI Transmitter 的功耗是一个敏感指标。几个常用的低功耗手段时钟门控在没有视频输出时关闭 TMDS 位时钟和像素时钟。电源域隔离把 HDMI PHY 放在独立的电源域不用时完全断电。动态频率调整根据视频分辨率动态调整像素时钟频率。我在一个移动项目里通过时钟门控把 HDMI Transmitter 的待机功耗从 15mW 降到了 2mW 以下。关键是要确保门控逻辑不会导致状态机丢失状态所以门控前要先把状态机复位到一个已知状态。7. 常见问题排查与调试技巧7.1 接收端无显示或显示花屏这是最常见的问题。排查思路如下现象可能原因排查方法完全无显示HPD 未拉高、EDID 读取失败检查 DDC 通信、测量 HPD 电平显示花屏TMDS 编码错误、时序不对用 ILA 抓取编码前后数据颜色不对色彩空间转换错误检查 AVI InfoFrame 的配置间歇性黑屏HDCP 认证失败、密钥更新超时检查 HDCP 状态寄存器我的经验是先确认 HPD 和 EDID 没问题再看 TMDS 编码最后查 HDCP。这个顺序能覆盖 90% 以上的问题。7.2 音频断续或变调音频问题通常和 ACR 包的 N/CTS 值有关。如果 N/CTS 算错了接收端恢复的音频时钟就会有偏差。排查方法是用音频分析仪测量接收端恢复的音频时钟频率和发送端的音频时钟频率对比如果偏差超过 100ppm检查 N/CTS 的计算还有一个可能是音频 FIFO 溢出或下溢。可以在硬件里加一个 FIFO 水位计数器通过寄存器读出来看看是否在正常范围内。7.3 时序不收敛的典型场景HDMI 2.0 的 600MHz 时序收敛是老大难问题。除了前面说的流水线划分还有几个技巧使用寄存器输出所有跨模块的信号都打一拍再输出减少组合逻辑路径。避免宽多路选择器宽多路选择器在 FPGA 里会消耗大量 LUT而且延迟大。可以用分级多路选择器代替。手动例化 DSP 或 BRAM某些计数或存储逻辑用 DSP48 或 BRAM 实现比用 LUT 更快。我在一个 Kintex UltraScale 项目里通过把 TMDS 编码器的 popcount 改用 DSP48 实现把时序余量从 -0.3ns 提升到了 0.2ns。8. 从 1.4 升级到 2.0 的改造路径8.1 Scrambling 模块的插入位置HDMI 2.0 在 340MHz 以上引入了 Scrambling目的是降低 EMI。Scrambling 是在 TMDS 编码之前进行的用一个 LFSR 生成的伪随机序列和视频数据异或。插入 Scrambling 模块时要注意LFSR 的初始种子规范里有定义不能随便改。Scrambling 的使能条件只在 340MHz 以上使能以下不使能。和 HDCP 的顺序Scrambling 在 HDCP 加密之后TMDS 编码之前。8.2 更高串行化比的实现从 1.4 的 10:1 串行化升级到 2.0 的 20:1需要更宽的 OSERDES 或级联多个 OSERDES。在 Xilinx 的架构里可以用两个 OSERDESE2 级联实现 20:1。但要注意时钟的分频比和相位关系。8.3 向后兼容的设计策略如果你的 Subsystem 要同时支持 1.4 和 2.0最好的策略是参数化设计。把串行化比、Scrambling 使能、时钟频率等做成参数在例化时根据配置选择。这样一套 RTL 可以覆盖两种模式减少维护成本。我在一个项目里就是这么做的通过一个HDMI_VERSION参数控制综合出两个不同的网表分别用于 1.4 和 2.0 的场景。实测下来资源开销只增加了 15% 左右但省去了维护两套代码的麻烦。9. 写在最后的一些个人体会做 HDMI Transmitter Subsystem 这些年最大的感受是协议规范要读透但不能只读规范。规范告诉你应该怎么做但不会告诉你实际做的时候哪里会出问题。比如 TMDS 编码的直流平衡规范里只给了算法但实际实现时流水线怎么切、popcount 怎么优化全靠经验积累。另一个体会是验证要趁早。HDMI 的问题往往在系统联调时才暴露这时候改硬件的成本很高。所以我在项目里会尽早搭建 FPGA 验证平台哪怕是一个简化版的也能提前发现大部分问题。最后不要忽视软件的作用。HDMI Transmitter 的很多功能比如 EDID 解析、HDCP 认证、InfoFrame 更新都需要软件配合。硬件设计时要考虑软件的易用性比如寄存器布局要清晰、中断要明确、状态要可读。这样软件同事调试起来也轻松整个项目的进度也会更顺。