🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
` `在使用ZYNQ7021系列的FPGA,若涉及到PL部分读写DDR,可使用过AXI-Lite,AXI4-FULL,AXI-Stream这三个IP来实现,使用的是这三个IP的主机模式。 ## AXI 4总线协议解析 * [ ] AXI4:主要面向高性能地址映射通信的需求; * [ ]  AXI4-Lite:是一个简单地吞吐量地址映射性通信总线; * [ ]  AXI4-Stream:面向高速流数据传输; * [ ] AXI4总线分为主、从两端,两者间可以连续的进行通信; ` `AXI 4总线采用READY,VALID握手通信机制,主设备收到从设备发送的READY,主设备将数据和VALID信号同时发送给从设备。 ` `AXI 4-Lite所有的猝发长度为1,数据总线宽度为32位或64位 ` `AXI 4-Stream数据总线宽度:8、16、32、64、128、256、512和1024位; **axi总线分为五个通道:** * --读地址通道,包含ARVALID,ARADDR, ARREADY信号; * --写地址通道,包含AWVALID,AWADDR,AWREADY信号; * --读数据通道,包含RVALID,RDATA, RREADY, RRESP信号; * --写数据通道,包含WVALID, WDATA,WSTRB,WREADY信号; * --写应答通道,包含BVALID,BRESP, BREADY信号; ` `AWLEN,猝发长度,是一个猝发中传送的数据个数,传送个数=AWLEN+1;如AWLEN=5,传送6个数据。 ` `AWSIZE,猝发大小,猝发中每个传送的数据的大小,字节数为=2^AWSIZE;如AWSIZE=0,每个数据是1个字节,AWSIZE=3,每个数据大小是8个字节 ; **axis信号分为:** * --TREADY信号:从告诉主做好传输准备; * --TVALID信号:主告诉从数据传输有效; * --TLAST信号:主告诉从该次传输为突发传输结尾 * --TDATA信号:数据,可选宽度32,64,128,256bit * --TSTRB信号:为1的bit为对应tdata有效字节,宽度为tdata/8 * --TUSER信号  :用户定义信号,宽度为128bit * --ACLK信号:总线时钟,上升沿有效; * --ARESETn信号:总线复位,低电平有效; ` `读操作 ![](https://img.kancloud.cn/a7/69/a769a8fccdd6d97164e8f76c89d1ffc6_583x243.png) ` `在读交易中,主设备先发送ARADDR和ARVALID给从设备,从设备回发ARREADY,通知主设备该地址有效,当ARVALID和ARREADY都为高电平时,主设备发出RREADY,表示主设备准备好接受读数据和相应信号了。从设备发送RVALID、RDATA以及RRESP,当RVALID和RREADY都为高电平时,数据被写入主设备。 ` `写操作 ![](https://img.kancloud.cn/e9/8a/e98ac9f8d422cc8f910b0a11f002f5f9_600x325.png) ` `在写操作中,主设备往从设备中写入AWADDR和AWVALID,然后主设备并没有等待从设备的AWREADY,而是继续发送WVALID和WDATA,当从设备回应AWREADY有效后,紧接着从设备发送WREADY表示从设备准备好接受数据,当WVALID和WREADY都为高电平是数据写入从设备。主设备发送的AWVALID和WVALID要有重叠区。 ## 关于同时对DDR写操作的说明 ` `在进行数据存储时,有时候有几类数据需要写入DDR,并且这些数据相互间没有相关性,比如将AD芯片K5394获取的数据存入地址为0x02000000,长度为0xf,将时间信息存入0x020000f0,长度为0XF0的DDR空间,这两者间数据基本无相关性,即获得了数据就将其写入DDR中,在使用这些IP来写数据时不需要考虑数据写入之间的竞争情况,因为AXI的IP模块有自己的WID,不同的WID可以乱序写入,但是同一个WID需要顺序写入,模块将会根据WID来X向DDR写数据。同时AK5394的数据得到为流水线形式,且得到两个数据之间的时间较大,因此这种的数据适合使用AXI-Lite来进行数据传输。 ` `验证例程为pl_write_ddr_lite,其中MCU_2_FPGA的IP为修改的axi-lite的slave的IP,用于PS向PL部分发送数据。 ![](https://img.kancloud.cn/4f/ab/4fab9ee493820340c482b518bfd7db57_369x291.png) ` `axi_lite_wrddr模块是修改的AXI-Lite的Master的IP,用于实现想DDR的某一地址写入数据。 ![](https://img.kancloud.cn/b4/a8/b4a82ff99d50caa1bb27628a1ce3dac9_398x234.png) ` `在使用时,由于对该IP进行了修改,若设置多次传输数据,将会对同一地址写入相同的数据,该模块主要是为了对一个地址写一个数据,不进行突发数据写入,因此需要设置写入次数为1次,如下图所示。同时写入的地址和写入的数据被引出,方便使用。 ![](https://img.kancloud.cn/f1/67/f167a16beb2402e8a77d2410fa89af6e_952x669.png) ## HP和GP接口 ` `在 ZYNQ 芯片内部用硬件实现了 AXI 总线协议,包括 9 个物理接口,分别为 AXI-GP0~AXI- GP3,AXI-HP0~AXI-HP3,AXI-ACP 接口。 ` `AXI_ACP 接口,是 ARM 多核架构下定义的一种接口,中文翻译为加速器一致性端口,用 来管理 DMA 之类的不带缓存的 AXI 外设,PS 端是 Slave 接口。 ` `AXI_HP 接口,是高性能/带宽的 AXI3.0 标准的接口,总共有四个,PL 模块作为主设备连 接。主要用于 PL 访问 PS 上的存储器(DDR 和 On-Chip RAM) ` `AXI_GP 接口,是通用的 AXI 接口,总共有四个,包括两个 32 位主设备接口和两个 32 位 从设备接口。 ![](https://img.kancloud.cn/ba/1a/ba1adcec15cd1c81ee537e5ddfd2ce72_986x567.png) ` `可以看到,只有两个 AXI-GP 是 Master Port,即主机接口,其余 7 个口都是 Slave Port(从 机接口)。主机接口具有发起读写的权限,ARM 可以利用两个 AXI-GP 主机接口主动访问 PL 逻 辑,其实就是把 PL 映射到某个地址,读写 PL 寄存器如同在读写自己的存储器。其余从机接口 就属于被动接口,接受来自 PL 的读写,逆来顺受。 ` `另外这 9 个 AXI 接口性能也是不同的。GP 接口是 32 位的低性能接口,理论带宽 600MB/s,而 HP 和 ACP 接口为 64 位高性能接口,理论带宽 1200MB/s。有人会问,为什么高 性能接口不做成主机接口呢?这样可以由 ARM 发起高速数据传输。答案是高性能接口根本不 需要 ARM CPU 来负责数据搬移,真正的搬运工是位于 PL 中的 DMA 控制器。 ## 几个AXI IP介绍 ` `下面为几个常用的 AXI 接口 IP 的功能介绍: ` `AXI-DMA:实现从 PS 内存到 PL 高速传输高速通道 AXI-HP<---->AXI-Stream 的转换 。 ` `AXI-FIFO-MM2S:实现从 PS 内存到 PL 通用传输通道 AXI-GP<----->AXI-Stream 的转换 。 ` `AXI-Datamover:实现从 PS 内存到 PL 高速传输高速通道 AXI-HP<---->AXI-Stream 的转换,只 不过这次是完全由 PL 控制的,PS 是完全被动的。 ` `AXI-VDMA:实现从 PS 内存到 PL 高速传输高速通道 AXI-HP<---->AXI-Stream 的转换,只不 过是专门针对视频、图像等二维数据的。 ` `AXI-CDMA:这个是由 PL 完成的将数据从内存的一个位置搬移到另一个位置,无需 CPU 来 插手。 ` `AXI 协议严格的讲是一个点对点的主从接口协议,当多个外设需要互相交互数据时,我们 需要加入一个 AXI Interconnect 模块,也就是 AXI 互联矩阵,作用是提供将一个或多个 AXI 主设 备连接到一个或多个 AXI 从设备的一种交换机制(有点类似于交换机里面的交换矩阵)。 ` `这个 AXI Interconnect IP 核最多可以支持 16 个主设备、16 个从设备,如果需要更多的接 口,可以多加入几个 IP 核。 ## 多个AXI模块并存时的地址分配 ` `当使用多个AXI的IP(主机/从机),需要对地址进行映射。 ![](https://img.kancloud.cn/2f/36/2f36d1a39d3901182bb2f9849d3c6e98_943x773.png) ` `需要对每一个主机会映射的从机进行地址分配,如硬核PS的AXI主机接口需要连接MCU_2_FPGA的IP,那么就对这哥IP进行地址分配。axi_lite_wrddr需要映射到PS的HP接口,则对HP进行地址分配。 ![](https://img.kancloud.cn/56/95/569561c5bcf608f194fd53f580111f4a_873x450.png) ` `但是需要注意的是,两个IP不要对重复地址段进行写数据,因为这样会造成数据混乱。 ## 测试程序说明 ` `main文件代码如下: ``` /* * helloworld.c: simple test application * * This application configures UART 16550 to baud rate 9600. * PS7 UART (Zynq) is not initialized by this application, since * bootrom/bsp configures it to baud rate 115200 * * ------------------------------------------------ * | UART TYPE BAUD RATE | * ------------------------------------------------ * uartns550 9600 * uartlite Configurable only in HW design * ps7_uart 115200 (configured by bootrom/bsp) */ #include <stdio.h> #include "platform.h" #include "xil_printf.h" #include "sleep.h" #include "xil_io.h" #include "xparameters.h" #include "xparameters_ps.h" #include "mcu_2_fpga.h" #include "stdbool.h" void mcu2fpga_write(int id,u32 data) { switch(id){ case(0):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_0_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG0_OFFSET,data);break; case(1):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_0_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG1_OFFSET,data);break; case(2):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_0_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG2_OFFSET,data);break; case(3):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_0_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG3_OFFSET,data);break; default:break; } } void mcu2fpga1_write(int id,u32 data) { switch(id){ case(0):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_1_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG0_OFFSET,data);break; case(1):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_1_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG1_OFFSET,data);break; case(2):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_1_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG2_OFFSET,data);break; case(3):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_1_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG3_OFFSET,data);break; default:break; } } void mcu2fpga2_write(int id,u32 data) { switch(id){ case(0):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_2_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG0_OFFSET,data);break; case(1):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_2_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG1_OFFSET,data);break; case(2):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_2_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG2_OFFSET,data);break; case(3):MCU_2_FPGA_mWriteReg(XPAR_MCU_2_FPGA_2_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG3_OFFSET,data);break; default:break; } } u32 mcu2fpga_read(int id) { switch(id){ case(0):return MCU_2_FPGA_mReadReg(XPAR_MCU_2_FPGA_0_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG0_OFFSET);break; case(1):return MCU_2_FPGA_mReadReg(XPAR_MCU_2_FPGA_0_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG1_OFFSET);break; case(2):return MCU_2_FPGA_mReadReg(XPAR_MCU_2_FPGA_0_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG2_OFFSET);break; case(3):return MCU_2_FPGA_mReadReg(XPAR_MCU_2_FPGA_0_S00_AXI_BASEADDR,MCU_2_FPGA_S00_AXI_SLV_REG3_OFFSET);break; default:break; } return 12; } //分配DDR中对应AD数据的内存地址给数组 u32 ak5394_m1[60] __attribute__((section(".ak5394M1Section")));//hpy test PS read DDR u32 ak5394_m2[60] __attribute__((section(".ak5394M2Section")));//hpy test PS read DDR void pl_update_ddr(u32 addr,int offset,_Bool display)//测试PL写DDR,用于发送数据到FPGA更新DDR中的数据 { for(int j = 0;j<30;j++){ mcu2fpga_write(3,4*j + offset); mcu2fpga_write(2,4*j+1 + offset); mcu2fpga_write(1,4*j+2 + offset); usleep(10); mcu2fpga_write(0,addr + j*16); if(display == true){ printf("data[%d] = %d\n",4*j,4*j + offset); printf("data[%d] = %d\n",4*j+1,4*j+1 + offset); printf("data[%d] = %d\n",4*j+2,4*j+2 + offset); printf("data[%d] = %d\n",4*j+3,4*j+3 + offset); } usleep(100); } } //利用MCU_2_FPGA向PL发送数据,然后PL将数据写入DDR void lite_write(u32 ad,int off)、{ for(int i=0;i<30;i++){ mcu2fpga2_write(1,i+off); mcu2fpga1_write(1,i+off+1); mcu2fpga2_write(0,ad+i*4); mcu2fpga1_write(0,ad+i*4 + 120); usleep(100); } pl_update_ddr(0x020000F0,off,false); } void write_ddr_lite_test(){ int i = 0; int mdata = 5; u32 ad0 = 0x02000000; u32 ad = ad0; int cnt=0; while(1){ lite_write(ad,cnt); if(cnt>=100)cnt = 0; else cnt++; printf("--------------------------\n"); usleep(1000000); Xil_DCacheInvalidateRange(ad0,sizeof(ak5394_m1));//将DDR中数据更新到cache中 Xil_DCacheInvalidateRange(0x020000F0,sizeof(ak5394_m2));//将DDR中数据更新到cache中 for(u32 j=0;j<30;j++){// PS读取DDR并显示 printf("a[%d]=%d a[%d]=%d b[%d]=%d\n ",j,ak5394_m1[j],j+30,ak5394_m1[j+30],j,ak5394_m2[j]); } } } int main() { init_platform(); int max = 7; int i = 1; u32 addr = 0x02000000; u32 addr2 =0x02000000; write_ddr_lite_test(); pl_update_ddr(addr,0,false);//测试函数 cleanup_platform(); return 0; } ``` ## link脚本文件编写 ` `为了方便数据读出,将写入DDR的地址段分配给数组,得益于GCC的强大功能,通过``__attribute__``可以实现给数组分配指定地址空间。 ` `文件编写如下 ``` /*******************************************************************/ /* */ /* This file is automatically generated by linker script generator.*/ /* */ /* Version: 2018.3 */ /* */ /* Copyright (c) 2010-2016 Xilinx, Inc. All rights reserved. */ /* */ /* Description : Cortex-A9 Linker Script */ /* */ /*******************************************************************/ _STACK_SIZE = DEFINED(_STACK_SIZE) ? _STACK_SIZE : 0x2000; _HEAP_SIZE = DEFINED(_HEAP_SIZE) ? _HEAP_SIZE : 0x2000; _ABORT_STACK_SIZE = DEFINED(_ABORT_STACK_SIZE) ? _ABORT_STACK_SIZE : 1024; _SUPERVISOR_STACK_SIZE = DEFINED(_SUPERVISOR_STACK_SIZE) ? _SUPERVISOR_STACK_SIZE : 2048; _IRQ_STACK_SIZE = DEFINED(_IRQ_STACK_SIZE) ? _IRQ_STACK_SIZE : 1024; _FIQ_STACK_SIZE = DEFINED(_FIQ_STACK_SIZE) ? _FIQ_STACK_SIZE : 1024; _UNDEF_STACK_SIZE = DEFINED(_UNDEF_STACK_SIZE) ? _UNDEF_STACK_SIZE : 1024; /* Define Memories in the system */ MEMORY { ps7_ddr_0 : ORIGIN = 0x100000, LENGTH = 0x1f00000 /*0x3FF00000*/ ps7_ram_0 : ORIGIN = 0x0, LENGTH = 0x30000 ps7_ram_1 : ORIGIN = 0xFFFF0000, LENGTH = 0xFE00 /*以下为自己定义,需要分配的地址段*/ AK5394_M1 : ORIGIN = 0x02000000, LENGTH = 0x000000F0 AK5394_M2 : ORIGIN = 0x020000F0, LENGTH = 0x000000F0 } /* Specify the default entry point to the program */ ENTRY(_vector_table) /* Define the sections, and where they are mapped in memory */ SECTIONS { .ak5394M1Section :{ __ak5394Section_start = .; *(.ak5394M1Section) __ak5394Section_end = .; } > AK5394_M1 .ak5394M2Section :{ __ak5394Section_start = .; *(.ak5394M2Section) __ak5394Section_end = .; } > AK5394_M2 .text : { KEEP (*(.vectors)) *(.boot) *(.text) *(.text.*) *(.gnu.linkonce.t.*) *(.plt) *(.gnu_warning) *(.gcc_execpt_table) *(.glue_7) *(.glue_7t) *(.vfp11_veneer) *(.ARM.extab) *(.gnu.linkonce.armextab.*) } > ps7_ddr_0 .init : { KEEP (*(.init)) } > ps7_ddr_0 .fini : { KEEP (*(.fini)) } > ps7_ddr_0 .rodata : { __rodata_start = .; *(.rodata) *(.rodata.*) *(.gnu.linkonce.r.*) __rodata_end = .; } > ps7_ddr_0 .rodata1 : { __rodata1_start = .; *(.rodata1) *(.rodata1.*) __rodata1_end = .; } > ps7_ddr_0 .sdata2 : { __sdata2_start = .; *(.sdata2) *(.sdata2.*) *(.gnu.linkonce.s2.*) __sdata2_end = .; } > ps7_ddr_0 .sbss2 : { __sbss2_start = .; *(.sbss2) *(.sbss2.*) *(.gnu.linkonce.sb2.*) __sbss2_end = .; } > ps7_ddr_0 .data : { __data_start = .; *(.data) *(.data.*) *(.gnu.linkonce.d.*) *(.jcr) *(.got) *(.got.plt) __data_end = .; } > ps7_ddr_0 .data1 : { __data1_start = .; *(.data1) *(.data1.*) __data1_end = .; } > ps7_ddr_0 .got : { *(.got) } > ps7_ddr_0 .ctors : { __CTOR_LIST__ = .; ___CTORS_LIST___ = .; KEEP (*crtbegin.o(.ctors)) KEEP (*(EXCLUDE_FILE(*crtend.o) .ctors)) KEEP (*(SORT(.ctors.*))) KEEP (*(.ctors)) __CTOR_END__ = .; ___CTORS_END___ = .; } > ps7_ddr_0 .dtors : { __DTOR_LIST__ = .; ___DTORS_LIST___ = .; KEEP (*crtbegin.o(.dtors)) KEEP (*(EXCLUDE_FILE(*crtend.o) .dtors)) KEEP (*(SORT(.dtors.*))) KEEP (*(.dtors)) __DTOR_END__ = .; ___DTORS_END___ = .; } > ps7_ddr_0 .fixup : { __fixup_start = .; *(.fixup) __fixup_end = .; } > ps7_ddr_0 .eh_frame : { *(.eh_frame) } > ps7_ddr_0 .eh_framehdr : { __eh_framehdr_start = .; *(.eh_framehdr) __eh_framehdr_end = .; } > ps7_ddr_0 .gcc_except_table : { *(.gcc_except_table) } > ps7_ddr_0 .mmu_tbl (ALIGN(16384)) : { __mmu_tbl_start = .; *(.mmu_tbl) __mmu_tbl_end = .; } > ps7_ddr_0 .ARM.exidx : { __exidx_start = .; *(.ARM.exidx*) *(.gnu.linkonce.armexidix.*.*) __exidx_end = .; } > ps7_ddr_0 .preinit_array : { __preinit_array_start = .; KEEP (*(SORT(.preinit_array.*))) KEEP (*(.preinit_array)) __preinit_array_end = .; } > ps7_ddr_0 .init_array : { __init_array_start = .; KEEP (*(SORT(.init_array.*))) KEEP (*(.init_array)) __init_array_end = .; } > ps7_ddr_0 .fini_array : { __fini_array_start = .; KEEP (*(SORT(.fini_array.*))) KEEP (*(.fini_array)) __fini_array_end = .; } > ps7_ddr_0 .ARM.attributes : { __ARM.attributes_start = .; *(.ARM.attributes) __ARM.attributes_end = .; } > ps7_ddr_0 .sdata : { __sdata_start = .; *(.sdata) *(.sdata.*) *(.gnu.linkonce.s.*) __sdata_end = .; } > ps7_ddr_0 .sbss (NOLOAD) : { __sbss_start = .; *(.sbss) *(.sbss.*) *(.gnu.linkonce.sb.*) __sbss_end = .; } > ps7_ddr_0 .tdata : { __tdata_start = .; *(.tdata) *(.tdata.*) *(.gnu.linkonce.td.*) __tdata_end = .; } > ps7_ddr_0 .tbss : { __tbss_start = .; *(.tbss) *(.tbss.*) *(.gnu.linkonce.tb.*) __tbss_end = .; } > ps7_ddr_0 .bss (NOLOAD) : { __bss_start = .; *(.bss) *(.bss.*) *(.gnu.linkonce.b.*) *(COMMON) __bss_end = .; } > ps7_ddr_0 _SDA_BASE_ = __sdata_start + ((__sbss_end - __sdata_start) / 2 ); _SDA2_BASE_ = __sdata2_start + ((__sbss2_end - __sdata2_start) / 2 ); /* Generate Stack and Heap definitions */ .heap (NOLOAD) : { . = ALIGN(16); _heap = .; HeapBase = .; _heap_start = .; . += _HEAP_SIZE; _heap_end = .; HeapLimit = .; } > ps7_ddr_0 .stack (NOLOAD) : { . = ALIGN(16); _stack_end = .; . += _STACK_SIZE; . = ALIGN(16); _stack = .; __stack = _stack; . = ALIGN(16); _irq_stack_end = .; . += _IRQ_STACK_SIZE; . = ALIGN(16); __irq_stack = .; _supervisor_stack_end = .; . += _SUPERVISOR_STACK_SIZE; . = ALIGN(16); __supervisor_stack = .; _abort_stack_end = .; . += _ABORT_STACK_SIZE; . = ALIGN(16); __abort_stack = .; _fiq_stack_end = .; . += _FIQ_STACK_SIZE; . = ALIGN(16); __fiq_stack = .; _undef_stack_end = .; . += _UNDEF_STACK_SIZE; . = ALIGN(16); __undef_stack = .; } > ps7_ddr_0 _end = .; } ``` ` `然后利用`__attribute__`进行分配地址。`u32 ak5394_m1[60] __attribute__((section(".ak5394M1Section")));//hpy test PS read DDR` ## 串口显示验证 ![](https://img.kancloud.cn/91/2b/912bcb2f11bca154de4ae933c4e6dd5e_1247x704.png)