☰
VIVADO 2019.1下FPGA以太网接口搭建全流程与踩坑记录
2026/10/5 12:14:04 网站建设 项目流程

板子拿回来第一天我就想直接把网口调通,结果在VIVADO 2019.1里折腾了好几天,才从零把一个以太网接口跑起来。回想整个过程,最熬人的不是写逻辑,而是那些“看起来没问题但就是不工作”的硬件细节。这篇文章把我在VIVADO 2019.1上搭建以太网接口的完整过程、踩坑记录和排查思路写出来,给正准备做同样事情的你一个参考。

VIVADO 2019.1目前是很多中低端FPGA开发板的主力版本,像Artix-7系列的板子,官方教程大多用这个版本。以太网接口在FPGA项目里属于“看着简单、做起来全是坑”的部分。它涉及的链路很长:PHY芯片、MAC逻辑、DMA数据通路、CPU协议栈,每一层都可能成为瓶颈。

1. 方案选型:在VIVADO里搭以太网接口,你到底需要什么

1.1 先分清自己是哪条路:Zynq硬核还是纯FPGA软核方案

开始之前,先问自己一个最关键的问题:你的芯片上有没有以太网硬核MAC。

如果你用的是Zynq-7000,事情会简单很多。Zynq的PS侧自带千兆以太网MAC,也就是常说的GEM(Gigabit Ethernet MAC)。你要做的只是把PS的MIO或EMIO引脚连到外部的PHY芯片上,然后在SDK里配置BSP,根本不需要在PL侧搭建以太网IP核。很多网友把Zynq和纯FPGA的方案混在一起,看到别人的AXI Ethernet教程就去抄,结果自己的工程里根本不需要这个东西。

如果你是纯FPGA,比如Artix-7或者Kintex-7,情况完全不同。芯片内部没有硬核MAC,必须用PL侧逻辑实现。VIVADO里有两个现成IP可以用:AXI Ethernet和AXI Ethernet Lite。对于绝大多数项目,选择AXI Ethernet,这就是你的MAC层。

在做技术选型时,还要看一下板子的PHY芯片型号和接口。比如常见的88E1512、RTL8211、YT8531,这些PHY都支持RGMII。如果你的板子用的是GMII接口的老PHY,或者SGMII接口的PHY,就要相应的选择对应的PHY Interface配置项。

还有一点需要提前确认:VIVADO 2019.1的IP核支持情况。同一个IP核在不同版本里的默认配置和Bug修复都不一样,如果你用的板子需要在2019.1上开发,建议先查一下Xilinx官方文档中AXI Ethernet IP核在这个版本的支持状态。我个人用的2019.1版本跑Artix-7的RGMII是完全没有问题的,但如果是UltraScale+的部分新器件,建议换到更新的版本来做。

1.2 AXI Ethernet和AXI Ethernet Lite怎么选

打开VIVADO 2019.1的IP Catalog,搜索“ethernet”,你会看到两个关键词:AXI Ethernet和AXI Ethernet Lite。

AXI Ethernet Lite是一个简化版实现,特点是资源占用低,逻辑结构简单,但数据通路是AXI4-Lite,CPU需要逐个寄存器读写,效率非常低。它适合什么场景呢?比如你只是需要一个远程调试口,跑一点点控制数据,或者做教学演示,那它够用。但你只要想跑TCP/IP协议栈、收发视频流、传UART透传数据,Lite版本马上变成瓶颈。

AXI Ethernet才是完整版本。它支持AXI4-Stream数据接口,还能在IP核内部集成DMA控制器,也就是Scatter Gather模式。数据可以自动从内存搬运到MAC,不需要CPU干预,性能高得多。

我在选型时的判断标准很简单:只要板子上PHY能跑千兆,就直接用AXI Ethernet。不要考虑Lite,因为后续一旦性能不够,迁移完整版的工作量比一开始直接用大得多。这个道理跟写代码一样,先用最简单的方式跑通,再考虑优化的思路,在这里并不成立。

1.3 接口MAC/PHY分工:GMII、RGMII和SGMII的区别

AXI Ethernet配置界面里会要求你选择PHY Interface。这里不是让你选板载PHY芯片的品牌,而是选MAC和PHY之间的物理接口类型。

