纯RTL实现FPGA的ICMP Ping应答器
简介一套基于Verilog HDL的FPGA ICMP Ping功能实现源码包面向硬件设计工程师与网络协议开发者解决在FPGA上完整实现ICMP回显请求/应答处理的需求。代码覆盖以太网帧解码、IP协议解析、ICMP回显请求与应答生成以及基于状态机的数据包收发与时序控制并利用了FPGA并行处理与低延迟特性。资源共15个文件压缩包仅32KB其中含10个Verilog源文件包括顶层模块、协议处理、回显应答、测试平台等另有XDC约束文件、Shell仿真脚本和Markdown说明文档便于仿真、板级调试与后续扩展。目前已有85人学习。通过Testbench与仿真脚本读者可直接观察数据包处理时序结合约束文件可快速部署到FPGA开发板是学习FPGA网络协议栈与状态机设计的实用参考。1. 为什么要把Ping这种小事搬进FPGA做FPGA网络项目的工程师几乎都经历过这个尴尬时刻板子焊好了、电源正常、时钟稳定网口插上电脑键盘敲下ping 192.168.1.10结果没有任何回应。链路明明连着灯也是亮的问题出在FPGA里根本没有网络协议栈收到的ICMP Echo Request报文被直接丢弃了。最常见的解法是塞一个MicroBlaze或者软核进去跑lwIP但有三个让人不舒服的点第一为了一个Ping功能你要多分配不少Block RAM和LUT给CPU和协议栈第二启动时间变长软核要从Flash搬代码、初始化外设对上电立刻能响应的场景不友好第三调试复杂度直线上升——本来是硬件问题还是软件问题变得很难分清。所以我在做板卡级网络设备的时候更倾向于用纯RTL实现一个ICMP应答器代码量不过几百行Verilog逻辑资源消耗可以忽略不计上电即通延迟固定还方便在仿真里完整覆盖。这套方案适合几类人已经跑通了一个Ethernet MAC、想在用户侧加协议处理逻辑的FPGA工程师做板卡自检和产线测试需要在没有CPU的环境下验证网口连通性的硬件同事以及纯粹想搞懂ICMP细节的学生——自己动手写一遍对校验和、字节序、字段偏移的理解会非常透彻。1.1 硬实时、无CPU场景下的网络探针纯逻辑ICMP应答最常见的应用是板级自检链路。我在产线调试时经常遇到一个需求板卡刚烧完FPGA配置还没有任何应用固件这时候想确认网口的物理链路、变压器、PHY芯片、MAC IP这一整条通路是否正常。如果FPGA里固化了ICMP应答逻辑测试人员直接ping就能判断硬件通路而不需要先加载一个完整的嵌入式系统。另一个场景是链路保活。有些背板互连或远程设备需要定期探活主机端发PingFPGA端回显双方都不需要跑复杂的网络协议栈。因为响应是在硬件里直接生成的延迟通常在微秒级比软核方案低一到两个数量级而且不会因为CPU调度抖动导致丢包。还有一个我比较常用的思路把ICMP应答器作为一个调试后门。在做复杂的网络处理之前先用这么一个小模块验证MAC层收发通路确认RGMII引脚约束、时钟相位、FIFO读写时序都没问题再接着做上层功能。相当于在FPGA逻辑里先给网络通路做一次点亮LED级别的冒烟测试。1.2 不搞整套协议栈ICMP应答器就够了有人会问既然要支持网络为什么不直接集成一个成熟方案这里要分清需求边界。像Corundum那样的开源FPGA网卡项目处理链路包含完整的MAC、DMA、描述符环、甚至TCP分段卸载设计目标是跑满100Gbps的高性能网卡自然要动用大量工程资源。但如果你只需要能ping通、能回显那整套方案就是杀鸡用牛刀。纯逻辑实现Ping回显本质上只需要两个功能块一个是ARP应答处理谁有这个IP地址的广播查询另一个是ICMP Echo Reply处理Ping的请求报文。这两个功能加起来代码量很小不需要操作系统不需要DMA不需要中断控制器。在Xilinx 7系列这种主流器件上整套逻辑跑在125MHz的千兆以太网时钟域几乎不占资源。1.3 一个被不少人忽略的前提ARP也得管很多第一次做FPGA Ping功能的同学会踩同一个坑只写了ICMP回显逻辑上板测Ping死活不通。原因在于主机在发出ICMP Echo Request之前会先发一个ARP广播询问这个IP地址对应的MAC地址是谁。如果FPGA不对ARP请求做应答主机根本不知道目标MAC地址是什么ICMP报文压根不会发到FPGA这边。所以一个完整的硬件Ping功能实际包含两部分ARP应答模块加ICMP回显模块。本文重点讲ICMP部分但我会在实战章节里把ARP的坑单独列出来因为这个问题太典型了。2. 回显一个ICMP报文前必须算清的两笔账2.1 报文字段的搬运清单先理清一次完整的Ping交互过程。主机发出的Echo Request帧从内到外依次是以太网头14字节、IPv4头20字节、ICMP头8字节、载荷Windows默认ping是32字节Linux默认是48或56字节。FPGA收到这帧数据后要做的事情不是原样返回而是要改掉几个字段再重新算校验和。我把请求帧和应答帧的字段差异整理成了一张表这是整个设计最核心的搬运清单字段请求帧收到应答帧发出处理方式目的MACFPGA的MAC主机的MAC交换源MAC主机的MACFPGA的MAC交换EtherType0x08000x0800不变IP源地址主机的IPFPGA的IP交换IP目的地址FPGA的IP主机的IP交换IP标识、标志、片偏移任意值复制请求值原样拷贝TTL任意值复制请求值或填64建议原样拷贝IP协议号0x01(ICMP)0x01(ICMP)不变IP头校验和请求值重新计算重算ICMP类型8(Echo Request)0(Echo Reply)修改ICMP代码00不变ICMP标识符、序列号请求值复制请求值原样拷贝ICMP校验和请求值重新计算重算载荷请求内容同样内容原样拷贝这张表说明了一个重要事实Echo应答不是简单的交换MAC地址就能完成的。IP头校验和覆盖整个IPv4头只要源IP和目的IP变了校验和必须重算ICMP校验和覆盖类型、代码、校验和字段以及后面的所有载荷类型从8改成0校验和也必须跟着变。这两个校验和是回显逻辑里最容易被忽视、也最容易算错的地方。2.2 校验和教科书算法与硬件实现IP和ICMP用的都是同一个算法16位反码和的反码。具体步骤是把要校验的数据按16位一组切分顺序累加累加过程中如果产生进位把进位回卷到最低位这叫end-around carry全部累加完成后按位取反得到校验和。硬件实现时有一个细节必须处理两个16位数相加结果可能是17位。比如0xFFFF加0xFFFF得0x1FFFE这个进位不能丢掉要加回到低16位上去。我用Verilog实现时的标准写法是wire [16:0] sum_tmp acc din_word; acc sum_tmp[15:0] sum_tmp[16]; // 进位回卷累加完成后做一次取反得到最终校验和assign checksum ~acc;这里有个容易混淆的点IP头校验和只覆盖20字节的IPv4头不包含以太网头而ICMP校验和覆盖整个ICMP报文——从类型字段开始一直到载荷的最后一个字节中间包括校验和字段本身。计算ICMP校验和时校验和字段要按0处理这也是课程里经常考、工程里经常错的地方。2.3 重构校验和的三种思路明确了要算什么接下来是怎么算的问题。我见过三种做法各有适用场景第一种是纯组合逻辑全量重算。从寄存器里读出应答帧头部所有字段拼成20字节IP头和若干字节ICMP报文用组合逻辑做加法树一次算出校验和。优点是延迟低缺点是很费组合逻辑而且报文长度一旦可变组合逻辑的规模很难控制。第二种是增量更新incremental update。利用校验和的线性性质只对变化的字段做修正。比如ICMP类型从8变成0原来类型加代码这个16位字是0x0800现在变成0x0000那么新校验和可以用旧校验和推算出来。公式是新校验和 ~(~旧校验和 ~旧值 新值)。这个思路很巧妙但在FPGA里实现时容易出边界问题后面我会专门讲这个坑。第三种是两遍读BRAM计算也是我在这个设计里采用的方式。把收到的请求帧整体存进BRAM应答时先复习一遍把要输出的应答帧字节流完整过一遍校验和累加器但此时校验和字段输出0算完后再真正把应答帧发出去此时校验和字段填入上一步算好的值。因为Ping帧很小读取一遍BRAM只要几十个周期对性能毫无影响但逻辑实现简单直接代码可读性也高。这就是典型的用存储器换逻辑复杂度的思路。3. RTL实现解析、缓存、重组三个模块3.1 顶层架构与帧存储方案假设你的MAC IP已经把前导码和CRC剥掉了用户侧拿到的是从目的MAC开始的完整以太网帧接口是8位AXI-Stream位宽转换、RGMII时序这些都不用管我们的设计专注在字节流处理上。整体分三个部分接收解析状态机、双口BRAM帧缓存、发送重组状态机。选择BRAM缓存整帧而不是边收边发有两个原因。第一应答帧的IP和ICMP校验和依赖整个ICMP报文的内容如果边收边发校验和字段必须在报文末尾才能确定而它在报文头部这就产生了矛盾第二交换MAC和IP地址需要先知道请求帧的源MAC和源IP也就是要等到IP头解析完才拿得到。虽然可以在接收过程中把源字段存进寄存器但载荷还得原样返回所以无论如何都得把载荷存下来。既然都要存干脆整帧存入BRAM逻辑最简单。顶层模块的接口可以这样设计module icmp_ping #( parameter [47:0] MY_MAC 48h00_11_22_33_44_55, parameter [31:0] MY_IP 32hC0_A8_01_02 // 192.168.1.2 )( input wire clk, input wire rst_n, // RX: AXI-Stream从MAC来 input wire [7:0] rx_tdata, input wire rx_tvalid, input wire rx_tlast, // TX: AXI-Stream到MAC output reg [7:0] tx_tdata, output reg tx_tvalid, output reg tx_tlast, input wire tx_tready );MY_MAC和MY_IP做成参数方便不同板卡直接改参数例化不用动逻辑内部代码。3.2 接收解析状态机逐字节打本文还有配套的精品资源点击获取