FPGA硬核实现ICMP Ping协议栈(Verilog纯RTL)
2026/9/13 1:06:59 网站建设 项目流程

简介:本资源是面向FPGA开发初学者与数字通信实践者的Verilog网络协议学习项目,聚焦以太网环境下ICMP协议的硬件实现与Ping功能验证。通过该工程,读者可系统掌握以太网帧解析、IPv4头部识别、ICMP回显请求/应答(Type 8/0)报文生成与校验和计算等关键逻辑设计方法,适用于课程设计、毕业设计及嵌入式网络接口开发场景。压缩包共147个文件,含11个核心Verilog源码(.v)、8个VHDL模块(.vhd)、16个Quartus编译数据库(.cdb)及多个测试平台文件(如icmp_rx_tb.v.bak、ping_test_tb.v.bak),辅以readme说明与仿真脚本(.do),整体大小979KB,结构完整、层次清晰。目前已有428人学习下载,提供从协议分析、模块划分、RTL编码到ModelSim仿真验证的全流程参考,特别适合理解软硬协同视角下的网络协议栈硬件化实现路径。

1. 在 FPGA 上硬核实现 ICMP Ping 协议栈:不是调用 API,而是从以太网帧头开始逐字节构造与解析

你手头有一块带千兆以太网 PHY 的 FPGA 开发板(比如 Xilinx Zynq-7000 或 Intel Cyclone V),想让它主动向局域网内某台设备(如 192.168.1.100)发起 ICMP Echo Request,并准确接收、校验、解析返回的 Echo Reply——但你不打算用 MicroBlaze 软核跑 lwIP,也不依赖任何现成 IP 核的“Ping 功能按钮”。你要的是:纯 Verilog 实现的、可综合、可调试、可嵌入任意以太网 MAC 控制器下游的 ICMP 协议逻辑。这个8_icmp_ping.zip_fpga命名暗示了它极可能是一个精简、模块化、面向教学或轻量级网络诊断的参考设计:8 字节 ICMP 头(Type+Code+Checksum+Identifier+Sequence)是 Echo Request/Reply 的最小有效载荷边界;.zip表明它被封装为可开箱即用的工程包;fpga和重复出现的verilog强调其 RTL 层实现属性。它解决的不是“能不能联网”,而是“如何在无操作系统、无 TCP/IP 协栈、无动态内存管理的裸机 FPGA 环境下,让硬件自己理解并生成符合 RFC 792 规范的 ICMP 报文”。适合 FPGA 初学者理解协议分层,也适合工业现场设备开发者构建低延迟网络心跳检测模块。

2. 为什么必须绕过软核与 IP 核:从以太网帧到 ICMP 报文的四层硬解析路径

2.1 协议栈裁剪决策:为何只做 ICMP,且仅支持 Echo 类型

在资源受限的 FPGA 中实现完整 TCP/IP 协议栈是低效且危险的。ICMP 是网络层协议,不依赖传输层端口,其 Echo Request/Reply 报文结构固定(RFC 792 定义)、校验和算法明确(RFC 1071)、交互模式简单(请求-应答一对一)。相比 TCP 的三次握手、滑动窗口、重传机制,ICMP 的状态机仅需维护“发送序号”与“等待应答超时”两个变量。8_icmp_ping.zip中的“8”直接指向 ICMP 报文头部长度:Type (1B) + Code (1B) + Checksum (2B) + Identifier (2B) + Sequence Number (2B) = 8 字节。后续可选携带时间戳或数据载荷(如 32 字节填充),但核心逻辑完全由这 8 字节驱动。放弃对 Destination Unreachable、Time Exceeded 等复杂类型的支持,是保证逻辑面积 < 2000 LUT、时序收敛于 100MHz 的关键取舍。常见做法是:将 ICMP 模块置于 MAC 层(如 Xilinx GMII 或 Intel Avalon-ST Ethernet)的 TX/RX 数据通路末端,直接对接 MAC 的tx_data/rx_data总线,跳过所有中间协议处理。

