基于Zynq UltraScale+ MPSoC的PL DDR4直写多NVMe条带阵列(RAID 0)与exFAT高速采集存储系统

本文介绍一种面向高速ADC连续采集场景的存储架构:采集数据首先进入PL侧DDR4环形缓冲区,随后直接作为NVMe写命令的数据源写入多块SSD;PS侧Linux负责设备管理、exFAT文件系统和采集控制,但采集载荷不经过PS DDR,也不进入Linux页缓存。多块NVMe由自定义虚拟块设备组成条带阵列,对上层只呈现一个/dev/plstripe0设备和一个exFAT文件系统。

一、项目背景

在高速ADC采集系统中,真正困难的通常不是"把ADC数据送进FPGA",而是如何长期、连续、无丢点地保存数据。

本项目基于Zynq UltraScale+ MPSoC构建,主要需求如下:

  • PL侧持续接收多通道ADC数据;
  • 使用PL侧8 GiB DDR4作为环形缓冲区;
  • 采集数据直接从PL DDR4写入NVMe SSD,不搬运到PS DDR;
  • 使用多块NVMe扩展持续写入带宽;
  • Linux只负责设备控制、文件预分配和exFAT元数据管理;
  • 支持预分配文件池、多文件无间断切换和重复覆盖采集;
  • 能够检测丢点、重复数据、乱序以及通道错误。

当前测试源为8通道、每通道16 bit。以250 MSPS为例,原始数据率为:

text 复制代码
250,000,000 × 8 × 16 / 8
= 4,000,000,000 Byte/s
≈ 3814.7 MiB/s

单块PCIe 3.0 ×4 NVMe很难长期稳定承担这一数据率,因此系统采用双NVMe并行条带化写入。

二、为什么不采用"PL → PS DDR → 文件系统 → SSD"

传统Linux采集路径通常是:

text 复制代码
ADC → PL FIFO → DMA → PS DDR → write() → Linux页缓存 → NVMe

这条路径实现简单,但在数GiB/s持续采集场景中存在几个明显问题:

  1. 采集数据需要从PL搬运到PS DDR;
  2. 文件写入还可能发生页缓存复制;
  3. CPU、内存控制器和缓存维护压力较大;
  4. 数据通路延迟和抖动容易受Linux调度影响;
  5. PS DDR带宽会同时被采集、文件系统和其他应用占用。

因此,本项目将PL DDR4中的物理地址直接填入NVMe命令的PRP字段。SSD通过PCIe读取PL DDR4中的采集载荷,PS只处理少量命令、描述符、PRP List和文件系统元数据。

三、系统总体架构

