1. 项目概述
在FPGA上实现完整的以太网协议栈一直是个有趣的挑战。最近我完成了一个纯Verilog实现的以太网接口设计,支持UDP和TCP协议,从MAC层到TCP/IP协议栈全部用硬件描述语言手写完成。这个设计最大的特点是严格分离了MAC层和协议栈模块,使得整个架构非常清晰,也便于后续扩展。
目前已经实现了百兆网(RMII接口)和千兆网(GMII转RGMII接口)两个版本,都在实际开发板上通过了测试。TCP模块实现了完整的校验和计算和重发机制,可以与电脑端的网络应用程序直接通信。整个设计可以封装为AXI接口(AXI Stream或AXI Lite),方便集成到SoC系统中。
2. 架构设计
2.1 整体架构
整个设计采用分层架构,主要分为三个部分:
- 物理接口层:处理与PHY芯片的通信,包括时钟恢复、数据对齐等
- MAC层:实现以太网帧的组装和解析,CRC校验等
- 协议栈层:实现IP、UDP和TCP协议
这种分层设计使得每个模块的功能非常明确,也便于单独测试和验证。比如MAC层可以独立于协议栈工作,只处理以太网帧的收发。
2.2 模块划分
具体模块划分如下:
-
MAC层模块:
- 接收路径:帧同步、CRC校验、帧过滤
- 发送路径:帧组装、CRC生成、帧间隔控制
- 流量控制:半双工模式的冲突检测和处理
-
IP层模块:
- IP包封装和解封装
- IP地址过滤
- 校验和计算
-
传输层模块:
- UDP协议实现
- TCP协议实现(包括连接管理、流量控制、重传机制)
3. MAC层实现细节
3.1 接收路径
接收路径的核心是CRC校验模块。为了达到100Mbps的线速处理能力,我采用了一种高效的CRC32实现方式:
verilog复制always @(posedge clk) begin
if(rx_en) begin
crc_next = {crc[22:0], 1'b0} ^ (rx_data[7] ? 32'h04C11DB7 : 0);
//...省略中间6位处理
crc <= crc_next ^ ({27'b0, rx_data[0]} << 24);
end
end
这种实现方式可以在一个时钟周期内完成一个字节的CRC计算,完全满足100Mbps的实时性要求。在Nexys4 DDR开发板上实测,可以稳定处理满带宽的数据流。
3.2 发送路径
发送路径采用了三级流水线设计:
- 第一级:帧头组装(前导码、帧起始定界符)
- 第二级:有效载荷处理
- 第三级:帧尾处理(CRC附加、帧间隔控制)
帧间隔(IFG)控制是发送路径的一个关键点。标准要求最小帧间隔为96位时间,在100Mbps下对应960ns。通过精确的计数器控制,我们确保了符合标准的帧间隔:
verilog复制reg [7:0] ifg_counter;
always @(posedge clk) begin
if(tx_done) begin
ifg_counter <= 8'd12; // 12字节间隔 = 96位
end else if(ifg_counter != 0) begin
ifg_counter <= ifg_counter - 1;
end
end
4. TCP协议实现
4.1 状态机设计
TCP协议的核心是一个复杂的状态机,需要处理连接建立、数据传输和连接终止的全过程。我将其实现为一个Moore型状态机,主要状态包括:
- CLOSED
- LISTEN
- SYN_SENT
- SYN_RECEIVED
- ESTABLISHED
- FIN_WAIT_1
- FIN_WAIT_2
- CLOSE_WAIT
- LAST_ACK
- TIME_WAIT
状态转移严格按照RFC 793规范实现,确保与标准TCP协议栈的兼容性。
4.2 重传机制
可靠的传输是TCP的核心特性,重传机制是实现可靠性的关键。我采用了一个环形缓冲区来管理需要重传的数据包:
verilog复制reg [7:0] retry_queue[0:15];
reg [3:0] wr_ptr, rd_ptr;
always @(posedge retry_clk) begin
if(need_retry && !full) begin
retry_queue[wr_ptr] <= pkt_id;
wr_ptr <= wr_ptr + 1;
end
if(!timer_active && wr_ptr != rd_ptr) begin
retransmit_pkt(retry_queue[rd_ptr]);
rd_ptr <= rd_ptr + 1;
timer <= 16'hFFFF; //初始重传超时
end
end
这个设计可以缓存最多16个需要重传的数据包,采用指数退避算法调整重传超时时间。在实际测试中,即使故意断开网络连接再恢复,TCP连接也能自动重建并继续传输。
4.3 窗口管理
TCP的滑动窗口机制是流量控制的关键。我实现了一个基于寄存器的窗口管理模块:
verilog复制reg [31:0] snd_una; // 最早未确认的序列号
reg [31:0] snd_nxt; // 下一个要发送的序列号
reg [15:0] snd_wnd; // 发送窗口大小
always @(posedge clk) begin
if(tcp_ack_valid) begin
if(ack_num > snd_una && ack_num <= snd_nxt) begin
snd_una <= ack_num;
// 更新窗口
snd_wnd <= new_window;
end
end
end
为了与现代操作系统兼容,还实现了窗口缩放选项:
verilog复制assign tcp_options = {8'h03,8'h03,8'h01,8'h08}; //窗口缩放值8
5. 系统集成
5.1 AXI接口封装
为了便于集成到SoC系统中,设计提供了AXI Stream和AXI Lite两种接口封装。AXI Stream接口更适合高速数据传输,而AXI Lite接口适合控制寄存器访问。
AXI Stream FIFO的实现示例:
verilog复制axis_fifo #(.DWIDTH(64)) rx_fifo (
.s_axis_tdata(phy_rx_data),
.s_axis_tvalid(phy_rx_valid),
.m_axis_tready(cpu_ready),
.almost_full(eth_busy)
);
FIFO深度的选择是一个平衡点:太浅容易丢包,太深会占用过多BRAM资源。经过多次测试,最终选择了适中的深度,在KC705开发板上实现了850Mbps的TCP吞吐量。
5.2 SoC集成示例
在SoC版本中,以太网接口作为从设备通过AXI Interconnect与RISC-V核连接。系统架构如下:
- RISC-V核作为主设备
- AXI Interconnect作为总线桥接
- 以太网接口作为从设备
- 其他外设(如存储器控制器)也可以挂接在同一总线上
这种架构非常灵活,可以根据需要添加其他外设,或者替换主处理器核。
6. 测试与验证
6.1 测试方法
为了全面验证设计的正确性,采用了多层次的测试策略:
- 模块级测试:使用Verilog testbench验证每个模块的功能
- 系统级测试:在实际开发板上测试整个协议栈
- 互操作性测试:与各种操作系统和应用程序进行通信测试
6.2 测试工具
测试过程中使用了多种工具:
- 网络调试助手:基本的通信测试
- Wireshark:抓包分析协议细节
- Python脚本:自动化测试
- 自定义测试固件:内置的环回测试模式
Python测试脚本示例:
python复制import socket
s = socket.create_connection(('192.168.1.10', 6000))
s.send(b'FPGA真香!')
6.3 常见问题与解决
在开发过程中遇到并解决了一些典型问题:
- TCP连接建立失败:因为没有正确处理窗口缩放选项,添加选项后解决
- 重传效率低:优化了重传定时器管理,提高了重传速度
- 吞吐量不稳定:调整了FIFO深度和DMA参数,使吞吐量稳定在850Mbps左右
7. 性能与资源占用
7.1 资源占用
设计在Xilinx Artix-7 FPGA上的资源占用情况:
- LUT:约2000个(TCP模块占1200个)
- FF:约1500个
- BRAM:8个(用于数据缓冲)
- 时钟资源:2个(125MHz和200MHz)
7.2 性能指标
- 百兆网版本:100Mbps线速
- 千兆网版本:实测850Mbps吞吐量
- 延迟:端到端延迟<100μs
- 连接数:支持单个TCP连接(可扩展)
8. 开发经验分享
8.1 调试技巧
- 内置日志:设计了一个带时间戳的日志缓存,可以记录关键事件
- 环回测试:实现了多种环回模式(MAC层环回、协议栈环回等)
- 自动化测试:开发了自动生成测试向量的脚本
8.2 优化建议
- 流水线设计:关键路径采用流水线提高时钟频率
- 资源共享:在不同状态下复用相同的计算单元
- 参数化设计:使用参数使设计易于配置
8.3 扩展方向
- 增加多连接支持
- 实现更高级的拥塞控制算法
- 添加IPv6支持
- 集成更高级的协议(如HTTP)
这个项目最宝贵的产出其实不是代码本身,而是那套完整的调试体系和文档。通过四份配套文档(抓包实测、协议介绍、代码说明和报文概念),新手可以系统地学习网络协议在FPGA上的实现方法。在实际使用中,这套设计已经证明是稳定可靠的,可以作为更复杂网络应用的基础。