
做了这么多年FPGA高速通信我越来越觉得很多项目卡壳并不是算法多复杂而是两个FPGA之间怎么稳定、高效地把数据搬过去。当年我第一次接触到Xilinx Chip2Chip IP核其实就是被逼的板子上主控FPGA和协处理FPGA之间要跑连续高速数据流如果用并行总线PCB布线、时序收敛、跨板连接全是麻烦如果完全自己写Aurora 8B/10B协议栈状态机和初始化序列能磨掉你两个星期。Chip2Chip说白了就是Xilinx已经帮你把Aurora PHY和上层用户逻辑之间的衔接打包好了用户侧露出干净的流水接口底层自动处理打包解包、通道对齐、错误检测。这篇文章我就把实际的配置过程、仿真流程还有那些文档里不会写清楚的坑一次说透。1. Chip2Chip项目到底在做什么1.1 一个真实的工程场景先还原一下我当时的应用场景。整个系统由两块板卡组成主控板上有Zynq系列FPGA负责采集和协议解析协处理板上用了另一颗Kintex系列FPGA专门跑大吞吐量的FFT和滤波运算。两板之间通过连接器走高速串行差分对。最初规划用的是LVDS并行总线数据位宽做到16位频率提到200MHz结果布线工程师直接拍桌子跨板连接器的信号完整性问题太多200MHz的并行总线在工程上基本是找罪受。后来大家统一意见改用SerDes高速串行链路一对差分线就能顶掉16根并行线而且协议层还能带来CRC校验和错误重传能力。这里就面临一个选择是直接调Aurora 8B/10B IP核还是调Chip2Chip IP核。Aurora IP核直接暴露的是流接口和控制信号需要自己处理用户逻辑的帧格式、流量控制、以及和业务模块的状态握手。Chip2Chip则把Aurora封装得更靠上直接提供用户友好的数据通道接口适合两个FPGA之间做透明数据传输。我当时业务需求是“把采集数据送到协处理FPGA再把处理结果拿回来”天然适合Chip2Chip这种桥接型IP。1.2 为什么选Chip2Chip而不是自己写收发器自己写一套基于GTP/GTX的收发器理论上可行但你要面对的是GT内部繁杂的动态重配置端口、复位状态机、接收端极性处理、字节对齐、时钟修正等一整套底层机制。这些机制在芯片内部环环相扣任何一个信号处理时序不对链路就是起不来而且定位问题非常痛苦。用IP核的价值不在于帮你把逻辑写好了而在于把经过硅前验证、工艺厂确认过的底层行为封装成了标准接口你只需要关心配置参数和外部时序约束。Chip2Chip相比直接用Aurora 8B/10B核额外多做的一层是用户数据的打包和分发。当用户侧数据通过AXI4-Stream接口进入IP核时IP会把数据切分成符合Aurora协议帧结构的载荷加上序列号接收端再按序恢复。这意味着应用层不需要关心通道初始化时发送的那些控制序列、IDLE字符和时钟补偿序列也不需要关心多通道时数据在通道间的分配。省下这部分逻辑开发量往往就是一周甚至更长的工期。当然代价也不能忽视。用Chip2Chip之后你能干预的底层信号变少了如果遇到极特殊的自定义帧格式需求会很别扭。另外必须遵循IP核规定的复位和初始化时序不能用自己临时的复位逻辑去“碰运气”。我从一开始就强调这一点因为后续所有仿真和板级调试都是围绕这个约束展开的。2. 链路架构与通信原理拆解2.1 Chip2Chip IP核的功能边界Chip2Chip IP核在整体链路中扮演的角色可以理解为一个网桥。用户侧接标准的AXI4-Stream接口支持数据位宽可配发送方向从用户逻辑接收数据打包后交给Aurora PHY发送接收方向从Aurora PHY拿到数据解包后以AXI4-Stream时序送到用户逻辑。它内部会维护通道状态机处理链路初始化、数据路径的建立和断开、错误标志上报等。这里要特别注意Chip2Chip IP核虽然内部用Aurora协议但它暴露的复位和状态信号并不完全等同于Aurora IP核。很多初次使用的同事会照着Aurora核的例程去拉reset结果时序对不上。Chip2Chip对外通常提供axi_resetn这类用户侧复位信号和Aurora的gt_reset、sys_reset作用域不同。sys_reset是IP核内部Aurora逻辑的复位axi_resetn是用户接口逻辑的复位不能一把梭全接在一起。在上层Chip2Chip还提供状态寄存器接口通过AXI4-Lite接口可以读取链路状态、通道状态、错误计数等。不过在大部分Mesh结构或桥接应用中我更习惯直接拉出channel_up这类状态信号作为全局复位条件之一。只有当链路真正up起来之后用户逻辑才开始发送业务数据这个思路简单可靠。2.2 Aurora PHY的角色与8B/10B编码Chip2Chip底层承载的PHY协议是Aurora 8B/10B。这个协议最重要的工作有三件字节同步、通道对齐、时钟补偿。在8B/10B编码下每个字节会被映射为10bit码字接收端通过特殊的逗号字符实现字节对齐多通道时还需要通过通道对齐序列保证各通道的边界一致同时因为收发双方参考时钟存在微小频差协议会定期插入时钟补偿序列避免FIFO溢出或欠载。理解了这三件事回头看很多调试问题就清楚了。比如我们在仿真和板级测试中看到通道偶尔up偶尔down大概率就是参考时钟频偏太大或者外部参考时钟没有接到GT专用的Reference Clock引脚上导致时钟抖动超标。Aurora PHY的抗频偏能力不是无限的8B/10B的时钟补偿机制要求频偏在正负几百ppm级别内如果板卡上用了精度不足的晶振就会出现“过一会儿链路断了”这种诡异现象。在Chip2Chip配置过程中你还会看到GT参考时钟频率和线速率的选择。线速率越高单位时间传输的数据越多但对PCB损耗越敏感。在大多数FR4板材的板卡上6.6Gbps的GTX是相对平衡的点如果信号质量一般降到3.125Gbps会更稳定。这个选择直接影响对端GT接收灵敏度和眼图裕量建议在前期和硬件工程师一起定。2.3 时钟、复位与初始化序列Chip2Chip相关时钟包括GT参考时钟、GT收发恢复时钟、用户接口时钟。GT参考时钟从专用引脚进入经过GT内部的PLL产生串行时钟用户接口时钟在IP内部基于恢复时钟和参考时钟产生同时支持从外部输入同一时钟域。考虑到跨板数据链路我通常会尽量让用户接口时钟和业务逻辑时钟保持一致避免二次跨时钟域。复位和初始化序列是整个项目最容易踩坑的地方。Aurora 8B/10B链路建立要经历一个比较长的初始化过程上电后先完成GT PLL锁定释放gt_reset然后Aurora状态机开始发送初始化序列接收端检测到有效字符后进入对齐状态最终拉高channel_up。在此期间用户侧不能发送业务数据否则数据会丢失或触发协议错误。Chip2Chip IP核的Example Design对这个过程做了很好的演示但实际工程中很多人会做一个“自作聪明”的操作看到aresetn变高了就开始灌数据忽略了channel_up还没拉高。我自己的血泪经验是一定要用channel_up以及必要时的gt_pll_lock参与用户逻辑的软件复位释放条件不要只依赖IP核内部的复位信号。3. IP核配置实操从Vivado到工程集成3.1 创建与配置Chip2Chip IP核我用的版本是Vivado 2021.2其他版本界面略有差异但套路一致。打开Vivado工程后在IP Catalog里搜索Chip2Chip双击进入配置界面。首先是Component Name我习惯命名为chip2chip_0方便后续例化。配置页里会让选择PHY接口选Aurora 8B/10B这一步就决定了底层协议类型。接着设置数据位宽和用户接口模式。Chip2Chip IP核通常支持8字节、16字节等数据位宽选项用户接口是AXI4-Stream。实际使用中数据位宽应该和业务逻辑的位宽匹配如果业务侧是32bitIP核配置成4字节用户位宽比较自然。不要在用户侧做各种位宽转换徒增时钟域和握手的复杂度。在配置界面的Lane速率和参考时钟部分需要填写线速率和GT参考时钟频率。以3.125Gbps线速率为例参考时钟可以选125MHz或156.25MHz。我的习惯是选125MHz因为125MHz时钟在多数板子上都是标准晶振频率而且3.125Gbps / 125MHz 25倍数关系非常工整GT内部PLL比较喜欢这种整数关系。若选非整数倍关系虽然有些情况下也能锁定但不确定性变大。3.2 Aurora 8B/10B PHY的关键参数Aurora 8B/10B核配置里有一堆参数不是每个都要动。最需要注意的是Lane Count、Line Rate、Reference Clock Frequency、Dataflow Mode以及Flow Control。Lane Count决定用几对SerDes差分线Chip2Chip通常可以配置1、2、4通道。通道数越多总带宽越大但PCB布线和协议对齐复杂度也上来。对第一版项目我强烈建议先用单通道把链路调通再扩展到多通道。Dataflow Mode建议选Duplex双工除非你明确只需要单向传输。单向模式看似省了逻辑但如果后续要加回传状态、错误信息就得重新配置和验证性价比很低。Flow Control选项如非对端芯片有强制要求保持默认关闭即可。打开流量控制会引入额外的广播帧和站点请求增加IP核内部逻辑而且调试时多了很多状态寄存器容易让人分心。真正的系统级背压可以通过AXI Stream的ready信号处理交给上层协议更灵活。3.3 GT位置、参考时钟与引脚分配配置完IP核接下来就是工程集成时最容易被忽略的物理约束。Chip2Chip内部的Aurora PHY会实例化GT transceiverVivado会根据IP配置自动生成GT的位置约束但前提是你必须在综合后的XDC或工程约束里明确指定GT参考时钟引脚和差分信号引脚。我踩过这样一个坑IP例化之后没有把MGTREFCLK差分引脚约束到板卡上对应的专用参考时钟引脚上结果综合布线后GT参考时钟被工具自动绕到了普通IO仿真也通过了但板级实测时链路完全无法建链。仪表一看参考时钟输入根本是无效的。GT参考时钟一定是MGTREFCLK_X_X_P/N专用引脚差分对的位置要和GT所在bank匹配不能跨bank乱接。对于差分数据引脚我习惯在XDC里明确写上每个GT的引脚和电气标准约束例如set_property PACKAGE_PIN AC11 [get_ports {cpld_rx_p[0]}] set_property PACKAGE_PIN AC10 [get_ports {cpld_rx_n[0]}] set_property PACKAGE_PIN AE11 [get_ports {cpld_tx_p[0]}] set_property PACKAGE_PIN AE10 [get_ports {cpld_tx_n[0]}] set_property IOSTANDARD LVDS [get_ports {cpld_rx_p[0]}] set_property IOSTANDARD LVDS [get_ports {cpld_rx_n[0]}] set_property IOSTANDARD LVDS [get_ports {cpld_tx_p[0]}] set_property IOSTANDARD LVDS [get_ports {cpld_tx_n[0]}] set_property DIFF_TERM TRUE [get_ports {cpld_rx_p[0]}] set_property DIFF_TERM TRUE [get_ports {cpld_tx_p[0]}]注意GT的差分IO标准通常是LVDS或LVCMOS在GTX/GTH收发器上参考时钟通常用LVDS而串行数据口由GT内部处理并不需要像普通IO那样设IOSTANDARD。更准确说SerDes数据引脚由IP内部连接外部顶层引脚连接的是ip核的txp/txn/rxp/rxnIO约束由IP核的XDC管理。所以刚那段XDC示例只适合作为补充说明真实工程里要小心。更常见的是直接用IP核生成例化时自动生成的引脚约束再根据板卡原理图微调。3.4 用户接口和地址映射设计Chip2Chip IP核的用户侧AXI4-Stream接口包含tdata、tvalid、tready、tlast、tkeep等信号。发送方向用户逻辑在tready tvalid都拉高时传输有效数据接收方向IP核输出有效数据时tvalid拉高用户逻辑通过tready进行反压。很多新手上手时会模仿普通FIFO的接口去接Chip2Chip忽略tlast和tkeep。Aurora帧协议里需要知道一个包什么时候结束tlast就是干这个的。如果你的业务数据是定长包那也要在包边界来临时拉高tlast如果是不定长帧必须在数据结束时正确置位tlast否则接收端无法完成帧同步。tkeep则是对最后一拍有效字节的掩码比如用户位宽是16字节最后一拍只有6个字节有效tkeep相应位置就是1。调试时可以在用户接口处挂一个简单的计数器对发送包数、接收包数、周期状态做统计。如果发送端计数持续增加、接收端计数不变优先检查channel_up状态和复位时序如果两端计数都在跳但数据内容对不上再看字节序和tkeep是否正确。这种“先查链路、再查逻辑”的思路能省大量时间。4. 仿真验证从Example Design到自定义激励4.1 仿真环境与模型选择Chip2Chip IP核自带Example Design这是快速上手的最好入口。在Vivado的IP Sources里右键该IP选择Open IP Example Design会生成一个独立工程包含例化了的IP核、测试平台、约束文件和顶层wrapper。先对这个Example Design做一次完整仿真能帮助你确认本机仿真环境、Xilinx库编译是否正常同时也能看到链路从复位到channel_up的完整时序。仿真工具方面我平时主要用Vivado自带的XSim省心。也有一部分公司强制要求用ModelSim/Questa那就需要提前编译Xilinx仿真库在ModelSim里通过vlib和vmap把unisim、secureip库建好。不建库的后果是仿真一跑起来GT模型全是未知状态波形里红成一片。在仿真时Chip2Chip和Aurora PHY模型会经过很长的初始化过程。不要上来就给几百纳秒的时间这玩意儿经常需要几十上百微秒才能看到channel_up拉高。我第一次跑就是只给了2微秒然后盯着波形发呆以为链路没起来后来把仿真时间拉到200微秒才看到完整建链过程。4.2 复位与建链的仿真时序Aurora链路建链时序在仿真波形里大概是这个顺序先看到gt_reset释放紧接着GT TX PLL和RX PLL开始锁定gt_pll_lock信号拉高然后Aurora状态机开始发送初始化字符接收端进行字节同步这个阶段会看到rx_aligned之类内部信号跳动最后当通道对齐完成channel_up拉高。建议把这个channel_up拉高时刻记下来在后续自定义testbench里作为用户逻辑开始发送数据的触发条件。不要用简单的#延时来等不同参数下建链时间可能不同。正确的做法是写一个等待循环// 等待channel_up拉高再开始发送业务数据 wait_channel_up: while (!channel_up) begin (posedge user_clk); end // 此时再向master接口写入数据这样做的好处是哪怕你调整了线速率、参考时钟频率testbench依然能自适应链路的准备时间不会因为时序变化而出错。4.3 数据收发的验证方法在Chip2Chip两侧各挂一个数据源和一个数据接收模块这是最直接的验证模型。发送侧产生递增数据包格式定义为起始标志、32bit递增计数、若干有效数据、结束标志。接收侧检查递增计数是否连续以及包长度是否一致。由于Chip2Chip本质是透明传输理想情况下发送端写入什么接收端就能恢复什么。如果出现数据错位优先检查用户位宽和tkeep如果数据频繁丢包再看channel_up是否稳定以及复位信号是否在数据发送过程中被误触发。这里有个小技巧在testbench里故意在数据发送过程中拉高tready一段时间模拟接收端反压观察数据传输是否正确暂停和恢复。Aurora链路本身不感知上层反压但Chip2Chip的AXI4-Stream接口是有tready机制的如果IP核没有正确传递背压数据就会被强制丢弃。这个测试能快速暴露IP配置里Flow Control相关选项是不是影响了缓存深度。除了仿真还可以利用IP核状态寄存器通过AXI4-Lite接口读取链路状态。比如channel_up寄存器、错误计数寄存器。板级调试时把这些寄存器映射到软核或ILA里能直接看到链路健康状况。4.4 modelsim仿真波形红线等坑很多人在ModelSim里跑Xilinx高速收发器仿真遇到波形里全是红色未知态第一反应是代码问题其实九成是仿真库没弄对。Xilinx的GT模型依赖加密的secureip仿真模型必须用Vivado的compile_simlib功能把当前器件型号的库编译到ModelSim指定目录然后通过vmap映射到工程。没有正确映射时GT底层模块输出全是x态导致channel_up永远无法拉高。另一个坑是仿真超时。Aurora建链需要的时间远大于普通逻辑给testbench设置足够长的运行时间时最好使用run -all并配合timeout机制不要人为设置一个太短的仿真上限。同时复位释放的时刻也要控制好不能在gt_pll_lock没有拉高之前就去触发axi_resetn。我见过一个莫名其妙的问题ModelSim仿真中数据偶尔对偶尔错后来发现是ModelSim的优化选项把某些信号优化掉了导致观察波形不方便但实际功能没问题。此时只要在vsim命令里加一行关闭优化vsim -voptargsacc work.tb_top就能把所有内部信号保留下来方便调试。类似的坑还有编译器版本不一致导致的仿真模型不兼容所以最好用和Vivado版本匹配的ModelSim版本。5. 调试中的常见问题与避坑实录5.1 gt_reset、reset、power_down的配合gt_reset、reset、power_down这三个信号几乎每个调Aurora相关IP的人都会纠结。先说power_down它控制GT是否进入低功耗模式如果被拉高GT的PLL不会工作链路不可能建立。有些设计者为了省电把power_down做成动态控制结果在链路需要重训的时候没有及时拉低导致链路长时间起不来。我的建议是如果系统没有强制的低功耗需求把power_down固定拉低不要让上层逻辑在运行中随意控制它。gt_reset是GT层的复位通常连接到Aurora IP核的gt_reset接口。这个复位在上电时必须保证一个有效的低脉冲并且复位释放后要等GT PLL锁定。reset是Aurora核的系统复位一般在gt_pll_lock之后释放。如果两个复位同时释放经常导致初始化状态机跑飞。我最终用的复位逻辑是这样外部输入一个异步复位sys_rst_n通过(gt_pll_lock sys_rst_n)生成gt_rst再通过(channel_up gt_pll_lock sys_rst_n)生成用户侧aresetn。这样用户逻辑永远不会在链路未就绪时乱发数据。这个方法不是最精巧的但是是最稳的项目的上线时间紧稳定压倒一切。5.2 参考时钟和差分引脚问题参考时钟的问题在板级调试里隐蔽性极强。板卡上如果用了非专用Bank的普通差分引脚作为GT参考时钟输入即使仿真模型不看物理连接实际芯片内部也无法把外部时钟正确引入GT的专用时钟树。结果就是PLL始终无法锁定gt_pll_lock一直为低。如果你看到IP核init_clk和gt_refclk在仿真里都没问题板级却锁不住一定要检查原理图是不是把MGTREFCLK接到了专用时钟引脚上。另外GT参考时钟的端接电阻也必须按芯片手册配置好有些板卡为了通用性做了可调端接导致参考时钟差分电压不达标同样会锁不住。在多板级联场景里参考时钟源可能来自背板时钟或者时钟芯片。我遇到过一次时钟芯片配置错误输出的频率偏差超过千分之一链路能建起来但稳定不了几分钟。排查到最后用示波器量时钟频率才发现问题。这里强烈建议在系统调试前先用仪表或寄存器读出实际参考时钟频率不要只看原理图的标示。5.3 驱动与下载器问题在线调试Chip2Chip工程时常见的下载调试工具是Xilinx Platform Cable USB或者Digilent JTAG。有的Windows机器插入下载器后提示“无法加载这个硬件的设备驱动”这个大概率是USB驱动版本和Vivado版本不匹配。解决办法是去Xilinx官网下载对应的Cable Drivers并重新安装。装好了之后再打开Vivado Hardware Manager应该能看到设备。板级调试的另一个注意点不要用ILA去采样GT高速信号。ILA只能观测用户逻辑域的信号比如user_axis_tvalid、channel_up、复位信号等。GT串行信号是没法直接采样的。所以调试时重点观察Chip2Chip的用户接口和状态寄存器不要试图在ILA里抓高速串行波形。如果ILA占用逻辑过大导致时序收敛困难可以用Vivado的硬件管理器在线读寄存器或者通过AXI4-Lite接口读取Chip2Chip状态。这样做工程代价小只是多了几根寄存器接口线。5.4 时序收敛与系统稳定调试经验Chip2Chip吞吐量高的时候整个FPGA内部逻辑容易跑在较高频率上。用户接口时钟如果是200MHz甚至更高就要注意数据路径的寄存器切分。AXI Stream的tready和tvalid组合逻辑如果太长时序会很快违例。我一般在每个用户接口信号上直接打一拍寄存器虽然带来一个周期延迟但换来的时序收敛省心很多。当出现数据偶发错误时除了检查逻辑还要看看是不是多个时钟域交叉引入了亚稳态。Chip2Chip的channel_up状态信号是从Aurora逻辑域来的如果直接用在用户逻辑的组合逻辑里理论上应先用两级触发器同步。由于channel_up变化频率很低亚稳态概率小但仍建议做同步处理否则某天换了芯片批次或温度变化后问题可能突然爆发到时候非常难定位。还有一个稳定性的隐性因素电源纹波。高速收发器GT对电源噪声很敏感在链路长时间运行压力测试中出现偶发错误优先让硬件同事检查GT所用电源轨的纹波。FPGA逻辑再正确电源不达标也会导致serdes眼图恶化。软件层面能做的就是尽量在链路层加CRC或者重传机制。6. 一点个人体会Chip2Chip这套东西用熟了之后真的能大幅缩短高速互联类项目的开发周期。把Aurora PHY的细节交给IP核处理把业务逻辑集中到用户接口上来整个人都轻松了不少。我个人的习惯是不管配置界面看起来多简单第一版一定先把Example Design完整跑一遍仿真把复位和建链时序在波形里吃透然后再往自己的工程里集成。这个看似多余的一步能帮你省掉后面至少两天的查错时间。另外调试中遇到奇怪问题不要急着改代码先把链路状态寄存器全部读一遍确认gt_pll_lock、channel_up、错误计数这些基础信号再回头查逻辑。很多时候问题根本不是你想的那样出在业务逻辑上而是底层GT时钟、复位、电源这些“看不见的手”在捣乱。把这些基础工程做扎实Chip2Chip才能真正变成你的坚实底座。