把FPGA的GTY收发器当成一个“超级串口”:我的自定义协议通信实践(基于KCU116开发板)
把FPGA的GTY收发器当成一个“超级串口”:我的自定义协议通信实践(基于KCU116开发板)
第一次接触Xilinx的GTY收发器时,我被官方文档里那些复杂的参数和协议搞得晕头转向。直到有一天,我突发奇想:为什么不把它看作一个"超级串口"呢?这个简单的类比彻底改变了我对高速串行通信的理解。本文将分享如何用GTY实现两块KCU116开发板之间的点对点通信,完全避开复杂的Aurora协议,就像使用一个波特率超高的UART那样简单直接。
1. GTY收发器的"超级串口"本质
GTY本质上就是一个加强版的串口模块。想象一下传统UART的功能:并行数据转串行发送(TX)、串行数据转并行接收(RX)、需要双方约定波特率。GTY同样做这些事情,但有几个关键升级:
- 波特率自由:不再需要精确匹配的时钟,GTY自带时钟恢复功能
- 自动对齐:不用手动计算起始位,内置的逗号检测(comma alignment)自动完成对齐
- 超高速率:KCU116的GTY轻松达到10Gbps以上,是普通UART的百万倍
// 传统UART发送一个字节需要约10个时钟周期(含起始/停止位) always @(posedge clk) begin if (tx_start) begin tx_data <= {1'b0, data_byte, 1'b1}; // 添加起始位和停止位 tx_count <= 0; end else if (tx_count < 10) begin tx_out <= tx_data[tx_count]; tx_count <= tx_count + 1; end end对比GTY的发送逻辑,核心差异在于:
| 特性 | 传统UART | GTY收发器 |
|---|---|---|
| 时钟要求 | 严格匹配波特率 | 自动时钟恢复 |
| 对齐方式 | 起始位检测 | 逗号检测+K码对齐 |
| 典型速率 | 115200bps | 10Gbps+ |
| 错误检测 | 奇偶校验 | 8B/10B编码+CRC校验 |
2. 极简GTY IP核配置指南
在Vivado中创建GTY IP核时,记住我们的"超级串口"原则:只启用必要功能。以下是关键配置步骤:
- 选择
GTY Quad类型,参考时钟选择156.25MHz(KCU116板载晶振) - 线速率设为10.3125Gbps(实际可用约10Gbps)
- 数据位宽选择32bit(平衡效率和复杂度)
- 必须启用的功能:
- RX字节和字对齐(替代UART起始位)
- 8B/10B编码(基础错误检测)
- 建议禁用的复杂功能:
- Aurora协议支持
- 弹性缓冲区
- 动态重配置
注意:GTY的TX和RX需要分别提供独立的用户时钟(usrclk),频率为线速率除以40(10.3125Gbps/40≈257.8MHz)。KCU116的MMCM可以生成这个时钟。
配置完成后,IP核会生成以下关键接口信号:
// 发送接口 input [31:0] gty_txdata, // 并行发送数据 input gty_txvalid, // 数据有效标志 output gty_txready, // 发送就绪 // 接收接口 output [31:0] gty_rxdata, // 并行接收数据 output gty_rxvalid, // 数据有效标志 input gty_rxreset, // 接收复位 // 状态指示 output gty_rxbytealigned, // 字节对齐完成 output gty_rxbyterealign, // 重新对齐请求3. 自定义通信协议设计
既然把GTY当作串口,我们需要设计一个简单的帧结构。考虑到高速传输特性,协议需要:
- 极简帧头/帧尾:快速识别帧边界
- 轻量级校验:平衡可靠性和效率
- 小端序:匹配GTY的默认字节序
我设计的帧格式如下(每行32bit):
0x55AA55AA // 帧头(交替的01模式便于对齐) [长度] // 数据长度(单位:32bit字) [类型] // 数据类型标识 [数据]... // 有效载荷 [CRC32] // 校验码(多项式0x04C11DB7)实现发送逻辑的关键代码:
// 状态机定义 typedef enum { IDLE, SEND_HEADER, SEND_LENGTH, SEND_TYPE, SEND_DATA, SEND_CRC, WAIT_ACK } tx_state_t; // 发送状态机 always @(posedge usrclk) begin case (state) IDLE: if (start_send) begin crc <= 32'hFFFF_FFFF; state <= SEND_HEADER; end SEND_HEADER: if (gty_txready) begin gty_txdata <= 32'h55AA_55AA; state <= SEND_LENGTH; end // 其他状态类似... SEND_DATA: if (gty_txready && data_count < length) begin gty_txdata <= payload[data_count]; crc <= next_crc32(crc, payload[data_count]); data_count <= data_count + 1; if (data_count == length-1) state <= SEND_CRC; end endcase end接收端需要特别处理对齐问题。当检测到rxbytealigned信号变高后,开始寻找帧头:
always @(posedge usrclk) begin if (gty_rxvalid) begin case (state) WAIT_HEADER: if (gty_rxdata == 32'h55AA55AA) state <= RECV_LENGTH; RECV_LENGTH: length <= gty_rxdata[15:0]; crc <= next_crc32(32'hFFFF_FFFF, gty_rxdata); state <= RECV_TYPE; // 其他接收状态... endcase end end4. 时钟恢复与通道绑定实战
GTY最神奇的功能莫过于自动时钟恢复,这解决了传统串口最头疼的时钟同步问题。实现原理:
- 发送端将时钟信息嵌入数据流(通过8B/10B编码)
- 接收端使用CDR(Clock Data Recovery)电路提取时钟
- 弹性缓冲区补偿时钟差异
在KCU116上验证时钟恢复的简单方法:
// 发送伪随机数测试时钟稳定性 reg [31:0] lfsr; always @(posedge usrclk) begin if (gty_txready) begin lfsr <= {lfsr[30:0], lfsr[31] ^ lfsr[21] ^ lfsr[1] ^ lfsr[0]}; gty_txdata <= lfsr; gty_txvalid <= 1'b1; end end接收端统计错误率的简单方法:
reg [31:0] err_count; always @(posedge usrclk) begin if (gty_rxvalid) begin if (lfsr_expected != gty_rxdata) err_count <= err_count + 1; lfsr_expected <= {lfsr_expected[30:0], lfsr_expected[31]^lfsr_expected[21]^lfsr_expected[1]^lfsr_expected[0]}; end end实测发现,即使故意将参考时钟偏移±200ppm,GTY仍能保持零误码,这得益于其强大的时钟恢复能力。
5. 调试技巧与性能优化
经过多次实践,我总结出几个GTY调试的关键技巧:
眼图扫描:使用Vivado的IBERT工具观察信号质量
- 打开
Hardware Manager→Open IBERT - 设置合适扫描时间和电压阈值
- 优化PCB布局如果眼图开口不足
- 打开
误码注入测试:验证协议健壮性
// 可控误码注入模块 reg [5:0] err_rate = 0; // 误码率控制(0-63) always @(posedge usrclk) begin if (gty_txready && |err_rate) begin if ($urandom % 64 < err_rate) gty_txdata <= gty_txdata ^ (32'h1 << ($urandom % 32)); end end性能优化参数:
参数 推荐值 作用说明 RXDFE_CFG 0x210000 优化均衡器设置 TXDIFFCTRL 4'b1010 调整发送端差分电压 RXLPMRESET_TIME 7'b0001111 低功耗模式复位时间 CPLL_CFG 24'hB807D0 锁相环优化配置
在KCU116上实现的最终性能指标:
- 吞吐量:9.8Gbps(32bit @ 306.25MHz)
- 延迟:端到端约320ns(包含协议处理)
- CPU占用:几乎为零(完全硬件加速)
- 误码率:连续72小时测试零误码
6. 进阶应用:多通道聚合
单个GTY通道已经很快,但有时我们需要更高带宽。KCU116的GTY Quad包含4个通道,可以聚合使用:
位宽扩展:将4个32bit通道合并为128bit总线
// 发送端聚合 assign gty0_txdata = data128[31:0]; assign gty1_txdata = data128[63:32]; assign gty2_txdata = data128[95:64]; assign gty3_txdata = data128[127:96]; // 接收端同步检测 always @(posedge usrclk) begin if (gty0_rxvalid & gty1_rxvalid & gty2_rxvalid & gty3_rxvalid) data128_out <= {gty3_rxdata, gty2_rxdata, gty1_rxdata, gty0_rxdata}; end通道绑定配置:
- 在IP核中启用
Channel Bonding - 设置主通道(通常为通道0)
- 配置相同的
CHAN_BOND_SEQ序列
- 在IP核中启用
性能对比:
模式 理论带宽 实测带宽 资源占用 单通道 10Gbps 9.8Gbps 1个GTY 四通道聚合 40Gbps 38.2Gbps 4个GTY
实际项目中,我使用这种方案实现了两块KCU116之间的视频原始数据传输,完全替代了传统的光纤方案,延迟降低到原来的1/5。
