ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

FPGA图像采集实战:LVDS接口与FIFO缓冲设计复盘

FPGA图像采集实战:LVDS接口与FIFO缓冲设计复盘 简介面向FPGA开发者的CAM内容寻址存储器设计资源提供基于Xilinx 7系列FPGA的完整VHDL实现源自官方xapp1151应用笔记。压缩包共104个文件、约1.43MB以Vivado工程配置xpr/xml、21个VHDL源文件、Modelsim仿真脚本do/wlf为主另含批处理、日志等辅助文件目录结构清晰便于快速定位设计文件。目前已有742人学习。包内project_1可直接导入Vivado 2018.3配合Modelsim 10.6进行功能仿真VHDL代码覆盖比较单元、地址生成器、冲突处理与控制逻辑等关键模块可直观理解CAM的并行匹配机制。通过分析源码与波形开发者能掌握FPGA上高速查找逻辑的实现方法并可根据需求调整容量、位宽和优先级策略进一步用于路由器查找表、数据库搜索、协议解析等场景。 前几天同事扔给我一个压缩包文件名就叫 Xilinx_CAM.rar说是之前一个摄像头采集项目的历史归档让我有空帮忙看看有没有还能复用的部分。解压完翻了翻发现这套东西还挺有代表性的一块 Xilinx FPGA 通过 LVDS 接口接收 CMOS 摄像头数据中间用 FIFO 做跨时钟域缓冲再用 LUT 查表做实时滤镜处理最后输出到显示端。整个链路从接口、存储到图像算法都有涉及正好适合拿来拆开讲讲。如果你正在做 FPGA 图像采集或者刚接触摄像头接口、FIFO 设计这类东西这篇复盘应该能帮你少走不少弯路。1. 项目归档剖析拿到 Xilinx_CAM.rar 之后的第一步1.1 归档文件背后的项目全貌一个名为 Xilinx_CAM 的工程核心功能基本可以猜到一半Xilinx 平台上的 Camera 采集处理单元。但这个包打开之后里面并不是只有一种摄像头方案而是把调试过程中的多个版本、多个模块归档在了一起。从目录结构看包含基于 ISE 14.7 的老工程、基于 Vivado 的迁移工程、以及若干独立 IP 核的仿真工程。如果你是自己从网上下载或接手别人的代码包第一步永远不是打开顶层模块看代码而是先把文件清单列清楚。我习惯先做一次归档体检主要关注几个点工程文件是 .ise 还是 .xpr对应的是哪一代工具RTL 代码是 Verilog 还是 VHDL这决定了后面仿真环境的搭建方式有没有附带约束文件.ucf 或 .xdc没有约束文件的工程基本等于没法直接上板IP 核是 Xilinx 官方生成的还是第三方写的官方 IP 会有 .xci 或 .xco 文件第三方 IP 往往需要单独授权。把这些信息理清楚你才知道这个工程能不能在当前环境下编译、上板验证的代价有多大。另外归档包中通常还会有几份文档比如设计说明、引脚分配表、调试日志。很多人不重视这些直接打开代码就开干结果在引脚分配上栽跟头。我的经验是先看文档再看代码文档里写的时钟频率、像素格式、电压标准往往比代码本身更能反映设计的真实约束。代码可以写得绕但引脚表和设计说明不会说谎。1.2 工程目录结构与关键文件识别以 ISE 14.7 时代的工程为例典型的目录结构大概是这样的cam_top.ise工程主文件双击打开即可进入 ISEsrc/RTL 源码目录里面有顶层模块、摄像头接口模块、图像处理模块ipcore_dir/Xilinx Coregen 生成的 IP 核比如 FIFO、PLL、MMCMucf/用户约束文件定义引脚位置和时序约束sim/仿真测试平台Testbench 都放在这儿iseconfig/ISE 的工具配置目录有时候包含 FPGA 型号信息顶层模块命名一般是cam_top或camera_link_top它的端口列表会直接告诉你这个系统对外的接口有哪些摄像头数据差分对、像素时钟、行场同步、显示输出接口、调试用的串口或 LED。看到这些端口整个项目的框架就基本清楚了。有一个细节需要注意归档包里的 IP 核生成文件能否在现有版本工具中正常打开。ISE 14.7 生成的 FIFO IP 核你拿到 Vivado 2020.1 里直接打开 .xco 文件大概率是不认的需要重新生成。这就意味着如果你只有归档包而没有原始工程环境那部分 IP 核必须在新工具里手动重建工作量不小。所以解压之后先确认工具链版本再决定是直接编译还是需要迁移。2. 摄像头采集链路的核心技术点2.1 摄像头接口选型为什么用 LVDS 而不是并行接口这个项目里摄像头输出用的是 LVDS低压差分信号接口而不是传统的并行 RGB 接口。原因其实挺实际并行接口在像素时钟高的时候信号完整性问题会非常突出线间串扰、时钟偏斜都会让你在时序收敛上耗费大量时间。LVDS 天生就是差分传输抗共模干扰能力强而且能走比较长的线缆适合摄像头模组和 FPGA 分开布局的场景。Xilinx FPGA 里用 LVDS 接收数据IO 标准需要在约束文件里明确指定。以 1.8V LVDS 为例UCF 里写NET cam_clk_p LOC J15 | IOSTANDARD LVDS_25; NET cam_data_p[0] LOC K16 | IOSTANDARD LVDS_25;注意1.8V 和 2.5V 的 LVDS 标准不同一个是 LVDS_18一个是 LVDS_25选错了电压标准轻则采样不到正确数据重则芯片发热甚至损坏。另一个容易被忽略的点是差分终端电阻Vivado 里可以设置DIFF_TERM TRUE来使能片内 100 欧姆终端ISE 里则需要外部电路配合。LVDS 接收端还有一个很关键的环节位对齐和字对齐。差分对传输的串行数据到达 FPGA 之后要先经过 ISERDES 串并转换再通过 IDELAY 调整相位保证采样点落在数据眼图中间。这个过程通常用一个 training pattern 来完成摄像头上电后会输出一段已知的同步码FPGA 收到之后不断调整延迟直到找到正确的对齐位置。这个逻辑在代码里看起来简单但调试起来非常依赖时序报告和 ILA 逻辑分析仪。2.2 FIFO 缓冲的架构设计与深度计算摄像头采集的像素时钟和显示端的像素时钟往往不是同一个来源中间必须有一个跨时钟域缓冲。这个设计里用的是 Xilinx FIFO IP 核底层是基于 BRAM 实现的异步 FIFO。选 FIFO 而不是直接写双口 RAM好处是省心读指针和写指针的跨时钟域同步、空满标志生成这些容易出错的细节IP 核都帮你处理好了。FIFO 深度怎么定这里有一个简单的计算思路。假设摄像头分辨率为 1280x720RGB565 格式每个像素 16bit一行数据量是 1280 × 16 20480 bit约 2.5KB。如果突发写入一整行然后显示端慢慢地读那么 FIFO 至少要能存下一行数据。再留出 20%-30% 的余量应对显示端回刷、消隐期的抖动选择 4KB 左右的 BRAM FIFO 是合理的。实际上很多工程并不会只做行缓冲而是做多行缓存目的是支持图像算法比如边缘检测中的 3x3 卷积。这就意味着要缓存 3 行像素FIFO 深度要乘以 3。资源占用会翻倍但换来的算法能力提升是值得的。2.3 时钟域规划像素时钟、串行时钟与显示时钟的关系摄像头送进来的像素时钟经过 PLL/MMCM 分频倍频后产生串行时钟用于 ISERDES同步接收差分数据。显示端如果走 HDMI那用的是另一路像素时钟来自显示时序生成模块。两路时钟频率可能完全无关所以 FIFO 在这中间承担的角色就是异步桥。在设计时钟树的时候我习惯先把所有时钟频率列成一张表注明来源和用途再决定哪一路作为主时钟哪一路作为派生时钟。Xilinx 的时序约束里用create_clock定义输入时钟用create_generated_clock定义 PLL 输出时钟。时钟约束如果漏了综合和布线阶段的时序报告会一塌糊涂很多新手排查半天发现是约束没写全。3. 图像处理管线与 LUT 滤镜实现3.1 LUT 滤镜的基本原理与实现方式LUT 滤镜这个词在视频后期领域很常见比如 Blackmagic CAM 的 LUT 滤镜包本质上是把输入的颜色值映射到输出颜色值。在 FPGA 上做这件事最直接的方式就是建一张查找表用像素值作为地址去查表查出来的内容就是处理后的像素值。一维 LUT 处理灰度图像比较简单输入 8bit 灰度值输出 8bit 灰度值一张 256 项的 BRAM 就能搞定。但彩色图像通常要用三维 LUT输入 R、G、B 三个分量组合成一个地址查表输出新的 R、G、B。这个查找表的大小是 256 × 256 × 256约 1600 万项一个 36Kb 的 BRAM 根本放不下。实际工程一般不会用完整的 3D LUT而是把每个颜色通道独立做一维 LUT或者生成一个降采样的大表再插值。这个项目里实现的是经典的复古色调滤镜把每个像素的 R、G、B 分别经过三段不同的查表曲线再输出。R 通道曲线整体抬升G 通道稍微压暗B 通道保持中低灰阶的细节。最终效果就是画面偏暖、对比增强很像胶片后期里常说的 teal-orange 风格。3.2 在 FPGA 上实现 LUT 映射的工程细节FPGA 实现 LUT 映射核心是把查找表内容初始化到 BRAM 里。用 Verilog 写的话就是在 initial 块里加载数据或者用$readmemh读取外部数据文件reg [7:0] lut_table [0:255]; initial begin $readmemh(lut_red.hex, lut_table); end这个lut_red.hex文件就是预先算好的红色通道映射曲线每行一个十六进制数。修改滤镜效果不需要改逻辑只需要替换 hex 文件这也是 LUT 方案在实际工程里最受欢迎的原因。流式处理时像素数据是每个时钟周期进来一个直接用像素值做地址从 BRAM 读取映射结果组合逻辑路径可以做到两个周期以内不会成为系统瓶颈。3.3 利用 LUT 同步实现 FIR 滤波与降采样项目里还顺带做了个小功能用 FIR 滤波器做水平方向的 2 倍插值然后发现设置不当的时候工具不会自动识别为半带优化。这个点值得单独说一下。FIR 插值的常规做法是输入像素流通过一个多相滤波器结构输出速率翻倍。Xilinx FIR Compiler IP 里可以配置插值因子选了 2 倍插值之后IP 内部会自动把滤波器系数拆成两个多相分支一半处理偶数序列一半处理奇数序列输出交错合并。这样实现效率高BRAM 和 DSP48 的占用都比直接做卷积更优。但有个坑当你把 FIR IP 的系数导入之后工具不一定自动启用半带优化。半带优化要求滤波器系数满足对称性约束奇数位置系数全零如果系数不满足条件工具只会按普通 FIR 实现资源占用直接翻倍。解决方法是先查看 FIR Compiler 的配置界面勾选“Optimize for Half-Band”选项并且确保系数文件本身满足半带条件。这个细节不留意综合后的资源报告会给你一个“惊喜”。4. Xilinx 工具链的使用要点4.1 ISE 14.7 在 Linux 环境下的安装与运行经验这个项目归档用的是 ISE 14.7这是 Xilinx 支持老器件比如 Spartan-6、Virtex-6的最后一个版本。ISE 14.7 在 Linux 下安装有几个需要注意的地方它依赖 32 位库64 位系统上必须先装好 ia32-libs 或者对应的 lib32 包安装过程需要 Java 运行时环境版本不能太新也不能太老OpenJDK 8 实测比较稳。安装完成后命令行启动比较可靠source /opt/Xilinx/14.7/ISE_DS/settings64.sh ise GUI 和命令行编译器都可以用。实际跑综合布线的时候我更喜欢命令行方式可以加日志重定向方便排查错误xst -ifn top.xst -ofn top.syr ngdbuild -uc top.ucf top.ngc top.ngd map -o top_map.ncd top.ngd top.pcf par -w top_map.ncd top_par.ncd top.pcf bitgen top_par.ncd top.bit这套流程执行完会生成 .bit 比特流用 iMPACT 工具下载到板卡。如果只是仿真验证用 ISim 就够不需要走到布线那一步。4.2 工程迁移到 Vivado 的注意事项如果你想把 ISE 工程迁到 Vivado 上做有几个需要注意的地方旧工程里的 Coregen IP 核FIFO、PLL、DCM不兼容需要重新在 Vivado IP Catalog 里生成UCF 约束文件不兼容Vivado 用的是 XDC语法和时钟约束定义方式差别很大xst综合风格被Vivado Synthesis替代部分 RTL 写法会有优化差异但基本语法兼容迁移工作看起来繁重但好处是 Vivado 的时序分析和调试能力比 ISE 强很多。ISE 时代看时序收敛基本靠猜Vivado 里直接在 Timing Summary 里看关键路径一目了然。如果条件允许新项目建议直接上 Vivado老项目维持 ISE 不动。4.3 IP 核配置与仿真调试技巧FIFO IP 核配置的时候有几个参数非常关键读模式选择 Standard 还是 First-Word Fall-Through前者读使能之后数据晚一个周期出来后者数据直接出现在读端口上适合需要低延迟的应用。但我实际用下来Standard 模式的时序更稳不容易在边界条件下出错。仿真调试的时候推荐用 Testbench 把摄像头行为模型建出来模拟一个 1280x720 帧的信号源通过 AXI Stream 接口灌注给处理链路然后在输出端抓像素值和 LUT 表里的期望值对比。这样可以快速验证滤镜逻辑是否正确不用等到上板再用逻辑分析仪慢慢找问题。5. 常见问题与排查技巧实录5.1 LVDS 采样不到数据的排查顺序上电后发现摄像头数据没进来一类典型特征是 VIO/ILA 里看到的像素值全是 F 或者 0。先不要急着改代码按下面的顺序排查确认摄像头模组已经正常初始化I2C 配置有没有写进去很多模组必须初始化之后才输出像素数据检查 FPGA 的 bank 电压和 IO 标准是否匹配1.8V 的 LVDS 用 LVDS_25 标准采样数据肯定是错的用示波器或者差分探头看摄像头输出时钟有没有起来时钟都没起后面全都是白搭确认 IDELAY 和 ISERDES 的延迟配置是否处于有效范围内可以尝试用扫描方式改变延迟值观察锁定状态变化如果数据偶尔错位大概率是位对齐逻辑的问题。给摄像头发送复位指令让它在每条有效的行同步信号后面输出一段固定的前导码FPGA 侧检测到这个前导码就更新对齐位置直到连续多次对齐成功才判定链路稳定。5.2 FIFO 溢出与图像撕裂问题显示端偶尔出现图像撕裂多半是 FIFO 的读写速率不匹配造成的。读侧时钟比写侧稍快FIFO 会被读空写侧比读侧快FIFO 会溢出。解决思路有几个开启 FIFO 的空满标志中断在 almost_full 时暂停写入或插入消隐等待周期合理设计缓存阈值避免 FIFO 在缓冲区和满区间来回跳使用 show-ahead 模式配合读数据计数确保读到完整帧之后再送到显示端我曾经在一个项目里被这个问题折磨了整整两天最后发现是显示控制器的帧缓冲地址没有按行对齐导致每次刷新多读了一行像素。FIFO 本身没问题问题出在使用方式上。5.3 常见问题速查表现象可能原因排查手段摄像头无输出数据模组未初始化或 LVDS 端接不对I2C 寄存器回读、检查 DIFF_TERM图像水平错位位对齐失效扫描 IDELAY 值找眼图中心图像有雪花噪声电源纹波过大示波器测量电源轨增加去耦电容画面卡顿、撕裂FIFO 溢出检查读写速率差调整 FIFO 阈值颜色偏色严重LUT 表数据加载失败换用固定测试图案验证时序收敛失败关键路径过长在 Vivado 中查看时序报告插流水寄存器5.4 一个容易被忽视的工程习惯版本化归档归档包里散落着多个名字类似的版本文件比如cam_top_v1.v、cam_top_final.v、cam_top_final_2.v这种命名方式在接手时非常痛苦。建议在项目一开始就引入版本管理工具至少用 Git 做本地版本管理。压缩包归档只做发布快照日常开发版本用 Git 分支管理这样每个人拿到手的是清晰的提交历史而不是一堆难以归类的 .v 文件。这个项目里 FIFO 和 LUT 模块是我觉得最值得保留复用的部分。FIFO 可以作为其他跨时钟域场景的通用缓冲LUT 滤镜模块则只要换一张数据表就能适配完全不同的视觉效果。支持多路摄像头同步采集的时候这套架构也能直接扩展每路摄像头挂独立 FIFO后端用仲裁器选择输入源处理完再汇入统一显示链路。基本不需要改动核心逻辑工作量主要集中在外围接口适配和资源评估上。踩过几次 LVDS 时序的坑之后我现在的习惯是拿到任何摄像头方案第一件事就是查数据手册里的眼图和建立保持时间参数再上板写代码这样调起来就有底得多。本文还有配套的精品资源点击获取
返回列表