GMII是经典的并行接口。发送和接收都用8位数据线,加上控制信号和时钟,需要20多根线。它每个时钟周期采8位,125MHz时就是千兆。缺点是引脚太多,布线麻烦,现在新板子已经很少用GMII了。

RGMII是GMII的简化版,数据线减半,4根发送、4根接收,利用时钟的双沿采样来保住同样的带宽。RGMII最大的好处是引脚少,很多PHY芯片都支持,是当前最主流的MAC-PHY连接方式。对应的时钟在千兆时是125MHz。

SGMII是串行接口,数据通过一对差分线跑,引脚最少,常用在背板、交换机这种需要高速走线的场景。SGMII通常要借助FPGA的高速收发器(比如Artix-7上的GTP),配置复杂度比RGMII高一个级别,功耗和资源占用也更大。

如果你只是做一块普通的开发板或者嵌入式控制板,RGMII是性价比最高的选择。2019.1版本的AXI Ethernet核在配置页面对RGMII支持得很好,下面我会把具体的参数解释一下。

2. 工程准备与IP核配置:从空工程到AXI Ethernet跑起来的几个关键动作

2.1 新建工程时的版本选择、安装路径和许可证问题

VIVADO 2019.1这个版本有个特点:它对7系列的支持非常稳定,但安装时不能图省事装精简版,否则很多IP核用不了。安装时要勾选对应的器件族,比如Artix-7就勾Artix-7,Zynq就勾Zynq-7000。否则你创建工程时找不到目标器件,就算找到了,IP Catalog里也没有AXI Ethernet核。

安装路径不能有中文和空格,这个问题老生常谈了,但还是有人踩。装好之后许可证要单独处理,Xilinx的license就是那个.lic文件,在Help -> License Manager里导入。如果你的license不包含对应器件或IP的授权,生成比特流或仿真时会报2035类似的错误。很多网友遇到的“vivado注册 2035”,其实就是License验证失败的典型报错,卡住的人非常多。解决办法要么换一个完整的license,要么把不需要的IP从工程里禁用掉。

新建工程时,器件型号一定要选对。以Arty A7为例,型号是xc7a35tcsg324-1。这里面csg324是封装,-1是速度等级,差一个字母后期约束全是问题。选错了器件,管脚名和时序模型全对不上,工程等于白建。

2.2 添加AXI Ethernet IP核时,哪些参数必须核对

双击IP Catalog里的AXI Ethernet,进入配置界面,我建议按下面这几项逐一看:

  • DMA Type:这个选项决定是否在IP核内部集成DMA。建议选Scatter Gather,这样CPU下发数据时只需要维护描述符,IP核自动搬运数据到内存,效率高很多。如果选None,后面就得自己在Block Design里接一个AXI DMA,多一层连线就多一层出错的可能。
  • PHY Interface:按板子的实际接口选。我的是RGMII,就选RGMII。
  • Reference Clock Frequency:填125。这里指的是IP核内部逻辑使用的参考时钟,不是PHY芯片的晶振,注意别混淆。
  • Enable MDIO:勾上。MDIO是用来读写PHY寄存器的,链路状态、速率协商都要靠它。
  • Number of Channels:一般选1个就够用。多通道是给特殊场景的,比如网口备份和聚合。

配置完这些,点击OK,生成IP核。你会在Block Design里看到一堆端口,这个时候先别急着连,先想清楚你的数据通路是怎么走的。

2.3 时钟的来龙去脉:125MHz为什么这么关键

以太网最核心的时钟就是125MHz。千兆RGMII需要125MHz的TX时钟,百兆是25MHz,十兆是2.5MHz。AXI Ethernet核会根据当前协商速率自动调整内部逻辑,但外部必须给它提供正确的基准时钟。

最可靠的方案是:板载晶振(比如50MHz或100MHz)进Clock Wizard,通过MMCM/PLL生成125MHz和25MHz。125MHz提供给AXI Ethernet的GTX_CLK,25MHz可以接到PHY的时钟输入或者给MAC做百兆参考时钟。

有一个坑我印象特别深:单独给AXI Ethernet的gtx_clk连了时钟,却忘了给ref_clk连时钟。综合能过,实现也能过,但上板之后网口完全不通。后来用VIVADO的report_clocks查了下,发现ref_clk根本没有时钟源,IP核内部的逻辑全在空跑。

在XDC里,外部输入时钟要显式约束。比如板子上的系统时钟是100MHz,那么至少要写:

