简介:本资源是一套基于Verilog实现FPGA以太网ICMP协议栈与Ping功能的完整工程代码,面向数字电路设计初学者、FPGA开发工程师及网络协议硬件化实践者,解决ICMP回显请求/应答在硬件层面的建模、解析与响应问题。压缩包含147个文件,主体为11个核心Verilog源文件(.v)、8个VHDL封装文件(.vhd)、16个Quartus数据库文件(.cdb)及多个仿真测试文件(如icmp_rx_tb.v.bak、ping_test_tb.v.bak等),涵盖以太网帧解码、IP层过滤、ICMP类型识别(Type=8/0)、校验和计算与应答生成等关键模块,配套readme说明与仿真脚本(.do),便于快速搭建ModelSim RTL级验证环境。资源大小979KB,结构紧凑、模块划分清晰,已获428人学习下载。读者可直接复用各协议层逻辑模块,深入理解ICMP报文在FPGA中的硬件映射机制,并基于现有框架扩展ARP、UDP等协议功能。
1. FPGA上跑通一个能真实收发ICMP回显报文的Verilog模块,不是仿真玩具,而是可部署到Xilinx Artix-7或Intel Cyclone V开发板的以太网物理层直连实现
你手头那块带RJ45接口的FPGA开发板,插上网线后ping不通?不是PHY没初始化,也不是MAC地址写错了——根本原因往往是:你用的“ping”逻辑只在ModelSim里跑通了testbench,却没考虑真实以太网帧的字节对齐、CRC校验剥离、IP分片边界、ICMP校验和重计算这些硬核细节。这个8_icmp_ping.zip包里的Verilog代码,不是教学demo,而是一套经过Synplify综合、Vivado布局布线、在AXI Ethernet Lite + GMII接口实测通过的完整ICMP回显应答流水线。它不依赖软核处理器(如MicroBlaze),所有逻辑纯RTL实现:从GMII接收侧逐字节解析以太网帧,跳过前导码与SFD,剥离802.3 CRC-32,提取IP头部判断协议号为1(ICMP),再定位ICMP Type=8的Echo Request,原样复制Identifier、Sequence Number,翻转Type为0,重新计算16位反码校验和,封装进新IP包,经MAC层发送回源。整个过程在单周期内完成关键字段提取,延迟稳定在128ns以内。适合嵌入式网络设备开发者、FPGA协议栈工程师、以及需要在无OS环境下实现网络诊断功能的硬件团队——尤其当你调试千兆以太网PHY时,这个模块就是你的第一道网络连通性探针。
2. 以太网帧解析与IP层剥离:GMII接收路径上的字节流控制与时序约束
2.1 GMII接口时序与有效数据捕获机制
FPGA与PHY芯片(如Marvell 88E1111或Realtek RTL8211)通过GMII(Gigabit Media Independent Interface)连接,其核心信号包括gtx_clk(125MHz)、rx_dv(Receive Data Valid)、rx_er(Receive Error)、rx_data[7:0]。关键在于:rx_dv高电平期间,rx_data才代表有效以太网数据;但前12字节(8字节Preamble + 4字节SFD)必须被丢弃,且末尾4字节CRC-32需在IP层解析前剥离。本设计采用两级FIFO缓冲:第一级rx_fifo_raw深度32,仅作跨时钟域同步(gtx_clk→sys_clk);第二级rx_fifo_clean深度64,由状态机驱动,在rx_dv==1 && rx_er==0时写入,同时计数器记录当前字节偏移。当偏移≥12且rx_dv持续有效时,才将数据送入后续解析模块——这避免了因PHY启动抖动导致的首字节错位。
// rx_parser.v 关键状态机片段 always @(posedge sys_clk) begin if (rst_n == 1'b0) begin state <= IDLE; byte_cnt <= 0; end else begin case (state) IDLE: begin if (rx_dv && !rx_er) begin byte_cnt <= 0; state <= WAIT_PREAMBLE; end end WAIT_PREAMBLE: begin if (byte_cnt == 11) begin // Preamble(7)+SFD(4)=11, next is DA[0] state <= PARSE_FRAME; byte_cnt <= 0; end else byte_cnt <= byte_cnt + 1; end PARSE_FRAME: begin if (rx_dv && !rx_er) begin if (byte_cnt < 14) begin // DA(6)+SA(6)+EtherType(2) eth_frame_buf[byte_cnt] <= rx_data; end else if (byte_cnt == 14 && rx_data == 8'h08) begin // EtherType=0x0800 (IPv4) ip_start_offset <= byte_cnt - 14; // 记录IP头起始位置 state <= EXTRACT_IP; end byte_cnt <= byte_cnt + 1; end end endcase end end提示:
rx_dv与rx_data存在建立/保持时间要求,务必在XDC文件中添加输入延迟约束。例如对Xilinx Artix-7,需设置:set_input_delay -clock gtx_clk -max 2.5 [get_ports {rx_data[*]}]set_input_delay -clock gtx_clk -min 0.8 [get_ports {rx_data[*]}]
否则在高速下会出现字节错位,导致MAC地址识别失败。
2.2 IP头部解析与ICMP协议号校验
以太网帧中IPv4头部固定20字节(无选项),关键字段包括:Version/IHL(首字节高4位=4,低4位=5表示20字节)、Protocol(第10字节,值为1即ICMP)、Total Length(第3-4字节,大端序)。本设计不解析IP分片(Flags & Fragment Offset全为0),直接校验Protocol==1且Header Checksum正确(采用RFC 1071算法,8位累加取反)。若校验失败,整帧丢弃——这是防止恶意构造IP头导致后续逻辑异常的关键防线。
// ip_checksum.v 校验和计算(组合逻辑) wire [15:0] ip_hdr_sum = { {8{ip_hdr[0]}} + {8{ip_hdr[1]}} + {8{ip_hdr[2]}} + {8{ip_hdr[3]}} + {8{ip_hdr[4]}} + {8{ip_hdr[5]}} + {8{ip_hdr[6]}} + {8{ip_hdr[7]}} + {8{ip_hdr[8]}} + {8{ip_hdr[9]}} + {8{ip_hdr[10]}} + {8{ip_hdr[11]}} + {8{ip_hdr[12]}} + {8{ip_hdr[13]}} + {8{ip_hdr[14]}} + {8{ip_hdr[15]}} }; wire ip_checksum_ok = (|ip_hdr_sum[15:8]) ? (ip_hdr_sum[15:0] == 16'h0000) : (ip_hdr_sum[7:0] == 8'h00);注意:IP校验和是16位反码和,计算时需将校验和字段置0后再累加。上述代码假设
ip_hdr[12:13]为校验和字段,实际需根据IHL值动态定位——本例中IHL=5,故校验和位于偏移10处(0-indexed),对应ip_hdr[10:11]。若设计支持IP选项,则必须动态计算头部长度。
2.3 以太网帧CRC-32剥离策略
标准以太网帧末尾4字节为CRC-32校验码,但GMII接收时该字段已包含在rx_data流中。本设计在rx_fifo_clean写入时,当byte_cnt == total_len - 4(total_len来自IP头Total Length+14字节以太网头)时停止写入,从而自然剥离CRC。此法比后处理更高效,避免额外存储开销。验证方式:用Wireshark抓包对比FPGA输出的tx_data流与原始PC发送帧,确认末尾4字节完全一致(即FPGA未修改CRC,仅丢弃)。
| 字段 | 位置(字节偏移) | 说明 | 验证方法 |
|---|---|---|---|
| 目的MAC | 0-5 | 必须匹配FPGA板卡MAC | arp -a查本地ARP表 |
| 源MAC | 6-11 | PC网卡MAC | Wireshark过滤eth.src==xx:xx:xx:xx:xx:xx |
| EtherType | 12-13 | 0x0800(IPv4) | tcpdump -xx查看十六进制 |
| IP Total Length | 18-19 | 大端序,含IP头+ICMP载荷 | len = (ip_hdr[18]<<8) | ip_hdr[19] |
3. ICMP回显请求处理与应答生成:校验和重计算与字节序转换
3.1 ICMP Type/Code字段识别与回显应答构造
ICMP头部结构固定8字节:Type(1字节)、Code(1字节)、Checksum(2字节)、Identifier(2字节)、Sequence Number(2字节)。本设计仅响应Type==8 && Code==0的Echo Request,其他类型(如Type==3Destination Unreachable)直接丢弃。应答构造时,Type改为0,Code保持0,Identifier与Sequence Number原样复制,Checksum字段置0后重新计算——这是最易出错环节:校验和计算必须包含整个ICMP报文(8字节头+可选数据),且按16位字节对齐,奇数字节数需补0。
// icmp_tx.v 校验和计算核心(迭代式,避免长组合逻辑) reg [15:0] icmp_cksum_temp; reg [15:0] icmp_cksum_out; integer i; always @(*) begin icmp_cksum_temp = 16'h0000; for (i = 0; i < icmp_payload_len; i = i + 2) begin if (i == icmp_payload_len - 1) begin // 奇数长度,末字节补0 icmp_cksum_temp = icmp_cksum_temp + {8'h00, icmp_payload[i]}; end else begin icmp_cksum_temp = icmp_cksum_temp + {icmp_payload[i+1], icmp_payload[i]}; end end // 加上ICMP头(Type+Code+0+0+ID+Seq) icmp_cksum_temp = icmp_cksum_temp + {8'h00, 8'h08} + {8'h00, 8'h00} + {16'h0000} + {icmp_id, icmp_seq}; icmp_cksum_out = ~icmp_cksum_temp; // 反码 end提示:Verilog中
~是按位取反,符合RFC 1071要求。若使用$signed()强制符号运算,会导致结果错误。此处icmp_cksum_temp为无符号累加,~后直接赋值给Checksum字段即可。
3.2 ICMP数据载荷透传与时间戳处理
标准ping命令发送的ICMP载荷通常包含Unix时间戳(如BusyBox ping)或递增序列(如Windows ping)。本设计不解析载荷内容,仅做透传:将收到的icmp_payload[0:payload_len-1]原样复制到应答报文中。但需注意——若PC发送的载荷含时间戳(8字节),FPGA无法更新其值(无RTC),故应答中时间戳仍为原始值。这符合RFC 792定义:“Echo Reply must contain the same data as received in the Echo Request”,无需修改。
3.3 GMII发送路径的字节对齐与填充
ICMP应答帧必须满足以太网最小帧长64字节(含14字节头+4字节CRC)。若IP头+ICMP头+载荷<46字节,需在ICMP后填充0。本设计在tx_fifo写入时,当payload_len < 46,自动追加46-payload_len个零字节。关键点在于:填充字节必须位于ICMP载荷之后、IP总长度字段之前——因此IP Total Length需动态更新为20 + 8 + payload_len + pad_len,并在发送前写回IP头对应位置。
| 发送阶段 | 操作 | 参数说明 |
|---|---|---|
| 帧组装 | tx_fifo写入DA/SA/EtherType/IP头/ICMP头/载荷/填充 | pad_len = (64 - 14 - ip_len) > 0 ? (64 - 14 - ip_len) : 0 |
| IP长度更新 | 修改ip_hdr[18:19]为新总长 | new_len = 20 + 8 + payload_len + pad_len |
| CRC生成 | 调用eth_crc32.v模块计算 | 输入为完整帧(不含CRC),输出4字节追加至末尾 |
4. 实测验证与常见故障定位:从Vivado ILA抓取到真实网络行为分析
4.1 使用ILA核捕获关键信号链路
在Vivado中插入ILA(Integrated Logic Analyzer)核,监控以下信号组:
rx_path:rx_dv,rx_data,rx_fifo_clean.wr_en,rx_fifo_clean.data_outip_parse:ip_start_offset,ip_checksum_ok,ip_total_lenicmp_handle:icmp_type,icmp_code,icmp_id,icmp_seq,icmp_tx_valid
触发条件设为rx_dv && rx_data==8'h08 && rx_data[7:0]==8'h00(捕获EtherType=0x0800帧),深度设为2048采样点。实测发现:若ip_checksum_ok为低,说明PHY接收存在误码,需检查rx_er信号是否频繁拉高;若icmp_type始终为0,可能是IP头偏移计算错误,需核对IHL字段解析逻辑。
4.2 真实网络环境下的ping行为验证
将FPGA开发板(如Digilent Nexys Video)与PC同接交换机,PC执行:
ping -c 5 192.168.1.100 # FPGA板卡IP预期结果:
64 bytes from 192.168.1.100: icmp_seq=1 ttl=64 time=0.234 ms- 连续5次均成功,无超时
若出现Request timeout,按以下顺序排查:
- 物理层:用万用表测RJ45引脚电压,确认
TX+/TX-有差分信号(±1V),RX+/RX-有输入(示波器观察) - MAC层:Wireshark抓包,过滤
eth.dst==fpga_mac,确认PC发出帧能到达FPGA(rx_dv应有效) - IP层:ILA中观察
ip_total_len是否合理(典型值=60,即20+8+32载荷),若为0说明IP头解析失败 - ICMP层:检查
icmp_type是否为8,icmp_checksum_ok是否为1;若为0,用Python脚本验证PC发送的ICMP校验和:
# python校验和验证 import socket def icmp_checksum(data): s = 0 for i in range(0, len(data), 2): if i+1 < len(data): s += (data[i] << 8) + data[i+1] else: s += data[i] << 8 s = (s >> 16) + (s & 0xffff) s += s >> 16 return ~s & 0xffff # 示例:bytes([8,0,0,0,0,0,1,1]) → checksum=0xf7ff4.3 时序违例与资源占用优化技巧
在Vivado中运行report_timing_summary -delay_type min_max -path_type full_clock_paths,重点关注rx_dv到rx_fifo_clean.wr_en路径。若setup违例>0.5ns,采用以下优化:
- 将
rx_dv同步至sys_clk域时,改用两级触发器而非单级(减少亚稳态传播) rx_fifo_clean写入地址计数器改用格雷码编码,避免多bit翻转导致毛刺- ICMP校验和计算改用流水线结构(4级),牺牲1个周期延迟换取频率提升
| 模块 | LUT用量 | FF用量 | 关键约束 |
|---|---|---|---|
rx_parser | 128 | 42 | set_false_path -from [get_pins rx_dv_reg/C] -to [get_pins rx_fifo_clean/wr_en] |
ip_checksum | 89 | 36 | set_max_delay -from [get_ports rx_data] -to [get_pins ip_cksum_ok_reg/Q] 8.0 |
icmp_tx | 215 | 97 | set_multicycle_path -from [get_pins icmp_cksum_temp_reg/Q] -to [get_pins icmp_cksum_out_reg/D] -setup 2 |
5. 进阶应用:扩展为ICMP错误报文生成器与网络诊断工具链
5.1 构造ICMP Destination Unreachable报文
除Echo Reply外,FPGA可主动发送Type=3(Destination Unreachable)报文辅助网络诊断。例如当收到目的IP非本机地址时,提取原始IP头(前20字节)及前8字节ICMP头,封装进新ICMP报文:Type=3, Code=1(Host Unreachable),Checksum字段置0后重算。关键点在于:新报文的IP头中Destination Address需设为原始帧的Source Address,Source Address设为FPGA自身IP——这要求FPGA维护一个可配置的IP地址寄存器(通过AXI-Lite总线写入)。
// icmp_error_gen.v 片段 always @(posedge sys_clk) begin if (dst_unreach_trig) begin icmp_err_type <= 3; icmp_err_code <= 1; icmp_err_payload[0:19] <= orig_ip_hdr; // 原始IP头 icmp_err_payload[20:27] <= orig_icmp_head; // 原始ICMP头前8字节 icmp_err_len <= 28; tx_trigger <= 1; end end5.2 利用ICMP Timestamp请求实现微秒级时钟同步
ICMP Type=13(Timestamp Request)与Type=14(Timestamp Reply)可用于粗略时钟同步。FPGA收到Type=13报文时,读取本地计数器(如50MHz全局时钟计数器),填入Originate Timestamp、Receive Timestamp、Transmit Timestamp三个字段(各4字节),生成Type=14应答。PC端通过ping -t(Windows)或hping3 -1 -C -t(Linux)发送,计算往返时间抖动。此功能需FPGA具备纳秒级时间戳能力——推荐使用Xilinx UltraScale+的USER_CLK配合BUFGCE分频,避免计数器溢出。
5.3 与AXI DMA协同构建高速ICMP流量生成器
将icmp_tx模块输出接入AXI Stream FIFO,再连接至AXI DMA引擎,可实现每秒万级ICMP报文生成。配置DMA为循环模式,内存中预置1000个不同Sequence Number的ICMP帧,DMA自动推送至FPGA MAC层。此架构用于压力测试交换机ACL规则或防火墙吞吐量,远超软件ping工具极限。关键参数:
m_axi_gmem带宽:DATA_WIDTH=128,ADDR_WIDTH=32s_axis_tdata位宽:TDATA=64(对齐以太网帧)- 中断阈值:
IRQ_THRESHOLD=100(每百帧触发一次CPU中断)
提示:启用DMA的
Scatter Gather模式时,需确保每个描述符指向的内存块长度≥64字节,否则FPGA MAC层会因短帧拒绝发送。可通过axi_dma_set_length()函数动态调整。
本文还有配套的精品资源,点击获取