系统由PL侧高速数据面、PS侧Linux控制面、双NVMe物理通道和exFAT文件层组成。总体框图如下:
#mermaid-svg-fjTbVt8Wyj9gNiqD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fjTbVt8Wyj9gNiqD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fjTbVt8Wyj9gNiqD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fjTbVt8Wyj9gNiqD .error-icon{fill:#552222;}#mermaid-svg-fjTbVt8Wyj9gNiqD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fjTbVt8Wyj9gNiqD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fjTbVt8Wyj9gNiqD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fjTbVt8Wyj9gNiqD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fjTbVt8Wyj9gNiqD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fjTbVt8Wyj9gNiqD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fjTbVt8Wyj9gNiqD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fjTbVt8Wyj9gNiqD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fjTbVt8Wyj9gNiqD .marker.cross{stroke:#333333;}#mermaid-svg-fjTbVt8Wyj9gNiqD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fjTbVt8Wyj9gNiqD p{margin:0;}#mermaid-svg-fjTbVt8Wyj9gNiqD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fjTbVt8Wyj9gNiqD .cluster-label text{fill:#333;}#mermaid-svg-fjTbVt8Wyj9gNiqD .cluster-label span{color:#333;}#mermaid-svg-fjTbVt8Wyj9gNiqD .cluster-label span p{background-color:transparent;}#mermaid-svg-fjTbVt8Wyj9gNiqD .label text,#mermaid-svg-fjTbVt8Wyj9gNiqD span{fill:#333;color:#333;}#mermaid-svg-fjTbVt8Wyj9gNiqD .node rect,#mermaid-svg-fjTbVt8Wyj9gNiqD .node circle,#mermaid-svg-fjTbVt8Wyj9gNiqD .node ellipse,#mermaid-svg-fjTbVt8Wyj9gNiqD .node polygon,#mermaid-svg-fjTbVt8Wyj9gNiqD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fjTbVt8Wyj9gNiqD .rough-node .label text,#mermaid-svg-fjTbVt8Wyj9gNiqD .node .label text,#mermaid-svg-fjTbVt8Wyj9gNiqD .image-shape .label,#mermaid-svg-fjTbVt8Wyj9gNiqD .icon-shape .label{text-anchor:middle;}#mermaid-svg-fjTbVt8Wyj9gNiqD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fjTbVt8Wyj9gNiqD .rough-node .label,#mermaid-svg-fjTbVt8Wyj9gNiqD .node .label,#mermaid-svg-fjTbVt8Wyj9gNiqD .image-shape .label,#mermaid-svg-fjTbVt8Wyj9gNiqD .icon-shape .label{text-align:center;}#mermaid-svg-fjTbVt8Wyj9gNiqD .node.clickable{cursor:pointer;}#mermaid-svg-fjTbVt8Wyj9gNiqD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fjTbVt8Wyj9gNiqD .arrowheadPath{fill:#333333;}#mermaid-svg-fjTbVt8Wyj9gNiqD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fjTbVt8Wyj9gNiqD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fjTbVt8Wyj9gNiqD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fjTbVt8Wyj9gNiqD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fjTbVt8Wyj9gNiqD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fjTbVt8Wyj9gNiqD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fjTbVt8Wyj9gNiqD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fjTbVt8Wyj9gNiqD .cluster text{fill:#333;}#mermaid-svg-fjTbVt8Wyj9gNiqD .cluster span{color:#333;}#mermaid-svg-fjTbVt8Wyj9gNiqD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fjTbVt8Wyj9gNiqD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fjTbVt8Wyj9gNiqD rect.text{fill:none;stroke-width:0;}#mermaid-svg-fjTbVt8Wyj9gNiqD .icon-shape,#mermaid-svg-fjTbVt8Wyj9gNiqD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fjTbVt8Wyj9gNiqD .icon-shape p,#mermaid-svg-fjTbVt8Wyj9gNiqD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fjTbVt8Wyj9gNiqD .icon-shape .label rect,#mermaid-svg-fjTbVt8Wyj9gNiqD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fjTbVt8Wyj9gNiqD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fjTbVt8Wyj9gNiqD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fjTbVt8Wyj9gNiqD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} PS侧PetaLinux控制面
PL侧高速数据面
S2MM命令
NVMe命令
NVMe命令
PRP载荷 / AXI-MM读取
PRP载荷 / AXI-MM读取
PCIe 3.0 ×4
PCIe 3.0 ×4
虚拟LBA → SSD0成员LBA

最高QD32
虚拟LBA → SSD1成员LBA

最高QD32
完成中断
完成中断
AXI-Lite控制 / 批量释放
采集状态 / 中断
ADC / adc_gen

8通道 × 16 bit
标准多通道采集接口
AXI4-Stream FIFO

跨时钟与背压缓冲
AXI DataMover

S2MM
capture_control_axi

描述符与环形区控制
PL DDR4环形缓冲区

8 GiB / 8192 × 1 MiB
IntelliProp NVMe Host Core 0

命令与完成队列
IntelliProp NVMe Host Core 1

命令与完成队列
PCIe Root Complex 0

AXI-MM ↔ PCIe
PCIe Root Complex 1

AXI-MM ↔ PCIe
NVMe SSD 0
NVMe SSD 1
pl_direct_pool.sh

pl_direct_writer
exFAT文件池

.partial + .map → .bin
iprop_nvme_block

/dev/plstripe0

1 MiB服务层条带
pl_capture驱动

producer / consumer / ready
采集载荷不经过PS DDR

不进入Linux页缓存

