基于ZCU104的PS和PL数据交互例程(七):驱动编写与测试

基于ZCU104的PS和PL数据交互例程(七):驱动编写与测试

基于ZCU104的PS和PL数据交互例程(一):简介
基于ZCU104的PS和PL数据交互例程(二):vivado中封装现有工程成IP
基于ZCU104的PS和PL数据交互例程(三):vivado中创建IP
基于ZCU104的PS和PL数据交互例程(四):vivado中制作Block Design
基于ZCU104的PS和PL数据交互例程(五):Petalinux流程
基于ZCU104的PS和PL数据交互例程(六):上板流程
基于ZCU104的PS和PL数据交互例程(七):驱动编写与测试

更正

注意:做到这一步的时候出现了一些问题,所以修正了之前六篇的内容目前都已更正

主要包括:

1.在第(四)篇,设计block design的时候把cdma的中断引脚连接,之前忘记连接了,导致linux调用时一直接受不到cdma的中断信号
2.在第(三)篇,controller的代码编写时,忘记给Start_DUT信号赋值,导致上板后,一直启动不了

调整

需要对dut.v做出一个调整

在设计dut.v的时候,读取和写入bram的地址每次要加4,因为数据是32bit,当做成系统的时候,bram的地址会按照字节在linux系统中进行映射,所以我们一次读写一个32bit数据,地址对应要加4。这点跟纯PL开发不一样。另外我们在verilog是地址还是从0开始读写,但是在linux系统中要从bram的首地址对应的物理地址开始读写

原理

通过之前的步骤,我们生成了基于pynq的rootfs+我们自己定制的硬件平台的Linux系统,现在我们上板之后,在linux下如何操作PS和PS交互呢?

核心思想是,我们要找到PS需要操作什么?

回顾下block design中,PL和PS交互的数据类型有两类,信号类数据类,分别通过AXI-LITE的IP和CDMA完成数据交互。进一步分析发现,

  1. 对于AXI-LITE的IP,最终是PS是通过操作内部的寄存器(slave0~4)来跟PL交互的
  2. 对于CDMA,本质上也是PS操作CDMA的配置寄存器来控制CDMA

所以很显然,PS通过操作各个寄存器就可以控制AXI-LITE的IP和CDMA。其实知道这个就可以了,这个不是唯一的方法,但是是有效的方法。

那么PL的计算核心DUT怎么操作呢?其实这个问题,仔细回顾下block design的设计架构,就会发现已经解决了。

