上一篇把HDMI发送端跑通之后我以为实验已经收尾了——毕竟TMDS编码、像素时钟、差分输出都验证过画面也能稳定点亮显示器。可真正把接收端加进来才发现发送只是把数据推出去而HDMI数据的接收发送整个闭环要面对的是另一套问题怎么把差分对上的串行字节重新组合成像素怎么让源端愿意给你出信号以及怎么把接到的画面不丢帧地再送出去。这篇是系列实验的续篇重点记录接收侧和闭环回灌调试的过程。适合正在做HDMI/DVI相关FPGA实验的人尤其是那些发送端已经能出画面、一接接收端就花屏或握不上手的开发者。下面的内容会尽量把调试思路讲透而不是堆结论。1. 从单向发送到收发闭环实验平台的搭法与分辨率策略1.1 为什么上一轮的发端成果不能直接复用从发送切到接收第一反应是管脚够不够用。发送端在FPGA内部只占一组串化输出和像素时钟PLL接收端却要占用输入引脚做差分解调、解串、对齐、解码资源占用大一圈而且输入侧的信号完整性比输出侧苛刻得多。上一轮发送实验里的输出差分对走线、串联匹配电阻都能继续用但接收引脚如果板子本身没有做输入终端匹配就得外接100Ω的差分电阻。这个差别在做实验一开始就要确认否则后面所有问题都会被信号根本没进来掩盖。发送端的逻辑相对好做拿到像素、控制信号按像素时钟一组一组地编码输出就行最多关注一下PLL相位和引脚约束。接收端则一上来就要面对串行字节边界在哪的问题还要处理输入信号的电气特性。我这次用的板卡接收引脚上没有板载终端HDMI转接板倒是带了100Ω差分电阻但板内走线比较长在74.25MHz以下还能接受频率一高眼图就开始变差。做这个实验前我建议先查三件事转接板的差分电阻是否匹配、源端输出的信号幅度是否正常、接收引脚附近有没有预留测点。前两件事决定你能否收到干净的信号第三件事决定你后面能否快速定位问题。1.2 这次收发闭环的整条链路这次实验的链路是我在工位上搭的主机作为信号源通过HDMI线连到第一块转接板进入FPGA的接收模块FPGA内部把解出来的像素流做跨时钟域处理后再交给发送模块发送模块通过第二块转接板输出到显示器。两块转接板完全独立5V、HPD、DDC线都不互相牵连不然源端和显示器可能会互相拉扯电平。示波器探头的测点分别设在两个HDMI座附近用来分别观察输入和输出。选转接板时不用追求昂贵但建议确认插座是不是带屏蔽的、地线是否铺得完整。便宜转接板在640x480和800x600这种低时钟下基本没问题到了1080p就可能出现丢包和花屏。实验初期我会先默认它是好的但如果同样分辨率下换了两块板子现象不同就先换回原板排查信号路径。这里有个小习惯每次改完硬件先把线重新插拔一次确认接触良好很多灵异现象都是松动的HDMI头造成的。1.3 先定分辨率再选时钟别一上来就碰4KHDMI在TMDS模式下像素时钟是多少每根数据通道上的串行速率就是它的10倍。因为每个8bit像素要编码成10bit符号三根数据通道并行传输所以串行速率等于像素时钟乘以10。下面是我常用的几个验证档位分辨率刷新率像素时钟每通道串行速率640x4806060Hz25.175MHz251.75Mbps800x6006060Hz40.0MHz400Mbps1280x7206060Hz74.25MHz742.5Mbps1920x10806060Hz148.5MHz1.485Gbps为什么必须分级验证因为在FPGA里逻辑代码在低时钟和高时钟下行为基本一样但PCB走线、连接器、线材的限制完全不同。我第一次直接跑1080p接收端出来的画面只有偶尔一帧正常排查很久才发现是转接板差分线太长眼图早就闭上了。后来降到800x600问题立刻消失——这说明RTL基本正确瓶颈在物理层。所以正经顺序是25MHz通、40MHz通、74.25MHz通最后再试着冲148.5MHz。每一档都要确认显示正常之后再往上。2. TMDS到底传了什么接收端拆包的原理基础2.1 三对数据线和一对时钟线的分工HDMI在TMDS模式下物理层由三对数据差分线和一对时钟差分线组成每对都是P/N两根走低压差分信号。三对数据线分别对应像素的B/G/R分量在DE有效期间一路传数据DE无效的消隐期里数据通道还要承载HSYNC、VSYNC以及控制信号。正是这个机制决定了接收端不能像解UART那样简单地按位接收而是要区分数据区和控制区两个状态来拆包。时钟差分线传的是像素时钟比如720p用74.25MHz1080p用148.5MHz。接收端用这个时钟去采样三根数据线所以HDMI没有传统串行通信里的时钟数据恢复CDR时钟是单独从旁边送过来的。这一点让实验难度下降不少。真正麻烦的是三根数据线上的串行符号不是对齐的因为发送端出来时虽然字节对齐但经过PCB走线和连接器之后每根线延迟不同接收端必须自己重新找对齐边界。另外这个接口上还有一组I2CDDC通道、一根热插拔检测线HPD和一根5V电源线它们跟高速数据是两套系统。实验时经常出现明明发了信号却没有画面的情况最后发现是低速握手没通过。我一开始也栽在这上面总以为只要高速链路配好就能直接点亮显示器实际上源端非常依赖低速通道反馈的信息。2.2 8bit像素到10bit符号的编码逻辑TMDS编码本质上是把每路8bit像素扩展成10bit符号再逐bit串行发送。具体做法是把8bit数据拆成低5位和高3位低5位通过5b/6b的编码变成6位符号高3位通过3b/4b的编码变成4位符号然后根据这一路之前发送符号的直流偏移情况决定整个10bit是否取反最终凑出直流平衡的符号。所谓直流平衡就是长时间看下来1和0的个数尽量相等不会让差分线上累积出明显的直流分量。为什么宁可多花两bit也要做这件事因为差分信号如果长期偏向某一电平接收端的前端放大器会被拉偏而且容易辐射能量干扰别的设备。把电平平均分配之后电磁干扰变小信号也更稳定。这个编码不是随便定一种查表就能用的它必须保证控制符号有固定的特征模式、数据符号不会跟控制符号混淆而且符号内要有足够多的跳变沿方便接收端采样。2.3 接收拆包的三步采样、对齐、解码接收模块的常规流程可以拆成三步。第一步是采样用像素时钟的双沿去抓三根数据线上的bit流FPGA里一般会调用解串原语把串行数据转成10位并行。第二步是对齐从并行数据里找出符号边界因为无法预知当前抓到的10bit是从哪个bit开始切分的只能依靠控制符号的特征模式来确定边界也就是常说的bit slip。第三步是解码按10bit符号反查回8bit像素同时把DE、VSYNC、HSYNC从控制区间的符号里还原出来。这中间最容易出问题的就是第二步。我一开始写的对齐逻辑只在DE有效期间找数据符号结果总是锁不定。后来明白必须在DE无效期间利用控制符号对齐因为控制符号的模式是固定且唯一的而像素数据什么值都可能出现。改完之后只要输入信号质量不差一次就能锁住。这一步也是接收端和发送端最不对称的地方发送只管发出去接收却必须先猜边界再解码猜错一个bit整个通道都是乱的。3. 发送侧先别急着接显示器把信号量一遍3.1 示波器测差分对的测点与接法接收端调了好几天我开始怀疑发送信号本身可能就有问题于是把示波器拿出来重新量输出。测量位置建议放在HDMI连接器附近的过孔或焊盘上而不是FPGA引脚上因为板内走线、转接板、线材才是真正影响信号完整性的环节FPGA引脚上的波形再漂亮也没用。有差分探头最好没有的话也可以用两个相同型号的通道做减法但地线一定要短否则测出来的高频毛刺其实是探头地线感应的不是真实信号。带宽是另一个需要接受现实的地方。如果手头只有100MHz带宽的示波器去测74.25MHz的像素时钟通道问题不大但想看清1.485Gbps的串行数据眼图是想多了。在低速档位波形看起来已经接近方波在高速档位变成正弦波是正常的毕竟带宽不够时谐波被滤掉了。所以发送侧自检我建议就在25MHz和40MHz两档把波形和眼图看明白够用就好不要用示波器能力去对抗物理极限。3.2 输出差分对的基本检查项具体看几个指标眼高、眼宽、上升沿还有时钟通道与数据通道之间的相对延迟。眼图如果明显闭合第一反应不是改RTL而是检查差分对终端匹配和驱动强度。很多开发板的发送端默认驱动强度偏强在短走线下会振铃眼图中间反而像长了毛刺降低IO驱动强度或者串一个22Ω到33Ω的电阻往往立刻改善。这一条在直连显示器的短线上尤其明显我调完驱动强度之后显示器上的噪点肉眼可见少了很多。现象可能原因优先检查方向眼图开口小、中央有毛刺驱动过冲或阻抗不连续串阻、IO驱动强度、连接器接触时钟通道比数据通道提前或滞后明显PLL输出相位配置不对发送原语的时钟-数据相位设置画面偶发黑条或掉行像素FIFO读空空标志处理逻辑、FIFO深度3.3 发送侧最常见的三个毛刺第一个毛刺DE信号和像素数据没有严格同步。DE是消隐和数据区的分界线如果DE变化时数据还是上一行的显示器上就会出现一条水平错位。解决办法不是加延时而是让DE和像素流在同一个时钟域里经过同一拍寄存器再送进编码器保证切换瞬间边界对齐。我上一轮实验偷懒直接用了组合逻辑结果换了台显示器才暴露出问题看着像时序违规其实根因就这一条。第二个毛刺像素FIFO读空却还在继续编码。读空时数据总线要么保持旧值、要么是零编出来的全是一个个黑色像素或假控制符号。现象是整个画面每隔一段时间出现一条黑带尤其在视频流带宽不足时更明显。正确做法是检测到空标志后停止读操作并让发送端保持当前行状态而不是强行灌零。灌零表面上看逻辑简单实际上会把消隐状态破坏掉。第三个毛刺时钟通道与数据通道的相位没有校准。有的FPGA发送原语允许调整输出时钟相对数据的相位如果差半个周期接收端也能勉强解码但眼图裕量很小。把时钟对数据通道的相位放在眼图正中间是发送侧自检中最值得花时间的一项。判断标准不是显示器能出图而是把时钟相位左右各移一点点仍然稳定才行否则线材老化或者换一根线可能就触发花屏。4. 接收侧调试的完整排查链路EDID、对齐与像素定位4.1 没信号输入时首先怀疑低速握手接上主机之后如果示波器上什么都看不到问题通常不在高速链路而在低速握手。HDMI要求接收端在HPD脚上给出高电平同时通过DDC本质是I2C提供EDID数据源端读到EDID之后才会启动TMDS输出。这个顺序反了的话源端会认为没有合法显示器一根数据都不发。我这次就遇到完全静默的状态拿逻辑分析仪抓DDC发现主机确实在发读请求但接收端始终没有ACK最后查到是扩展板上拉电阻太大I2C高电平爬不上去。这类问题用万用表量电阻根本看不出来必须看波形。给一个检查顺序先量5V是否到位再量HPD是否为高然后用逻辑分析仪抓DDC波形确认有没有ACK。如果ACK正常再用I2C回读0x50地址的EDID头00 FF FF FF FF FF FF 00能读到就开始排查高速侧。很多新手一上来就改接收RTL结果是在跟空气斗智斗勇。我后来把这个顺序写成固定流程再没在握手上浪费过时间。4.2 接收模块的初始化顺序不能乱等源端真的输出信号了接收模块的启动顺序仍有讲究。我踩过的坑是先在代码里对齐再等PLL锁定结果PLL未锁定期间对齐逻辑一直误锁导致后面恢复很慢。正确顺序是先复位解串器等待像素时钟稳定然后观察DE是否周期性出现确认DE干净之后再启动字对齐。如果DE没起来就做对齐控制符号位置是错的锁出来的边界也是错的表现出来的就是偶尔能出一帧、其他全是雪花。为什么对齐必须放在DE之后因为字对齐需要利用DE无效期间的控制符号作为唯一的边界特征DE没起来说明控制符号还没有稳定这时候强行对齐等于拿噪声当参考。我在代码里加了两个状态信号clock_ready和de_detected只有两个都置位才允许对齐状态机往下走。加完之后接收模块重新上电的首次出图速度明显稳定多了不再需要反复手动复位。4.3 花屏、错色、行不同步的定位方法画面能出来不代表解码正确。我实际遇到过几种典型症状整理成表更便于对照症状定位方向验证手法满屏粉红或红蓝互换数据通道顺序错误发送纯红/纯蓝测试图观察RGB分量满屏杂色但能看到水平同步线符号对齐bit slip没锁对手动把对齐位置在0到9之间滑动只有上半屏正常、下半屏雪花DE或VSYNC提取的时序与像素错位检查DE延迟、FIFO读写指针颜色正确但有垂直细条纹串行采样相位不对调整输入延时tap或时钟采样沿为了快速区分这些症状我给接收端做了一个测试图注入功能当检测到某个按键时FPGA内部不再解析外部输入而是直接生成整屏纯色图送到发送链路。这样如果发送链路本身正常显示器上就应该出现对应的纯色一旦出现偏色立刻能判断是外部输入通道映射错还是内部输出映射错。这个手段比对着波形猜快得多做完测试图注入之后整个排错效率提高了一个量级。还有一个容易被忽略的坑接收端解出来的像素时钟和本地送给发送端的像素时钟不是同一个时钟。如果两个时钟域之间没有用异步FIFO隔离哪怕频率完全相同相位漂移积累到一定程度也会导致采样出错现象是画面隔一段时间闪一下或者出现一条斜纹。这就是下一章要重点处理的闭环问题。5. 回灌发送的最终闭环FIFO缓存与时钟异步处理5.1 接收像素时钟不能直接驱动发送端闭环节点上最容易犯的错误是把接收解出来的像素时钟直接接到发送模块的时钟输入上。表面上源端是74.25MHz本地PLL也是74.25MHz好像没什么问题但实际上两个时钟不可能绝对同频晶振本身存在ppm级误差长时间运行就会产生相位滑移。而且接收到的数据流本身是不连续的可能在消隐、对齐重锁、FIFO读空时突然断掉直接用这个时钟去驱动发送端显示器会接受到断断续续的时序最终表现为画面周期性地跳一帧或出现一条斜纹。所以必须把接收侧和发送侧拆成两个独立的时钟域中间放一个异步FIFO。接收侧用自己的恢复时钟往FIFO里写发送侧用显示器端要求的本地像素时钟从FIFO里读。这样一来两边互不拖累发送端面对的是一个干净的、连续的数据源显示器看到的时序才符合规范。5.2 最小可用方案先做两行FIFO再升级整帧缓存我做闭环实验时先用了两行深度的FIFO。为什么不是一行因为DE跨度通常超过一行如果只在DE有效开始时取数据读出的行内容可能不完整两行缓冲允许写入端和读取端在行边界附近错开一些容错更好。两行FIFO的做法是接收端把解析出来的一行像素按行写进FIFO发送端检测到DE开始后从FIFO读出一整行送到编码器。在800x600分辨率下两行FIFO已经足够让回灌画面稳定但遇到源端帧率和本地帧率不一致时依然会出现周期性撕裂。治本的方案是加完整帧缓存接收端把整帧存入帧缓冲发送端按自己的时序读出。当接收端写入指针追上了读指针说明源端太快就丢一帧当读指针追上了写指针说明源端太慢就重复一帧。丢帧和重复帧的决策一定要放在发送端一侧做否则显示器看到的时序会乱。这个方案我最后才上因为它涉及DDR控制器或BRAM调度代码量和调试复杂度都比两行FIFO高一个量级但只有它才能真正解决帧率差问题。5.3 回灌链路的验证手法与最终结果验证回灌链路是否真正稳定只对着显示器看是不够的。我给接收端解出来的画面叠了一层简单的计数条在画面右上角每帧递增一个数字然后让输出接到第二台显示器。这样如果有丢帧或者重复帧数字会跳变不连续肉眼可以立刻看出来。更好的做法是让主机播放一段带时间码的视频用手机慢动作拍摄输出画面逐帧对比时间码是否连续但实验阶段用计数条已经能抓出大多数问题。实测下来800x60060Hz的闭环链路可以稳定挂机一整天不丢帧不撕裂720p在短时间测试里也能跑通但连续半小时偶尔会闪一下。定位后发现是接收端输入信号裕量不足偶尔误码导致对齐重锁跟发送端没关系。把转接板换成走线更短的板子之后720p就不闪了。这个结果也印证了前面分级验证的思路多数异常不是逻辑问题而是物理层没达到条件。如果再让我做一遍这个实验我会把物理层测量放到更靠前的位置。第一次做发送实验时只顾着跑逻辑导致接收侧出问题后花了很多时间排查RTL最后才发现是信号完整性问题。建议还没动手的人尽量先准备好示波器测点并且从最低分辨率开始把每一档的接收正常、发送正常、回灌正常分别标记清楚再往上一档走。这个习惯能让整个系列实验的进度更可控排查问题时也更容易把逻辑问题和物理问题分开处理。