图中粗实线表示采集载荷和PCIe数据通路,虚线表示命令、状态、中断及文件系统控制。/dev/plstripe0是自定义服务层条带设备,不是Linux mdadm RAID0;PS负责exFAT和LBA映射,SSD通过PRP地址直接读取PL DDR4中的采集载荷。

简化文本框图:

text 复制代码
                         ┌──────────────────────────────┐
ADC / adc_gen            │             PL               │
      │                  │                              │
      ▼                  │  AXI4-Stream FIFO            │
多通道采集接口 ──────────►  DataMover                   │
                         │      │                       │
                         │      ▼                       │
                         │  PL DDR4环形缓冲区(8 GiB)  │
                         │      │                       │
                         │      ├──── NVMe Host 0 ─ PCIe ─ SSD0
                         │      └──── NVMe Host 1 ─ PCIe ─ SSD1
                         └──────────────┬───────────────┘
                                        │ AXI-Lite/IRQ
                                        ▼
                         ┌──────────────────────────────┐
                         │       PS端PetaLinux           │
                         │                              │
                         │  pl_capture                  │
                         │  iprop_nvme_block            │
                         │  /dev/plstripe0              │
                         │  exFAT + 文件池管理           │
                         └──────────────────────────────┘

这套架构中有两个需要特别说明的概念。

1. "PL DDR4直写NVMe"并不是ADC直接接SSD

当前使用的IntelliProp NVMe Host Accelerator Core通过PRP地址访问系统挂接内存,不提供采集载荷的AXI4-Stream接口。因此数据仍然需要先存入一块可寻址、可保持的数据缓冲区。

本项目选择PL DDR4作为这块缓冲区。NVMe命令完成之前,对应PL DDR缓冲区不会被采集逻辑覆盖。

2. PS仍然参与控制,但不搬运采集载荷

PS侧主要完成以下工作:

  • 启动、停止和复位采集;
  • 读取producer、consumer、ready和错误状态;
  • 创建并预分配exFAT文件;
  • 获取文件对应的物理LBA映射;
  • 向NVMe驱动提交PL DDR物理地址和目标LBA;
  • 等待完成中断并释放环形缓冲区;
  • 更新文件长度、目录项和其他exFAT元数据。

真正的采集载荷不会复制到PS DDR,也不会进入Linux页缓存。

四、PL侧采集与DDR4环形缓冲区

1. 标准采集接口与AXI4-Stream FIFO

测试阶段使用adc_gen模拟ADC数据源。ADC数据经过标准化采集接口和AXI4-Stream FIFO后进入DataMover,再写入PL DDR4。

adc_gen和DDR写模块之间加入FIFO有三个作用:

  • 解耦采样时钟与DDR写入瞬时背压;
  • 吸收短时间AXI仲裁抖动;
  • 便于通过tvalid/tready分析数据链路是否丢拍。

2. 1 MiB缓冲块与8 GiB环形区

PL DDR4环形缓冲区的典型配置为:

项目 配置
PL DDR4基地址 0x1000000000
PL DDR4容量 8 GiB
单缓冲块大小 1 MiB
缓冲块数量 8192
描述符大小 32 Byte

PL侧维护producer,Linux驱动维护consumer:

text 复制代码
ready = producer - consumer

ready持续上涨时,说明采集产生数据的速度大于落盘消费速度。如果ready接近8192,8 GiB环形区即将被填满。

3. 批量交换接口

高带宽场景下,如果每完成一个1 MiB缓冲就进行一次系统调用和寄存器交互,控制开销会非常明显。当前方案使用批量交换接口:

  • 一次获取最多8个PL缓冲块;
  • 8 MiB数据拆分为64条128 KiB NVMe命令;
  • 两块SSD分别保持最高QD32;
  • 一次ioctl完成"释放上一批+获取下一批";
  • 固定环形地址可使用隐式描述符,减少描述符RAM读取。

典型启动信息如下:

text 复制代码
direct batch pipeline: up to 8 PL buffers / 64 NVMe commands,
hardware=dual-QD32 control=super-exchange descriptor=implicit release=batch

五、自定义多NVMe条带阵列

1. 为什么没有使用mdadm

