zedboard dma_fft 研究记录

本文参考xilinx教程: https://support.xilinx.com/s/article/58582?language=en_US,在 zedboard 上实现一个 FFT 的加速 IP ,并且通过 DMA 与 PS 端的 DDR 进行交互,做成一个协处理器;同时,将之前的回环检测也做一下实验记录。

系统:ubuntu 22.04

vivado 版本:2022.2

回环检测

在做 FFT 加速之前,先做一个回环检测看看 DMA 读写功能是否按预期的那样子运行。在回环检测中,我们从 PL 的 AXI DMA IP 核从 DDR3 中读取数据,并将数据写回到 DDR3 中,如下图所示

img

硬件部分

新建工程

img

img

创建 block design

img

添加 axi dma IP

img

配置如下

img

添加 ZYNQ ,选择 zedboard 自动配置

img

img

添加 AXI HP

img

添加中断信号

img

添加 axis_data_fifo IP

img

添加 Concat IP

img

执行 Run Connection Automation ,进行自动连线,同时,对中断信号进行手动连接

img

手动连接 axi_data_fifo IP

img

连接 axi_data_fifo 的时钟和复位

img

执行 Run Block Automation ,引出外部引脚

img

至此, block design 就设计完成了,按 F6 键对设计进行验证,出现 validation successful 就说明设计是无误的

img

从设计图可以看出数据的流动方向,上面的是从 DDR 读取数据到 PL ,中间的是从 PL 端写数据到 DDR 中,最下面的是 PS 通过 GP0 接口与 DMA 进行通信,以设置、启动和监控数据的传输

img

img

img

Address Editor 这里没出现 Assign All,就说明已经配置好了

img

选择 File > Export > Export Block Design ,可以将 bd 保存成 tcl 文件

接下来就是将硬件综合、布线、生成 bitstream ,然后导出到 vitis 就行了

在 source 窗口中,选中 Design Source 下的 .bd , 右键,依次执行 Generate Output Products 和 Create HDL Wrapper

在左侧找到 PROGRAM AND DEBUG,选中 Generate Bitstream

生成 bitstream 以后,在菜单栏选择 File > Export > Export Hardware 导出硬件,勾选 Include bitstream ,然后在菜单栏选择 Tools > Launch Vitis IDE

软件部分

Vitis IDE 在右上角点击搜索的按钮,搜索 serial ,可以打开串口工具,选择 /dev/ttyUSB0 ,若无法选中,则尝试以下命令解决

sudo chmod 666 /dev/ttyUSB0

选择 Create Application Project > Create a new platform from hardware (XSA) ,应用工程命名为 axi_fft ,选择模板为 Empty Application ©

右键 src ,选择 new file ,新建一个叫做 main.c 的文件

main.c 的代码如下:

/***************************** Include Files *********************************/

#include "xaxidma.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xscugic.h"

/************************** Constant Definitions *****************************/

#define DMA_DEV_ID          XPAR_AXIDMA_0_DEVICE_ID
#define RX_INTR_ID          XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
#define TX_INTR_ID          XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
#define INTC_DEVICE_ID      XPAR_SCUGIC_SINGLE_DEVICE_ID
#define DDR_BASE_ADDR       XPAR_PS7_DDR_0_S_AXI_BASEADDR   //0x00100000
#define MEM_BASE_ADDR       (DDR_BASE_ADDR + 0x1000000)     //0x01100000
#define TX_BUFFER_BASE      (MEM_BASE_ADDR + 0x00100000)    //0x01200000
#define RX_BUFFER_BASE      (MEM_BASE_ADDR + 0x00300000)    //0x01400000
#define RESET_TIMEOUT_COUNTER   10000    //复位时间
#define TEST_START_VALUE        0x0      //测试起始值
#define MAX_PKT_LEN             0x100    //发送包长度

/************************** Function Prototypes ******************************/

static int check_data(int length, u8 start_value);
static void tx_intr_handler(void *callback);
static void rx_intr_handler(void *callback);
static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
        u16 tx_intr_id, u16 rx_intr_id);
