第三章 交易网卡MAC层设计:10G/25G Ethernet MAC IP核例化、GMII/RGMII接口、MAC地址过滤逻辑

各位同学,今天我们来聊聊MAC层。说实话,MAC层是整个交易网卡里最“实在”的部分——它直接决定了你的网卡能不能正确收发数据包。我在做第一版交易网卡时,就因为在MAC层偷了个懒,结果回环测试时丢包率高达5%,查了整整三天才找到问题。嗯,从那以后我对MAC层的设计就再也不敢马虎了。

3.1 10G/25G Ethernet MAC IP核例化

先说说IP核例化。你想想看,现在谁还会自己从头写MAC层?除非你是做学术研究,否则直接用厂商提供的IP核是最明智的选择。Xilinx和Intel都有现成的10G/25G MAC IP核,咱们直接拿来用就行。

我个人习惯用Xilinx的10G/25G Ethernet MAC IP核。例化时要注意几个关键参数:

  • 数据位宽:10G一般用64位,25G建议用128位。为什么?因为25G的线速率更高,64位会导致时钟频率过高,时序容易出问题。
  • 时钟频率:10G模式下,64位数据位宽对应156.25MHz;25G模式下,128位数据位宽对应195.3125MHz。这个要算清楚,别搞混了。
  • 流控模式:交易场景下,我建议关闭流控。因为流控帧会引入不确定的延迟,对交易系统来说是致命的。

重要提醒:IP核例化时,一定要勾选“Enable Statistics Gathering”选项。我在项目中遇到过,没有统计功能,调试时就像瞎子摸象,根本不知道丢包发生在哪一环。

下面是一个典型的例化代码片段:

// 10G/25G MAC IP核例化示例
ten_gig_eth_mac_0 inst_mac (
    .gt_rx_clk(gt_rx_clk),          // 收发器恢复时钟
    .gt_tx_clk(gt_tx_clk),          // 收发器发送时钟
    .rx_clk(rx_clk),                // 接收用户时钟
    .tx_clk(tx_clk),                // 发送用户时钟
    .rx_axis_tdata(rx_axis_tdata),  // 接收数据总线
    .rx_axis_tkeep(rx_axis_tkeep),  // 接收字节使能
    .rx_axis_tvalid(rx_axis_tvalid),// 接收数据有效
    .rx_axis_tlast(rx_axis_tlast),  // 接收帧结束
    .rx_axis_tuser(rx_axis_tuser),  // 接收错误标志
    .tx_axis_tdata(tx_axis_tdata),  // 发送数据总线
    .tx_axis_tkeep(tx_axis_tkeep),  // 发送字节使能
    .tx_axis_tvalid(tx_axis_tvalid),// 发送数据有效
    .tx_axis_tlast(tx_axis_tlast),  // 发送帧结束
    .tx_axis_tready(tx_axis_tready) // 发送准备好
);

3.2 GMII/RGMII接口详解

GMII和RGMII,这两个接口你可能在教科书上见过。但实际项目中,它们的使用场景完全不同。

GMII接口:8位数据总线,时钟频率125MHz。优点是时序简单,缺点是引脚太多——24根信号线。我在一个4口网卡项目里用过GMII,结果PCB布局时差点没把走线布通。说白了,GMII适合对引脚数量不敏感的FPGA,比如大封装的芯片。

RGMII接口:4位数据总线,时钟频率125MHz,但用DDR方式在时钟上升沿和下降沿各传输4位数据。这样只需要12根信号线,引脚数直接减半。我个人更推荐RGMII,尤其是做多口网卡时,引脚资源太宝贵了。

这里有个坑,我曾经踩过:RGMII的时序要求非常严格,数据信号和时钟信号之间的延迟必须控制在1ns以内。如果你直接用FPGA的普通IO输出,大概率会出问题。解决办法是使用OSERDES/ISERDES原语来保证时序对齐。

实战技巧:在Vivado中,可以直接用“RGMII to GMII Bridge”这个IP核来做接口转换。它会自动处理DDR采样和时钟对齐,省去你手动调时序的麻烦。

