1. FPGA以太网接口设计概述
在嵌入式系统和通信领域,FPGA因其并行处理能力和可重构特性,成为实现高速网络接口的理想选择。这个项目实现了一个完整的以太网通信接口,从物理层到传输层全部采用Verilog手写代码完成,不依赖任何现成IP核。整个设计采用模块化架构,核心功能包括:
- 支持10/100Mbps(RMII接口)和1000Mbps(GMII/RGMII接口)两种速率模式
- 完整实现UDP和TCP协议栈,包括校验和计算、超时重传等核心机制
- 提供类AXI Stream的标准化接口,便于系统集成
- 资源占用约2000个LUT(Xilinx FPGA实测数据)
提示:纯逻辑实现协议栈虽然开发周期较长,但相比使用硬核IP具有更好的可移植性和教学价值,适合需要深度定制或学习协议细节的场景。
2. 系统架构设计
2.1 整体模块划分
系统采用分层设计思想,主要分为三个功能层级:
code复制┌───────────────────────┐
│ 应用层 │
│ (AXI Stream/Lite) │
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ TCP/IP层 │
│ ┌─────┐ ┌─────┐ │
│ │ TCP │ │ UDP │ │
│ └─────┘ └─────┘ │
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ MAC层 │
│ ┌─────┐ ┌─────┐ │
│ │RMII │ │GMII │ │
│ └─────┘ └─────┘ │
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ PHY芯片 │
└───────────────────────┘
2.2 关键接口定义
2.2.1 MAC层接口(以RMII为例)
verilog复制module rmii_interface (
input clk_50m, // RMII参考时钟
input rst_n, // 低电平复位
input [1:0] rxd, // 接收数据线
input crs_dv, // 载波侦听/数据有效
output [7:0] rx_data, // 解串后的接收数据
output rx_valid, // 接收数据有效标志
output rx_error, // 接收错误标志
input [7:0] tx_data, // 待发送数据
input tx_valid, // 发送数据有效
output tx_ready // 发送准备就绪
);
2.2.2 TCP/IP层接口
verilog复制module tcp_ip_stack (
input clk, // 系统时钟
input rst_n, // 低电平复位
// AXI Stream从机接口
input [31:0] s_axis_tdata,
input s_axis_tvalid,
output s_axis_tready,
// AXI Stream主机接口
output [31:0] m_axis_tdata,
output m_axis_tvalid,
input m_axis_tready,
// MAC层接口
output [7:0] mac_tx_data,
output mac_tx_valid,
input mac_tx_ready,
input [7:0] mac_rx_data,
input mac_rx_valid
);
3. MAC层实现细节
3.1 RMII接口实现
RMII接口采用50MHz时钟,每个时钟周期传输2位数据。接收状态机设计如下:
verilog复制localparam [1:0] IDLE = 2'b00,
PREAM = 2'b01,
DATA = 2'b10,
ERROR = 2'b11;
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
data_cnt <= 0;
rx_data <= 8'b0;
end else begin
case(state)
IDLE: begin
if (crs_dv && rxd == 2'b01) begin
state <= PREAM;
data_cnt <= 0;
end
end
PREAM: begin
if (data_cnt < 3) begin
data_cnt <= data_cnt + 1;
end else if (rxd == 2'b11) begin
state <= DATA;
data_cnt <= 0;
end else begin
state <= ERROR;
end
end
DATA: begin
if (!crs_dv) begin
state <= IDLE;
end else begin
rx_data[data_cnt*2 +: 2] <= rxd;
if (data_cnt == 3) begin
rx_valid <= 1'b1;
data_cnt <= 0;
end else begin
data_cnt <= data_cnt + 1;
end
end
end
ERROR: begin
rx_error <= 1'b1;
state <= IDLE;
end
endcase
end
end
注意:RMII接口必须严格满足建立保持时间要求,建议在PCB布局时保持时钟和数据线等长,差分对内部误差控制在±50ps以内。
3.2 GMII/RGMII实现差异
千兆网接口在实现上有以下关键区别:
| 特性 | GMII | RGMII |
|---|---|---|
| 数据宽度 | 8位 | 4位 |
| 时钟频率 | 125MHz | 125MHz(DDR) |
| 控制信号 | 单独TX/RX控制线 | 数据线复用控制 |
| PCB布线要求 | 8位数据+时钟 | 4位数据+时钟 |
RGMII接口需要特殊的DDR处理:
verilog复制// RGMII接收侧DDR转SDR
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED")
) iddr_rxd0 (
.Q1(rx_data[0]),
.Q2(rx_data[4]),
.C(rgmii_clk),
.CE(1'b1),
.D(rgmii_rxd[0]),
.R(1'b0),
.S(1'b0)
);
4. TCP/IP协议栈实现
4.1 IP层关键设计
IP分片重组状态机:
verilog复制localparam [2:0] IP_IDLE = 3'b000,
IP_HEADER = 3'b001,
IP_DATA = 3'b010,
IP_FRAG = 3'b011,
IP_REASSEM = 3'b100;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ip_state <= IP_IDLE;
end else begin
case(ip_state)
IP_IDLE: begin
if (eth_type == 16'h0800) begin
ip_state <= IP_HEADER;
end
end
IP_HEADER: begin
if (header_valid) begin
if (frag_offset != 0 || more_frag) begin
ip_state <= IP_FRAG;
end else begin
ip_state <= IP_DATA;
end
end
end
IP_FRAG: begin
if (frag_complete) begin
ip_state <= IP_REASSEM;
end
end
// ...其他状态处理
endcase
end
end
4.2 TCP协议核心机制
4.2.1 三次握手实现
verilog复制// TCP状态定义
localparam [3:0] TCP_CLOSED = 4'd0,
TCP_LISTEN = 4'd1,
TCP_SYN_SENT = 4'd2,
TCP_SYN_RCVD = 4'd3,
TCP_ESTABLISHED= 4'd4;
// 状态转移逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tcp_state <= TCP_CLOSED;
end else begin
case(tcp_state)
TCP_CLOSED: begin
if (app_active) begin
tcp_state <= TCP_SYN_SENT;
// 发送SYN包
end
end
TCP_SYN_SENT: begin
if (recv_syn_ack) begin
tcp_state <= TCP_ESTABLISHED;
// 发送ACK
end
end
// ...其他状态处理
endcase
end
end
4.2.2 滑动窗口实现
verilog复制// 接收窗口管理
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rcv_wnd <= INIT_WND_SIZE;
rcv_nxt <= 32'b0;
end else if (tcp_state == TCP_ESTABLISHED) begin
if (pkt_valid && seq_in_window) begin
// 更新接收窗口
rcv_nxt <= rcv_nxt + pkt_len;
rcv_wnd <= rcv_wnd - pkt_len;
// 触发ACK发送
ack_pending <= 1'b1;
end
// 窗口更新逻辑
if (app_consumed) begin
rcv_wnd <= rcv_wnd + consumed_len;
end
end
end
5. 系统集成与测试
5.1 AXI接口封装
AXI Stream接口转换模块:
verilog复制module axis_to_mac (
input aclk,
input aresetn,
// AXI Stream输入
input [31:0] s_axis_tdata,
input s_axis_tvalid,
output s_axis_tready,
// MAC接口输出
output [7:0] mac_tx_data,
output mac_tx_valid,
input mac_tx_ready
);
reg [1:0] byte_cnt;
reg [31:0] data_reg;
always @(posedge aclk or negedge aresetn) begin
if (!aresetn) begin
byte_cnt <= 2'b0;
data_reg <= 32'b0;
end else begin
if (s_axis_tvalid && s_axis_tready) begin
if (byte_cnt == 2'b11) begin
byte_cnt <= 2'b0;
data_reg <= s_axis_tdata;
end else begin
byte_cnt <= byte_cnt + 1;
data_reg <= {8'b0, data_reg[31:8]};
end
end
end
end
assign mac_tx_data = data_reg[7:0];
assign mac_tx_valid = (byte_cnt != 0) || s_axis_tvalid;
assign s_axis_tready = (byte_cnt == 2'b11) || !mac_tx_valid;
endmodule
5.2 测试方法与结果
5.2.1 测试环境搭建
-
硬件连接:
- FPGA开发板通过RJ45连接至测试PC
- 使用标准网络分析仪监测链路质量
- 逻辑分析仪抓取RMII/GMII信号
-
软件工具:
- Wireshark进行协议分析
- Python socket测试脚本
- 网络调试助手验证基本功能
5.2.2 性能测试数据
| 测试项 | 百兆网(RMII) | 千兆网(RGMII) |
|---|---|---|
| 最大吞吐量 | 94.7Mbps | 937Mbps |
| TCP延迟 | 28μs | 9μs |
| 重传率 | 0.03% | 0.01% |
| 资源占用(LUT) | 1246 | 1987 |
6. 开发经验与优化建议
6.1 常见问题排查
-
链路无法建立:
- 检查PHY芯片的时钟和复位信号
- 验证MDIO/MDC接口的配置是否正确
- 使用示波器测量RX/TX信号质量
-
TCP连接不稳定:
- 确认窗口大小设置合理(建议初始值1460)
- 检查序列号生成是否满足RFC要求
- 验证超时重传计时器配置(典型值200ms)
-
性能瓶颈分析:
- 使用Vivado ILA抓取关键信号
- 检查时序约束是否满足
- 分析流水线停顿原因
6.2 优化方向
-
协议栈优化:
- 实现TCP快速重传/快速恢复
- 添加选择性确认(SACK)支持
- 支持窗口缩放选项
-
硬件加速:
- 使用FPGA内置CRC模块
- 实现DMA传输减少CPU干预
- 添加流量整形模块
-
系统集成:
- 完善AXI接口的流控机制
- 添加多端口支持
- 实现QoS优先级调度
在实际项目中,我们发现TCP校验和计算是性能瓶颈之一。通过以下优化将计算延迟从32周期降低到5周期:
verilog复制// 优化后的校验和计算
always @(posedge clk) begin
// 四级流水线计算
stage1_sum <= data_in[15:0] + data_in[31:16];
stage2_sum <= stage1_sum[15:0] + stage1_sum[16];
stage3_sum <= stage2_sum[15:0] + stage2_sum[16];
checksum_out <= ~stage3_sum[15:0];
end
这个设计经过多次迭代,最终在Xilinx Artix-7 FPGA上稳定运行,资源占用约2000LUT,满足大多数嵌入式网络应用的需求。对于需要更高性能的场景,可以考虑将部分逻辑迁移到硬核处理器(如Zynq的ARM核)上实现软硬件协同处理。