static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
        u16 rx_intr_id);

/************************** Variable Definitions *****************************/

static XAxiDma axidma;     //XAxiDma实例
static XScuGic intc;       //中断控制器的实例
volatile int tx_done;      //发送完成标志
volatile int rx_done;      //接收完成标志
volatile int error;        //传输出错标志

/************************** Function Definitions *****************************/

int main(void)
{
    int i;
    int status;
    u8 value;
    u8 *tx_buffer_ptr;
    u8 *rx_buffer_ptr;
    XAxiDma_Config *config;

    tx_buffer_ptr = (u8 *) TX_BUFFER_BASE;
    rx_buffer_ptr = (u8 *) RX_BUFFER_BASE;

    xil_printf("\r\n--- Entering main() --- \r\n");

    config = XAxiDma_LookupConfig(DMA_DEV_ID);
    if (!config) {
        xil_printf("No config found for %d\r\n", DMA_DEV_ID);
        return XST_FAILURE;
    }

    //初始化DMA引擎
    status = XAxiDma_CfgInitialize(&axidma, config);
    if (status != XST_SUCCESS) {
        xil_printf("Initialization failed %d\r\n", status);
        return XST_FAILURE;
    }

    if (XAxiDma_HasSg(&axidma)) {
        xil_printf("Device configured as SG mode \r\n");
        return XST_FAILURE;
    }

    //建立中断系统
    status = setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
    if (status != XST_SUCCESS) {
        xil_printf("Failed intr setup\r\n");
        return XST_FAILURE;
    }

    //初始化标志信号
    tx_done = 0;
    rx_done = 0;
    error   = 0;

    value = TEST_START_VALUE;
    for (i = 0; i < MAX_PKT_LEN; i++) {
        tx_buffer_ptr[i] = value;
        value = (value + 1) & 0xFF;
    }

    Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN);   //刷新Data Cache

    status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) tx_buffer_ptr,
    MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) rx_buffer_ptr,
    MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN);   //刷新Data Cache
    while (!tx_done && !rx_done && !error)
        ;
    //传输出错
    if (error) {
        xil_printf("Failed test transmit%s done, "
                "receive%s done\r\n", tx_done ? "" : " not",
                rx_done ? "" : " not");
        goto Done;
    }

    //传输完成,检查数据是否正确
    status = check_data(MAX_PKT_LEN, TEST_START_VALUE);
    if (status != XST_SUCCESS) {
        xil_printf("Data check failed\r\n");
        goto Done;
    }

    xil_printf("Successfully ran AXI DMA Loop\r\n");
    disable_intr_system(&intc, TX_INTR_ID, RX_INTR_ID);

    Done: xil_printf("--- Exiting main() --- \r\n");
    return XST_SUCCESS;
}

//检查数据缓冲区
static int check_data(int length, u8 start_value)
{
    u8 value;
    u8 *rx_packet;
    int i = 0;

    value = start_value;
    rx_packet = (u8 *) RX_BUFFER_BASE;
    for (i = 0; i < length; i++) {
        if (rx_packet[i] != value) {
            xil_printf("Data error %d: %x/%x\r\n", i, rx_packet[i], value);
            return XST_FAILURE;
        }
        value = (value + 1) & 0xFF;
    }

    return XST_SUCCESS;
}

//DMA TX中断处理函数
static void tx_intr_handler(void *callback)
{
    int timeout;
    u32 irq_status;
    XAxiDma *axidma_inst = (XAxiDma *) callback;

    //读取待处理的中断
    irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DMA_TO_DEVICE);
    //确认待处理的中断
    XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);

    //Tx出错
    if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
        error = 1;
        XAxiDma_Reset(axidma_inst);
        timeout = RESET_TIMEOUT_COUNTER;
        while (timeout) {
            if (XAxiDma_ResetIsDone(axidma_inst))
                break;
            timeout -= 1;
        }
        return;
    }

    //Tx完成
    if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
        tx_done = 1;
}

