一、开发流程概述
在 FPGA 外挂 NVMe 硬盘的场景中,通常将 FPGA 配置为 Root Complex (RC),而 NVMe SSD 作为 Endpoint (EP)。NVMe 协议基于 PCIe 总线,属于其特殊应用。开发核心在于 PCIe 链路的建立、配置空间枚举以及 NVMe 控制器的初始化与命令交互。
1.1 PCIe 基础机制
PCIe 协议涉及两种机制(请求与完成)、三层结构(事务层、链路层、物理层)及四种事务类型(内存、I/O、配置、消息)。数据交换采用请求 - 响应模式,用户主要关注事务层 TLP 包的构造与解析。
TLP 包头部包含 Fmt(格式位,区分 3DW/4DW)和 Type(传输类型),两者结合确定数据包属性。开发过程中常用的事务类型包括 MRd(内存读)、MWr(内存写)、CfgRd0/CfgWr0(配置读写)、Cpl/CplD(完成包)及 Msg(消息)。
1.1.1 PCIe 配置空间
RC 与 EP 均需访问各自的 PCIe 配置空间(共 4KB)。每个 Function 对应一个配置空间:
- 0-3Fh:PCI 兼容头(Type 0 用于 PCIe 设备,Type 1 用于桥)。
- 40h-FFh:能力结构体(Capability Structures),如 MSI/MSI-X 中断相关。
- 100h-FFFh:扩展配置空间。
扫描能力结构体是必须的步骤。通过 Header 中的 Capability Pointer 寄存器遍历链表,直至指针为 0。不同设备类型的 Capability ID 不同,例如 0x11 代表 MSI-X 中断。
1.2 建链与枚举
1.2.1 建链
上电后,FPGA 与 NVMe 设备根据 PCIe 协议进入 LTSSM 状态机,自动协商速率和宽度。若无硬件问题,两端应最终稳定在 L0 状态。可通过 PCIe IP 核的接口参数检测链路状态(Link Up, Width, Rate)。
1.2.2 枚举
Active 状态下开始设备枚举。RC 通过 CfgRd 访问 EP 的 BDF(Bus, Device, Function),读取 Vendor ID、Device ID 等寄存器确认设备存在。若收到 CPLD 完成包且 ClassCode 符合(如 NVMe 为 0x010802),则枚举成功。
1.3 RC 与 EP 初始化流程
在读写前,需按顺序完成 PCIe 配置空间初始化及 NVMe 控制器寄存器配置。
1.3.1 RC 端初始化
FPGA 端的 PCIe 配置空间(ID、BAR、能力结构等)通常由 IP 核设置自动完成。访问与重配置可通过核的配置管理接口(CMI)实现。
1.3.2 EP 端初始化
NVMe 硬盘的配置空间初始化需手动操作:
- 获取 Capabilities 信息:从偏移 0x34 读取 Capability Pointer,遍历链表获取所有能力结构。
- 配置能力结构:如 Cap ID=0x10 为 PCIe 能力,需配置 Device Control 等寄存器。
- 配置 BAR 基地址:消费级 NVMe 通常一页 4KByte。通过 CfgWr 设置 TYPE0 配置空间偏移 0x04 的 BAR0,确保 4K 对齐。
- 配置 MSI-X 中断:MSI-X 支持独立的中断向量表(Table)和 Pending 表,存放在 BAR 空间中。需配置 Table BIR、Table Offset 及 Message Control 寄存器(使能位、Mask 位、Table Size)。
1.3.3 NVMe 控制器寄存器配置
完成 PCIe 配置后,需配置 NVMe 控制器寄存器(位于 BAR0/1 映射空间):
- 等待 CSTS.RDY 变为 0(若不满足则配置使其就绪)。
- 配置 AQA(Admin 队列属性)、ASQ(Admin SQ 基址)、ACQ(Admin CQ 基址)。
- 配置 CC(控制器配置)寄存器,设置队列元素大小、页大小等。
- 置 CC.EN 为 1 使能控制器。
- 等待 CSTS.RDY 置 1。
关键寄存器说明:
- CAP:控制器能力(页大小范围、DB 步长等)。
- CC:当前配置(CAP 的子集,重启可换)。
- AQA/ASQ/ACQ:定义 Admin 队列。
- DoorBell (0x1000+):提交队列(SQ)和完成队列(CQ)的门铃寄存器,Host 写入新尾指针通知 Controller。
1.4 NVMe 协议命令
NVMe 命令分为 Admin 命令和 IO 命令,均封装为 16 DW 的 TLP 包。
| Dword | 字段 |
|---|---|
| 0 | CID、传输方式、聚合操作、操作码 |
| 1 | NID(命名空间 ID) |
| 2-3 | 保留 |
| 4-5 | 元数据指针 (MPTR) |
| 6-9 | 数据指针 (DPTR) |
| 10-15 | 命令特定参数 |
1.4.1 Admin 命令
Admin 命令仅提交到 Admin SQ/CQ,用于管理控制器。常用指令如下:
| 操作码 | 指令 | 作用 |
|---|---|---|
| 00h | 删除 I/O SQ | 释放 SQ 空间 |
| 01h | 创建 I/O SQ | 分配地址、优先级、大小 |
| 02h | 获取日志 | 返回日志页 |
| 04h | 删除 I/O CQ | 释放 CQ 空间 |
| 05h | 创建 I/O CQ | 分配地址、中断向量、大小 |
| 06h | Identify | 返回 Controller/Namespace 数据结构 |
| 09h | Set Features | 设置特性 |
| 0Ah | Get Features | 获取特性 |
| 80h | Format | 擦除 LB 内容 |
注意:先创建 CQ 再创建 SQ。Admin 队列通过 ASQ 等寄存器创建。
1.4.2 IO 命令
IO 命令负责数据传输,以 LB(逻辑块)为单位。常用指令:
| 操作码 | 指令 | 作用 |
|---|---|---|
| 00h | Flush | 提交数据到 NVM |
| 01h | Write | 写入 NVM |
| 02h | Read | 读取 NVM |
| 04h | Write Uncorrectable | 标记无效块 |
| 05h | Compare | 比较数据 |
1.4.3 命令交互过程
- Host 准备命令:将 16 DW 命令写入分配的 SQ 中。
- 更新 DoorBell:Host 通过 MWr 向 Controller 的 SQ DB 寄存器(BAR0+0x1000)写入尾指针增量。
- Controller 取命令:Controller 通过 DMA 从 ASQ 读取命令。
- 执行命令:Controller 处理数据。
- 返回完成:Controller 通过 DMA 将 4 DW 完成包写入 Host 的 CQ 基址(ACQ)。
- 中断通知:Controller 发起 MSI-X 中断(需检查未屏蔽)。
- Host 响应:Host 接收中断,更新 CQ DoorBell(BAR0+0x1004)告知已处理完成。
1.4.4 执行顺序
RC 读写前需按序执行 Admin 命令:
- Identify(获取数据结构)。
- Set Features(申请 IO 队列数量)。
- 创建 IO CQ。
- 创建 IO SQ。
创建的队列数需与 AQA 寄存器中的 ACQS/ASQS 及 Set Features 申请值一致(最大 4K,最小 2 个)。
二、读写测试与结果
2.1 硬件环境
测试需 FPGA 板卡(如 7 系列)、M.2 接口及 NVMe 硬盘。PCIe Gen2 x4 理论上限约 2GB/s。本次测试使用拆机 M.2 NVMe 盘(512GB,LB 512B,LBA 约 10 亿个)。建议测试时选用高性能盘(如三星 980/990)以获得更准确带宽表现。
2.2 文件系统
NVMe 支持 NTFS/exFAT 等,但 FPGA 纯逻辑实现文件系统较为繁琐。本测试跳过文件系统层,直接通过 LB 地址进行读写,适用于数据采集回放等场景。
2.3 状态监控
程序启动后可读取以下关键状态:
w_Namespace_Size:LB 数目。w_lb_size:单 LB 字节数(2^lb_size)。w_nvme_config_done:配置完成标志。w_link_width/rate:链路宽度与速率(如 x4, 5.0GT/s)。w_pl_ltssm_state:LTSSM 状态(应为 L0)。
2.4 测速结果
通过 VIO 控制读写,BAR 设置为 4K 对齐。分别测试 1GB 至 100GB 数据量的读写速率:
| 测试数据量 | 写速率 | 读速率 |
|---|---|---|
| 1GB | 1094 MB/S | 1251 MB/S |
| 10GB | 920 MB/S | 1261 MB/S |
| 50GB | 843 MB/S | 1262 MB/S |
| 100GB | 836 MB/S | 1261 MB/S |
写 1GB 耗时约 116938546 个时钟周期(125MHz),读 1GB 耗时约 102270401 个时钟周期。整体性能受限于 PCIe 通道带宽及 SSD 自身性能。

