第二章 PCIe DMA 基础与框架

各位同学,今天我们来聊聊PCIe DMA。说实话,这个主题是FPGA交易网卡开发中最核心的部分之一。我刚开始接触这个领域时,也被PCIe的复杂协议栈搞得晕头转向。但别担心,我会用最接地气的方式,把这块硬骨头啃下来。

2.1 PCIe协议概述

PCIe,全称是Peripheral Component Interconnect Express。说白了,就是电脑内部的高速总线。为什么叫Express?因为它快啊!

我个人习惯把PCIe理解成一个高速公路系统。传统的PCI是并行总线,就像一条多车道的老路,所有设备共享带宽。而PCIe是串行总线,每个设备都有自己的专用车道,叫Lane。一条Lane是一对差分信号,一个发送一个接收。

常见的配置有x1、x4、x8、x16。x16就是16条Lane,带宽最大。我在项目中用过x8的配置,跑10Gbps的网卡数据,绰绰有余。

PCIe的关键特性:

  • 点对点连接:每个设备直接连到Root Complex,不共享总线
  • 差分信号:抗干扰能力强,频率可以跑得很高
  • 分层协议:事务层、数据链路层、物理层,各司其职
  • 流量控制:基于信用机制,防止数据溢出

为什么会设计成分层协议?你想想看,如果所有功能都揉在一起,调试起来得多痛苦。分层的好处是,每一层只关心自己的事。物理层只管信号的收发,数据链路层保证数据不出错,事务层处理读写请求。

这里我画了一张PCIe协议栈的结构图,帮你理清层次关系:

事务层(Transaction Layer) TLP封装、内存读写、IO访问、配置空间 数据链路层(Data Link Layer) DLLP、CRC校验、ACK/NAK重传、序列号 物理层(Physical Layer) 8b/10b编码、串并转换、电气特性、Lane管理 数据流向:应用 → 事务层 → 数据链路层 → 物理层 → 对端 每个TLP在数据链路层被封装成DLLP,再在物理层加上起始和结束标志 TLP = 事务层包,是PCIe通信的基本单位

嗯,这里要注意,事务层包(TLP)是我们FPGA开发者最常打交道的。所有的DMA传输,本质上就是在构造和解析TLP。

2.2 DMA传输原理

DMA,Direct Memory Access,直接内存访问。为什么需要DMA?

想象一下,如果没有DMA,CPU要从网卡收一个数据包,得先读网卡的寄存器,再把数据搬到内存。每搬一个字节,CPU就得停下手里的活。这效率,你想想看,得多低。

DMA的核心理念是:让设备自己把数据搬到内存,搬完了通知CPU一声就行。CPU只需要在开始的时候告诉设备「数据放哪里」,然后就可以去干别的事了。

我在项目中遇到过这样的情况:刚开始用PIO(Programmed IO)方式,CPU占用率直接飙到90%以上。换成DMA后,CPU占用率降到5%以下。差距就是这么明显。

DMA传输的关键要素:

  1. 源地址:数据从哪里来(比如网卡的FIFO)
  2. 目的地址:数据放到哪里(主机内存地址)
  3. 传输长度:要搬多少数据
  4. 描述符:描述一次DMA传输的控制信息
  5. 完成通知:传输完成后如何告诉CPU

DMA传输一般分为两种模式:

模式 特点 适用场景
寄存器模式 CPU直接读写DMA控制寄存器 小数据量、控制类操作
描述符模式 CPU在内存中构建描述符链表,DMA控制器自动取指执行 大数据量、高性能场景

交易网卡通常使用描述符模式。为什么?因为网卡的数据包是连续不断的,用描述符链表可以做到流水线处理。CPU准备好一批描述符,DMA控制器一个一个执行,执行完了再通知CPU。这样CPU和DMA可以并行工作。

避坑指南:我曾经在描述符对齐问题上吃过亏。PCIe要求描述符地址必须按16字节对齐,否则会触发Unsupported Request。后来我养成了习惯,所有描述符都用__attribute__((aligned(16)))来声明。

2.3 XDMA IP核配置