//DMA RX中断处理函数
static void rx_intr_handler(void *callback)
{
    u32 irq_status;
    int timeout;
    XAxiDma *axidma_inst = (XAxiDma *) callback;

    irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);
    XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DEVICE_TO_DMA);

    //Rx出错
    if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
        error = 1;
        XAxiDma_Reset(axidma_inst);
        timeout = RESET_TIMEOUT_COUNTER;
        while (timeout) {
            if (XAxiDma_ResetIsDone(axidma_inst))
                break;
            timeout -= 1;
        }
        return;
    }

    //Rx完成
    if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
        rx_done = 1;
}

//建立DMA中断系统
//  @param   int_ins_ptr是指向XScuGic实例的指针
//  @param   AxiDmaPtr是指向DMA引擎实例的指针
//  @param   tx_intr_id是TX通道中断ID
//  @param   rx_intr_id是RX通道中断ID
//  @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
        u16 tx_intr_id, u16 rx_intr_id)
{
    int status;
    XScuGic_Config *intc_config;

    //初始化中断控制器驱动
    intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);
    if (NULL == intc_config) {
        return XST_FAILURE;
    }
    status = XScuGic_CfgInitialize(int_ins_ptr, intc_config,
            intc_config->CpuBaseAddress);
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    //设置优先级和触发类型
    XScuGic_SetPriorityTriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);
    XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);

    //为中断设置中断处理函数
    status = XScuGic_Connect(int_ins_ptr, tx_intr_id,
            (Xil_InterruptHandler) tx_intr_handler, axidma_ptr);
    if (status != XST_SUCCESS) {
        return status;
    }

    status = XScuGic_Connect(int_ins_ptr, rx_intr_id,
            (Xil_InterruptHandler) rx_intr_handler, axidma_ptr);
    if (status != XST_SUCCESS) {
        return status;
    }

    XScuGic_Enable(int_ins_ptr, tx_intr_id);
    XScuGic_Enable(int_ins_ptr, rx_intr_id);

    //启用来自硬件的中断
    Xil_ExceptionInit();
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
            (Xil_ExceptionHandler) XScuGic_InterruptHandler,
            (void *) int_ins_ptr);
    Xil_ExceptionEnable();

    //使能DMA中断
    XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
    XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);

    return XST_SUCCESS;
}

//此函数禁用DMA引擎的中断
static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
        u16 rx_intr_id)
{
    XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
    XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
}

右键工程,选择 build ,对工程进行编译。打开开发板,选择 Debug as > Launch Hardware ,打开调试界面

在调试界面,打开 Memory Monitors ,添加地址 0x1200000 ,也就是 DMA 从 DDR 中读取数据的起始地址 TX_BUFFER_BASE

img

在 new Renderings 里面设置为 Hex Integer ,以 16 进制的格式查看

img

右键选择 Format > Column Size 选择为 1 ,方便查看

img

img

这时候,地址的数据就显示如下

img

以同样的方式添加地址 0x1400000 ,也就是 DMA 将数据写回到 DDR 当中的起始地址 RX_BUFFER_BASE

img

设置运行断点,在以下位置设置断点

img

先将程序运行到断点 1 处,此时可以看出 0x1200000 地址处的值变为 00 ,随后的地址数据依次递增 1 ,由于 CPU 和 DDR 之间是通过 Cache 交互的,数据暂存在 Cache 中,没有刷新到 DDR 中,显示的数据是 Data Cache 中的。

img

接着运行到断点 2 处,Data Cache 中的数据已经刷新到 DDR 中了,此时我们将 Memory Monitor 窗口中的监视内容切换到 0x1400000 ,看看地址 0x1400000 处的数据是什么时候更新的。运行到断点 3 处,执行完第 94 行 DMA 的发送函数,完成从内存中读取数据传输给 PL ,即 DMA 从地址 0x1200000 处读取数据传输给外设,此时地址 0x1400000 处的数据还未更新。运行到断点 4 处,执行完第 100 行的 DMA 接收函数,完成从 PL 读取数据写入内存,即将刚才写入到 PL 的数据通过一个 axis_data_fifo 以后读取出来,并从 DDR 的地址 0x1400000 处开始写入。此时 DDR 当中的数据已经更新,而 Memory Monitor 当中显示的是 Data Cache 中的数据,因此需要刷新 Data Cache 以后才能在 Memory Monitor 中查看到数据。运行到断点 5 处,刷新完 Data Cache 以后,我们就发现 0x1400000 处的值已经发生了变化。

