zedboard axiDMA linux驱动

本文介绍了一个基于Zynq的AXI DMA在Linux下的使用案例,包括PL端设计和PS侧软件实现。PL端设计包含四个AXI DMA IP,并通过concat IP将中断信号传递给Zynq。软件部分则提供了测试DMA性能的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面的话:

本篇是AXI DMA在linux下使用的例子。

包括PL端设计,基于vivado 2015.4,petalinux 2016.1,基于linux 4.4内核。

我在git hub 上托管了代码,https://github.com/shichaog/zynq-dma,如果想加入github上这个项目,给我邮箱发信息shichaog@126.com,方便为你配权限。

1.PL端设计:

PL端设计包括四个AXI DMA IP,它们分别和zynq处理IP的HP口相连接。

这个设计是基于Avnet-Digilent-ZedBoard-v2016.1-final.bsp,由于其它的ip都是xilinx开发环境开发环境就有,所以这里就不详细每一步设计过程了。

这些IP包括AXI interconnect, system reset,axi dma,concat。

注意concat是用来将AXI DMA的中断传递给zynq之用的,这是必须有的,否则在hdf导入时,会出现如下错误:

2 接下来是创建module和app了


这两个命令执行后,会分配在components/apps和components/modules目录下生成dmaBench和ds_axidma两个文件夹,该文件下的两个文件内容使用如下的文件替换。

《dmaBench.c》
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>

unsigned long tStart, tEnd;
unsigned long data;

unsigned long getTime(){
	struct timeval temp;

	gettimeofday(&temp, NULL);

	return temp.tv_sec * 1000 * 1000 + temp.tv_usec;
}

void report(char *msg, unsigned long data, unsigned long time, unsigned long dmaUsed){
	printf("%s\t%ld\t%ld\t%f\t%d\n", msg, data, time, data * 1.0 / time, dmaUsed);
	FILE *f = fopen("report.dat", "a");
	fprintf(f, "%s\t%ld\t%ld\t%f\t%d\n", msg, data, time, data * 1.0 / time, dmaUsed);
	fclose(f);
}

#define REPORT(f, timeStart, timeEnd, dataPtr, msg, dmaUsed) *timeStart = getTime(); *dataPtr = f; *timeEnd = getTime(); report(msg, *dataPtr, *timeEnd - *timeStart, dmaUsed);

void checkData(char *bufferIn, char *bufferOut, unsigned int elems){
	int i;

	if(!memcmp(bufferIn, bufferOut, elems*sizeof(char))){
		printf("DMA Ok!\n");
	}
	else{
		for(i=0;i<elems;i++)
			printf("%d\t%d\t%d\t%d\n", i, bufferIn[i], bufferOut[i], (i==0 ? 0 : bufferOut[i] - bufferOut[i-1]));
	}
}

unsigned long memCpy_ARM(char *bufferIn, char *bufferOut, unsigned long elems, size_t size){
	int i;

	for(i=0; i<elems; i++)
		bufferOut[i] = bufferIn[i];

	return elems * size;
}

unsigned long memCpy_DMA(char *bufferIn, char *bufferOut, unsigned long elems, size_t size, int dmaToUse){

#define FIFO_LEN 4000
#define DMA_NUM 4

	int fd[DMA_NUM];

	fd[0] = open("/dev/axi_dma_0", O_RDWR);
	fd[1] = open("/dev/axi_dma_1", O_RDWR);
	fd[2] = open("/dev/axi_dma_2", O_RDWR);
	fd[3] = open("/dev/axi_dma_3", O_RDWR);

	unsigned long byteMoved = 0;
	unsigned long byteToMove = 0;

	int i;

	while(byteMoved!=size * elems){
		byteToMove = size * elems - byteMoved > FIFO_LEN ? FIFO_LEN : size * elems - byteMoved;

		for(i=0; i<dmaToUse; i++){
			write(fd[i], &bufferIn[byteMoved], byteToMove);
		}

		for(i=0; i<dmaToUse; i++)
			read(fd[i], &bufferOut[byteMoved], byteToMove);

		byteMoved += byteToMove;
	}

	close(fd[0]);
	close(fd[1]);
	close(fd[2]);
	close(fd[3]);

	return elems * size * dmaToUse;
}