下面我们回顾,整体数据流程,跟第(四)篇中一致

  1. PS端开始任务后,首先通过CDMA把初始数据送到BRAM_INIT(PS读写寄存器完成)
  2. 初始数据传输完成后,PS端通过AXI4-LITE给控制器发送初始数据加载完成信号(PS写寄存器完成)
  3. 拿到PS给的初始数据完成信号后,控制器发送Start_DUT信号给DUT (PS不需要操作,PL逻辑控制)
  4. DUT通过BRAM_INIT_Port读取BRAM_INIT中的数据(这里按照自己DUT的逻辑读取即可 (PS不需要操作,PL逻辑控制)
  5. DUT读取完数据后,开始计算 (PS不需要操作,PL逻辑控制)
  6. DUT计算完成后,把结果数据通过BRAM_RES_Port写到BRAM-RES(PS不需要操作,PL逻辑控制)
  7. DUT发送DUT_Finish信号给控制器 (PS不需要操作,PL逻辑控制)
  8. 控制器通过AXI4-LITE发送PL处理结束信号 (PS读寄存器完成)
  9. PS端拿到PL处理结束信号后,通过CDMA把结果数据从BRAM_RES中读走 (PS写寄存器完成)
  10. 完成一次交互逻辑,后续重复操作

其实PS需要做的事情已经完成了,这里注意芯片设计跟软件编程的区别,当你把硬件电路设计好之后,FPGA已经通过加载比特流把逻辑单元变成了你的电路功能,不管你是否调用,电路都在那里。不像软件那边,必须调用才能有效。

驱动编写

linux驱动是挺复杂的事情,这里我们只是针对这个例程进行简单的驱动编写。

linux从系统层次调用寄存器,需要用mmap函数,本质上可以理解成在block design的时候,系统为寄存器分配了地址,就是Address页面里面分配的,这些是物理地址。而通过mmap之后就可以把这些物理地址映射成虚拟地址,然后就可以在linux系统层面,通过指针操作了。映射完之后,从linux系统层面看,跟操作普通的指针没有区别。
在这里插入图片描述

这里我们对controller和cdma准备好寄存器映射和相关操作的头文件,后面直接调用就行

controller

#define SLV_REG0_START_SET   0x00000001         // operate OR
#define SLV_REG0_START_RESET 0x00000000         // operate AND

typedef struct axi_lite_reg_t{
	volatile uint32_t	slv_reg0;  //4
	volatile uint32_t	slv_reg1;  //4
    volatile uint32_t	slv_reg2;  //4
	volatile uint32_t	slv_reg3;  //4
}axi_lite_reg_t;

axi_lite_reg_t	*axi_lite_reg;

cdma


/** @name Bitmasks of XAXICDMA_CR_OFFSET register
 * @{
 */
#define XAXICDMA_CR_RESET_MASK  0x00000004 /**< Reset DMA engine */
#define XAXICDMA_CR_SGMODE_MASK 0x00000008 /**< Scatter gather mode */

/** @name Bitmask for interrupts
 * These masks are shared by XAXICDMA_CR_OFFSET register and
 * XAXICDMA_SR_OFFSET register
 * @{
 */
#define XAXICDMA_XR_IRQ_IOC_MASK    0x00001000 /**< Completion interrupt */
#define XAXICDMA_XR_IRQ_DELAY_MASK  0x00002000 /**< Delay interrupt */
#define XAXICDMA_XR_IRQ_ERROR_MASK  0x00004000 /**< Error interrupt */
#define XAXICDMA_XR_IRQ_ALL_MASK    0x00007000 /**< All interrupts */
#define XAXICDMA_XR_IRQ_SIMPLE_ALL_MASK 0x00005000 /**< All interrupts for simple only mode */
/*@}*/

/** @name Bitmasks of XAXICDMA_SR_OFFSET register
 * This register reports status of a DMA channel, including
 * idle state, errors, and interrupts
 * @{
 */
#define XAXICDMA_SR_IDLE_MASK         0x00000002  /**< DMA channel idle */
#define XAXICDMA_SR_SGINCLD_MASK      0x00000008  /**< Hybrid build */
#define XAXICDMA_SR_ERR_INTERNAL_MASK 0x00000010  /**< Datamover internal err */
#define XAXICDMA_SR_ERR_SLAVE_MASK    0x00000020  /**< Datamover slave err */
#define XAXICDMA_SR_ERR_DECODE_MASK   0x00000040  /**< Datamover decode err */
#define XAXICDMA_SR_ERR_SG_INT_MASK   0x00000100  /**< SG internal err */
#define XAXICDMA_SR_ERR_SG_SLV_MASK   0x00000200  /**< SG slave err */
#define XAXICDMA_SR_ERR_SG_DEC_MASK   0x00000400  /**< SG decode err */
#define XAXICDMA_SR_ERR_ALL_MASK      0x00000770  /**< All errors */
/*@}*/

typedef struct dma_reg_t{
	volatile uint32_t	CR;  //4
	volatile uint32_t	SR;  //4

	volatile uint64_t	CURD;//8
	volatile uint64_t	TAIL;//8

	volatile uint64_t	SA;  //8
	volatile uint64_t	DA;  //8
	volatile uint32_t	LEN; //4
}dma_reg_t;

dma_reg_t	*dma_reg;

void CDMA_Transfer(uint32_t* Source,uint32_t* Destination,uint32_t Len)
{
    dma_reg->SA = Source;
    dma_reg->DA = Destination;
    dma_reg->LEN = Len;
    printf("[INFO] CDMA0 SA DA LEN : %x %x %d.\r\n",(dma_reg->SA),dma_reg->DA,dma_reg->LEN);
}

void CDMA_Initialize()
{
    dma_reg->CR = XAXICDMA_CR_RESET_MASK;
    while(dma_reg->CR & XAXICDMA_CR_RESET_MASK);
    printf("[INFO] RESET CDMA0 and CR reg : %x.\r\n",dma_reg->CR);
    printf("[INFO] CDMA0 SR reg : %x.\r\n",(dma_reg->SR));

    //Enable Interrupt for simple
    dma_reg->CR = XAXICDMA_XR_IRQ_SIMPLE_ALL_MASK;
    printf("[INFO] Enable Interrupt and CR reg : %x.\r\n",dma_reg->CR);
    printf("[INFO] CDMA0 SR reg : %x.\r\n",(dma_reg->SR));
}

int CDMA_Finish()
{
    uint32_t TimeOut=5;
    while(TimeOut>=0)
    {
        // printf("[INFO] CDMA0 SR reg : %x.\r\n",(dma_reg->SR));
        if(dma_reg->SR & XAXICDMA_SR_IDLE_MASK){break;}
        else{TimeOut--;}
    }
    if(TimeOut<0)
        printf("[ERROR] CDMA0 SR CURD TimeOut : %x %x %d.\r\n",(dma_reg->SR),(dma_reg->CURD),TimeOut);
    //clear interrupt flag
    dma_reg->SR = dma_reg->SR & XAXICDMA_SR_IDLE_MASK;
    if(TimeOut<0) return -1;
    else          return  1;
}

上板测试

烧录好镜像到SD卡,zcu104选择SD卡启动模式,上电,通过串口连接电脑

配置网络

简单配置

  1. 查看网卡,用ifconfig查看,我的网卡是eth0

image-20231104105110378

  1. 配置ip为192.168.137.149,注意要在Window电脑里面把相连的网口,配置为192.168.137.1,子网掩码是255.255.255.0

    sudo ifconfig eth0 192.168.137.149
    

新建测试工程

文件结构如下

└── pl_bram_test
    ├── CMakeLists.txt
    ├── include
    │   ├── axi_lite.h
    │   └── cdma.h
    └── src
        └── pl_bram_test.c

pl_bram_test.c

这个是主程序代码

首先声明物理地址,这个跟block design中设计的Address中的地址对应

#define XPAR_AXI_CDMA_0_BASEADDR 0xA0000000	 
#define CDMA_PSDDR_BASEADDR		 0x10000000
#define BRAM_INIT_BASEADDR 	     0xC0000000
#define BRAM_RES_BASEADDR 	     0xC2000000
#define PL_USER_BASEADDR 	     0xA0010000
1.打开dev/mem
/******** Step1: Initialize /dev/mem ********/
	printf("[INFO] DUT Test Start! \n");
	int memfd = open("/dev/mem", O_RDWR | O_SYNC);
	if (memfd < 0) {
		printf("[ERROR] Can not open /dev/mem \n");
		return (-1);
	}
	printf("[INFO] /dev/mem is open \n");

CDMA的驱动出现问题,应该是当初block_design的时候没有连接中断线导致的

2.mmap
 /******** Step2: Memory map ********/
        //CDMA
    dma_reg = (dma_reg_t*)mmap(0, DMA_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, memfd, XPAR_AXI_CDMA_0_BASEADDR);
	if(dma_reg == (dma_reg_t*)-1) return -2;
	printf("[INFO] CDMA0 MAPPED ADDRESS : %x.\r\n",dma_reg);
        //PS_DDR need single data so choose uint32_t
    volatile uint32_t* psddr_base =  (uint32_t*)mmap(NULL, PSDDR_SIZE, PROT_READ | PROT_WRITE,
    		MAP_SHARED, memfd, CDMA_PSDDR_BASEADDR);
    printf("[INFO] PSDDR MAPPED ADDRESS : %x.\r\n",psddr_base);
        //PL_USER
    // volatile uint32_t* pl_base =  (uint32_t*)mmap(NULL, PL_USER_SIZE, PROT_READ | PROT_WRITE,
    // 		MAP_SHARED, memfd, PL_USER_BASEADDR);
    axi_lite_reg = (axi_lite_reg_t*) mmap(NULL, PL_USER_SIZE, PROT_READ | PROT_WRITE,
     		MAP_SHARED, memfd, PL_USER_BASEADDR);

将CDMA,PS_DDR和controller的物理地址映射到虚拟地址

3.初始化CDMA
 /******** Step3: Initialize CDMA ********/
        CDMA_Initialize();
4.启动CMDA,传递给bram_init
/******** Step4: Transfer data to BRAM INIT ********/
        //initialize psddr
        uint32_t data = 0;
        for(int i=0;i<CDMA_TEST_DATA_NUM;i++)
        {
            *(volatile uint32_t *)(psddr_base + i) = 0x10203040+(i);
        }
        printf("Data already write to ddr \n");
        for(int i = 0;i < CDMA_TEST_DATA_NUM;i++){
            data = *(volatile uint32_t* )(psddr_base+i);
            //Virtual address
            printf("Read addr:%x Read data : %lx \n",(psddr_base+i),data); 
        }
        //transfer PSDDR ---> BRAM
        CDMA_Transfer(CDMA_PSDDR_BASEADDR,BRAM_INIT_BASEADDR,CDMA_TEST_DATA_LEN);
        CDMA_Finish();
        printf("[INFO] CDMA Transfer OVER:[PSDDR] --> [BRAM]\n");
        //transfer BRAM  ---> PSDDR
        for(int i=0;i<CDMA_TEST_DATA_NUM;i++)
        {
            *(volatile uint32_t *)(psddr_base + i) = 0;
        }
        printf("Data is already cleaned to ddr \n");
        for(int i = 0;i < CDMA_TEST_DATA_NUM;i++){
            data = *(volatile uint32_t* )(psddr_base+i);
            printf("Read addr:%x Read data : %lx \n",(psddr_base+i),data); 
        }
        CDMA_Transfer(BRAM_INIT_BASEADDR,CDMA_PSDDR_BASEADDR,CDMA_TEST_DATA_LEN);
        CDMA_Finish();
        printf("[INFO] CDMA Transfer OVER:[BRAM] --> [PSDDR]\n");
        for(int i = 0;i < CDMA_TEST_DATA_NUM;i++){
            data = *(volatile uint32_t* )(psddr_base+i);
            printf("Read addr:%x Read data : %lx \n",(psddr_base+i),data); 
        }

先初始化psddr,写入测试数据,然后通过cdma将数据从psddr移动到BRAM_INIT中

然后清空psddr,将数据从BRAM_INIT移动到psddr,检查下数据是否真的写入

5.启动controller
/******** Step5: Start controller ********/
        //reset finish flag
        axi_lite_reg->slv_reg1 = 0;
        //pluse slv_reg0
        axi_lite_reg->slv_reg0 = 0xFFFFFFFF;
6.等到计算完成
/******** Step6: Wait controller Finish********/
        //wait finish flag
        for(int w=0;w<=1000;w++)
        {
            if(axi_lite_reg->slv_reg1 != 0)
                break;
            if(w==1000){printf("PL error \n");}
        }
        

如果打印PL error,则表示出现问题,PL没有计算完成

7.启动CDMA,从bram_res取回数据
/******** Step7: Transfer data from BRAM Res ********/
         //transfer BRAM  ---> PSDDR
        for(int i=0;i<CDMA_TEST_DATA_NUM;i++)
        {
            *(volatile uint32_t *)(psddr_base + i) = 0;
        }
        printf("Data is already cleaned to ddr \n");
        for(int i = 0;i < CDMA_TEST_DATA_NUM;i++){
            data = *(volatile uint32_t* )(psddr_base+i);
            printf("Read addr:%x Read data : %lx \n",(psddr_base+i),data); 
        }
        CDMA_Transfer(BRAM_RES_BASEADDR,CDMA_PSDDR_BASEADDR,CDMA_TEST_DATA_LEN);
        CDMA_Finish();
        printf("[INFO] CDMA Transfer OVER:[BRAM] --> [PSDDR]\n");
        for(int i = 0;i < CDMA_TEST_DATA_NUM;i++){
            data = *(volatile uint32_t* )(psddr_base+i);
            printf("Read addr:%x Read data : %lx \n",(psddr_base+i),data); 
        }

用CDMA从BRAM_RES中把数据移动到psddr,查看是否符合逻辑

8.关闭映射
/******** Step: End ********/
    close(memfd);
    munmap(dma_reg,DMA_SIZE);
    munmap(psddr_base,PL_USER_SIZE);
    //munmap(pl_base,PL_USER_SIZE);
    munmap(axi_lite_reg,PL_USER_SIZE);
    return 0;

注意关闭,防止内存泄漏等问题。

测试

如果没有build文件夹,就先创建一个

cd build
cmake ..
make
sudo ./pl_bram_test

注意:一定要用sudo来运行可执行文件,否则不能打开dev/mem

输出结果

image-20231105112820265

可以看到最后从BRAM_RES中读取的结果,正好就是BRAM_INIT数据对应加上dut中的cnt值(0,4,8,12,16,…)

总结

至此我们从最初设计到verilog实现IP,到block design创建整体架构,到petalinux生成镜像,再到linux运行已经成功走完。

最后说一下,后面还会补一个ILA调试,因为下板之后,不方便debug,还是要走下ILA会方便点。

另外就是如果是其他板卡,只需要对应替换IP的支持板卡,petalinux 的BSP包,就行

如果是其他的加速器,想实现这种PS+PL的交互通信,做成演示demo。这个系列博客,可以为大家提供一种参考思路。

希望对大家有帮助,因为这里的很多步骤我当时也是踩了很多坑才走下来的,所以打算把这个流程分享出来,仅能保证按照这个系列的流程是可以走通的,其他的不敢保证。

有什么问题直接评论就行。

另外我想知道做个什么样的工程出来,可以更方便大家使用(就是直接替换自己的加速器,然后按照流程走完就可以上板实现),欢迎大家提意见,如果可行的话(我能搞定的话),我也想做一个开源出来。

本系列的工程代码开源

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水流water

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值