ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

FPGA变身IPv6千兆以太网服务器:MicroBlaze+lwIP实战

FPGA变身IPv6千兆以太网服务器:MicroBlaze+lwIP实战 手头这块米联客MA703FA开发板吃灰了挺久最近翻出来想让它干点正经活。研究了一圈发现最实用的玩法就是把它做成一个带IPv6能力的千兆以太网服务器。用MicroBlaze软核跑一个轻量级HTTP服务把开发板变成一个有IP地址、能上网、能远程交互的嵌入式小主机这件事本身的性价比极高——不用外挂Linux不用买昂贵的SoC一块Artix-7级别的FPGA就能搞定。这篇东西不是那种官方文档翻译是我自己从建工程到调通IPv6的完整记录。MA703FA、MicroBlaze、千兆以太网、IPv6这些关键词背后其实是FPGA工程师从逻辑设计走向嵌入式系统联调的一条很典型的路。不管你是想学MicroBlaze还是想把板卡上的网口真正用起来或者是被“FPGA怎么上网”这个问题卡住这篇文章都能给你一份可以照抄的作业。1. 项目全景与方案选型1.1 为什么用MicroBlaze加lwIP而不是硬核ARMFPGA上做网络通信有几种常见路线各有各的适用场景。最粗暴的是用硬核处理器配Linux比如Zynq系列性能强、生态全但需要跑系统、配启动、移植驱动工程量大。相对轻量的是用MicroBlaze这样的软核处理器搭配Xilinx官方的lwIP库在FPGA内部把CPU、以太网MAC、DMA控制器、内存控制器全部搭起来整个系统就是一个可综合的IP核集合适合中低速率、低延迟、确定性强的嵌入式网络应用。MA703FA这款板卡用的是Artix-7系列FPGA本身没有硬核ARM但逻辑资源足够跑一个带Cache的MicroBlaze再加上一个AXI Ethernet模块和AXI DMA完全能撑起千兆线速收发。选择这条路的直接好处是不依赖操作系统不依赖外部CPU整个协议栈和用户代码都在FPGA内部运行非常适合做协议转换、远程采集、工业控制这类数据量不大但对实时性和可控性要求较高的场景。当然也要说清楚它不适合什么。如果你要跑复杂的加密套件、高并发Web服务、海量连接管理MicroBlaze加lwIP的方案会显得吃力。它适合的是“把设备接入网络并完成特定功能”这种任务而不是“用FPGA当通用服务器”。1.2 硬件资源梳理与准备工作搭建这套系统前我把MA703FA的板载资源核对了一遍。Artix-7系列FPGA、板载DDR3、千兆以太网PHY支持RGMII接口、UART转USB、LED、按键、JTAG下载调试这些基础资源已经足够覆盖整个项目的全部需求。实际工程中DDR3用作MicroBlaze的运行内存和DMA缓冲区千兆PHY经RGMII接到FPGA内部的AXI Ethernet IPUART则作为调试串口输出lwIP的调试信息。软件工具链方面我使用的是Vivado 2018.3加SDK。很多新手纠结版本问题其实Xilinx工具链的版本跟随性很强如果你的工程是从旧版本迁移过来的建议保持同一版本避免IP核升级带来的兼容性麻烦。项目开始前还需要准备一根网线、一根USB线调试串口与JTAG共用、一个路由器或者直接连电脑网口。如果手头有逻辑分析仪更好排查PHY初始化问题时能省不少时间。注意MA703FA的不同批次可能板载PHY型号不同比如有的用RTL8211系列有的用Marvell的PHY。不同PHY的寄存器配置差异很大强烈建议先查清楚自己板卡上的PHY型号再对应修改Vivado IP配置里的PHY地址和连接方式。2. Vivado工程搭建与MicroBlaze子系统设计2.1 Block Design核心模块配置打开Vivado新建工程选择MA703FA对应的FPGA型号。创建Block Design之后主体思路是围绕MicroBlaze处理器搭建一个AXI互联的SoC系统。如下图所示文字版核心模块包括MicroBlaze、DDR3控制器MIG、AXI Ethernet、AXI DMA、UART、GPIO、中断控制器和定时器这些都是Xilinx IP目录里开箱即用的东西。MicroBlaze本身配置为带MMU的版本更好因为后面启用lwIP需要虚拟内存映射虽然MMU会消耗更多LUT资源但对于跑网络协议栈来说很值。时钟方面我用一个100MHz的差分时钟作为系统输入经过MMCM分频后给MicroBlaze、AXI总线和Ethernet模块提供各自的时钟。DDR3的时钟和地址时序由MIG IP自动生成只需要在配置里选对板卡对应的DDR3颗粒型号和位宽。AXI Ethernet IP是整套系统的核心需要开启RGMII接口、加入DMA通道。我习惯在IP配置里打开“Support for optional input FIFO”这样接收方向会有更好的缓冲能力避免短时突发流量把数据包丢了。AXI DMA配置为支持Scatter Gather模式有了SG模式后DMA可以维护一个描述符链表多个数据包不用CPU逐个搬运对提升吞吐量帮助很大。2.2 地址映射与DMA缓冲区规划地址空间的划分是一个容易被低估的细节。MicroBlaze是32位地址空间DDR3基地址一般分配在0x80000000Ethernet相关的AXI DMA寄存器映射在0x40000000附近UART、GPIO等外设放到低地址段。这样做的好处是按照“内存、外设、配置”三个区间管理后续在SDK里写链接脚本的时候非常直观。DMA缓冲区的规划也要提前想清楚TX/RX缓冲区在DDR3里需要对齐到字节边界通常要求至少4字节对齐实际操作建议按32字节对齐这样能避免不少缓存一致性的坑。每个描述符里包含缓冲区地址、长度、控制标志和状态字接收描述符环和发送描述符环分别维护独立的头和尾指针。lwIP在底层会通过Xilinx的xemacps_if_dma.c或类似驱动来维护这些描述符但理解原理能帮你在出现异常时快速定位。我实际分配的是DDR3从0x80000000开始前0x8000008MB给lwIP的内存池和PBUF0x80800000到0x81000000之间给DMA描述符和缓冲区再往上是运行时堆和栈。这块配置不是死的可以根据实际内存大小调整但建议留足余量。2.3 生成Bitstream与导出硬件完成Block Design后先在Address Editor里确认所有外设地址没有冲突然后右键Design Sources点击Generate Output Products等待IP核的仿真和综合文件生成完毕。接着运行综合、布局布线生成Bitstream。这个过程在Artix-7上一般几分钟到十几分钟取决于机器配置。Bitstream生成后需要导出硬件文件给SDK使用。在Vivado菜单里依次点击File - Export Hardware勾选Include Bitstream然后Launch SDK。这里有个小坑如果在SDK里改动了MicroBlaze的BSP配置重新编译不会自动更新硬件此时需要在Vivado里重新Export并Launch或者通过硬件平台导入的方式刷新。我一开始踩过这个坑改完BSP发现板子跑起来还是老逻辑后来才想起来硬件描述文件也要同步更新。提示如果只是修改软件代码不需要重新生成Bitstream但如果修改了Block Design里的任何IP配置或者调整了地址映射就一定要走一遍Generate Output Products、综合、布局布线、生成Bitstream的完整流程否则SDK中看到的硬件描述和实际FPGA加载的镜像会不一致这个不一致会在运行期以莫名其妙的方式暴露出来。3. SDK软件工程lwIP协议栈初始化与HTTP服务器3.1 创建BSP并启用lwIP在SDK中新建一个Application ProjectBoard Support Package选择standalone版本要和Vivado的IP版本配套。BSP创建完成后在system.mss文件里找到lwIP库点击管理库版本确认lwIP已经被添加进来。lwIP在Xilinx的BSP里是一个可选库默认不一定启用必须在BSP设置里把lwip141选项勾上同时取消勾选默认版本之外的其他网络库避免冲突。这个步骤看起来简单但很多人的板子ping不通问题就出在BSP里网络库配置和实际硬件不匹配。AXI Ethernet在lwIP驱动层的接口是通过xemacps和xaxiethernet两个模块协同工作的你要在BSP的config settings中对应地使能Xilinx AXI Ethernet driver并且设置PHY link speed为auto negotiation或者固定为1000 Mbps。我还建议把lwIP的调试输出等级调高一些比如LWIP_DEBUG1这样在初始化阶段能看到MAC地址、IP地址分配信息和PHY协商状态对定位问题非常有帮助。3.2 网络接口初始化与中断处理lwIP层面Xilinx官方提供了一份基于RAW API的示例文件名叫lwIP echo server或者类似的模板。这份模板的核心逻辑不算复杂初始化系统时钟、中断控制器、以太网DMA、PHY然后调用lwIP的netif_add添加网络接口设置默认网关和子网掩码最后进入while(1)死循环轮询。用RAW API是因为它不依赖操作系统在standalone裸机环境下就能跑得动如果以后迁移到FreeRTOS或者Xilinx的RTOS上RAW API的代码也可以直接复用。中断处理是整个系统稳定性的关键。MicroBlaze的外部中断控制器收集AXI Ethernet和AXI DMA的中断信号在中断服务程序里调用lwIP驱动的接收回调。这个回调不能做太多事情正确做法是只负责把收到的数据包从DMA描述符里取出来提交给tcpip_input或者直接交给RAW API的接收函数处理逻辑放在主循环或协议栈内部。我的做法是接收回调里只做一件事置一个标志位主循环检测到标志位后再去调netif-input这样可以避免在中断上下文里调用耗时函数带来的不确定延迟。3.3 HTTP服务器代码实现使用lwIP的RAW API写HTTP服务器本质上就是注册一个TCP监听端口80然后在accept回调里创建连接结构之后的接收回调里解析HTTP请求行和头字段最后在poll或send回调里发送响应内容。网上很多示例只处理GET /返回一个“Hello World”实际项目里可以做得稍微丰富一点把FPGA的温度传感器、计数器、LED状态这些信息通过网页展示出来。我在工程里加了一个简单的动态页面请求路径带参数就控制LED开关。这里涉及一个性能问题lwIP的发送缓冲区是有限大小的如果要返回的HTML字符串较大必须分段发送配合tcp_write和tcp_output的分批调用。最稳妥的做法是先把完整响应内容放到本地静态缓冲区然后设置一个起始偏移指针每次tcp_sndbuf查询可用空间能塞多少就写多少等上次数据发送完成、收到TCP ACK后再继续发送下一段直到整个响应发送完毕。这样实现虽然代码量多一点但避免了tcp_write返回ERR_MEM导致的数据丢失。看了最新的热门提问“microblaze以太网测试”和“2018.3如何microblaze mmi bit elf文件烧写msc”这类问题我推测不少人在这一步卡住。我的经验是如果你发现HTTP响应经常残缺优先确认两个东西一是DMA的接收FIFO是否溢出二是TCP窗口是否因为内存池太小被降得很低。把lwIP的MEM_SIZE和PBUF_POOL_SIZE适当调大能明显改善大页面的传输稳定性。4. IPv6支持的配置与验证4.1 lwIP的IPv6使能开关IPv6已经不是新东西了但在嵌入式FPGA生态里支持IPv6的示例少得可怜。Xilinx lwIP库从2017.4版本开始其实已经集成了IPv6协议栈只是默认关闭很多中文资料压根没提过这个开关。要开启很方便在BSP的lwip141设置里找到“Enable IPv6”勾选启用然后重新编译BSP。开启之后需要重新初始化网络接口不能再使用IPv4-only的默认初始化函数。lwIP中IPv6的地址配置主要分两种方式一种是手工指定静态IPv6地址另一种是通过无状态地址自动配置获取。在嵌入式裸机环境下我建议优先使用手工静态地址因为无状态自动配置需要设备定期发送RS请求还要等RA报文Deprecated状态和Preferred状态的处理会让调试过程变得复杂。IPC6技术本身的上游连接也需要注意和IPv4的关键区别是IPv6报文头部更复杂发送和接收路径都要走协议栈的扩展头解析逻辑编译选项里最好把LWIP_IPV6_ROUTE_TABLE_SUPPORT打开这样多接口转发更可靠。4.2 无状态地址自动配置的尝试前面说了优先静态地址但我还是把无状态自动配置完整跑了一遍因为“让板子自己拿地址”这件事在演示时更酷也方便后续接入路由器。要开启这个功能网卡初始化里要设置netif_set_up和netif_set_link_up然后调用netif_create_ip6_linklocal_address让lwIP自动生成链路本地地址。如果路由器上有NDP功能板卡也能自己请求到全局IPv6地址。这里要提醒大家注意一个名词混淆很多人把“路由器下发IPv6地址”叫DHCPv6但无状态自动配置使用的是ICMPv6的RS/RA机制也就是SLAAC走的不是DHCPv6。SLAAC获取的是前缀加主机接口标识组合出来的地址主机的接口标识通常由48位MAC地址扩展成64位的EUI-64格式。调试时如果把SLAAC当成DHCPv6去抓包会发现板卡发出的是一些ICMPv6类型为133、134的报文并不是常见的UDP 546/547端口这个细节对于做更复杂的嵌入式IPv6功能比较关键。4.3 用浏览器和ping命令双重验证开启IPv6之后板卡会有一个以fe80开头的链路本地地址如果接的是路由器还会有国际唯一地址。先用串口把打印出来的IPv6地址抄下来然后从电脑上执行ping6命令测试通连。Windows下ping6 fe80::xxxx这种链路本地地址要加接口限定格式为ping6 fe80::xxxx%NN是电脑网卡的接口编号可以通过netsh interface ipv6 show interfaces查看。不少人在这一步被卡住ping了没用是因为少了百分号区域ID。浏览器验证则更直观直接在地址栏输入以下格式链路本地地址访问http://[fe80::xxxx%N]Windows下全局地址访问http://[2001:db8::xxxx]注意IPv6地址在URL里必须用方括号括起来这是URL语法的规定和开发板无关。如果浏览器能正常打开控制页面说明从PHY、MAC、DMA到lwIP协议栈再到应用层整条链路全部跑通。实测下来同一个开发板同时监听IPv4和IPv6页面的响应延迟没有明显差异这在千兆以太网环境下是符合预期的。5. 调试心得与常见问题排查5.1 现象ping不通或应答缓慢做网络相关的FPGA项目ping不通是第一大坑。排查顺序通常是先看PHY有没有协商上千兆通过串口打印PHY寄存器的寄存器值确认再看DMA有没有收到完整的数据帧最后看lwIP是否把地址正确绑定到netif上。这个顺序不能乱因为低层不对上层必然白搭。我踩过最典型的一个坑是PHY初始化时序。上电后PHY需要几十毫秒稳定如果代码里复位后立刻读PHY寄存器读到的可能是复位前的残留值导致链路状态误判。解决方法是写一个简单的延时函数等待PHY的链接状态寄存器bit 1变为1再加几百毫秒的额外保险。如果PHY一直起不来还要检查RGMII的IO标准是否设置成LVCMOS 1.8或2.5以及时钟引脚有没有正确约束。当ping通但延迟很高时优先怀疑DMA描述符环管理问题。lwIP的接收回调把描述符处理完必须重新把缓冲区交还给DMA如果这个环节出错后续数据包不会被正确接收每个包都要等超时重传延迟自然起飞。检查方法是在接收回调里增加计数器对比串口打印的丢包信息一两次就能定位问题。5.2 DMA描述符和缓存一致性问题MicroBlaze若开启了D-CacheDMA写的内存区域和CPU读的数据容易出现一致性问题。lwIP的驱动在接收路径上通常会调用Xil_DCacheInvalidateRange在发送路径上调用Xil_DCacheFlushRange。很多性能瓶颈和随机数据错误都源于这些Cache维护函数的位置不对或者范围和长度算错。我采用的一个简单可靠的办法把DMA描述符和缓冲区所在的内存段设置为非缓存的地址空间。在MicroBlaze linker script或Memory Regions里可以为特定内存区间设置缓存属性。这样就不需要频繁手动维护Cache虽然整体吞吐率会有一点点损耗但换来的是极高的稳定性和极低的调试成本对于这个量级的应用来说很划算。注意如果发现收到的数据包内容偶尔错位、长度超出预期、或者DMA描述符的状态字看起来像野指针大概率是Cache问题而不是FPGA逻辑问题。先尝试关闭D-Cache或者把缓冲区放进非缓存段看现象是否消失再用真正的Bug排查思路去深究。5.3 烧写与配置问题热门搜索词里提到“microblaze mmi bit elf文件烧写msc”这个其实是SDK里Program Flash Memory的操作。简单说如果想让开发板上电后自动运行程序而不是每次依赖JTAG下载需要把Bitstream写到配置Flash里同时把应用程序写到另一个Flash分区或存储介质里再由Bootloop机制加载。最省事的方式是在SDK里直接点击Xilinx - Program Flash Memory选择那个带MicroBlaze的BIT文件和编译好的ELF文件设置好Flash偏移地址一次性烧进去。要注意的是如果选择的是QSPI Flash需要确保Vivado工程里已经勾选了SPIx4支持且FSBL或对应的启动代码配置正确。我尝试过程中遇到过烧进去白屏的情况排查结果是Flash型号选错导致读出来都是全0xFF换对型号后一次成功。6. 性能测试与扩展方向6.1 实际吞吐量数据调通之后我在实验室做了一轮简单的吞吐量测试用一台笔记本直连开发板通过iperf3测试TCP传输的吞吐量。配置是MicroBlaze跑在100MHzDDR3位宽16bitAXI Ethernet工作在1000Mbps。测试结果峰值大约在250Mbps到400Mbps之间具体数值与DMA缓冲区大小、CPU负载、TCP窗口设置都有关系。虽然离千兆线速还有距离但这个结果其实是可以接受的。如果把MicroBlaze频率提高到150MHz或200MHz把TX/RX描述符数量从32增加到128再把lwIP的TCP_MSS调大吞吐量还能往上走一些。但如果需要跑到900Mbps以上靠软核处理器就比较吃力了。换言之这样一套方案更适合对吞吐要求中等、但对实时性和确定性要求高的应用场景。我做了一个小表格方便对比不同模式下的实测结果模式平均吞吐量延迟表现备注IPv4 TCP280Mbps左右稳定默认参数IPv6 TCP260Mbps左右稳定开启IPv6后略低IPv4 UDP发送350Mbps左右有偶发丢包缓冲不足时丢包明显IPv6 UDP发送330Mbps左右有偶发丢包与IPv4接近6.2 后续可以玩的方向这套平台建好之后扩展空间其实很大。既然网口通了、HTTP服务跑起来了下一步可以做远程FPGA寄存器读写。FPGA内部的用户逻辑如果挂到AXI总线上就能通过网络动态修改寄存器值实现远程重配DDS频率、远程控制电机、远程采集ADC数据等操作。这类应用在实际工控和仪器仪表方案里非常有价值。更进一步还可以加入MQTT协议把开发板接入物联网云平台做数据上报或者用lwIP的SNTP客户端做时间同步配合FPGA内部的高精度计数器实现分布式采样。如果板卡Flash空间够还可以把网页文件系统化预存多个页面做成一个小型Web配置界面。每次打开浏览器输入ID地址就能看到虚拟仪表盘和数据曲线这个体验比单纯printf串口输出好太多。7. 一些心里话这块MA703FA平台的潜力比想象中大得多不只是跑马灯和逻辑实验。用MicroBlaze把网络协议栈跑起来相当于动手把“软件定义硬件”真正落地了一次。编代码、改地址映射、调DMA、抓报文每一个环节都在逼你去理解整条数据链路而不只是某个孤立知识点。我最想分享的一点体会是做嵌入式网络项目千万不要一头扎进代码里调。先用最小系统把PHY调试通再逐步加lwIP最后加应用逻辑每层都验证清楚后再叠下一层。TCP/IP协议栈本身的复杂性不可怕可怕的是底层不稳导致的高层错乱。把底层链路梳理清楚、打印信息设计好你会发现自己排查问题的速度会明显加快。现阶段网口服务器已经能稳定响应IPv4和IPv6请求下一步我准备把板卡接入一个真实的小型监控场景让传感器数据定期推送到网页上看看长时间运行下的稳定性表现。如果你的板卡和工具链版本跟我类似可以直接照着这套流程做一遍有任何卡住的地方欢迎在评论区交流各自的处理经验。
返回列表