做机器视觉的朋友应该都碰到过这个尴尬:相机选的是CameraLink接口,画质稳定、传输可靠,结果线缆长度卡在十米以内,产线稍微绕一点路就掉链子。客户不会因为这件事改布局,相机和工控机的距离就摆在那,于是CameraLink转SFP光口这种需求越来越频繁地出现在FPGA项目里。我在几个AOI检测和远距离图像采集项目里反复折腾过这套架构,最终固定下来的方案就是标题里写的:GT Transceivers Wizard + Aurora 8B10B。这套组合的优点很直接:不依赖厂商私有协议、IP核免费、逻辑资源占用可控,并且只要FPGA选型正确,线速率余量可以给到非常大。下面把我实际的工程做法、配置参数和踩坑记录完整展开,给准备做同样需求的朋友一条能直接走的路。
1. CameraLink转SFP光口:机器视觉现场绕不开的传输改造
1.1 为什么要把CameraLink搬到光纤上
CameraLink本身是好东西,尤其是对工业相机来说,它信号抖动小、时序稳定、相机支持度高。但限制也很死:Base模式在最大像素时钟85MHz下,推荐线缆长度只有10米左右;即使降低时钟跑,也很难超过15米。很多3D相机、线阵相机甚至需要Medium或Full模式,线缆更粗、更硬、更贵。
实际项目里,相机往往装在设备内部或运动机构上,而图像处理主机放在几十米外的电气柜里。这中间隔着电缆拖链、屏蔽管、过墙接口,普通CameraLink线缆根本不可能布过去。换GigE相机要改采集成像链路,成本高周期长;用CameraLink光纤延长器又是专用设备,价格几万块且灵活性差。所以自己用FPGA做CameraLink转光纤、对端再还原回CameraLink或直接进采集卡,就成了性价比最高的方案。
用FPGA做的好处也明显:像素格式和行场时序都握在自己手里,中间想做图像预处理、ROI裁剪、多路合并都方便。比起买成品光电转换器,自己造的板子还能把图像数据顺带打包成更利于后端处理的格式。
1.2 CameraLink协议里真正要搬走的是哪几位数据
CameraLink的数据结构说复杂不复杂。Base模式下,一个Channel Link芯片组包含4对LVDS数据线加1对LVDS时钟线,在像素时钟的上下沿把28位并行数据串行传出。这28位通常就是:24位图像数据(通常按X/Y/Z三个通道分配,对应RGB888)加上4位控制信号:FVAL(帧有效)、LVAL(行有效)、DVAL(数据有效)、SPARE(保留)。
做FPGA端接收时,如果用解串芯片(比如常见的DS90CR288A),芯片会直接输出并行的28bit数据和像素时钟,FPGA拿到的就是这个信号。要是省掉解串芯片直接用FPGA的LVDS/ISERDES接收,也能还原出同样的28bit和像素时钟,只是需要额外处理bit slip,不建议新手一上来就这样搞。
带宽估算很简单:假设相机是1280x1024@60Hz,RGB888,图像带宽就是1280乘1024乘60乘24bit,约1.89Gbps。Base模式在85MHz像素时钟下的承载上限约2.38Gbps(含控制位),跑这种分辨率还勉强够。但要是相机的像素时钟超过85MHz,或者要跑Full模式的三组Channel Link,那就得用更宽的传输通道——这也是后面选10G光口而不是千兆光口的原因。
2. Aurora 8B10B和GT Transceivers Wizard:这对组合为什么能扛住视频流
2.1 Aurora 8B10B协议的工作逻辑
Aurora是Xilinx自家定义的高速串行链路协议,8B10B编码版本主打轻量化和低延迟。8B10B编码大家应该不陌生,就是把每8bit数据扩展成10bit码字,保证直流平衡和足够多的跳变沿,便于接收端恢复时钟。
Aurora协议本身做得比较简洁:链路初始化阶段发送端和接收端通过握手序列对齐,建立lane_up和channel_up状态;建立完成之后,用户数据就可以通过AXI-Stream接口灌进去,IP核负责加控制字符、多lane绑定时分发数据、接收端再去偏斜。协议开销主要就是10/8编码开销加上少量控制字符,没有以太网那种复杂的MAC、IP、ARP逻辑,所以非常适合做实时视频流。
在10.3125Gbps线速率下,8B10B编码的实际有效数据带宽是10.3125乘以0.8,约8.25Gbps。我们按1.89Gbps的图像带宽算一下,余量超过4倍。这意味着即使相机分辨率再翻倍,或者把两路Base相机合并进一路光口,带宽也绰绰有余。
2.2 为什么选GT Transceivers Wizard而不是自己写SerDes
刚开始接触高速收发器的人很容易有个误区,觉得GTX/GTP就是FPGA里的一组差分收发引脚,配置一下就行。实际上高速串行收发器牵涉到PLL频率规划、参考时钟分配、PMA/PCS层配置、8B10B开关、弹性缓冲、时钟修正、通道绑定等一系列细节。自己写RTL去控制这些底层寄存器和状态机,工作量非常大,而且时序收敛很难。
GT Transceivers Wizard就是Xilinx提供的图形化配置工具,把线速率、参考时钟、用户数据宽度、极性翻转、环回模式这些参数直接填表生成IP核。生成之后,你拿到的是一组被封装好的GTX/GTP收发通道,用户侧接口也比较规整。Aurora 8B10B IP核再跑在GT通道之上,负责链路层的初始化、数据分帧和对齐。这两层配合,等于把从物理层到链路层的大部分工作用IP核吃掉了,我们要做的只剩应用逻辑。
当然,用IP核不等于什么都不用管。时钟树的连接、复位的顺序、IP核生成之后与用户逻辑的握手,这些还都得自己搞清楚,否则光link up就能卡你一星期。后面两章会重点讲。
3. Vivado里从GT Wizard到Aurora IP:配置细节与参数表
3.1 GT Transceivers Wizard配置速查表
直接给我验证过能跑通的配置值,大家先照着配,再理解每一项为什么这样填。
| 配置项 | 我的取值 | 说明 |
|---|---|---|
| 目标FPGA | Kintex-7 XC7K325T-2 | 要跑10G级别线速率,A7的GTP上不去 |
| Line Rate | 10.3125 Gbps | 匹配主流10G SFP+光模块 |
| Reference Clock | 156.25 MHz | 标准参考时钟,必须走GTREFCLK专用引脚 |
| TX/RX | 均使能 | Aurora握手需要双向通道 |
| 用户数据宽度 | 4 Bytes | 对应32bit AXIS接口 |
| PLL选择 | CPLL | 单通道场景下配置简单 |
| 8B10B编码 | 使能 | Aurora 8B10B协议要求 |
特别说一下参考时钟。156.25MHz在10.3125Gbps线速率下是Xilinx官方最常见的搭配,但前提是你板上必须有干净的差分参考时钟源接到GTREFCLK引脚。很多的坑都出在把参考时钟接到了普通IO上,结果CPLL锁定不稳定,信道时好时坏。
FPGA选型也要注意。Artix-7上的是GTP,最高线速率大约6.6Gbps,跑10.3125G是不可能的。如果预算确实紧,也可以把线速率降到3.125Gbps,然后选对应的光模块,Aurora 8B10B协议不用变,逻辑代码基本不用动。只是带宽余量会缩小。
3.2 Aurora 8B10B IP核的配置要点
GT Wizard配置完,在工程里添加Aurora 8B10B IP核,配置界面里有几个选项要一致:
| Aurora配置项 | 我的取值 | 说明 |
|---|---|---|
| Line Rate | 10.3125 Gbps | 必须和GTWizard一致 |
| GT Refclk | 156.25 MHz | 必须和GTWizard一致 |
| Dataflow Mode | Duplex | 全双工,握手需要 |
| Interface | Streaming | 直接AXI-Stream,简单 |
| Flow Control | 不勾选 | 视频流不需要协议级流控 |
| Lanes | 1 | 单通道先调通,不够再加 |
这里重点讲一下为什么选Streaming模式。Aurora 8B10B的Frame模式会提供SOP/EOP等帧边界信号,看似方便,但一旦接收端出现误码导致帧边界错乱,状态恢复比较麻烦。Streaming模式就是一个持续的数据流加valid信号,帧边界完全由我们自己在用户数据里定义,反倒灵活。视频数据里本身就带FVAL/LVAL,丢几行也能从下一行边界恢复,完全够用。
还有一点容易忽略:Aurora IP例化后的S_AXI_ACLK时钟必须接GT的用户时钟,也就是GT Wizard生成的TXOUTCLK分频出来的时钟。32bit接口、10.3125Gbps线速率下,这个时钟约是257.8125MHz。有人图省事把S_AXI_ACLK接了FPGA板上的100MHz系统时钟,结果Aurora IP内部时序全乱,channel_up永远拉不起来。
3.3 时钟和复位的连接顺序
这套架构的复位顺序很关键,我建议按下面的顺序操作:
- 先给GT Wizard的IP核发reset,等pll_lock和txresetdone/rxresetdone信号拉高。
- 等待大约几百微秒,确认GT通道稳定后再释放Aurora IP的复位。
- Aurora IP跑完初始化和对端握手后,lane_up信号拉高,随后channel_up拉高。
- channel_up拉高之后,用户逻辑再开始向Aurora发送数据。
如果直接把两个IP的复位绑在一起释放,Aurora可能在GT还没稳定的情况下就开始初始化,经常会出现第一帧图像花屏、后面才恢复的现象。我一开始图省事用同一个复位信号控制所有模块,后来发现错帧率明显偏高,改成延迟链控制之后才干净。
4. 顶层RTL怎么搭:视频通过光口的完整数据流
4.1 CameraLink接收端:从解串芯片到异步FIFO
硬件上如果用了DS90CR288A这种解串芯片,FPGA收到的就是28bit并行数据加一路像素时钟。先别急着直接接进Aurora,像素时钟是跟着相机走的,和GT用户时钟完全是两个时钟域,中间必须做异步FIFO。
FIFO的深度不用太大。Base模式相机即便全速输出,写入突发速率也就是像素时钟乘以4字节,约2.24Gbps;Aurora侧读出带宽8.25Gbps,远大于写入。所以FIFO只用来吸收两个时钟域的相位抖动和短时流量不均,深度设512或1024就够。
但要注意,FIFO写侧的数据必须在DVAL有效时才写入,否则会把行消隐和帧消隐期的无效数据也打包进去。从数据量来说,消隐期数据丢了不可惜,只需要在FVAL和LVAL有效范围内取数据。
4.2 数据打包:用户帧格式设计
Aurora只负责把数据送到对端,不会关心你传的是什么。为了让接收端能正确恢复行场时序并检测丢帧,需要在图像数据前加自定义包头。我用的帧格式是每行一个包:
| 字段 | 字节数 | 内容 |
|---|---|---|
| 帧同步头 | 4 | 固定值0xAA55AA55 |
| 帧号 | 2 | 发送端每帧递增 |
| 行号 | 2 | 当前行号 |
| 有效像素数 | 2 | 这一行有效像素个数 |
| 保留字段 | 2 | 填充0 |
| 像素数据 | N字节 | 一行图像数据,32bit对齐 |
这个包头看起来占了不少带宽,但一行1280像素,数据量5120字节,包头只有12字节,开销完全可以忽略。
RTL里给一个示意:
// Aurora Streaming接口发送 assign s_axi_tdata = {camera_pixel[23:0], fval, lval, dval, spare, 4'b0}; assign s_axi_tvalid = fifo_valid; // 有数据就给valid assign s_axi_tlast = line_end; // 行尾标志,Streaming模式下可不用把28bit CameraLink数据塞进32bit tdata,低4位预留,接收端拆包时直接从tdata[27:24]取控制位,tdata[23:0]取像素,处理起来非常直观。
还有一个容易踩的点:Aurora的tdata是32bit,如果发送端不主动处理tkeep,IP会默认4字节全部有效,也就是低4位pad字节也会占用光口带宽。视频带宽本来就富余,这样没问题;但如果你后面想上多路合流,最好在打包时把低4位有效利用起来,或者改用tkeep配合压缩。
4.3 4套工程源码的定位和复用方法
这一套方案我拆成了4个工程,每个工程解决一个具体场景,联调时能快速定位问题:
| 工程名 | 方向 | 核心内容 | 解决什么问题 |
|---|---|---|---|
| cameralink_tx_sfp | 发射 | CameraLink解串+异步FIFO+Aurora TX | 相机信号远传到主机端 |
| sfp_rx_cameralink | 接收 | Aurora RX+时序恢复+CameraLink输出 | 光纤信号还原成采集卡可识别的时序 |
| aurora_loopback_test | 自测 | GT回环+ILA抓状态信号 | 调通物理层和Aurora链路 |
| multi_cam_mux | 扩展 | 两路Base合路到一路10G光口 | 多相机复用光纤资源 |
第一个工程是主项目,基本就是第四章讲的结构。第二个工程是配对的接收端,Aurora RX恢复出的32bit数据流里解析包头,还原FVAL/LVAL,再用这控制信号驱动CameraLink发送端的28bit时序输出。
第三个工程是自测用的。单独把Aurora TX和RX用光纤回环接起来,或者用GT的near-end PMA loopback模式,配合Vivado的ILA抓取channel_up、lane_up、复位状态和收发数据。项目跑不通的第一步就是先跑这个工程,把链路问题限定在物理层还是逻辑层。
第四个工程是给多相机场景用的。两路Base信号分别在各自时钟域解析,进入同一时钟域后按帧号仲裁,把多路数据按优先级塞进同一个Aurora通道。由于10G带宽远大于两路Base总带宽,合流逻辑可以做得比较简单,核心是帧同步和写FIFO时的地址仲裁。
5. 实际调试中踩过的坑:每一根白发都是工时换来的
5.1 光链路物理层先验证:IBERT和光模块的电平问题
这部分是血泪教训。第一次上板,我直接烧了完整工程,然后盯着channel_up发呆,一动不动。后来养成了习惯:任何高速链路项目,第一次上板先跑IBERT。
IBERT是Vivado里的集成误码率测试工具,它会占用GT资源,生成一个测试工程,直接把GT的TX和RX配置成可调参数,测误码率。用IBERT之前,先确认光模块的TX_DISABLE引脚是拉低的,这个信号高电平会关闭光模块发送。好几个朋友踩过这个坑,误以为光模块坏了,其实是FPGA控制脚把光模块关了。
IBERT扫一遍后,再用短光纤把板子的两个SFP+口环起来,或者用单个SFP环回模块,看Bit Error Rate是不是0或者极低。如果BER很高,优先检查GT参考时钟的引脚连接、光模块供电和PCB差分走线。
5.2 channel_up起来了但数据错:极性和字节序问题
channel_up能拉起来,说明Aurora链路已经握手成功,但图像还是花的、数据乱序,常见根因有三个:
第一是差分极性接反。GTX的P/N如果和光模块或连接器之间交叉了,channel_up可能起不来,但有时候也能起——只是数据全反。这时候打开GT Wizard里的TX/RX Polarity Inversion选项,或者直接改原理图,都能解决。
第二是字节序不对。Aurora的32bit数据发送时,内部有MSB/LSB顺序,接收端拆包时如果按错方向读取,像素通道就会错位。调试时我习惯发送固定pattern,比如0xDEADBEEF,在接收端抓数据看是原样还是字节交换、bit交换,一目了然。
第三是8B10B错误码被忽略。Aurora IP有属性接口可以看到hard_error和soft_error计数,接收端的逻辑里一定要把这个计数接出来。有时候图像偶尔花一帧,现场不好复现,但错误计数一直在涨,这就指明了问题方向。
5.3 CameraLink侧的时序处理和信号完整性问题
CameraLink接口的LVDS信号做解串时,注意像素时钟的质量。有些相机的像素时钟在低分辨率模式下会间歇性停顿,异步FIFO的写时钟不能直接拿这个时钟去驱动其他逻辑,否则会出现时序违例。我习惯在解串后加一个简单的时钟有效检测,像素时钟停顿超过一定时间就触发回中状态。
还有CameraLink满带宽传输时,如果板子上解串芯片和FPGA之间的走线太长,28bit并行信号的眼图会变差。解决方法是尽量把解串芯片靠近FPGA,或者改用低速时钟模式。实在不行,就在FPGA内部对像素时钟做一次PLL重新同步,但要注意PLL的锁定时间,别让相机输出已经开始了PLL还没锁上。
5.4 排查链路:信道建立失败的完整定位过程
外接一个自测工程,我一般按下面的顺序排查:
- 看光模块的LOS引脚。LOS为高说明接收端没有光进来,先查光纤和发光端。
- 用IBERT直接发pattern测误码。如果误码高,问题在GT配置、参考时钟或硬件电路,先不要碰Aurora。
- IBERT正常但Aurora起不来,检查GT Wizard和Aurora IP的线速率、参考时钟是否完全一致,检查S_AXI_ACLK是不是接的GT用户时钟。
- 检查复位顺序是否按GT先复位、Aurora后复位的顺序执行。
- 以上都正常仍然起不来,用Vivado的Hardware Manager在线抓Aurora IP的debug端口,确认卡在哪一步:REFCLK没锁定,还是GT没完成复位,还是没有对端ACK。
这套流程走下来,绝大多数链路问题都能在一两天内锁定。我见过不少同行一上来就改RTL,其实大部分信道建立失败都是时钟和复位配置问题,和数据逻辑关系不大。
最后分享两个我现在还在用的小习惯
这套CameraLink转SFP光口的方案我前后经历过三个项目迭代。头一个项目花了两周才调通,主要时间都消耗在信道建立和各种时钟问题上;后来总结出这套标准流程之后,第二个项目不到三天就把收发链路跑通了。现在我自己做类似需求时,一定先把知识点前置:GT Wizard配置是否正确,Aurora IP的S_AXI_ACLK是否连对,是决定项目周期最关键的两个点。
调试时还有一个习惯值得保留:硬件管理器里同时挂上Aurora IP的status端口和CameraLink侧的帧计数/行计数信号,上电一次就能同时看到链路状态和数据状态,不用来回切换窗口。对做这类高速视频传输项目来说,这个习惯能省下大量调试时间。