本项目没有使用Linux MD子系统,也不依赖mdadm。原因是生产采集路径不是普通的页缓存或块层BIO写入,而是:

text 复制代码
PL DDR物理地址 + 文件物理LBA → NVMe直接写命令

因此驱动内部实现了面向当前硬件的虚拟条带块设备:

text 复制代码
/dev/ipropnvme0 ─┐
                  ├── 自定义条带映射 ── /dev/plstripe0
/dev/ipropnvme1 ─┘

设备树将两个控制器标记为stripe member。两个成员均初始化完成后,驱动只注册一个/dev/plstripe0。在没有stripe member属性的单盘工程中,仍保持兼容并注册/dev/ipropnvme0

2. 条带映射方式

虚拟磁盘使用1 MiB条带单元:

text 复制代码
虚拟0~1 MiB    → SSD0
虚拟1~2 MiB    → SSD1
虚拟2~3 MiB    → SSD0
虚拟3~4 MiB    → SSD1
......

对于普通文件系统BIO,驱动会在条带边界拆分请求并送入对应SSD。对于PL DDR直接写,驱动把文件的虚拟LBA转换成成员盘LBA,再通过两个独立的队列、CID空间、PRP List和完成中断并行提交。

FLUSH命令只有在两块SSD都完成刷新后才向上层返回成功。

3. RAID 0的含义和风险

该方案具有RAID 0的容量与带宽特征,但没有数据冗余:

  • 总容量约为所有成员盘可用容量之和;
  • 理论持续带宽约为各成员盘持续带宽之和;
  • 任意一块SSD损坏,都可能导致整个exFAT文件系统不可用;
  • 挂载/dev/plstripe0后,禁止同时直接访问成员盘。

因此它适合高速原始采集,不适合需要冗余保护的长期归档。

六、在条带阵列上维护一个exFAT文件系统

1. 文件系统只创建一次

双盘组成/dev/plstripe0后,在虚拟盘上创建一个exFAT文件系统:

bash 复制代码
sudo mkfs.exfat /dev/plstripe0
sudo mkdir -p /mnt/nvme
sudo mount -t exfat /dev/plstripe0 /mnt/nvme

上层只看到一个挂载点和普通的.bin文件,不需要关心文件数据具体位于哪块SSD。

2. 为什么必须先预分配文件

直接写NVMe需要提前知道目标LBA,而普通文件在写入过程中可能动态分配或产生碎片。因此生产采集开始前必须完成:

  1. 创建.partial临时文件;
  2. 为文件分配全部exFAT簇;
  3. 获取文件逻辑偏移到虚拟块设备LBA的映射;
  4. 检查空洞、对齐和物理run边界;
  5. 保存.map物理映射缓存;
  6. 采集时锁定文件,禁止其他进程修改映射。

PetaLinux 2022.2自带的exFAT实现可能不支持标准fallocate(),并且传统FIBMAP只有32 bit,无法安全表示大容量SSD高位LBA。本项目为Linux exFAT增加了:

  • 快速簇分配ioctl,支持--fast-prepare跳过顺序零填充;
  • 64 bit FIEMAP支持;
  • FIBMAP兼容回退与边界校验;
  • 物理run映射缓存和快速复核。

3. .partial与原子重命名

采集过程中写入的是:

text 复制代码
capture_0000.bin.partial

只有该文件的全部数据成功写入并完成必要的刷新后,才原子重命名为:

text 复制代码
capture_0000.bin

这样即使采集被中断,也能区分完整文件和未完成文件。

七、预分配文件池与无间断换文件

连续采集不能在每个文件结束后停下来重新分配空间,因此系统使用预分配文件池。

例如准备4个4 GiB文件:

bash 复制代码
sudo pl_direct_pool.sh prepare --fast-prepare \
    -b /dev/plstripe0 \
    -m /mnt/nvme \
    -n 4096 \
    -c 4 \
    -C 8 \
    -p capture

主要参数含义:

参数 含义
-n 4096 每个文件包含4096个1 MiB缓冲,即4 GiB
-c 4 文件池包含4个文件
-C 8 8通道校验模式
-p capture 文件名前缀
--fast-prepare 快速分配exFAT簇,不进行顺序零填充

