link
在使用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信号:总线复位,低电平有效;
读操作
在读交易中,主设备先发送ARADDR和ARVALID给从设备,从设备回发ARREADY,通知主设备该地址有效,当ARVALID和ARREADY都为高电平时,主设备发出RREADY,表示主设备准备好接受读数据和相应信号了。从设备发送RVALID、RDATA以及RRESP,当RVALID和RREADY都为高电平时,数据被写入主设备。
写操作
在写操作中,主设备往从设备中写入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部分发送数据。
axi_lite_wrddr模块是修改的AXI-Lite的Master的IP,用于实现想DDR的某一地址写入数据。
在使用时,由于对该IP进行了修改,若设置多次传输数据,将会对同一地址写入相同的数据,该模块主要是为了对一个地址写一个数据,不进行突发数据写入,因此需要设置写入次数为1次,如下图所示。同时写入的地址和写入的数据被引出,方便使用。
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 位 从设备接口。
可以看到,只有两个 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(主机/从机),需要对地址进行映射。
需要对每一个主机会映射的从机进行地址分配,如硬核PS的AXI主机接口需要连接MCU_2_FPGA的IP,那么就对这哥IP进行地址分配。axi_lite_wrddr需要映射到PS的HP接口,则对HP进行地址分配。
但是需要注意的是,两个IP不要对重复地址段进行写数据,因为这样会造成数据混乱。
测试程序说明
main文件代码如下:
#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 ; }
u32 ak5394_m1[ 60 ] attribute ( ( section ( “.ak5394M1Section” ) ) ) ; u32 ak5394_m2[ 60 ] attribute ( ( section ( “.ak5394M2Section” ) ) ) ;
void pl_update_ddr ( u32 addr, int offset, _Bool display) { 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 ) ;
}
} 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+ i4 ) ; mcu2fpga1_write ( 0 , ad+ i4 + 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) ) ; Xil_DCacheInvalidateRange ( 0x020000F0 , sizeof ( ak5394_m2) ) ; for ( u32 j= 0 ; j< 30 ; j++ ) { 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__
可以实现给数组分配指定地址空间。
文件编写如下
_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 ;
MEMORY { ps7_ddr_0 : ORIGIN = 0x100000 , LENGTH = 0x1f00000 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 }
ENTRY ( _vector_table)
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 ) ;
. 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
串口显示验证