create_clock -period 10.000 -name system_clk [get_ports clk_100m]

如果不创建,VIVADO会默认给你推导,但推导出来的时钟网络经常不是你想要的那个,后面的时序报告也会误导你。而像rx_clk这样的PHY输出时钟,也要记得约束,否则ILA采集时会出现奇怪的采样结果。

3. 连线、地址与约束:Block Design里最容易翻车的地带

3.1 带内置DMA的AXI Ethernet如何和MicroBlaze互联

我推荐把DMA Type选成Scatter Gather,所以Block Design里不需要单独加AXI DMA。这时,AXI Ethernet对外暴露的主要接口包括:

  • S_AXI:管理寄存器接口,供MicroBlaze配置MAC和DMA寄存器。
  • AXI_MM2S和AXI_S2MM:这是内部DMA读写内存的接口,连到axi_interconnect,最后接DDR控制器。
  • AXI_Stream接口:内部已经和MAC连好,外部不可见。
  • TX_INTR和RX_INTR:DMA发送和接收的中断输出。
  • PHY接口:RGMII引脚、MDIO引脚。

MicroBlaze这边,M_AXI_DC和M_AXI_IC接axi_interconnect的S_AXI侧即可。然后,axi_interconnect的M_AXI分别连到AXI Ethernet的S_AXI和它的MM2S/S2MM接口。

如果你用的是DDR3/DDR4内存,别忘了在Block Design里加一个MIG(Memory Interface Generator)或者直接加一个AXI DDR控制器,把DDR的地址空间映射出来。否则MicroBlaze能运行程序但没内存,lwIP一分配报文缓冲区就崩。

一个常见的地址映射错误:给AXI Ethernet的寄存器地址分配了和DDR重叠的地址段,结果软件访问寄存器时实际访问到了DDR,读出来的数据自然不对。Block Design的Address Editor里要仔细检查,所有外设的地址段必须唯一,不能重叠。

3.2 中断信号怎么连,优先级怎么处理

只要用了Scatter Gather模式,以太网的收发就依赖中断。TX_INTR和RX_INTR这两个中断信号,要连接到中断控制器axi_intc。

连接步骤:

  • 添加axi_intc IP核,配置为单通道或双通道均可。
  • 把TX_INTR和RX_INTR分别连到axi_intc的intr端口,注意intr是多bit的INTC输入,每个bit对应一个中断源。
  • 把axi_intc的irq连到MicroBlaze的INTERRUPT输入。
  • 在SDK中,BSP会自动生成xintc的驱动。应用里需要给每个中断源注册回调函数。

优先级分配一般不用太纠结,但要注意:如果多个中断源共用一个axi_intc,默认情况下同一个intc产生的中断优先级是可以通过寄存器配置的,但在小系统里,只要保证不丢中断就行。我的经验是,接收中断(RX_INTR)的优先级给高一点,发送中断可以略低,这样接收侧不容易丢包。

3.3 XDC管脚约束里那些“少写一行就废了”的约束

Block Design连线完成后,需要Create HDL Wrapper生成顶层,然后新建XDC文件做管脚约束。

RGMII接口的引脚约束比较直观,比如:

set_property -dict {PACKAGE_PIN R2 IOSTANDARD LVCMOS33} [get_ports rgmii_txd[0]] set_property -dict {PACKAGE_PIN T3 IOSTANDARD LVCMOS33} [get_ports rgmii_tx_ctl]

但要额外注意以下几点:

  1. rx_clk是PHY输出给FPGA的,必须声明为时钟:
create_clock -period 8.000 -name rgmii_rxc [get_ports rgmii_rxclk]
  1. PHY复位引脚,大部分PHY复位是低有效,所以FPGA侧要把它拉低才是正常。有些人写成拉高,结果PHY一直处于复位状态,网口灯不亮,数据完全出不去。

  2. MDIO的MDC和MDIO引脚要加合适的IO标准,MDIO的信号质量要求没那么苛刻,但MDC是时钟,最好也加上create_clock约束,让VIVADO知道它是时钟。

  3. 如果板子上PHY芯片有独立的LED引脚,也要约束,否则PHY的link状态灯不亮,你根本不知道线的状态。

约束写完,直接跑综合和实现。生成比特流失败是最常见的现象之一,绝大多数失败原因是时序收敛不过去。我都建议先点开Report Timing Summary,看是哪个时钟域timing violation,90%的情况都是某个外部时钟没有约束,或者约束的周期不对。