int main(int argc, char **argv)
{
    char *bufferIn, *bufferOut_ARM, *bufferOut_DMA;

    if(argc!=3){
    	printf("Usage: ./dmaBench DATA DMA_TO_USE\n");
    	exit(0);
    }

    unsigned long DATA = atoi(argv[1]);
    unsigned int DMA_TO_USE = atoi(argv[2]);

    bufferIn = (char *) malloc(sizeof(char) * DATA);
    bufferOut_ARM = (char *) malloc(sizeof(char) * DATA);
    bufferOut_DMA = (char *) malloc(sizeof(char) * DATA);

    int i;

    for(i=0; i<DATA; i++){
    	bufferIn[i] = i;
    }

    memset(bufferOut_ARM, 0, sizeof(char) * DATA);
    memset(bufferOut_DMA, 0, sizeof(char) * DATA);

    REPORT(memCpy_ARM(bufferIn, bufferOut_ARM, DATA, sizeof(char)), &tStart, &tEnd, &data, "ARM", 0);
    for(i=0; i<DMA_TO_USE; i++){
    	REPORT(memCpy_DMA(bufferIn, bufferOut_DMA, DATA/(i+1), sizeof(char), (i+1)), &tStart, &tEnd, &data, "DMA", (i+1));
    }

    checkData(bufferIn, bufferOut_ARM, DATA);
    checkData(bufferIn, bufferOut_DMA, DATA);

    return 0;
}
和<ds_axidma>