准备完成后启动采集:

bash 复制代码
sudo pl_direct_pool.sh capture \
    -b /dev/plstripe0 \
    -m /mnt/nvme \
    -n 4096 \
    -c 4 \
    -C 8 \
    -p capture

多文件切换期间PL不会停止。文件刷新和exFAT元数据更新产生的短时延迟由8 GiB PL DDR环形缓冲区吸收。

如果希望反复使用相同的文件空间,可使用:

bash 复制代码
sudo pl_direct_pool.sh capture --overwrite \
    -b /dev/plstripe0 \
    -m /mnt/nvme \
    -n 4096 \
    -c 4 \
    -C 8 \
    -p capture

--overwrite会重新启用已完成文件并保留映射缓存,不需要每次重新执行prepare

八、SSD预处理与性能测试

快速预分配只完成文件系统簇分配,并不一定真正写过所有目标LBA。SSD控制器首次触达这些LBA时,FTL映射、垃圾回收和功耗状态可能造成额外抖动。

可以在正式采集前执行一次预处理:

bash 复制代码
sudo pl_direct_pool.sh precondition \
    -b /dev/plstripe0 \
    -m /mnt/nvme \
    -n 4096 \
    -c 4 \
    -C 8 \
    -p capture

该命令使用PS DDR中的数据,以O_DIRECT + libaio + 1 MiB + QD32覆盖预分配空间,让SSD提前建立FTL映射。它是破坏性操作,但不会启动ADC采集。

停止ADC后,可测试PL DDR到双NVMe路径的最大带宽:

bash 复制代码
sudo pl_direct_pool.sh benchmark \
    -b /dev/plstripe0 \
    -m /mnt/nvme \
    -n 4096 \
    -c 32 \
    -C 8 \
    -p capture

benchmark循环读取PL DDR并写入SSD,用于测量存储链路上限。它不会生成可供ADC图样校验的数据,执行后必须重新完成正式采集才能运行verify

九、如何证明采集没有丢点

仅仅看到文件大小正确并不能证明采集无丢点。为此,测试源使用8通道seq128图样:

text 复制代码
CH0 = sequence[15:0]
CH1 = sequence[31:16]
CH2 = sequence[47:32]
CH3 = sequence[63:48]
CH4 = ~sequence[15:0]
CH5 = ~sequence[31:16]
CH6 = ~sequence[47:32]
CH7 = ~sequence[63:48]

低4通道组成一个64 bit单调递增序号,高4通道是它的按位取反。这样可以同时检测:

  • 帧丢失:序号向前跳变;
  • 数据重复或回退:序号不递增;
  • 通道错位:重组后的序号异常;
  • 位翻转或通道损坏:高低半部不满足互补关系。

执行完整文件验证:

bash 复制代码
sudo pl_direct_pool.sh verify -f \
    -b /dev/plstripe0 \
    -m /mnt/nvme \
    -n 4096 \
    -c 4 \
    -C 8 \
    -p capture

快速抽样验证可省略-f,也可以通过-q设置抽样间隔。最终验收仍建议至少执行一次全文件校验,并进行卸载、重新挂载后的二次验证:

bash 复制代码
sync
sudo umount /mnt/nvme
sudo mount -t exfat /dev/plstripe0 /mnt/nvme
sudo pl_direct_pool.sh verify -f \
    -b /dev/plstripe0 -m /mnt/nvme \
    -n 4096 -c 4 -C 8 -p capture

十、典型性能结果

在双盘、双QD32、停止ADC的PL DDR循环写基准中,实测结果约为:

text 复制代码
payload ≈ 4400~4450 MiB/s

250 MSPS、8通道、16 bit采集源需要约3814.7 MiB/s。因此双盘存储路径具备一定带宽余量,实际连续采集速度约3.7~3.8 GiB/s,主要由采集源数据率决定。

如果提高到270 MSPS,输入带宽变为:

text 复制代码
270,000,000 × 8 × 16 / 8
≈ 4119.9 MiB/s