来看一个RGMII接口的时序参数表:

参数 最小值 典型值 最大值 单位
Tclk_skew -0.5 0 0.5 ns
Tdata_setup 1.0 1.5 - ns
Tdata_hold 1.0 1.5 - ns
Tcycle 7.5 8.0 8.5 ns

3.3 MAC地址过滤逻辑

MAC地址过滤,这是交易网卡的核心功能之一。为什么?因为交易场景下,网卡收到的数据包99%都是无关的广播包和多播包。如果你不做过滤,CPU会被这些垃圾包淹没,交易延迟就会飙升。

我设计的过滤逻辑一般分三层:

  1. 第一层:目的MAC地址匹配。只接收发给自己MAC地址的单播帧,以及特定的多播帧(比如PTP时间同步帧)。其他的一律丢弃。
  2. 第二层:VLAN ID过滤。如果网络中有VLAN划分,只接收属于本交易网段的VLAN帧。
  3. 第三层:帧类型过滤。只接收以太网类型为0x0800(IPv4)和0x86DD(IPv6)的帧,其他类型直接丢弃。

我曾经在一个项目中,只做了第一层过滤,结果CPU占用率还是很高。后来加上第二层和第三层过滤,CPU占用率直接从80%降到了5%。你想想看,这个差距有多大。

注意:MAC地址过滤逻辑一定要放在接收路径的最前端,也就是在FIFO缓存之前。这样做的目的是:尽早丢弃无效帧,避免占用存储资源和处理带宽。

下面是一个简单的MAC地址过滤模块代码:

// MAC地址过滤模块
module mac_filter (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [47:0] dst_mac,      // 目的MAC地址
    input  wire [15:0] eth_type,     // 以太网类型
    input  wire        frame_valid,  // 帧有效标志
    output reg         frame_pass    // 帧通过标志
);

// 本机MAC地址(可配置)
parameter [47:0] LOCAL_MAC = 48'h00_0A_35_01_02_03;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        frame_pass <= 1'b0;
    else if (frame_valid) begin
        // 第一层:MAC地址匹配
        if (dst_mac == LOCAL_MAC || dst_mac == 48'hFF_FF_FF_FF_FF_FF)
            // 第二层:帧类型过滤
            if (eth_type == 16'h0800 || eth_type == 16'h86DD)
                frame_pass <= 1'b1;
            else
                frame_pass <= 1'b0;
        else
            frame_pass <= 1'b0;
    end
end

endmodule

3.4 本章知识体系

为了让你更直观地理解MAC层的整体设计,我画了一张结构图:

交易网卡MAC层设计结构图 物理接口层 GMII(8位数据总线) | RGMII(4位DDR数据总线) 时钟频率:125MHz | 数据速率:1Gbps/10Gbps/25Gbps 10G/25G Ethernet MAC IP核 数据位宽:64位/128位 | 时钟频率:156.25MHz/195.3125MHz 功能:帧封装/解封装、CRC校验、流控管理 MAC地址过滤逻辑 第一层:目的MAC地址匹配 第二层:VLAN ID过滤 第三层:帧类型过滤(0x0800/0x86DD) 用户逻辑接口(AXI4-Stream) 数据通道 | 控制通道 | 状态统计

这张图把MAC层的四个层次清晰地展示出来了。从底层的物理接口,到中间的IP核处理,再到上层的过滤逻辑,最后到用户接口。每一层都有明确的职责和边界,这样设计出来的网卡才够稳定、够高效。

个人建议:在实际项目中,不要试图一次性把所有功能都做进去。先搭一个最小系统——物理接口+MAC IP核+简单的过滤逻辑,跑通回环测试。然后再逐步添加高级功能,比如PTP时间戳、多队列支持等。这样调试起来会轻松很多。

好了,这一章的内容就到这里。MAC层的设计,说白了就是三个关键点:IP核例化要准、接口时序要对、过滤逻辑要狠。把这三点做好,你的交易网卡就成功了一半。

专注资料整理