/*
 * Xilinx AXI DMA Driver
 *
 * Authors: 
 *    Fabrizio Spada - fabrizio.spada@mail.polimi.it
 *    Gianluca Durelli - durelli@elet.polimi.it
 *    Politecnico di Milano
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/dma-mapping.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/mm.h>
#include <asm/io.h>

#define MM2S_DMACR	0x00
#define MM2S_DMASR	0x04
#define MM2S_SA	0x18
#define MM2S_LENGTH	0x28

#define S2MM_DMACR	0x30
#define S2MM_DMASR	0x34
#define S2MM_DA	0x48
#define S2MM_LENGTH	0x58

#define DRIVER_NAME "ds_axidma_pdrv"
#define MODULE_NAME "ds_axidma"

#define DMA_LENGTH	(32*1024)

static struct class *cl;	// Global variable for the device class 

struct ds_axidma_device
{
	phys_addr_t bus_addr;
	unsigned long bus_size;
	char *virt_bus_addr;
	dev_t dev_num;
	const char *dev_name;
	struct cdev c_dev;
	char *ds_axidma_addr;
	dma_addr_t ds_axidma_handle;

	struct list_head dev_list;
};
LIST_HEAD( full_dev_list );

static struct ds_axidma_device *get_elem_from_list_by_inode(struct inode *i)
{
	struct list_head *pos;
	struct ds_axidma_device *obj_dev = NULL;
	list_for_each( pos, &full_dev_list ) {
		struct ds_axidma_device *tmp;
    	tmp = list_entry( pos, struct ds_axidma_device, dev_list );
    	if (tmp->dev_num == i->i_rdev)
    	{
    		obj_dev = tmp;
    		break;
    	}
  	}
  	return obj_dev;	
}
// static void dmaHalt(void){
// 	unsigned long mm2s_halt = ioread32(virt_bus_addr + MM2S_DMASR) & 0x1;
// 	unsigned long s2mm_halt = ioread32(virt_bus_addr + S2MM_DMASR) & 0x1;
// 	int count = 0;
// 	printk(KERN_INFO "Halting...\n");
// 	iowrite32(0, virt_bus_addr + S2MM_DMACR);
// 	iowrite32(0, virt_bus_addr + MM2S_DMACR);
// 	while( !mm2s_halt || !s2mm_halt){
// 		// mm2s_halt = ioread32(virt_bus_addr + MM2S_DMASR) & 0x1;
// 		mm2s_halt = virt_bus_addr[MM2S_DMASR] & 0x1;
// 		//s2mm_halt = ioread32(virt_bus_addr + S2MM_DMASR) & 0x1;
// 		s2mm_halt = virt_bus_addr[S2MM_DMASR] & 0x1;
// 		count++;
// 		if (count>100 )
// 		{
// 			break;
// 		}
// 	}

// 	printk(KERN_INFO "DMA Halted!\n");
// }

static int my_strcmp(const char *str1, const char *str2)
{
  int i;
  i = 0;
  while (str1[i] || str2[i])
    {
      if (str1[i] != str2[i])
        return (str1[i] - str2[i]);
      i++;
    }
  return (0);
}

static int dmaSynchMM2S(struct ds_axidma_device *obj_dev){
	//	sleep(6);
	//	return;

	unsigned int mm2s_status = ioread32(obj_dev->virt_bus_addr + MM2S_DMASR);
	while(!(mm2s_status & 1<<12) || !(mm2s_status & 1<<1) ){
		mm2s_status = ioread32(obj_dev->virt_bus_addr + MM2S_DMASR);

	}
	return 0;
}

static int dmaSynchS2MM(struct ds_axidma_device *obj_dev){
	unsigned int s2mm_status = ioread32(obj_dev->virt_bus_addr + S2MM_DMASR);
	while(!(s2mm_status & 1<<12) || !(s2mm_status & 1<<1)){
		s2mm_status = ioread32(obj_dev->virt_bus_addr + S2MM_DMASR);
	}
	return 0;
}

static int ds_axidma_open(struct inode *i, struct file *f)
{
	/* printk(KERN_INFO "<%s> file: open()\n", MODULE_NAME); */
	struct ds_axidma_device *obj_dev = get_elem_from_list_by_inode(i);
	if (!request_mem_region(obj_dev->bus_addr, obj_dev->bus_size, MODULE_NAME))
	{
		return -1;
	}	
	obj_dev->virt_bus_addr = (char *) ioremap_nocache(obj_dev->bus_addr, obj_dev->bus_size);
	return 0;
}

static int ds_axidma_close(struct inode *i, struct file *f)
{
	/* printk(KERN_INFO "<%s> file: close()\n", MODULE_NAME); */
	struct ds_axidma_device *obj_dev = get_elem_from_list_by_inode(i);
	iounmap(obj_dev->virt_bus_addr);
	release_mem_region(obj_dev->bus_addr, obj_dev->bus_size);
	return 0;
}

static ssize_t ds_axidma_read(struct file *f, char __user * buf, size_t
			 len, loff_t * off)
{
	/* printk(KERN_INFO "<%s> file: read()\n", MODULE_NAME); */
	struct ds_axidma_device *obj_dev;
	if (len >= DMA_LENGTH)
	{
		return 0;
	}
	obj_dev = get_elem_from_list_by_inode(f->f_inode);
	iowrite32(1, obj_dev->virt_bus_addr + S2MM_DMACR);
	iowrite32(obj_dev->ds_axidma_handle, obj_dev->virt_bus_addr + S2MM_DA);
	iowrite32(len, obj_dev->virt_bus_addr + S2MM_LENGTH);
	dmaSynchS2MM(obj_dev);
	memcpy(buf, obj_dev->ds_axidma_addr, len);
	return len;
}

