多通道数据采集 DMA 设计详解
8 通道 × 16-bit ADC 数据,如何通过纯 FPGA 逻辑实现低延时、高带宽的 DMA 搬运?本文从架构到状态机,逐层拆解。
这个 DMA 有什么特别?
在介绍 DMA 之前,先说一下它的前级------触发采集模块 。它实现了多板级联同步、预触发捕获(Pre-trigger)、5 种触发模式、微秒级确定性延时等特性,这些功能让采集到的数据"值得被搬运"。而本文的主角 DMA 引擎,负责把这些数据高效、可靠地搬进 DDR。
这个 DMA 引擎的核心设计亮点:
🚫 零 CPU 参与
全链路纯 FPGA 硬件逻辑,上位机只需配置 5 个 AXI Lite 寄存器,剩下的全部由状态机自动完成。没有中断、没有轮询、没有驱动开销。
🧩 跨时钟域无缝桥接
ADC 采样域(100MHz)、AXI 总线域(100MHz)、DDR 接口域(133MHz)三者异步,DMA 引擎通过异步 FIFO + 状态机握手,在三个时钟域之间安全搬运数据,无毛刺、无丢失。
🎯 一个 collect_end 信号统一三种采集模式
触发采集、实时采集、瞬时采集------三种模式的数据保存,巧妙通过同一个 collect_end 采样结束标志实现。DMA 状态机不关心"为什么结束",只关心"结束了怎么收尾",实现了采集策略与搬运逻辑的彻底解耦。配合双缓冲和自动回卷,同一条 DMA 通道即可覆盖从单次快照到连续流式采集的全部场景。
💡 双缓冲(Ping-pong Buffer)+ 自动回卷
DMA 写指针到达缓冲区末尾后自动归零,形成环形缓冲。同时自动维护半满标志位,上位机可在 DMA 写后半缓冲区的同时读取前半缓冲区。瞬时模式写满即停,实时模式循环写入不中断------同一套硬件逻辑,零修改切换模式。
📦 8 通道合并搬运
8 个触发通道的数据合并为 128-bit 总线,一次 AXI Burst 搬运所有通道的当前采样值,保证通道间时间同步性。
⚡ 固定 Burst 长度优化
每次 DMA 传输固定 256 word(4096 字节),对齐 AXI4 协议最大 Burst 限制,最大化总线利用率。
📊 400 倍带宽余量
AXI4 总线带宽 1.6 GB/s,实际数据率仅 4 MB/s,DMA 永远不会成为瓶颈。
如果你对"如何用纯硬件逻辑替代 CPU 做实时数据搬运"感兴趣,或者正在为多通道高速采集的 DMA 设计发愁,这篇文章值得一读。
一、为什么需要专门的 DMA 设计?
在数据采集系统中,ADC 采样时钟和 DDR 读写时钟往往不同,数据速率也存在突发特性。如果用 CPU 去搬运数据,不仅占用宝贵的处理带宽,还会引入不确定的延时。
DMA(Direct Memory Access)的核心价值: 让硬件逻辑自主完成"从 ADC 缓冲区 → DDR 存储"的数据搬运,CPU 只需配置参数、查询状态。
在我们的纯 FPGA 架构中,DMA 引擎需要解决三个核心问题:
- 跨时钟域:ADC 采样时钟(100MHz)、AXI 总线时钟(100MHz)、DDR 接口时钟(133MHz)三者异步
- 多通道并行:8 个通道独立触发、独立缓存,但共享一条 DMA 通道
- 突发效率:DDR 的读写效率依赖 Burst 长度,单次传输太短会浪费带宽
二、整体数据流架构
┌─────────────────────────────────────────┐
│ mstar_collect (采集顶层) │
│ │
CM2268 ADC (8ch) │ ┌──────────┐ ┌──────────┐ │
16-bit × 250kSPS │ │ triggel │ │ triggel │ × 8 通道 │
│ │ │ (触发判断) │ │ (触发判断) │ │
▼ │ └────┬─────┘ └────┬─────┘ │
ad_driver │ │tADdat[127:0] │ │
(采样时序控制) │ │tADen │ │
│ │ └──────┬───────┘ │
│ sADdat[127:0] │ │ │
│ sADen │ ▼ │
└───────────────▶│ 数据缓存 (FIFO) │
│ │ │
└──────────────┼───────────────────────────┘
│
┌──────────────┼───────────────────────────┐
│ mstar_collectdma (DMA 引擎) │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ onedmafifo_con │ │
│ │ (跨时钟域 FIFO + │ │
│ │ DMA 状态机) │ │
│ └─────────┬──────────┘ │
│ │ AXI4 Full │
│ │ 128-bit, Burst=256 │
│ ▼ │
│ ┌──────────────────┐ │
│ │ AXI Full Matrix │ (总线仲裁) │
│ └────────┬─────────┘ │
└─────────────┼────────────────────────────┘
│
▼
┌──────────────┐
│ DDR3 SDRAM │
│ (数据存储) │
└──────┬───────┘
│
┌──────▼───────┐
│ 以太网桥接 IP │
│ (DDR→UDP打包) │
└──────┬───────┘
│
▼
RGMII PHY → 网线 → PC
关键设计决策:
- 采集模块(
mstar_collect)和 DMA 模块(mstar_collectdma)分离,各自独立时钟域 - 8 通道触发后的数据合并为 128-bit 总线(8 × 16-bit),送入 DMA
- DMA 只有一条通道,通过内部 FIFO 缓冲多通道突发
三、触发模块:triggel
每个通道独立拥有一个 triggel 模块,负责:
3.1 触发模式
| 模式 | 编码 | 行为 |
|---|---|---|
| 不触发 | 3'b000 |
采满缓存即结束,等价于"连续采集" |
| 电平触发 | 3'b001 |
数据达到阈值时触发 |
| 上升沿触发 | 3'b010 |
数据从低于阈值跳变到高于阈值 |
| 下降沿触发 | 3'b011 |
数据从高于阈值跳变到低于阈值 |
| 连续采集 | 3'b100 |
不等待触发,持续采样 |
3.2 预触发缓存
触发模块的核心难点是预触发(pre-trigger)缓存------需要保留触发点之前的数据。
采集窗口:
|← pre_cachsize →|← cachsize - pre_cachsize →|
触发前数据 触发后数据
↑
触发点 (trig_relative_position)
实现方式:
- 通道使能后,数据持续写入环形缓冲区(
r_position_counter循环计数) - 当计数器达到
pre_cachsize时,r_firststorage_flg置位,开始触发判断 - 触发条件满足后,记录
trig_relative_position - 继续采集直到
cachsize - pre_cachsize个数据,输出colection_end
3.3 多板同步触发
除了内部触发,还支持外部触发输入(in_trigflg):
verilog
// 外部触发上升沿检测
get_singer_edge#(.SING_WIDTH(1)) get_triger_edge_u0(
.clk(ad_clk),
.i_sing(_in_trigflg),
.o_sing_up(_in_trigflg_up),
...
);
// 外部触发锁存:一旦触发,保持到采集结束
always @(posedge ad_clk) begin
if (_in_trigflg_up && collect_en > 0)
in_trigflg_r <= 1; // 锁存
if (collect_end_up)
in_trigflg_r <= 0; // 采集结束清零
end
触发后还可以通过 trig_out 输出信号级联触发下一块板卡,实现多板同步采集。
四、DMA 引擎:onedmafifo_con
这是整个数据采集 DMA 的核心模块。
4.1 跨时钟域 FIFO
ADC 数据在 ad_clk(100MHz)域产生,DMA 传输在 m_axi_clk(100MHz)域进行。两者虽然频率相同,但相位不确定,必须经过跨时钟域处理。
ad_clk 域 m_axi_clk 域
│ │
│ s_axis_tvalid/tdata[127:0] │
▼ │
┌──────────────┐ 异步 FIFO ┌──────▼───────┐
│ axis_dmadat │ ──────────────▶ │ DMA 状态机 │
│ _fifo │ (FIFORDWN=10) │ (地址生成) │
└──────────────┘ └──────────────┘
FIFO 深度 1024 word(FIFORDWN=10,2^10),足以吸收 ADC 采样和 DDR 突发之间的速率差异。
FIFO 满判断:
verilog
// FIFO 可读数据量 >= 1/4 总深度时,允许发起 DMA
assign w_buf_dmaen_ch = (fifo_rdwn[FIFORDWN-1:FIFORDWN-2] > 0) && w_dma_channel_en;
当 FIFO 中积累了至少 256 word(高位 bit9:8 > 0)时,触发 DMA 传输。
4.2 DMA 状态机
w_DMA_channel_enup
┌────────┐ ──────────────────────▶ ┌────────────┐
│ IDLE │ │ DMAWAIT │
│ (空闲) │ ◀────────────────────── │ (等待FIFO) │
└────────┘ r_dma_oksta && └─────┬──────┘
w_DMA_channel_enup │
│ w_buf_dmaen_ch
│ (FIFO数据足够)
▼
control_done ┌────────────┐
┌────────┐ ◀────────────────────── │ DMAST │
│ IDLE │ │ (启动传输) │
└────────┘ r_dma_numremain==0 └─────┬──────┘
▲ │ ~control_done
│ ▼
┌─────┴──────┐ ┌────────────┐
│ DMADO │ ◀───── │ DMAST │
│ (传输完成) │ │ (等待启动) │
└────────────┘ └────────────┘
四个状态详解:
| 状态 | 编码 | 功能 |
|---|---|---|
DMASTA_IDLE |
3'b000 |
等待通道使能上升沿,清零所有指针 |
DMASTA_DMAWAIT |
3'b001 |
等待 FIFO 数据足够,或采集结束信号 |
DMASTA_DMAST |
3'b010 |
向 AXI Write Master 发起传输请求 (control_go=1) |
DMASTA_DMADO |
3'b100 |
等待 AXI 传输完成 (control_done) |
4.3 地址生成与 Burst 控制
每次 DMA 传输固定 256 个 128-bit word(即 DMALEN=256),对应 AXI4 Burst 长度:
verilog
localparam DMALEN = 256;
// DMAWAIT 状态:准备传输参数
if (w_buf_dmaen_ch) begin
r_buf_curDatNum_ch <= r_buf_curDatNum_ch + DMALEN; // 更新已传输计数
r_dma_numremain <= 1; // 标记本次传输未完成
r_control_write_length <= DMALEN; // 传输长度 = 256 word
r_dmastate <= DMASTA_DMAST;
control_go <= 1; // 启动 AXI 传输
end
地址计算:
verilog
// 目标地址 = 缓冲区基址 + 已传输数据量
control_write_base <= buf_base_ch[ADDRESSWIDTH-1:4] + r_buf_curDatNum_ch;
注意 buf_base_ch 是字节地址,但 AXI 数据宽度为 128-bit(16 字节),所以地址偏移以 16 字节为单位([ADDRESSWIDTH-1:4])。
4.4 传输完成与循环
verilog
// DMADO 状态:AXI 传输完成
if (control_done) begin
if (r_dma_numremain) begin
// 本次 256 word 传输完成,回到 DMAWAIT 检查是否继续
r_dmastate <= DMASTA_DMAWAIT;
if (buf_length_ch[ADDRESSWIDTH-1:4] == r_buf_curDatNum_ch)
r_buf_curDatNum_ch <= 0; // 到达缓冲区末尾,地址回卷
end else begin
// 所有数据传输完成
r_dmastate <= DMASTA_IDLE;
r_dma_oksta <= 1; // 置位 DMA 完成标志
r_buf_curDatNum_ch <= 0;
end
end
缓冲区回卷机制: 当 r_buf_curDatNum_ch 达到 buf_length_ch 时,自动归零,实现环形缓冲。上位机可以通过 AXI Lite 接口读取当前写入位置。
4.5 半满标志(DMA_half_flg)
verilog
DMA_half_flg <= r_buf_curDatNum_ch >= buf_length_ch[ADDRESSWIDTH-1:5]
&& buf_length_ch[ADDRESSWIDTH-1:5] > 0;
当 DMA 已写入数据达到缓冲区一半时,DMA_half_flg 置位。上位机可以据此判断:
- 缓冲区前半部分已有数据,可以开始读取
- 后半部分正在写入,不要覆盖
这是典型的双缓冲(ping-pong buffer)策略。
4.6 collect_end:一个信号统一三种采集模式
本 DMA 支持触发采集、实时采集、瞬时采集 三种模式,它们的区别在于"什么时候开始采"和"什么时候停止采",但对 DMA 引擎来说,只需要知道一件事------采集结束了 。这个信息通过一个 collect_end 信号传递,这是整个设计中非常巧妙的一个抽象。
三种采集模式的 collect_end 产生方式
┌─────────────┬───────────────────────┬────────────────────────────┐
│ 采集模式 │ 何时产生 collect_end │ 典型应用场景 │
├─────────────┼───────────────────────┼────────────────────────────┤
│ 瞬时采集 │ 缓冲区写满自动结束 │ 单次快照、设备调试 │
│ (无触发) │ position == max_pos │ │
├─────────────┼───────────────────────┼────────────────────────────┤
│ 触发采集 │ 触发后采够 post 数据 │ 示波器、故障捕获、 │
│ (边沿/电平) │ 自动结束 │ 局部放电检测 │
├─────────────┼───────────────────────┼────────────────────────────┤
│ 实时采集 │ 上位机主动写 │ 连续监测、长时间记录、 │
│ (手动停止) │ collect_endreq=1 │ 振动分析 │
└─────────────┴───────────────────────┴────────────────────────────┘
三种模式产生 collect_end 的时机完全不同,但信号波形一样------都是一个单周期脉冲。DMA 状态机不需要知道"为什么结束",只需要知道"结束了",然后执行统一的收尾流程。
这就是策略与机制的解耦: 触发模块决定"何时停",DMA 模块负责"怎么收尾"。两者通过一个信号握手,互不侵入。
DMA 收到 collect_end 后的收尾流程
verilog
// DMAWAIT 状态:检测到 collect_end
else if (r_collect_end && (~r_dma_oksta)) begin
if (fifo_rdwn > 0) begin
// FIFO 还有残留数据 → 发起最后一次 Burst,把数据搬完
r_buf_curDatNum_ch <= r_buf_curDatNum_ch + fifo_rdwn;
r_control_write_length <= fifo_rdwn;
r_dma_numremain <= 0; // 标记为最后一次传输
r_dmastate <= DMASTA_DMAST;
control_go <= 1;
end else begin
// FIFO 已空 → 直接结束
r_dma_oksta <= 1; // 置位完成标志,通知上位机
r_dmastate <= DMASTA_IDLE;
end
end
关键设计点:
- 不丢数据 :即使收到
collect_end,DMA 也不会立即停止,而是先把 FIFO 中的残留数据搬完 r_dma_numremain = 0:标记这次传输是最后一次,传输完成后直接进入 IDLE,不再循环r_dma_oksta = 1:通知上位机"数据已经全部在 DDR 里了,可以来读了"
collect_end + 双缓冲 + 自动回卷 = 三种模式无缝切换
DMA 的缓冲区管理有两个关键机制:
缓冲区自动回卷:
verilog
// 每次 Burst 完成后检查:到达缓冲区末尾了吗?
if (buf_length_ch[ADDRESSWIDTH-1:4] == r_buf_curDatNum_ch)
r_buf_curDatNum_ch <= 0; // 地址归零,从头开始写
半满标志(双缓冲指示):
verilog
DMA_half_flg <= r_buf_curDatNum_ch >= buf_length_ch[ADDRESSWIDTH-1:5]
&& buf_length_ch[ADDRESSWIDTH-1:5] > 0;
这两个机制配合 collect_end,让同一条 DMA 通道自然支持不同场景:
场景 1:瞬时采集(单次快照)
─────────────────────────────────────────────
DDR 缓冲区: [0 .................. buf_length]
DMA 写入: |████████████████████| collect_end → DMA 停止
上位机读取: |← 一次性全部读出 →|
场景 2:触发采集(故障捕获)
─────────────────────────────────────────────
DDR 缓冲区: [0 .................. buf_length]
DMA 写入: |████████████████████| collect_end → DMA 停止
上位机读取: |← 触发前后数据全部读出 →|
场景 3:实时采集(连续流式)
─────────────────────────────────────────────
DDR 缓冲区: [0 ........... half ........... buf_length]
DMA 写入 1: |████████████| half_flg=1
上位机读取: |← 读前半 →|
DMA 写入 2: |████████████| 回卷到 0
上位机读取: |← 读后半 →|
DMA 写入 3: |████████████| half_flg=1
... 循环不中断,直到上位机发 collect_endreq
场景 3 的精髓: 实时采集时,collect_end 一直不来,DMA 写指针到达缓冲区末尾后自动归零,继续从头写入。上位机通过 DMA_half_flg 判断哪一半缓冲区可以安全读取,实现"DMA 写一半、上位机读另一半"的 ping-pong 操作。当上位机决定停止采集时,写入 collect_endreq=1,触发模块产生 collect_end,DMA 执行收尾流程,干净利落地结束。
三种模式,同一套硬件逻辑,零修改切换。 这就是纯 FPGA 架构的优势------不需要软件重新配置,不需要切换代码路径,只需要改变 collect_end 的产生时机。
五、AXI Lite 寄存器接口
DMA 引擎通过 ad_dma_axictrl 模块暴露 5 个 32-bit 寄存器,供上位机配置:
| 偏移地址 | 名称 | 读/写 | 说明 |
|---|---|---|---|
0x00 |
ADDR_ADMA_ID |
只读 | 模块 ID(MODEL_ID),用于识别设备 |
0x04 |
ADDR_DMACON |
读写 | 控制寄存器:[0] 通道使能,[1] 复位请求;状态:[16] 半满标志,[17] DMA 完成 |
0x08 |
ADDR_BUFBASE |
读写 | 缓冲区基地址(DDR 物理地址) |
0x0C |
ADDR_BUFLEN |
读写 | 缓冲区长度(单位:128-bit word) |
0x10 |
ADDR_PREBUF |
读写 | 预触发缓冲区长度 |
寄存器操作时序:
写操作(配置 DMA):
AWADDR=0x04, WDATA=0x01 → 使能通道 0
AWADDR=0x08, WDATA=0x1000_0000 → 设置缓冲区基址
AWADDR=0x0C, WDATA=0x0010_0000 → 设置缓冲区长度 (1M word)
读操作(查询状态):
ARADDR=0x04 → RDATA[17:16] = {half_flg, dma_ok}
RDATA[1:0] = {rst_req, channel_en}
控制逻辑的安全设计:
verilog
// 使能和复位的互锁逻辑
if (dma_ok_up) begin
DMA_channel_en <= 0; // DMA 完成后自动清零使能
DMA_rst_req <= 0;
end else if (w_hs && waddr == ADDR_DMACON) begin
if (DMA_channel_en && WDATA[1])
DMA_rst_req <= WDATA[1]; // 已使能时写 bit[1] = 请求复位
else if (WDATA[0])
DMA_channel_en <= WDATA[0]; // 写 bit[0] = 使能通道
end
必须先使能通道,才能请求复位;DMA 完成后自动清除使能,防止重复触发。
六、AXI4 Full 写通道
DMA 状态机通过 axi_dma_example_axi_write_master 模块驱动 AXI4 Full 写通道:
6.1 接口信号
control_go → 启动一次传输
control_done ← 传输完成
control_addr_offset → 目标地址 (字节对齐)
control_xfer_size_in_bytes → 传输字节数
m_axi_awvalid/awready/awaddr/awlen → 写地址通道
m_axiwvalid/wready/wdata/wstrb/wlast → 写数据通道
m_axi_bvalid/bready → 写响应通道
6.2 Burst 参数计算
verilog
localparam integer LP_AXI_DW_BYTES = ADDRESSWIDTH / 8; // 地址宽度/8 = 字节数
localparam integer LP_AXI_BURST_LEN = 4096 / LP_AXI_DW_BYTES < 256
? 4096 / LP_AXI_DW_BYTES : 256;
AXI4 协议规定单次 Burst 最大 4096 字节。当地址宽度为 28-bit 时:
LP_AXI_DW_BYTES = 28 / 8 = 3(实际按 4 字节对齐)LP_AXI_BURST_LEN = min(4096/4, 256) = 256
6.3 outstanding 传输
verilog
localparam integer LP_AXI_BRAM_DEPTH = 512;
localparam integer LP_AXI_RD_MAX_OUTSTANDING = LP_AXI_BRAM_DEPTH / LP_AXI_BURST_LEN;
允许最多 512 / 256 = 2 个 outstanding 传输,提高总线利用率。
七、多通道合并与分时复用
8 个通道的触发数据通过 mstar_collect 顶层合并:
verilog
// 8 通道触发后的数据合并为 128-bit
wire [127:0] tADdat; // 8 × 16-bit = 128-bit
wire tADen; // 任一通道有数据时有效
assign tADen = |t_ADen; // 8 通道 OR
分时复用策略:
- 8 通道共享一条 128-bit 数据总线
- 每个 ADC 采样周期,8 通道数据同时出现在总线上
- DMA 以 128-bit 宽度接收,一次传输包含所有 8 通道的当前采样值
这种方式避免了 8 条独立 DMA 通道的资源竞争,同时保证了通道间的时间同步性。
八、性能分析
8.1 带宽计算
| 参数 | 值 |
|---|---|
| ADC 采样率 | 250 kSPS/ch |
| 通道数 | 8 |
| 每采样数据量 | 16 bit × 8 = 128 bit |
| 原始数据率 | 250k × 128 = 32 Mbps = 4 MB/s |
| AXI4 总线带宽 | 100MHz × 128-bit = 1.6 GB/s |
| DDR 写带宽 | 133MHz × 128-bit × 2 (DDR) = 4.2 GB/s |
| 带宽利用率 | 4 MB/s / 1.6 GB/s ≈ 0.25% |
结论: DMA 带宽远大于实际需求,瓶颈在 ADC 采样率而非总线。
8.2 延时分析
| 环节 | 延时 |
|---|---|
| ADC 采样 → 触发判断 | 1 个采样周期 (4μs @250kSPS) |
| 触发 → FIFO 写入 | 1 个时钟周期 (10ns) |
| FIFO 积累 256 word | 256 × 10ns = 2.56μs |
| AXI Burst 传输 | 256 × 10ns = 2.56μs |
| 总 DMA 延时 | ~5μs |
从触发到数据写入 DDR,延时在微秒级别,远优于 CPU+OS 方案(通常毫秒级)。
8.3 资源占用
| 资源 | 用量 |
|---|---|
| LUT | ~60% (PH1P35) |
| FF | ~40% |
| BRAM | 2 个 (跨时钟域 FIFO) |
| 时钟域 | 3 个 (50M/100M/133M) |
九、设计经验与踩坑记录
9.1 跨时钟域信号必须打拍
verilog
// 错误:直接用 ad_clk 域的信号控制 m_axi_clk 域的状态机
if (ad_collect_endflg) // ❌ 跨时钟域直接采样
// 正确:两级寄存器同步
always @(posedge m_axi_clk) begin
r_ad_collect_endflg <= {r_ad_collect_endflg[0], ad_collect_endflg};
end
assign w_ad_collect_endup = (r_ad_collect_endflg[1] ^ r_ad_collect_endflg[0])
& r_ad_collect_endflg[0]; // ✅ 上升沿检测
9.2 AXI 地址对齐
AXI4 协议要求地址按数据宽度对齐。128-bit 数据宽度下,地址低 4 位必须为 0:
verilog
// buf_base_ch 是字节地址,传给 AXI Master 时需要右移 4 位
control_write_base <= buf_base_ch[ADDRESSWIDTH-1:4] + r_buf_curDatNum_ch;
// 实际地址 = {control_write_base, 4'b0000}
9.3 DMA 完成标志的清除时机
verilog
// 错误:在 IDLE 状态清除完成标志
DMASTA_IDLE: begin
r_dma_oksta <= 0; // ❌ 一进入 IDLE 就清零,上位机可能还没读到
// 正确:在通道使能上升沿清除
if (w_DMA_channel_enup) begin
r_dma_oksta <= 0; // ✅ 上位机主动启动新一次采集时才清零
end
9.4 FIFO 满信号的处理
verilog
// FIFO 满时不能简单丢弃数据,需要背压上游
assign tx_tready = !fifo_full && data_active;
// 但 ADC 采样不能暂停,所以 FIFO 深度要足够大
// 经验公式:FIFO_DEPTH >= (ADC采样率 / AXI时钟频率) × Burst长度 × 2
// = (250k / 100M) × 256 × 2 ≈ 1280 word
// 实际选择 1024 word (FIFORDWN=10),靠 DMA 状态机保证不溢出
十、总结
本文详解了一个纯 FPGA 实现的多通道数据采集 DMA 引擎,核心设计要点:
- 触发与 DMA 分离 :
triggel模块专注触发判断,onedmafifo_con专注数据搬运,职责清晰 - collect_end 统一三种采集模式:触发采集、实时采集、瞬时采集,通过同一个信号完成策略与机制的解耦
- 跨时钟域 FIFO 缓冲:吸收 ADC 采样和 DDR 突发之间的速率差异
- 双缓冲 + 自动回卷:半满标志实现 ping-pong 读写,地址到末尾自动归零,支持连续流式采集
- 固定 Burst 长度:256 word/次,简化地址生成逻辑,提高总线效率
- AXI Lite 寄存器接口:上位机只需配置 5 个寄存器即可控制整个 DMA
- 多通道合并:8 通道共享 128-bit 总线,保证时间同步性
整个 DMA 引擎在 100MHz 时钟下,可实现 1.6 GB/s 的写带宽,远超 8 通道 250kSPS ADC 的 4 MB/s 数据率,留有 400 倍余量。
这就是纯 FPGA 架构做 DMA 的优势:确定性、低延时、高带宽、零 CPU 开销。
项目地址:
- FPGA 端:https://gitee.com/mstar_ypc/mstar-collect-fpga
- 上位机:https://gitee.com/mstar_ypc/mstar-oscilloscope
作者是一名 FPGA 工程师,专注于纯硬件架构的数据采集系统设计。如果你对"如何用 FPGA 替代 CPU 做实时数据处理"感兴趣,欢迎交流。