尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA以太网通信完整实现:从RMII到ARP/UDP协议栈

FPGA以太网通信完整实现:从RMII到ARP/UDP协议栈 做FPGA开发做到第七篇我一直没提网络通信这事。不是不想是前面串口还没彻底玩明白的时候压根不敢碰以太网。但需求这东西是躲不掉的——我把板子上的数据往电脑传用UART跑115200bps满打满算一秒钟也就传11KB左右想传一张640x480的灰度图要转两分钟调个参数看一眼效果半天就没了。直到我下决心把FPGA的网络通信设计拆开啃完才意识到这个领域对新手真正的门槛不是难而是杂PHY、MAC、RMII、ARP、UDP、CRC32这些词混在一起看起来像一座山真正动手拆开之后每一块其实都是一个标准得不能再标准的定义。这篇文章面向的是和我当初一样的玩家会写简单的Verilog状态机用过UART、SPI能跑通Vivado工程的综合下载流程但一听到网络协议栈就头皮发麻。我会用一整条从硬件到协议的完整链路把FPGA侧实现以太网通信的路径讲清楚顺便把那些文档里从来不写、只有亲手踩过才知道的坑也一并交代了。1. 网络通信在FPGA开发里的真实分量先认清这块骨头有多硬1.1 为什么第七站是网络通信如果你是从本系列第一篇一路写过来的大概已经习惯了这种节奏先是流水灯和组合逻辑然后是时序逻辑、状态机、UART、SPI再往后就是各种接口协议。到了这个阶段很多人会面临同一个瓶颈——我的FPGA板子算力够了、逻辑也写得顺了但数据怎么和外界高速交互串口显然是第一个选项便宜、简单、调试方便但速度天花板摆在那里。哪怕你上到921600波特率刨去起始位停止位真实吞吐也就90KB/s出头。这还是在电脑端串口助手给力的前提下。一旦数据量上到图像、音频、ADC连续采样流串口立刻变成全场最拖后腿的环节。于是以太网几乎成了绕不开的一站。我见过不少人直接绕过网络跳到PCIe、DDR、图像处理这些方向继续折腾但只要涉及到把大量数据从开发板搬到PC上分析绕一圈最后还是会回到以太网。它不是性能最强的方案——PCIe和光口都比它猛得多——但它是平衡点带宽够用、生态成熟、调试工具多而且成本极低一块PHY芯片加一个RJ45座子十几块钱就能解决物理层。1.2 网络通信在FPGA项目里的典型应用场景在真正动手之前你先要搞清楚自己为什么需要网络。不同目的直接决定了你要在FPGA里实现到什么程度做的活完全不一样。拿我自己接触过的场景举例数据采集类项目ADC采样、传感器节点通常只需要FPGA把数据打包成UDP报文持续往外发PC端用Wireshark或者自写脚本收图像传输类项目会用到UDP的大包连续发送但对协议完整度要求依然不高控制类项目则需要双向交互PC下发指令、FPGA回传状态这就要把ARP和ICMP一并处理好再往上还有音频流同步、多节点组网、远程更新固件这类需求那就要碰TCP、甚至要上SoC跑Linux纯逻辑实现性价比已经不高了。结论是对从0开始学FPGA的人来说最合理的毕业目标是在纯FPGA逻辑里实现一个能用的ARP应答、ICMP回显和UDP收发子集。不需要TCP的复杂状态机不需要DHCP自动获取IP更不需要搞什么链路聚合。当你把这个精简协议栈跑通再去按需扩展才有资格谈设计网络通信系统。这一篇我带你走的就是这条路。2. 从物理层往上走一遍以太网的数据到底是怎么钻进网线的2.1 从快递物流理解以太网的层次结构很多新手被网络通信劝退是因为一上来就看到OSI七层模型、TCP/IP四层模型这些名词。我换个说法以太网通信就是寄快递每一层都在干打包和贴单子的事。你的FPGA要发一段数据先把它装进一个信封里信封上写清楚这封信是给哪个进程的这个信封就是UDP数据报信封上的收件人进程号叫端口号。然后这个信封再被装进一个更大的文件袋文件袋上写清楚IP地址这就是IP数据报。接着文件袋又被塞进一个标准纸箱纸箱上写着源MAC地址和目的MAC地址这就是以太网帧。最后这个纸箱交给快递公司由物理层的PHY芯片负责把帧转成网线上能跑的差分电压信号。这个过程里有三个寻址信息特别容易搞混MAC地址是快递员找门牌号用的只在乎局域网内把帧送到哪个网卡出了网口就被交换机刮掉了IP地址是跨城市路由寻址用的从你的电脑一路寻址到对方设备所在子网端口号则是到了这栋楼之后把信交给哪个部门的标识。FPGA自带的网口没有操作系统所以它不需要关心端口号和进程的映射——当你说FPGA监听5000端口实际含义只是我只处理UDP目的端口等于5000的报文其余一律丢弃。2.2 MAC帧长什么样一个包裹的完整外观物理层之上第一层叫数据链路层承载它的单元就是以太网帧。新手做网络通信第一件事就是把帧结构背下来这和学I2C要背起始停止条件是一个道理。一个标准以太网帧从线缆上的角度看开头是7字节0x55前导码和1字节0xD5帧起始定界符这两者用来让接收端恢复时钟、找到边界。从目的MAC地址开始才是真正进了帧头的范畴6字节目的MAC、6字节源MAC、2字节长度/类型。注意这个字段有讲究——如果值小于0x0600也就是1536它表示载荷长度如果大于等于0x0600它表示上层协议类型。你会在抓包里最常见到的是0x0800代表IPv40x0806代表ARP0x86DD代表IPv6。帧头之后是载荷区规范要求最少46字节、最多1500字节。如果实际数据不足46字节底层要自动填充到46字节。帧的尾部是4字节FCS帧校验序列本质就是CRC32从目的MAC开始一直算到载荷末尾。发送时CRC附加在末尾接收端算完比对不一致说明这帧在传输中坏了直接丢弃。这里有一个我当初纠结很久的问题既然CRC32算法是公开的那随便拿个软件工具算出来的结果为什么和线上抓到的FCS对不上答案是以太网用的CRC32在字节内部是LSB-first反射传输的多项式是0x04C11DB7初值为0xFFFFFFFF输出还要异或0xFFFFFFFF。你如果按常规的MSB-first算结果当然对不上。这个坑后面我会专门讲。2.3 PHY与MAC的分工谁负责干什么FPGA网络设计里最让人迷惑的就是哪些事归PHY管、哪些事归MAC管分不清这个后面所有代码都是糊涂账。PHY芯片负责物理层的脏活累活把MAC送给它的数字信号编码成适合在双绞线上传输的电平信号同时把接收到的模拟信号解码成数字信号负责自动协商autonegotiation——和交换机或电脑网卡商量好双方通信速率是全双工100M还是半双工10M负责载波侦听CSMA/CD里的那个CS还要管链路状态检测发现网线断了就通过状态寄存器汇报给MAC。MAC是数据链路层的核心在FPGA设计里一般由你写的逻辑承担它要拼帧前导码加帧头加载荷加CRC32、拆帧去前导码解出帧头载荷、做地址过滤只有目的MAC等于自己或广播地址才收下、做流控如果处理不过来就暂停发送。PHY和MAC之间就是通过MII/RMII这类接口对接的不需要自己去控制网线电平。简单说你让PHY把线上的比特流翻译成相对规整的数字信号交给你你处理帧方向的事两边各干各的。2.4 精简协议子集ARP、ICMP、UDP一个都不能少真正开始为FPGA设计协议栈之前你要先规划好只实现哪些协议。我建议的最小集是三个ARP、ICMP和UDP。ARP用来解决我知道对方IP但不知道对方MAC的问题。当你第一次在电脑上ping FPGA的IP比如192.168.1.100电脑会先广播一条ARP请求谁知道192.168.1.100的MAC地址FPGA收到后回一条ARP应答是我我的MAC是xx:xx:xx:xx:xx:xx电脑拿到这个MAC才能真正发出ICMP报文。所以如果ARP不实现电脑连第一步都迈不出去。ICMP主要用于调试最常见的就是ping。电脑发来一个Echo Request请求回显FPGA要把类型字段从8改成0算好校验和原样再把数据发回去。能通ping至少证明MAC帧收发、IP收发和校验和逻辑都是对的这是网络通与不通的最核心判据。UDP提供面向无连接的传输通道。它的头部只有源端口、目的端口、长度和校验和总共8字节。IPv4下UDP校验和允许置0表示不校验这对FPGA实现来说是天大的好消息——意味着你不用为UDP算任何校验和只要把IPv4的header checksum算对就行。这三样加起来逻辑复杂度大概也就比一个完整的UART收发器高两到三倍真的不多。3. 硬件连接实操RMII接口和PHY芯片选型避坑3.1 常见PHY芯片型号与选型对比做FPGA网络实验你首先得有一块带PHY芯片的板子或者自己画一块PHY子板。市面上常见的PHY芯片我大致列一下芯片型号接口支持速率特点LAN8720ARMII10/100M自带50MHz时钟输出便宜Micrel/微芯家RTL8201FMII/RMII10/100M老牌资料多寄存器兼容性好KSZ8081MII/RMII10/100MMicrochip家自带LDO省外围YT8512MII/RMII10/100M国产替代淘宝板常用价格低RTL8211RGMII10/100/1000M千兆一般配合要求更高的板子对入门来说首选RMII接口的芯片。原因很直接MII需要7根数据线和独立收发时钟RX_CLK/TX_CLK各25MHz/2.5MHzRMII把数据线压到2根收发共用一根50MHz参考时钟总共只需要TXD[1:0]、TX_EN、RXD[1:0]、CRS_DV、REF_CLK、MDIO、MDC这8根线电平都是单端3.3V/LVCMOS非常适合自己画板子或者飞线调试。3.2 RMII引脚逐根讲解与关键硬件设计我用LAN8720A举例RTL8201F引脚基本兼容把RMII每个信号线的角色讲透这样你拿到任何PHY芯片的datasheet都能自己对照。REF_CLK50MHz参考时钟这是RMII的核心节拍。收发方向的每一位数据都在REF_CLK上升沿被采样2位数据线搭配50MHz就等于100Mbps吞吐。这个时钟可以由外部晶振直接给PHY也可以由MAC也就是FPGA输出LAN8720A还支持从它的XL1/CLKOUT引脚输出。关键是收发两端必须共享同一个50MHz参考时钟。TXD[1:0] TX_EN发送数据线和发送使能。FPGA在REF_CLK上升沿前把数据摆到TXD上拉高TX_EN表示这组数据有效。发送使能无效时TXD必须保持0否则线上会出现非法符号。RXD[1:0] CRS_DV接收数据线和载波侦听/数据有效指示。连线时CRS_DV拉高说明PHY正在向你送数据RXD在REF_CLK上升沿有效。有些PHY还把这个信号拆成CRS和RX_DV用逻辑或的关系组合。FPGA接收逻辑一定要等CRS_DV拉高之后才开始采RXD否则会把前导码之前的空闲噪音当数据。MDIO MDC管理接口。MDC是时钟MDIO是双向数据线用于读写PHY内部寄存器。这是PHY的控制台寄存器名从0到31寄存器0是基本控制、1是基本状态、2是PHY标识、4是自协商通告、5是自协商链路伙伴能力。后面查问题全靠它。PHY地址配置脚LAN8720A的PHYAD0引脚决定PHY地址最低位一般上拉到1、其余位接地地址就是0x01。淘宝绝大多数模块出厂地址就是0x01。MDIO总线上可以挂多个PHY靠地址区分。nRST复位脚低电平复位。上电后要保证PHY复位释放时间足够datasheet要求一般几十毫秒并且复位完成后要等待一段时间才能访问MDIO。硬件连接上还有两个很容易忽略的细节。第一REF_CLK必须保证质量如果由FPGA输出尽量用PLL的专用时钟输出引脚比如Vivado里的clk_out不要用普通IO反转生成否则抖动会让PHY在高温或线缆较长时频繁丢帧。第二RMII是单端信号但TXD、RXD这些线在PCB上仍然尽量做到组内等长长度差控制在5mm以内比较稳网口变压器的中心抽头、信号对地和共模电感参考PHY芯片的参考设计来画别自己发明。3.3 网口变压器与连接器从数字信号到网线的一公里路PHY芯片出来的信号并不能直接进网线。以太网的物理层规定信号要经过隔离变压器俗称网络变压器再上RJ45。变压器的作用有三层一是隔直把PHY芯片侧和网线侧的直流电位完全隔离避免地环路干扰二是提供共模抑制把双绞线上耦合进来的共模噪音压掉三是阻抗变换网线特性阻抗100欧姆PHY侧内部有端接电阻变压器负责两边参数匹配。入门阶段最省事买了集成了变压器的RJ45座子比如HR911105A这种自带网口灯的型号。TXD/TXD-、RXD/RXD-四根线从PHY芯片出来后按芯片手册接到变压器的对应脚上即可。如果你用的是普通RJ45座子加独立网络变压器一定要分清次级侧中心抽头的接法——有的需要接电源、有的需要接电容到地以PHY芯片手册为准错了大概率不通。还有一个细节我要单独说MDIX交叉自动翻转。早年间两台设备直接网线互联要用交叉线连交换机用直通线。现在的PHY和交换机基本都支持自动翻转但你自己画板子时如果PHY芯片的MDIX功能需要在寄存器里开务必检查出厂的默认值。淘宝的LAN8720A模块默认是打开的但也有个别PHY默认关闭板子做出来怎么都连不上被这问题坑一次半天就没了。4. FPGA内部的分层设计MAC收发器和协议处理状态机4.1 模块划分把自己当成一个小网卡硬件上PHY就位之后真正的工程量在FPGA逻辑侧。一个可以跑通的简化设计我建议按下面这几个模块来组织rmii_rx接收PHY送来的RXD[1:0]和CRS_DV完成位对齐、把2位的半字节拼成8位的完整字节。mac_rx识别前导码和帧起始定界符拆帧提取目的MAC、源MAC、类型字段计算CRC32并比对接收到FCS。mac_tx反方向把待发送的数据拼成完整MAC帧加上前导码、帧头、CRC32按RMII的2位接口把数据送出去。arp_module解析ARP请求根据内容生成ARP应答。icmp_module解析ICMP Echo Request生成Echo Reply。udp_dispatch检查UDP目的端口命中则把载荷交给上层应用同时支持把收到的数据原样回发形成回环。tx_arbiter发送仲裁器。ARP应答、ICMP应答、UDP数据可能同时想占用发送通路需要一个简单的优先级仲裁一般让ARP高优、ICMP次之、UDP最低即可。这个结构的核心思想是分层。接收和发送的物理细节全部收敛在mac_rx和mac_tx内部arp/icmp/udp模块永远不用关心RMII引脚的时序。后面你要换千兆PHY、改RGMII接口只需要重写mac收发两个模块协议层完全不用动。4.2 接收通路从RXD原始信号到MAC帧解析mac_rx接收端的核心状态机大致是这样的IDLE状态等CRS_DV拉高然后进入PREAMBLE状态数7个0x55遇到0xD5说明前导码结束接下来开始收帧头。这期间每一拍REF_CLK采2位攒够4拍拼成1字节然后写入一个FIFO或直接流式处理。这里我要说一个容易被忽略的细节CRS_DV拉高之后前导码并不一定从字节边界开始。PHY芯片的接收逻辑对前导码的起点没有做对齐约束你从任意一位开始接到的可能是0x55移位后的值比如遇到的是0x2A、0x95这样的值。因此mac_rx的PREAMBLE状态要设计成滑动匹配只要连续看到接近0x55模式的数就持续判断真正锁定边界的是SFD0xD5一旦出现0xD5从此以后每8个数据位对齐到一个字节。很多新人在这里直接按字节找0xD5结果偶尔通偶尔不通其实就是没理解前导码的位级对齐问题。帧头收完后我们需要判断这个帧该不该收。判定规则一般是目的MAC等于自己的MAC加广播地址0xFFFFFFFFFFFF则收下否则直接丢弃。由此开始把数据送往协议分发——类型字段是0x0806就送arp_module是0x0800就进IP层解析如果校验CRC失败整帧丢弃协议层根本不知道这件事。4.3 发送通路状态机与CRC32的正确打开方式发送方向是新手栽跟头最密集的地方。mac_tx的整体流程为等待tx_arbiter给出要发一帧数据的请求然后状态机依次走过PREPEND_PREAMBLE发7字节0x55和1字节0xD5、SEND_HEADER根据协议填入目的MAC、源MAC、类型字段、SEND_DATA从数据源读字节发出去、SEND_CRC发4字节CRC32、DONE拉高一个周期的完成信号回到IDLE。TX_EN信号在PREPEND_PREAMBLE开始拉高DONE时拉低。注意这期间TXD[1:0]必须始终是有效数据哪怕数据FIFO空了也要用填充数据顶上否则线上会出现残缺的字节流对方PHY会报帧错误。标准以太网载荷最小46字节如果应用层给的数据不足要在SEND_DATA阶段先发够填充字节再发CRC。CRC32的FPGA实现我强烈建议不要自己从头写反射位序的逻辑直接按下面的规则来CRC寄存器初值0xFFFFFFFF。从目的MAC的第一个字节开始每个字节的LSB先进入计算即字节内按位反转。所有数据算完CRC寄存器结果按位取反。取反后的结果以LSB-first方式逐位发送到线缆上即CRC32寄存器从bit0开始逐位送出去。用Vivado自带或者网上搜ethernet crc32 verilog可以找到现成的并行计算模块。验证方法最简单的是如果接收端算完整个帧包括FCS之后CRC寄存器回到固定的魔数0xC704DD7B说明CRC算法正确。这个魔数是检验标准算法最硬核的依据比对着波形看靠谱得多。4.4 ARP应答与ICMP回显让协议栈转起来ARP模块的逻辑其实不复杂收到目标IP等于自己IP的ARP请求后把请求帧里的发送方MAC和IP摘出来作为目的方把自己的MAC填进去作为发送方操作码从1请求改成2应答类型写成0x0806然后交给mac_tx发出去。要注意ARP应答无需改动载荷之外的任何字段整个帧长固定42字节不含IP头所以CRC计算范围要从ARP头开始算。ICMP模块稍微绕一点电脑发来的ICMP Echo Request是完整封装在IP包里的FPGA回复时要先把整个IP头原样拷贝回给电脑但把IP头里的源IP和目的IP对调把TTL减1也可以不减网关会减重新计算IP头校验和然后到ICMP部分把Type从8改成0Code保持0重新算ICMP校验和Identifier和Sequence Number必须原样带回否则电脑端的ping程序会丢弃这个应答。这里有个设计上容易忽略的点IP头校验和和ICMP校验和是两套独立的校验很多人只改了其中一个导致ping请求通了但每次都要等超时才能打出来。如果你只想做最简单的验证可以把ICMP模块做成收到Echo Request就把整个IP包按规则翻转后原样踢回去这样速度和正确性都最容易保证。5. 第一个可上电实验让PC能ping通你的FPGA5.1 实验硬件与软件准备在做实验之前先把环境理清。我用的板子是自家画的FPGA是Xilinx Artix-7系列Vivado工程PHY模块是淘宝常见的LAN8720A小板通过一组排针连到FPGA的通用IO上RJ45端插一根网线直连电脑的千兆网口。如果你的板子是黑金、正点原子这类带板载PHY的连法类似只是引脚已经定死了约束文件直接用厂家的即可。软件方面除了Vivado之外你还需要一个Wireshark抓包工具以及一个能发送UDP报文的工具Python、网络调试助手都可以。抓包在入门阶段非常重要因为以太网协议是黑盒你得先看电脑发出的到底是什么帧才能判断FPGA处理得对不对。给FPGA分配一个固定的IP地址比如192.168.1.100MAC地址用随便编一个不冲突的就行比如0xAABBCCDDEEFF电脑的网卡设置成静态IP地址192.168.1.10子网掩码255.255.255.0。注意这要求你的电脑网卡关闭DHCP、防火墙对ICMP放行Windows的ping一般不用管防火墙但某些第三方安全软件会拦截。5.2 板卡初始化与PHY自协商检查上电之后别急着写复杂协议先做两步基础检查。第一步是确认REF_CLK到了。用示波器或者ILA如果你把REF_CLK连进逻辑看PHY的CLKOUT引脚是否有干净的50MHz波形。没有波形先查电源和晶振波形有但频率抖得像心电图查地线和电源纹波很多PHY问题都是电源不干净导致的。第二步是MDIO读写测试。在FPGA里写个最简单的MDIO控制器试着读PHY寄存器0基本控制寄存器和寄存器1基本状态寄存器。寄存器1的bit2是链路状态1表示已建立连接bit5是自协商完成标志。链路状态为1之后再接网线插到电脑网口上网口的绿灯应该亮起。如果灯不亮大概率是PHY的复位或者自协商配置有问题先把这个解决了再往下走——省得后面出了问题分不清是MAC逻辑的问题还是PHY物理层的问题。5.3 ping通的瞬间ARP和ICMP的完整链路把mac_rx、mac_tx、arp_module、icmp_module综合下载到板子上然后在电脑上执行ping 192.168.1.100正常情况下你会看到回应时间小于1ms的应答包。这个过程在后台发生的事情是电脑检查自己的ARP缓存表发现没有192.168.1.100对应的MAC地址于是向全网络发广播ARP请求。FPGA的mac_rx收到广播帧送给arp_module识别出这是ARP Request且目标IP就是自己于是生成ARP Reply单播给电脑。电脑更新ARP缓存然后正式发出ICMP Echo Request目标MAC是FPGA的MAC。FPGA的icmp_module把Type改成0、算好校验和通过mac_tx送出Echo Reply。电脑收到应答显示ping通。第一次看到time1ms那个瞬间的感受我记得特别深——从网线里的模拟波形到FPGA里二进制的帧头解析再到最终被电脑操作系统认可这一整条链路全是你亲手搭出来的成就感比跑通十个流水灯都大。5.4 用Wireshark和ILA验证数据流ping通了之后别高兴太早建议马上用Wireshark抓包把交互过程录下来验证每个字段。你打开Wireshark选对网卡过滤框里输入arp or icmp重新ping一次会看到类似的会话第一条ARP Request源MAC是电脑的目的MAC是广播地址目标IP是192.168.1.100。第二条ARP Reply源MAC是FPGA的目的MAC是电脑的发送方IP是192.168.1.100。第三条ICMP Echo Request源IP 192.168.1.10目的IP 192.168.1.100。第四条ICMP Echo Reply源IP和目的IP刚好和上一条对调。每条帧展开后你可以逐一核对源MAC、目的MAC、类型字段、协议字段是否符合预期。如果Wireshark显示ICMP checksum incorrect而逻辑上你觉得明明算对了就把ILA挂到icmp模块的校验和输出上对比Wireshark显示的应该值逐字节找位序和字节序差异。这类问题基本都能在半个小时内定位。6. 实测踩坑记录网线插上只是万里长征第一步6.1 链路灯不亮与自协商失败排查我从第一次做这个实验到最终ping通花了三个晚上一半时间耗在PHY怎么都协商不上这条路上。现象是网线插上之后电脑右下角出现一个黄色的警告小旗或者链路指示灯完全不亮。排查过程分四步先看PHY的供电和时钟是否正常再看MDIO能不能读回寄存器1尤其关注bit5Auto-Negotiation Complete是不是1。如果自协商始终不能完成很可能是PHY芯片没有正确配置成100M全双工模式或者直接因为电路上又没有强制拉高的配置引脚导致芯片一直在10M半双工模式打转。这时候可以通过MDIO写寄存器0把bit13强制100M和bit8全双工置位同时关闭自协商看看能不能协商上。如果强制100M就通说明硬件没问题问题回到了PHY的初始配置上。另一个常见原因是MDIO的PHY地址读错了。LAN8720A默认地址是0x01但如果模块上PHYAD0引脚没有拉对你读0x01地址得到全1链路状态自然不对。我最后定位到的问题是模块上的REF_CLK走线太长和旁边的网线产生了串扰导致PHY偶尔自协商成功、偶尔失败。在FPGA引脚约束里给REF_CLK加了更严格的IO标准LVCMOS33SLEWFAST板上又在CLK附近补了个22pF的电容到地一下子稳定了。这件事给我的教训是以太网设计里物理层的问题往往不是逻辑问题排查思路要敢往硬件那边摸。6.2 CRC32永远不对字节序与位序的恩怨第二个让我头大的坑是CRC32。我第一次写完CRC模块在仿真里对着网上找的CRC计算器的结果验证发现怎么都对不上。后来我把计算器的输入字节序从MSB-first切到LSB-first输出从正常序切到反转序总算凑上了——但这只是凑不理解原理换个场景还是会栽。后来我把这个逻辑彻底捋清了以太网的FCS并不是直接在字节流上按第一个字节的高位先算这种顺序做的。物理层送到MAC的数据是LSB-first的也就是说每个字节的最低位最先到达。所以CRC32计算的输入顺序本质上是按线缆上的位到达顺序来算的即每个字节的bit0、bit1……bit7依次进入。如果你手里的CRC算法是MSB-first版本就得把每个字节先按位反转再喂进去算出来的结果再按位反转发送才能和线上格式一致。这里我强烈建议你做一个自检工具找一个在线的CRC计算器比如sunshine的CRC tool选多项式0x04C11DB7、初始值0xFFFFFFFF、输入反射、输出异或0xFFFFFFFF然后算一下下面这个已知样本报文内容目的MAC 6字节源MAC 6字节类型2字节数据AA BB CC DD EE FF 00 11 22 33 44 55 08 00 45 00 00 1C算出来的CRC32如果和Wireshark抓到的FCS字段完全一致那你的输入输出方向就对了。凡是做以太网FPGA的人这个样本值得存一份。6.3 ping通但UDP收发错乱载荷解析的隐含对齐ping通了之后我满心欢喜开始做UDP。结果又出现一个诡异现象PC发给FPGA的UDP数据FPGA收到后转发的数据PC端用网络调试助手能看到但内容前几个字节总是错乱隔三差五还丢包。查到最后发现两个问题。第一个是我在mac_rx里拼字节时把CRS_DV的采样时机搞错了半拍。RMII是2位并行接口REF_CLK每个上升沿采2位但我在状态机里是先采高两位再采低两位这样凑字节导致字节边界错位偶尔把帧头当作数据收了进去。正确的做法是从SFD字节对齐之后每4个REF_CLK周期取RXD[1:0]拼成一个完整字节并且第0拍采到的两位放字节的高位。这个细节仿真时因为激励数据太规整不容易发现一上真实PHY就原形毕露。第二个是UDP载荷里的数据对齐问题。IP头固定20字节UDP头8字节所以UDP载荷起点在MAC帧里的偏移是14MAC帧头20IP头8UDP头42字节。我在做回环时直接把收到的UDP载荷整体搬进发送FIFO却忘了IP头里的Total Length和UDP头里的Length要给新报文重新赋值。PC端比较挑剔发现长度字段和实际字节数不一致直接丢弃。这种问题纯靠看波形根本抓不到必须用Wireshark一条一条对比帧结构。6.4 时序收敛与跨时钟域的小心得到了这一步整个设计的数据通路已经跑通了但综合完之后你去看Vivado的时序报告大概率红一片。原因在于PHY送来的RXD和CRS_DV是异步进入FPGA的你不能直接把它们接进系统时钟域的寄存器。正确做法是做两层同步器两个级联的Flip-Flop把RXD[1:0]和CRS_DV同步到系统时钟域。同步之后再做边沿检测和状态机切换。不过要注意RMII接口的数据其实和REF_CLK是同步的如果你的系统时钟就用REF_CLK那实际上数据是同步的不需要打两拍但如果你用PLL把50MHz倍频到了100MHz做内部逻辑时钟那跨时钟域就不可避免必须做异步FIFO或者握手。对于mac_tx发送方向FIFO的读时钟和PHY的REF_CLK之间的配合是关键。我最终的方案是mac_tx完全工作在REF_CLK时钟域协议层把数据写进发送FIFO用的是系统时钟域两侧通过一个标准异步FIFO跨接。时序报告里只剩等待input/output delay约束合理设置之后剩下的违例基本可以清零。约束上给RMII接口加上适当的input delay和output delay数值参考FPGA手册和PHY的datasheet再给路径打个set_max_delay就能收工。7. 后续进阶路线从能通到好用还差多远7.1 换更高性能的接口RGMII、光口与PCIe本文的整个设计锚定在100Mbps RMII接口上。如果你的板子要在实际项目里跑图像流720p 30fps原图大约需要130MB/s会超100M但压缩之后可以、多通道采集建议升级到千兆。千兆的物理接口主流是RGMII——数据线从2根变成4根时钟从50MHz变成125MHzDDR双边沿采样对FPGA开发者的时序要求高一个量级但原理上仍然是MAC层拼接帧、PHY层转信号你前面写好的arp/icmp/udp模块完全可以复用只需要重写mac层和phy_rgmii适配器。再往上就是SFP光口甚至PCIe了。光口通常搭配RTL8211、RTL8211FD这种支持SerDes的PHY信号走高速串行通过MGTXilinx的GTP/GTX接收。PCIe则完全是另一个世界涉及TLP报文、DMA引擎很多做FPGA的人绕了很久还在踩坑。我个人建议的路径是先把100M的以太网彻底玩透再花时间研究RGMII最后再碰高速串行。7.2 从裸逻辑到SoCZynq的ARM核能帮你分担什么如果开发板是Zynq这种带ARM处理器的SoC FPGA网络通信又是另一套玩法——Xilinx在PS侧直接集成了GEMGigabit Ethernet MAC控制器配套Linux的驱动你几乎不用自己写任何MAC逻辑。这时FPGA部分的价值从实现协议变成做数据通路加速比如通过DMA把PHY收进来的数据直接喂给PL侧的图像处理模块或者反过来把FPGA算好的结果通过AXI总线交给PS打包成TCP/UDP发出去。我也见过不少人用STM32H743FPGA做FMC通信的方案思路类似高速ADC或图像数据先进FPGA做预处理然后通过FMC并行总线交给STM32由STM32自带MACPHY完成网络通信。这样协议栈交给ARM生态FPGA专心做并行计算算是不错的低成本架构。7.3 把网络接到你自己的图像或采集模块上走到这一步你就不再是实现网络通信了而是在使用网络通信。我之后做的一个小项目是把OV5640摄像头经FPGA采集的图像先缓存到DDR3再以UDP包的形式通过PHY发到电脑上。协议栈部分用的基本上就是本文这套设计只是把udp_dispatch里固定回环的载荷换成了DDR3读出的数据同时加了简单的分包计数。电脑端用Python的socket库接收并存成bmp整个系统的开发时间比想象中短很多。还有做传感器阵列采集的朋友是把ADC的连续数据流在FPGA里做卡尔曼滤波或FIR滤波用DSP48硬核滤波结果再通过UDP发出去——这套链路里以太网模块反而成了最省心的部分。所以网络通信在FPGA的开发技能树里真正的位置不是终点而是把你之前积累的所有逻辑资源跟外界打通的那道门。这道门通了数据能进出你能做的事情一下子多出好几个量级。最后再说一个我自己的体会很多新手学FPGA网络习惯先google大段文献和论文抱着协议栈规范啃一个月还没动一行代码。我的建议恰恰相反——先让ping通再回头补协议细节。先把最小链路跑起来那种整个系统在自己控制下运转的正反馈比任何教科书都能更快带你入门。踩过坑、抓过包再回去翻RFC文档每一句都能落到具体的波形和帧上那才算真正懂了。
返回列表