前阵子调一块板卡,两块FPGA之间要传一路视频流,再带一路寄存器读写控制,加起来大概3.2Gbps。一开始想走LVDS并行总线,算了下差分对要三十多对,PCB布线直接头大;换PCIe的话又觉得杀鸡用牛刀,光是枚举和驱动就够折腾。后来在Vivado的IP Catalog里翻到Chip2Chip这个核,才意识到这就是干这活的合适人选。从配置到仿真再到上板,前后花了一周,链路终于稳定跑起来。这篇文章就把Chip2Chip IP核基于Aurora PHY的通信配置过程、仿真验证方法和我踩过的坑完整记下来,给准备用高速串行链路做板间互联的朋友一个参考。
1. 为什么需要Chip2Chip:高速板间互联方案怎么选
1.1 LVDS、PCIe、Aurora和Chip2Chip的定位区别
板间高速互联的常规选择无非这么几种:并行LVDS、PCIe、Aurora协议,以及这里要重点说的Chip2Chip IP核。它们之间不是简单的“谁替代谁”,而是适用场景差别很大。
| 方案 | 典型带宽 | 资源开销 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| LVDS并行总线 | 几百Mbps到2Gbps | 大量IO引脚 | 低 | 短距离、低速率、带宽要求不高 |
| PCIe | 单通道2.5Gbps起 | 硬核或PCIe IP + DMA逻辑 | 高 | 主机与FPGA、系统级复杂互联 |
| Aurora 8B/10B | 从1Gbps到10Gbps以上 | GT + Aurora IP + 用户逻辑 | 中 | 任意两点间的高速流式传输 |
| Chip2Chip | 和Aurora一致 | GT + Chip2Chip IP | 中低 | 两块FPGA之间点对点数据传输和寄存器访问 |
PCIe最大的问题不是性能,而是“重”。一旦用了PCIe,就得考虑链路训练、BAR空间映射、DMA引擎、驱动适配,如果只是两块FPGA之间互相传数据,这些全是额外负担。而直接用Aurora 8B/10B IP核,功能很强,但需要自己处理初始化时序、流控、错误恢复等一大堆协议细节,对于只要“把数据从A点搬到B点”的场景来说,学习成本略高。
Chip2Chip本质上是在Aurora 8B/10B物理层之上包了一层简化协议,把很多底层细节藏了起来,用户看到的基本上是AXI-Stream或AXI接口。它的优势在于:不需要写复杂的Aurora协议状态机,配置完IP核后直接读写数据即可。同时它还支持远程寄存器访问,这对需要跨板卡读写控制寄存器的场景特别实用。
1.2 什么场景用Chip2Chip最划算
根据我的实际使用体会,以下场景优先考虑Chip2Chip:
- 两块FPGA之间需要稳定跑2Gbps以上的数据流,比如视频流、高速ADC采样数据、雷达中频数据。
- 需要跨板卡访问另一端的寄存器或存储空间,希望像访问本地地址一样操作远端。
- 不想自己维护Aurora协议的初始化、复位、错误处理逻辑。
- 希望链路有相对确定的延迟,而非类似以太网那样的不确定性转发。
- 工程周期紧,需要快速把物理层跑通,把精力集中在业务逻辑上。
反过来,如果需要连接FPGA和主机CPU,或者需要组网能力,那还是老老实实用PCIe或Ethernet,Chip2Chip只适合点对点场景,这一点必须在方案定型前想清楚。
2. 动手配置前先算好参数:时钟、位宽和GT资源
2.1 线速率与GT参考时钟频率的匹配关系
Chip2Chip的物理层是GT收发器,GT内部靠QPLL或CPLL将参考时钟倍频到串行线速率。所以线速率和参考时钟频率之间必须满足一个倍频关系,不是随便组合都能用的。
参考时钟频率、线速率和GT内部倍频系数的关系可以简单理解为:
线速率 = 参考时钟频率 × GT倍频系数
举个例子,如果线速率选2.5Gbps,参考时钟用125MHz,那么GT倍频系数就是20,这在GT的合法配置范围内。如果线速率是3.125Gbps,参考时钟同样给125MHz,倍频系数就是25,也常见。再比如5Gbps线速率,搭配156.25MHz参考时钟,倍频系数是32。
配置IP核时,Vivado会自动校验参考时钟频率和线速率是否匹配。如果匹配不上,界面会直接报错或者在生成时弹警告。这里最容易踩的坑是自己想当然地给一个参考时钟,觉得“FPGA内部PLL总能搞定”,实际上GT的PLL是有分频系数范围限制的,不同FPGA器件、不同GT类型的限制还不一样。
我的建议是:先定线速率,再查器件手册或参考Vivado下拉列表里能选的参考时钟频率,不要反推。Vivado的Chip2Chip配置界面里,参考时钟那栏会列出该线速率下支持的合法频率,选一个离自己板卡时钟树最近的值,后面硬件设计会省很多事。
2.2 用户时钟频率与数据位宽怎么算
Chip2Chip的用户接口支持2字节和4字节两种数据位宽,用户时钟频率直接取决于线速率和位宽,计算公式如下:
用户时钟频率 = 线速率 / (10 × 数据位宽字节数)
这里除以10是因为Aurora 8B/10B编码把每字节扩成了10bit传输。举个例子:
- 线速率2.5Gbps、数据位宽2字节,用户时钟 = 2.5G / (10 × 2) = 125MHz。
- 线速率3.125Gbps、数据位宽2字节,用户时钟 = 3.125G / 20 = 156.25MHz。
- 线速率6.6Gbps、数据位宽4字节,用户时钟 = 6.6G / 40 = 165MHz。
这个结果一定要在IP配置之前算清楚,因为它决定了工程里用户逻辑的时钟域。实际项目中,很多第一次用Chip2Chip的人会在这一步犯迷糊,尤其是选了4字节位宽后,以为用户时钟还是和线速率差不多快,结果后面跨时钟域处理全乱了。
另外要注意,虽然数据位宽增大后用户时钟频率会降低,但GT通道占用数也会变化。2字节位宽对应1个GT通道,4字节位宽对应2个GT通道,这一点下面细说。
2.3 Chip2Chip占用几个GT通道,BANK怎么放
Chip2Chip不像Aurora那样配置多条lane扩展,它的通道数基本由数据位宽决定。2字节位宽单通道即可跑通,4字节位宽需要2条GT通道,两路并行分摊数据,用户侧看到的就是更高带宽的并行接口。
通道数确定后,还要考虑GT在FPGA物理位置上的约束。GT收发器是按Quad组织的,每个Quad包含4个GT通道。单通道Chip2Chip随便放,双通道就尽量放在同一个Quad里,这样QPLL资源可以共用,时序也更容易收敛。
如果板卡上有多个GT参考时钟输入,务必让Chip2Chip使用的GT通道和参考时钟在同一个Quad或相邻Quad。有些FPGA里GT参考时钟的布线是有限制的,跨太远会导致参考时钟无法布线,或者在实现阶段出现严重的时序问题。这个坑在原理图设计阶段就要避掉,等PCB做回来再发现就麻烦了。
3. Chip2Chip IP核核心配置实操
3.1 生成主从两个IP实例,角色别选错
Chip2Chip是成对使用的IP,通信双方一端为主(Primary),另一端为从(Secondary)。配置时需要在Vivado IP Catalog里搜索chip2chip,然后分别生成两个IP实例,一个选Primary,一个选Secondary。
这里有个很容易忽略的细节:两个实例虽然在配置界面上选项很多,但协议类型、线速率、数据位宽必须完全一致,否则物理层链路协商不起来。角色不同只影响初始化时的握手顺序和部分控制信号极性,不影响数据面格式。
接口类型按需选择。典型用法是数据通道用AXI4-Stream,控制寄存器用AXI4-Lite。如果只想传流式数据,AXI4-Stream就够了,配置也最简单;如果还需要跨板卡读写寄存器,就再加上AXI4-Lite接口。两个接口可以同时存在于一个Chip2Chip IP核里,只是会多占一些内部资源。
生成完IP后,建议先在IP Sources里打开Example Design,把它作为学习参考。Example Design里包含完整的初始化模块、时钟模块和收发数据验证模块,比对着用户手册硬啃高效得多。我一般习惯直接基于Example Design改,而不是从空工程开始。
3.2 协议层选项:Aurora 8B/10B PHY、流控和CRC
Chip2Chip配置界面里,物理层协议可以选择Aurora 8B/10B PHY或简化物理层(不使用Aurora协议)。如果只要两个Chip2Chip互相通信,用哪种都能跑通。但考虑到可观测性和通用性,我推荐选Aurora 8B/10B PHY,原因有三个:
- 链路初始化、字节对齐、通道绑定这些底层操作由Aurora PHY的状态机管理,稳定可靠。
- 调试时可以直接参考Aurora的lane_up和channel_up信号判断物理层状态。
- 如果以后需要改成和其他Aurora设备互通,至少物理层是一致的。
流控建议根据业务需求决定。如果两端的数据发送速率是固定的,且接收端FIFO深度足够,可以不开启流控,简化逻辑。如果存在突发流量,建议开启Native Flow Control,这样接收端FIFO快满时能通知对端暂停发送,避免丢数据。
CRC校验属于可靠性增强选项。开启后Chip2Chip会在每个数据包尾部附加校验值,接收端检测错误后可以上报。对于需要高可靠传输的场景,我建议开启,代价是有效带宽略降,但对于大多数Gbps级别的应用来说可以接受。
3.3 复位与初始化逻辑千万别填错
Chip2Chip的初始化过程并不是上电后自己就乖乖完成的,它需要用户侧提供合适的复位时序和初始化时钟。最核心的几点:
channel_init_clk是初始化模块的工作时钟,频率不能太高,通常建议50MHz左右。这个时钟要保证在GT复位期间持续稳定,不能等channel_up之后再给。gt_reset信号必须有效复位一段时间后释放。释放太早可能导致GT的PLL还没完成锁定,后面初始化卡死;释放太晚也不会有什么问题,就是浪费时间。gt_txresetdone和gt_rxresetdone两个信号是GT单元给出的复位完成标志,Chip2Chip内部的Aurora初始化状态机依赖这两个信号推进状态。
在Example Design里,这些信号都被封装好了,基本不用自己动。但如果自己搭工程,最容易犯的错就是把channel_init_clk和user_clk接到同一个时钟上。有些用户时钟频率很高(比如157MHz、165MHz),拿来当初始化时钟可能会导致时序违例,使初始化状态机乱跳。稳妥做法是单独分出一个50MHz时钟给初始化模块,数据通路再跑高速用户时钟。
还有一点关于power_down信号,通常保持低电平即可。如果把GT的power_down意外拉高,GT会进入低功耗模式,链路怎么都练不起来,初始化状态机会一直卡在等待复位完成那一步。仿真时不注意还容易忽略,因为仿真模型里power_down拉高并不一定立刻报错。
4. 仿真搭建与Aurora PHY初始化避坑
4.1 用Example Design快速搭一个双端仿真工程
Chip2Chip的仿真一定要由两个IP实例配合完成:一端主、一端从,GT发送端的差分信号txp/txn接到对端接收端的rxp/rxn。可以在顶层模块里先实例化两个IP,然后把信号连起来。
最快的起步方式是在生成IP后,使用Vivado Tcl命令行打开Example Design:
open_example_design -ip [get_ips chip2chip_0]Example Design里自带时钟模块、复位模块、数据生成和比对模块,直接跑行为仿真就能看到channel_up拉高的完整时序。我第一次做的时候,就是先把主从两端各自Example Design打开,然后把两边的顶层里面与GT相连的差分收发信号互相交叉连接,再跑仿真。
仿真时间要设够。GT的PLL锁定和复位完成在仿真模型里会模拟出真实硬件的时间尺度,通常需要几十微秒到几百微秒。如果仿真时间只给几微秒,可能还没看到channel_up就结束了。建议至少跑到500微秒,等channel_up稳定后再做数据收发验证。
4.2 初始化时序里到底发生了什么
Chip2Chip和Aurora PHY的初始化大致分三个阶段:
- PMA初始化:GT的模拟部分上电复位,QPLL/CPLL开始锁定,
gt_txresetdone和gt_rxresetdone依次拉高。 - lane初始化:发送端开始发送时钟对齐序列,接收端通过8B/10B的comma字符完成字节对齐,
lane_up拉高。 - channel初始化:两端交换初始化状态机信息,完成通道握手,最终
channel_up拉高,用户数据通路使能。
仿真的时候可以拉出lane_up和channel_up信号,观察它们抬起的顺序。正常情况下,lane_up会先于channel_up。如果只看到lane_up拉高而channel_up一直不拉,问题多半出在主从两端参数不一致,或者状态机配置有差异。
值得一提的是,用户接口的复位信号通常是低有效复位,也就是说axi_resetn在channel_up拉高后才能释放。如果外部逻辑不管三七二十一先把用户逻辑复位释放了,而此时链路还没建立,数据就会在发送端堆积,恢复后可能出现先入先出的对齐问题。工程上建议直接用channel_up信号作为用户逻辑复位释放的条件,或者至少用它做一次同步。
4.3 数据回环验证:从发计数器到收计数器比对
链路初始化通过后,第一件事不是跑业务数据,而是做一个简单的回环验证。我常用的方法是在发送端用一个计数器循环发送0x0000到0xFFFF的数据,接收端把收到的数据和本地产生的预期值进行比对,一旦不一致就拉高错误标志。
在Example Design里,自带的验证模块已经做了类似事情,可以省不少事。如果想自己写一个精简版,核心代码逻辑大致是这样:
// 发送端:16bit计数器循环递增 always @(posedge user_clk) begin if (!tx_ready) tx_data <= tx_data + 1'b1; end // 接收端:比对接收数据与本地递增计数 always @(posedge user_clk) begin if (rx_valid) begin if (rx_data != expected_data) error_flag <= 1'b1; else expected_data <= expected_data + 1'b1; end end仿真时不要只比对一两个包,建议连续跑几万个数据。如果链路存在偶发误码,或者初始化过程复位不干净,短时间内不一定暴露问题,但长时间数据比对能大概率发现。
数据验证通过后,再引入实际业务逻辑。迭代时先保通道、再加业务,这个顺序能极大减少排障时间。
4.4 仿真中常见错误和解决方案速查
我整理了一张自己在仿真中经常遇到的错误对照表,基本覆盖了Chip2Chip初始化阶段的高频问题:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| channel_up一直为低 | 主从两端线速率或位宽不一致 | 检查两端IP配置是否完全一致 |
| lane_up不拉高 | GT参考时钟没起振或频率不对 | 查看仿真波形,确认参考时钟频率正确 |
| gt_txresetdone不拉高 | gt_reset释放太早或channel_init_clk没跑 | 拉长复位时间,确保init_clk先运行 |
| 数据偶发错位 | 用户复位释放时机错误 | 用channel_up同步释放axi_resetn |
| 仿真波形出现大量X态 | 某些GT配置参数未初始化 | 确认是否直接用了Example Design的仿真设置 |
| 上板后链路正常但仿真不过 | 仿真模型与实际硬件复位时间不同 | 仿真中适当延长等待时间 |
还有一个容易被忽视的问题:仿真模型默认情况下不会模拟出真实的QPLL锁定失败场景。也就是说,即使参考时钟频率不对,行为仿真也可能看起来一切正常,但上板后链路怎么都练不起来。所以仿真通过不等于硬件一定没问题,这一点要特别留意。
5. 上板调试时容易忽略的硬件细节
5.1 参考时钟、差分走线和接地
Chip2Chip跑的是高速串行信号,物理层对参考时钟和差分走线非常敏感。参考时钟请使用时钟源专用引脚输入,不要拿普通IO模拟差分时钟,抖动会直接影响GT的误码率。如果条件允许,用独立晶振或时钟芯片给GT参考时钟供给干净时钟源。
差分走线要按100欧姆差分阻抗控制,尽量短,避免过孔。如果走线跨层,记得在换层附近加回流地孔。两块FPGA板卡之间用连接器互连时,连接器的信号完整性问题也要考虑,最好选经过验证的高速连接器。
还有一个容易被忽略的点:两块板卡的GND必须可靠连通。如果地电位不一致,GT的共模电压会漂移,轻则误码率高,重则链路完全无法建立。调试时先用万用表确认两端地线连接正常,再上业务。
5.2 上板后如何快速定位链路状态
上板后如果链路不通,不要急着改代码,先看信号。我一般按这个顺序排查:
- 用逻辑分析仪或ILA观察
gt_txresetdone和gt_rxresetdone是否拉高,确认GT复位完成。 - 观察
lane_up状态,确认字节对齐是否完成。 - 观察
channel_up状态,确认两端握手是否成功。 - 如果都能拉高,再跑数据回环,抓取接收端错误标志。
FPGA内部这些信号都很容易引出到ILA。如果工程里不好加探针,可以直接在顶层模块里把几个关键信号引出来连到空闲LED上,用示波器或肉眼观察状态。LED虽然土,但真到了现场调试,往往比打开Vivado抓波形还快。
上板调试另一个常见问题是误码率不稳。排除接线问题后,优先尝试降低GT线速率,比如从5G降到2.5G,如果误码消失,说明信号完整性问题比较大,需要检查PCB走线和连接器。这个诊断方法简单有效,能快速把问题范围缩小到信号完整性还是协议配置上。
最后再说两句
Chip2Chip这个IP核,表面上看起来只是一个“串口加强版”,用熟了以后才会发现它内部的Aurora PHY初始化其实藏着不少细节。它最友好的地方在于Xilinx用标准化封装把底层协议打包了,让用户把精力放到数据通路和业务逻辑上。但我个人的体会是,越是这样封装的核,越要在仿真阶段把初始化时序吃透,否则上板出了问题反而更不好排查。
我后来的固定套路是:新项目只要涉及Chip2Chip,第一步一定是拿Example Design跑双端仿真,看到channel_up稳定拉高后,再改成自己的业务逻辑。这一步省掉了我不知道多少的回头调试时间。希望这篇实战经验能帮你少走弯路。