需要注意:停止采集时测得的4.4 GiB/s,并不等于边采集边落盘时也能达到相同带宽。采集过程中PL DDR4同时承担:

text 复制代码
ADC写DDR:约4.32 GB/s
双NVMe读DDR:约4.32 GB/s

DDR控制器还存在读写方向切换、Bank冲突和AXI仲裁开销。当实际落盘速度低于输入速度时,ready会持续上涨,最终可能出现:

text 复制代码
status=0x0000000d
error=0x00000012

这通常表示环形缓冲区接近耗尽后产生的buffer full/write error级联,不应简单判断为NVMe硬盘损坏。

十一、性能优化中的关键经验

1. 不要只看SSD标称顺序写速度

PC上的SSD标称速度通常来自大队列深度、理想温度、较短测试区间和主机DDR。FPGA系统还会受到PCIe Root Complex、AXI位宽转换、PRP生成、DDR并发访问和中断处理影响。

2. QD1无法发挥双盘性能

早期同步QD1路径只有约1 GiB/s。改成中断完成、每盘QD32、多命令并发后,停止采集的双盘聚合带宽提升到约4.4 GiB/s。

3. 128 KiB命令不一定比256 KiB慢

命令大小需要结合Host Core最大传输长度、PRP List、SSD内部并行度和条带边界综合选择。当前使用128 KiB命令,可以让8个1 MiB缓冲精确形成64条命令,并在两块盘之间维持双QD32。

4. 文件映射不能在采集中动态变化

直接LBA写绕过了文件系统数据路径,因此必须保证:

  • 文件已经完整分配;
  • 映射经过校验;
  • 采集期间不能截断、删除、碎片整理或改写文件;
  • .partial完成后才能重命名为.bin

5. dma-coherent不能随意添加

如果完整硬件、PS初始化和驱动缓存一致性链路没有验证通过,错误添加dma-coherent可能造成NVMe读到旧数据或全零数据。当前稳定配置不在设备树NVMe节点中声明dma-coherent

6. ready斜率比瞬时带宽更重要

持续采集时应重点观察:

bash 复制代码
sudo pl_capture_ctl status

判断标准包括:

  • ready可以短暂波动,但不能跨文件持续单调上涨;
  • overflow=0
  • error=0x00000000
  • producer与consumer长期平均速度一致;
  • 多文件切换后系统能回收所有已完成缓冲。

十二、推荐的完整测试流程

1. 查看设备与驱动

bash 复制代码
lsblk
lsmod | grep -E 'pl_capture|iprop_nvme'
cat /proc/interrupts | grep -Ei 'nvme|pl-capture'
sudo pl_capture_ctl info
sudo pl_capture_ctl status

2. 创建并挂载exFAT

以下操作会清除两块成员盘上的现有数据。

bash 复制代码
sudo mkfs.exfat /dev/plstripe0
sudo mkdir -p /mnt/nvme
sudo mount -t exfat /dev/plstripe0 /mnt/nvme

3. 快速准备文件池

bash 复制代码
sudo pl_direct_pool.sh prepare --fast-prepare \
    -b /dev/plstripe0 -m /mnt/nvme \
    -n 4096 -c 32 -C 8 -p data

4. 首次触达预处理

bash 复制代码
sudo pl_direct_pool.sh precondition \
    -b /dev/plstripe0 -m /mnt/nvme \
    -n 4096 -c 32 -C 8 -p data

5. 存储链路基准测试

bash 复制代码
sudo pl_direct_pool.sh benchmark \
    -b /dev/plstripe0 -m /mnt/nvme \
    -n 4096 -c 32 -C 8 -p data

6. 正式覆盖采集

bash 复制代码
sudo pl_capture_ctl reset

sudo pl_direct_pool.sh capture --overwrite \
    -b /dev/plstripe0 -m /mnt/nvme \
    -n 4096 -c 32 -C 8 -p data

7. 完整数据校验

bash 复制代码
sudo pl_direct_pool.sh verify -f \
    -b /dev/plstripe0 -m /mnt/nvme \
    -n 4096 -c 32 -C 8 -p data

