
引言
本指南介绍如何在 AMD(原 Xilinx)Ultrascale FPGA 上配置一个多吉比特收发器(MGT),并将其连接到 Xillyp2p IP 核。本示例中使用的是 GTH,但绝大多数细节同样适用于 GTY 以及后续 FPGA(如 Ultrascale+ 等)上的新型 MGT。
这里展示的示例,是对 IP 核端口与 API 指南 中简化应用框图的实现:

如上图详细所示,GTH 与 Xillyp2p 之间的接口非常简单。尽管如此,要正确配置 GTH 并确定其端口连接方式仍有一定难度。本指南给出了一个完整的设计示例,包括屏幕截图、Verilog 代码和时序约束。
本示例以 KCU105 开发板为目标,这是 Kintex Ultrascale 系列中最流行的开发板。该设计利用板上的 SFP0 接口,借助光收发模块和光纤,以 5 Gb/s 的数据速率与另一块板通信(如上图所示)。
此设计可根据其他用途和接口进行修改。由于采用了最简单的 GTH 配置(无编码),如果出现连接问题,相对容易诊断。此外,Xillyp2p IP 核提供了一些诊断输出信号,可以连接到 LED 指示灯上(如下所示),这非常有用,尤其是其中一个 LED 会在物理链路上每出现一个比特错误时闪烁一次。
对于使用 7 系列 FPGA(Kintex-7、Artix-7、Virtex-7 和 Zynq-7000)的用户,另有专门的指南介绍如何配置 Kintex-7 GTX(而不是本例中的 GTH)。
配置 GTH
配置过程通过带简要注释的截图说明。本示例使用 Vivado 2025.1 创建,但其他版本也应适用,可能仅有细微差别。
您也可以下载一个现成的 XCI 文件,其中包含了此处所示的配置。
在 Vivado 中,打开 IP 目录(IP Catalog),选择 UltraScale FPGAs Transceiver Wizard。根据以下截图调整配置。请注意,收发器可以通过向导中的子菜单进行更详细的配置,但这并非必需:所有需要修改的内容均已在下方截图中体现。向导中“Start from scratch”的默认配置在其他方面是合适的。
向导的初始窗口应配置如下:
需要注意以下几点:
- IP 核的名称设为“fibergt”。这将在下面 Verilog 示例的例化中使用。
- 线速率(Line Rate)在两个方向上都设为 5 Gb/s。您可以根据设备需要使用不同的速率。
- 为简单起见,PLL 类型选择 CPLL。这是每个收发器独立的 PLL,与 Quad PLL(四个收发器共享,功能更多但配置更复杂)不同。
- “Actual Reference Clock”是外部参考时钟的频率。KCU105 板上默认在 P6/P5 引脚提供 156.25 MHz 的低抖动时钟,因此使用该时钟。
- “Encoding”保持默认设置 Raw(无编码),因为 Xillyp2p 期望收发器像普通 SERDES 一样工作,不进行任何数据流操作。
- 用户数据宽度(User Data Width)和内部数据宽度(Internal Data Width)在发射器和接收器上均已默认正确设置为 32 位。用户数据宽度应与 Xillyp2p IP 核指定的并行字宽度匹配,这里已经一致。
- 在接收器一侧,端接(Termination)设置为 AVTT。这设定了施加到 GTH 电接收输入端的偏置电压。如果您使用的不是带 SFP+ 模块的光纤链路,则可能需要不同的链路耦合、端接以及可编程端接电压设置。这些设置会显著影响链路质量。即使每个并行字到达时包含大量错误,调整这些设置有时也能使链路完全无误。
- ppm 频率偏移设为 200 ppm,考虑了每侧 100 ppm 的时钟振荡器容差。这个数值可能偏大。
接下来,转到“Physical Resources”选项卡:
这里唯一需要修改的是将自由运行时钟的频率设为 125 MHz。这个额外的时钟提供给收发器用于其内部管理任务。在下面的示例设计中,使用 KCU105 板上的一个时钟来实现此目的,其频率为 125 MHz。
该窗口的下半部分允许选择使用 FPGA 中的哪些 GTH 资源。这些设置会被项目 XDC 文件中的布局约束覆盖,因此本节对最终结果没有影响。也可以删除 XDC 文件中的约束,依赖这些设置。使用哪种方法取决于个人偏好;在本示例中,忽略向导的这些设置。
接下来,转到“Optional Features”选项卡:
此处显示该选项卡只是为了说明无需更改任何内容。特别是接收端的逗号检测与对齐,以及接收时钟校正等功能均未使用,因为这些功能需要启用编码,而此处并未启用。Xillyp2p 内部实现了自己的同步、对齐和时钟校正方案。
接下来,转到最后一个选项卡“Structural Options”:
这里,将“Include simple receiver / transmitter user clocking network in the”改为 Core,而不是 Example design。这样,几个 BUFG 会被添加到核内部,而不是在设计例化中。这只是一个小差异。
窗口的下半部分包含许多用于向收发器接口添加端口的选项。本示例都不需要,因此默认设置已足够。
至此,GTH 的配置就完成了。接下来,给出一个示例 Verilog 设计。
收发器的例化
顶层 Verilog 模块的开头如下:
module example
(
input gth_refclk_n,
input gth_refclk_p,
input gth_rxn,
input gth_rxp,
output gth_txn,
output gth_txp,
input init_clk_p,
input init_clk_n,
output sfp_tx_enable,
output [7:0] gpio_led
);
前六个端口属于 GTH 本身:参考时钟、接收和发送引脚。
init_clk_p/n 端口是 KCU105 板上自由运行时钟的差分输入,频率为 125 MHz。sfp_tx_enable 将在下面简要说明。
最后是八个 LED 输出。
在例化 GTH 之前,向设计中添加两个时钟缓冲器:
wire gt_refclk;
wire init_clk, init_clk_i;
IBUFDS_GTE3 #(
.REFCLK_EN_TX_PATH(1'b0),
.REFCLK_HROW_CK_SEL(2'd0),
.REFCLK_ICNTL_RX(2'd0)
) ibufds_gt (
.I(gth_refclk_p),
.IB(gth_refclk_n),
.CEB(1'b0),
.O(gt_refclk),
.ODIV2()
);
IBUFDS ibufds (.O(init_clk_i), .I(init_clk_p), .IB(init_clk_n));
BUFG bufg (.O(init_clk), .I(init_clk_i));
与 Xillyp2p IP 核接口需要用到以下这些连线:
wire rx_clk;
wire tx_clk;
wire async_reset;
wire [31:0] in_data;
wire [31:0] out_data;
assign async_reset = 0;
本例中将 async_reset 接地。这有点粗糙,但设计仍然可以工作。在实际设计中,建议使用此异步复位在上电后立即复位逻辑。
接下来是一个虽小但可能重要的细节:
assign sfp_tx_enable = 1; // Enable SFP+ transmitter
sfp_tx_enable 输出端口通过一个 MOSFET 晶体管连接到光收发器的 TX_DISABLE 输入,这会使其极性反转。默认情况下,板子上安装了一个跳线,使收发器的发送器保持使能。但如果该跳线缺失,收发器的发送器将被关闭。将此输出保持为高电平,会强制 TX_DISABLE 为低电平,确保光收发器正常工作。
现在例化收发器:
fibergt fibergt_ins (
.gtwiz_userclk_tx_reset_in(async_reset),
.gtwiz_userclk_tx_srcclk_out(),
.gtwiz_userclk_tx_usrclk_out(),
.gtwiz_userclk_tx_usrclk2_out(tx_clk),
.gtwiz_userclk_tx_active_out(),
.gtwiz_userclk_rx_reset_in(async_reset),
.gtwiz_userclk_rx_srcclk_out(),
.gtwiz_userclk_rx_usrclk_out(),
.gtwiz_userclk_rx_usrclk2_out(rx_clk),
.gtwiz_userclk_rx_active_out(),
.gtwiz_reset_clk_freerun_in(init_clk),
.gtwiz_reset_all_in(async_reset),
.gtwiz_reset_tx_pll_and_datapath_in(1'b0),
.gtwiz_reset_tx_datapath_in(1'b0),
.gtwiz_reset_rx_pll_and_datapath_in(1'b0),
.gtwiz_reset_rx_datapath_in(1'b0),
.gtwiz_reset_rx_cdr_stable_out(),
.gtwiz_reset_tx_done_out(),
.gtwiz_reset_rx_done_out(),
.gtwiz_userdata_tx_in(out_data),
.gtwiz_userdata_rx_out(in_data),
.drpclk_in(init_clk),
.gthrxn_in(gth_rxn),
.gthrxp_in(gth_rxp),
.gtrefclk0_in(gt_refclk),
.gthtxn_out(gth_txn),
.gthtxp_out(gth_txp),
.gtpowergood_out(),
.rxpmaresetdone_out(),
.txpmaresetdone_out()
);
收发器直接连接到物理引脚 gth_rxp、gth_rxn、gth_txp 和 gth_txn。参考时钟 gt_refclk 是 IBUFDS_GTE3 时钟缓冲器的输出。
自由运行时钟 init_clk 连接到 gtwiz_reset_clk_freerun_in 和 drpclk_in。如上所述,该时钟用于收发器的控制逻辑。
注意 tx_clk 和 rx_clk 是收发器的输出。换句话说,收发器通过 gtwiz_userclk_tx_usrclk2_out 和 gtwiz_userclk_rx_usrclk2_out 端口提供这两个时钟。与 in_data 和 out_data 一起,这些是收发器和 Xillyp2p IP 核之间的唯一连接,如下所示。
Xillyp2p IP 核的例化
本示例使用了在 IP Core Factory 生成的 IP 核中的 core A。物理链路的另一端应使用 core B(除非核是对称的,相关主题在 IP Core Factory 指南 末尾有说明)。
IP 核的配置如下面这个网络应用截图所示:
IP 核的通用参数与 GTH 匹配:32 位并行字宽,时钟频率 156.25 MHz。请注意,这也是 rx_clk 和 tx_clk 的频率,恰好与参考时钟相同。例如,如果改变 GTH 的线速率(不改变参考时钟和并行字宽),rx_clk 和 tx_clk 的频率也会相应改变。此时,需要在 IP Core Factory 中指定更新后的频率。
为此 IP 核定义了三个数据流:demo32_to_b、demo32_to_a 和 demo_256。可以根据应用需要配置不同数量、不同名称和属性的数据流。
在例化 IP 核之前,还需要一些额外的连线。以下代码段直接从 IP 核的例化模板中复制:
wire status_link_down;
wire status_initializing;
wire status_link_partner_mismatch;
wire status_bit_error;
wire status_rev_polarity;
wire [31:0] status_debug;
wire [2:0] error_test_rate;
// Wires related to data stream "demo32_to_a"
wire user_rx_demo32_to_a_wr_en;
wire [31:0] user_rx_demo32_to_a_wr_data;
wire user_rx_demo32_to_a_full;
wire user_rx_demo32_to_a_eop;
// Wires related to data stream "demo32_to_b"
wire user_tx_demo32_to_b_rd_en;
wire [31:0] user_tx_demo32_to_b_rd_data;
wire user_tx_demo32_to_b_empty;
wire user_tx_demo32_to_b_eop;
// Wires related to data stream "demo_256"
wire user_tx_demo_256_rd_en;
wire [255:0] user_tx_demo_256_rd_data;
wire user_tx_demo_256_empty;
wire user_tx_demo_256_eop;
现在进行例化本身。这部分也是从 core A 的例化模板中复制的:
xillyp2p_core_a xillyp2p_core_a_ins (
// Ports related to data stream "demo32_to_a"
// Inbound data stream:
.user_rx_demo32_to_a_wr_en(user_rx_demo32_to_a_wr_en),
.user_rx_demo32_to_a_wr_data(user_rx_demo32_to_a_wr_data),
.user_rx_demo32_to_a_full(user_rx_demo32_to_a_full),
.user_rx_demo32_to_a_eop(user_rx_demo32_to_a_eop),
// Ports related to data stream "demo32_to_b"
// Outbound data stream:
.user_tx_demo32_to_b_rd_en(user_tx_demo32_to_b_rd_en),
.user_tx_demo32_to_b_rd_data(user_tx_demo32_to_b_rd_data),
.user_tx_demo32_to_b_empty(user_tx_demo32_to_b_empty),
.user_tx_demo32_to_b_eop(user_tx_demo32_to_b_eop),
// Ports related to data stream "demo_256"
// Outbound data stream:
.user_tx_demo_256_rd_en(user_tx_demo_256_rd_en),
.user_tx_demo_256_rd_data(user_tx_demo_256_rd_data),
.user_tx_demo_256_empty(user_tx_demo_256_empty),
.user_tx_demo_256_eop(user_tx_demo_256_eop),
// General signals
.rx_clk(rx_clk),
.tx_clk(tx_clk),
.async_reset(async_reset),
.in_data(in_data),
.out_data(out_data),
.status_link_down(status_link_down),
.status_initializing(status_initializing),
.status_link_partner_mismatch(status_link_partner_mismatch),
.status_bit_error(status_bit_error),
.status_rev_polarity(status_rev_polarity),
.status_debug(status_debug),
.error_test_rate(error_test_rate)
);
// error_test_rate should always be zero unless you want to the test
// what happens when there are errors on the physical data link.
assign error_test_rate = 3'd0;
如前所述,IP 核使用四根线与 GTH 连接:rx_clk、tx_clk、in_data 和 out_data。其余连接与应用程序逻辑相关,下面讨论。
与应用程序逻辑交换数据
熟悉 Xillybus 的 PCIe 或 XillyUSB IP 核的用户会发现这部分非常相似。
首先,回顾一下,本示例围绕 core A 展开,而物理链路另一侧的 FPGA 上(通常)应使用 core B。
名为“demo32_to_a”的应用程序数据流允许另一侧(使用 core B)的 FPGA 向使用 core A 的 FPGA 发送数据。
访问这些数据的主流方法是通过 FIFO。例如,fifo_32 是一个标准的双时钟 FIFO,数据字宽为 32 位,在 Vivado 项目中定义并按如下方式例化:
fifo_32 data_in_fifo
(
.rst(async_reset),
.wr_clk(tx_clk),
.rd_clk(<连接到应用逻辑>),
.din(user_rx_demo32_to_a_wr_data),
.wr_en(user_rx_demo32_to_a_wr_en),
.full(user_rx_demo32_to_a_full),
.rd_en(<连接到应用逻辑>),
.dout(<连接到应用逻辑>),
.empty(<连接到应用逻辑>)
);
名为 user_rx_demo32_to_a_* 的三根线连接到 Xillyp2p IP 核。IP 核使用这些信号将来自 core B 的数据填入 FIFO。
请注意,FIFO 的“full”端口也被连接。Xillyp2p IP 核会尊重该信号,并通过其流量控制机制避免溢出。此机制是可选的,但推荐使用(注意在 IP 核配置截图的所有三个数据流的“Flow control”列中均为“Yes”)。
FIFO 的其余端口连接到应用程序逻辑,后者根据需要读取数据。应用程序逻辑不需要以特定速率读取数据;唯一的要求是避免在 FIFO 为空时尝试读取。
另请注意,FIFO 的 wr_clk 端口连接到了 tx_clk,尽管它接收数据。所有与应用程序逻辑的交互都基于 tx_clk。不要被其他信号名称中的“rx”前缀搞混——rx_clk 仅用于与收发器相关的部分,并且只与 in_data 一起使用。
对于相反方向的数据流,“demo32_to_b”允许 core A 向另一侧(使用 core B)的 FPGA 发送数据。用于此目的的 FIFO 例化如下:
fifo_32 data_out_fifo
(
.rst(async_reset),
.wr_clk(<连接到应用逻辑>),
.rd_clk(tx_clk),
.din(<连接到应用逻辑>),
.wr_en(<连接到应用逻辑>),
.full(<连接到应用逻辑>),
.rd_en(user_tx_demo32_to_b_rd_en),
.dout(user_tx_demo32_to_b_rd_data),
.empty(user_tx_demo32_to_b_empty)
);
assign user_tx_demo32_to_b_eop = 0;
名为 user_tx_demo32_to_b_* 的三根线连接到 Xillyp2p IP 核。IP 核使用这些信号在 FIFO 非空时立即从中取出数据。
FIFO 的其他端口连接到应用程序逻辑,应用程序逻辑以常规方式向 FIFO 写入数据。应用程序逻辑向 FIFO 写入多少数据没有要求,也不需要特殊操作来使数据到达另一端。唯一明显的要求是避免在 FIFO 已满时写入。
由于此数据流启用了流量控制,只有当对端的 FIFO 未满时,才会从此端的 FIFO 中取出数据。因此,core B 侧的应用程序逻辑实际上控制了端到端的数据流:如果对侧(core B)的应用程序逻辑从其 FIFO 读取数据的速度慢于此侧(core A)向 FIFO 写入数据的速度,那么此侧的 FIFO 最终会变满,迫使应用程序逻辑停止写入。换句话说,这种安排等效于一个写端口在一个 FPGA 上、读端口在另一个 FPGA 上的单一 FIFO。
另请注意,本例中未使用 user_rx_demo32_to_a_eop,而 user_tx_demo32_to_b_eop 被接地。这两个端口与随数据字一起发送数据包结束标志(EOP)的能力有关。当需要通过数据流发送数据段或数据包时,此功能非常有用。关于 EOP 的更多信息,请参阅 Xillyp2p 端口与 API 指南。
关于数据交换的几点说明
出于测试目的,将来自 core B 的数据环回给它自身可能很有用。换句话说,将“demo32_to_a”接收到的数据直接路由到“demo32_to_b”。这可以通过例化一个 FIFO 而不是两个来实现,如下所示:
fifo_32 loopbackfifo
(
.rst(async_reset),
.wr_clk(tx_clk),
.rd_clk(tx_clk),
.din(user_rx_demo32_to_a_wr_data),
.wr_en(user_rx_demo32_to_a_wr_en),
.full(user_rx_demo32_to_a_full),
.rd_en(user_tx_demo32_to_b_rd_en),
.dout(user_tx_demo32_to_b_rd_data),
.empty(user_tx_demo32_to_b_empty)
);
assign user_tx_demo32_to_b_eop = 0;
再次注意,FIFO 两侧都使用 tx_clk。
与“demo_256”的接口以相同方式处理,使用一个数据字宽为 256 位的 FIFO。请注意,此数据流使用 256 位宽的数据字,这比物理链路的 32 位并行字宽得多。而且,该数据流的最大理论数据速率为 256 * 156.25 MHz / 8 = 5000 MB/s,远高于物理链路约 606 MB/s 的容量。
尽管如此,如果应用程序的自然数据格式由 256 位字组成,那么使用 256 位宽的数据流是合适的,即使它不提供带宽优势。这种选择确保每个字以正确的格式和对齐方式在接收端交付。
LED 指示灯与诊断信号
KCU105 板上有 8 个 GPIO LED。Xillyp2p IP 核有几个输出可以连接到 LED,用于指示链路状态,详见本指南。
推荐的设置如下:
reg [26:0] txclk_cnt, initclk_cnt;
always @(posedge init_clk)
initclk_cnt <= initclk_cnt + 1;
always @(posedge tx_clk)
txclk_cnt <= txclk_cnt + 1;
assign gpio_led[1:0] = { txclk_cnt[26], initclk_cnt[26] };
ledhelper ledhelper_ins[5:0]
(
.clk(tx_clk),
.in( {
status_link_down,
status_link_partner_mismatch,
status_initializing,
status_bit_error,
status_debug[2],
status_debug[0] } ),
.led(gpio_led[7:2])
);
对于不太熟悉 Verilog 的用户,ledhelper 的例化重复了六次,每次(分别)对应“in”输入的一位及其相应的“led”输出。
从上可见,GPIO LED 0 和 1 是简单的心跳信号,通过闪烁指示 init_clk 和 tx_clk 处于活动状态。
其余六个 LED 显示来自 Xillyp2p IP 核的六个状态信号。ledhelper 模块确保 LED 保持亮起或熄灭足够长时间,以便人眼感知短至单个时钟周期的事件。例如,如果物理链路上检测到错误,status_bit_error 会在一个时钟周期内为高电平。如果没有 ledhelper,这样短暂的事件将是不可见的。
ledhelper 模块定义如下:
module ledhelper(
input clk,
input in,
output reg led
);
// 22 bits = 4194304 counts, ~26.8 ms at 156.25 MHz clock
reg [21:0] count;
always @(posedge clk)
if (count != 0)
count <= count - 1;
else if (in != led)
begin
led <= in;
count <= ~0;
end
endmodule
该模块在每次变化后的 222 个时钟周期内冻结“led”输出。因此,如果输入在一个时钟周期内为高电平,LED 将保持点亮约 26.8 毫秒,这足以被人眼感知。此外,如果输入快速变化,LED 会剧烈闪烁,人眼会正确将其解释为高活动性。
当有 8 个 LED 可用时,这种方法很合适。如果可用的 LED 较少,status_bit_error 通常是最有用的指示,其次是 status_link_partner_mismatch、status_link_down 和 status_debug[2]。同时也建议至少有一个心跳 LED。
XDC 约束
为了完整性,展示并解释 XDC 文件中的约束。请记住,使用的板卡是 KCU105。
首先,与收发器直接相关的约束:
create_clock -name refclk -period 6.4 [get_ports gth_refclk_n]
set_property PACKAGE_PIN P6 [get_ports gth_refclk_p];
set_property PACKAGE_PIN P5 [get_ports gth_refclk_n];
set_property LOC GTHE3_CHANNEL_X0Y10 \
[get_cells -hier -filter { ref_name =~ GT*_CHANNEL }]
参考时钟周期设为 6.4 ns,对应 156.25 MHz,与之前的讨论一致。注意,rx_clk 和 tx_clk 也具有相同频率纯属巧合。
然后明确放置参考时钟的引脚,覆盖向导中的定义。
之后,为收发器显式选择 GTH,再次覆盖向导中的选择。注意,用于查找 GTH 的表达式匹配设计中的任何 GTH;这之所以有效,是因为整个设计中只有一个 GTH。因此,如果存在多个 GTH,则需要更具体的约束,但这样的约束可能无法在不同版本的 Vivado 中一致工作。
接下来是自由运行时钟的约束:
create_clock -name initclk -period 8 [get_ports init_clk_n]
set_property -dict { PACKAGE_PIN F10 IOSTANDARD LVDS } \
[get_ports "init_clk_n"];
set_property -dict { PACKAGE_PIN G10 IOSTANDARD LVDS } \
[get_ports "init_clk_p"];
自由运行时钟周期设为 8 ns,即 125 MHz。请注意,如果更改此时钟频率,则必须在 Transceiver Wizard 中更新该设置。
一旦两个时钟都被定义和约束,需要告知 Vivado 逻辑设计将 init_clk、rx_clk 和 tx_clk 视为不相关时钟,从而将所有跨越这些时钟域的路径设为伪路径:
set_clock_groups -asynchronous \
-group [get_clocks -include_generated_clocks -of_objects \
[get_ports init_clk_n]] \
-group [get_clocks -of_objects [ get_nets rx_clk ] ] \
-group [get_clocks -of_objects [ get_nets tx_clk ] ]
如果没有这一步,设计可能无法满足时序约束。
最后是 GPIO LED 和 sfp_tx_enable(琐碎部分):
set_property PACKAGE_PIN AP8 [get_ports "gpio_led[0]"];
set_property PACKAGE_PIN H23 [get_ports "gpio_led[1]"];
set_property PACKAGE_PIN P20 [get_ports "gpio_led[2]"];
set_property PACKAGE_PIN P21 [get_ports "gpio_led[3]"];
set_property PACKAGE_PIN N22 [get_ports "gpio_led[4]"];
set_property PACKAGE_PIN M22 [get_ports "gpio_led[5]"];
set_property PACKAGE_PIN R23 [get_ports "gpio_led[6]"];
set_property PACKAGE_PIN P23 [get_ports "gpio_led[7]"];
set_property IOSTANDARD LVCMOS18 [get_ports "gpio_led[*]"]
set_false_path -to [get_ports "gpio_led[*]"]
set_property -dict {PACKAGE_PIN AL8 IOSTANDARD LVCMOS18} \
[get_ports sfp_tx_enable];
结论
本指南提供了一个完整的示例,展示了如何在 AMD(原 Xilinx)UltraScale FPGA 上配置多吉比特收发器(GTH)并将其连接到 Xillyp2p IP 核。从 Vivado 中的 Transceiver Wizard 配置开始,经过时钟、FIFO 和应用程序逻辑的例化,到诊断 LED 的连线以及 XDC 约束的定义,每一步都进行了详细说明。
本示例既可作为使用 UltraScale GTH 收发器和 Xillyp2p IP 核的实用参考,也可作为学习工具。
请回顾上文,关于为 7 系列 FPGA(Kintex-7、Artix-7、Virtex-7 和 Zynq-7000)配置收发器的示例,请参阅另一份指南。





