FPGA HDMI视频输入与环路输出:完整解析视频数据链路

FPGA HDMI视频输入与环路输出:完整解析视频数据链路 先说一个我经常在新手群里看到的现象几乎每个人都做过HDMI输出的彩条实验开发板接上显示器五颜六色的竖条一显示大家都觉得FPGA这块肯定入门了。但紧接着就会陷入一个尴尬——所有教程都在教你“怎么把数据推出去”却几乎没有教程教你“怎么把真实视频接进来”。没有视频输入后续的图像处理就是无源之水。而这个实验——HDMI视频输入与环路输出正好把这最后一块拼图补上。它让电脑画面从HDMI进开发板再从开发板送到显示器数据流完整穿过RX和TX让你第一次真正看懂“视频在FPGA里是怎么流动的”。如果你做完了彩条输出、正准备往图像处理方向走或者看过各种HDMI接口方案但始终没想通整个链路这篇文章就是给你准备的。1. 为什么“环路输出”是FPGA视频入门最适合的第一个实验1.1 视频开发从“只输出”到“有输入”的关键转变FPGA视频开发这件事很多人的第一个实验都是“输出”。输出彩条、输出棋盘格、输出一张图片本质上是同一个套路生成像素时钟用计数器产生行场同步和DE信号把内存里的数据按顺序推给显示器。这套流程跑通之后你对VGA或者HDMI的输出时序就有了直观理解。但紧接着问题就来了真正的图像处理系统比如边缘检测、颜色转换、目标跟踪处理的都是外部进来的实时视频不是自己生成的彩条。这时候你需要一个能接收外部视频的板卡和一套完整的输入通路。环路输出恰好是连接“输出”和“输入”之间最短的一座桥。它的要求很简单开发板的HDMI输入接口接上电脑HDMI输出接口接上显示器FPGA不做任何复杂算法把进来的像素总线原封不动地透传到输出端。这个过程里你会接触到一个完整视频系统所需要的全部基本元素HDMI物理接口、TMDS解码编码、像素时钟恢复、IIC寄存器配置、EDID握手、HPD热插拔检测。而且因为只是透传你不需要写任何图像算法可以把全部精力放在理解整个链路本身上。从学习曲线看这个实验的坡度非常平缓。手边有板子、有根HDMI线、有台带HDMI输出的电脑就能开工。黑金开发板中带HDMI输入输出接口的型号大多可以直接套用这套流程芯片方案也比较统一网上能找到大量参考资料。一旦把环路跑通后续做图像处理就只是在这条通路上“插入”自己的模块思路会清晰很多。1.2 环路输出在真实设备中的价值有些人可能会觉得环路输出就是把画面绕了一圈又送出去这能有什么用实际上它在真实产品里用得相当普遍。最简单的例子是视频分配器或者拼接控制器一个信号源送入设备设备内部既要本机处理比如录制、分析、叠加OSD又要让原始信号继续往下级设备传输这就必须支持环路输出。监控领域尤其典型一台编码器既要抓取摄像头画面做压缩存储又要保留原始的模拟或HDMI信号让本地监视器预览环路输出和“直通”几乎是标配功能。另一个典型场景是低延迟信号转发。手术内窥镜、工业视觉、赛事转播这类对实时性要求极高的应用信号每经过一级处理多一帧延迟都可能造成严重后果。FPGA做环路输出时数据几乎不经过存储延迟可以压缩到几个像素时钟周期以内这是纯软件方案很难做到的。理解了这个背景再回头看这个实验你就知道它不是一个“玩具演示”而是一类真实产品的核心技术基础。1.3 一个实验覆盖的技能点清单我习惯把一个实验的价值拆成技能点来看。这个实验表面上是“画面进去又出来”实际上它帮你打通了FPGA视频开发的底层链路IIC从设备配置ADV7611和ADV7513的寄存器初始化理解芯片级外设怎么与FPGA协作像素总线时序DE、HSYNC、VSYNC和24位RGB数据之间的对齐关系时钟恢复与应用HDMI输入信号中的像素时钟由RX芯片恢复出来FPGA直接使用这个时钟驱动输出逻辑EDID与HPD握手源端怎么知道你支持什么分辨率为什么要等HPD拉高之后显卡才开始输出基础调试手段ILA在线逻辑分析仪抓内部信号观察总线上的时序波形约束文件实践时钟约束、引脚约束以及多个时钟域同时存在时的处理这些技能每一个都是后续做任何视频相关FPGA项目的前提。早点在一个透传实验里把它们全部碰一遍比之后在做复杂算法时手忙脚乱地补课要划算得多。2. 硬件链路拆解信号从HDMI座子到FPGA再到显示器2.1 为什么要用RX/TX专用芯片而不是FPGA直连HDMI很多刚接触FPGA的人会有个疑惑FPGA引脚那么多、速度也不慢为什么不直接把HDMI的TMDS差分信号接到FPGA上做解码非要中间加一颗芯片答案要从HDMI物理层和协议层两方面说。HDMI的TMDS链路本质上是一种高速串行差分传输每条数据通道的速率在1080P60下大约为1.485Gbps像素时钟148.5MHz每个像素10bit编码3条数据通道分摊。这个速率如果你自己用FPGA的普通IO去解引脚速率压力很大而且HDMI的信号编码包含大量直流平衡和通道对齐逻辑要正确处理必须使用专门的收发器或者经过授权许可的HDMI IP。Xilinx的HDMI IP是需要单独授权购买的对于学习板卡来说成本完全不合理。相比之下ADI的ADV7611接收端和ADV7513发送端这类桥接芯片把物理层接收、TMDS解码、时钟恢复、色彩空间转换全部封装好了FPGA只需要面对一组并行RGB信号和行场同步信号。这就像你不需要自己搭建一个完整的网卡驱动栈只需要操作一个已经帮你做好协议转换的串口芯片一样。从教学角度看这种“芯片帮你搞定物理层FPGA专注像素处理”的分工恰好是最适合入门的架构。你要学的是视频数据的流动方式而不是把整个HDMI协议栈从零造一遍。2.2 RX端ADV7611TMDS进去并行RGB出来以黑金开发板上常见的ADV7611为例这颗芯片的输入就是HDMI座子出来的TMDS差分对包括3个数据通道和1个时钟通道。芯片内部会完成几件事首先是时钟恢复从TMDS时钟通道或者数据流中恢复出像素时钟这个时钟会直接从芯片的像素时钟引脚输出来驱动下游所有逻辑其次是通道解串和8b10b解码把1.485Gbps的串行数据恢复成24位并行RGB数据最后是同步信号提取从数据流中把VSYNC、HSYNC和DE信号分离出来。对FPGA来说ADV7611出来的信号极其友好一个像素时钟一组24位RGB数据三个同步信号。你甚至可以把它理解成一台“数字摄像头传感器”只是接口变成了并行RGB。它支持输入最高1080P60在入门阶段绰绰有余。需要注意的是这颗芯片的输出格式可以通过IIC配置可以输出RGB888也可以输出YCbCr422数据位宽会因此变化。黑金板卡的例程一般默认配置成24位RGB888和后续图像处理模块的接口习惯比较一致。2.3 TX端ADV7513并行RGB进去TMDS出来发送端ADV7513的工作流程正好相反。FPGA把像素时钟、24位RGB数据、行场同步信号和DE送到芯片输入引脚芯片内部把这些并行数据编码成TMDS串行信号再从HDMI座子输出给显示器。ADV7513支持最高150MHz像素时钟对应1080P60完全没问题。发送端有一个很重要的细节HPD信号。HPD是HDMI源端用来检测显示器是否连接的引脚对电脑来说HPD没有拉高就意味着你没有接显示器它根本不会启动HDMI输出。在设计上HPD可以由ADV7513内部寄存器直接控制也可以由FPGA来控制。黑金方案中HPD信号通常由FPGA在初始化流程的最后阶段主动拉高这样做的好处是时序完全可控——你可以先配置好TX和RX等一切就绪之后再告诉源端“我准备好了”保证源端一启动就能顺利出图。2.4 IIC拓扑两片芯片挂同一条总线ADV7611和ADV7513的寄存器配置都通过IIC接口完成这两颗芯片可以挂在同一条IIC总线上。ADV7611的IIC地址通常默认在0x988位寻址模式ADV7513则根据地址引脚的电平通常配置在0x72或0x7A7位寻址模式。FPGA作为IIC主机通过两个不同的地址区分这两颗芯片。配置顺序上我个人的习惯是先配置TX端ADV7513再配置RX端ADV7611最后拉高HPD。原因很直接——HPD代表的是一整条链路的状态如果RX还没准备好提前告诉源端“有显示器”会导致源端开始输出但后续链路却没接管数据容易出现黑屏或者花屏。初始化顺序就像开一条生产线要先确保下游设备就绪才能通知上游开机。黑金的例程工程里一般都有一个IIC写寄存器模块只要把配置表替换成自己板卡对应的寄存器值即可。3. 时序和时钟域环路输出“零延迟透传”的核心秘密3.1 VESA时序一个1080P60画面的基本单位聊环路输出绕不开VESA时序。很多人初学FPGA输出视频时已经接触过这部分但往往没有真正理解它的含义。我这里再用1080P60举个具体的例子。1080P60的参数如下参数数值像素时钟148.5MHz水平有效像素1920水平总像素2200垂直有效行数1080垂直总行数1125行同步宽度44像素场同步宽度5行行同步极性正极性场同步极性正极性简单理解就是每输出一个像素像素时钟跳一次每扫描完1920个有效像素加上消隐区里的“额外”像素一共2200个像素算一行这时候HSYNC就会拉高一下每扫描完1080行有效行加45行消隐一共1125行算一帧VSYNC拉高一下。显示器就是靠HSYNC和VSYNC来确定画面的刷新节奏靠DE信号来判断当前传输的是不是有效像素。环路输出实验里FPGA做的事情本质上就是把这个节奏“原封不动”地接过来再送出去。3.2 EDID和DDC握手源端怎么决定输出什么画面EDID是HDMI协议里非常关键但又容易被忽略的一环。源端比如电脑显卡在上电或者检测到HPD拉高之后会通过DDC通道本质上是IIC总线读取显示器的EDID数据。EDID是一段128字节的扩展显示标识数据里面记录了显示器支持的分辨率、像素时钟范围、色彩格式等信息。显卡读完EDID之后会选一个自己支持且EDID中声明支持的分辨率按照对应的VESA时序开始输出。在环路输出实验里有个常见的困惑电脑接上开发板之后为什么能识别出显示器原因就是ADV7611内部带有EDID RAMFPGA在初始化时通过IIC把黑金板卡预置的EDID数据写入芯片内部源端读取时由芯片自动响应。如果EDID没有写成功或者校验和不对源的端表现是“没有检测到显示器”直接不输出。EDID还有一个细节是全字节求和校验最后一个字节的作用是让整个128字节求和为0如果写错了一个数显卡就会拒绝使用这份EDID画面自然出不来。3.3 同一个时钟域直接贯穿为什么环路不需要跨时钟域缓冲环路输出最让我觉得巧妙的地方在时钟域设计。ADV7611恢复出来的像素时钟直接作为整个数据通路的时钟不仅RX逻辑用它TX端发送逻辑也用它。也就是说从输入总线上的24位RGB数据到输出总线上的24位RGB数据全部处于同一个时钟域里中间不需要异步FIFO、不需要缓存一帧图像、不需要跨时钟域同步。实际Verilog代码里最简单的实现就是几拍寄存器打拍RX送来的数据打两拍缓冲一下然后直接赋值给TX总线。我用过的实际工程里从RX引脚到TX引脚的延迟通常只有几个像素时钟周期大约几十纳秒人眼完全感知不到。这正是环路输出这类应用的杀手锏——对实时性要求高的信号链这种“零缓冲”设计是帧缓存方案做不到的。3.4 帧缓存方案的延迟账都花在哪里有对比才更容易理解环路方案的优点。很多人做视频输出时习惯先把一帧数据写进DDR需要输出时再从DDR读出来。这种帧缓存方案的优势是灵活可以做格式转换、分辨率缩放、多路拼接但代价是延迟。延迟主要花在这几个地方写入端要等一整帧数据写完才能开始下一帧操作大约16.7毫秒输出端要等读帧命令调度和DDR行缓存填充再加几毫秒如果中间有缩放或者旋转模块还会额外增加几十行像素的流水延迟。综合下来帧缓存方案至少比纯流式方案多出两帧以上的延迟。在一些需要“眼睛盯着显示器操作”的场景里这个延迟非常明显你鼠标动一下画面要几十毫秒后才响应。环路输出走的是纯流式通路不需要动DDR延迟控制在像素级。当然这并不意味着帧缓存方案不好它只是适用场景不同。做环路输出的核心价值是透明传输做图像处理或者格式转换帧缓存则是必需品。理解了这两条路径的取舍你以后选型时心里就有底了。4. 代码框架从空白工程到点亮环路4.1 IIC配置状态机与寄存器初始化顺序整个工程的代码量并不大但IIC配置模块的初始化顺序需要认真对待。我习惯把配置过程设计成一个简单的状态机依次完成以下几个步骤等待FPGA全局复位释放再等待一个固定的上电延迟一般100ms左右配置ADV7513的输入格式、输出格式、同步信号极性设置为HDMI模式配置ADV7611的输入通道、输出接口格式、色彩空间转换使其输出24位RGB888向ADV7611的EDID缓冲区写入预设EDID数据拉高HPD信号通知源端链路就绪进入等待或运行状态配置表通常以“寄存器地址 配置值”的二维数组形式保存在Verilog的ROM里状态机逐条写入。为了降低初学难度黑金的例程工程里一般会把这套逻辑封装成独立的IIC控制器接口就是“寄存器地址”、“数据”和“触发写脉冲”这么简单。你可以先直接用现成模块跑通之后再自己动手写一版这样理解更深。ADV7611和ADV7513的关键寄存器配置项大致如下具体值以你板卡配套例程的配置表为准芯片寄存器作用ADV76110x0D解除关机状态ADV76110x14选择HDMI输入通道ADV76110xAF配置输出RGB格式ADV75130x15输入格式RGB888ADV75130x16输出格式RGB888ADV75130x40设置HDMI模式ADV75130x98HPD控制4.2 最小数据通路代码环路输出最核心的数据通路代码简单到让人怀疑是不是漏了什么。假设RX端信号已经约束好最小实现无非是这样// ADV7611恢复的像素时钟作为整个通路的时钟 wire rx_pclk; wire rx_vsync, rx_hsync, rx_de; wire [23:0] rx_data; reg tx_vsync_r, tx_hsync_r, tx_de_r; reg [23:0] tx_data_r; // 环路输出打两拍缓冲后直接透传给ADV7513 always (posedge rx_pclk) begin tx_vsync_r rx_vsync; tx_hsync_r rx_hsync; tx_de_r rx_de; tx_data_r rx_data; end assign tx_vsync tx_vsync_r; assign tx_hsync tx_hsync_r; assign tx_de tx_de_r; assign tx_data tx_data_r;打完两拍寄存器是为了改善时序质量。直接从芯片引脚进来的信号如果大量组合逻辑直连输出时序收敛会比较紧张打两拍相当于给数据一个重新对齐的机会。这段代码看着简单但它已经是流式视频处理最典型的结构——模块的输入是一组像素总线输出也是一组像素总线你要做的图像处理模块只要插在这中间就行。这就是为什么说这个实验是图像处理的地基。4.3 加一个OSD叠加来验证通路真的通这里有个我的经验环路输出直接透传画面一亮就成功看起来很有成就感但透传本身太顺了反而不容易暴露问题。想确认整条数据链路确实可靠我建议在透传基础上加一个简单的OSD叠加在画面左上角放一个醒目的色块或者显示几条彩色条纹这样你能立刻判断数据通路是否真正工作正常。实现OSD叠加不需要复杂的坐标模块直接用行场计数器就行reg [11:0] h_cnt; // 行内像素计数 reg [11:0] v_cnt; // 帧内行计数 // 在DE有效时计数 always (posedge rx_pclk) begin if (rx_de) begin if (h_cnt 1919) begin h_cnt 0; if (v_cnt 1079) v_cnt 0; else v_cnt v_cnt 1; end else h_cnt h_cnt 1; end else if (!rx_hsync) begin h_cnt 0; end end wire osd_en (h_cnt 100 h_cnt 200) (v_cnt 100 v_cnt 160); // 叠加一个红色块同时不影响原图像 assign tx_data osd_en ? 24hFF0000 : rx_data;这个模块会让画面左上角多出一个100像素宽、60行高的红色块。别小看这几行代码它验证了两件事一是你的RX像素时钟和同步信号确实被正确恢复否则计数位置会乱跑二是透传通路上的数据是逐像素可信的。以后你想叠加真正的文字、边框、帧率显示都是从这个小OSD扩展出来的。4.4 约束文件与时钟约束环路输出工程的约束文件同样值得认真写。引脚约束要严格对照原理图RGB数据的每根线、VSYNC、HSYNC、DE、像素时钟和IIC的SDA、SCL每一个都不能出错。我第一次做这类实验时想当然地以为原理图上同样的网络名可以随便对应结果红蓝通道对调画面颜色彻底不对排查了半天才发现是引脚映射错了。时钟约束上有一个隐蔽的坑RX恢复出来的像素时钟是ADV7611芯片的输出Vivado默认会把它当作primary clock或者根本没有时钟定义。这种情况下逻辑路径的时序分析可能不准。我建议在XDC里把RX恢复时钟明确建立为一个时钟并且用set_clock_groups -asynchronous把RX时钟和FPGA主时钟设为异步时钟组。这样IO时序约束不会乱报综合布局布线也能正确处理跨时钟域路径。如果你在工程里插入了ILAILA的采样时钟建议直接用RX像素时钟这样抓到的波形和实际画面节奏完全一致。5. 上板实测三个高频问题和它们的定位链路5.1 黑屏优先查HPD链路再查EDID环路输出上板之后最常遇到的情况就是黑屏。这种问题有个排查顺口溜“先查HPD再查EDID最后查IIC。”HPD没拉高之前电脑根本不会启动HDMI输出所以你在这后面看什么都是黑的。HPD排查很直白用万用表量HDMI座子的19脚看看是不是高电平。如果FPGA代码还没拉高HPD那就是初始化时序的问题如果HPD已经是高电平但电脑依然显示无信号接下来查EDID。EDID对应的现象是系统能识别到“有个显示器”但分辨率列表中可能什么都不显示或者显示为“未知显示器”。这时候可以用ILA抓ADV7611的IIC回读流程看源端是否成功发起了DDC读取以及回读的数据是否符合预期。特别注意EDID最后一个校验字节很多“源端识别不了显示器”的问题最后都出在这个字节上。如果HPD和EDID都正常还是黑屏那就把ILA挂到RX像素总线上看VSYNC和DE是否有翻转。没有翻转说明RX芯片没解出信号重点检查ADV7611的配置寄存器是否正确写入了。有翻转但画面黑再查TX端ADV7513的输入输出格式配置是否匹配。5.2 闪屏和雪花像素时钟裕量与ESD问题闪屏的问题比黑屏要狡猾一些因为它偶尔出现不容易复现。从我调过的板子来看环路输出闪屏主要有两个来源。第一个来源是时序裕量不足。RX恢复时钟直接驱动TX发送逻辑整条链路里包含组合逻辑和跨芯片引脚延迟如果时序收敛不到位高速场景下某个像素周期内数据不稳定就会出现零星闪烁或者细小雪花点。解决方式就是前面说的给像素数据多加两级寄存器打拍确保输出寄存器的建立时间裕量足够。如果加了打拍还是闪可以考虑在RX和TX之间插入一个PLL重新生成干净的发送时钟但要注意PLL锁定期间的瞬间黑屏需要做好使能控制。第二个来源经常被忽略HDMI线材和电源纹波。环路实验对线材质量远比单纯输出彩条实验敏感因为输入进来的信号本身就是源的端经过线缆传输后的结果线材差、屏蔽不好高频分量衰减直接反映在恢复出来的像素时钟抖动上。另外如果开发板的3.3V或者1.8V电源纹波偏大ADV7611的模拟部分和时钟恢复PLL都会受影响表现为画面间歇性跳动。遇到这种问题换一根短一点的HDMI线材或者把供电从USB换成独立电源往往就有奇效。5.3 偏色和画面错位数据位宽和引脚交换环路输出已经出画面但颜色明显不对比如红蓝颠倒、人脸变蓝、整体发绿这基本是数据位宽或者引脚映射的问题。先回到原理图24位RGB数据线的每个网络名从ADV7611输出到FPGA引脚再到FPGA引脚连接到ADV7513输入这条路径上任何一处位序被颠倒都会导致颜色通道错位。我自己的经验是这类问题用ILA抓RX总线波形反而不容易看出来因为数据本身是合法的只是含义错了。更高效的办法是让电脑显示一张纯红色或者纯绿色的测试图然后观察输出端颜色几秒就能定位是哪根数据线出了问题。画面错位、左右分裂则大概率是HSYNC或者DE和像素数据的对齐关系不对。ADV7611输出像素总线时有些配置下DE信号和数据的对齐关系可以通过寄存器微调。环路输出里如果只把RX数据直接给TX但同步信号却差了若干周期显示器就会把消隐区当有效区画面就歪了。这种问题需要ILA同时抓DE和数据观察每个像素周期的对齐关系再做针对性的打拍调整。5.4 额外的发现环路输出的分辨率自适应最后说一个我实际测试中发现的、让我对环路输出好感倍增的特性分辨率自适配。我做这个实验时电脑端从1080P60切到720P60再切到1366x768这种非标准分辨率环路输出竟然都自动跟随了显示画面没有任何手动配置的干预。原因其实不复杂——环路输出没有EDID或帧缓存里写死的目标分辨率它完全实时地使用RX恢复出来的时序源端切什么分辨率RX就恢复出对应像素时钟和同步信号TX跟着走就行。这个特性在真实项目中很实用比如前端的信号源分辨率不固定、需要透明透传的设备用环路结构就不用操心分辨率表的维护问题。不过要提醒的是ADV7611和ADV7513都有像素时钟的上限约束超出1080P60的规格就容易出问题。如果你的应用要支持4K或者高刷新率就得换支持更高带宽的RX/TX方案。入门阶段在1080P以下做环路调试这套链路已经足够扎实。我调完这个实验最大的感受是FPGA视频应用并没有想象的那么高不可攀它的复杂度主要来自“链路长、环节多”而环路输出就像一个极佳的“全链路体检”把IC配置、时序、时钟、显示握手这些基础概念一次性都过了一遍。后面再做帧差检测、缩放、多路拼接我对数据从哪里来、到哪里去、什么时候该缓存心里就特别有底了。如果你正卡在“输出彩条之后不知道该干嘛”的阶段不妨拿手边的带HDMI输入输出的开发板把环路跑一遍这几十根线的工程代码含金量比肉眼看到的“画面绕了一圈”要高得多。