FPGA HDMI视频环路输出实验:从硬件架构到Verilog实现 1. 实验整体设计与硬件架构拆解1.1 环路输出到底是什么先把这个实验名字拆开看。HDMI视频输入指的是通过HDMI接口把外部视频源比如电脑、机顶盒、另外一块开发板送过来的画面信号让FPGA成功接收到环路输出英文叫Loop Out是指FPGA把接收到的视频信号原封不动地再送出去接到显示器上能看到和输入完全一样的画面。很多刚接触FPGA视频处理的人第一反应是环路输出不就是一根线分成两路吗实际上在FPGA开发板里事情没那么简单。这个实验本质上是让你彻底搞清楚一条完整的视频数据链路HDMI接口物理层信号进来之后怎么被解码变成FPGA能处理的并行数据经过FPGA内部之后又如何被重新编码成HDMI信号送出去。环路输出只是验证这条链路通不通的最直观方式——输入什么输出什么画面一致性一目了然。这个实验在整个FPGA视频开发的学习路径里属于承上启下的关键节点。往上接HDMI接口协议、TMDS编码、DDC通道这些基础概念往下接帧缓存、图像缩放、色彩空间转换、OSD叠加等真正的图像处理算法。如果你打算以后做图像采集、视频拼接、AI推理前处理这类项目这个实验就是绕不开的第一课。1.2 硬件链路与芯片选型做这个实验你得先清楚开发板上视频信号实际走的物理路径。以常见的黑金FPGA开发板为例整条链路通常是这样的HDMI输入接口进来信号先到HDMI接收芯片比如ADI的ADV7611或者Silicon Image的SiI9134这类芯片负责把HDMI串行差分信号TMDS解码成并行视频数据包括24位RGB数据、行场同步信号、数据有效信号DE和像素时钟然后这些并行信号送入FPGA的GPIO引脚。FPGA内部可以做各种处理处理完之后把并行数据再交给HDMI发送芯片比如ADV7513由它重新编码成TMDS差分信号送给显示器。这里要强调一点FPGA本身并不直接处理HDMI的TMDS高速串行信号而是通过外接的PHY芯片物理层收发器完成串并转换。FPGA只处理并行视频总线。这就好比网线接口和网卡芯片的关系FPGA更像是主板上的CPU它拿到的是已经转换好的数字信号而不是网线上那种模拟差分信号。我对ADV7611和ADV7513这对组合比较熟悉因为它们在国产FPGA开发板上用得特别多。ADV7611支持最高1080p60的输入分辨率内置EDID RAM可以通过I2C接口配置ADV7513支持最高4K30的输出同样通过I2C配置。两个芯片都是3.3V供电I2C接口电平也是3.3V跟FPGA引脚直接对接没问题但要注意有些型号是1.8V接口的选型时一定要看数据手册。1.3 两种环路实现方式对比模拟直通与数字环路环路输出在硬件实现上有两种思路。第一种是模拟直通把输入的TMDS差分信号通过PCB走线直接分出一路到输出接口信号完全不经过FPGA处理纯粹是物理层的fan-out。这种方式延迟最低、电路最简单但缺点是没法做任何图像处理而且对PCB布线要求很高——HDMI属于高速差分信号阻抗控制、等长匹配、串扰抑制稍微不到位画面就会出现雪花点或者直接黑屏。第二种是数字环路也就是本实验采用的方式。HDMI接收芯片把信号解码成并行RGB数据送进FPGAFPGA再把这份数据原封不动地交给发送芯片编码输出。虽然多了一次串并转换和重新编码信号延迟会多出几十个微秒但好处非常明显数据在FPGA内部是可见、可控的你可以随时插入图像处理逻辑也可以在画面的任意位置叠加OSD信息甚至可以做帧率转换和分辨率缩放。这就是为什么做视频处理项目时几乎都选数字环路而不是模拟直通。两个方案的核心区别我列个表格做个对比对比项模拟直通数字环路信号处理方式物理层直接分发差分信号接收芯片解码后FPGA处理再编码是否可插入图像算法不可以可以延迟纳秒级微秒级PCB设计难度高需严格阻抗匹配相对低只需要保证并行信号质量灵活性无高可扩展图像处理做实验阶段数字环路明显是更有价值的选择因为你后面所有视频处理实验都是在这条数字链路上扩展出来的。2. 关键电路细节与工程搭建2.1 HDMI接口电路与供电电流的几个细节很多人第一次画HDMI电路时只关心那几对差分线结果做出来的板子问题一堆。我整理一些实测中的关键点供你画板或者排查问题时参考。先说HDMI接口的5V供电。HDMI规范里要求源端设备通过19脚输出5V电源用于给显示器的DDC显示数据通道和HPD热插拔检测电路供电。这个5V需要多少电流规范规定最小值是50mA但实际设计中我一般留到200mA以上的余量。原因很简单显示器端的EDID EEPROM上电瞬间会有比较大的浪涌电流如果供电能力不足会拉低5V电压导致显示器检测不到源端设备表现为“无信号输入”。HPD引脚的处理也是一个典型坑。HPD是HDMI接口的第18脚由接收端显示器拉高表示“我已连接”。源端主机通过检测这个引脚电平来判断是否要发起HDMI通信。开发板上通常会让HPD经过一个电阻连接到FPGA或者接收芯片的检测引脚但有些板子为了省事直接把HPD接死在高电平这样会导致一个问题主机无法感知显示器的插拔事件也就不会重新发送视频信号。做实验时如果你发现显示器睡着了叫不醒先量一下HPD有没有正常拉高。再说TMDS差分线的ESD保护。HDMI接口经常被热插拔如果没有ESD防护器件静电很容易打坏接收芯片的输入级。正规设计会在每组差分线上加TVS管或者ESD阵列比如USBLC6-2SC6这类器件摆放位置要尽量靠近连接器。开发板上一般已经加了这些保护但如果你是自己画的底板这个钱不要省。最后是HDMI输出侧的电流驱动能力。HDMI源端输出需要驱动15米甚至更长的线缆TMDS对的驱动电流通常在10mA左右由发送芯片内部的可编程电流源控制。ADV7513之类的芯片通过寄存器可以调节输出电流幅度和预加重参数线缆较长或者画面有重影时可以试着调大驱动电流和预加重。2.2 FPGA内部数据通路设计搞清楚了硬件链路接下来就要设计FPGA内部的数据通路。这个实验的核心通路非常清晰接收芯片过来的并行视频数据经过一个数据对齐模块然后进入一个可选的图像处理模块实验阶段直接透传最后送到发送芯片。并行视频数据长什么样以24位RGB模式为例每个像素时钟周期传输一个像素数据线包括RGB各8位、VS垂直同步、HS水平同步、DE数据有效以及像素时钟PCLK。DE有效的时候RGB总线上是有效像素数据DE无效的时候总线上的数据无效对应的是行消隐和场消隐区间。FPGA内部代码上我习惯的第一步是给所有输入信号加一级寄存器打拍做时钟域同步和信号对齐。不要小看这一步虽然看起来只是延迟了一个时钟周期但能有效避免亚稳态问题特别是在PCLK与FPGA内部时钟不是同源的情况下。环路输出实验里FPGA不涉及跨时钟域处理接收芯片输出的PCLK直接作为整个视频通路的时钟这样最简单也最稳定。数据通路的基本代码思路可以概括为输入信号打拍对齐然后根据DE信号把像素数据分流有效数据进入处理逻辑处理完再和VS、HS、DE、PCLK一起打包送给发送芯片。实验阶段的“处理逻辑”就是直接透传但模块接口上要留好位置方便以后插入真算法。2.3 I2C配置与寄存器初始化要点HDMI接收和发送芯片都是需要初始化配置的配置手段就是I2C总线。这一步是实验能否成功的核心很多新手在这里卡了一整天板子检查了半天没毛病结果代码一跑还是黑屏最后发现I2C根本没通。为什么需要配置因为接收芯片和发送芯片上电后不是默认就能工作的必须通过I2C写寄存器告诉它们输入信号是什么格式、输出分辨率是多少、色彩空间怎么处理、是否启用内部均衡器等等。ADV7611在上电后有一整套初始化序列要依次写入几十个寄存器值才能进入正常工作状态。I2C配置这块我建议你不要自己凭感觉写寄存器直接参考开发板厂商提供的初始化代码或者芯片数据手册里的推荐配置。ADV7611的驱动中有一段关键序列包括配置输入端口选择、视频格式检测使能、输出格式选择24位RGB还是YCbCr 4:2:2等。其中输出格式的选择要特别注意必须和发送芯片的输入格式匹配否则画面颜色会错乱或者干脆黑屏。调试I2C时有个非常实用的技巧先把接收芯片的I2C地址读出来确认总线畅通。ADV7611的8位I2C地址默认是0x987位地址0x4C如果读不到ACK说明硬件连接有问题或者芯片没有正常上电不要继续往下调。我遇到过I2C上拉电阻虚焊导致总线时通时断的情况用示波器量SCL和SDA波形就能看出来——正常的总线空闲状态应该都是高电平如果波形有毛刺或者幅度不够优先查上拉电阻和电源噪声。2.4 时钟方案像素时钟是怎么来的视频处理和普通逻辑设计最大的区别就是有时钟连续性要求。FPGA里的视频数据通路时钟就是接收芯片输出的PCLK这个时钟的频率取决于输入视频的分辨率和刷新率。1080p60的像素时钟是148.5MHz720p60是74.25MHz1080p30也是74.25MHz。这个时钟频率和芯片的I2C配置是配套的关系。接收芯片通过检测输入信号的时序参数会自动识别出当前分辨率和刷新率然后输出的PCLK频率会对应变化。FPGA这边不需要做任何动态配置只要保证时序约束里PCLK的时钟约束正确就行。但是这里有一个容易被忽略的问题PCLK是一个来自外部芯片的时钟它和FPGA内部主时钟比如50M或者200M晶振是异步的。如果你的设计里除了视频通路还有别的逻辑比如按键控制、串口打印千万不能把PCLK域的信号直接和主时钟域信号混用跨时钟域要加同步器或者FIFO。环路输出实验里可以不做跨时钟域但养成这个意识非常重要后面做图像缓存时一定会用到。3. 工程实现与Verilog核心代码3.1 工程整体模块划分下面进入实操部分。我用Vivado工具来演示黑金开发板大多是Xilinx的FPGA芯片Vivado是标配。整个工程我建议按照下面的模块划分来组织top.v顶层模块例化所有子模块完成引脚分配iic_drv.vI2C主机控制器用于配置ADV7611和ADV7513hdmi_rx_cfg.v接收芯片初始化状态机控制I2C写入ADV7611寄存器hdmi_tx_cfg.v发送芯片初始化状态机控制I2C写入ADV7513寄存器video_align.v输入视频数据对齐模块打拍同步loop_back.v视频数据透传模块实验阶段用后面可扩展这个模块划分的思路是让每个模块职责单一方便调试和复用。特别说一下video_align.v很多初学者会忽略它直接拿输入信号用结果时序分析报一堆FAIL。HDMI接收芯片送过来的数据和PCLK之间是有固定相位关系的但FPGA内部走线会让信号产生延迟差异所以必须在PCLK上升沿统一打拍一次保证所有信号对齐到同一个时钟沿。3.2 接收端数据同步与对齐代码先说接收端。ADV7611输出的信号有PCLK、DE、VS、HS、RGB数据24位。如果想要后续做图像处理还可能有FID字段等实验阶段不用管。对齐模块的代码很简洁但作用关键module video_align #( parameter DATA_WIDTH 24 )( input wire pclk, input wire rst_n, input wire de_i, input wire vs_i, input wire hs_i, input wire [DATA_WIDTH-1:0] data_i, output reg de_o, output reg vs_o, output reg hs_o, output reg [DATA_WIDTH-1:0] data_o ); // 第一级同步直接用PCLK上升沿打拍 // 这里不需要异步处理因为所有信号本来就和PCLK同步 always (posedge pclk or negedge rst_n) begin if (!rst_n) begin de_o 1b0; vs_o 1b0; hs_o 1b0; data_o {DATA_WIDTH{1b0}}; end else begin de_o de_i; vs_o vs_i; hs_o hs_i; data_o data_i; end end endmodule你可能觉得这段代码没什么技术含量但它解决了一个真实的问题输入信号之间的相位偏差。FPGA引脚上的信号经过IBUF、布线之后到达触发器的延迟不完全一致如果DE比数据早到半个时钟周期你抓到的数据就会错位画面出现杂色条纹。打拍之后所有信号统一延迟一个时钟周期相位自然就对齐了。3.3 发送端配置与数据打包发送端ADV7513的初始化是另一个关键点。它上电后默认处于待机模式必须配置正确才能开始编码输出。核心配置包括视频输入格式24位RGB还是16位YCbCr、输出分辨率对应的时钟分频参数、同步信号极性、色彩空间转换开关等。ADV7513初始化的代码和接收端类似也是I2C写寄存器序列。我建议在hdmi_tx_cfg.v里写成参数化的数组形式方便修改// 以ADV7513为例写入寄存器的参数列表 // 每个元素表示 {寄存器地址, 寄存器值} localparam integer REG_NUM 47; reg [7:0] reg_addr [0:REG_NUM-1]; reg [7:0] reg_data [0:REG_NUM-1]; // 初始化阶段利用I2C驱动依次写入 // 核心配置项包括 // 0x41: 输出格式为RGB24位 // 0x48: 使能视频输入 // 0x55: 设置像素时钟分频根据输入分辨率选择 // 0x9A: 设置同步信号极性适应输入信号特征这里有一个很重要的思想发送芯片的输出并不是凭空产生的它只是把你给它的并行数据编码成TMDS差分信号。所以输入给它的VS、HS、DE、数据和PCLK必须严格符合你期望输出分辨率的时序要求。环路输出实验里这些信号直接来自接收芯片相当于时序天然匹配但如果以后你做分辨率转换就必须自己生成一套符合目标分辨率的同步时序了。3.4 透传模式与画面验证所有初始化配置完成后环路通路的核心代码其实就一句话// 透传模块接收端的数据和同步信号完全原样传递给发送端 wire [23:0] rx_data_aligned; wire rx_de_aligned; wire rx_vs_aligned; wire rx_hs_aligned; loop_back u_loop_back ( .pclk (pclk), .de_in (de_aligned), .vs_in (vs_aligned), .hs_in (hs_aligned), .data_in (data_aligned), .de_out (tx_de), .vs_out (tx_vs), .hs_out (tx_hs), .data_out (tx_data) );下载到板子上之后接上HDMI信号源显示器应该能立刻看到画面。如果没画面先别急大概率不是代码问题而是初始化卡住了。这时先确认I2C配置是否完成比如在状态机里加一个计数器配置完成信号拉高后再让数据通路使能避免数据在芯片没初始化好的时候就送过去。实测中我建议在发送端数据通路上加一个使能信号初始化完成前输出DE拉低初始化完成后再放行数据。这个做法虽然简单但能避免很多莫名其妙的黑屏问题——芯片还没准备好就喂数据它可能输出乱码甚至直接锁死。4. 常见问题与排查技巧实录4.1 新手最容易踩的坑写在前面做这个实验时新手最容易出现几个扎心的情况显示器显示“无信号”、画面满是雪花、颜色明显偏色、图像有噪点或花屏、画面能显示但是会间歇性闪屏。下面整理一张速查表都是我实际调试中踩过的坑和对应的处理方法现象可能原因排查方向完全无信号HPD没有被拉高检查HDMI座子18脚电平确认接收芯片HPD输出配置寄存器正确完全无信号I2C配置失败用逻辑分析仪抓I2C波形确认地址和ACK信号正常蓝屏/黑屏但有菜单接收芯片没有检测到输入检查输入信号源分辨率是否被芯片支持检查输入端口选择寄存器雪花点或杂色条纹数据位序错位或相位未对齐检查软件上数据线序配置ADV7611的引脚交换寄存器偏色严重色彩空间不匹配确认接收芯片输出RGB还是YCbCr确认发送芯片输入格式配置一致间歇性闪屏像素时钟不稳定或电源纹波大示波器测PCLK频率稳定度检查板卡3.3V和1.8V电源纹波画面有重影TMDS输出驱动电流不够或线缆过长调大ADV7513输出电流幅度换短线缆验证只有HDMI输出能显示VGA等接口不行接口电路与接收芯片通道映射不匹配核对通道选择寄存器和实际硬件接口走线对应关系4.2 I2C读不回来的排查实战I2C问题是最多的单独拎出来详细说。I2C总线只有两根线SCL和SDA看起来简单但排查起来很容易让人心态崩溃。如果I2C读不回来数据按照下面的顺序逐步排查第一量电压。SCL和SDA线在总线空闲时都应该被上拉到3.3V如果哪根线是低电平说明有器件把它拉死了逐个断开I2C从设备排查。第二看波形。用示波器抓SCL的时钟信号看频率是否正确高电平幅度是否足够。ADV7611的I2C最高支持400kHz但我的经验是第一版驱动放在100kHz最稳妥信号质量差也不容易出错。第三确认地址。7位地址和8位地址容易搞混ADV7611的7位地址是0x4C但写寄存器的8位地址是0x98读是从0x99开始。很多人的代码里地址写错了一直读不到ACK。还有一个很容易忽略的细节I2C总线上多个器件时上拉电阻的阻值要合理。如果上拉电阻过大总线上升沿太慢高速模式就容易通信失败。2.2k到4.7k之间通常合适具体要看总线上挂了多少个器件。4.3 环回画面正常但有小瑕疵的处理思路有时候环路已经通了画面也能显示但仔细看会发现某些颜色过渡区域有轻微的噪点或者竖直细线边缘有彩边。这个问题多半出在色彩采样格式上。ADV7611可以输出RGB格式也可以输出YCbCr 4:2:2格式。如果接收芯片配置为输出YCbCr 4:2:2而发送芯片仍然希望输入RGB中间就需要做色彩空间转换和色度插值跳过这一步就会导致颜色偏色和边缘劣化。最简单的解决办法是让两个芯片都用RGB 4:4:4格式中间保持透传这样就能避免格式转换带来的画质损失。环路输出的实验场景中我建议直接用RGB 4:4:4 24位模式这是最简单、画质最好、中间不需要任何额外处理的工作方式。等你后面要做图像算法了再根据算法的需要决定是否切换到YCbCr处理——那时候会专门设计色彩空间转换模块而不是像现在这样直接透传。4.4 我在实际调试中积累的几点小经验最后分享几条没法写进代码注释里的经验是用时间堆出来的。其一保持I2C初始化状态机的可观测性。在状态机里加一个心跳计数器每完成一步配置就翻转一次调试引脚用示波器或者LED就能直观看到初始化进度。哪一步卡住了LED就停在哪个状态比翻代码快得多。我习惯用4位拨码开关显示状态编号调试效率提升非常明显。其二视频调试的黄金法则是一次只改一个变量。很多人遇到黑屏立刻开始同时改I2C配置、改数据通路、换线缆结果问题没解决连之前能用的状态都回不去了。正确做法是第一步验证I2C通信第二步单独测试接收芯片输出写一个简单的计数器代替真实数据第三步固定一个已知良好的输入源第四步再动发送芯片配置。每一步都确认无误才进行下一步。其三信号源的选择也有讲究。不要上来就用4K电脑输出测试1080p的接收芯片分辨率超出芯片能力会导致检测混乱。最好的调试信号源是一台可以手动设置输出分辨率的设备从720p60开始调起逐级往上测基本能覆盖所有问题。其四也是很多文档不会写的HDMI输出对线缆质量非常敏感。开发板和显示器之间的HDMI线如果太长或者质量太差即使信号链路本身没问题也可能显示异常。手头至少备一根1米以内的短线和一根质量好的HDMI线用于排除线缆因素。别问我为什么强调这一点我被一根劣质线坑过整整一个下午。做这个实验时我心里一直记着一句话视频链路是流水线任何一级出问题都会导致最终画面异常但调试时一定要逆向逐级定位从最靠近显示器的环节开始反推而不是从信号源开始一路查下去。先确认发送芯片配置无误再确认数据到达了发送芯片再确认接收芯片输出了数据最后才去怀疑输入信号本身。这个思路看起来反直觉实际效率却是最高的——因为“显示器没画面”这个问题源头大概率不在输入而在输出和中间链路。