我之前带 FPGA 入门项目时,很多同学跑完流水灯和串口回环之后,会陷入一个"不知道下一步做什么"的空窗期。其实有个实验特别适合卡在这个节点做——HDMI 视频输入与环路输出。它不像图像算法那样依赖大量数学基础,也不像高速接口那样上来就怼 GTX,却能一次性把视频时序、像素时钟、TMDS 编码、约束文件这些 FPGA 开发的核心概念全串起来。做完这个实验,你对"FPGA 到底在板子上怎么干活"的理解会完全不一样。
这个实验的最终效果很直观:电脑或电视盒子的 HDMI 输出接开发板,开发板再把同一路视频信号从另一个 HDMI 口送出去,接上显示器就能看到完全一样的画面。中间 FPGA 什么都没改,只是"接住"信号,再"递出去"。听起来有点傻?但这正是关键——环路输出意味着你要完整地实现 HDMI 接收端和发送端,把输入的视频流原封不动搬进 FPGA 内部,再原封不动搬出去。这个过程中任何一根线没接对、任何一个时序参数有问题,画面就直接黑屏,没有任何中间状态可以糊弄。
这篇文章我会用黑金系列开发板(AX7A035 这类 Artix-7 板卡)作为参考,把从 HDMI 信号原理、硬件电路、RTL 设计到上板调试的完整链路讲一遍。文章里所有步骤都是按"自己重新搭一个工程"的思路写的,不是让你直接烧现成 bit 文件,而是让你真正知道每一行代码为什么这么写。
1. HDMI 环路实验的必然性与整体框架
1.1 为什么是 HDMI,而不是直接上 MIPI 或 PCIe
很多初学者会觉得,HDMI 都是成熟的商用方案了,FPGA 在这里面还能有什么戏?实际上恰恰相反。HDMI 是目前最容易在开发板上跑通的"真高速视频接口"——它的单通道速率在 1080p60 下大约是 1.485Gbps,已经远超普通 GPIO 的能力范围,但又没有快到必须动用 SerDes 的程度,普通 IO 配上差分对就能胜任。
对比一下 MIPI(移动设备摄像头/屏幕常用)和 PCIe(电脑总线),你会发现 HDMI 是性价比最高的教学载体:
| 接口 | 信号特点 | FPGA 处理难度 | 入门友好度 |
|---|---|---|---|
| MIPI D-PHY | 源同步、DDR、低电压摆幅 | 需要特殊 IO 和逻辑校准 | 较高 |
| PCIe | 高速 SerDes、协议栈复杂 | 需要硬核或 GTX 收发器 | 很高 |
| HDMI | TMDS 编码、单沿数据、电平标准明确 | 无需硬核,普通 IO 即可 | 较低 |
HDMI 用的 TMDS(Transition Minimized Differential Signaling)虽然也是差分信号,但数据速率相对于 PCIe 的 5Gbps 甚至 8Gbps 来说温和得多。它在 FPGA 里的实现路径是:专用差分输入缓冲器 → 串转并 → 字节对齐 → 像素恢复。每一步都有明确对应的原语和 IP,学起来不会像 PCIe 那样一头雾水。
1.2 环路输出到底在环什么
"环路输出"(Loop-out)在专业视频设备里很常见,比如矩阵切换器、监视器、采集卡,输入信号进来之后除了自身处理,还要原样转发一路给下一级设备。在 FPGA 实验里,环路的意思就是:
输入端 HDMI 信号 → 解码出并行像素数据和同步信号 → 原封不动按输出时序重新编码 → HDMI 发送端输出
整个环路中间没有帧存储、没有格式转换,是纯粹的组合逻辑通路。正因为中间"什么都没做",所以它是验证 HDMI 收发链路是否正确的黄金测试。如果环路输出都不工作,别急着上图像算法,先回头查链路。
这个实验还有一个隐性好处:它让你提前接触"视频处理流水线"的雏形。后面你要做缩放、OSD 叠加、边缘检测,都是在"视频数据流"上做变换,而这个数据流的获取方式,就是这个实验要解决的。
2. 先厘清 HDMI 信号链路:TMDS 通道与时序参数
2.1 HDMI 线里到底有几路信号
HDMI 线看起来很粗,里面实际有用的高速差分信号是 4 对:3 对数据通道(Channel 0/1/2)和 1 对时钟通道(Clock)。在 1080p60 下面,像素时钟是 148.5MHz,时钟通道上跑的就是这个 148.5MHz 的差分时钟,3 个数据通道各自以 3.5 倍像素时钟的速率(约 520Mbps)传输数据。
为什么是 3.5 倍?因为 HDMI 1.4 采用 TMDS 编码,每 8 bit 像素数据编码成 10 bit 传输,然后把 24 bit 像素(RGB 各 8 bit)拆成三路,每路 8 bit 编成 10 bit,串行发出去。这样 8 bit × 10/8 × 3 = 30 bit/像素时钟周期,正好是 3 通道 × 10 bit 串行。这就是为什么每条 TMDS 数据通道的速率总是像素时钟的 10 倍,而加上编码冗余是 3.5 倍像素时钟(10/3 约等于 3.33,实际上是 10 倍像素时钟除以 3 路,即 3.33... 倍;但 520Mbps 这个数值通常是按 148.5MHz × 3.5 来的,这个 3.5 是包含了控制和辅助数据在消隐区传输的带宽预算)。
初学者看到这些数字很容易晕,但后面写 RTL 时你就明白了:FPGA 内部处理的永远是 10 bit 并行数据,串行速率只是约束和时序分析时才需要关心的数字。你只需要保证"接收端的串并转换把 10 bit 数据抓对,发送端的并串转换把 10 bit 数据发出去",中间的逻辑完全不涉及串行比特。
2.2 像素时序的三个关键信号:DE、HSYNC、VSYNC
HDMI 的数据流本质上和 VGA 一脉相承,只是物理层编码不同。一个完整的视频帧由若干行组成,每行又分为有效像素区和消隐区(blanking)。发送端会持续输出三个同步信号:
- DE(Data Enable):高电平表示当前时钟周期正在传输有效像素数据,低电平表示处于消隐期。
- HSYNC(行同步):每行开始时的同步脉冲,用于接收端确定行边界。
- VSYNC(帧同步):每帧开始时的同步脉冲,用于接收端确定帧边界。
这三个信号加上像素时钟 PCLK,就是 HDMI 接收端(无论 SiI9134、ADV7611 还是 FPGA 内部逻辑)恢复视频流的核心。我们说的"视频时序参数"(如 1920×1080@60Hz 的 hActive=1920, hFrontPorch=88, hSyncWidth=44, hBackPorch=148,行总数 2200;vActive=1080, vFrontPorch=4, vSyncWidth=5, vBackPorch=36,帧总数 1125)描述的就是这些信号的精确位置。
环路输出的设计目标,就是让输出端的这三个信号和输入端的完全一样,不做任何改动。实际上你会发现,HDMI 接收 IP(黑金的 hdmi_rx 模块)解出来的 DE/HSYNC/VSYNC 可以直接送到发送模块,画面就能正确显示。
2.3 接收端还有一个隐藏角色:EDID
我在第一次做这个实验时忽略了一个东西——电脑的显卡不会自动把 HDMI 信号发给你的开发板,它要先"问"一下显示设备支持什么分辨率。这个"问"的过程就是通过 DDC 通道(I2C,SCL/SDA)读取 EDID(Extended Display Identification Data)。
如果你的开发板上没有接 EEPROM,也没有用 FPGA 逻辑模拟 EDID 响应,电脑会认为"这个 HDMI 口没接显示器",于是拒绝输出信号。黑金系列开发板的 HDMI 输入口旁边都有一颗 24C02 之类的 EEPROM,出厂预烧了 1920×1080@60Hz 的 EDID 数据,所以插上线就能直接出画面。但如果你想用自己的板子或者想支持不同分辨率,就必须自己搞懂 EDID 的 128 字节结构,这是后面进阶要处理的事。
环路实验在这一点上很取巧:输入什么样的信号,就输出什么样的信号,不需要主动协商分辨率。但你必须知道 EDID 的存在,否则换了电脑没画面时会一头雾水。
3. 硬件视角:开发板上的 HDMI 电路与引脚约束
3.1 HDMI 座子背后的电路:直连与转换
黑金 AX7A035 这样定位的板卡,HDMI 输入和输出电路通常有两种做法:一是 HDMI 座子直接引出 TMDS 差分对到 FPGA 引脚(直连方案),二是经过一颗 HDMI 转 LVDS 或转并行 RGB 的芯片(桥接芯片方案)。
直连方案更适合做这个实验,因为你需要 FPGA 直接处理 TMDS 电平。如果板子上有 SiI9134 或 ADV7611 这样的接收芯片,信号已经被转成并行 RGB + 时钟了,那就变成了"并行视频输入实验",跟 TMDS 解码没太大关系。所以做环路实验前,先看板卡原理图,确认你的板子是不是直连方案。
3.2 迪康的 I/O 标准到底该选什么
XDC 约束文件里最关键的两行是:
set_property IOSTANDARD TMDS_33 [get_ports {hdmi_rx_p[0]}] set_property PACKAGE_PIN K17 [get_ports {hdmi_rx_p[0]}]这里的 TMDS_33 表示 3.3V 的 TMDS 电平标准。你可能会有疑问:HDMI 标准电平不是 5V 吗?实际上 HDMI 的 TMDS 差分摆幅在 400mV 左右,共模电压约 3.3V,FPGA 的 TMDS_33 IO 标准就是为这种信号设计的。Artix-7 系列的 HP(High Performance)和 HR(High Range)bank 都能支持,但最好用 HR bank,耐压更宽。
除了电平标准,还有一个关键点是差分对的位置约束。FPGA 引脚定义里会明确标注哪些引脚是正负配对(P-N pair),比如 hdmi_rx_p[0] 在 K17,那 hdmi_rx_n[0] 一定在相邻的 K18。XDC 里只需要约束正引脚,负引脚会自动匹配,但如果你在 XDC 里写错 pair,综合时会直接报错。
3.3 电源与去耦:为什么 HDMI 供电电流要留足
热搜词里有一条"hdmi供电电流需要多大",这个问题在开发板上不突出,但如果是自己做板子就要重视。HDMI 座子的 +5V 引脚给源端(电脑)提供的是检测信号,标准要求至少 50mA 带载能力。开发板上这颗 +5V 通常由 USB 或 DC 座供电而来,如果电源设计太弱,可能出现"插上 HDMI 线电脑就重启"的奇葩问题。
我自己调试时遇到过输入图像有水波纹,排查到最后是开发板的 3.3V 电源纹波太大。HDMI 眼图对电源噪声很敏感,尤其 TMDS 输出的共模噪声会直接变成画面上的雪花点。所以做这个实验之前,建议用示波器看一下开发板供电纹波,如果超过 50mV,加个 LC 滤波或换电源适配器比改代码有效得多。
4. 环路输出逻辑:接收与发送的像素搬运
4.1 接收端 RTL:从差分信号到并行像素
直连方案下,FPGA 首先要做的是把 TMDS 串行数据变成并行数据。这里有两个层次:物理层要用 IBUFDS 原语把差分信号转成单端;然后是串并转换,把 10 bit 串行流变成 10 bit 并行。
黑金的参考工程常用一个基于 Xilinx ISERDESE2 的接收模块。ISERDESE2 是 FPGA 内部的高速串并转换器,支持 1:10 模式,正好匹配 TMDS 的 10 bit 编码。关键代码结构大致是:
// 差分转单端 IBUFDS #(.DIFF_TERM("TRUE"), .IOSTANDARD("TMDS_33")) u_ibufds_ch0 (.I(hdmi_rx_p[0]), .IB(hdmi_rx_n[0]), .O(tmds_ch0_s)); // 串并转换核心 ISERDESE2 #( .DATA_RATE("DDR"), // TMDS 是 DDR,每个时钟边沿都采样 .DATA_WIDTH(10), .INTERFACE_TYPE("NETWORKING") ) u_iserdes_ch0 ( .D(tmds_ch0_s), .CLK(tmds_clk_p), // 像素时钟 .CLKB(~tmds_clk_p), .CLKDIV(pclk_x2), // 仍然是比特时钟/2,用于并行数据域 .O({q9, q8, q7, q6, q5, q4, q3, q2, q1, q0}) );这段代码有个坑:ISERDESE2 的 O 输出在不同位宽模式下,位序是反的,需要根据实际仿真来定。我第一次照搬参考代码时,画面颜色完全错乱,就是位序没对齐。建议接上 Vivado 仿真,先给一个 8'hFF 的像素值,看 ISERDES 输出的 q[9:0] 是不是 10'b1111111111,如果不是就调一下位序。
4.2 字节对齐:抓住那个"魔法 10 bit 头"
串并转换出来的 10 bit 数据流本身不知道边界在哪,因为 HDMI 数据是连续的。接收端必须找到 TMDS 编码中的控制字符边界,这个过程叫"字节对齐"或"字符对齐"。
TMDS 在消隐区会发送固定的控制码,比如行同步头对应的控制码是 10'b1101010100 或 10'b0010101011,这些特定编码在其他位置不会出现。所以接收逻辑的思路是:不断滑动 10 bit 窗口,如果连续几个窗口都匹配控制码,就认为当前相位对齐了。
这里有个经验技巧:对齐逻辑不要看一两个周期就决定,最好连续等到 4~8 个控制码都匹配再锁定。否则在画面复杂区域的随机数据也可能碰巧匹配,造成误锁,表现就是画面周期性撕裂。这个"一定时间内持续匹配才锁定"的思路,在高速接口调试里叫"锁定滞后",属于标配操作。
4.3 像素数据恢复:解码 24 bit RGB
字节对齐搞定后,接下来的工作是 TMDS 解码——把 10 bit 数据还原成 8 bit 像素。这一步可以直接用 Xilinx 官方 IP 或者自己写查找表。黑金参考工程里通常会给一个tmds_decode模块,核心逻辑是:
// TMDS 解码是通过查找表完成,10 bit 输入,8 bit 输出 // 其实也可以做成组合逻辑,根据编码规则反推 assign pixel_rgb[7:0] = lookup_r[data10];自己写解码逻辑时,最稳妥的方式是生成一张 1024×8 的 lookup 表,直接把所有 10 bit 码字对应的 8 bit 值列出来。这个过程可以写个 Python 脚本离线生成 Verilog 文件,比手写组合逻辑可靠得多,也不容易出现译码错误。
4.4 发送端 RTL:原样再编码
接收端把数据恢复成 24 bit RGB + DE/HSYNC/VSYNC + 像素时钟之后,发送端的任务就是完全反向的操作。但环路输出有一个微妙问题:输入像素时钟是外部给的,输出像素时钟也必须由外部提供,中间不能打 buffer 缓存数据。
如果你在接收和发送之间插了一个 FIFO(哪怕是异步 FIFO),就会有延迟,而且输入输出的时钟相位会不同步,导致画面抖动或卡顿。环路实验的精髓就是零缓存、纯组合逻辑直通:
// 核心接线,就这么直白 assign hdmi_tx_data[0] = tmds_encode(hdmi_rx_rgb[7:0], hdmi_rx_de, hdmi_rx_hsync); assign hdmi_tx_data[1] = tmds_encode(hdmi_rx_rgb[15:8], hdmi_rx_de, hdmi_rx_vsync); assign hdmi_tx_data[2] = tmds_encode(hdmi_rx_rgb[23:16], hdmi_rx_de, 1'b0);3 个通道分别编码 R、G、B 像素,同时把 HSYNC 和 VSYNC 嵌入对应的控制通道的消隐期。发送端的 TMDS 编码器逻辑也是用 lookup 表实现,或者用 Xilinx 提供的tmds_encoderIP。编码完成后交给 OSERDESE2 做并串转换,再经过 OBUFDS 转成差分对输出。
OSERDESE2 同样有 DDR 位序问题,做发送端时如果颜色不对,优先怀疑位序,而不是怀疑时序约束。我在实际项目中还发现过一个更隐蔽的问题:OSERDESE2 的 OCLK 必须用 BUFIO 驱动,不能直接用全局时钟,否则时序收敛不了,报一堆 hold 违例。
4.5 复位与跨时钟域:最容易忽略的细节
环路虽然数据通路是直通的,但接收和发送模块都有自己的时钟域。接收端用输入像素时钟(从 TMDS Clock 通道恢复的时钟),发送端按理说应该也用同一个像素时钟,因为输入和输出分辨率相同。
但这里的"同一个"有两种实现:一是直接拿接收到的时钟通道信号(经过 IBUFDS + BUFG)同时驱动接收和发送模块,这是最简单的;二是锁定到 MMCM/PLL 重新生成一个同频时钟,用于平滑抖动。
我推荐第二种方案。因为输入 TMDS 时钟经过长线传输后抖动比较大,直接拿它驱动发送端 OSERDESE2,产生的输出信号恐怕过不了 HDMI 合规性测试。用 MMCM 重新生成一个同频同相时钟,虽然增加了两个时钟周期延迟,但信号质量好很多。黑金参考工程里的做法就是先用 MMCM 把输入像素时钟倍频/分频到目标频率,再同时驱动收发的并行逻辑,效果好很多。
5. 分辨率与时序适配:环路实验里不用改,但要懂的东西
5.1 1080p60、720p60、480p 的时序参数对照
环路实验有个好处:输入什么分辨率,就输出什么分辨率,不需要在 FPGA 里做缩放。但调试时你必须知道不同分辨率对应的时序参数,这样看波形图才知道哪里出了问题。
下面是我整理的最常用的三种分辨率参数(CEA-861 标准):
| 分辨率 | 像素时钟 (MHz) | hActive | hFrontPorch | hSyncWidth | hBackPorch | 行总数 | vActive | vFrontPorch | vSyncWidth | vBackPorch | 帧总数 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1920×1080@60 | 148.5 | 1920 | 88 | 44 | 148 | 2200 | 1080 | 4 | 5 | 36 | 1125 |
| 1280×720@60 | 74.25 | 1280 | 110 | 40 | 220 | 1650 | 720 | 5 | 5 | 20 | 750 |
| 720×480@60 | 27.0 | 720 | 16 | 62 | 60 | 858 | 480 | 9 | 6 | 30 | 525 |
调试时用 720p 比用 1080p 省心得多,因为像素时钟只有一半,Slack 更好满足,信号完整性压力也小。如果你的输入源支持切换分辨率(比如电脑的显示设置),先切到 720p 调试,通了再切到 1080p。
5.2 为什么显示器黑屏:EDID 协商失败的典型现场
环路输出模式下,显示器接的是开发板的 HDMI 输出口。这里有个容易被忽略的点:显示器能不能点亮,取决于开发板输出的信号是否符合显示器的 EDID 要求。如果显示器只支持 1080p,而你的输入源是 4K,开发板环路输出的还是 4K 信号,显示器就黑屏。
这时候你会误以为"FPGA 环路坏了",实际上信号是好的,只是显示器不支持。验证方法很简单:把电脑 HDMI 直接插到显示器,如果也能出画面,那说明显示器肯定支持这个分辨率;如果不支持,换个支持的分辨率再试。
另外,显示器的 EDID 协商是靠 HDMI 输出座的 DDC 引脚读的。开发板的 HDMI 发送电路里通常也有一颗 EEPROM 存放板端 EDID,用来告诉显示器"我能输出最高 1080p"。如果你发现显示器只能输出低分辨率,大概率是这颗 EEPROM 的 EDID 内容限制的,可以通过 I2C 重新烧写 EDID 来解决。
5.3 视频时序发生器 vs 环路直通:调试对比
为了排除环路逻辑的问题,黑金参考工程通常会提供一个"测试模式"——一个视频时序发生器(Pattern Generator)直接驱动发送端,输出彩条或棋盘格信号。调试顺序非常推荐:
- 先跑测试模式,让开发板直接输出彩条,接到显示器看是否能正常显示。
- 如果能显示,说明发送端链路(OSERDES、编码器、约束)没问题。
- 再把输入信号接入,把环路逻辑使能,逐步排查接收端。
这个思路完全符合"先测路径后半段,再测前半段"的调试原则。我遇到过好几个学员,环路不通就直接怀疑接收端,折腾了两天才发现是发送端发送端时序约束少了 create_clock 导致时序收敛不了,输出眼图全花了。
6. 上板调试:常见故障表现与排查顺序
6.1 画面完全无信号:从电源到 EDID 的逐层检查
"插上去显示器显示无信号"是最烦人的故障,因为可能的原因太多。我的排查顺序是:
- 看开发板指示灯:HDMI 输入口通常有电源指示灯,确认 +5V 正常。如果插上 HDMI 后开发板电源指示灯微闪,说明电源被拉垮了,换电源适配器。
- 看电脑端的显示设置:打开电脑的显示设置,看是否识别出了第二块屏幕。如果没有识别,先查 EDID——用 I2C 工具读输入口 EEPROM,确认里面数据完好。
- 看 FPGA 内部信号:用 ILA(集成逻辑分析仪)抓输入端的 rx_de 信号,看有没有波形。如果 DE 一直是 0,说明接收端没有锁定,可能是 TMDS 时钟没恢复出来。
ILA 抓信号这个步骤非常关键。在 Vivado 里综合时把 rx_de、rx_vsync、pxl_clk、locked 这几根信号加进去,上板后实时看。如果 pxl_clk 有翻转但 rx_de 恒为 0,问题一定在字节对齐或解码;如果 pxl_clk 都没有,问题在时钟恢复或输入时钟约束。
6.2 画面有但花屏或撕裂:位序和同步信号的战争
花屏通常分两种:一种是有图像轮廓但颜色错乱,另一种是画面周期性撕裂。
颜色错乱几乎可以肯定是位序问题。TMDS 的每个通道传输的是 8 bit 数据(某个颜色分量),ISERDESE2 或 OSERDESE2 的位序不对,会导致输出的 8 bit 值不是原来的值。比如红通道的 bit0 变成了 bit7,那红色分量就变成 0x80 而不是 0x01。这种问题用彩条信号源最容易验证——正常彩条的颜色渐变是规律的,错乱后的渐变色会明显异常。
周期性撕裂则多半是同步信号的问题。VSYNC 或 HSYNC 在环路直通时被额外打了一拍,或者发送端把 HSYNC 和 VSYNC 的编码通道搞混了。在 TMDS 编码里,HSYNC 是嵌在 Channel 0 的控制区,VSYNC 是嵌在 Channel 1 的控制区。如果你在代码里写反了,画面会上下滚动或左右偏移,而且帧率和行频都会不对。
6.3 浅尝辄止:信号完整性导致的偶发黑屏
这类故障最隐蔽。电脑显示画面正常,但只要一动 HDMI 线,画面就闪黑,或者一开机有时有画面有时没画面。
原因基本是 FPGA 引脚和 HDMI 座之间的阻抗不连续,TMDS 信号反射严重,导致接收端有时候能锁定有时候不能。解决办法,如果是自己画的板子,检查差分走线的阻抗控制在 100 欧姆;如果是现成开发板,检查 HDMI 座是否虚焊,尤其是 DD 和 DDC 信号。
另外,开发板的 HDMI 座如果长期插拔导致簧片松动,也会出现这种间歇性故障。不要老觉得是代码问题,硬件接触不良才是这种"时好时坏"故障的最高频原因。
6.4 用眼图还是用示波器:普通调试就够用
很多初学者会问,要不要用高速示波器看眼图来定位信号完整性?说实话,做环路实验真用不上。只要你的开发板是正规设计的,TMDS 信号质量通常都有保证。你需要的只是普通双通道示波器,能看差分对的眼图(用数学通道做 A-B 运算),确认摆幅和分离度大概正常就行。
真正的眼图测试需要 1GHz 以上带宽示波器 + HDMI 治具,一般只有做产品认证才需要。在开发阶段,逻辑排查远比物理层排查重要——大多数问题出在 RTL 位序和对齐逻辑上,不在信号完整性上。把 ILA 用好,比纠结眼图有用一万倍。
7. 从环路输出到视频处理:下一步扩展建议
7.1 在环路上加 OSD:显示帧计数或调试信息
环路输出来回跑通后,下一个自然的方向就是在这条数据通路上做点"小手脚"——比如叠加 OSD。做法是在 DE 有效期间,判断当前像素坐标是否落在某个矩形区域,如果是就把像素值替换成固定颜色:
always @(posedge pxl_clk) begin if (de) begin if ((h_cnt >= 100) && (h_cnt < 200) && (v_cnt >= 100) && (v_cnt < 120)) pixel_out <= 16'hFFFF; // 左上角一块白色区域 else pixel_out <= pixel_in; end end这里的关键是需要自己维护 h_cnt 和 v_cnt 计数器,用 DE 清零和 HSYNC/VSYNC 同步。这个逻辑非常简单,但它是后面所有图像处理的基础——因为一切图像算法,本质都是"在正确的坐标位置对像素值做变换"。叠加一个帧计数,就能帮你直观地确认输入视频的场序和帧率。
7.2 帧缓冲与缩放:为什么要引入 DDR3
环路直通虽然简洁,但做不了"冻结画面"或"帧率转换"——因为没有任何存储,数据必须实时流过去。当你想实现帧缓冲、缩放、去隔行这些功能时,就得把视频帧存到 DDR3 里,然后再按输出时序去读。
黑金 AX7A035 板载 DDR3,用 MIG IP 可以很轻松地作为帧缓存。但这一步的复杂度会突然上升很多倍:DDR 的仲裁、读写带宽匹配、行缓冲管理、帧同步逻辑……每一项都是独立的知识点。
我的建议是,环路实验先不要碰 DDR。先把"零缓存直通"玩到熟,理解了像素流和同步信号的互动关系之后,再引入 DDR 做帧缓冲。否则你会在"DDR 读写冲突"和"视频时序"两个大坑之间来回跳水,最终什么也没学明白。
7.3 边缘检测与颜色转换:FPGA 图像处理的第一步
环路已经具备了"RGB 流输入 + RGB 流输出"的能力,那你完全可以在中间插一个图像处理模块。最简单的实验是用 Sobel 边缘检测做 3×3 卷积,或者做 RGB 转灰度:
assign gray = (pixel_r[7:0] * 77 + pixel_g[7:0] * 150 + pixel_b[7:0] * 29) >> 8;灰度转换只需要 3 个乘法器和加法器,一行代码的事。但做完之后你会立刻遇到一个概念:像素流的行缓存。Sobel 需要同时取当前行的相邻像素和上一行、下一行的相邻像素,这要求你必须把至少两行像素缓存起来。这时你会发现,原来"视频处理"的难点从来没有先进算法,而是怎么管理好行缓冲和时序对齐。
这一步做完,你对 FPGA 图像处理的理解就真正入门了。
做这个实验时,我经常跟学员说一句话:"凡是黑屏,先查链路;凡是花屏,先查位序;凡是撕裂,先查同步。"听起来像顺口溜,但确实是我踩了无数坑之后总结出来的排查顺序。环路输出最大的价值不在于它本身,而在于它逼着你把视频链路的一切细节都过一遍——而这些东西,是所有后续 FPGA 视频项目的地基。