static ssize_t ds_axidma_write(struct file *f, const char __user * buf,
			  size_t len, loff_t * off)
{
	/* printk(KERN_INFO "<%s> file: write()\n", MODULE_NAME); */

	struct ds_axidma_device *obj_dev;
	if (len >= DMA_LENGTH)
	{
		return 0;
	}

	obj_dev = get_elem_from_list_by_inode(f->f_inode);
	memcpy(obj_dev->ds_axidma_addr, buf, len);

	// printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + MM2S_DMASR));
	// printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + MM2S_DMACR));
	// printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + S2MM_DMASR));
	// printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + S2MM_DMACR));

	iowrite32(1, obj_dev->virt_bus_addr + MM2S_DMACR);
	iowrite32(obj_dev->ds_axidma_handle, obj_dev->virt_bus_addr + MM2S_SA);
	iowrite32(len, obj_dev->virt_bus_addr + MM2S_LENGTH);

	// dmaSynchMM2S(obj_dev);

	// printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + MM2S_DMASR));
	// printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + MM2S_DMACR));
	// printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + S2MM_DMASR));
	// printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + S2MM_DMACR));	
	// printk(KERN_INFO "%X\n", bus_addr);
	// printk(KERN_INFO "%lu\n", bus_size);

	return len;
}

static struct file_operations fops = {
	.owner = THIS_MODULE,
	.open = ds_axidma_open,
	.release = ds_axidma_close,
	.read = ds_axidma_read,
	.write = ds_axidma_write,
	/* .mmap = ds_axidma_mmap, */
	/* .unlocked_ioctl = ds_axidma_ioctl, */
};

static int ds_axidma_pdrv_probe(struct platform_device *pdev)
{
	/* device constructor */
	struct ds_axidma_device *obj_dev = (struct ds_axidma_device *)
            kmalloc( sizeof(struct ds_axidma_device), GFP_KERNEL );
    obj_dev->bus_addr = pdev->resource[0].start;
    obj_dev->bus_size = pdev->resource[0].end - pdev->resource[0].start + 1;
	obj_dev->dev_name = pdev->name + 9;
	
	printk(KERN_INFO "<%s> init: registered\n", obj_dev->dev_name);
	if (alloc_chrdev_region(&(obj_dev->dev_num), 0, 1, obj_dev->dev_name) < 0) {
		return -1;
	}
	if (cl == NULL && (cl = class_create(THIS_MODULE, "chardrv")) == NULL) {
		unregister_chrdev_region(obj_dev->dev_num, 1);
		return -1;
	}
	if (device_create(cl, NULL, obj_dev->dev_num, NULL, obj_dev->dev_name) == NULL) {
		class_destroy(cl);
		unregister_chrdev_region(obj_dev->dev_num, 1);
		return -1;
	}
	cdev_init(&(obj_dev->c_dev), &fops);
	if (cdev_add(&(obj_dev->c_dev), obj_dev->dev_num, 1) == -1) {
		device_destroy(cl, obj_dev->dev_num);
		class_destroy(cl);
		unregister_chrdev_region(obj_dev->dev_num, 1);
		return -1;
	}

	printk(KERN_INFO "DMA_LENGTH = %u \n", DMA_LENGTH);
	/* allocate mmap area */
	obj_dev->ds_axidma_addr =
	    dma_zalloc_coherent(NULL, DMA_LENGTH, &(obj_dev->ds_axidma_handle), GFP_KERNEL);
	list_add( &obj_dev->dev_list, &full_dev_list );
	return 0;
}

static int ds_axidma_pdrv_remove(struct platform_device *pdev)
{
	/* device destructor */
	struct list_head *pos, *q;
	list_for_each_safe( pos, q, &full_dev_list ) {
		struct ds_axidma_device *obj_dev;
    	obj_dev = list_entry( pos, struct ds_axidma_device, dev_list );
    	if (!my_strcmp(obj_dev->dev_name, pdev->name + 9))
    	{
    		list_del( pos );
    		cdev_del(&(obj_dev->c_dev));
    		device_destroy(cl, obj_dev->dev_num);
    		unregister_chrdev_region(obj_dev->dev_num, 1);
    		/* free mmap area */
			if (obj_dev->ds_axidma_addr) {
				dma_free_coherent(NULL, DMA_LENGTH, obj_dev->ds_axidma_addr, obj_dev->ds_axidma_handle);
			}
    		kfree(obj_dev);
    		break;
    	}
  	}
  	if (list_empty(&full_dev_list))
  	{
  		class_destroy(cl);
  	}
	printk(KERN_INFO "<%s> exit: unregistered\n", MODULE_NAME);
	return 0;
}