4. 软件侧:SDK、BSP和lwIP的配置才是真正的分水岭

4.1 导出硬件到SDK时容易忽略的选项

硬件工程跑了Generate Bitstream后,下一步是File -> Export Hardware,注意勾选Include bitstream。如果不勾选,SDK里没有比特流,下载程序时会提示器件没有被配置。

打开SDK后,新建Application Project。Board Support Package的创建向导会让你选择OS和库,这里务必在库列表里勾选lwip和xilffs(如果需要文件系统)。BSP生成后,右键BSP目录选Board Support Package Settings,可以修改lwIP的配置。

在SDK版本匹配方面,2019.1自带的SDK对应lwIP有v1.4.1和v2.0.x两个选项。我建议选v2.0.x。v1.4.1虽然老,资料多,但API有些地方和新的lwIP不兼容,尤其是RAW API的注册方式。

这里插一句:有些朋友在PC上遇到过“续订接口以太网时出错无法联系DHCP服务器请求超时”,那个问题多半是Windows网络栈或者路由器的问题,跟FPGA这边自己搭的DHCP客户端完全不是一回事。在板子上跑DHCP时,你更该关心的是PC端抓包能不能看到DHCP Discover报文。

4.2 lwIP的缓冲区配置直接影响性能和稳定性

BSP里的lwIP配置项很多,这里只挑三个我觉得最关键的说:

  • MEM_SIZE:lwIP堆内存总大小。默认值可能只有几十KB,如果跑TCP大数据,很容易内存不足。建议调到150KB以上,纯FPGA的MicroBlaze一般有足够BRAM。
  • PBUF_POOL_SIZE:报文缓冲区数量。这个直接决定你在高负载下能缓存多少包。收包速度大于CPU处理速度时,池子小了就会丢包。建议调到32以上。
  • PBUF_POOL_BUFSIZE:单个缓冲区大小。至少要能装下一个最大以太网帧,也就是1518字节,最好留点余量,设成1600。

除了这三个,还有MEMP_NUM_TCP_PCB、MEMP_NUM_UDP_PCB这些协议控制块数量,如果你要同时开15个TCP连接,默认的数量不够,需要调大。

改完这些参数后,重新生成BSP,再编译应用工程。这里有一个坑:BSP配置改动后,应用工程的库文件路径和宏定义会自动更新,但有时候编译不会触发重新编译,你会看到旧版本的配置还在生效。解决办法是Clean一下工程,Project -> Clean,再重新Build。

4.3 最小可跑的lwIP代码:从PHY初始化到UDP echo

在SDK里,BSP会自动生成lwIP和以太网驱动的基础框架。你只需要在main函数里完成几件事:

  • 初始化以太网MAC:调用对应驱动,设置PHY地址。PHY地址一般在硬件原理图上标注,比如很多PHY的地址是0x00或0x04。
  • 设置MAC地址、IP地址、子网掩码。
  • 启动网络接口,进入lwIP并存。
  • 注册UDP echo回调。

以RAW API为例,伪代码大概是:

#include "lwip/init.h" #include "lwip/udp.h" #include "netif/xadapter.h" static struct netif netif; static void udp_echo_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) { udp_sendto(pcb, p, addr, port); pbuf_free(p); } int main(void) { lwip_init(); IP4_ADDR(&ipaddr, 192, 168, 1, 10); IP4_ADDR(&netmask, 255, 255, 255, 0); IP4_ADDR(&gw, 192, 168, 1, 1); netif_add(&netif, &ipaddr, &netmask, &gw, NULL, ethernetif_init, ethernet_input); netif_set_default(&netif); netif_set_up(&netif); struct udp_pcb *pcb = udp_new(); udp_bind(pcb, IP_ADDR_ANY, 5001); udp_recv(pcb, udp_echo_callback, NULL); while (1) { ethernetif_poll(); } return 0; }

这段代码里,ethernetif_poll的调用频率是关键。如果你的BSP配了中断驱动,那么不需要轮询;如果是轮询模式,主循环要尽可能多的调用,否则吞吐上不去。

我最开始照抄网上Zynq例程,结果那个例程里用的是emacps驱动,和纯FPGA的AXI Ethernet驱动(xemac)完全不一样,编译报错一堆。这里提醒一下:纯FPGA项目千万不要照抄Zynq的PS侧例程,要找AXI Ethernet对应的例程。

