DMA搬运数据实验

前言

阅读学习杰哥过去留下的 limfx 文档及相关资料后,我发现完整工程并不是一次性搭建完成的,而是由多个小实验逐步验证、组合而成。因此,本阶段我也计划先从几个关键实验入手,在实践中逐步理解 PS 端 DMA 搬运、DDR 缓存以及后续留盘功能的实现方式。

1. 实验目标

PL 端产生测试数据,通过 AXI DMA 搬运到 PS 端 DDR 中,再由 Vitis 裸机程序读取 DDR 数据,并通过 UART 串口打印出来,以验证 PL 到 PS DDR 的数据通路是否正确。

2. 实验设计与流程

总体流程如下所示:

测试数据产生模块(testdata_gen.v)    // 模拟产生 5 张子卡、共 160 通道的同步数据
    ↓
数据格式转换模块(ad_to_dma.v)       // 将 5 路 512 bit 数据整理为 AXI-Stream 数据,并通过FIFO完成缓存与跨时钟域,将数据送入 DMA
    ↓
AXI DMA                              // 通过 S2MM 通道将数据写入 PS DDR
    ↓
Vitis 裸机程序读取 DDR                // 从指定 DDR 地址读取 DMA 写入的数据
    ↓
UART 串口打印验证                     // 打印数据内容,判断搬运结果是否正确

测试数据产生模块

testdata_gen.v 用于在 PL 端产生测试数据,模拟真实 AD 采集数据。当前设计中模拟 5 张子卡,每张子卡以2M/s的采样率输出 512 bit 数据,对应 32 个 16 bit 通道。该模块的输入输出为:

input  wire         clk,            //系统时钟
input  wire         rst_n,          //复位信号
input  wire         enable,         //模块使能信号
output reg  [511:0] testdata0_o,    //子卡0测试数据
output reg  [511:0] testdata1_o,    //子卡1测试数据
output reg  [511:0] testdata2_o,    //子卡2测试数据
output reg  [511:0] testdata3_o,    //子卡3测试数据
output reg  [511:0] testdata4_o,    //子卡4测试数据
output reg          data_valid      //数据有效信号

测试数据格式为:

testdata0_o: sample_cnt + 通道号
testdata1_o: sample_cnt + 通道号 + 0x1000
testdata2_o: sample_cnt + 通道号 + 0x2000
...

数据格式转换模块

ad_to_dma.v 用于接收 5 张子卡的数据,并将其拆分、整理为 128 bit 宽度的 AXI-Stream 数据流,供后续 AXI DMA 接收。

由于每个采样点的数据大小为512*5 = 2560 bit,也就是需要拆分成 20 个 128 bit 的数据,考虑到为便于后续数据解析,每个采样点的边界应当清晰,所以在每个采样点前加一帧作为帧头,因此当前帧格式为:

1 个帧头 + 20 拍数据

数据转换模块的输入输出为:

input  wire         ad_clk,              //PL侧时钟信号        
input  wire         ad_rst_n,            //PL侧复位信号                                             
input  wire         dma_clk,             //PS侧时钟信号    
input  wire         dma_rst_n,           //PS侧复位信号                                     
input  wire         enable_i,            //模块使能信号 
                                     
input  wire [511:0] ad_data0_i,          //采集数据及有效信号输入
input  wire [511:0] ad_data1_i,          
input  wire [511:0] ad_data2_i,          
input  wire [511:0] ad_data3_i,          
input  wire [511:0] ad_data4_i,          
input  wire         data_valid_i,  
                                                               
output wire [127:0] axis_tdata_o,        //AXI-Stream 数据总线,输出给 AXI DMA S_AXIS_S2MM
output wire [15:0]  axis_tkeep_o,        //字节有效标志,每 1 bit 对应 tdata 中 1 byte,16'hFFFF 表示 128 bit 全部有效
output wire         axis_tvalid_o,       //数据有效信号,表示当前 tdata/tkeep/tlast 有效
input  wire         axis_tready_i,       //下游就绪信号,由 AXI DMA/FIFO 返回,表示可以接收当前数据
output wire         axis_tlast_o         //帧结束信号,拉高表示当前拍是一帧数据的最后一拍

DMA配置及Block Design设计

Block Design 中需要加入 Zynq PS、AXI DMA 以及自定义 RTL 模块。 加入后首先配置Zynq UItraScale+ MPSoC:

alt text

配置好Zynq UItraScale+ MPSoC后需要进行DMA配置。DMA配置为:

SG:关闭
Control/Status Stream:关闭
MM2S:关闭
S2MM:开启
S2MM Stream Width:128 bit
S2MM Memory Map Width:128 bit

然后进行Block Design连线。Block Design设计如下所示:

alt text

Vitis开发

首先将之前Vivado中的设计导出为.xsa文件。然后在vitis中:

  • 新建workspace;
  • 新建platform并导入.xsa文件、编译;
  • 新建application,添加main.c文件并进行编译。

main.c部分代码如下:

//关键参数
#define DMA_BASEADDR        XPAR_XAXIDMA_0_BASEADDR        //AXI DMA 控制寄存器基地址
#define GPIO_BASEADDR       XPAR_XGPIO_0_BASEADDR          //AXI GPIO 控制寄存器基地址

#define GPIO_CHANNEL        1U
#define GPIO_ENABLE_MASK    0x1U

#define RX_BUFFER_BASE      0x10100000U                    //DMA 写入 DDR 的目标地址
#define FRAME_BEATS         21U
#define BEAT_BYTES          16U
#define RX_BYTES            (FRAME_BEATS * BEAT_BYTES)     //一次 DMA 接收字节数
//main函数
int main(void)
{
    int status;

    xil_printf("\r\n");
    xil_printf("========================================\r\n");
    xil_printf("DDAQ simple PL -> DMA -> DDR test\r\n");
    xil_printf("DMA  baseaddr : 0x%08x\r\n", DMA_BASEADDR);
    xil_printf("GPIO baseaddr : 0x%08x\r\n", GPIO_BASEADDR);
    xil_printf("DDR  buffer   : 0x%08x\r\n", RX_BUFFER_BASE);
    xil_printf("RX bytes      : %d\r\n", RX_BYTES);
    xil_printf("========================================\r\n");

    status = init_gpio();
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    status = init_dma();
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    status = init_dma_interrupt();
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    status = run_dma_once_by_interrupt();
    if (status != XST_SUCCESS) {
        XGpio_DiscreteWrite(&Gpio, GPIO_CHANNEL, 0x0U);
        xil_printf("DMA test failed\r\n");
        print_rx_buffer();
        return XST_FAILURE;
    }

    print_rx_buffer();
    xil_printf("\r\nTest finished\r\n");

    while (1) {
    }

    return XST_SUCCESS;
}

3. 仿真与测试

PL端测试

通过顶层模块将测试数据产生模块数据格式转换模块实例化,外部接入使能信号,拉高后模块进行工作。然后搭建仿真平台,通过仿真波形观察数据及其时序是否正确。当前仿真结果显示,每帧输出稳定为 21 拍,帧头固定为55AA55AA55AA55AA55AA55AA55AA55AA,符合当前帧格式设计。

alt text

DDR写入测试

运行Vitis裸机程序后,串口打印出来的数据如下所示,可以看出 PL 数据可经 DMA 正确写入 DDR,传输长度、数据顺序和中断完成均正常。

alt text


本文章使用limfx的vscode插件快速发布