img

至此,DMA 回环检测任务算是完成,继续执行到程序结束,可以在 Serial 中查看到应用程序打印的信息

img

FFT

FFT 的工程图如下所示,大概的思路就是将上面回环检测的 stream fifo 替换成 FFT 的 IP 核就行了。官方的教程里面用 tcl 写了创建工程的脚本,但是他所用的 Vivado 版本为 2015 ,为了和我们所开发的环境兼容,我打算学习一下官方教程里面的 bd 设计方法,用Vivado 2022 里面重新搭建一个。

img

硬件部分

创建控制层

回到 Vivado 工程,新建一个 block design ,命名为 design_2

img

添加新单元,命名为 ctrl

img

双击进入

添加 axi dma IP 核

img

IP 核的配置如下所示

img

添加 axi gpio IP 核

img

IP 核配置如下

img

添加 axi_interconnect_0 IP 核

img

IP 核配置如下

img

添加 axi_interconnect_1 IP 核,配置如下

img

添加 edge_detect IP 核,这个 IP 核是在 xilinx 教程里面自己封装好的,从 github 上下载来的文件夹里面能看到,功能很简单,就是检测一个连续两个信号是否有变化,如果有变化,就输出 1 ,否则输出 0

img

img

打开 Vivado > Settings > IP > Repository > Add ,选中 IP 核所在的文件夹

img

点击 Apply

回到 Block Design ,添加自定义的 IP 核

img

配置如下

img

添加 processor_system_reset IP 核

img

配置如下

img

添加 Concat IP 核,无需配置

img

下面开始连线

这里连的通路是从 PS DDR 读数据的通路, M_AXI_DMA_DATA 连接 ZYNQ 的 HP 接口,经过 axi_interconnect 连到 axi_dma 的 M_AXI_MM2S 端口,转换成 AXI_STREAM 的格式,从 M_AXIS_ACCEL 端口输出

img

这里连的通路是从 PL 处理过的数据写入 PS DDR 写数据的通路,PL 处理过的数据从 S_AXIS_ACCEL 进入,经过 DMA 将 Stream 转换成 Memory Map 的数据连到 S01_AXI ,通过 M00_AXI 传到 ZYNQ 的 HP 接口

img

这里连的是从 ZYNQ GP0 接口连接 axi_gpio 和 axi_dma 的 S_AXI_LITE 的接口的通路,其中 GPIO 用于 PS 和 FFT IP 核之间进行寄存器相关的配置

img

这里连的是 axi_dma 的中断处理通路,当 DMA 完成 Tx 或者 Rx 时候,会向 ZYNQ 发送中断信号,随后 PS 端进行中断的处理

img

这里连的是 GPIO 和 FFT 配置寄存器相关的通路, PS 通过 GPIO 向 FFT 发送配置数据,当两次发送的数据不同的时候, fft_config_tvalid 输出为 1 , fft_config_tdata 将数据发送出去

img

这里连的是各个模块的复位信号

img

最后这里连的是各个模块的时钟信号

img

至此,控制层就创建好了

img

这里的 M_AXI_DMA_DATA 连接 ZYNQ 的 HP 接口,用于在 PS 的 DDR 和 DMA 之间进行数据传输, M_AXIS_ACCEL 是用于将 DMA 从 PS DDR 中读取的数据转换成 Stream 格式传给加速器端的接口, S_AXI 连接 PS 端的 GP 接口,用于 PS 对 DMA 和加速器进行寄存器的配置, S_AXIS_ACCEL 是用于将加速器端处理好的 Stream 格式的数据传给 DMA ,再发送给 PS 端 DDR 的接口

创建加速器层

添加新单元,命名为 accelerator

img

