前言
分享记录以太网MAC控制器的验证,关于Synopsys DWC Ethernet QoS IP核。在学习和调试过程中,发现描述符(Descriptor)机制是整个DMA传输的核心,也是理解以太网控制器工作原理的关键。
刚开始接触时,面对几百页的Databook文档,各种寄存器位域、状态标志位,感觉非常抽象。经过反复阅读文档、查看波形、调试代码,终于对描述符机制有了一些理解。特此整理成文,分享给正在学习网络协议栈、以太网驱动开发或IC验证的同学。
本文基于**DWC Ethernet QoS Databook (Version 5.10a)**整理,结合实际工程经验,力求专业准确、通俗易懂。
.
一、描述符机制概述
1.1 什么是描述符?为什么需要它?
在深入技术细节之前,让我们先用一个生活中的例子来理解描述符的概念。
生活中的类比:快递配送单
想象一下快递配送的场景:
- 传统方式:快递员每送一个包裹,都要打电话给收件人,等收件人下来取件,效率很低
- 描述符方式 :快递员拿着一叠配送单,每张单子上写着:
- 收件人地址(数据存放位置)
- 包裹大小(数据长度)
- 是否需要签收(是否产生中断)
- 是否是最后一个包裹(包结束标志)
快递员可以按照配送单批量配送,不需要每次都联系收件人,效率大大提高。
技术层面的定义
在以太网控制器中,**描述符(Descriptor)**就是这样的"配送单":
描述符 = 数据传输的元数据结构
它告诉DMA控制器:
- 数据在内存的哪个位置(缓冲区地址)
- 数据有多少字节(长度)
- 如何处理这些数据(控制标志)
- 传输完成后做什么(中断、状态写回)
1.2 描述符机制的优势
传统的PIO(Programmed I/O)方式,CPU需要直接参与每次数据传输:
CPU: 读取数据 → 写入寄存器 → 等待完成 → 读取下一个数据
这种方式CPU利用率低,无法满足高速网络的需求(10Gbps甚至更高)。
描述符机制的优势:
✅ 零拷贝传输 :DMA直接访问内存,CPU只需配置描述符,无需搬运数据
✅ 批量处理 :一个描述符链可以传输多个数据包,减少CPU干预
✅ 异步操作 :CPU准备描述符后可做其他工作,DMA独立完成传输
✅ 分散/聚集I/O :支持数据分散在多个缓冲区,适应各种内存管理方案
✅ 硬件加速:支持TSO、checksum offload等功能
1.3 描述符在系统中的位置
┌─────────────────────────────────────────────────────┐
│ 应用层 │
├─────────────────────────────────────────────────────┤
│ 协议栈 │
│ (TCP/IP) │
├─────────────────────────────────────────────────────┤
│ 驱动层 │
│ ┌──────────────┐ │
│ │ 创建描述符链 │ ←── 配置描述符 │
│ │ 管理缓冲区 │ │
│ └──────────────┘ │
├─────────────────────────────────────────────────────┤
│ 硬件层 │
│ ┌──────────────────────────────────┐ │
│ │ DMA控制器 │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ 读取描述符 → 传输数据 │ │ ←── 硬件自动│
│ │ │ 写回状态 → 产生中断 │ │ │
│ │ └────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
│ ↓ ↑ │
│ ┌────────┐ ┌────────┐ │
│ │ 发送FIFO│ │接收FIFO│ │
│ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────┘
↓ ↑
┌────────────────────┐
│ 物理层 (PHY) │
└────────────────────┘
关键点:驱动层在内存中创建描述符链,DMA控制器自动读取并执行数据传输,实现软硬件协同工作。
二、描述符类型与结构
2.1 两种描述符类型
DWC Ethernet QoS支持两种描述符类型,各有不同用途:
(1) Normal Descriptor(普通描述符)
用途:承载数据包的实际内容
特点:
- 包含数据缓冲区的地址和长度
- 包含传输控制信息(中断、校验等)
- 包含状态信息(错误、过滤结果等)
类比:就像快递的包裹本身,里面装着实际的货物。
(2) Context Descriptor(上下文描述符)
用途:传递控制参数,不承载数据
特点:
- 用于配置VLAN标签、TSO参数等
- 用于IEEE 1588时间戳校正
- 不占用数据缓冲区
类比:就像快递的特殊配送指令,比如"需要冷藏配送"、"易碎物品"等标签,本身不是货物,但影响配送方式。
2.2 描述符的组织结构
环形链表结构(Ring Structure)
DWC Ethernet QoS采用环形描述符链表,这是最常用的组织方式:
内存布局:
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Descriptor │ │ Descriptor │ │ Descriptor │
│ 0 │─→│ 1 │─→│ 2 │─┐
└────────────┘ └────────────┘ └────────────┘ │
↑ │
└───────────────────────────────────────────┘
(循环回到Descriptor 0)
寄存器配置:
- DMA_CH0_TxDesc_ListAddr: 描述符链起始地址
- DMA_CH0_TxDesc_RingLen: 描述符数量
为什么用环形结构?
✅ 内存效率高 :无需频繁分配/释放内存
✅ 循环使用 :描述符处理完后可重新初始化使用
✅ 适合高速场景 :减少内存管理开销
✅ 实现简单:硬件只需维护当前指针
描述符大小
- 标准描述符:16字节(4个32位字)
- 增强描述符:32字节(用于时间敏感网络等高级特性)
每个描述符包含4个32位字段:
┌─────────────────────────────────────┐
│ DES0 (Word 0) - 32 bits │
├─────────────────────────────────────┤
│ DES1 (Word 1) - 32 bits │
├─────────────────────────────────────┤
│ DES2 (Word 2) - 32 bits │
├─────────────────────────────────────┤
│ DES3 (Word 3) - 32 bits │
└─────────────────────────────────────┘
三、发送描述符详解
3.1 发送普通描述符 - 读格式(Read Format)
使用场景:驱动程序配置待发送的数据包时,设置此格式。
3.1.1 TDES0 - Buffer 1地址指针
31 0
┌──────────────────────────────────┐
│ Buffer 1 Address Pointer │
└──────────────────────────────────┘
说明:
- 存放数据缓冲区的物理地址(注意不是虚拟地址)
- 无地址对齐限制,可以是任意地址
- 通常指向数据包的起始位置
SystemVerilog定义:
// TDES0字段定义
typedef struct packed {
logic [31:0] buffer1_addr; // Buffer 1物理地址
} tdes0_t;
3.1.2 TDES1 - Buffer 2地址指针
31 0
┌──────────────────────────────────┐
│ Buffer 2 Address Pointer │
└──────────────────────────────────┘
说明:
- 用于双缓冲模式,指向第二个缓冲区
- 在40/48位地址模式下,存放Buffer 1的高位地址
- 单缓冲模式下可设为0
双缓冲模式的应用场景:
场景1:头部与数据分离
┌─────────────┐
│ Descriptor │
├─────────────┤
│ Buffer1 Addr│ ──→ [Ethernet Header (14B)]
│ Buffer2 Addr│ ──→ [IP + TCP + Payload]
└─────────────┘
场景2:大数据包分段
┌─────────────┐
│ Descriptor │
├─────────────┤
│ Buffer1 Addr│ ──→ [前半部分数据]
│ Buffer2 Addr│ ──→ [后半部分数据]
└─────────────┘
3.1.3 TDES2 - 控制字段
这是发送描述符的核心控制字段,包含多个重要标志位:
31 30 29 16 15 14 13 0
┌────┬────┬─────────────┬───────┬──────────────┐
│IOC │TTSE│ B2L │ VTIR │ B1L/HL │
└────┴────┴─────────────┴───────┴──────────────┘
各字段详细说明:
| 字段 | 位宽 | 名称 | 说明 |
|---|---|---|---|
| IOC | 1 bit | Interrupt on Completion | 传输完成产生中断 |
| TTSE | 1 bit | Transmit Timestamp Enable | 使能IEEE 1588时间戳 |
| B2L | 14 bits | Buffer 2 Length | Buffer 2的数据长度(字节) |
| VTIR | 2 bits | VLAN Tag Insert/Replace | VLAN标签操作控制 |
| B1L/HL | 14 bits | Buffer 1 Length / Header Length | Buffer 1长度或头部长度 |
IOC位详解:
IOC = 1: 当此描述符传输完成时,DMA产生中断
IOC = 0: 不产生中断
应用场景:
- 通常只在数据包的最后一个描述符(LD=1)设置IOC=1
- 这样可以在整个包发送完成后得到通知
- 减少中断次数,提高性能
VTIR字段详解(VLAN标签操作):
VTIR = 00: 不添加VLAN标签
VTIR = 01: 移除VLAN标签(仅对已标记的包有效)
VTIR = 10: 插入VLAN标签(从MAC_VLAN_Incl寄存器或上下文描述符获取)
VTIR = 11: 替换VLAN标签(仅对已标记的包有效)
SystemVerilog定义:
// TDES2字段定义
typedef struct packed {
logic ioc; // [31] Interrupt on Completion
logic ttse; // [30] Transmit Timestamp Enable
logic [13:0] buffer2_len; // [29:16] Buffer 2 Length
logic [1:0] vlan_tag_ctrl; // [15:14] VLAN Tag Insert/Replace
logic [13:0] buffer1_len; // [13:0] Buffer 1 Length
} tdes2_t;
// VTIR枚举定义
typedef enum logic [1:0] {
VLAN_NO_ACTION = 2'b00, // 不操作
VLAN_REMOVE = 2'b01, // 移除标签
VLAN_INSERT = 2'b10, // 插入标签
VLAN_REPLACE = 2'b11 // 替换标签
} vlan_tag_op_e;
3.1.4 TDES3 - 控制与状态字段
这是最重要的字段,包含所有权、包边界、帧长度等关键信息:
31 30 29 28 27 26 25 23 22 19 18 17 16 15 0
┌────┬────┬────┬────┬────┬────────┬────────┬────┬────┬─────────┐
│OWN │CTXT│ FD │ LD │ CPC│ SAIC │SLOTNUM │ THL│ TSE│ FL │
└────┴────┴────┴────┴────┴────────┴────────┴────┴────┴─────────┘
核心字段详解:
(1) OWN位 - 所有权位(最关键!)
OWN = 1: DMA控制器拥有此描述符,驱动程序不能修改
OWN = 0: 驱动程序拥有此描述符,DMA控制器不能访问
工作流程:
发送流程:
驱动程序: 配置描述符 → 设置OWN=1 → 通知DMA启动
DMA控制器: 检测OWN=1 → 读取描述符 → 传输数据 → 清除OWN=0 → 写回状态
驱动程序: 检测OWN=0 → 读取状态 → 释放缓冲区 → 重新配置描述符
重要提示:OWN位是驱动与DMA同步的核心机制,必须正确设置!
(2) FD位 - First Descriptor(首描述符)
FD = 1: 这是数据包的第一个描述符
FD = 0: 不是第一个描述符
用途:标识一个完整数据包的起始位置
(3) LD位 - Last Descriptor(尾描述符)
LD = 1: 这是数据包的最后一个描述符
LD = 0: 不是最后一个描述符
用途:
- 标识数据包的结束位置
- DMA只对LD=1的描述符写回状态和时间戳
- 通常在LD=1的描述符上设置IOC=1产生中断
(4) TSE位 - TCP Segmentation Enable(TCP分段使能)
TSE = 1: 使能TCP Segmentation Offload (TSO)
TSE = 0: 正常传输
TSO功能:硬件自动将大TCP包分段,减轻CPU负担
(5) FL字段 - Frame Length(帧长度)
FL [14:0]: 发送帧的总长度(字节)
范围:0 ~ 32767字节
注意:这是整个帧的长度,包括以太网头、IP头、TCP头、数据
SystemVerilog定义:
// TDES3字段定义
typedef struct packed {
logic own; // [31] Own Bit
logic ctxt; // [30] Context Type (0 for normal)
logic first_desc; // [29] First Descriptor
logic last_desc; // [28] Last Descriptor
logic [1:0] crc_pad_ctrl; // [27:26] CRC Pad Control
logic [2:0] sa_ins_ctrl; // [25:23] Source Address Insert Control
logic [3:0] slot_num; // [22:19] Slot Number (for EST)
logic tcp_seg_en; // [18] TCP Segmentation Enable
logic [1:0] checksum_ins; // [17:16] Checksum Insert Control
logic [14:0] frame_len; // [14:0] Frame Length
} tdes3_t;
3.2 发送描述符 - 写回格式(Write-Back Format)
使用场景:DMA完成数据传输后,将状态信息写回描述符。
重要 :写回操作只对LD=1的描述符(最后一个描述符)进行。
┌─────────────────────────────────────┐
│ TDES0: Timestamp Low [31:0] │ ← IEEE 1588时间戳低32位
├─────────────────────────────────────┤
│ TDES1: Timestamp High [31:0] │ ← IEEE 1588时间戳高32位
├─────────────────────────────────────┤
│ TDES2: Reserved │
├─────────────────────────────────────┤
│ TDES3: OWN=0 + Status [30:0] │ ← OWN清零,状态位有效
└─────────────────────────────────────┘
时间戳说明:
- 当TTSE=1时,DMA记录数据包发送的精确时间
- 用于IEEE 1588 PTP协议,实现时钟同步
- 时间戳精度通常为纳秒级
四、接收描述符详解
4.1 接收普通描述符 - 读格式(Read Format)
使用场景:驱动程序为接收数据包准备缓冲区时,设置此格式。
4.1.1 RDES0 - Buffer 1地址
31 0
┌──────────────────────────────────┐
│ Buffer 1 Address Pointer │
└──────────────────────────────────┘
说明:
- 存放接收缓冲区的物理地址
- MAC接收的数据将写入此地址
- 建议缓存行对齐(如64字节对齐)以提高性能
4.1.2 RDES1 - Buffer 2地址
31 0
┌──────────────────────────────────┐
│ Buffer 2 Address Pointer │
└──────────────────────────────────┘
说明:
- 用于双缓冲模式
- 当Buffer 1满时,数据继续写入Buffer 2
- 避免因缓冲区不足导致丢包
4.1.3 RDES2 - 控制字段
31 0
┌──────────────────────────────────┐
│ Reserved │
└──────────────────────────────────┘
说明:接收描述符的RDES2在读格式下保留,由DMA在写回时填充状态。
4.1.4 RDES3 - 控制字段
31 30 29 0
┌────┬────┬────────────────────────┐
│OWN │IOC │ Reserved │
└────┴────┴────────────────────────┘
| 字段 | 说明 |
|---|---|
| OWN | 所有权位:1=DMA拥有,0=驱动拥有 |
| IOC | Interrupt on Completion - 接收完成产生中断 |
SystemVerilog定义:
// RDES3读格式定义
typedef struct packed {
logic own; // [31] Own Bit
logic ioc; // [30] Interrupt on Completion
logic [29:0] reserved; // [29:0] Reserved
} rdes3_rd_t;
4.2 接收描述符 - 写回格式(Write-Back Format)
使用场景:DMA接收完数据包后,将状态信息写回描述符。
4.2.1 RDES3 - 状态字段(最重要)
31 30 29 28 27 26 25 24 23 22 21 20 19 0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬─────────┐
│OWN │CTXT│ LD │ FD │ ERR│ B1L│ PL │ ES │ CE │ GP │RWT │ Length │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴─────────┘
核心状态位详解:
| 字段 | 位 | 名称 | 说明 |
|---|---|---|---|
| OWN | 31 | Own Bit | 0表示DMA已完成,驱动可处理 |
| CTXT | 30 | Context | 1表示这是上下文描述符 |
| LD | 29 | Last Descriptor | 数据包的最后一个描述符 |
| FD | 28 | First Descriptor | 数据包的第一个描述符 |
| ERR | 27 | Error Summary | 错误汇总位 |
| ES | 24 | Error Status | 错误状态 |
| CE | 23 | CRC Error | CRC校验错误 |
| GP | 22 | Giant Packet | 巨型包(超过最大帧长) |
| RWT | 21 | Receive Watchdog Timeout | 接收看门狗超时 |
| Length | 14:0 | Frame Length | 接收到的帧长度 |
错误状态位详解:
ES (Error Status) = 1 时,表示接收出错,可能原因:
- CE (CRC Error): CRC校验失败
- GP (Giant Packet): 包长度超过最大值
- RWT (Watchdog Timeout): 接收超时,包被截断
- OE (Overflow Error): FIFO溢出
- RE (Receive Error): GMII接收错误信号
SystemVerilog定义:
// RDES3写回格式定义
typedef struct packed {
logic own; // [31] Own Bit (0 after write-back)
logic ctxt; // [30] Context Descriptor
logic last_desc; // [29] Last Descriptor
logic first_desc; // [28] First Descriptor
logic error_summary; // [27] Error Summary
logic buffer1_len_valid;// [26] Buffer 1 Length Valid
logic payload_len_valid;// [25] Payload Length Valid
logic error_status; // [24] Error Status
logic crc_error; // [23] CRC Error
logic giant_packet; // [22] Giant Packet
logic rx_watchdog_tmo; // [21] Receive Watchdog Timeout
logic overflow_error; // [20] Overflow Error
logic rx_error; // [19] Receive Error
logic [18:0] reserved; // [18:0] Reserved
logic [14:0] frame_len; // [14:0] Frame Length
} rdes3_wb_t;
4.2.2 RDES0 - 扩展状态字段
31 29 28 27 26 19 18 17 16 15 0
┌─────┬────┬────┬──────────┬────┬────┬────┬─────────┐
│FltID│L4FM│L3FM│ MADRM │ HF │DAF │SAF │ HL │
└─────┴────┴────┴──────────┴────┴────┴────┴─────────┘
过滤与匹配状态:
| 字段 | 说明 |
|---|---|
| L4FM | Layer 4 Filter Match - L4端口过滤匹配 |
| L3FM | Layer 3 Filter Match - L3 IP地址过滤匹配 |
| MADRM | MAC Address Match or Hash Value - MAC地址匹配或哈希值 |
| HF | Hash Filter - 哈希过滤状态 |
| DAF | Destination Address Filter Fail - 目的地址过滤失败 |
| SAF | Source Address Filter Fail - 源地址过滤失败 |
| HL | L3/L4 Header Length - L3/L4头部长度 |
应用场景:
- 网络过滤:根据MAC/IP/端口过滤数据包
- 哈希过滤:快速多播过滤
- 分离解析:分离L2/L3/L4头部
五、描述符工作流程详解
5.1 发送流程完整解析
让我们通过一个完整的例子,理解发送描述符的工作流程。
场景:发送一个1500字节的IP数据包
步骤1:驱动程序准备数据
// 假设数据包已准备好
byte packet_data[1500]; // IP数据包
logic [31:0] buffer_addr = 'h1000_0000; // 缓冲区物理地址
步骤2:配置发送描述符
// 创建发送描述符
tx_descriptor_t tx_desc;
// TDES0: Buffer 1地址
tx_desc.tdes0 = buffer_addr; // 'h1000_0000
// TDES1: Buffer 2地址(不使用)
tx_desc.tdes1 = 32'h0;
// TDES2: 控制字段
tx_desc.tdes2 = {
1'b1, // IOC = 1 (传输完成产生中断)
1'b0, // TTSE = 0 (不使能时间戳)
14'h0, // B2L = 0 (无Buffer 2)
2'b00, // VTIR = 00 (不操作VLAN)
14'd1500 // B1L = 1500 (Buffer 1长度)
};
// TDES3: 控制与状态
tx_desc.tdes3 = {
1'b1, // OWN = 1 (DMA拥有)
1'b0, // CTXT = 0 (普通描述符)
1'b1, // FD = 1 (首描述符)
1'b1, // LD = 1 (尾描述符)
2'b00, // CPC = 00 (CRC控制)
3'b000, // SAIC = 000 (不插入源地址)
4'h0, // SLOTNUM = 0
1'b0, // TSE = 0 (不使能TSO)
2'b00, // CIC = 00 (校验控制)
15'd1514 // FL = 1514 (以太网头14B + IP数据1500B)
};
步骤3:通知DMA启动传输
// 写入DMA寄存器,启动传输
write_register(DMA_CH0_TxDesc_ListAddr, desc_addr);
write_register(DMA_CH0_TxControl, 32'h1); // ST = 1, 启动发送
步骤4:DMA控制器工作
DMA控制器内部流程:
1. 读取描述符 (TDES0-TDES3)
2. 检查OWN位 (OWN=1, 可以处理)
3. 从Buffer地址读取数据 (1500字节)
4. 添加以太网头、CRC等
5. 通过MAC发送到PHY
6. 清除OWN位 (OWN=0)
7. 写回状态和时间戳
8. 产生中断 (因为IOC=1)
步骤5:驱动程序处理完成
// 中断处理程序
task handle_tx_complete();
// 检查OWN位
if (tx_desc.tdes3[31] == 1'b0) begin // OWN = 0
// DMA已完成
if (tx_desc.tdes3[30] == 1'b0) begin // 不是上下文描述符
// 读取时间戳(如果使能了TTSE)
logic [63:0] timestamp = {tx_desc.tdes1, tx_desc.tdes0};
// 释放缓冲区
free_buffer(buffer_addr);
// 重新初始化描述符,供下次使用
init_tx_descriptor(tx_desc);
end
end
endtask
5.2 接收流程完整解析
场景:接收一个数据包
步骤1:驱动程序准备接收缓冲区
// 分配接收缓冲区
logic [31:0] rx_buffer_addr = 'h2000_0000;
logic [31:0] rx_buffer_size = 2048; // 2KB缓冲区
步骤2:配置接收描述符
// 创建接收描述符
rx_descriptor_t rx_desc;
// RDES0: Buffer 1地址
rx_desc.rdes0 = rx_buffer_addr; // 'h2000_0000
// RDES1: Buffer 2地址(可选)
rx_desc.rdes1 = 32'h0; // 不使用双缓冲
// RDES2: 保留
rx_desc.rdes2 = 32'h0;
// RDES3: 控制字段
rx_desc.rdes3 = {
1'b1, // OWN = 1 (DMA拥有)
1'b1, // IOC = 1 (接收完成产生中断)
30'h0 // Reserved
};
步骤3:DMA控制器接收数据
DMA控制器内部流程:
1. MAC从PHY接收数据
2. 数据写入Rx FIFO
3. DMA读取描述符 (RDES0-RDES3)
4. 检查OWN位 (OWN=1, 可以写入)
5. 将数据从FIFO写入Buffer
6. 清除OWN位 (OWN=0)
7. 写回状态和长度
8. 设置FD/LD位
9. 产生中断 (因为IOC=1)
步骤4:驱动程序处理接收数据
// 中断处理程序
task handle_rx_complete();
// 检查OWN位
if (rx_desc.rdes3[31] == 1'b0) begin // OWN = 0
// DMA已完成
// 检查是否为完整包 (FD=1 and LD=1)
if (rx_desc.rdes3[29] && rx_desc.rdes3[28]) begin
// 获取接收长度
logic [14:0] pkt_len = rx_desc.rdes3[14:0];
// 检查错误
if (rx_desc.rdes3[24]) begin // ES = 1
// 接收出错
logic crc_err = rx_desc.rdes3[23];
logic giant_pkt = rx_desc.rdes3[22];
$display("RX Error: CRC=%0d, Giant=%0d", crc_err, giant_pkt);
end else begin
// 接收成功,处理数据
process_rx_packet(rx_buffer_addr, pkt_len);
end
// 重新分配缓冲区,准备下次接收
rx_desc.rdes3[31] = 1'b1; // OWN = 1
end
end
endtask
5.3 多描述符传输(分散/聚集I/O)
当一个数据包分散在多个缓冲区时,需要使用多个描述符:
场景:发送一个数据包,头部和数据分离
Buffer布局:
┌──────────────┐
│ Eth + IP + TCP│ 54字节 (头部)
└──────────────┘
↓
Buffer 1 (地址: 0x1000)
┌──────────────┐
│ Payload │ 1460字节 (数据)
└──────────────┘
↓
Buffer 2 (地址: 0x2000)
描述符配置:
┌─────────────┐
│ Descriptor0 │ FD=1, LD=0
├─────────────┤
│ TDES0 = 0x1000│ (头部地址)
│ TDES2 = 54 │ (头部长度)
│ TDES3 = 0x80000000│ (OWN=1, FD=1, LD=0)
└─────────────┘
↓
┌─────────────┐
│ Descriptor1 │ FD=0, LD=1
├─────────────┤
│ TDES0 = 0x2000│ (数据地址)
│ TDES2 = 1460 │ (数据长度)
│ TDES3 = 0x90000000│ (OWN=1, FD=0, LD=1, IOC=1)
└─────────────┘
SystemVerilog示例:
// 配置分散/聚集I/O
task setup_scatter_gather_tx();
// 第一个描述符:头部
tx_desc[0].tdes0 = 'h1000; // 头部地址
tx_desc[0].tdes1 = 32'h0;
tx_desc[0].tdes2 = {1'b0, 1'b0, 14'h0, 2'b00, 14'd54};
tx_desc[0].tdes3 = {1'b1, 1'b0, 1'b1, 1'b0, 26'h0}; // OWN=1, FD=1, LD=0
// 第二个描述符:数据
tx_desc[1].tdes0 = 'h2000; // 数据地址
tx_desc[1].tdes1 = 32'h0;
tx_desc[1].tdes2 = {1'b1, 1'b0, 14'h0, 2'b00, 14'd1460}; // IOC=1
tx_desc[1].tdes3 = {1'b1, 1'b0, 1'b0, 1'b1, 26'h0}; // OWN=1, FD=0, LD=1
endtask
六、上下文描述符详解
6.1 发送上下文描述符
用途:传递控制参数,不承载数据。
应用场景
- VLAN标签配置:插入或替换VLAN标签
- TSO参数:设置TCP分段的最大段大小(MSS)
- IEEE 1588时间戳校正:单步时间戳校正
TDES2 - 上下文描述符
31 16 15 14 13 0
┌───────────────┬───────┬──────────────┐
│ IVT │ Rsvd │ MSS │
└───────────────┴───────┴──────────────┘
| 字段 | 说明 |
|---|---|
| IVT | Inner VLAN Tag - 内层VLAN标签(用于Q-in-Q) |
| MSS | Maximum Segment Size - TSO最大段大小 |
TDES3 - 上下文描述符
31 30 29 28 27 26 25 23 22 18 17 16 15 0
┌────┬────┬────┬────┬────┬────┬────┬─────┬────┬────┬───────┐
│OWN │CTXT│Rsvd│OSTC│TCMSV│Rsvd│CDE │Rsvd │IVLTV│VLTV│ VT │
└────┴────┴────┴────┴────┴────┴────┴─────┴────┴────┴───────┘
| 字段 | 说明 |
|---|---|
| OWN | 所有权位 |
| CTXT | Context Type - 必须为1 |
| OSTC | One-Step Timestamp Correction - 单步时间戳校正 |
| TCMSSV | Timestamp Correction/MSS Valid |
| IVLTV | Inner VLAN Tag Valid |
| VLTV | VLAN Tag Valid |
| VT | VLAN Tag - VLAN标签值 |
SystemVerilog示例:
// 配置TSO上下文描述符
task setup_tso_context(input logic [13:0] mss);
tx_descriptor_t ctx_desc;
ctx_desc.tdes0 = 32'h0; // 时间戳低(用于单步校正)
ctx_desc.tdes1 = 32'h0; // 时间戳高
ctx_desc.tdes2 = {16'h0, 2'b00, mss}; // MSS
ctx_desc.tdes3 = {
1'b1, // OWN = 1
1'b1, // CTXT = 1 (上下文描述符)
2'b00, // Reserved
1'b0, // OSTC = 0
1'b1, // TCMSSV = 1 (MSS有效)
2'b00, // Reserved
1'b0, // CDE = 0
5'b00000, // Reserved
1'b0, // IVLTV = 0
1'b0, // VLTV = 0
16'h0 // VT = 0
};
endtask
6.2 接收上下文描述符
用途:当普通描述符的状态位不足以表示所有信息时,DMA写入上下文描述符。
应用场景:
- IEEE 1588时间戳(高精度)
- 扩展过滤状态
- 附加错误信息
七、增强描述符(Enhanced Descriptor)
7.1 概述
用途:用于时间敏感网络(TSN)、IEEE 802.1Qbv门控调度等高级特性。
大小:32字节(标准描述符的2倍)
7.2 结构
┌─────────────────────────────────────┐
│ ETDESC4: Reserved │
├─────────────────────────────────────┤
│ ETDESC5: Launch Time [23:0] │ ← 发送时间(纳秒)
├─────────────────────────────────────┤
│ ETDESC6: Reserved │
├─────────────────────────────────────┤
│ ETDESC7: GSN + Control │ ← 门控调度槽号
├─────────────────────────────────────┤
│ TDESC0: Buffer 1 Address │
├─────────────────────────────────────┤
│ TDESC1: Buffer 2 Address │
├─────────────────────────────────────┤
│ TDESC2: Control │
├─────────────────────────────────────┤
│ TDESC3: OWN + Status │
└─────────────────────────────────────┘
关键字段:
| 字段 | 说明 |
|---|---|
| Launch Time | 数据包的计划发送时间 |
| GSN | Gate Control Schedule Number - 门控调度序号 |
7.3 应用场景
时间敏感网络(TSN)
场景:工业控制网络,要求确定性延迟
配置:
1. 设置Launch Time = 目标发送时间
2. 设置GSN = 对应的门控调度槽
3. DMA在指定时间发送数据包
效果:
- 保证数据包在确定的时间窗口发送
- 避免冲突,实现零丢包
- 满足工业自动化的实时性要求
八、SystemVerilog完整实现示例
8.1 描述符结构体定义
// 包:GMAC描述符定义
package gmac_descriptor_pkg;
// ============================================
// 发送描述符定义
// ============================================
// TDES0: Buffer 1地址
typedef struct packed {
logic [31:0] buffer1_addr;
} tdes0_t;
// TDES1: Buffer 2地址
typedef struct packed {
logic [31:0] buffer2_addr;
} tdes1_t;
// TDES2: 控制字段
typedef struct packed {
logic ioc; // [31] Interrupt on Completion
logic ttse; // [30] Transmit Timestamp Enable
logic [13:0] buffer2_len; // [29:16] Buffer 2 Length
logic [1:0] vlan_tag_ctrl; // [15:14] VLAN Tag Control
logic [13:0] buffer1_len; // [13:0] Buffer 1 Length
} tdes2_t;
// TDES3: 控制与状态
typedef struct packed {
logic own; // [31] Own Bit
logic ctxt; // [30] Context Type
logic first_desc; // [29] First Descriptor
logic last_desc; // [28] Last Descriptor
logic [1:0] crc_pad_ctrl; // [27:26] CRC Pad Control
logic [2:0] sa_ins_ctrl; // [25:23] SA Insert Control
logic [3:0] slot_num; // [22:19] Slot Number
logic tcp_seg_en; // [18] TCP Segmentation Enable
logic [1:0] checksum_ins; // [17:16] Checksum Insert
logic [14:0] frame_len; // [14:0] Frame Length
} tdes3_t;
// 完整的发送描述符
typedef struct packed {
tdes0_t tdes0;
tdes1_t tdes1;
tdes2_t tdes2;
tdes3_t tdes3;
} tx_descriptor_t;
// ============================================
// 接收描述符定义
// ============================================
// RDES0: Buffer 1地址(读格式)
typedef struct packed {
logic [31:0] buffer1_addr;
} rdes0_rd_t;
// RDES0: 扩展状态(写回格式)
typedef struct packed {
logic [2:0] filter_id; // [31:29] Filter ID
logic l4_filter_match; // [28] L4 Filter Match
logic l3_filter_match; // [27] L3 Filter Match
logic [7:0] mac_addr_match; // [26:19] MAC Address Match
logic hash_filter; // [18] Hash Filter
logic da_filter_fail; // [17] DA Filter Fail
logic sa_filter_fail; // [16] SA Filter Fail
logic [15:0] reserved; // [15:0] Reserved
} rdes0_wb_t;
// RDES3: 控制字段(读格式)
typedef struct packed {
logic own; // [31] Own Bit
logic ioc; // [30] Interrupt on Completion
logic [29:0] reserved; // [29:0] Reserved
} rdes3_rd_t;
// RDES3: 状态字段(写回格式)
typedef struct packed {
logic own; // [31] Own Bit
logic ctxt; // [30] Context Descriptor
logic last_desc; // [29] Last Descriptor
logic first_desc; // [28] First Descriptor
logic error_summary; // [27] Error Summary
logic buffer1_len_valid;// [26] Buffer 1 Length Valid
logic payload_len_valid;// [25] Payload Length Valid
logic error_status; // [24] Error Status
logic crc_error; // [23] CRC Error
logic giant_packet; // [22] Giant Packet
logic rx_watchdog_tmo; // [21] Receive Watchdog Timeout
logic overflow_error; // [20] Overflow Error
logic rx_error; // [19] Receive Error
logic [18:0] reserved; // [18:0] Reserved
logic [14:0] frame_len; // [14:0] Frame Length
} rdes3_wb_t;
// 完整的接收描述符
typedef struct packed {
logic [31:0] rdes0;
logic [31:0] rdes1;
logic [31:0] rdes2;
logic [31:0] rdes3;
} rx_descriptor_t;
// ============================================
// 枚举定义
// ============================================
// VLAN标签操作
typedef enum logic [1:0] {
VLAN_NO_ACTION = 2'b00,
VLAN_REMOVE = 2'b01,
VLAN_INSERT = 2'b10,
VLAN_REPLACE = 2'b11
} vlan_tag_op_e;
// CRC和填充控制
typedef enum logic [1:0] {
CRC_PAD_AUTO = 2'b00, // 自动添加CRC和填充
CRC_PAD_DISABLE = 2'b01, // 禁用
CRC_PAD_RESERVED = 2'b10,
CRC_PAD_ALL = 2'b11 // 添加所有
} crc_pad_ctrl_e;
endpackage
8.2 描述符管理类
import gmac_descriptor_pkg::*;
// 描述符管理类
class DescriptorManager;
// 描述符队列
tx_descriptor_t tx_desc_queue[$];
rx_descriptor_t rx_desc_queue[$];
// 统计信息
int tx_desc_count;
int rx_desc_count;
// ============================================
// 发送描述符操作
// ============================================
// 初始化发送描述符
function void init_tx_descriptor(
ref tx_descriptor_t desc,
input logic [31:0] buf1_addr,
input logic [31:0] buf2_addr,
input logic [13:0] buf1_len,
input logic [13:0] buf2_len,
input bit is_first,
input bit is_last,
input bit enable_interrupt
);
// TDES0
desc.tdes0.buffer1_addr = buf1_addr;
// TDES1
desc.tdes1.buffer2_addr = buf2_addr;
// TDES2
desc.tdes2.ioc = enable_interrupt ? 1'b1 : 1'b0;
desc.tdes2.ttse = 1'b0; // 默认不使能时间戳
desc.tdes2.buffer2_len = buf2_len;
desc.tdes2.vlan_tag_ctrl = VLAN_NO_ACTION;
desc.tdes2.buffer1_len = buf1_len;
// TDES3
desc.tdes3.own = 1'b1; // DMA拥有
desc.tdes3.ctxt = 1'b0; // 普通描述符
desc.tdes3.first_desc = is_first ? 1'b1 : 1'b0;
desc.tdes3.last_desc = is_last ? 1'b1 : 1'b0;
desc.tdes3.crc_pad_ctrl = CRC_PAD_AUTO;
desc.tdes3.sa_ins_ctrl = 3'b000;
desc.tdes3.slot_num = 4'h0;
desc.tdes3.tcp_seg_en = 1'b0;
desc.tdes3.checksum_ins = 2'b00;
desc.tdes3.frame_len = 15'(buf1_len + buf2_len);
tx_desc_count++;
endfunction
// 检查发送完成
function bit check_tx_complete(
input tx_descriptor_t desc,
output logic [63:0] timestamp
);
if (desc.tdes3.own == 1'b0) begin
// DMA已完成
timestamp = {desc.tdes1.buffer2_addr, desc.tdes0.buffer1_addr};
return 1'b1;
end
return 1'b0;
endfunction
// ============================================
// 接收描述符操作
// ============================================
// 初始化接收描述符
function void init_rx_descriptor(
ref rx_descriptor_t desc,
input logic [31:0] buf1_addr,
input logic [31:0] buf2_addr,
input bit enable_interrupt
);
desc.rdes0 = buf1_addr;
desc.rdes1 = buf2_addr;
desc.rdes2 = 32'h0;
desc.rdes3 = {
1'b1, // OWN = 1
enable_interrupt ? 1'b1 : 1'b0, // IOC
30'h0
};
rx_desc_count++;
endfunction
// 检查接收完成
function bit check_rx_complete(
input rx_descriptor_t desc,
output logic [14:0] pkt_len,
output bit has_error
);
rdes3_wb_t rdes3_wb;
// 转换为写回格式
rdes3_wb = rdes3_wb_t'(desc.rdes3);
if (rdes3_wb.own == 1'b0) begin
// DMA已完成
if (rdes3_wb.first_desc && rdes3_wb.last_desc) begin
// 完整包
pkt_len = rdes3_wb.frame_len;
has_error = rdes3_wb.error_status;
return 1'b1;
end
end
return 1'b0;
endfunction
// 打印接收状态
function void print_rx_status(input rx_descriptor_t desc);
rdes3_wb_t rdes3_wb;
rdes3_wb = rdes3_wb_t'(desc.rdes3);
$display("=== RX Descriptor Status ===");
$display("OWN: %0d", rdes3_wb.own);
$display("First Desc: %0d", rdes3_wb.first_desc);
$display("Last Desc: %0d", rdes3_wb.last_desc);
$display("Frame Length: %0d bytes", rdes3_wb.frame_len);
$display("Error Status: %0d", rdes3_wb.error_status);
if (rdes3_wb.error_status) begin
$display(" CRC Error: %0d", rdes3_wb.crc_error);
$display(" Giant Packet: %0d", rdes3_wb.giant_packet);
$display(" Watchdog Timeout: %0d", rdes3_wb.rx_watchdog_tmo);
$display(" Overflow Error: %0d", rdes3_wb.overflow_error);
end
endfunction
endclass
8.3 测试平台示例
module gmac_descriptor_tb;
import gmac_descriptor_pkg::*;
// 实例化描述符管理器
DescriptorManager desc_mgr;
// 测试描述符
tx_descriptor_t tx_desc;
rx_descriptor_t rx_desc;
// 测试数据
logic [31:0] tx_buffer = 'h1000_0000;
logic [31:0] rx_buffer = 'h2000_0000;
logic [63:0] timestamp;
logic [14:0] pkt_len;
bit has_error;
initial begin
// 创建描述符管理器
desc_mgr = new();
$display("========================================");
$display("GMAC Descriptor Testbench");
$display("========================================\n");
// ============================================
// 测试1:发送描述符配置
// ============================================
$display("Test 1: TX Descriptor Configuration");
$display("----------------------------------------");
desc_mgr.init_tx_descriptor(
.desc(tx_desc),
.buf1_addr(tx_buffer),
.buf2_addr(32'h0),
.buf1_len(14'd1500),
.buf2_len(14'h0),
.is_first(1),
.is_last(1),
.enable_interrupt(1)
);
$display("TX Descriptor Created:");
$display(" TDES0 (Buffer1 Addr): 0x%08h", tx_desc.tdes0);
$display(" TDES1 (Buffer2 Addr): 0x%08h", tx_desc.tdes1);
$display(" TDES2 (Control): 0x%08h", tx_desc.tdes2);
$display(" TDES3 (Status): 0x%08h", tx_desc.tdes3);
$display(" OWN: %0d, FD: %0d, LD: %0d",
tx_desc.tdes3.own,
tx_desc.tdes3.first_desc,
tx_desc.tdes3.last_desc);
$display("");
// ============================================
// 测试2:接收描述符配置
// ============================================
$display("Test 2: RX Descriptor Configuration");
$display("----------------------------------------");
desc_mgr.init_rx_descriptor(
.desc(rx_desc),
.buf1_addr(rx_buffer),
.buf2_addr(32'h0),
.enable_interrupt(1)
);
$display("RX Descriptor Created:");
$display(" RDES0 (Buffer1 Addr): 0x%08h", rx_desc.rdes0);
$display(" RDES1 (Buffer2 Addr): 0x%08h", rx_desc.rdes1);
$display(" RDES2 (Reserved): 0x%08h", rx_desc.rdes2);
$display(" RDES3 (Control): 0x%08h", rx_desc.rdes3);
$display("");
// ============================================
// 测试3:模拟接收完成
// ============================================
$display("Test 3: Simulate RX Complete");
$display("----------------------------------------");
// 模拟DMA写回
rx_desc.rdes3 = {
1'b0, // OWN = 0 (DMA完成)
1'b0, // CTXT = 0
1'b1, // LD = 1
1'b1, // FD = 1
1'b0, // ERR = 0
1'b0, // B1L = 0
1'b0, // PL = 0
1'b0, // ES = 0 (无错误)
1'b0, // CE = 0
1'b0, // GP = 0
1'b0, // RWT = 0
1'b0, // OE = 0
1'b0, // RE = 0
19'h0, // Reserved
15'd1514 // Frame Length = 1514 bytes
};
// 检查接收完成
if (desc_mgr.check_rx_complete(rx_desc, pkt_len, has_error)) begin
$display("RX Complete Detected!");
$display(" Packet Length: %0d bytes", pkt_len);
$display(" Has Error: %0d", has_error);
desc_mgr.print_rx_status(rx_desc);
end
$display("");
// ============================================
// 统计信息
// ============================================
$display("========================================");
$display("Statistics:");
$display(" TX Descriptors: %0d", desc_mgr.tx_desc_count);
$display(" RX Descriptors: %0d", desc_mgr.rx_desc_count);
$display("========================================");
$finish;
end
endmodule
九、常见问题与调试技巧
9.1 描述符错误(Descriptor Error)
现象
- DMA设置DE位(Descriptor Error)
- 传输停止,无法继续
可能原因
-
OWN位设置错误
错误:驱动设置OWN=0,DMA无法获取描述符
正确:驱动设置OWN=1,DMA才能处理 -
FD/LD位设置不正确
错误:一个包的描述符没有正确设置FD和LD
正确:第一个描述符FD=1,最后一个描述符LD=1 -
缓冲区地址无效
错误:Buffer地址为0或指向无效内存
正确:确保Buffer地址有效且可访问 -
描述符链断裂
错误:描述符链中间断开,DMA无法继续
正确:确保描述符链连续且形成环形
调试方法
// 检查描述符有效性
function bit validate_tx_descriptor(input tx_descriptor_t desc);
bit is_valid = 1'b1;
// 检查OWN位
if (desc.tdes3.own == 1'b0) begin
$display("Error: OWN bit is 0, DMA cannot process");
is_valid = 1'b0;
end
// 检查Buffer地址
if (desc.tdes0.buffer1_addr == 32'h0) begin
$display("Error: Buffer 1 address is NULL");
is_valid = 1'b0;
end
// 检查长度
if (desc.tdes2.buffer1_len == 14'h0) begin
$display("Warning: Buffer 1 length is 0");
end
// 检查FD/LD
if (desc.tdes3.first_desc && desc.tdes3.last_desc) begin
$display("Info: Single descriptor packet");
end
return is_valid;
endfunction
9.2 接收溢出问题
现象
- OE位(Overflow Error)被设置
- 数据丢失,丢包率上升
原因分析
接收溢出的根本原因:
1. 接收FIFO满 → 数据无法写入
2. 描述符不足 → 无可用缓冲区
3. 处理速度慢 → 来不及回收描述符
解决方案
-
增加描述符数量
// 建议配置
parameter RX_DESC_NUM = 512; // 增加到512个
parameter RX_BUFFER_SIZE = 2048; // 2KB缓冲区 -
使用更大的接收缓冲区
// 支持Jumbo Frame
parameter RX_BUFFER_SIZE = 9216; // 9KB缓冲区 -
优化中断处理
// 批量处理接收描述符
task process_rx_batch();
int processed = 0;// 一次处理多个描述符 while (processed < BATCH_SIZE) begin if (check_rx_complete(rx_desc_queue[rx_index], pkt_len, has_error)) begin process_rx_packet(pkt_len); reinit_rx_descriptor(rx_desc_queue[rx_index]); rx_index = (rx_index + 1) % RX_DESC_NUM; processed++; end else begin break; // 没有更多完成的描述符 end end // 只在批量处理完成后清中断 clear_rx_interrupt();endtask
9.3 性能优化建议
描述符数量配置
经验值:
- 1Gbps网络:TX/RX各256个描述符
- 10Gbps网络:TX/RX各1024个描述符
- 高负载场景:TX/RX各2048个描述符
计算公式:
描述符数量 = (吞吐量 × 最大延迟) / (MTU × 8)
缓冲区大小配置
标准配置:
- MTU = 1500字节
- Buffer Size = 2048字节(预留头空间)
Jumbo Frame配置:
- MTU = 9000字节
- Buffer Size = 9216字节
对齐要求:
- 建议缓存行对齐(64字节)
- 提高DMA传输效率
中断优化
优化策略:
1. NAPI方式:轮询+中断混合
2. 中断合并:多个包合并一次中断
3. CPU亲和性:绑定到特定CPU核心
配置示例:
- Rx Pacing: 使能,设置合理的包数阈值
- Tx Interrupt: 只在描述符队列快空时产生中断
十、总结与展望
10.1 核心知识点回顾
通过本文的学习,我们掌握了:
-
描述符的本质
- 描述符是DMA与驱动之间的数据传输契约
- 包含地址、长度、控制、状态等信息
- 实现零拷贝、异步传输
-
两种描述符类型
- Normal Descriptor:承载数据
- Context Descriptor:传递控制参数
-
关键字段理解
- OWN位:实现驱动与DMA的同步
- FD/LD位:标识数据包边界
- IOC位:控制中断产生
-
工作流程
- 发送:驱动配置 → DMA传输 → 状态写回
- 接收:驱动准备 → DMA接收 → 状态写回
-
高级特性
- 双缓冲模式:支持分散/聚集I/O
- TSO:TCP Segmentation Offload
- IEEE 1588:精确时间戳
- TSN:时间敏感网络
10.2 实践建议
-
阅读Databook
- DWC Ethernet QoS Databook Chapter 19
- 重点关注描述符格式和工作流程
-
查看波形
- 使用波形查看工具观察DMA行为
- 重点观察OWN位变化、状态写回
参考资料
-
Synopsys DWC Ethernet QoS Databook (Version 5.10a)
- Chapter 19: Descriptors (Page 1316-1351)
- Chapter 5: Station Management Agent
-
IEEE 802.3 Ethernet Standard
- MAC Control and DMA specification
-
IEEE 1588 Precision Time Protocol (PTP)
- Timestamping mechanism
-
IEEE 802.1Qbv Time-Aware Shaping
- TSN gate control scheduling
附录:描述符位域速查表
A.1 发送描述符TDES3速查表
| 位 | 名称 | 值 | 说明 |
|---|---|---|---|
| 31 | OWN | 1/0 | 所有权位:1=DMA拥有,0=驱动拥有 |
| 30 | CTXT | 0 | 上下文类型:0=普通描述符 |
| 29 | FD | 1/0 | 首描述符:1=包的第一个描述符 |
| 28 | LD | 1/0 | 尾描述符:1=包的最后一个描述符 |
| 27:26 | CPC | 00 | CRC和填充控制:00=自动 |
| 25:23 | SAIC | 000 | 源地址插入控制 |
| 22:19 | SLOTNUM | - | 门控调度槽号 |
| 18 | TSE | 0/1 | TCP分段使能 |
| 17:16 | CIC | 00 | 校验插入控制 |
| 14:0 | FL | - | 帧长度(字节) |
A.2 接收描述符RDES3速查表
| 位 | 名称 | 值 | 说明 |
|---|---|---|---|
| 31 | OWN | 1/0 | 所有权位:1=DMA拥有,0=驱动拥有 |
| 30 | CTXT | 0/1 | 上下文描述符标识 |
| 29 | LD | 1/0 | 尾描述符:1=包的最后一个描述符 |
| 28 | FD | 1/0 | 首描述符:1=包的第一个描述符 |
| 27 | ERR | 1/0 | 错误汇总 |
| 24 | ES | 1/0 | 错误状态:1=接收出错 |
| 23 | CE | 1/0 | CRC错误 |
| 22 | GP | 1/0 | 巨型包 |
| 21 | RWT | 1/0 | 接收看门狗超时 |
| 20 | OE | 1/0 | 溢出错误 |
| 19 | RE | 1/0 | 接收错误 |
| 14:0 | Length | - | 接收帧长度(字节) |
来源 :DWC Ethernet QoS Databook学习笔记
声明:本文基于Synopsys官方文档整理,仅供学习交流使用