static int ds_axidma_pdrv_runtime_nop(struct device *dev)
{
	/* Runtime PM callback shared between ->runtime_suspend()
	 * and ->runtime_resume(). Simply returns success.
	 *
	 * In this driver pm_runtime_get_sync() and pm_runtime_put_sync()
	 * are used at open() and release() time. This allows the
	 * Runtime PM code to turn off power to the device while the
	 * device is unused, ie before open() and after release().
	 *
	 * This Runtime PM callback does not need to save or restore
	 * any registers since user space is responsbile for hardware
	 * register reinitialization after open().
	 */
	return 0;
}

static const struct dev_pm_ops ds_axidma_pdrv_dev_pm_ops = {
	.runtime_suspend = ds_axidma_pdrv_runtime_nop,
	.runtime_resume = ds_axidma_pdrv_runtime_nop,
};

static struct of_device_id ds_axidma_of_match[] = {
	{ .compatible = "ds_axidma", },
	{ /* This is filled with module_parm */ },
	{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, ds_axidma_of_match);
module_param_string(of_id, ds_axidma_of_match[1].compatible, 128, 0);
MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio");

static struct platform_driver ds_axidma_pdrv = {
	.probe = ds_axidma_pdrv_probe,
	.remove = ds_axidma_pdrv_remove,
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.pm = &ds_axidma_pdrv_dev_pm_ops,
		.of_match_table = of_match_ptr(ds_axidma_of_match),
	},
};

module_platform_driver(ds_axidma_pdrv);

MODULE_AUTHOR("Fabrizio Spada, Gianluca Durelli");
MODULE_DESCRIPTION("AXI DMA driver");
MODULE_LICENSE("GPL v2");

3.编译,生成BOOT.BIN文件


images/linux/目录下将生成的BOOT.BIN和image.ub文件拷贝到SD卡,插上SD卡。启动串口敲入用户名和密码(均root):


这里可以看到ds_axidma.ko这个内核module。同时可以看到apps


测试方法如下:


至此,DMA的简单实例就完成了,PS侧的DMA可以参考Audio侧,另外,如果有一些文件挂载分区,则如下:

