尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA入门必做实验:HDMI环路输出原理与调试指南

FPGA入门必做实验:HDMI环路输出原理与调试指南 做了快四年FPGA如果让我给刚入门的朋友推荐一个“练手不劝退、学完有成就感”的实验十有八九我会选HDMI视频输入与环路输出。这个实验之所以值得写是因为它几乎把FPGA入门的核心知识点都串起来了差分接口、时钟域、视频时序、同步信号、寄存器配置再加上一块看得见摸得着的显示器输出成功那一刻的正反馈特别强。用“环路输出”这个词可能有些朋友不太熟悉。简单说就是把HDMI输入进来的画面在FPGA里做处理后直接从HDMI输出口送出去形成一个“视频环路”。最朴素的环路就是不做任何处理输入什么就输出什么但即便什么都不做中间也一定要经过接收、时序恢复、重新驱动这几个环节而这些恰恰是以后做图像缩放、OSD叠加、目标检测的基础。这篇教程就以黑金开发板为例带你从头到尾跑通这个实验每个引脚怎么约束、每段代码在干什么、上板后黑屏怎么排查我尽量都写清楚。1. 实验核心思路先搞懂环路输出到底在做什么1.1 什么是HDMI环路输出环路输出Loop Through在专业视频设备里非常常见比如切换台、分配器、监视器上都有。它指的是视频信号进入设备后经过内部处理后原样输出到下一级设备多个显示设备看同一个画面就是靠这个功能实现的。在FPGA实验里做环路输出本质上是在复现这条链路HDMI输入接口接收到来自电脑、电视盒子或者测试信号源的视频流板载的接收芯片把串行TMDS信号解码成并行RGB数据和同步信号FPGA拿到这组并行数据后不做任何处理或者只做简单的时钟同步直接送给HDMI发送芯片重新编码成TMDS信号输出到显示器上。最简版本可以理解为“逻辑直通”。但把事情做对没那么简单因为输入侧像素时钟和输出侧像素时钟必须严格保持一致否则画面会撕裂甚至完全错乱。这个实验真正的难点不在代码量而在于理解视频数据的流向和时序关系。1.2 为什么拿这个实验做FPGA入门我见过很多人入门FPGA第一反应是点LED、跑流水灯然后做按键消抖做到UART收发已经觉得自己很厉害了。但实际上这些都停留在“逻辑”层面而HDMI环路把 FPGA 拉进了“系统”层面要同时处理接口协议、时钟域、视频标准的问题这才是工程和逻辑玩具的分水岭。而且这个实验的反馈极其直观。跑流水灯再快也就是几个灯闪但把HDMI环路调通的那一刻你显示器上出现电脑桌面的延伸画面时那种“我真的用FPGA把视频干通了”的感觉比看一万行仿真波形都来得实在。此外这个实验留了非常大的扩展空间。环路通了以后想加个RGB颜色交换、灰度转换、缩放、OSD文字都是在现有链路上加模块的事。很多公司招FPGA图像方向的实习生第一道题目就类似这个。所以不管你是学生还是转行自学的朋友这个实验都值得认真做一遍。1.3 需要的硬件与软件环境我用的是黑金的AX7020开发板主芯片是Xilinx Artix-7系列XC7A020。这个板子自带一个HDMI输入接口和一个HDMI输出接口刚好满足环路实验的需求。如果你的板子是其他型号也不要紧逻辑思路完全一样只要板卡上同时有HDMI输入输出、FPGA逻辑资源超过5K LUT就够了。软件方面我用的是Vivado 2019.2版本不用纠结2018.3之后都没问题。另外准备一个带HDMI输出的电脑或电视盒子作信号源一台HDMI显示器两根HDMI线。这里有个小细节建议用带屏蔽层的好一点的HDMI线我之前用一根特别便宜的线调试信号质量差到接收端偶尔失锁排查了整整一天。2. 硬核原理铺垫HDMI接口、TMDS与视频时序2.1 HDMI接口定义和信号线到底是怎么回事HDMI接口一共19根引脚看起来很多但真正传输视频数据的只有3对TMDS差分线外加1对TMDS时钟差分线。每一对TMDS线在发送端通过编码把8bit数据转成10bit串行数据在接收端再解码还原。这也是为什么接收芯片能输出并行数据、而不是直接给FPGA串行数据——串行信号的比特率太高FPGA直接处理并不划算交给专用芯片解码是更合理的方案。除了TMDS数据线之外还有几根慢速线值得关注DDC线I2C用于读取显示器的EDID信息让信号源知道显示器支持什么分辨率。HPD线Hot Plug Detect热插拔检测信号源靠它判断显示器有没有接入。CEC线消费电子控制用于设备联动做实验基本用不上。我建议你拿起手里的HDMI接头对着引脚定义看一下然后把“哪几根是数据、哪几根是控制”在脑子里过一遍。看懂这个后面接约束文件的时候就不慌了因为你知道哪几个引脚是接收芯片出来的有效数据引脚。2.2 视频时序行场同步、消隐区和有效像素视频时序是所有视频实验绕不开的基础。无论是VGA还是HDMI底层都是同一套标准HDMI只是把信号的物理传输方式改了逻辑层面依然是像素时钟、行同步HSync、场同步VSync和数据使能DE这几个信号在配合工作。可以用一个很生活化的比喻显示器每一帧就是一块画布的扫描过程。行同步告诉你“我要开始画下一行了”场同步告诉你“我要开始画下一帧了”DE为高时送出去的数据才是真正显示在屏幕上的像素DE为低时数据属于消隐区只是为了给扫描电路留出回扫时间。以1080p60这个最常用的分辨率为例关键参数如下参数数值像素时钟148.5 MHz每行总像素2200有效像素1920行同步宽度44行前肩88行后肩148每帧总行数1125有效行数1080场同步宽度5场前肩4场后肩36很多教程喜欢直接发一堆常数但不解释怎么来的。我建议你把上面这张表背下来原因是后面无论做时序检测还是做输出驱动都要精确对齐这些数字差一个像素画面都会歪。2.3 环路输出为什么要做时序“透传”刚开始做这个实验的时候我踩过一个经典误区想自己用计数器按标准时序生成1080p信号然后送给输出芯片。结果显示器直接提示“无视频输入”折腾一圈发现输入侧信号的像素时钟和时序参数和标准只是看起来一样实际上存在微小的偏差和抖动自己生成一套时序根本没法做到和输入信号严格对齐。这也是环路输出的核心思想不要自己造时序而是从输入信号中把行场同步和数据使能“提取”出来套在并行RGB数据上直接输出。输入和输出之间的时钟必须同源也就是直接用接收芯片恢复出来的像素时钟驱动输出侧这样做才能做到真正“透传”。有朋友可能会问那以后做图像缓存呢答案是做缓存的时候才引入异步时钟域和缓冲FIFO那是另一套思路。环路实验的目的就是把最简单的通路先跑通建立正确的时序观。2.4 接收芯片在链路里的角色黑金AX7020板载的HDMI输入芯片是ADI的ADV7611。这颗芯片负责完成TMDS串行解码、时钟恢复、色彩空间转换、EDID管理最终通过48bit或24bit并行总线把RGB数据和同步信号交给FPGA。用一颗专用芯片而不是让FPGA直接接收TMDS有两方面原因。其一TMDS的串行速率在1080p60下约为1.485Gbps这种速率下时序收敛压力极大普通初学者完全驾驭不了其二ADV7611内部集成了均衡器劣化严重的信号也能恢复板级设计难度大大降低。所以整条数据链路可以画成HDMI输入线 → ADV7611 → 并行RGB HS/VS/DE → FPGA逻辑 → HDMI发送芯片 → HDMI输出线FPGA在中间扮演的角色是数据搬运工但你别小看这个搬运工它能搬对就说明你已经对同步信号、像素时钟和视频时序有了足够深的理解。3. 实战第一步Vivado工程搭建与引脚约束3.1 创建工程时选对芯片型号打开Vivado后第一步别急着写代码先把工程建对。黑金AX7020搭载的是XC7A020-2CSG324后缀里的“-2”代表速度等级CSG324是封装。不同的板子型号可能不一样你在Create New Project选择器件型号时一定要对照板卡丝印或者用户手册确认。工程名称我用的是hdmi_loop_test工程路径里不要带中文和空格这个习惯是被无数血泪教训换来的。Vivado在某些版本下对中文路径支持不好波形仿真、综合报告都有可能出现诡异问题干脆从源头杜绝。选择完器件型号后建议直接创建空的RTL工程就可以了不需要向导自动创建各种文件后面我们自己来添加。3.2 引脚约束XDC文件里到底要管哪些引脚引脚约束可能是很多入门朋友最陌生的部分。你拿到一个开发板原理图满屏的芯片名和脚位号一开始容易看花眼但其实做这个实验只需要关注三组引脚第一组是复位和按键引脚。我习惯把板载按键作为异步复位高有效还是低有效要看原理图。黑金AX7020的用户按键按下时拉到GND所以需要用低有效复位写代码的时候注意一下方向就好。第二组是ADV7611输出的并行RGB和同步引脚。这组引脚数量较多24bit RGB加上HS、VS、DE还有恢复时钟加起来接近30根。每个引脚都要一一对应到FPGA的BANK引脚上错一个都出不来画面。第三组是HDMI输出芯片的输入引脚。输出芯片选用的是ADI的ADV7513或者兼容型号它接收FPGA送来的并行RGB、同步信号和像素时钟再编码成TMDS串行信号。实际的XDC文件片段大概是这样的set_property PACKAGE_PIN AF14 [get_ports hdmi_rx_clk_p] set_property PACKAGE_PIN AG15 [get_ports hdmi_rx_data_p[0]] set_property IOSTANDARD TMDS_33 [get_ports hdmi_rx_clk_p] create_clock -period 6.734 [get_ports hdmi_rx_clk_p]看到这里你可能想问clk的period 6.734ns是怎么来的这是1080p60的标准像素时钟148.5MHz对应的周期1秒除以148.5M约等于6.734纳秒。Vivado需要知道这个时钟频率才能对后续的时序路径做收敛分析。3.3 为什么像素时钟约束这么关键很多人写代码时只约束了引脚没有约束时钟综合也过了下载也没报错可是上板之后画面偶尔闪动甚至完全没画面。这种情况多半是时序约束缺失导致工具对路径不重视最终关键路径时序违规。FPGA工具本质上和编译器类似但它比编译器多做一件事布局布线时必须保证每个寄存器之间的路径延迟小于一个时钟周期。如果时钟约束不写工具会默认一个值可能和你实际的148.5MHz差得很远导致布出来的电路在真实频率下工作不了。时钟约束基本法接收芯片恢复出来的像素时钟用 create_clock 约束在输入端口上后续所有逻辑都跑在这个时钟域里工具会自动推导内部时序关系。如果有跨时钟域的模块比如之后扩展帧缓存还要补充异步FIFO和约束集但环路实验暂时不涉及。3.4 综合和布局布线前的选项小技巧在综合之前还有一个实用建议打开Project Settings里的Synthesis把综合策略改成Vivado默认的Performance_Explore。这个策略对时序收敛更友好代价是综合时间稍微长一点点对于这个实验来说就是多等一两分钟的事值得。还有一个我特别建议养成的习惯每一次修改代码后先只做综合Run Synthesis查看综合报告里的WNSWorst Negative Slack最差负时序裕量。如果WNS是负的说明时序不满足后面布局布线多半也会稀碎。如果WNS是正的再接下去跑布局布线。别每次都一股脑直接跑到底等几分钟才发现时序挂了又得从头来浪费时间。4. 核心模块设计每段代码在做什么为什么4.1 接收端ADV7611的寄存器初始化连接ADV7611和FPGA之间除了并行数据线还有一条I2C控制总线。ADV7611内部的几十个寄存器决定了输入格式、输出格式、同步信号极性等行为需要初始化成我们希望的模式。这一步不做的话数据引脚上永远是全零或者垃圾值。初始化配置我是用I2C主控逻辑完成的。最核心的几项配置是// 核心寄存器配置示例 // 0x98是ADV7611的主I2C地址8bit方式 // 配置输入为HDMI模式输出为24bit RGB 444 i2c_write(0x98, 0x0A, 0x80); // 定义BANK i2c_write(0x98, 0x0B, 0x44); // 输出格式RGB 4:4:4 i2c_write(0x98, 0x0C, 0x00); // 输出模式 i2c_write(0x98, 0x16, 0x10); // 使能视频输出我没法把全部寄存器列出来因为不同板卡配的ADV7611具体版本有差异完整寄存器配置表在黑金的例程里都有。你拿到例程后重点是看懂I2C写时序和每个寄存器的作用而不是无脑复制。尤其是0x0ABANK选择这个寄存器ADV7611把寄存器分成多个BANK写任何寄存器前都必须先写BANK号这个坑容易漏。4.2 时序检测从恢复出来的信号里提取有效的帧参数ADV7611把HS、VS、DE都恢复好了送给FPGA理论上FPGA直接透传就行。但为了保险我建议在透传之前先做一个“时序特征检测”模块用计数器测量输入HS的周期和DE的宽度从而判断输入信号是什么分辨率。always (posedge pix_clk or negedge rst_n) begin if (!rst_n) begin line_cnt 0; hsync_cnt 0; end else if (hsync_in) begin hsync_cnt hsync_cnt 1; end else if (hsync_in_d1 !hsync_in) begin // 检测到HS下降沿记录这一行计数 line_period hsync_cnt; hsync_cnt 0; end end这段代码做的事情是持续用像素时钟计数每次行同步信号出现下降沿时把这个行周期的计数值存下来。如果算出来的周期接近2200就知道收到的信号是1080p60如果接近1650那大概率是720p60。为什么这个检测重要因为环路输出虽然不需要重新计算时序但初始化阶段往往需要确认输入信号确实被锁住了。如果HS周期检测出来是0或者乱跳大概率是ADV7611的配置或者物理连接有问题可以提前中止实验不用继续调试。4.3 输出驱动透传也要管好复位和时钟使能输出侧其实比接收侧简单。FPGA需要把ADV7611恢复出来的像素时钟直接作为输出时钟送给发送芯片然后把RGB数据和同步信号打一拍再送出去。“打一拍”这个细节值得说道说道。接收芯片输出的数据和时钟之间是存在一点相位偏差的直接用原始数据可能有建立时间违例。我习惯用像素时钟先把所有输入信号寄存一拍再用同一时钟送给输出端这样输出端的信号比输入端延迟一个周期但相位关系完全对齐。always (posedge pix_clk or negedge rst_n) begin if (!rst_n) begin rgb_o 24d0; de_o 1b0; hsync_o 1b0; vsync_o 1b0; end else begin rgb_o rgb_i; de_o de_i; hsync_o hsync_i; vsync_o vsync_i; end end很多人问你做FPGA不就是写代码吗其实不是。代码只是25%剩下75%是理解接口、时序和硬件行为。透传模块虽然只有十几行但它是整个实验里对时序理解要求最精准的部分写错一个时钟沿输出就花屏。4.4 顶层模块把碎片串成一条链顶层模块的作用是把I2C初始化、输入接收、时序检测、输出驱动四个部分串起来。底层模块之间的信号连接要清晰我习惯把每一条连线都单独命名比如hdmi_rx_rgb、hdmi_rx_de、pixel_clk_out不要图省事直接用a、b、c这样的短名字不然一个月后再看自己的代码完全不知道在干什么。顶层模块里还有一个细节复位逻辑。ADV7611有一个复位引脚需要一个上电复位脉冲才能正常工作。复位信号的时序也是有要求的——必须在电源稳定之后释放。我习惯用计数器实现一个延时的上电复位拉低至少10毫秒后再释放。5. 上板调试遇到的问题和我的排查方法5.1 第一次上板的完整操作流程调试这个实验有一套固定的顺序我建议每次都严格按顺序执行可以省掉很多不必要的困惑。先确认硬件连接信号源电脑的HDMI线插到开发板的HDMI输入口上。注意开发板一般会标注“HDMI IN”和“HDMI OUT”两个口第一次插反的人非常多插反之后现象是显示器没有信号而板卡上没有任何明显报错。然后用另一根HDMI线连接开发板的HDMI OUT口和显示器。最后打开电脑和显示器电源给开发板上电。接下来是软件操作顺序打开Vivado的Hardware Manager下载bit文件观察显示器。如果正常情况下电脑的画面应该直接出现在目标显示器上。如果没出现不要慌按下面顺序排查。5.2 黑屏问题的排查思路我自己调试过程中踩过很多坑这里把最高频的几类问题整理成一张速查表你照着排查效率会高很多现象可能原因排查方法显示器完全无信号HDMI IN和OUT接反检查线缆物理连接显示器有信号但全黑ADV7611初始化失败用ILA抓I2C配置过程确认寄存器写成功画面有严重雪花噪点像素时钟约束错误或数据位错位检查XDC约束检查RGB位序画面滚动闪烁行场同步极性反了检查ADV7611寄存器里输出的同步极性设置画面正常但有偏移DE与RGB数据错拍检查输出透传模块的数据对齐热插拔后画面消失HPD信号没拉高确认板卡HPD连接到高电平黑屏原因里ADV7611初始化失败是我遇到率最高的。给一个非常有效的检查方法用Vivado的ILAIntegrated Logic Analyzer集成逻辑分析仪抓取ADV7611输出端的单个像素时钟周期数据。如果DE信号完全没有任何翻转说明接收端没有出数据如果DE有翻转但RGB恒定为0说明数据通道有问题如果RGB随着画面变化而变化说明问题出在输出链路。5.3 时钟约束没写对的典型翻车现场有一次我在一个非标准分辨率下做测试信号源输出的是1366x768但我XDC里写的还是1080p的148.5MHz时钟约束。综合时序检查没有问题因为额外约束的时钟和实际时钟偏差不大工具把路径按148.5MHz收敛了但实际信号像素时钟是85.5MHz一些在该频率下接近极限的路径就暴露了问题画面偶尔出现水平撕裂。后来我把XDC改成了更通用的方案用SDC约束里的create_clock -period直接指向HDMI接收芯片恢复的时钟端口配合Vivado推荐的纯透传路径也就是加set_false_path约束处理那些不需要时序收敛的跨时钟路径再重新布局布线问题彻底消失。从这件事我得到的教训是时钟约束不要写死某个分辨率对应的频率如果实验要做多种分辨率最好把约束写得更宽容或者干脆用自动检测频率的逻辑动态修正时钟频率的假设。当然对于入门实验先固定跑1080p60把约束写对之后再讨论多分辨率兼容。5.4 上板调试的一个小技巧利用OSD叠加验证通路我调试这个实验时最头疼的事情是画面不出来的时候搞不清到底哪一段出了问题——是输入侧没锁住还是输出侧时序不对还是中间逻辑写错了后来我养成了一个习惯在透传通路上加一个OSD叠加模块。也不是做什么复杂的菜单就是在画面角落画一个固定颜色的小方框用计数器判断行坐标和列坐标的范围处于方框区域时把原图像素替换成红色。这个小方框作用巨大。如果显示器上能看到红色方框说明整条链路是通的只是画面数据可能有其他问题如果红色方框都看不到说明从输出侧往回推大概率是ADV7611初始化或者FPGA逻辑有问题。这个判断能帮你把问题定位到一个很小的范围内而不是在整条链路里大海捞针。OSD叠加模块的代码非常简单核心就是行列计数// 在画面的左上角画一个100x100像素的红色方块 always (posedge pix_clk) begin if (de_i h_cnt 10 h_cnt 110 v_cnt 10 v_cnt 110) rgb_o 24hFF0000; else rgb_o rgb_i; end注意这里用到的h_cnt和v_cnt需要和DE信号配合计数DE为高时才累加DE为低时清零。这个逻辑放到后面的图像处理项目里就是所有像素级操作的基石现在多花点时间理解不亏。6. 环路通了这个实验就结束了吗当然不是环路输出实验跑通意味着你已经跨过了FPGA图像处理的第一道门槛。接下来你可以顺着这条链路往两个方向深入。第一个方向是做图像处理。输入端接好之后在透传链路上加一个灰度转换模块把RGB转成YUV再输出或者加一个亮度调节模块改变RGB三通道的系数。这些做起来不会太难但会让你慢慢理解图像数据在FPGA里是如何流动和变换的。第二个方向是做缓存和帧率变换。HDMI环路实验不受帧缓存限制但很多图像处理算法比如缩放、去隔行、叠加半透明菜单都需要用到DDR3缓存和跨时钟域处理。黑金板载DDR3颗粒跑起来之后就是一个完整的视频处理开发平台了。最后再分享一个我个人的经验这个实验做完后面的瓶颈不再是代码能力而是调试能力。FPGA视频项目90%的时间都花在了“画面为什么不对”这种现象级问题的排查上。你每做一次这样的实验对时序、接口、信号完整性的理解就会深一层这些理解是看多少书都换不来的。所以不要怕踩坑把坑都踩明白了你就真的懂FPGA了。
返回列表