十三、方案的适用范围与局限

这套架构适合:

  • 多通道高速ADC原始数据采集;
  • 雷达、通信、频谱和瞬态信号记录;
  • 对持续写带宽要求高、对冗余要求较低的场景;
  • 需要将SSD直接接入PL侧PCIe Root Complex的系统。

它也存在明确边界:

  • RAID 0没有冗余;
  • PL DDR仍同时承担采集写入和NVMe读取;
  • SSD长时间垃圾回收或热降速可能填满环形缓冲区;
  • exFAT直接LBA写依赖严格的预分配和映射锁定;
  • 当前IntelliProp Host Core没有用户数据AXI4-Stream接口,无法完全取消可寻址缓冲区。

如果未来需要进一步提高采样率,可以考虑:

  • 增加更多NVMe成员盘;
  • 使用更多独立DDR通道;
  • 使用URAM/BRAM作为短时流缓冲;
  • 引入带AXI4-Stream Command Translator的NVMe桥接架构;
  • 在正常路径中直接流式写盘,仅在SSD抖动时使用DDR应急缓存。

十四、总结

本项目实现了一条适合高速连续采集的存储通路:

text 复制代码
ADC → PL DDR4环形缓冲 → 多NVMe条带阵列 → exFAT文件

其核心不是简单地把两块SSD组成RAID 0,而是同时解决了以下问题:

  • PL DDR采集缓冲区的生产者/消费者管理;
  • PL物理地址直接作为NVMe PRP数据源;
  • 双NVMe独立队列与中断并行;
  • 自定义虚拟条带块设备;
  • exFAT文件预分配及64 bit物理映射;
  • 文件池无间断切换与重复覆盖;
  • seq128图样的端到端无丢点验证。

最终,Linux保留了文件系统管理的灵活性,采集载荷又绕过了PS DDR和页缓存,兼顾了高速数据通路与标准exFAT文件可访问性。对于Zynq UltraScale+ MPSoC上的数GiB/s级数据采集,这是一个具有较强工程实用性的实现方案。


附:软件组件

组件 功能
pl_capture.ko 采集控制、描述符校验、中断确认和缓冲释放
iprop_nvme_block.ko IntelliProp NVMe块设备、PRP List、双QD32和虚拟条带盘
pl_capture_ctl 配置、启动、停止、复位和状态查询
pl_direct_writer PL DDR到NVMe的直接写入及多文件切换
pl_direct_pool.sh prepare、precondition、benchmark、capture、run、verify流程
pl_capture_verify seq128、seq64及通道斜坡图样验证
相关推荐
ALINX技术博客1 小时前
【黑金云课堂】FPGA技术教程Vitis开发:AXI总线
fpga开发·vitis
星夜离尘5615 小时前
UART 串口通信:从帧格式到收发回显(附完整 Verilog 实操)
fpga开发
仲南音19 小时前
Xilinx FPGA—— DDR4读写
fpga开发
思尔芯S2C1 天前
思尔芯RCF RTL自动分割工具,助力大规模AI/HPC芯片原型验证
人工智能·fpga开发·内存模型·自动编译·ddr5·prototyping·原型验证
Terasic友晶科技2 天前
答疑解惑|Agilex 3 FPGA QSPI时钟设置注意事项
fpga开发·de23-lite·terasic·qspi flash
国科安芯2 天前
卫星星务计算机数据管理中SEU容错机制的技术实现研究——以AS32S601存储器ECC架构为例
网络·单片机·算法·fpga开发·架构·云计算·risc-v
国科安芯2 天前
卫星电推进系统阀门驱动控制中的宽温域高可靠MCU应用研究——基于AS32S601电气特性与辐照试验数据的工程分析
单片机·嵌入式硬件·fpga开发·云计算·信息与通信·卫星·抗辐射芯片
郝旭帅电子设计团队3 天前
图像处理是什么
图像处理·fpga开发·郝旭帅电子设计团队·郝旭帅
ARM|X86+FPGA工业主板厂家3 天前
基于ARM+FPGA平台在精密测控领域的应用:8路同步IEPE振动采集系统
arm开发·fpga开发