双击进入

添加 FFT IP 核

img

IP 核的配置如下所示

img

img

img

如下图所示

img

创建总工程

添加 ZYNQ IP 核,添加中断和 HP 接口

添加 ZYNQ IP核

img

添加 Preset 为 Zedboard

img

添加中断

img

添加 HP 接口

img

最终的 IP 核如下图所示

img

把对应的端口连起来就可以了

img

选择 File > Export > Export Block Design ,可以将 bd 保存成 tcl 文件

接下来就是将硬件综合、布线、生成 bitstream ,然后导出到 vitis 就行了

在 source 窗口中,选中 Design Source 下的 .bd , 右键,依次执行 Generate Output Products 和 Create HDL Wrapper

在左侧找到 PROGRAM AND DEBUG,选中 Generate Bitstream

生成 bitstream 以后,在菜单栏选择 File > Export > Export Hardware 导出硬件,勾选 Include bitstream ,然后在菜单栏选择 Tools > Launch Vitis IDE

软件部分

选择 Create Application Project > Create a new platform from hardware (XSA) ,应用工程命名为 dma_fft ,选择模板为 Empty Application ©

右键 src ,选择 Import Source ,找到官方的 dma_fft/sw 文件夹,将其导入到项目内

img

这里需要对 ddr 的内存分配进行修改,避免分配内存的时候报错。打开 link script ,把 Heap Size 调成 0x20000

右键工程,选择 build ,对工程进行编译

在 vitis 界面右上角搜索 Serial ,打开串口 ttyUSB0

打开开发板,选择 Run as > Launch Hardware ,可以看到程序已经运行起来了

 Hello World!
What would you like to do?
0: Print current FFT parameters
1: Change FFT parameters
2: Perform FFT using current parameters
3: Print current stimulus to be used for the FFT operation
4: Print results of previous FFT operation
5: Quit
---------------------------------------------
fwd_inv   = forward
num_pts   = 1024
scale_sch = 0x2AB
---------------------------------------------
What would you like to do?
0: Print current FFT parameters
1: Change FFT parameters
2: Perform FFT using current parameters
3: Print current stimulus to be used for the FFT operation
4: Print results of previous FFT operation
5: Quit
Okay, which parameter would you like to change?
0: Point length
1: Direction
2: Exit
What would you like to set the FFT point length to? Type:
0: FFT point length = 16
1: FFT point length = 32
2: FFT point length = 64
3: FFT point length = 128
4: FFT point length = 256
5: FFT point length = 512
6: FFT point length = 1024
7: FFT point length = 2048
8: FFT point length = 4096
9: FFT point length = 8192
Okay, setting the core to perform a 16-point FFT.
What would you like to do?
0: Print current FFT parameters
1: Change FFT parameters
2: Perform FFT using current parameters
3: Print current stimulus to be used for the FFT operation
4: Print results of previous FFT operation
5: Quit
FFT complete!
What would you like to do?
0: Print current FFT parameters
1: Change FFT parameters
2: Perform FFT using current parameters
3: Print current stimulus to be used for the FFT operation
4: Print results of previous FFT operation
5: Quit
Xk(0) = -723 + j*-1
Xk(1) = 1364 + j*-1281
Xk(2) = 175 + j*-148
Xk(3) = -151 + j*248
Xk(4) = 1570 + j*-1414
Xk(5) = 524 + j*-318
Xk(6) = 412 + j*-157
Xk(7) = 376 + j*-69
Xk(8) = 366 + j*0
Xk(9) = 375 + j*68
Xk(10) = 412 + j*156
Xk(11) = 524 + j*317
Xk(12) = 1570 + j*1414
Xk(13) = -150 + j*-249
Xk(14) = 176 + j*147
Xk(15) = 1365 + j*1280
What would you like to do?
0: Print current FFT parameters
1: Change FFT parameters
2: Perform FFT using current parameters
3: Print current stimulus to be used for the FFT operation
4: Print results of previous FFT operation
5: Quit

程序代码的原理还在研究中 …

都看完了,不点个赞嘛

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值