5. 实测阶段:从“网口灯亮”到“ping通”、再到“大包不丢”

5.1 ILA抓信号:看不到波形之前别乱猜

在没有逻辑分析仪的情况下,ILA是你最好的朋友。在Vivado 2019.1里,ILA核可以直接插入到Block Design里,也可以插入到RTL的任意信号处。

我的做法是:在AXI Ethernet的PHY接口上插一组观察点,重点观察:

  • rgmii_tx_ctl / rgmii_txd[3:0]
  • rgmii_rx_ctl / rgmii_rxd[3:0]
  • gtxx_clk

触发条件可以设置为rgmii_tx_ctl==1,这样只要FPGA发出数据,ILA就会抓到。采样深度建议1024或2048,因为一个以太网帧最短也有64字节,512深度的ILA可能只抓到半个帧,分析起来很被动。

如果你发现ILA触发不了,说明MAC根本没尝试发送数据,问题多半在DMA或者软件侧。如果触发了但波形里全是0或者不变,那就要往时钟和复位的方向查。

ILA的采样频率不是越高越好。对于125MHz的RGMII时钟,ILA用250MHz采样能看清双沿,但如果你用500MHz,数据可能会因为采样对齐问题出现毛刺。尽量让ILA的采样时钟和被测时钟同源,这样抓出来的波形才真实。

5.2 ping不通的排查链路:从底层往上层一层层排除

ping不通的时候,我一般按这个顺序排查,能省很多时间:

第一步:看PHY的link灯。灯不亮,那就是物理层问题。先查复位信号、时钟、电源。 第二步:用串口或SDK读PHY寄存器。读寄存器0和1,bit状态能看出速率和link状态。如果读出来全是0xFF或全0,说明MDIO根本没通,查MDC/MDIO引脚连接和PHY地址。 第三步:PC端Wireshark抓包。如果PC发ARP请求,板子没有回复,说明问题在MAC或DMA。如果板子回复了ARP,那二层是通的,继续查IP层和ICMP。 第四步:确认板子的IP、MAC没有被重复配置。如果板子和PC在同一个网段,但板子MAC地址和虚拟机的某个虚拟网卡冲突,ARP就会出问题导致ping不通。

遇到“ping第一次通第二次不通”,多数是lwIP的ARP表老化或者DMA描述符没释放。把PBUF_POOL_SIZE和描述符数量调大之后,这种问题会明显缓解。

5.3 大包传输失败和吞吐不达标的调优记录

我有一次调UDP传输,PC发1500字节的包,板子收不全,总是在后半段丢数据。排查了半天,最后发现是PBUF_POOL_BUFSIZE设置太小,只有800字节,大于这个值的包分片后直接被lwIP丢弃。

解决办法就是前面说的,把缓冲区至少调到1600字节。另外如果PHY支持Jumbo Frame,而板子需要传超大帧,那还要同步调大IP核的FIFO深度和DMA描述符的大小。

吞吐不达标的问题更常见。我用裸机+lwIP跑UDP,刚开始吞吐只有几Mbps,后来做了几个优化:

  • 把MicroBlaze的主频从100MHz提高到150MHz。
  • 开启MicroBlaze的指令缓存和数据缓存,注意数据缓存要和DDR地址空间打通。
  • 把DMA描述符数量从默认的16增加到64。
  • 把lwIP的TCP窗口调大,TCP_SND_BUF和TCP_WND都加大。

优化后,UDP单向吞吐能跑到700Mbps左右(接近千兆线速),TCP也有明显提升。这个数字在Zynq或者带硬核MAC的平台上可能不稀奇,但在纯FPGA软核方案里已经是不错的成绩了。

最后说一点我在实际项目里的个人体会。VIVADO 2019.1搭建以太网接口,难的不是某个IP核配置,而是整条链路太长,每个环节都有可能是坑。遇到问题一定要用ILA和串口日志把问题边界缩小,不要上来就猜代码。XDC里所有外部输入时钟都必须主动约束,这个习惯能让你在后面少走非常多的弯路。这些经验是我在Artix-7平台上踩出来的,换一块板子、换一颗PHY芯片,步骤基本类似,但引脚、PHY地址、时钟频率这些细节还是要以你手上的板子为准。祝大家一次点亮网口。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询