1. 为什么SFP光口是FPGA以太网开发的"第一道坎"
很多刚接触FPGA的朋友,在板子上跑通了流水灯、数码管,甚至写了个UART收发,就觉得自己已经入门了。结果一拿到带SFP光口的板子,打开Vivado,面对一堆GT、GTH、GTY收发器原语和时钟约束,瞬间就懵了。我当初也是这样,手里攥着一块黑金AX7系列板子,盯着那两个SFP笼子看了整整两天,不知道从哪下手。
SFP光口实现千兆以太网传输,本质上要解决三个层面的问题:物理层的光电转换与高速串行收发、数据链路层的8b/10b编解码与帧同步、用户逻辑层的MAC帧收发与流控。这三个层面任何一环出问题,现象都是"链路不通"或者"丢包严重",而FPGA开发最难受的就是——你没法像软件那样打个断点看看变量,只能靠ILA抓波形,一点点猜。
XAPP1082这份文档之所以经典,是因为它把Xilinx 7系列FPGA上用1G/2.5G Ethernet PCS/PMA IP核实现SFP千兆以太网的完整流程讲透了。但文档毕竟是文档,很多"文档里不会写、但你不做就通不了"的细节,只有真正在板子上跑过一遍才知道。这篇内容就是把我从零跑通SFP千兆以太网的全过程拆开,包括IP核配置、时钟方案、约束文件、上板调试,以及我踩过的那些坑。
适合谁看?如果你已经会基本的Vivado操作,写过简单的Verilog,但没碰过高速收发器,那这篇就是给你准备的。如果你已经跑通过,也可以看看我在调试环节总结的排查链路,说不定能帮你省下几个通宵。
2. 动手之前:SFP链路里那些绕不开的基础概念
2.1 SFP光口到底在做什么
SFP是Small Form-factor Pluggable的缩写,中文叫"小型可插拔光模块"。你可以把它理解成一个"翻译官":FPGA内部处理的是并行数字信号,而光纤上跑的是高速串行光信号,SFP模块负责把电信号转成光信号发出去,同时把收到的光信号转回电信号。
在千兆以太网场景下,SFP模块的工作速率是1.25 Gbps(注意不是1 Gbps,因为8b/10b编码会带来25%的开销,1.25 Gbps × 8/10 = 1 Gbps 有效数据速率)。这个1.25 Gbps的串行信号通过板上的差分走线连接到FPGA的GT收发器引脚,由GT收发器完成串并转换。
这里有个容易混淆的点:SFP模块分"光模块"和"电模块"两种。光模块插光纤,电模块插网线(也就是常说的"电口SFP")。两者在FPGA侧的接口完全一样,都是差分对加几个低速控制信号。所以你在实验室用光模块跑通了,换成电模块接普通网线也一样能通,调试阶段用电模块反而更方便,因为不需要配对端的光模块和光纤。
2.2 8b/10b编码为什么必须要有
很多新手会问:为什么不能直接把8位数据发出去,非要编成10位?这不是白白浪费25%的带宽吗?
原因在于直流平衡和时钟恢复。高速串行链路上没有独立的时钟线,接收端要从数据流本身恢复出时钟。如果数据里长时间出现连续的0或连续的1,接收端的CDR(Clock Data Recovery)电路就会"找不到北",时钟漂移导致误码。8b/10b编码保证任意5个连续bit里至少有2个1和2个0,这样信号跳变足够频繁,CDR就能稳定工作。同时编码后的数据长期来看0和1的数量基本相等,直流分量接近零,交流耦合电容才能正常工作。
Xilinx的1G/2.5G Ethernet PCS/PMA IP核内部已经集成了8b/10b编解码器,你不需要自己写。但理解这个原理很重要,因为调试时如果看到链路不稳定,很可能就是编码器配置或者收发器均衡参数的问题。
2.3 GMII、RGMII、SGMII这几个接口别搞混
刚接触的时候我被这几个缩写搞得头大,这里用一句话说清楚:
| 接口类型 | 位宽 | 时钟 | 用途 |
|---|---|---|---|
| GMII | 8位 | 125 MHz | MAC与PHY之间的标准并行接口 |
| RGMII | 4位 | 125 MHz DDR | GMII的简化版,引脚少一半 |
| SGMII | 1位串行 | 625 MHz | 串行版GMII,走高速差分线 |
| 1000BASE-X | 1位串行 | 1.25 Gbps | 光纤上的原始千兆以太网 |
SFP光口用的是1000BASE-X,也就是1.25 Gbps串行。而FPGA内部的MAC核通常输出GMII接口。所以PCS/PMA IP核的作用就是做"GMII ↔ 1000BASE-X"的转换,中间包含8b/10b编解码、弹性缓冲、时钟校正等模块。
XAPP1082的核心思路就是:用1G/2.5G Ethernet PCS/PMA IP核把GMII转成1000BASE-X,直接驱动SFP的差分对。这样你就不需要外挂PHY芯片,FPGA直接和SFP模块对话。
3. Vivado工程搭建:从IP核配置到引脚约束
3.1 IP核选型与关键参数
打开Vivado,在IP Catalog里搜索"1G/2.5G Ethernet PCS/PMA",选那个带"or SGMII"的版本。双击打开配置界面,几个关键参数必须设对:
Standard选择:选"1000BASE-X"而不是"SGMII"。这两个的区别是SGMII带自动协商,1000BASE-X是纯光纤模式。如果你用的是SFP光模块,选1000BASE-X;如果用电口SFP接交换机,有些交换机要求SGMII协商,那就选SGMII。我建议先用1000BASE-X,因为逻辑简单,不涉及协商状态机。
Physical Interface:选"Device Specific Transceiver",这样IP核会直接调用你FPGA里的GT收发器,而不是走SelectIO。
Core Configuration:勾选"Include Shared Logic in core"还是"in example design"?如果你的工程里只有这一个IP核用GT,选"in core"省事;如果多个IP核共享GT,选"in example design"然后手动例化shared logic。新手建议选"in core"。
Clock Configuration:这里是最容易出错的地方。IP核需要几个时钟:
clk_p:125 MHz,GMII接口的时钟clk_gt:GT收发器的参考时钟,取决于你的板子晶振clk_ref:200 MHz或125 MHz,用于IDELAYCTRL
以我用的黑金AX7为例,板子上给GT的参考时钟是125 MHz差分晶振。所以clk_gt的频率要设成125 MHz,然后在约束文件里把它绑定到正确的引脚。
3.2 时钟方案:一个容易翻车的地方
SFP千兆以太网对时钟的要求非常苛刻。GMII接口的125 MHz时钟必须和收发器恢复出来的时钟同源,否则弹性缓冲会溢出或下溢。
IP核内部有一个弹性缓冲(Elastic Buffer),它的作用是吸收发送时钟和接收时钟之间的微小频差。但这个缓冲深度有限(通常几十个字节),如果两端时钟频差太大(超过±100 ppm),缓冲就会周期性溢出,表现为链路时通时断。
所以你的125 MHz参考时钟精度很关键。普通晶振精度大概±50 ppm,勉强够用;如果有条件,用±20 ppm的温补晶振更稳。我一开始用板载的普通晶振,链路跑几分钟就断一次,换了高精度晶振后连续跑24小时没出过问题。
另外,Vivado的Clock Wizard生成的125 MHz时钟,如果输入是100 MHz,会有分数分频,jitter比较大。建议直接用板上的125 MHz晶振经过BUFG驱动,不要用MMCM从其他频率分频。
3.3 约束文件怎么写才不出错
约束文件是新手最容易忽略、也最容易出问题的地方。XAPP1082的example design里有一份参考约束,但直接抄往往不通,因为引脚定义和你的板子不一样。
首先,GT收发器的引脚约束必须和原理图完全一致。以我的板子为例:
# GT参考时钟 set_property PACKAGE_PIN F6 [get_ports gt_refclk_p] set_property IOSTANDARD LVDS [get_ports gt_refclk_p] # SFP发送差分对 set_property PACKAGE_PIN D4 [get_ports sfp_txp] set_property PACKAGE_PIN D3 [get_ports sfp_txn] # SFP接收差分对 set_property PACKAGE_PIN C4 [get_ports sfp_rxp] set_property PACKAGE_PIN C3 [get_ports sfp_rxn]这些引脚号必须查你板子的原理图,不能照抄。我见过有人直接抄了example design的引脚,结果综合报错说引脚不存在,折腾半天才发现板子型号不对。
其次,时钟约束要写清楚:
create_clock -period 8.000 -name gt_refclk [get_ports gt_refclk_p] create_clock -period 8.000 -name clk_125m [get_ports clk_125m_p]8 ns周期对应125 MHz。如果你用的是200 MHz参考时钟,周期就是5 ns。
还有一个隐藏的坑:SFP模块的TX_DISABLE和TX_FAULT信号。很多板子把这两个信号接到了FPGA的普通IO上,如果你不驱动它们,SFP模块可能处于关闭状态。TX_DISABLE低电平有效,所以要把它拉低;TX_FAULT是输入,可以不管或者接到LED上做指示。
assign sfp_tx_disable = 1'b0; // 使能SFP发送这个细节XAPP1082里提了一句,但很容易被忽略。我当初就是忘了拉低TX_DISABLE,ILA抓波形发现GT有发送数据,但光模块就是不出光,查了半天才想起来。
4. 用户逻辑设计:从MAC帧到数据回环
4.1 先跑通回环再谈协议
IP核配置好、约束写对之后,不要急着写复杂的MAC控制器。第一步应该是跑通回环测试:把接收到的数据直接发回去,看看链路能不能通。
具体做法是在IP核的example design基础上改,把rx_data直接接到tx_data,rx_valid接到tx_valid。这样外部发什么,FPGA就回什么。用两台PC通过SFP电模块对接,一台ping另一台,如果能ping通,说明物理层和链路层都通了。
这个回环测试的价值在于:它把问题范围缩小到了"IP核配置+时钟+约束"这三件事上。如果回环不通,你不需要怀疑MAC逻辑,只需要查这三样。我当初回环跑了整整一天才通,最后发现是GT参考时钟的引脚约束写错了,导致收发器根本没工作。
4.2 GMII接口的时序细节
回环通了之后,就可以接自己的MAC逻辑了。GMII接口的时序有几个细节要注意:
发送方向:gmii_tx_clk是IP核输出的125 MHz时钟,你的MAC逻辑要用这个时钟的上升沿输出gmii_txd和gmii_tx_en。注意gmii_tx_clk是从IP核出来的,不是你自己生成的,必须用它来同步发送数据。
接收方向:gmii_rx_clk是从接收数据恢复出来的时钟,和gmii_tx_clk同频但可能不同相。你的接收逻辑要用gmii_rx_clk采样gmii_rxd和gmii_rx_dv。
这里有个经典问题:跨时钟域。发送用gmii_tx_clk,接收用gmii_rx_clk,如果你要在收发之间传递信息(比如流控),必须做跨时钟域处理。简单的方法是加一级FIFO,用异步FIFO把接收数据搬到发送时钟域。
4.3 一个极简MAC发送模块的写法
下面这段代码是我实际用的GMII发送模块,功能是把FIFO里的数据打成以太网帧发出去:
module mac_tx ( input wire clk_125m, input wire rst_n, input wire fifo_empty, input wire [7:0] fifo_dout, output wire fifo_rd_en, output reg [7:0] gmii_txd, output reg gmii_tx_en ); // 前导码 + SFD reg [7:0] preamble [0:7]; initial begin preamble[0] = 8'h55; preamble[1] = 8'h55; preamble[2] = 8'h55; preamble[3] = 8'h55; preamble[4] = 8'h55; preamble[5] = 8'h55; preamble[6] = 8'h55; preamble[7] = 8'hD5; // SFD end reg [3:0] state; reg [2:0] pre_cnt; reg [10:0] frame_len; reg [10:0] byte_cnt; localparam IDLE = 4'd0; localparam PRE = 4'd1; localparam DATA = 4'd2; localparam DONE = 4'd3; always @(posedge clk_125m or negedge rst_n) begin if (!rst_n) begin state <= IDLE; gmii_tx_en <= 1'b0; gmii_txd <= 8'h00; pre_cnt <= 3'd0; byte_cnt <= 11'd0; end else begin case (state) IDLE: begin gmii_tx_en <= 1'b0; if (!fifo_empty) begin state <= PRE; pre_cnt <= 3'd0; end end PRE: begin gmii_tx_en <= 1'b1; gmii_txd <= preamble[pre_cnt]; if (pre_cnt == 3'd7) begin state <= DATA; byte_cnt <= 11'd0; end else begin pre_cnt <= pre_cnt + 1'b1; end end DATA: begin gmii_tx_en <= 1'b1; gmii_txd <= fifo_dout; if (byte_cnt == frame_len - 1) begin state <= DONE; end else begin byte_cnt <= byte_cnt + 1'b1; end end DONE: begin gmii_tx_en <= 1'b0; state <= IDLE; end endcase end end assign fifo_rd_en = (state == DATA); endmodule这段代码的核心逻辑是:检测到FIFO非空后,先发7个0x55前导码加1个0xD5 SFD,然后连续发送数据字节,最后拉低gmii_tx_en表示帧结束。实际使用中还需要加CRC校验和帧间隙(IFG),但作为入门示例,这个框架已经能跑通最基本的UDP包发送了。
4.4 接收方向的帧对齐
接收比发送麻烦一点,因为你要从连续的字节流里找到帧的起始位置。标准做法是状态机搜索SFD:持续监测gmii_rxd,当连续收到7个0x55后跟一个0xD5时,认为帧开始,然后开始计数接收后续字节。
这里有个坑:前导码可能被弹性缓冲"吃掉"一部分。因为弹性缓冲会插入或删除空闲字符来调整时钟频差,有时候你收到的帧前面只有5个0x55而不是7个。所以状态机不能死等7个0x55,应该用"收到0xD5就认为帧开始"的策略,前面的0x55数量不固定。
我在调试时用ILA抓过接收波形,发现前导码数量在5到7之间跳变,这就是弹性缓冲在工作。如果你的状态机写死了等7个0x55,就会出现"偶尔丢帧"的现象,而且很难查。
5. 上板调试:链路不通时的排查链路
5.1 先看GT有没有起来
链路不通,第一步不是查MAC逻辑,而是确认GT收发器有没有正常工作。在Vivado的Hardware Manager里,找到你的GT通道,看几个关键状态:
gt_powergood:必须为1,表示GT供电正常gt_pll_lock:必须为1,表示PLL锁定gt_rx_cdr_lock:接收端CDR锁定,有光信号时应该为1gt_tx_reset_done和gt_rx_reset_done:复位完成
如果gt_pll_lock为0,说明参考时钟有问题。检查晶振有没有起振、引脚约束对不对、参考时钟频率设置是否正确。我遇到过一次gt_pll_lock死活不锁,最后发现是IP核里参考时钟频率设成了125 MHz,但板子上实际是200 MHz晶振。
如果gt_rx_cdr_lock为0,说明接收端没有收到有效信号。可能的原因:光纤没插好、对端没发数据、SFP模块没使能(TX_DISABLE没拉低)、收发差分对接反了。差分对接反是个隐蔽的坑,TX和RX如果画反了,现象就是CDR永远不锁,但你看原理图又觉得没问题。这时候可以试着在约束里把P和N对调一下,如果通了就是画反了。
5.2 ILA抓波形的正确姿势
GT起来之后,如果链路还是不通,就要用ILA抓GMII接口的波形了。插入ILA核,抓gmii_tx_clk、gmii_txd、gmii_tx_en、gmii_rx_clk、gmii_rxd、gmii_rx_dv这几组信号。
触发条件设成gmii_rx_dv上升沿,这样能抓到接收帧的起始。正常的接收波形应该是:gmii_rx_dv拉高后,gmii_rxd依次出现0x55、0x55...0xD5,然后是目标MAC地址、源MAC地址、类型字段、数据、CRC。
如果gmii_rx_dv一直是0,说明IP核没收到有效帧。回到上一步查CDR锁定。如果gmii_rx_dv有跳变但数据全是乱码,可能是8b/10b解码错误,检查收发器均衡参数。
ILA的采样深度要设够,至少抓1024个采样点,因为一个最小以太网帧也有64字节,加上前导码和IFG,需要几百个时钟周期。采样深度不够的话,你只能看到帧头,看不到帧尾。
5.3 弹性缓冲溢出怎么判断
弹性缓冲溢出的现象是:链路能通,但跑一段时间后丢包,然后自动恢复,周期性出现。判断方法是抓IP核的rx_elastic_buffer_overflow和rx_elastic_buffer_underflow信号,如果这两个信号有脉冲,就是频差太大了。
解决办法有两个:一是换更高精度的参考时钟晶振;二是在IP核配置里把弹性缓冲深度设大一点(如果有这个选项)。Xilinx的IP核通常不让你改缓冲深度,所以只能从时钟精度入手。
我实测下来,普通晶振跑千兆以太网,平均几小时会出现一次弹性缓冲溢出,表现为ping测试偶尔丢一个包。对于一般应用够用了,但如果要做视频流传输这种对丢包敏感的场景,必须用高精度晶振。
5.4 收发差分对极性反了的补救
前面提到差分对可能画反。如果你怀疑是这个问题,不用改板子,在约束文件里把P和N对调就行:
# 原来 set_property PACKAGE_PIN D4 [get_ports sfp_txp] set_property PACKAGE_PIN D3 [get_ports sfp_txn] # 对调后 set_property PACKAGE_PIN D3 [get_ports sfp_txp] set_property PACKAGE_PIN D4 [get_ports sfp_txn]GT收发器内部有极性反转功能,所以约束对调后重新生成比特流,功能就正常了。这个技巧在调试阶段非常实用,能帮你快速排除"是不是画反了"这个怀疑。
6. 从回环到实战:UDP数据回传的完整实现
6.1 为什么选UDP而不是TCP
在FPGA上实现完整的TCP协议栈是件很痛苦的事,状态机复杂、资源占用大、调试困难。对于大多数FPGA以太网应用,UDP就够了。UDP无连接、无重传、头部简单(只有8字节),非常适合FPGA这种"发出去就不管"的场景。
当然UDP不保证可靠传输,但你可以自己在应用层加简单的应答机制。比如PC发一个请求包,FPGA回一个数据包,PC收到后发下一个请求。这样虽然效率低一点,但逻辑简单,不容易出错。
6.2 UDP包格式与校验和计算
一个完整的UDP/IP包结构如下:
| 层级 | 字段 | 长度 |
|---|---|---|
| 以太网头 | 目标MAC + 源MAC + 类型 | 14字节 |
| IP头 | 版本 + 长度 + ID + 标志 + TTL + 协议 + 校验和 + 源IP + 目标IP | 20字节 |
| UDP头 | 源端口 + 目标端口 + 长度 + 校验和 | 8字节 |
| 数据 | 用户数据 | 可变 |
IP头的校验和计算是新手容易出错的地方。算法是:把IP头按16位分组,逐组相加,如果有进位就回卷加到低位,最后取反。Verilog实现如下:
function [15:0] ip_checksum; input [159:0] header; // 20字节IP头 reg [31:0] sum; integer i; begin sum = 0; for (i = 0; i < 10; i = i + 1) begin sum = sum + {16'b0, header[i*16 +: 16]}; end sum = (sum & 32'hFFFF) + (sum >> 16); sum = (sum & 32'hFFFF) + (sum >> 16); ip_checksum = ~sum[15:0]; end endfunctionUDP校验和是可选的,在IPv4里如果校验和字段为0表示不校验。为了简化,很多FPGA实现直接把UDP校验和设成0,PC端也能正常接收。但有些操作系统(比如某些Linux发行版)会检查UDP校验和,所以稳妥起见还是算一下。
6.3 一个可用的UDP回传架构
我的UDP回传架构是这样的:
- 接收路径:GMII接收 → 帧解析状态机 → 提取目标MAC和IP → 判断是否是发给FPGA的包 → 提取UDP数据 → 写入接收FIFO
- 用户逻辑:从接收FIFO读数据 → 处理(比如加1、取反、回传) → 写入发送FIFO
- 发送路径:从发送FIFO读数据 → 组装以太网头、IP头、UDP头 → 计算校验和 → GMII发送
这个架构的关键是FIFO的深度。如果PC发得快、FPGA处理得慢,接收FIFO会溢出。所以FIFO深度要够,至少能缓存几个最大帧(1500字节)。我用的是2048×8的FIFO,实测能扛住PC端连续发送。
另外,发送路径要处理帧间隙(IFG)。以太网标准要求两个帧之间至少间隔12个字节的时间(96个bit时间)。如果你的发送状态机发完一帧立刻发下一帧,有些交换机会认为这是非法帧而丢弃。所以在DONE状态后要等至少12个时钟周期再回到IDLE。
6.4 上板实测:ping通之后做什么
回环通了、UDP回传也通了之后,可以用PC端的网络调试助手发UDP包测试。我常用的测试流程:
- 用
ping命令测试链路层是否通(需要FPGA端能响应ARP,或者PC端配静态ARP) - 用网络调试助手发UDP包,看FPGA是否回传
- 逐渐加大发送速率,观察是否丢包
- 用Wireshark抓包,分析帧格式是否正确
Wireshark是个好东西,能看到FPGA发出来的包长什么样。如果Wireshark显示"Malformed Packet",说明你的帧格式有问题,通常是长度字段或者校验和算错了。
我当初调试时,Wireshark一直报IP校验和错误,查了半天发现是Verilog里16位加法没有处理进位回卷。改完之后就正常了。所以校验和计算一定要仔细,这是最容易出错的地方。
7. 几个让我熬夜的坑与对应的解法
7.1 综合报错"GT not available"
这个错误通常是因为你选的FPGA型号和IP核不匹配。比如你选的是Artix-7,但IP核配置成了Kintex-7的GT。解决办法是在IP核配置里把"Silicon Revision"和"Device"改成和你实际芯片一致的选项。
还有一种可能是GT的参考时钟引脚被其他IP占用了。检查你的Block Design里有没有其他IP也在用同一个GT Quad。如果有,需要把shared logic提取出来共用。
7.2 比特流生成失败"Implement Design变红"
Implement Design变红通常是因为时序不满足。打开Timing Report,看WNS(Worst Negative Slack)是多少。如果是负数,说明有时序违例。
SFP千兆以太网工程里,最容易违例的是GMII接口的125 MHz路径。因为125 MHz周期只有8 ns,如果逻辑级数太多,很容易超。解决办法:
- 在GMII接口上加一级寄存器打拍
- 把组合逻辑拆成流水线
- 用
set_max_delay约束放松非关键路径
我当初遇到的是接收状态机的组合逻辑太长,从gmii_rxd到状态机输出经过了好几级LUT。后来在状态机输入加了一级寄存器,时序就过了。
7.3 ILA抓不到波形
ILA抓不到波形的原因很多,最常见的是采样时钟选错了。ILA的采样时钟必须是被抓信号的同步时钟。如果你抓gmii_rx_clk域的信号,采样时钟就要选gmii_rx_clk,不能选系统时钟。
另一个原因是触发条件设得太苛刻。比如你设了gmii_rx_dv == 1 && gmii_rxd == 8'hD5,但实际波形里gmii_rx_dv和gmii_rxd的时序关系可能和你想的不一样。建议先用简单的触发条件(比如gmii_rx_dv上升沿),抓到波形后再分析。
还有一种情况是ILA核的时钟域和JTAG时钟域不同步,导致上传数据失败。这时候可以降低ILA的采样深度,或者换用更小的ILA核。
7.4 链路通但丢包严重
链路能ping通但丢包严重,通常是以下几个原因:
- 弹性缓冲溢出:前面说过,换高精度晶振
- FIFO溢出:加大FIFO深度,或者加流控
- 帧间隙不够:确保发送状态机在帧之间等待至少12个时钟周期
- CRC错误:检查CRC计算逻辑,或者直接用IP核的CRC校验功能
我遇到过一次丢包率50%的情况,最后发现是发送FIFO的读使能逻辑有问题,偶尔会多读一个字节,导致帧长度不对。用ILA抓发送波形,对比Wireshark的抓包结果,才定位到这个问题。
8. 工程固化与上电自启动
8.1 生成固化文件
调试完成后,需要把比特流固化到Flash里,让板子上电自动加载。Vivado里生成固化文件的步骤:
- 打开Hardware Manager,连接板子
- 右键FPGA设备,选"Add Configuration Memory Device"
- 选择你板子上的Flash型号(常见的是Micron或Winbond的SPI Flash)
- 右键选"Program Configuration Memory Device"
- 选择你的
.bin文件(不是.bit文件)
注意:.bit文件是JTAG加载用的,.bin文件才是固化用的。在Vivado里生成.bin文件的方法是在Settings里勾选"Bin File"选项,或者用write_cfgmem命令转换。
8.2 上电自启动的注意事项
固化之后,板子上电应该自动加载。如果没加载,检查几个地方:
- Flash型号选对了没有
- 板子的启动模式跳线是不是设成了SPI Flash启动
.bin文件有没有生成正确
我遇到过固化后不启动的情况,最后发现是Flash型号选错了。板子上实际是W25Q128,我选成了W25Q64,容量不匹配导致加载失败。所以选Flash型号时一定要查原理图。
8.3 固化后SFP链路不工作
有时候JTAG加载时链路正常,固化后就不通了。这通常是因为复位逻辑的问题。JTAG加载时,Vivado会自动复位GT;固化后上电,GT的复位需要你自己控制。
正确的做法是在逻辑里加一个上电复位计数器,等GT参考时钟稳定后(通常几毫秒),再释放GT复位。Xilinx的example design里有这个逻辑,直接抄过来就行。
reg [19:0] reset_cnt; always @(posedge clk_125m) begin if (reset_cnt != 20'hFFFFF) reset_cnt <= reset_cnt + 1'b1; end wire gt_reset = (reset_cnt != 20'hFFFFF);这个计数器大概数100万个时钟周期,对应8毫秒左右,足够GT参考时钟稳定了。
9. 从XAPP1082延伸出去:还能玩什么
跑通SFP千兆以太网之后,这个平台可以扩展出很多应用。比如:
视频传输:把摄像头数据通过UDP包发到PC,PC端用Python或OpenCV接收显示。这个方案比HDMI采集卡便宜,而且FPGA端逻辑简单。
高速数据采集:ADC采样数据通过SFP传到服务器,做实时信号处理。千兆带宽对于大多数中低速ADC足够了。
多板卡同步:用SFP做板间通信,实现多块FPGA的同步采集和处理。SFP的延迟比普通IO低,而且抗干扰能力强。
网络时间同步:在FPGA里实现IEEE 1588协议,做高精度时间同步。这个对时钟要求更高,但原理和千兆以太网是一样的。
我目前在做的是把SFP千兆以太网和DDR3缓存结合起来,做一个"网络数据记录仪":PC通过网口发指令,FPGA把采集到的数据存到DDR3,然后通过网口回传。这个架构在工业数据采集场景很实用。
最后分享一个调试小技巧:如果你有逻辑分析仪,可以把SFP的差分信号接到分析仪上,直接看1.25 Gbps的串行波形。虽然分析仪可能解不出8b/10b编码,但能看到信号幅度和眼图,判断信号完整性。如果没有高端分析仪,就用ILA抓并行侧的信号,也能定位大部分问题。
SFP千兆以太网这个方向,入门门槛确实比流水灯高不少,但一旦跑通,你会发现FPGA的高速接口能力一下子打开了。XAPP1082是个很好的起点,但文档里的example design只是"能跑",要真正用到项目里,还需要根据实际需求做很多调整。希望这篇内容能帮你少走一些弯路,把更多时间花在应用逻辑上,而不是和底层接口死磕。