Xilinx的XDMA IP核,是我们做PCIe DMA开发的好帮手。它把复杂的PCIe协议封装成了简单的AXI接口。说白了,你不需要关心TLP怎么构造,只需要往AXI接口上读写数据就行。

XDMA IP核支持两种接口:

  • AXI4 Memory Map:带地址的总线接口,适合随机访问
  • AXI4-Stream:流式接口,适合连续数据流

我个人习惯在交易网卡中使用AXI4-Stream接口。为什么?因为网卡的数据是流式的,一个包接一个包,用Stream接口天然匹配。而且Stream接口延迟更低,吞吐量更高。

配置XDMA IP核时,有几个关键参数需要注意:

参数 说明 我的建议
Number of DMA Channels DMA通道数量 至少2个,一个读一个写
AXI Data Width 数据位宽 256位(64字节)性能最好
AXI Clock AXI接口时钟频率 250MHz以上,看你的FPGA等级
Max Payload Size 最大TLP负载 512字节,兼容性最好
Descriptor Bypass 是否绕过描述符 建议开启,减少延迟

嗯,这里要注意,Descriptor Bypass这个选项很多人会忽略。开启后,CPU可以直接指定DMA传输的源地址、目的地址和长度,不需要构建描述符。对于小数据量的控制命令,非常方便。

2.4 AXI4-Stream接口时序

AXI4-Stream接口,是XDMA IP核和用户逻辑之间的桥梁。它的时序其实很简单,核心就是一对握手信号:

  • TVALID:发送方说「我有数据要发」
  • TREADY:接收方说「我准备好收了」
  • TDATA:数据总线
  • TLAST:最后一个数据(包结束标志)
  • TKEEP:哪些字节是有效的

数据传输发生在TVALID和TREADY同时为高的时钟沿。这个规则很简单,但实际项目中容易出问题。

我曾经遇到过一个bug:发送方把TVALID拉高后,不等TREADY就改变了TDATA。结果接收方在TREADY拉高时采到的数据是错的。后来我加了一个状态机,确保TVALID拉高后,TDATA保持不变直到传输完成。

下面是一个标准的AXI4-Stream写时序示例:

// AXI4-Stream 发送状态机示例
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        tvalid <= 1'b0;
        tdata  <= 64'b0;
        tlast  <= 1'b0;
        state  <= IDLE;
    end else begin
        case (state)
            IDLE: begin
                if (fifo_empty) begin
                    state <= IDLE;
                end else begin
                    tdata  <= fifo_rdata;  // 从FIFO读取数据
                    tvalid <= 1'b1;
                    if (fifo_last) begin
                        tlast <= 1'b1;
                        state <= LAST;
                    end else begin
                        state <= SEND;
                    end
                end
            end
            SEND: begin
                if (tvalid && tready) begin
                    // 数据被成功接收
                    tvalid <= 1'b0;
                    tlast  <= 1'b0;
                    state  <= IDLE;
                end
            end
            LAST: begin
                if (tvalid && tready) begin
                    // 最后一个数据被接收
                    tvalid <= 1'b0;
                    tlast  <= 1'b0;
                    state  <= IDLE;
                end
            end
        endcase
    end
end

AXI4-Stream时序要点:

  • TVALID不能依赖TREADY,发送方必须主动拉高
  • TREADY可以随时拉高或拉低,接收方有流量控制权
  • TLAST必须在最后一个数据时拉高,且与TVALID同步
  • TKEEP用于指示哪些字节有效,比如64位数据中只有低32位有效

你想想看,如果接收方处理不过来,可以把TREADY拉低,发送方就会暂停发送。这种背压机制,保证了数据不会丢失。

最后,我画了一张AXI4-Stream的时序图,帮你直观理解:

CLK TVALID TREADY TDATA 传输发生 传输发生 当TVALID和TREADY同时为高时,数据在时钟上升沿被采样

好了,这一章的内容就到这里。PCIe协议的分层设计、DMA的传输原理、XDMA IP核的配置要点、AXI4-Stream的握手时序,这些都是后续开发的基础。把这些搞懂了,后面写驱动代码就会顺手很多。


公众号:蓝海资料掘金营,微信deep3321