FPGA入门项目:HDMI视频输入与环路输出实验详解

发布时间:2026/9/7 13:53:06
FPGA入门项目:HDMI视频输入与环路输出实验详解 不少学FPGA的朋友入门第一个正经项目就是LED流水灯第二个是串口收发到了第三个往往就不知道该折腾什么了。我个人的建议是直接做一个HDMI视频输入与环路输出实验——把电脑或HDMI信号源的视频接进FPGA在板上做点处理之后再从另一个HDMI口输出到显示器。这个实验麻雀虽小五脏俱全能把时钟、状态机、FIFO、跨时钟域、视频时序这些FPGA基础硬技能一次性串起来而且效果直观接上线看到画面出来那种成就感比对着Modelsim看波形强太多了。这篇文章以“黑金云课堂”系列里常见的FPGA基础实验为背景聊一聊HDMI视频输入与环路输出到底在做什么、怎么入手、踩过哪些坑。适合两类人看一是刚学完Verilog语法、想找个综合性项目练手的FPGA入门者二是准备做图像处理、视频采集相关项目但还没理清HDMI链路怎么打通的开发者。我会尽量把原理、代码结构、调试思路揉在一起讲不堆砌术语但涉及的关键参数一个都不会少。1. 实验定位与设计思路1.1 为什么拿HDMI输入和环路输出当入门项目别小看这个实验它和传统的“按键控制LED”“串口回环”有本质区别。串口实验的数据率通常是115200bps你甚至可以用延时函数慢慢收发而HDMI视频链路是真正的实时高速数据流。以1080p60为例像素时钟148.5MHz也就是说FPGA每个时钟周期都要处理一个像素没有任何喘息空间。在这种压力下你写代码的方式会发生变化组合逻辑能不能满足时序、状态机有没有冗余周期、FIFO深度够不够这些问题在低速实验里根本暴露不出来一到视频实验全变成真实存在的坑。另一个原因是视频实验的“反馈闭环”非常完整。信号从HDMI进经过FPGA处理再从HDMI出你能直接看到画面结果。比如你做色彩反转屏幕上立刻变色你做一个画框叠加立刻能看到边框你把DE信号搞错直接花屏。这种即时反馈对学习的帮助远大于波形仿真。我见过不少朋友做串口实验做得很好但一到时序设计就蒙本质原因就是缺少这种“实时信号流”的训练。这个实验还能顺带把I2C配置、芯片寄存器读写、像素时钟恢复、同步信号对齐这四件事全练了。这些技能在后续做MIPI、LVDS、DDR视频缓存时全部用得上属于FPGA图像处理方向的基础设施。1.2 硬件方案怎么选接收芯片、GTX还是IO直连做HDMI视频输入硬件上有三条路线我先把它们列清楚你再决定自己做的时候选哪条。方案实现方式难度成本适用场景接收芯片方案HDMI座接到ADV7611/ADV7612等专用芯片芯片把TMDS解码成并行RGB/YUV信号交给FPGA低高芯片贵入门实验、工业视频采集GTX收发器方案HDMI座直接接FPGA的GTX高速引脚用IP核完成TMDS接收与解码高低省芯片进阶、多通道视频、4K处理IO直连方案用普通Bank的LVDS差分IO接HDMI自己写IDELAY/ISERDES做串并转换很高低极少数低分辨率场景黑金这系列教程里大多用的是接收芯片方案因为对新手最友好。FPGA拿到的不再是高速串行差分信号而是已经解码好的并行像素数据一般是RGB888、DE、HSYNC、VSYNC、像素时钟这五个关键信号。你不需要处理TMDS 8b/10b解码也不需要自己做时钟恢复复杂度瞬间下降一个量级。输出侧也类似常见的做法是接一颗HDMI发送芯片FPGA把并行RGB和时序信号交给发送器由发送器完成串行化编码。这样整个实验的重点就落在FPGA内部的“数据通路处理”上而不是纠结高速接口物理层非常适合入门阶段。GTX方案当然更“硬核”Xilinx官方有HDMI 1.4/2.0 TX/RX Subsystem IP内部已经包含了PHY、TMDS编解码、音频插入等逻辑你只需要配置IP参数再写应用层。它的优点是不需要外部芯片、BOM成本低缺点是需要GTX Bank的专用引脚、IP授权、以及大量时间去搞懂PHY配置单是时钟恢复和链路训练就能折腾半个月。如果你是第一次做HDMI建议先从芯片方案跑通再考虑GTX。1.3 环路输出到底是什么环路输出这个词最早出现在专业视频设备里英文叫Loop Out或Loop Through指的是一个输入接口同时带一个“环通”输出接口信号进来后原样送出去再接下一台设备。比如一个视频分配器一路输入、多路输出其中某一输出就是环通用来串接下一级设备而不丢失信号。放到FPGA实验里“环路输出”变成了两种模式的统称。第一种叫直通模式输入像素数据进FPGA后最多做点颜色变换、OSD叠加然后立刻送给发送器输出延迟非常小只有几个像素时钟的处理时间第二种叫帧缓存模式输入数据先写进DDR3内存读出来再输出可以做画面冻结、帧率转换、任意大小缩放但延迟至少是一帧16.7ms。我建议入门阶段先做直通模式原因是它不涉及DDR控制器FIFO、寄存器打拍、时序重建这些基本功足够你消化一阵了。等直通模式稳定出图再往里面加帧缓存那时候你对数据流的理解已经到位写DDR读写仲裁也不会觉得无从下手。2. HDMI信号链路先搞清楚接口在传什么2.1 TMDS传输和像素时钟先算一笔账HDMI物理层用的是TMDS差分传输总共有4对差分信号线3对数据通道Data0、Data1、Data2加1对时钟通道。每个像素的RGB三色各8bit经过8b/10b编码后变成3路10bit数据分别在三个数据通道上并行传输。像素时钟则由发送端同步提供接收端拿这个时钟来采样数据。像素时钟和分辨率的对应关系是必须背下来的。1080p60的像素时钟是148.5MHz720p60是74.25MHz4K30是297MHz4K60是594MHz。注意这些数值不是简单的“宽×高×刷新率”因为还要考虑消隐区。1080p60的实际一行有2200个像素时钟其中有效像素只有1920一帧有1125行其中有效行只有1080所以148.5MHz 2200 × 1125 × 60而不是1920 × 1080 × 60。有了像素时钟整个链路的带宽就好算了。每通道数据率 像素时钟 × 10bit1080p60就是1485Mbps三通道合计4.455Gbps。这就是为什么很多入门级FPGA开发板最多只敢标称支持1080p60再往上4K的话普通IO已经跑不动必须上GTX高速收发器。你做实验前先确认自己的开发板和接收芯片支持到哪个分辨率别拿4K信号去喂一个只支持1080p的板子。2.2 三路数据通道里装的信息很多初学者以为HDMI传输的是RGB像素和行场同步信号分开走实际上行场同步根本没有独立引脚它们是编码进数据流里的。HDMI规定在有效显示区DE为高时Data0传蓝色B或Cb色度Data1传绿色G或Y亮度Data2传红色R或Cr色度。在消隐区DE为低时Data0和Data1通道会传HSYNC和VSYNC状态Data2通道则传CTL0~CTL3控制信号。接收端通过DE信号来判断当前是有效像素还是消隐期然后把HSYNC和VSYNC从数据流里解出来。这就引出一个关键点在FPGA内部处理时你真正要关心的是DE、像素时钟和像素数据这三个信号。HSYNC和VSYNC对图像处理逻辑来说没那么关键因为你完全可以靠DE来计数重建行场坐标。发送芯片输出给你的信号里一般会把DE、HSYNC、VSYNC都引出来但你只要把DE用对行场重建就成功了一大半。2.3 行场时序参数从VIC表到FPGA计数器做视频实验手边一定要有一份行场时序参数表。以最常见的1080p60为例关键参数如下参数数值行有效像素Hactive1920行前肩Hfront88行同步宽度Hsync44行后肩Hback148行总像素Htotal2200场有效行Vactive1080场前肩Vfront4场同步宽度Vsync5场后肩Vback36场总行数Vtotal1125这些参数不需要背但要知道去哪里查。视频电子标准协会VESA有完整的协调视频时序CVT标准HDMI规范里也有CEA-861的VIC表网上搜“1080p60 timing”就能找到。实际使用中接收芯片输出给FPGA的往往是“有效数据流”而不是原始TMDS时序所以FPGA侧你只需要根据DE的上升沿和下降沿来维护像素计数器和行计数器。我在代码里习惯这样写行场坐标reg [11:0] x_cnt; reg [11:0] y_cnt; always (posedge pclk_in or negedge rst_n) begin if (!rst_n) begin x_cnt 12d0; y_cnt 12d0; end else if (de_in) begin if (x_cnt 12d1919) begin x_cnt 12d0; if (y_cnt 12d1079) y_cnt 12d0; else y_cnt y_cnt 12d1; end else begin x_cnt x_cnt 12d1; end end else begin x_cnt 12d0; y_cnt 12d0; end end这个模块的含义很简单DE有效期间每个像素时钟x_cnt加1到1920就换行y_cnt在换行时加1到1080就归零DE无效时全部清零。这样一个计数器就复现了帧内每个像素的逻辑位置后续做画框、OSD叠加、区域处理全部依赖这个坐标值。3. FPGA内部模块拆解与实现3.1 输入侧接收芯片初始化与像素数据获取以常见的HDMI接收芯片ADV7611为例FPGA上电后要干的第一件事不是处理视频而是通过I2C接口初始化芯片。ADV7611的I2C从机地址一般是0x988bit地址格式内部寄存器分主映射和CP映射配置流程大体是上电延时、解除复位、配置输入端口、配置输出格式、开启视频处理通道、拉高HPD热插拔检测引脚。初始化代码里最容易犯错的地方是上电时序。如果复位拉低时间不够芯片内部PLL还没稳定你后面对寄存器写入再多也是白搭。我一般的做法是先拉低复位引脚并延时100ms再拉高再延时200ms然后才开始I2C写入。每个寄存器的写入间隔不要小于1ms宁可慢一点也不要让总线在芯片还没ready时就开始干活。配置完成后芯片会把HDMI输入转换成并行数据输出。最常见的输出格式是RGB888信号包括像素时钟pclk_out、数据使能DE、行同步HSYNC、场同步VSYNC以及RGB三组信号每组8bit。有些芯片也可以配置成YCbCr输出但对FPGA基础实验来说RGB是最直观的不需要做色彩空间转换。还要注意一个细节接收芯片在检测到有效HDMI信号之前DE会一直保持低电平像素时钟可能会停振或者输出乱跳。所以FPGA侧最好加一个“视频有效锁定”状态检测到连续若干帧都有正常的DE上升沿后再认为链路已经建立否则宁可保持输出黑屏也别把垃圾数据往外送。3.2 像素时序重建把DE变成行场计数器上一节给的计数器代码只是最基础的行场坐标实际工程里还要做两件增强一是像素数据对齐二是帧同步信号生成。像素数据对齐是因为接收芯片输出的RGB数据与DE可能存在一个或几个时钟周期的错位。如果FPGA在DE上升沿立刻采样RGB取到的可能是前一帧最后几个无效像素。保险做法是让RGB数据先寄存两拍然后寻找DE上升沿与数据边缘的稳定关系。我在实践中发现不同芯片的错位周期差异很大有些是0拍有些是1拍稳妥的办法是用一个小的状态机在DE上升沿之后的下一个时钟沿再开始计数器同时让RGB数据多打一拍保证采样点落在数据稳定区域。帧同步信号的生成也很简单。当y_cnt从最大值跳回0的那个时钟周期输出一个单脉冲frame_start。这个信号后面有大用处直通模式下做OSD叠加时用它来同步刷新显示内容帧缓存模式下它作为写DDR的帧同步起始点。我在这个模块里还会加一个“像素锁定检测”逻辑统计连续多少帧DE宽度是否符合预期。比如1080p60每一行的DE高电平周期数应该是1920个像素时钟。如果连续检测到100帧都是1920就认为输入信号稳定置一个lock信号然后才把数据送入后面的处理模块。这个设计能避免输入信号抖动时后面模块跟着乱跳。3.3 环路输出实现直通模式和帧缓存模式的取舍直通模式是环路输出实验的主打内容也是最容易出效果的部分。代码逻辑非常直观assign rgb_out_r invert_en ? ~rgb_in_r : rgb_in_r; assign rgb_out_g invert_en ? ~rgb_in_g : rgb_in_g; assign rgb_out_b invert_en ? ~rgb_in_b : rgb_in_b; assign de_out de_in; assign hs_out hs_in; assign vs_out vs_in; assign pclk_out pclk_in;这段代码把输入像素做了颜色反转其余信号原样透传。很多初学者做完这一步会发现画面居然真的能显示了顿时信心大涨。直通模式的优势是延迟极低。从接收芯片输出数据到FPGA把数据送到发送芯片中间只差几个时钟周期对实时交互场景非常友好。而且在这种模式下你可以在数据通路里插入任意图像处理逻辑比如灰度转换、边缘检测、OSD叠加每一帧画面都会实时呈现处理效果非常适合验证图像算法。帧缓存模式则完全是另一套思路。输入数据先写入片外DDR3的帧缓冲区输出侧从DDR3读回数据再发送。它解决的是“输入输出时钟不同源”的问题输入可能是60Hz的HDMI信号输出可能需要按另一个独立时钟发送两者之间没有固定的相位关系只能靠帧缓存来缓冲。帧缓存模式下“冻结画面”这个功能做起来非常容易——只要停止从DDR3读取新帧显示器上就是定格画面这个效果在安防监控和医疗显示行业非常常见。但帧缓存模式的代价也很明显延迟至少增加一帧60Hz信号下是16.7ms需要通过DDR控制器要处理读写仲裁、帧同步、buffer切换。这些内容足够单独写一篇长文入门阶段不要在一开始就all in进去。我见过太多人直通模式都没跑通就急着上DDR缓存结果画面不是撕裂就是花屏调试到怀疑人生。3.4 跨时钟域最容易被忽略的一环环路输出实验里有个隐藏地雷就是跨时钟域。输入侧接收芯片给出的像素时钟和输出侧发送芯片需要的像素时钟虽然数值可能都是148.5MHz但它们来自不同的晶振和PLL频率有微小偏差相位完全不同步。如果你不管三七二十一直接把输入数据和输入时钟同时送到输出侧短时间可能看不出问题但运行几分钟后画面上偶尔会出现一条亮线或闪一下这就是时钟频率偏差积累导致的数据溢出或下溢。解决跨时钟域问题最标准的手段是异步FIFO。写入端使用输入像素时钟读出端使用输出像素时钟FIFO空满状态用格雷码跨时钟域同步。FIFO深度选择有个简单的估算方法输入输出频率偏差按万分之二估算1080p60的像素率约1.48亿像素每秒偏差部分约三万像素每秒也就是不到一行。但如果输入输出各自由独立晶振锁定长期运行下偏差会不断累积所以FIFO深度最小也要留一行的余量我一般取2048深度用起来比较安心。需要注意的是FIFO的读使能不能简单地在“非空”时一直读而要结合DE信号。正确做法是写入端只在输入DE有效时写数据读出端在DE无效时先暂停读等DE有效时再连续读出整行数据。这里要保证FIFO不会在读DE有效中途变成空否则画面会出现横向断裂。为了保险我通常把DE也一起写入FIFO读出DE的同时也就恢复了行的边界。3.5 顶层模块怎么连线整个工程的顶层结构其实不复杂分成四块接收芯片初始化模块、视频时序计数模块、数据处理模块、输出透传模块。再加上一个跨时钟域FIFO。模块输入输出作用ctrl_i2cI2C总线寄存器配置初始化接收芯片rtc_videopclk、DE、RGBx_cnt、y_cnt、frame_start时序重建fifo_async输入时钟域数据输出时钟域数据跨时钟域缓冲video_process像素坐标、RGB处理后RGB、DE直通处理/OSD叠加tx_init如有输出数据发送芯片配置初始化发送芯片这里有一个建议把“数据处理”和“时序控制”完全分开。时序控制模块输出像素坐标数据处理模块只负责根据坐标和输入像素值算出输出像素。这样做的好处是后续你想替换图像算法只需要改video_process模块其他部分完全不动。我见过很多人的工程把计数器和图像处理写在一个always块里改起来极其痛苦。顶层连线时还要注意复位时序。接收芯片的PLL锁定需要时间所以FPGA内部的复位信号最好用一个上电延时复位模块在PLL锁存信号拉高后再释放复位。用“所有模块共用一个异步复位”的老办法虽然省事但在视频工程里容易出问题因为时序逻辑和收发芯片I2C模块对复位的响应速度要求不同。4. 实操流程从工程到上板4.1 创建Vivado工程与时钟配置打开Vivado新建RTL工程目标芯片选你板卡对应的型号。黑金这系列一般是Xilinx 7系列比如Artix-7 XC7A35T或者Xilinx Zynq系列选型号时注意封装和速度等级不要选错。时钟配置是整个工程的地基。以接收芯片方案为例FPGA侧需要的主时钟一般有板载晶振时钟通常是50MHz或200MHz用MMCM/PLL生成后续逻辑需要的时钟。接收芯片输出给FPGA的像素时钟可以直接作为输入时钟使用但建议先经过一个BUFG再做全局时钟网络。有些板子的接收芯片pclk是LVCMOS单端信号要确认引脚在Bank类型是否支持作为时钟输入。如果你用的板子上还有发送芯片发送芯片的工作时钟一般也是像素时钟只不过它需要由FPGA侧提供。这里就出现一个选择发送芯片的pclk是直接用接收侧pclk通过延迟线还是用MMCM重新生成一个独立像素时钟我的建议是直通模式下用MMCM重新生成并且与FIFO读时钟同源这样时序约束简单信号质量也更好。4.2 约束文件怎么写得不出错约束文件XDC是视频工程最容易翻车的地方而且报错往往不那么直接。先写物理约束把每个引脚对应到原理图上的信号名。HDMI相关的引脚和普通LED引脚不一样有些引脚在高速Bank上有些是差分对引脚分配前一定先查板卡的原理图确认Bank电压域是1.8V还是3.3V别把3.3V信号接到1.8V Bank上。时钟约束方面要给接收芯片输出的像素时钟创建输入时钟约束create_clock -name pclk_in -period 6.734 [get_ports pclk_in]1080p60的时钟周期是1000 / 148.5 ≈ 6.734ns。如果你的实验支持多分辨率切换可以先用最常用的分辨率做约束后续再优化成多时钟约束。跨时钟域FIFO的路径要设置异步时钟组或者false path不然时序分析会报一堆违例。但注意不要无脑地set_false_path所有跨时钟域路径只对真正的异步接口设置就可以了否则可能掩盖真实问题。还有一类容易忽略的约束是输出延迟。数据从FPGA送到发送芯片时要满足发送芯片的建立保持时间要求。如果发送芯片和FPGA在同一块板上走线很短一般不需要严格的output delay约束实测能正常工作就行。但如果是通过排线连接到扩展板就必须根据走线长度估算延迟在XDC里写output delay。4.3 上板验证步骤与ILA调试烧录程序之前先检查硬件接线HDMI信号源是否连接到接收输入口显示器是否连接到输出口电源供电是否足够。HDMI接口供电电流需要多大这个问题很多人忽略。HDMI线的5V电源一般由信号源提供接收芯片和发送芯片的供电来自FPGA板卡整块板子加HDMI收发后电流可能比裸板高不少建议电源适配器选余量大的比如实际电流300mA至少用500mA以上的适配器。上板后第一步不是看画面而是用ILA抓内部信号。把pclk_in、de_in、RGB数据、x_cnt、y_cnt这些信号添加到ILA核里触发条件设为de_in上升沿采样深度设成4096。先看输入侧有没有数据进来如果ILA里de_in一直是0说明接收芯片没锁定信号问题在输入链路而不在FPGA逻辑如果de_in有脉冲但RGB全为0检查接收芯片输出格式配置如果一切都正常再看输出侧FIFO读出端的数据。我调试视频工程有一个习惯先把输入侧的ILA信号全部分析通过才开始看输出侧。因为输入侧一旦有误后面所有的现象都是连锁反应很容易让人误判为逻辑bug实际是硬件配置问题。等ILA确认信号链路正常再观察显示器画面。如果画面正常就可以开始测试图像处理功能比如把颜色反转开关打开看画面是否变成负片效果。如果显示正常但颜色不对多半是RGB三色引脚顺序接错或者芯片输出格式配置成了YCbCr。5. 常见问题排查实录5.1 典型故障速查表现象可能原因排查方向显示器完全无信号接收芯片未锁定、HPD未拉高、发送芯片未初始化ILA抓输入DE检查I2C配置和HPD引脚画面满屏雪花输入数据未锁定、像素时钟不对检查接收芯片PLL锁定状态确认输入分辨率是否在支持范围内花屏/横向撕裂跨时钟域FIFO空满异常、DE与数据错位检查FIFO深度检查数据对齐寄存器颜色整体偏色RGB映射错误、芯片输出格式不对对比原理图检查引脚分配检查芯片输出格式寄存器画面偏移半个屏幕行同步计数器起始位置不对调整DE与数据对齐多打一拍或提前一拍画面偶发闪一下时钟频率偏差积累、FIFO有溢出增大FIFO深度检查时钟是否来自同一PLL这张表里的每个问题我几乎都真实遇到过尤其是“画面偶发闪一下”最难排查。当时我以为是DDR读写冲突查了整整两天最后发现就是两个晶振之间的频率偏差造成FIFO每几分钟溢出一次现象表现为极轻微的画面闪烁不盯很久根本看不出来。5.2 我踩过的三个坑第一个坑是接收芯片初始化顺序不对导致HPD拉不起来。当时我把HPD引脚直接赋值为高电平以为这样显示器就能识别到信号源实际上HPD必须等接收芯片内部上电完成、I2C可以通信之后才能拉高否则主机侧会认为HDMI线缆断开根本不发送信号。正确的做法是把HPD控制放在初始化模块的末尾芯片配置成功后再拉高。第二个坑是直通模式下的跨时钟域问题。当时我偷懒没有做异步FIFO直接把输入pclk作为输出时钟用把数据打几拍后送给发送芯片。结果看起来画面正常但运行半小时后显示器偶发黑屏一瞬间再自动恢复。用示波器对比输入输出像素时钟发现两者频率差了几十赫兹数据必然会在某个时刻溢出。后来老老实实加上异步FIFO问题彻底消失。做视频实验跨时钟域不是可选项而是必选项。第三个坑是DE信号和数据错位。接收芯片数据手册里明确写了RGB数据与DE之间有流水线延迟但我当时没仔细看直接按照“DE高电平时数据就是有效像素”的思路去处理结果画面整体向右偏移了20多个像素而且右侧出现一条竖线。后来把数据多打了一拍同时调整计数器起点画面才完全对齐。不同芯片的延迟参数不一样最靠谱的办法就是上板后边看边调整别完全相信手册里的典型值。5.3 环路输出延迟怎么测如果你做的是专业视频方向一定要会测环路输出延迟。直通模式的延迟很小大概只有几微秒到几十微秒用肉眼是完全感觉不到的。测量方法有两种一是用双通道示波器同时测输入接口和输出接口的VSYNC信号计算两个信号上升沿的时间差但要注意示波器探头接在源端的高频信号上会引入额外延迟最好用有源差分探头二是在FPGA内部打一个测试引脚在输入侧检测到帧起始时输出一个脉冲在输出侧检测到帧起始时再输出一个脉冲用ILA或示波器测两个脉冲的时间差。帧缓存模式的延迟测量要容易得多因为帧稳定后延迟是固定的大约在一帧时间左右。你可以做一个简单的测试输入一个每秒跳变的画面用相机同时拍摄原始输入和环路输出两个显示器根据两幅画面的相位差估算延迟精度虽然不是很高但能直观感受到帧缓存带来的延迟。理解这个延迟概念很重要。在很多实际应用中比如手术医疗显示、游戏低延迟模式、现场导播环出环路输出的延迟指标是核心卖点。FPGA直通方案延迟低所以经常被用在高端显示设备里而普通PC处理的延迟可能几十毫秒。这也是为什么很多视频设备里仍然保留FPGA做环通信号处理的原因。最后分享一点个人体会HDMI环路输出实验看起来简单但真正做完之后你会发现自己对FPGA的时序设计、跨时钟域、芯片调试这些概念的理解完全不一样了。它不像流水灯那样“跑起来就算成功”而是要保证每一个像素、每一个时钟周期都正确这种严谨性才是FPGA工程师最需要的思维习惯。下一步如果你有兴趣可以在直通模式的基础上加一个灰度处理模块或者尝试叠加一个简单的OSD菜单改动量不大但对图像处理的理解又能上一个台阶。