ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA实现TCP/IP通信:硬件协议栈设计与实践

FPGA实现TCP/IP通信:硬件协议栈设计与实践 简介基于FPGA的TCP/IP通信完整工程资源面向FPGA开发、嵌入式网络设计与硬件通信方向的学习者与工程师提供可在Vivado或Quartus II中直接编译运行的TCP/IP协议栈实现支持千兆/百兆/十兆自适应网口适用于网络回环测试、高速数据收发与协议分析。资源共289个文件压缩包26.36MB主体为sv/v硬件描述源码、tdf逻辑设计文件、qsf/qpf工程配置、sof位流文件以及cdb/hdb等编译中间数据readme与summary文件可辅助快速了解目录结构和构建流程。已有644人学习下载。内容覆盖TCP/IP架构中网络接口层、IP层与TCP层的关键机制包括MAC帧封装、IP地址处理、三次握手与四次挥手、可靠传输与回环检查逻辑。通过该工程可免去从零搭建协议栈的繁琐工作直接观察FPGA内部各模块间的信号连接和状态跳转为调试千兆网链路、理解协议硬件化实现、完成毕业设计或竞赛项目提供扎实的参考。 做FPGA开发这些年被问得最多的一个需求就是能不能用FPGA直接实现TCP/IP通信不是靠ARM核跑Linux而是用逻辑或者轻量级处理器把网络协议栈跑起来实现高速收发。这个项目标题看起来简单实际上是把以太网物理层、MAC、IP、TCP状态机、数据缓存和校验和计算全部串起来属于典型的“看着不难、做起来处处是坑”的工程。我最初接触这个项目时是想给一套高速数据采集系统做实时上传。板卡上FPGA负责采集ADC数据然后要把数据以TCP流的形式送到上位机。CPU方案能满足但中断、拷贝、协议栈调度在大流量下会吃掉大量CPU资源延迟也不可控。于是团队决定在FPGA内部完成TCP/IP通信把一部分网络功能下沉到硬件里。这篇文章就从这个角度出发聊聊整体设计、方案选型、核心细节、验证流程和我在实际调试中踩过的坑。适合正在做FPGA网络通信、考虑自研协议栈或者想移植开源项目的同学参考。1. 项目概述为什么要在FPGA上做TCP/IP通信1.1 一个很常见的业务场景假设你有一块FPGA开发板接收高速ADC或者摄像头传感器的数据数据流是连续产生的。你要把它通过网络传到PC或者服务器上。传统做法是FPGA把数据放进FIFO然后用UDP广播出去。但UDP有丢包风险对可靠性要求高的系统并不合适。要是改用TCP又必须维护连接状态、序列号、重传机制、滑动窗口工作量大很多。如果在FPGA里实现了TCP/IP通信那么数据路径可以做成纯硬件流水线。网络包从PHY进来经过MAC、IP解析、TCP解析直接写入接收缓存再由用户逻辑读取发送方向则把用户数据封装成TCP/IP报文交给MAC和PHY送出去。整个过程不需要CPU参与每个包的拷贝也没有操作系统的调度开销能做到非常低的延迟和可预测的时序。1.2 这个项目适合谁参考我觉得三类人最需要这份经验做高速数据采集和图像传输的工程师。这类系统数据量大、实时性要求高FPGA直接做TCP很合适。做自定义网络硬件加速的人。比如想要卸载TCP校验和、实现线速转发或者在网络数据路径上做硬件过滤。正在做毕设或者开源项目移植的学生。比如有热词提到的Corundum项目想把它从一个平台移植到另一个FPGA平台这里面的适配和验证思路就能直接复用。2. 整体设计思路与方案选型2.1 先想清楚协议栈放在哪里TCP/IP协议栈不是一定要在硬件里写常见有几种实现方式方案优点缺点适合场景FPGA 软核MicroBlaze/Nios II/RISC-V lwIP开发快TCP逻辑灵活吞吐受软核性能限制原型验证、控制面通信硬核处理器 内置MACZynq SoC成熟稳定Linux网络栈丰富延迟较高数据路径绕CPU通用嵌入式Linux网络设备纯FPGA逻辑实现MACIPTCP低延迟高吞吐数据路径自由开发难度大协议难改高速数据流实时传输、自定义网络加速开源IP核Corundum等有完整代码可参考支持高吞吐平台适配有门槛调试复杂100G网卡、DPU原型、学术研究我这次做的是第三类和第四类的混合底层MAC用FPGA工程里的现成IPTCP/IP层用一个自研的轻量级协议栈模块同时参考了一点Corundum的设计思想。如果从头写全部协议栈工作量太大而且容易在TCP状态机上出bug。2.2 推荐参考的软硬件框架如果你用的是Xilinx平台最简单的组合是AXI Ethernet IP核 lwIP协议栈。MicroBlaze上跑lwIP底层驱动直接调AXI Ethernet的DMA接口。这个方案优点是网上的资料很多半天就能ping通。缺点是数据吞吐受MicroBlaze和DMA中断处理能力限制线速传输比较难。如果目标是纯逻辑方案建议拆成三个模块MAC控制模块负责发送/接收以太网帧处理CRC和帧间隙。IP层模块解析IPv4头处理ARP、ICMP可以辅助ping转发TCP/UDP数据。TCP模块实现连接管理状态机、序列号跟踪、ACK生成和重传定时器。2.3 资源和速率该怎么估算在动手写代码之前先算清楚带宽和资源不然做到后面会发现资源不够、时序收敛不了。假设你的系统数据吞吐要求是1Gbps以太网物理层用千兆口MAC工作在125MHz、32位数据位宽或者62.5MHz、64位位宽。每个TCP报文去掉以太网头、IP头和TCP头有效载荷大概占1460字节。1Gbps换算成包率大约每秒8万到9万个TCP包。这意味着你的数据通路每12.5纳秒就得处理一个8字节数据。如果状态机里有一个地方阻塞FIFO很快就满。资源方面纯逻辑TCP/IP协议栈大约需要BRAM收发FIFO加描述符缓冲至少几十KB具体看数据缓存深度。LUT/FF状态机和解析逻辑一个简化版大概用几千到上万LUT。DSP一般用不到除非你要做加密或大量校验计算。我建议一步到位先按你系统最大瞬时数据速率来设计缓存不要做到一半再扩太痛苦。3. 核心细节解析与实操要点3.1 PHY芯片与MAC接口的初始化FPGA的网口一般外接一颗PHY芯片常见的是RTL8211、YT8512、Marvell 88E1512。PHY负责把模拟信号转成数字的RGMII/GMII接口数据FPGA这边的MAC通过MDIO接口配置PHY寄存器。MDIO的时序本质是一个两线的串行接口。MDC提供时钟MDIO是双向数据线。读PHY寄存器时先发32位的前导码再接控制码和寄存器地址最后读16位数据。实际项目里常见的问题是PHY地址没对上或者复位时序不对导致读出来的寄存器全是0xFFFF。我会在调试的时候先写一个简单的MDIO回读函数读PHY的寄存器0看看厂商ID和链路状态是否正确。RGMII接口需要特别注意数据对齐。千兆模式下RGMII在时钟上升沿和下降沿各采一次数据这意味着DDR时序。很多人第一次做的时候容易在PCB上把时钟和数据搞反相位导致板级调试时疯狂CRC错误。建议在FPGA内部用IDDR原语采数并且留一组可调的延迟约束。3.2 TCP状态机连接管理的核心TCP最核心的就是状态机。从FPGA实现的角度看有几个状态是必须的LISTEN模块等待上位机发起连接。SYN_RCVD收到SYN回复SYNACK。ESTABLISHED连接建立可以收发数据。FIN_WAIT/CLOSE_WAIT/TIME_WAIT连接关闭阶段。关键逻辑点在握手上位机发来SYN包序列号是X。FPGA回SYNACK自己的序列号是Y确认号是X1。上位机回ACK确认号是Y1连接建立。这里有个隐蔽的问题FPGA内部生成的初始序列号Y不能每次上电都一样否则可能被对端当成旧连接包。最好用一个计数器或者随机数种子来初始化。我见过有项目用固定Y0虽然能连上但会被防火墙或抓包工具提示异常。TCP状态机尽量不要全部用嵌套的case硬写建议拆成接收解析和发送控制两个模块。接收解析模块把TCP头里的序号、ACK号、标志位提取出来发送控制模块再根据当前状态生成响应报文。状态迁移图可以用localparam定义状态用状态转移表来管理这样后续加超时重传也方便。3.3 数据通路、缓冲与校验和卸载数据通路上的问题大部分出在缓冲管理。接收方向以太网帧进来后不能只用一个FIFO一路收到尾因为TCP需要先解析头部才知道这是不是自己需要的连接。实现上通常分两级第一级是MAC收包FIFO整帧缓存下来。第二级是解析后有效载荷FIFO只有确认数据属于当前连接后才搬入。发送方向类似上游把待发送数据写入发送FIFOTCP封装模块按最大报文段长度切片加上MAC/IP/TCP头然后送MAC发送。校验和是另一个容易踩坑的地方。IP头和TCP头都有校验和字段。IP校验和是对整个IP头做反码求和TCP校验和还要加伪头部源IP、目的IP、协议号、TCP长度。很多FPGA新手会直接对收到的包原样转发忘了重新计算校验和结果对端直接丢弃。硬件计算校验和其实很简单无非是16bit累加再取反。但是要注意累加时的进位回卷。我在代码里通常用组合逻辑一周期算完或者在DMA搬运时顺带计算保证不额外增加数据路径延迟。4. 实操过程与功能验证4.1 搭建最小可用的FPGA网络工程如果你从零开始我建议先在FPGA里搭一个最小工程包含三部分一个PLL产生MAC时钟和逻辑时钟。一个MAC IP核或者自研MAC模块连接PHY芯片。一个最简单的ARP应答模块让PC能ping通FPGA。不要一上来就写TCP三层。先用网线连接PC和板卡配置好静态IP比如FPGA在192.168.1.10PC在192.168.1.100然后打开命令窗口ping。如果ping不通问题大概率在PHY初始化、MDIO配置或者RGMII时序而不是在TCP层。我习惯在工程里加一个50MHz的调试时钟专门给ILA集成逻辑分析仪使用。抓信号时优先抓MAC接口的tx_valid、tx_data、rx_valid、rx_data能立刻看出有没有数据在跑。4.2 仿真验证在testbench里跑通TCP握手仿真阶段最容易忽略的是PHY行为模型。大多数IP核自带PHY模型但自研代码往往需要自己写一个简化模型。我会在testbench里模拟PC端的行为先发ARP请求收到回复后再发SYN包然后检查FPGA有没有回SYNACK。一个典型的收包解析testbench片段长这样task send_tcp_pkt; input [31:0] src_ip; input [31:0] dst_ip; input [15:0] src_port; input [15:0] dst_port; input [31:0] seq; input [31:0] ack; input [7:0] flags; input [7:0] payload[]; begin // 组装MAC头、IP头、TCP头填充校验和然后驱动rx_*信号 end endtask仿真时重点看几个时间点ARP请求后ARP应答是否正常发送。SYN进来后回包的ACK号是不是seq1。握手完成后连续发几个数据包确认对端ACK中的序号和接收数据长度一致。这些行为仿真不解决上板调试会痛苦很多倍。4.3 板级验证从PING到TCP收发板级验证我建议分三步走第一步PING通。这一步验证链路层和网络层没问题。如果PING不稳定先检查PHY的连接速率协商再用示波器看RGMII的时钟和数据眼图或者调整IO约束里的延迟。第二步连接TCP测试。这里我可以分享一个小工具流的做法PC上写一个Python脚本用socket连接FPGA的端口然后周期性发送固定长度的数据。FPGA收到后把数据原样回传PC端再校验内容。脚本里同时做收发统计能很方便看出有没有丢包、错序、内容错位。import socket s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((192.168.1.10, 8080)) for i in range(1000): data bfpga-tcp-test-%06d % i s.sendall(data) recv s.recv(len(data)) assert recv data, mismatch at %d % i s.close() print(all ok)第三步用Wireshark抓包对比。PC上同时开Wireshark抓FPGA和PC之间的交互。重点看TCP握手包、ACK序号变化、是否有虚假重传。如果Wireshark里总是提示乱序或者重传说明FPGA端的TCP序号逻辑或者重传定时器有问题。如果你移植的是Corundum这种项目验证方法也很类似。只不过第一次上板前要先改好PHY适配层、引脚约束和DMA地址映射然后在工程里加一个回环测试模式让MAC收到的帧直接送发送通道先验证PHY和MAC没问题再跑DMA和TCP协议逻辑。5. 常见问题与排查技巧实录5.1 建立连接失败PC报“TCP/IP connection terminated!”这个报错我在调试时见过太多次了。PC尝试连接FPGATCP握手始终完成不了应用程序等到超时后直接报连接被终止。遇到这个问题先别去看应用层按顺序查ARP是否通过。PC和FPGA在同一个局域网里PC发数据前先要发出ARP请求如果FPGA不回ARPTCP连接永远建不起来。抓Wireshark时看到只有ARP请求、没有ARP应答就是ARP模块的问题。SYN-ACK有没有发出去。如果ARP正常但TCP握手失败用ILA抓FPGA的发送通路看收到SYN之后有没有置起发送开始信号。如果解析模块没识别到SYN标志说明TCP头解析的偏移地址算错了。校验和是否正确。很多网卡对TCP校验和是硬件检查的校验和错误会直接丢包PC端表现就是只有SYN发送没有SYN-ACK返回。这时需要把FPGA发出的SYN-ACK包完整抓下来手动算一遍校验和。5.2 数据丢包、乱序问题当TCP连接建立成功但批量传输数据时出现丢包或者乱序大概率不是TCP状态机的问题而是接收缓存和数据通路的问题。FPGA收到一个TCP包后解析模块需要把有效载荷写入FIFO同时向上位机回复ACK。上位机收到ACK后才会发送下一个窗口的数据。如果用户逻辑来不及从FIFO读走数据接收FIFO满后续的包就被丢弃表现为接收方内容缺段。排查方法是统计FIFO的水位。我通常会在FIFO快满时拉高一个触发信号用ILA记录下来看看这时候用户逻辑在做什么。很多时候是用户逻辑的读使能没有持续拉高或者DMA搬运的突发长度太短导致FIFO吞吐跑不满。另外TCP头部里的序列号是字节流编号不是包编号。FPGA内部如果只按包序号去重或者排序很容易乱。正确姿势是维护一个期望接收的下一字节序号只有当包的序列号等于这个值时才把载荷写入FIFO大于期望值的包可以缓存或丢弃取决于是否需要乱序重组小于期望值的包直接丢弃。5.3 速率上不去怎么办很多项目在仿真和ping通后都很兴奋一到实际跑吞吐测试就发现问题千兆口只能跑到两三百兆。速率瓶颈一般出现在三个方面MAC接口时序RGMII在千兆下是双沿采样如果约束没做对时序裕量不足会丢包重传吞吐自然上不去。FIFO访问冲突发送方向用户逻辑写入发送FIFOTCP封装模块从FIFO读出并封包。如果两边在同一时刻访问FIFO带宽减半需要在FIFO位宽上做文章。比如用户逻辑写32位但读侧用64位一次取两拍数据。ACK处理不及时TCP的吞吐受窗口大小和ACK往返时间影响。如果FPGA回ACK的路径延迟太高PC端会降低发送速率。解决办法是收到数据后立即生成ACK不需要等用户逻辑处理完数据再回ACK。我遇到过一种情况FPGA发送吞吐正常但接收吞吐很低。后来排查发现接收路径上每收到一个包都要等IP层判断完成才把数据写到FIFO而IP层判断逻辑用了一个很深的组合逻辑链导致接口时钟跑不到125MHz。解决方法是把解析分成流水级一拍拍解析MAC头一拍拍解析IP头一拍拍解析TCP头这样时钟频率就上去了。另一个提速的经验是减少响应包的字节数。比如ACK包可以不带任何载荷我们直接用硬件拼一个TCP头只更新ACK号这样整个响应过程非常固定也容易做出时序友好的流水线。在调试这个项目的后期我最大的体会是FPGA做TCP/IP难点从来不在于“能不能发一个TCP包”而在于要像真正的网卡一样把连接状态、确序、流控、故障恢复这些行为都做得足够完整。而一个足够完整可用的版本也不可能一天写完一定要从ARP、PING、UDP、TCP逐步往上叠加每一步都验证扎实后再进入下一层。最后再分享一个小技巧如果你手头有开源协议栈或者IP核不要急着直接跑完整功能先把PHY回环测试跑通。所谓回环测试就是让MAC发送的数据不进PHY而是直接在FPGA内部接回收发通路。这样能暂时屏蔽掉网线、连接器、PHY芯片等硬件干扰把问题范围缩小到FPGA逻辑本身。等回环测试稳定了再把PHY介入你会发现整个排查过程会顺畅得多。本文还有配套的精品资源点击获取
返回列表