FPGA视频处理入门:HDMI环路输出实验深度解析
FPGA 视频处理入门十个人里有九个是从 HDMI 输入输出开始的。黑金云课堂这个《FPGA 基础HDMI 视频输入与环路输出实验》名字听着保守实际上把 FPGA 视频链路里最关键的几个环节全部串了一遍HDMI 物理接口怎么接、TMDS 信号怎么解、视频时序怎么对、DDC/EDID 怎么让源端“认”出你的板卡最后还要把图像原样编码送出去点亮显示器。环路输出就是输入信号在 FPGA 内部不经过外部设备直接把视频数据从输入端送到输出端实时显示。这个实验跑通之后后续做缩放、叠加、画框、色彩转换、目标检测前处理都是在它上面加东西。这篇文章我按照自己做项目时的思路从原理、方案选型、代码实现到上板排错把这个实验完整拆开讲一遍。适合刚接触 FPGA、手里有开发板想验证 HDMI 链路的同学也适合准备做视频处理但被接口层卡住的人。1. 环路输出到底在做什么1.1 一条链路上的两个方向这个实验的数据链路很清晰一共就四个节点视频源设备电脑主机、摄像头、机顶盒通过 HDMI 线输出信号开发板的 HDMI 输入接口接收FPGA 拿到数据后不做复杂处理直接送给板上的 HDMI 输出接口最后由另一根 HDMI 线送到显示器显示。环路输出里的“环路”两个字指的是数据在开发板内部走了一个“输入即输出”的回路。它不是一个协议术语更像一个工程叫法。实际项目中很多设备也这么用视频会议终端、导播台、监控拼接控制器都有类似功能某一路输入信号既要进 FPGA 做处理又需要原样送给另一个下级设备于是就把输入信号分一路直接输出只是它们往往用硬件芯片做分路而这个实验是在 FPGA 里完成的。从这个角度理解环路输出实验的本质不是“显示一张图”而是验证一条完整的数字视频通道。通道通了后面的算法处理才有立足点。1.2 为什么先拿它入门我见过不少人一上来就做色彩空间转换、边缘检测结果显示器始终没图像最后查了三天发现是 HDMI 输入通道对齐没做对。说实话FPGA 视频开发里最磨人的从来不是算法而是接口层的各种细节。HDMI 视频输入与环路输出实验看起来功能简单但它迫使你一次性解决这些基础问题硬件上HDMI 连接器的差分信号怎么接到 FPGA 的引脚上需不需要电平转换IO 标准怎么配置。时钟上视频源输出的 TMDS clock 怎么进 FPGAMMCM/PLL 怎么用这个时钟生成内部处理时钟。数据上三个 TMDS 数据通道如何完成串并转换和字节对齐。控制上HPD 热插拔检测、DDC 通道上的 EDID 读取少了任何一个源端都不会正常输出。约束上输入输出差分对、时钟约束、延迟约束都要写对缺一条综合都可能过不了。这些恰好是后续所有视频实验的共同地基。地基打牢了后面换分辨率、换接口DisplayPort、MIPI、LVDS都只是换汤不换药。1.3 平台选择与准备工作黑金 AX7 系列开发板是常见的实验平台Artix-7 芯片板上自带 HDMI 输入输出接口。需要注意不同批次板卡的 HDMI 输入方案可能有区别有些是 FPGA 直接用差分引脚接收 TMDS 信号有些在板上加了一颗 HDMI 接收芯片比如 ADI 的 ADV7611接收芯片先把 TMDS 解码成并行 RGB 数据再给 FPGA。这两种方案的代码实现差异很大。前者需要自己写 ISERDES 接收、做通道对齐难度更大但更能理解 HDMI 底层后者相当于已经帮你解完串FPGA 只需要采并行数据工作量小很多但很多底层细节被芯片屏蔽了。做这个实验前我建议你先翻开板卡原理图确认一下输入端到底是哪种结构这决定你接下来该看哪部分代码。准备清单并不复杂一块带 HDMI 输入输出接口的开发板、一条能输出 1080P60 的 HDMI 源设备电脑主机最方便因为可以设置分辨率和刷新率、一条显示器、两根质量正常的 HDMI 线。注意别用太长太差的线TMDS 信号在板内已经走了一段线材再不行很容易出现随机花屏。2. 啃懂 HDMI 底层实验才有底2.1 HDMI 物理接口与关键引脚HDMI 接口标准里最核心的是 4 对 TMDS 差分信号其中 3 对传输数据1 对传输像素时钟。所谓差分信号就是每一路数据用两根线、互为反相的方式传输接收端通过比较两根线的电压差来恢复数据抗干扰能力远强于单端信号。这里要特别强调几根容易被忽视的引脚因为它们直接决定你是否能看到画面。第一是 5V 电源脚由源端电脑、摄像头提供用来维持热插拔检测、DDC 供电这类辅助功能规范里给的能力通常是 50mA 级别不是给板卡大功率供电用的。第二是 HPD 热插拔检测脚Hot Plug Detect接收端通过拉高这个引脚告诉源端“我已经准备好了你可以开始输出视频”如果这个信号不对源端会认为没有接显示器整条链路都不会工作。第三是 DDC 引脚本质是一条 I2C 总线源端通过它读取接收端的 EDID 数据类似“自我介绍文档”里面描述了这设备支持什么分辨率、什么时序、什么色彩空间。很多初学者觉得 HDMI 就四对线 外壳地结果一接上显示器没反应怎么查代码都查不出问题最后才发现 HPD 或 DDC 环节出了状况。做硬件接口实验多看几眼原理图永远不亏。2.2 TMDS 编码在做什么TMDS 全称 Transition Minimized Differential Signaling是 HDMI/DVI 的物理层编码方式。它做的事情可以这样理解把 8 位像素数据编码成 10 位传输数据附加的 2 位用于保证直流平衡和减少信号跳变次数。为什么要这么做如果直接按原始数据传连续发送全 0 或全 1 时差分信号的电平长时间处于同一状态接收端很难从信号里提取时钟信息也不利于降低电磁干扰。TMDS 通过编码让传输线上的 0/1 跳变尽量均匀并且在 10 位编码里保证直流分量基本恒定接收端恢复数据的可靠性就高很多。具体到 FPGA 实现解码端要做的是先把接收到的 10 位编码数据做 TMDS 解码恢复出 8 位像素数据同时把行场同步信号和控制信号从数据流里解析出来。编码端反过来把 8 位像素数据和同步信号编码回 10 位再串行输出。7 系列 FPGA 里串并转换由 ISERDESE2 和 OSERDESE2 这类原语完成解码算法一般用查找表或者组合逻辑实现。2.3 视频时序像素时钟和同步信号屏幕上的画面是一个点一个点、一行一行扫描出来的。每个像素在固定的像素时钟驱动下传输像素时钟频率由分辨率和刷新率决定而有效像素之外的区域用来传送行消隐和场消隐信号这段时间里源端不会发送有效图像数据。下面列几个常用分辨率的时序参数做实验时把源设备的输出设置为其中之一调试会省很多事分辨率/帧率像素时钟行有效像素行消隐场有效线场消隐720P6074.25MHz1280370720301080P60148.5MHz19202801080451080P3074.25MHz1920280108045以 1080P60 为例有效分辨率是 1920×1080但每一行实际传输 2200 个像素多出来的 280 个像素属于 HBlank每一帧实际传输 1125 行多出来的 45 行属于 VBlank。像素时钟 148.5MHz意味着 1 秒钟要传输 2200×1125×60 个像素约 1.485 亿次像素传输。FPGA 解码时最关心的就是 data enableDE这个信号。DE 为高表示当前像素处于有效区域DE 为低表示处于消隐区。输出的时序必须完整保留 DE、HSync、VSync 和像素数据之间的关系环路输出才可能稳定。2.4 环路输出的两种实现路径既然要做环路输出最直接的做法是在硬件上把输入差分信号分一路出去这就是纯物理层环路。它的延迟极低但信号经过分路之后质量会下降而且完全不能做任何图像处理只能原样转发。更重要的是FPGA 拿不到数据了实验目的就失去了意义。FPGA 开发板上的环路输出通常走的是另一条路输入 TMDS 信号先进 FPGA完成解码后在像素域把数据“透传”到输出模块再用编码器重新编码发送。如果在 FPGA 内部不经过帧缓存只是打几拍寄存后直接送输出那么延迟只有几个像素时钟周期肉眼完全看不出延迟效果上等同于环路。有些工程为了提高可靠性会把输入视频缓存到 DDR 里再读出那延迟就是一两帧反而不叫严格意义的环路输出。我在实际做这个实验时坚持用无帧缓存方案输入解码后的 24 位 RGB 数据带着行场同步信号直接寄存一级送到输出编码模块。这样做最大的好处是实时性而且能验证“FPGA 对像素数据是否有完全掌控力”——这是任何视频处理的第一步。3. 用 FPGA 把环路搭起来3.1 输入侧差分接收与通道对齐输入侧的核心任务是把串行 TMDS 数据转成并行像素数据。7 系列 FPGA 上这部分要用到三个原语IBUFDS差分转单端、IDELAYE2每个输入比特的可调延迟、ISERDESE2串并转换。一个典型的接收通道思路是先由 IBUFDS 把一对差分线上的 TMDS 信号变成单端信号然后用 IDELAYE2 对信号加可调延迟让采样时钟落在数据眼图的中间位置再用 ISERDESE2 把串行数据按 1:4 或者 1:8 的倍数转成并行数据。通道对齐是整个实验里最容易卡壳的地方。HDMI 有三个数据通道每个通道在发送端都做了 8b/10b 编码对齐但经过传输和串并转换后三条通道的并行数据可能出现字节错位。怎么解决基本思路是选择一个通道作为参考找出训练字符或者利用视频数据特征然后扫描另外两个通道的 BITSLIP 控制信号让它们和参考通道对齐。黑金例程里通常有一个专门的 align 模块做这件事手动调不了的时候用 Vivado 的 ILA 观察并行数据的规律是最快的办法。如果你用的板子是带 HDMI 接收芯片的方案那输入侧就轻松一些。接收芯片已经把 TMDS 解码完成通过并行数据总线把 RGB 和行场同步送到 FPGAFPGA 只需要在合适的时钟沿采样。不过我还是建议你把原语方案看懂毕竟一旦换平台纯 FPGA 方案永远是最通用的。3.2 输出侧编码与驱动输出侧的核心任务和输入侧相反把并行 RGB 数据编码成 TMDS 信号通过差分引脚输出给显示器。Artix-7 上一般用 OSERDESE2 把 10 位并行编码数据转成高速串行再通过 OBUFDS 转成差分信号。编码部分需要自己实现 TMDS 编码器核心逻辑是对每个像素的 8 位 RGB 数据做异或/异或非编码然后再加上直流平衡调整位最后拼成 10 位。行场同步信号不是单独引脚传输的而是在数据通道的空闲期编码成特定的控制码这就是为什么 HDMI 接口只有三对数据线却能同时传视频和同步信号。输出侧要特别重视时钟。显示器的像素时钟必须和输入视频的像素时钟一致或者经过 MMCM 生成同频同相时钟这样才能保证每个像素的输出速率和输入速率匹配。环路输出最简单的方式是直接把输入恢复出的 TMDS clock 作为输出模块的工作时钟数据经过一级寄存器后送给 OSERDES相当于输入时钟和输出时钟同源省去了跨时钟域处理的麻烦。3.3 DDC/EDID让源端认识你的开发板电脑主机或者摄像头输出 HDMI 之前会主动做一件事情通过 DDC 总线读取显示器或者接收端设备的 EDID。如果读不到合法 EDID或者读到的内容无法匹配当前输出格式源端默认按“无显示器”处理一点信号都不会发这就是很多“无视频输入”问题的根源。EDID 是一段 128 字节的二进制数据块里面按规范排列着厂商信息、支持的分辨率列表、像素时钟、物理尺寸等信息。FPGA 侧实现的方式有两种。一种是在板上放一颗 I2C EEPROM预先写入一段标准的 1080P60 EDID源端读取时直接从 EEPROM 拿数据另一种是用 FPGA 逻辑模拟 I2C 从机响应源端的读请求根据寄存器地址回传 EDID 内容。我建议在工程里保留一个简单的 I2C 从机模块即使板子已经有 EEPROM也可以在调试时主动改返回内容来测试源端对不同分辨率的支持情况。比如你为了降低调试难度把 EDID 改成只声明 720P60 和 1080P60 两种格式电脑端的分辨率设置就会自动限制在可选项内防止源端输出一个时序参数很奇怪的模式。3.4 时钟架构与复位设计时钟是 FPGA 视频工程的命脉。HDMI 输入实验里最核心的时钟就是输入的 TMDS clock它承载着所有像素数据的节拍。推荐的做法是把 TMDS clock 直接看作主时钟同时用 MMCM 生成不同频率的处理时钟。以 1080P60 为例输入 clock 是 148.5MHz。如果你的内部处理逻辑跑不了这么高可以降到 74.25MHz通过 MMCM 把并行数据宽度翻倍来保持吞吐量。基础实验不必做这么复杂的降频处理但你要知道 MMCM 的输入输出关系怎么写约束否则综合工具会报出大量的时序错误甚至直接断言失败导致无法生成比特流。复位设计也要认真对待。视频解码模块需要在上电后用复位信号把内部状态清零但复位释放的时机必须和输入视频对齐。一个常见的错误是源端还没输出稳定的视频时钟时逻辑就已经开始采集导致状态机进入错误分支。我习惯用输入时钟的 locked 信号作为模块复位的释放条件确保时钟稳定后再启动所有逻辑。4. 完整实操记录4.1 硬件连接与上电顺序实际操作时我会按下列顺序连接设备先用 HDMI 线从电脑主机输出端口连接到开发板的 HDMI IN 接口。再用另一根 HDMI 线从开发板的 HDMI OUT 接口连接到显示器。给开发板上电启动 Vivado 下载比特流。最后打开电脑的显示设置把输出分辨率设置成 EDID 里声明过的模式。注意上电顺序有个讲究。如果 FPGA 配置还没完成就插着 HDMI 源源端可能在 FPGA 上电配置的空窗期尝试读 EDID读不到之后会进入某种异常状态需要重新插拔 HDMI 线才能恢复。我建议先下载完比特流再接源端或者下载完比特流后重新插一下 HDMI 输入线让源端重新做一次热插拔检测。4.2 Vivado 工程与约束工程创建没什么特殊的器件型号根据你的板卡选择 Artix-7 对应型号即可。关键在于约束文件里必须覆盖三个方面引脚约束把 HDMI 输入输出的差分引脚和普通引脚映射到具体 FPGA 管脚。注意差分引脚要成对出现Vivado 会自动识别 P/N 管脚对。时钟约束输入 TMDS clock 必须用create_clock声明比如create_clock -name hdmi_clk -period 6.734 [get_ports hdmi_rx_clk_p]这个时间段对应 148.5MHz。后续 MMCM 生成的时钟约束工具会根据源时钟自动推导不用手动写但建议打开 timing summary 确认没有 unconstrained 路径。IO 标准约束HDMI 输入输出引脚在 Artix-7 上通常配置为差分电平标准具体标准名取决于你板卡的电平设计可能是LVDS、TMDS或者DIFF_SSTL。这个必须查原理图不能想当然。以下是一段示意约束形式实际管脚名以你的板级约束为准set_property PACKAGE_PIN L16 [get_ports hdmi_rx_clk_p] set_property IOSTANDARD TMDS_33 [get_ports hdmi_rx_clk_p] set_property PACKAGE_PIN L15 [get_ports hdmi_rx_clk_n] set_property IOSTANDARD TMDS_33 [get_ports hdmi_rx_clk_n] create_clock -name hdmi_rx_clk -period 6.734 [get_ports hdmi_rx_clk_p]4.3 直通环路的核心代码这里给出一个最简化的环路直通模块帮助理解数据流向完整工程里还需要串并转换、通道对齐和编码器。module hdmi_loop_through #( parameter DW 24 )( input wire rx_clk, input wire rx_rst, input wire [DW-1:0] rx_pixel_data, input wire rx_hsync, input wire rx_vsync, input wire rx_de, output reg [DW-1:0] tx_pixel_data, output reg tx_hsync, output reg tx_vsync, output reg tx_de ); // 直通输出像素数据补一拍保持同步关系 always (posedge rx_clk) begin if (rx_rst) begin tx_pixel_data {DW{1b0}}; tx_hsync 1b0; tx_vsync 1b0; tx_de 1b0; end else begin tx_pixel_data rx_pixel_data; tx_hsync rx_hsync; tx_vsync rx_vsync; tx_de rx_de; end end endmodule这段代码不是完整环路但它体现了最基本的原则输入像素数据和行场同步信号必须保持在同一条延迟路径上像素数据打几拍同步信号也必须打几拍。如果同步信号少打一拍图像会整体偏斜或者出现花屏。完整的环路输出工程里输入侧解码后的数据按照这个思路寄存后送给输出编码模块编码模块内部再根据输入时钟完成 TMDS 编码和并串转换。4.4 用 ILA 上板调试整个工程综合、实现、生成比特流、下载之后如果显示器上直接出现图像那说明你运气很好。多数情况是黑屏或者花屏这时候就轮到 ILA 发挥价值了。ILA 是 Vivado 内置的逻辑分析仪核。建议在工程里提前例化一个 ILA观察这几组信号输入侧的 de、hsync、vsync、像素数据以及经过通道对齐之后恢复出的 RGB 数据。不要等出了问题再加 ILA那可就要重新综合一次耗时半小时到一小时。调试时先看 de 信号。如果 de 一直是低说明输入解码链路没有正常工作检查通道对齐状态和时钟如果 de 有规律的脉冲说明基本时序已经恢复再看像素数据是否合理。比如用电脑输出一张纯红色图片ILA 采样到的 R 通道数据应该接近最大值G、B 通道接近 0。拿标准数据去比比单纯看“有没有图”靠谱得多。5. 常见问题与排查技巧实录5.1 源端不输出屏幕上始终提示无视频输入这个现象大概率不是显示器的问题而是源端根本没检测到有效接收设备。排查步骤按优先级排列第一步量 HPD 电压。接收端的 HPD 引脚应该被拉高到 3.3V 或 5V 逻辑高电平源端检测到这个电平才认为有设备存在。如果 HPD 没拉起来检查板卡的 HPD 上拉电阻和连接电路。第二步观察 DDC 总线。用示波器或逻辑分析仪抓 SCL/SDA看有没有 I2C 读操作。如果完全没有时钟活动说明源端连读 EDID 的意图都没有基本可以判断问题在 HPD 或者 HDMI 线材。如果 SCL 有波形但 SDA 没有响应问题出在 EDID 模块。第三步确认 EDID 内容合法。很多工程直接把网上找的一段 EDID 塞进 EEPROM但里面可能声明了一些源端不支持的 3D 格式或者音频格式导致源端协商失败。调试阶段最稳妥的做法是从逻辑上返回一份最精简的 EDID只声明一种分辨率比如 1080P60这样源端没有任何理由拒绝。5.2 图像花屏、闪屏、偏色这类问题通常在通道对齐和时序采样阶段。花屏的大头是通道错位可以这样理解三个通道的数据如果互相错了一个字节画面就像被切割后乱拼一样。排查时用 ILA 固定抓某一帧边界观察三个通道的并行数据是否在同一拍出现像素起始标志如果不是就需要调整 BITSLIP。闪屏和偏色则更多和延迟锁定有关。IDELAYE2 的延迟值如果设置不当采样点落在数据跳变边缘恢复出的数据就会时好时坏表现出来就是某一个颜色通道偶尔丢数据画面闪烁或者颜色不正。稳妥的做法是在 Vivado 里跑一下输入数据眼图扫描选眼图张开最大的延迟值作为固定配置。另外HDMI 线材不好造成的信号劣化也会表现为类似症状。我在实验室遇到过反复调试代码无果最后换了一根短线就完全正常的情况。对于基础实验先排除物理链路问题再调逻辑。5.3 环路输出黑屏但输入侧 ILA 显示数据正常输入侧都正常问题就出在输出编码或者显示器的握手环节。优先检查输出编码模块是否在消隐期发送了正确的控制码。HDMI 规范里DE 为低的时段不是什么都不传而是必须传特定的控制码序列如果这些控制码不对显示器可能无法锁定信号直接黑屏。还要检查输出时钟是否等于输入时钟。环路输出时如果你用 MMCM 生成了内部时钟但因为配置问题导致实际输出频率和输入不一致显示器的行场频率就对不上画面要么黑屏要么强烈滚动。最简单的自查方法让输出时钟和输入 TMDS clock 同频并且直接送给 OSERDES然后看显示器有没有反应。分辨率不匹配也会导致问题。有些显示器不支持源端的 EDID 里声明的高分辨率但源端按 EDID 里的最高分辨率输出显示器反而显示不了。把 EDID 改成只声明一个中低分辨率720P60然后源端输出对应分辨率通常能解决。5.4 关于 HDMI 供电电流的几个误会网上经常有人问 HDIMI 的 5V 供电电流需要多大这里统一说清楚。HDMI 源端的 5V 引脚按规范可以提供最大 50mA 级别的电流这个电流主要用于热插拔检测和 DDC 通道的电路工作不是给 HDMI 接收芯片供电的也不是给显示设备供电的。开发板上如果有一颗 HDMI 接收芯片它的工作电流通常比这大得多噪音其实来自板卡的电源系统而不是 HDMI 接口。调试时你真正要关心的是 5V 引脚是否存在HPD 回路是否完整。很多“无视频输入”的问题源头就是板卡上 HPD 引脚没有正确连接到 FPGA 或者一个逻辑高电平源端根本不知道后级已经就绪。5.5 我总结的一套快速排查流程做过几次这个实验之后我给自己定了一套排查顺序每次都能快速定位问题先看 HPD 电平不对就查电路连接。再看 DDC 总线没有读写活动就回头查热插拔。用 ILA 看 de 信号没有规律脉冲就查输入解码和时钟。看像素数据值取一张标准纯色图去比对。如果输入侧全部正常再去查输出编码和显示握手。最后才考虑换线、换显示器这类物理因素。这套顺序解决了我遇到的大部分问题你也别一上来就怀疑代码逻辑先沿着信号链路一层一层排除效率会高很多。最后再分享一点我个人的体会把 HDMI 输入和环路输出实验做完一遍之后你会发现自己对 FPGA 的看法会有变化。它不只是用来跑状态机、做通信协议的东西而是一块能够逐像素处理视频流的逻辑阵列。环路输出表面上只是“原样转发”但它给了我一个非常踏实的起点我确确实实看到每一路像素数据都可以被我控制后续想叠加 OSD、做裁剪、改分辨率、加滤镜都只是在这个数据流上添加处理逻辑而已。如果你手边的开发板已经支持这个实验别急着往下赶进度先花点时间把 ILA 采到的每一类信号都盯一遍把花屏、无输入、黑屏这些坑都踩一遍再爬出来收获会比单纯下载官方例程跑通大得多。等这一步稳住再去碰 DDR 缓存、图像缩放、色彩空间转换你会发现自己已经有能力独立解决调试问题了。