
做FPGA开发这些年我越来越觉得视频接口是最适合入门到进阶的实战题材。原因很简单它看得见、摸得着出错了能直接看到画面异常成就感来得快调试起来也有迹可循。今天想和大家拆解一个非常经典的实验——黑金云课堂的FPGA基础实验HDMI视频输入与环路输出。这个实验的核心是让FPGA同时处理HDMI视频信号的接收与发送实现“输入什么就输出什么”的环路效果。它不像纯逻辑流水灯那样简单也不像图像算法加速那样复杂恰好卡在一个“跳一跳够得着”的位置非常适合已经跑通基本时序、想往图像处理方向走的开发者。先说结论这个实验做完你基本就摸清了视频传输的完整链路——从HDMI物理接口到TMDS编解码从像素时钟恢复到最后在显示器上看到稳定画面整条通路上的每一个环节你都会心里有数。无论你后续是做视频采集、图像缩放、字符叠加还是往AI加速方向转这套底子都是通用的。本文会从HDMI协议的核心原理讲起再到工程架构设计、关键模块实现、调试技巧和常见坑一步一步来。1. 实验整体设计与思路拆解1.1 环路输出到底在解决什么问题环路输出英文叫Loop-through或Pass-through意思是视频信号从HDMI输入进来之后FPGA不做复杂的图像处理只做必要的时序整理和信号恢复然后直接从HDMI输出端口送出去显示在监视器上。听起来很简单但中间其实藏着好几个关键问题要解决。首先是信号格式问题。HDMI输入的信号是串行的TMDS差分信号频率高、协议复杂FPGA不可能直接拿来做逻辑判断必须先做串并转换、时钟恢复、通道对齐、解码把它恢复成并行RGB数据和行场同步信号。其次是时序重建问题。输入视频的像素时钟不是FPGA自己产生的而是从数据流里恢复出来的这个时钟的质量直接决定了输出画面是否稳定。最后是输出重建问题。恢复出来的并行数据要重新编码成TMDS串行信号然后通过HDMI发送器输出。把这三个问题想清楚你就明白这个实验为什么值得做了它逼着你把视频传输的收发两端都亲手摸一遍。1.2 为什么选择FPGA而不是专用芯片有人会问市面上现成的HDMI转HDMI芯片一大堆比如硅谷数的SiI9293这类几十块钱一片干嘛还用FPGA费劲折腾答案是专用芯片是一个“黑盒”你只知道信号进去能出来但中间发生了什么一概不知。而FPGA方案把你放在“白盒”里每一个字节的流向、每一个时钟的沿都是你自己控制的。更重要的是环路输出只是第一步。FPGA的真正价值在于环路的同时还能“插一脚”——在数据流中间加入图像处理逻辑。比如做色彩空间转换、分辨率缩放、OSD字符叠加、边缘检测甚至神经网络推理这些专用芯片做起来非常困难而在FPGA里只是往数据通路里插一个模块的事。环路输出实验就是为这些后续扩展打地基的。从学费角度看FPGA方案也划算。一块入门级开发板比如黑金的AX7A035、AX7A100价格几百到上千配套的HDMI收发接口和例程都有学完这套再做别的项目板子不用换。UPduino、Tang Nano这些更便宜的板子也能跑就是资源和接口上要精打细算一些。1.3 黑金云课堂这套方案的特点黑金这套教程我实际跑下来有几点做得比较对路。一是它的接口设计把输入和输出分开成两个独立通道每个通道都有独立的I2C配置接口方便单独调试。二是它使用了ADV7611和ADV7513这两颗ADI的芯片前者负责HDMI输入接收后者负责HDMI输出发送两头都是成熟方案排除了很多硬件层面的不确定性让你能专心折腾FPGA逻辑。当然这套方案也有它的门槛。ADV7611的配置寄存器非常多I2C初始化序列长且复杂初学时很容易被这块绊住。黑金把配置代码写好了但如果你只是跑个例程而不去读配置流程后面换一颗芯片或者改一个分辨率就会抓瞎。所以我的建议是第一遍先跑通例程找感觉第二遍一定要回头逐段读I2C配置代码搞清楚每个关键寄存器的作用。这一步跨过去你对视频接口的理解会上一个台阶。2. 核心细节解析与实操要点2.1 HDMI输入的完整链路HDMI输入侧信号从插座进来之后先经过ADV7611。这颗芯片做的事情很多均衡放大、TMDS解码、通道对齐、时钟恢复、色彩空间转换最后输出并行RGB数据或者YCbCr以及对应的像素时钟、行场同步信号。关键点在于ADV7611输出的不是原始TMDS串行信号而是已经解码好的并行数据FPGA拿到的是“半成品”。这个“半成品”虽然大大降低了FPGA的处理难度但并不意味着你可以忽略背后的时序关系。并行数据同样是带着时钟来的而且是随路时钟——它的频率跟随输入信号的像素时钟变化。比如1080p60的信号像素时钟是148.5MHz720p60则是74.25MHz。你必须在FPGA里用这个动态时钟去采数据不能想当然地用一个固定时钟。我见过不少初学者在这里栽跟头用开发板上的固定时钟去采HDMI并行数据结果画面花屏、闪烁或者隔一会儿就断一下都是因为这个动态时钟没有处理好。正确做法是让ADV7611输出的像素时钟作为FPGA内部视频通路的主时钟所有视频处理逻辑都挂在这个时钟域下。2.2 TMDS编码与像素时钟的关系HDMI的底层传输机制是TMDS即Transition Minimized Differential Signaling最小化差分传输信号。它的核心思想是把8bit的数据编码成10bit保证传输线上0和1的跳变尽可能少同时保持直流平衡。那这跟像素时钟有什么关系简单算一笔账。一个像素包含R、G、B三个颜色分量每个分量8bit。TMDS编码后每个分量变成10bit分三条通道传输。也就是说一个像素要传30bit数据分到3条lane上就是每lane 10bit。如果像素时钟是148.5MHz那么每条lane的串行数据率就是148.5 × 10 1485Mbps也就是1.485Gbps。三条lane加起来就是4.455Gbps。这个计算告诉你一个实事HDMI的数据率是由像素时钟直接决定的。在FPGA里你需要产生一个频率是像素时钟10倍的串行时钟来做并转串。以148.5MHz像素时钟为例串行时钟就是1.485GHz这个频率在一般的FPGA上需要用GTP/GTX高速收发器来实现普通的引脚是跑不上去的。这也是为什么ADV7611/ADV7513这类外部芯片方案比直接用FPGA引脚做HDMI要简单——外部芯片把高速串行部分都处理掉了FPGA只处理相对低速的并行部分。2.3 环路输出的缓存策略问题环路输出实验里有个容易被忽视的点输入和输出的像素时钟并不是同一个时钟源。输入时钟来自ADV7611恢复出来的时钟输出时钟由FPGA内部PLL生成这两个时钟的频率不可能完全一致总是有微小的偏差。如果直接把输入数据用输出时钟去采累积下去就会出现丢帧或者重复帧——画面每隔一段时间会跳一下或者出现撕裂感。解决这个问题有两条路。第一条路是异步FIFO加帧同步。把输入数据先写入FIFO输出端以自己的时钟从FIFO读数据。但要注意这种“直接过FIFO”的方案只适用于环路输出本身因为输入和输出的分辨率、时序是相同的。如果你的输入是1080p输出要变成720p或者要做旋转、缩放等处理那就必须用到帧缓存也就是DDR3/DDR4里存几帧画面在输出端按新的时序读出来。第二条路是使用外部视频处理芯片自带的帧率转换功能但这等于绕开了FPGA的核心价值我不太推荐在进阶阶段用这条捷径。黑金的例程里用的是异步FIFO方案FIFO深度至少要能容纳一行数据。1080p一行是1920个像素深度设成4096就够用。不过在实际调试中会发现仅仅用FIFO并不能完全解决帧同步问题画面偶尔还是会撕裂这个时候就需要在FIFO的空满标志上做文章后面调试部分会详细讲。2.4 分辨率与参数的计算做HDMI实验绕不开分辨率参数的设置。以最常用的1080p60为例完整的一帧包含1920×1080个有效像素但实际传输过程中还要加上消隐区。H total 2200像素V total 1125行。有效区域是1920×1080剩下的就是水平消隐HBlank和垂直消隐VBlank。帧率 像素时钟 /H total × V total 148.5MHz /2200 × 1125≈ 60Hz。这些参数从哪里来有两个来源一是查VESA或CEA标准文档二是在Linux内核的drm_modes.h文件里找现成的结构体。做FPGA开发时建议把常用分辨率参数整理成一个表格存着需要的时候直接查。分辨率像素时钟H totalV total帧率720p6074.25MHz165075060Hz1080p60148.5MHz2200112560Hz1080p3074.25MHz2200112530Hz4Kp30297MHz4400225030Hz有一点要注意很多显示器的EDID里会写支持的分辨率列表但实际驱动能力可能和标称不一样。调试环路输出时如果画面不显示先别怀疑FPGA逻辑用一台确定能支持的显示器来排除源端问题。3. 实操过程与核心环节实现3.1 硬件连接与上电检查开始写代码之前硬件准备这一步千万别跳过。HDMI实验对硬件连接的要求比普通实验高一是信号完整性二是供电。HDMI线材建议用支持高速传输的合格线材长度不要超过2米。劣质线材在1080p下可能勉强能用但会出现花屏或者颜色偏色排查起来会让人误以为是逻辑问题。开发板的HDMI输入输出接口注意看丝印标识别把输入输出接反了这种低级错误最浪费时间。供电方面HDMI接口的5V供电电流需求并不大一般在50mA左右但整个开发板的供电要留足余量。FPGA在跑高速串行的时候功耗会比静态时高不少如果板子供电不足现象是画面偶发黑屏或者系统每隔一段时间重启。上电之后用示波器或逻辑分析仪抓一下ADV7611的中断引脚看它是否成功检测到HDMI输入信号。如果中断一直没有拉低大概率是输入信号没有进来先检查HDMI线材和信号源别急着看逻辑代码。3.2 FPGA工程的模块划分整个工程按功能可以分为几个模块我按照自己习惯的划分方式列一下I2C控制器模块负责配置ADV7611和ADV7513的寄存器HDMI RX端时序解析模块接收ADV7611输出的并行数据和同步信号生成标准的VESA时序异步FIFO模块跨时钟域缓存实现环形缓冲HDMI TX端时序生成模块按输出分辨率产生行场同步信号和有效数据使能环路数据通路模块把FIFO读出的数据对齐到输出时序工程顶层把这些模块连接起来形成一条从RX到FIFO再到TX的数据通路。这个结构非常清晰后续要加图像处理模块只需要在FIFO和TX中间再插入一级就行。3.3 I2C配置的关键寄存器ADV7611的I2C配置是整个实验里最枯燥但最关键的部分。它内部有多个映射地址每个映射地址下又有大量寄存器要配置的量有几百个字节。配置的核心目标有几个设置输入端口为HDMI模式使能TMDS时钟恢复和通道对齐配置输出格式为RGB888设置输出同步信号类型为分离同步Separate Sync根据需要配置色彩空间转换矩阵。ADV7513的配置目标则是设置输入格式为RGB888配置输入同步信号类型设置输出分辨率相关的时钟参数使能TMDS输出。实际调试中我发现ADV7611有个寄存器特别重要——0x0F寄存器组它控制视频格式相关的参数。如果输出的图像颜色不对比如红色和蓝色交换了多半是色彩空间配置错了。另外ADV7611的I2C地址默认是0x988bit模式配置前务必确认地址正确否则整段配置代码都在写一个不存在的设备画面自然出不来。3.4 环路数据通路的Verilog实现环路数据通路的核心其实很简单输入数据经过FIFO输出端按时序读出来。但有几个细节必须处理到位。第一个是同步信号的处理。ADV7611输出的行场同步信号和有效数据使能是输入端的时序。输出端的同步信号由FPGA自己生成两边的极性极性是指同步信号是高有效还是低有效可能不同。通过ADV7611和ADV7513的寄存器可以分别设置同步极性建议全部设成高有效这样逻辑里不用做多余的取反。第二个是FIFO的读写使能控制。写入端使用输入端的像素时钟和数据有效信号读出端使用输出端的像素时钟和输出有效信号。读出的时机必须精确对齐输出端的行场同步信号否则画面会出现偏移。第三个是颜色位宽匹配。ADV7611输出的RGB是24bit8bit×3ADV7513输入的RGB也是24bit中间不需要转换。但如果你后续要做色彩处理比如灰度化就要在数据通路上加一个RGB转YUV的模块FIFO的位宽也要相应调整。给一段简化的Verilog示意代码能说明FIFO读写控制的核心逻辑// 写端口使用输入像素时钟 wire [23:0] rgb_data_in; wire de_in; // 输入数据有效 wire vsync_in; // 输入场同步 wire hsync_in; // 输入行同步 fifo_wrapper #( .DATA_WIDTH(24), .FIFO_DEPTH(4096) ) u_fifo_video ( .wr_clk (pixel_clk_in), .wr_en (de_in), .wr_data (rgb_data_in), .rd_clk (pixel_clk_out), .rd_en (read_en), .rd_data (rgb_data_out), .full (fifo_full), .empty (fifo_empty) ); // 读端口控制在输出有效且FIFO非空时读取 assign read_en de_out !fifo_empty;这段代码能跑但还不够完善因为没处理FIFO的空满边界情况。实际工程里我会加一个“水位线”检测逻辑当FIFO中的数据量超过某个阈值时才开始读取当数据量低于某个值时暂停读取。这样做的好处是避免FIFO在空满之间频繁切换导致画面间歇性撕裂。3.5 仿真验证的注意事项写完了逻辑别急着上板先做仿真。但HDMI仿真有个天然的难点输入信号要模拟ADV7611的输出。如果你没有ADV7611的仿真模型就需要自己写一个测试模块来产生带有时序的RGB数据流。最简单的办法是用一个常数图像去驱动输入比如纯红色画面或者彩条信号。用Vivado的仿真环境跑一下重点观察三个地方FIFO的空满标志是否正常翻转、输出端的hsync和vsync时序是否符合VESA标准、环路数据是否能完整地在每个有效行内输出。仿真里还需要注意一个细节ADV7611输出的像素时钟在真实场景下是有抖动的仿真模型里通常不会模拟这个抖动。所以仿真通过了不代表上板一定没问题但至少能把逻辑层面的错误排除掉。上板之后遇到花屏优先从时钟树和FIFO水位线找问题。4. 常见问题与排查技巧实录4.1 画面无输出的排查顺序这是遇到最多的问题也是最容易让人焦头烂额的问题。我的排查顺序是这样的。先用ADV7611的中断引脚确认输入信号是否检测到。如果检测不到检查HDMI线材和信号源。然后用I2C回读寄存器确认配置是否正确写入。比如读0x00地址的IO版本寄存器如果是0xA1之类的值说明I2C通信正常。再确认FPGA内部PLL是否锁定。如果输出端的像素时钟完全没有产生后面的一切都是空谈。最后用示波器量一下ADV7513输出的TMDS差分信号看电平是否正常。这套流程走完基本上能把问题定位到具体模块。不少人是跳过前面几步直接怀疑最后的逻辑代码结果绕了一圈发现只是HDMI线松了。4.2 画面闪烁或间歇性黑屏画面闪烁这个现象多半是时钟稳定性的问题。前面说过输入时钟是ADV7611恢复出来的输出时钟是FPGA PLL生成的两个时钟有不可避免的频率偏差。如果偏差在正常范围内异步FIFO能扛住但如果偏差过大FIFO就会溢出或读空。溢出的表现是一部分帧数据被丢弃读空则会导致输出端无数据可读表现为黑屏或者画面跳变。解决思路有两个方向。一个是在FIFO读写控制里加入水位线的动态调整。比如FIFO半满时才开始读读到低于四分之一时暂停读这样能吸收瞬时的数据量波动。另一个方向是在系统层面做帧率匹配——通过微调输出PLL的频率让它无限逼近输入像素时钟的频率让FIFO的水位长期稳定在一个水平上。黑金例程里给的是PLL微调方案效果不错但需要在锁定参数上做迭代调试。4.3 颜色异常和偏色问题颜色不对先查色彩空间配置。ADV7611可以输出RGB或者YCbCr两种格式ADV7513也支持接收这两种格式但两者的配置必须一一对应。如果输入配置成RGB888输出配置成YCbCr444颜色就会完全乱掉。还有一个常见坑是字符位序问题。ADV7611的RGB输出引脚排列方式可能和ADV7513的输入引脚排列不一致需要在FPGA里做一次引脚重排。这个不用示波器看不出来一旦接错画面是偏色的但并不会花屏。实际调试时我遇到过一种诡异情况画面颜色整体偏绿但白色和黑色显示正常最后发现是ADV7611的A/V映射寄存器配置错了。这类问题没有捷径只能对照数据手册把每个色彩相关的寄存器逐一核对。4.4 分辨率不支持的判断环路输出有个天然限制输出的分辨率受限于输入的分辨率。如果输入是1080p输出的生成时序也只能按1080p来生成强行设成720p会导致画面被裁切或者拉伸变形。调试时如果显示器提示“无信号”先把信号源的分辨率降下来比如从1080p降到720p试试。如果720p能显示而1080p不行大概率是PLL配置的高频参数没算对而不是逻辑逻辑问题。这也能帮助缩小排查范围。4.5 高速信号调试的示波器技巧做HDMI调试示波器的带宽至少要1GHz才能看到TMDS信号的细节普通的100MHz示波器只能看到大概的包络。如果手边只有低带宽示波器也不要灰心还是能干一些事。用低带宽示波器抓ADV7611输出的并行数据和像素时钟能验证FPGA管脚端的接收逻辑是否正确。如果并行数据波形在此时展开推理每条数据线上能看到规律的脉动而且像素时钟的频率符合预期说明ADV7611这边的解码是成功的。另外用示波器测I2C波形能确认寄存器配置过程是否正常完成。真正要量TMDS差分信号的时候建议去实验室借一台高频示波器配合差分探头一起用。没有差分探头的话用单端探头分别量正负两个引脚也能看出信号是否正常只是幅度会比真实值小一些。5. 进阶方向与自主扩展建议环路输出只是起点。当你把这条链路点亮之后可以花点时间琢磨怎么把它变成更复杂的东西。最简单的扩展是在数据通路上加一个图像处理模块。比如灰度化、二值化、边缘检测这些算法模块本身不难写但接到视频通路之后时序约束、资源占用、延迟控制都是新的挑战。真的去把处理逻辑插入到FIFO和TX之间你才算真正体会到FPGA做图像处理的优势和代价。完整一点的扩展是加入帧缓存用DDR3存储输入的视频帧输出端可以任意调整时序参数这样就解耦了输入和输出的分辨率。更进一步可以在帧缓存的基础上做画中画、多窗口、图像旋转等功能应用场景就从简单的环路变成了真正的视频处理平台。还有一个有意思的方向是字符叠加。在视频数据流中把需要显示的文字或图形内容合成进去这在工业相机、医疗设备、监控系统中都有大量需求。字符叠加看似简单真正做起来要处理位图存储、透明度混合、时序对齐等细节是性价比很高的练手项目。对Zynq平台感兴趣的朋友可以试着把CPU加进来。ARM核通过AXI总线访问FPGA侧的帧缓存实现动态的字幕叠加或参数调节这个架构就是很多实际产品的雏形了。根据我个人经验做这些扩展的时候一定要守住一个原则先保证基础通路稳定再往上加功能。我就吃过亏在边缘检测模块还没调好的时候就急着一口气加了缩放和叠加结果出问题时根本定位不了是哪个模块的锅。把数据通路想象成一条水管先把水管修好通水再去接各种支路出了问题也容易排查。6. 学习路径与资源建议这套实验学完下一步该怎么走我根据自己的经验给三条路径看你的兴趣方向。偏高速接口的去学FPGA内置的高速收发器把GTP/GTX跑起来尝试直接收发TMDS信号不再借助外部芯片。这条路能让你彻底理解HDMI物理层的细节但难度不小建议先学完PCIE再来碰。偏图像处理的去学基本的图像算法在FPGA上的实现。从灰度化开始然后是高斯滤波、Sobel边缘检测、直方图均衡。算法本身不难难点在于怎么设计流水线结构、怎么管理行缓存、怎么控制时序延迟。这些做熟了再往深度学习推理方向走就有基础了。偏嵌入式系统的去玩Zynq的HDMI通路。用ARM核配置I2C、控制分辨率、读取帧统计数据FPGA只负责数据搬运。这个方向特别适合做产品原型验证也是目前工业应用的主流架构。视频接口这个领域从HDMI入门往上游可以延伸到DisplayPort、MIPI DSI/CSI往下游可以深入到图像传感器和显示驱动。每个方向都够吃好几年但起点都是你今天拿下的这个环路实验。搞定了它后面纵横拓展的路就宽了。