參考文檔
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為絕對主控。
以上。