<think>首先,用户的问题是关于在PL端直接控制AXI DMA启动数据传输。用户提到“在PL端直接控制AXIDMA开启数据传输”,所以我需要基于AXI DMA的知识来回答。从系统级指令:-所有行内数学表达式使用$...$,例如变量。 -独立公式使用$$...$$并单独成段。- LaTeX语法正确。-使用中文回答。-生成相关问题。 -回答中引用的段落末尾自然地添加引用标识,例如[^1]。用户可见层:-回答结构清晰,帮助用户逐步解决问题。 -保证回答真实可靠。-参考站内引用:有四个引用,是关于ZYNQAXIDMA的。引用摘要: -[^1]:ZYNQ Linux应用层利用 AXI DMA进行数据传输。- [^2]: ZYNQ多路AXI_DMA数据传输,使用查询模式。- [^3]:复旦微ZYNQ SOCAXI_DMA数据传输,驱动软件实现。 -[^4]:ZYNQ7000平台- Linux环境下PL-PS使用AXI-DMA数据传输工程文件。 用户的问题是:“我想在PL端直接控制AXI DMA启动数据传输请问如何在PL端直接控制AXI DMA开启数据传输” 关键点:用户想在PL端(Programmable Logic)直接控制AXI DMA启动数据传输,而不是在PS端(Processing System)通过软件控制。在标准AXIDMA操作中,通常PS端通过驱动来控制DMA,但用户想在PL端直接控制。这意味着PL需要触发DMA传输。 AXI DMA的核心:AXIDMA IP核有寄存器接口,PS通过访问这些寄存器来控制DMA传输。但PL端可以通过AXI-Lite接口或直接连接到控制信号来控制。 可能的解决方案: 1. **使用PL逻辑生成控制信号**:在PL端设计逻辑(如状态机)来直接写入AXI DMA的控制寄存器,从而启动传输。2.**AXI Stream接口**:AXI DMAAXI Stream接口用于数据传输。PL端可以通过AXI Stream发送数据或命令来触发传输。3.**中断或事件触发**:但用户指定“直接控制”,所以可能不需要PS介入。 参考引用: -[^3]提到“数据发送模块用来将数据从PS搬迁到PL,数据接收模块负责将PL数据搬迁到PS”,但这是在PS端驱动的。 -[^2]讨论查询和中断模式,但也是PS端控制。- [^4]提供工程文件,可能涉及PL-PS交互。 用户想绕过PS软件控制,直接在PL端启动。 实现方式: -AXI DMAIP核有控制寄存器,如MM2S_DMACR(内存到流DMA控制寄存器)和S2MM_DMACR(流到内存DMA控制寄存器)。这些寄存器可以通过AXI-Lite接口访问。-在PL端,可以实例化一个AXIMaster模块来写入这些寄存器。-例如,PL逻辑可以生成一个AXI事务来设置DMACR中的RUN位,启动传输。步骤:1.**配置AXIDMA IP**:在Vivado中配置AXIDMA IP,确保控制接口(AXI-Lite)暴露给PL。 2. **设计PL逻辑**:在PL中设计逻辑(如使用Verilog或VHDL)来访问AXI-Lite接口。逻辑可以:-写入AXIDMA的源地址寄存器(MM2S_SA)。-写入目标地址寄存器(S2MM_DA)。-写入控制寄存器设置RUN位。 3. **触发传输**:PL逻辑可以基于事件(如外部信号)写入寄存器来启动传输。 4. **数据传输**:一旦启动,数据通过AXIStream接口传输。注意事项: -PS端可能需要初始化DMA,但用户说“直接控制”,所以或许PS只负责配置,启动由PL做。 -在Linux环境中,PS驱动可能已经映射了寄存器,但PL直接访问可能冲突。需要确保PL访问不干扰PS。在纯PL控制场景: -PS可以配置DMA(如设置缓冲区),然后PL触发传输。 -或者,PL完全控制,PS不涉及。 回答结构:1.**理解需求**:解释在PL端直接控制AXIDMA的含义。 2. **实现原理**:描述AXI DMA的控制机制和PL如何介入。 3. **步骤指南**:提供实现步骤,包括Vivado配置和PL设计。 4. **代码示例**:如果可能,给出简单Verilog代码片段。5.**注意事项**:讨论潜在问题,如PS-PL冲突。 6. **生成相关问题**:在末尾添加相关问题。 使用中文回答。 生成相关问题:基于用户问题,生成2-3个相关问题。 引用处理:在回答中,如果参考引用,添加引用标识。例如,提到控制寄存器时引用[^3]。LaTeX:如果有数学表达式,如地址或位宽,使用$...$。例如,寄存器地址$0x00$。独立公式:如果有公式,单独成段。但这里可能不需要。 最终回答:-开头:确认用户问题。-主体:逐步解释。 -结尾:相关问题。</think>### 在PL端直接控制AXI DMA启动数据传输的方法 在ZYNQ平台上,AXI DMA(Direct Memory Access)通常由PS端(Processing System)通过Linux驱动控制,但您希望直接在PL端(Programmable Logic)触发并启动数据传输。这可以实现PL端自主控制DMA传输,减少PS介入,适用于实时性要求高的场景(如高速数据采集或自定义硬件加速)。下面我将基于AXI DMA的工作原理和ZYNQ架构,逐步解释实现方法。实现的核心在于PL端逻辑直接访问AXI DMA的控制寄存器(如设置RUN位),而不是依赖PS软件。参考引用[^3]提到,AXI DMA驱动在软件层涉及通道申请和地址填写,但PL端可以通过硬件逻辑模拟类似操作。 #### 1. **理解AXI DMA的控制机制** - AXI DMA IP核包含关键寄存器,用于控制数据传输: - **MM2S (Memory to Stream) 通道**:用于从PS内存向PL发送数据。 - 控制寄存器:`MM2S_DMACR`(地址偏移量通常为$0x00$),其中RUN位(bit 0)用于启动传输。 - 源地址寄存器:`MM2S_SA`(地址偏移量$0x18$),指定PS内存的起始地址。 - **S2MM (Stream to Memory) 通道**:用于从PL向PS内存发送数据。 - 控制寄存器:`S2MM_DMACR`(地址偏移量$0x30$),RUN位启动传输。 - 目标地址寄存器:`S2MM_DA`(地址偏移量$0x48$),指定PS内存的目标地址。 - 在标准流程中,PS端通过AXI-Lite接口写入这些寄存器来启动传输。但PL端可以直接生成AXI-Lite事务来访问这些寄存器,从而“直接控制”传输启停[^3]。 - **关键点**:PL端逻辑需要模拟AXI-Lite主设备(AXI Master),通过写入RUN位(设置为$1$)来触发传输。数据传输本身通过AXI-Stream接口完成,PL逻辑需确保地址和数据对齐。 #### 2. **实现步骤:在Vivado中配置和设计PL逻辑** 以下是逐步实现方案,基于ZYNQ7000平台(如ZedBoard)。整个过程在Vivado设计套件中完成,PL端使用Verilog或VHDL编码。参考引用[^4]提供的工程文件可作为基础,但需修改以支持PL端控制。 ##### 步骤 1: Vivado 工程配置 - **添加并配置AXI DMA IP核**: - 在Block Design中,添加AXI DMA IP(版本1.0或更高)。 - 配置DMA模式:选择“Simple”模式(查询模式),禁用Scatter Gather以简化控制。确保“Enable Control Stream Interface”选项关闭(避免PS介入)。 - 连接AXI接口: - AXI-Lite控制接口:连接到ZYNQ PS的GP端口(AXI_GP0),但后续PL逻辑将直接访问此接口。 - AXI-Stream数据接口:连接到PL端的数据源/汇(如自定义FIFO或逻辑模块)。 - 生成地址映射:记下AXI DMA的基地址(如$0x40400000$),用于PL逻辑访问寄存器[^2][^4]。 - **添加PL端AXI Master模块**: - 由于PL需访问AXI-Lite接口,需添加一个AXI Master IP(如AXI CDMA或自定义AXI Master)。或使用Verilog实现一个简易AXI-Lite主状态机。 - 连接:将PL AXI Master的输出连接到AXI DMAAXI-Lite从接口。 ##### 步骤 2: 设计PL控制逻辑(Verilog示例) 在PL端,设计一个状态机或计数器逻辑,直接生成AXI-Lite写事务来启动DMA。以下是简化的Verilog代码片段,展示如何写入`MM2S_DMACR`寄存器启动传输: ```verilog module pl_dma_control ( input wire clk, // 系统时钟(如100MHz) input wire reset_n, // 复位信号 input wire start_trig, // PL端触发信号(如外部事件) output reg [31:0] m_axi_awaddr, // AXI-Lite写地址 output reg [31:0] m_axi_wdata, // AXI-Lite写数据 output reg m_axi_awvalid, // 地址有效 output reg m_axi_wvalid, // 数据有效 input wire m_axi_awready, // 从设备准备好 input wire m_axi_wready // 从设备准备好 ); // 状态定义 typedef enum {IDLE, SET_ADDR, SET_DATA, START} state_t; state_t current_state; // AXI DMA寄存器地址(基地址 + 偏移量),例如基地址0x40400000 localparam DMA_BASE = 32'h40400000; localparam MM2S_DMACR_ADDR = DMA_BASE + 32'h00; // 控制寄存器地址 always @(posedge clk or negedge reset_n) begin if (!reset_n) begin current_state <= IDLE; m_axi_awvalid <= 1'b0; m_axi_wvalid <= 1'b0; end else begin case (current_state) IDLE: if (start_trig) begin m_axi_awaddr <= MM2S_DMACR_ADDR; // 设置写地址 m_axi_awvalid <= 1'b1; current_state <= SET_ADDR; end SET_ADDR: if (m_axi_awready) begin m_axi_awvalid <= 1'b0; m_axi_wdata <= 32'h00000001; // 写数据:RUN位=1,其他位默认0 m_axi_wvalid <= 1'b1; current_state <= SET_DATA; end SET_DATA: if (m_axi_wready) begin m_axi_wvalid <= 1'b0; current_state <= START; // 传输已启动 end START: current_state <= IDLE; // 返回空闲,等待下次触发 endcase end end endmodule ``` - **代码说明**: - 当`start_trig`信号(来自PL事件)为高时,状态机写入`MM2S_DMACR`寄存器,设置RUN位为$1$。 - 类似地,可以扩展为写入`MM2S_SA`(源地址)和`S2MM_DA`(目标地址)。地址值需在PL初始化时设置(如通过PS配置或固定值)。 - 数据传输启动后,AXI-Stream接口自动处理数据搬运,无需PL干预[^3]。 ##### 步骤 3: PS端初始化(可选但推荐) - PL端直接控制时,PS端仍需进行基础初始化: - 在Linux驱动中,分配DMA缓冲区并映射物理地址(使用`dma_alloc_coherent`)。 - 将缓冲区地址(如$0x1E000000$)写入PL逻辑或通过共享内存传递,用于设置`MM2S_SA`/`S2MM_DA`。 - 目的:避免PL和PS访问冲突,确保内存安全。参考引用[^1]描述了应用层数据传输的初始化步骤。 - 如果完全绕过PS,需在PL中硬编码地址,但灵活性较低。 ##### 步骤 4: 测试与验证 - 在Vivado中生成比特流,加载到ZYNQ板。 - 使用ILA(Integrated Logic Analyzer)监控PL触发信号和AXI-Lite事务。 - 执行回环测试:PL触发DMA传输后,检查PS内存数据是否正确。参考引用[^4]的工程文件包含测试脚本,可适配用于验证。 #### 3. **注意事项与潜在挑战** - **PS-PL冲突风险**:如果PS驱动同时访问DMA寄存器,可能导致系统崩溃。解决方案: - 在Linux驱动中禁用DMA控制(仅初始化缓冲区)。 - 使用互斥信号量或PL独占访问模式。 - **性能优化**: - 为减少延迟,PL逻辑应使用高速时钟(≥150MHz)。 - 对于多路传输,扩展状态机以支持多个DMA通道,参考引用[^2]的查询模式实现。 - **资源消耗**:PL AXI Master逻辑会消耗LUT和寄存器资源,需在Vivado报告中评估。 - **适用场景**:此方法适合PL主导的实时系统(如传感器数据采集),但复杂控制(如错误处理)仍需PS介入[^3]。 #### 4. **总结** 在PL端直接控制AXI DMA启动数据传输的核心是:通过PL逻辑生成AXI-Lite写事务,设置DMA控制寄存器的RUN位。这减少了PS软件开销,提升了实时性。实现时,需在Vivado中配置AXI DMA IP,并设计PL状态机访问寄存器。PS端可辅助初始化内存缓冲区。参考引用[^4]的工程文件可作为起点,但需修改为PL主导控制。如果您提供更多细节(如具体平台或触发事件),我可以进一步优化建议。
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

shichaog

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

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

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

打赏作者

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

抵扣说明:

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

余额充值