ZYNQ MP AXI datamover IP使用流程说明
参考文档
https://zhuanlan.zhihu.com/p/82509188
pg022_axi_datamover.pdf
前言
很久没更新FPGA相关的东西,忙TI平台的东西去了。
本来打算使用vivado2020进行demo开发,但发现vitis编译个helloworld工程都慢得要命(估计里面又加了许多没啥用处的东西),还是vivado2018保平安。
ZYNQ PL跟PS之间的数据交互主要通过AXI内部总线进行交互。
分从机主机、高速低速等。根据不同的需要使用不同的交互接口。
这里主要讲述AXI datamover IP的使用。
所以首先的问题就在于为啥使用这个IP。
IP功能:开发者通过操作AXI-stream接口操作PS端DDR。
IP交互逻辑:
对于写DDR(数据由PL端产生,通过操作AXI-stream,AXI-stream协议转换成AXI4,AXI4操作HP接口,从而写入DDR),本文讲述重点。
对于读DDR(数据通过HP接口读出到AXI4,AXI4转协议AXI-stream,PL读取AXI-stream的结果)
综上述,AXI datamover IP主要用于PL端开发者不想用复杂的AXI-4协议,偷懒使用AXI-stream所使用。此IP相当于一个协议转换的模块。毕竟,写个AXI-4协议比AXI-stream要复杂得多。
流程
这里主要以写功能为例,读出应该是类似的。项目中主要PL送数据给PS。
Example:单次写入64(数据量)*32bit(总线位宽)数据,划分了4块地址,写完单次后写入的基地址会递增,到4回退。
(1)在block design中新建IP。这里只使能了写数据通道,通道类型选择full(还有种是basic,貌似简单一点),位宽默认32bit,根据需要选择不同位宽。
Maximum Burst Size:最大突发长度,指的是AXI-4协议中的单次突发的数据个数,BTT value 小于等于4*最大突传。
Width of BTT field:指定BTT value的位宽,大于等于BTT value即可。
(2)这里取消了store forward。
The S2MM can include an optional Store and Forward block when the Enable Store
Forward parameter is enabled. Enabling this parameter ensures that transfers are not
posted to the AXI4 Write Address Channel until all of the data needed for the requested
transfer is present in the Store and Forward FIFO. 根据需要选择。
(3)连接对应信号,引出相应bus。
S_AXIS_S2MM_0:数据控制输入口
S_AXIS_S2MM_CMD_0:命令控制输入口
M_AXIS_S2MM_STS_0:状态信息回传,debug使用
这里HP接口的地址如下图所示:
(4)编写PL端控制模块。采用先发命令再发数据的模式。先后关系貌似可以交换,具体参考IP文档。
NOTE:BTT的值跟单次tvalid的数据量要一致,BTT value = data beat*data bus width/byte = 64*32/8 = 256,否则会出错。
`timescale 1ns/1ps module Control_AXI_stream ( input i_clk , input i_rst_n , output [31:0] S_AXIS_S2MM_0_tdata , output [3:0] S_AXIS_S2MM_0_tkeep , output S_AXIS_S2MM_0_tlast , input S_AXIS_S2MM_0_tready , output S_AXIS_S2MM_0_tvalid , output [71:0] S_AXIS_S2MM_CMD_0_tdata , input S_AXIS_S2MM_CMD_0_tready , output S_AXIS_S2MM_CMD_0_tvalid ); ////传输起始控制 wire w_tri_en; vio_0 inst_vio_0 ( .clk (i_clk), .probe_out0 (w_tri_en), .probe_out1 () ); reg [1:0] r_tri_en_edge = 'd0; always @(posedge i_clk) begin r_tri_en_edge <= {r_tri_en_edge[0],w_tri_en}; end ////命令注入 reg [71:0] r_S_AXIS_S2MM_CMD_0_tdata = 'd0; reg r_S_AXIS_S2MM_CMD_0_tvalid = 'd0; always @(posedge i_clk) begin if (r_tri_en_edge == 2'b01) r_S_AXIS_S2MM_CMD_0_tvalid <= 1'b1; else if (S_AXIS_S2MM_CMD_0_tready) r_S_AXIS_S2MM_CMD_0_tvalid <= 1'b0; end reg [31:0] r_addr_axi = 'd0; reg r_S_AXIS_S2MM_0_tlast = 1'b0; always @(posedge i_clk) begin if (r_S_AXIS_S2MM_0_tlast & S_AXIS_S2MM_0_tready) begin if (r_addr_axi == 'd192) r_addr_axi <= 'd0; else r_addr_axi <= r_addr_axi + 'd64; end end wire [71:0] w_S_AXIS_S2MM_CMD_0_tdata; wire [63:32] w_SADDR; wire [31:31] w_DRR; wire [30:30] w_EOF; wire [29:24] w_DSA; wire [23:23] w_Type; wire [22:0] w_BTT; assign w_SADDR = r_addr_axi; assign w_DRR = 'd0; assign w_EOF = 'd1; assign w_DSA = 'd0; assign w_Type = 'd1; assign w_BTT = 'd256; //256bytes assign w_S_AXIS_S2MM_CMD_0_tdata = { 8'd0, w_SADDR, w_DRR, w_EOF, w_DSA, w_Type, w_BTT }; reg [31:0] r_data = 'h1234; reg r_S_AXIS_S2MM_0_tvalid = 1'b0; always @(posedge i_clk) begin if (r_S_AXIS_S2MM_0_tvalid & S_AXIS_S2MM_0_tready) r_data <= r_data + 'd1; end reg [5:0] r_cnt_num = 'd0; always @(posedge i_clk) begin if (r_S_AXIS_S2MM_0_tvalid) begin if (S_AXIS_S2MM_0_tready) r_cnt_num <= r_cnt_num + 'd1; end else r_cnt_num <= 'd0; end always @(posedge i_clk) begin if ((r_cnt_num == 'd62) && S_AXIS_S2MM_0_tready) r_S_AXIS_S2MM_0_tlast <= 1'b1; else if (r_S_AXIS_S2MM_0_tlast & S_AXIS_S2MM_0_tready) r_S_AXIS_S2MM_0_tlast <= 1'b0; end always @(posedge i_clk) begin if (S_AXIS_S2MM_CMD_0_tready & r_S_AXIS_S2MM_CMD_0_tvalid) r_S_AXIS_S2MM_0_tvalid <= 1'b1; else if (r_S_AXIS_S2MM_0_tlast & S_AXIS_S2MM_0_tready) r_S_AXIS_S2MM_0_tvalid <= 1'b0; end assign S_AXIS_S2MM_0_tdata = r_data; assign S_AXIS_S2MM_0_tkeep = 4'b1111; assign S_AXIS_S2MM_0_tlast = r_S_AXIS_S2MM_0_tlast; assign S_AXIS_S2MM_0_tvalid = r_S_AXIS_S2MM_0_tvalid; assign S_AXIS_S2MM_CMD_0_tdata = w_S_AXIS_S2MM_CMD_0_tdata; assign S_AXIS_S2MM_CMD_0_tvalid = r_S_AXIS_S2MM_CMD_0_tvalid; ////debug ila_0 inst_ila_0 ( .clk (i_clk), .probe0 (S_AXIS_S2MM_0_tdata), .probe1 (S_AXIS_S2MM_0_tkeep), .probe2 (S_AXIS_S2MM_0_tlast), .probe3 (S_AXIS_S2MM_0_tready), .probe4 (S_AXIS_S2MM_0_tvalid), .probe5 (S_AXIS_S2MM_CMD_0_tdata), .probe6 (S_AXIS_S2MM_CMD_0_tready), .probe7 (S_AXIS_S2MM_CMD_0_tvalid), .probe8 (r_addr_axi), .probe9 (r_cnt_num) ); ////debug end endmodule // end the Control_AXI_stream model
(5)编译工程,导出到SDK。SDK源代码使用下述代码做简单测试,同时可以使用SDK中的memory调试界面实时显示DDR指定地址的数据值,查看是否写入。
#include <stdio.h> #include "xil_cache.h" #include "xparameters.h" #include "xparameters_ps.h" #include "xil_printf.h" #include "xil_io.h" #define DDR_BASEARDDR 0x00000000 //从设置基地址开始读取 int main() { int i=0; char A; int rev; Xil_DCacheDisable(); print("PL RW DDR TEST!\n\r"); print("Please input A to get data\n\r"); while(1){ scanf("%c",&A); if(A=='A'){ printf("start\n\r"); while(i*4<128){ rev = Xil_In32(DDR_BASEARDDR+i*4); xil_printf("the address at %x data is : %x \r\n" ,DDR_BASEARDDR+i*4, rev); ++i; } i=0; } } return 0; }
测试方法:
(1)通过VIO设置上升沿启动一次传输。PS通过串口打印相关地址对应的写入值查看是否写入。同时ILA在线debug总线信号,确认数据的一致性是否吻合。
通过SDK的memory调试界面,显示对应地址数据,查看是否写入。
综上,可以看到数据是正确的。
深度思考:
当然上述demo只是这个IP的一个简单使用,更高阶的用法根据应用场景的不同而不同。
注意DDR读写同一时刻只能一个主机,所以实际使用中需要PL跟CPU设置握手信号,避免读写错误的数据。
此IP的读写PS端DDR方式跟AXI DMA的异同?都是对PS端对应位置进行读写,但datamover的方式是CPU无需初始化DMA,PL为绝对主控。
以上。