提示:不要试图在 Verilog 中实现 ARP 请求来获取目标 MAC 地址。8_icmp_ping.zip类设计默认采用静态配置——目标 IP 对应的 MAC 地址(如44:31:92:xx:xx:xx)需在顶层模块中作为参数(parameter TARGET_MAC = 48'h443192_000001;)或通过 AXI-Lite 接口预写入寄存器。ARP 属于链路层,其广播特性与 FPGA 硬件的单播收发模型存在根本冲突,强行实现会显著增加逻辑复杂度与调试难度。

2.2 以太网帧封装:MAC 层到 IP 层的硬连接点

ICMP 报文必须封装在 IPv4 数据报中,而 IPv4 又必须承载于以太网帧内。8_icmp_ping.zip的 Verilog 实现必然包含三个紧耦合子模块:eth_tx_frame_gen(以太网帧生成器)、ip_pkt_gen(IPv4 包生成器)、icmp_pkt_gen(ICMP 报文生成器)。它们的调用顺序是严格流水线化的:

// 示例:顶层发送逻辑片段(简化) always @(posedge clk) begin if (ping_start && !tx_busy) begin // Step 1: 构造 ICMP Echo Request icmp_data <= {8'h08, 8'h00, 16'h0000, 16'h1234, 16'h0001}; // Type=8, Code=0, chk=0, id=0x1234, seq=1 icmp_len <= 8 + PING_DATA_LEN; // 8-byte header + optional data icmp_valid <= 1'b1; end // Step 2: 封装为 IPv4 包(固定字段:Version=4, IHL=5, TOS=0, TTL=64) ip_total_len <= 20 + icmp_len; // IPv4 header (20B) + ICMP payload ip_src_addr <= {32'hC0A80101}; // 192.168.1.1 (FPGA) ip_dst_addr <= {32'hC0A80164}; // 192.168.1.100 (target) ip_proto <= 8'h01; // Protocol = ICMP (1) // Step 3: 封装为以太网帧(DA=TARGET_MAC, SA=LOCAL_MAC, EtherType=0x0800) eth_da <= TARGET_MAC; eth_sa <= LOCAL_MAC; eth_type <= 16'h0800; end

关键参数说明:

  • TARGET_MAC:目标设备 MAC 地址,必须与目标 IP 绑定,否则帧将被交换机丢弃;
  • LOCAL_MAC:FPGA 自身 MAC 地址,需确保在局域网内唯一;
  • eth_type = 16'h0800:标识载荷为 IPv4,这是以太网帧解析的起点;
  • ip_proto = 8'h01:IPv4 头部的 Protocol 字段,告知接收方上层协议为 ICMP;
  • ip_total_len:必须精确等于 IPv4 头长度(20)加 ICMP 报文总长度(8 + data),否则接收方校验失败。

2.3 ICMP 校验和计算:Verilog 中的 16 位反码加法实现

ICMP 校验和是整个报文(包括头部和数据)按 16 位字进行反码加法的结果。8_icmp_ping.zip中的icmp_checksum.v模块通常采用迭代累加方式,而非一次性展开(后者消耗过多 LUT)。核心逻辑如下:

// icmp_checksum.v 关键逻辑(简化) reg [15:0] sum; reg [15:0] checksum_out; // 每个 16-bit word 进行累加 always @(posedge clk) begin if (rst) begin sum <= 16'h0000; checksum_out <= 16'h0000; end else if (calc_start) begin sum <= sum + data_in; // data_in is 16-bit word from ICMP packet end else if (calc_done) begin // 反码:先取反,再加 1?不,RFC 1071 要求的是 16-bit one's complement sum // 正确做法:sum = ~(sum[15:0] + sum[31:16]),但需处理进位 checksum_out <= ~({sum[15:0] + sum[31:16]}); // 伪代码,实际需多级加法器 end end

注意:Verilog 中实现 16 位反码加法需特别注意进位传播。常见错误是直接~(sum),这忽略了高位进位合并。正确做法是:将 32 位累加结果的高 16 位与低 16 位相加(sum_h + sum_l),若结果产生进位,则再加 1;最终对 16 位结果取反。8_icmp_ping.zip中的校验和模块必须在发送前将checksum_out写入 ICMP 头部第 2-3 字节,并在接收时对整个 ICMP 报文(含此 checksum 字段)重新计算,比对结果是否为16'h0000

3. 从接收以太网帧到判定 Ping 成功:RX 通路的逐层剥离与状态机设计

3.1 以太网帧接收状态机:过滤、提取、交付

FPGA 的以太网 RX 通路接收到的是原始字节流。8_icmp_ping.zipeth_rx_parser.v模块必须完成三重过滤:

  1. MAC 层过滤:检查目的 MAC 地址是否匹配LOCAL_MAC或广播地址ff:ff:ff:ff:ff:ff
  2. EtherType 过滤:确认eth_type == 16'h0800,排除 ARP、LLDP 等其他协议帧;
  3. IP 层过滤:解析 IPv4 头部,验证version == 4ihl >= 5protocol == 1(ICMP)、dst_addr == LOCAL_IP

只有同时满足以上条件的帧,才将其有效载荷(即 IPv4 数据部分)送入ip_rx_parser。该模块进一步提取ip_total_len字段,截取对应长度的数据,并验证 IP 头部校验和(可选,因 FPGA 通常信任上游 MAC 的 CRC 校验)。最终,纯净的 ICMP 报文(含 8 字节头部)被送入icmp_rx_handler

3.2 ICMP Echo Reply 解析:识别合法应答的四个硬性条件

icmp_rx_handler.v是判断 Ping 是否成功的最终裁判。它不关心 ICMP 数据内容,只验证以下四点:

  • Type 字段必须为 0:表示 Echo Reply(Echo Request 是 Type=8);
  • Code 字段必须为 0:ICMP Echo 类型无子码;
  • Identifier 字段必须匹配发送时的值(如0x1234):防止不同 Ping 会话混淆;
  • Sequence Number 字段必须匹配发送时的值(如0x0001):确保是本次请求的应答,而非历史残留。
// icmp_rx_handler.v 中的关键匹配逻辑 wire is_echo_reply = (icmp_type == 8'h00) && (icmp_code == 8'h00); wire id_match = (icmp_id == tx_id_reg); // tx_id_reg is latched from send path wire seq_match = (icmp_seq == tx_seq_reg); // tx_seq_reg is latched from send path assign ping_success = is_echo_reply && id_match && seq_match && checksum_ok;

提示:“ping wwwbaiducom未知的”类错误在此阶段即可定位:若is_echo_reply为假,说明收到的不是 Echo Reply(可能是 Destination Unreachable 或超时丢包);若id_matchseq_match为假,说明网络中存在报文乱序或重复(虽罕见,但在高负载网络中可能发生),此时应丢弃该报文并继续等待。

3.3 超时与重试机制:基于计数器的纯硬件定时方案

8_icmp_ping.zip不使用软件循环延时,而是依赖 FPGA 内部高频时钟(如 100MHz)驱动计数器。典型设计包含:

  • ping_timer_cnt:从发送ping_start信号起计数,满TIMEOUT_VAL(如100_000_000对应 1 秒)后置位timeout_flag
  • retry_counter:记录已发送次数(初始为 0),每次超时后自增,达MAX_RETRY=3则停止;
  • ping_state:三态机 —IDLESENDINGWAITING
// 状态机片段 always @(posedge clk or posedge rst) begin if (rst) state <= IDLE; else case (state) IDLE: if (user_ping_req) state <= SENDING; SENDING: state <= WAITING; WAITING: begin if (ping_success) state <= IDLE; // success! else if (timeout_flag && (retry_cnt < MAX_RETRY)) begin retry_cnt <= retry_cnt + 1; state <= SENDING; // re-send end else if (timeout_flag) state <= IDLE; // give up end endcase end

关键参数说明:

  • TIMEOUT_VAL:需根据网络物理距离设定。局域网内建议 100ms(10,000,000 cycles @100MHz);跨路由器可达 500ms;
  • MAX_RETRY:设为 3 是平衡可靠性与响应时间的常见选择;
  • user_ping_req:来自用户按键或 AXI-Lite 写入的脉冲信号,触发一次 Ping 流程。

4. 验证与调试:用 Wireshark 抓包对比 + FPGA 内部信号观测双轨法

4.1 Wireshark 抓包:确认 FPGA 发出的帧完全合规

将 FPGA 开发板与 PC 通过交换机互联,在 PC 上启动 Wireshark,过滤条件设为ip.addr == 192.168.1.100 && icmp。成功 Ping 时,应看到一对严格配对的帧:

  • Frame NSource: FPGA_MAC, Destination: TARGET_MACProtocol: ICMPInfo: Echo (ping) requestData: 32 bytes(若含数据);
  • Frame N+1Source: TARGET_MAC, Destination: FPGA_MACProtocol: ICMPInfo: Echo (ping) replyData: 32 bytes

重点检查:

  • Ethernet II HeaderDestinationSourceMAC 正确,Type: IPv4 (0x0800)
  • IPv4 HeaderVersion: 4Header Length: 20 bytesProtocol: ICMP (1)Source: 192.168.1.1Destination: 192.168.1.100Header checksum: 0xXXXX(应为有效值);
  • ICMP HeaderType: 8 (Echo Request)/0 (Echo Reply)Code: 0Checksum: 0xXXXX(发送帧的 checksum 必须非零,且 Wireshark 应标记为Good)。

提示:若 Wireshark 显示ICMP checksum: 0x0000 (unverified),说明 FPGA 计算的 checksum 有误,需回查icmp_checksum.v的进位处理逻辑;若显示Malformed Packet,则 IPv4 头部长度或总长度字段错误。

4.2 FPGA 内部信号观测:用 ILA(Integrated Logic Analyzer)抓取关键节点

Vivado 或 Quartus 的 ILA 核是调试8_icmp_ping.zip的核心武器。建议捕获以下信号组:

信号名位宽说明触发条件
tx_state2-bit发送状态机(IDLE/SENDING/WAITING)tx_state == SENDING
icmp_tx_data8-bit正在发送的 ICMP 字节流tx_valid && tx_ready
rx_state2-bit接收状态机(IDLE/PARSING/SUCCESS)rx_state == SUCCESS
ping_success_pulse1-bit成功标志脉冲ping_success_pulse
timeout_flag1-bit超时标志timeout_flag

设置 ILA 触发条件为ping_success_pulse == 1'b1,捕获前后 128 个周期的波形。理想情况下,应看到:

  • tx_stateIDLESENDINGWAITING的清晰跃迁;
  • icmp_tx_dataSENDING状态下连续输出08 00 ?? ?? 12 34 00 01 ...(Type=8, Code=0, chk=??, id=0x1234, seq=1);
  • rx_state在收到合法 Reply 后跳至SUCCESS,且ping_success_pulse产生一个时钟周期宽度的高电平。

4.3 “ping 一个ip显示接收第二个数据,其他都是请求超时”的典型排错表

当现象为“仅第二次 Ping 成功,其余均超时”,问题几乎必在ICMP Identifier 或 Sequence Number 的复位/保持逻辑。对照下表排查:

现象最可能原因Verilog 修复点验证方法
第一次 Ping 失败,第二次起成功tx_id_regtx_seq_regIDLE状态未清零,导致首次发送使用了随机初值检查tx_id_reg的 reset 逻辑:always @(posedge clk or posedge rst) if (rst) tx_id_reg <= 16'h0000;ILA 观察tx_id_regIDLE时是否为0
偶数次成功,奇数次失败retry_counter未在每次SENDING前更新tx_seq_reg,导致重试时序号未递增SENDING状态下必须执行tx_seq_reg <= tx_seq_reg + 1ILA 观察tx_seq_reg在连续两次SENDING中是否递增
所有 Ping 均超时,但 Wireshark 看到 Request 发出目标设备未回复(防火墙拦截、ICMP 禁用)或TARGET_MAC配置错误检查目标设备sysctl net.ipv4.icmp_echo_ignore_all是否为 0;用arp -a确认TARGET_MAC正确在目标设备执行tcpdump -i eth0 icmp,确认是否收到 Request

5. 进阶技巧:将 Ping 模块升级为可配置网络健康监测器

5.1 支持多目标轮询:用 RAM 存储 IP 列表与状态机扩展

8_icmp_ping.zip的基础版本只 Ping 单一 IP。要监控多个设备(如192.168.1.100,192.168.1.101,192.168.1.102),需扩展为轮询模式。核心改动:

  • IP 地址 RAM:例化一块block_ram,深度为N(目标数),宽度 32-bit,存储目标 IP 列表;
  • 轮询状态机:在WAITING状态超时后,不直接重试,而是index <= index + 1,加载下一个 IP 的TARGET_MACip_dst_addr
  • 状态寄存器:为每个目标维护alive_flag[N-1:0]ping_success_pulse触发时置位对应 bit。
// 轮询索引更新逻辑 always @(posedge clk) begin if (rst) index <= 0; else if (ping_success || timeout_flag) begin if (index == (N-1)) index <= 0; // wrap around else index <= index + 1; end end // 加载当前目标 IP always @(posedge clk) begin if (rst) target_ip <= 32'h00000000; else if (index_updated) target_ip <= ip_ram[index]; end

5.2 响应时间测量:利用 FPGA 高精度计数器实现微秒级 RTT

基础 Ping 仅判断通断。添加 RTT(Round-Trip Time)测量,需在SENDING状态启动计数器,在ping_success_pulse到来时锁存计数值:

reg [31:0] rtt_counter; reg [31:0] rtt_value; always @(posedge clk) begin if (rst) begin rtt_counter <= 32'h0; rtt_value <= 32'h0; end else if (state == SENDING) begin rtt_counter <= 32'h0; // reset on send end else if (state == WAITING) begin rtt_counter <= rtt_counter + 1; // count every cycle end else if (ping_success_pulse) begin rtt_value <= rtt_counter; // latch RTT end end

@100MHz 时钟,rtt_value单位为 10ns。若测得rtt_value = 125000,则 RTT = 1.25ms。此值可通过 AXI-Lite 寄存器暴露给处理器,或直接驱动 LED 闪烁频率(如 RTT<10ms 亮绿灯,10-50ms 黄灯,>50ms 红灯)。

5.3 与 AXI-Lite 总线集成:让 ARM 处理器动态控制 Ping 行为

Zynq 或 Intel SoC FPGA 用户常需 PS(Processing System)控制 PL(Programmable Logic)的 Ping 模块。标准做法是添加axilite_ping_ctrl模块,映射以下寄存器:

地址偏移名称读/写说明
0x00PING_CTRLWBit 0:start_ping, Bit 1:clear_stats
0x04PING_TARGET_IPW32-bit 目标 IP 地址
0x08PING_TIMEOUT_MSW超时毫秒值(自动换算为时钟周期)
0x0cPING_RTT_USR最近一次成功 Ping 的 RTT(微秒)
0x10PING_STATUSRBit 0:busy, Bit 1:success, Bit 2:timeout

这样,ARM 端只需执行:

// C 伪代码 *(volatile uint32_t*)(AXI_BASE + 0x00) = 0x1; // start ping while (!(*(volatile uint32_t*)(AXI_BASE + 0x10) & 0x2)); // wait for success uint32_t rtt = *(volatile uint32_t*)(AXI_BASE + 0x0c); printf("RTT: %d us\n", rtt);

8_icmp_ping.zip的 Verilog 工程若已包含axi_lite_if.v,则只需将ping_starttarget_ip等信号连接至该接口即可实现无缝集成。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询