【OpenHW參賽手記】AXI-Stream接口開發詳細流程
下麵講一個例子,來加深對上麵介紹內容的理解。筆者使用的軟件版本為ISE 14.2。
1.建立PlanAhead工程,一直到進入XPS,具體流程見官方文檔CTT[1]。
2.在XPS中,添加一個AXI-DMA模塊,配置界麵如圖1所示。
圖1 AXI-DMA模塊配置
其餘參數默認。SG模塊如果選上,那麼後麵軟件控製會相對複雜一些。這裏不選,采用Simple模式,實現較為簡單的傳輸。
3.選菜單Hardware->Createor Import Peripheral…,設計自定義IP。名稱起為my_stream_ip,自動版本為1.00a。遇到Bus Interface選擇AXI4-Stream類型,一直點下一步到最後結束。該類型IP的生成過程比AXI4-Lite和AXI4都要簡單。
4.添加一個my_stream_ip到係統中,連接見圖2。
圖2 AXI Stream IP硬件連接
由XPS自動生成的my_stream_ip實現了先接收8個32bit字,然後求和,再將結果發送回去(連續發送8次)。上圖連接方式說明是AXI-DMA模塊發送數據給my_stream_ip,然後my_stream_ip又將結果發回AXI-DMA。同時看到AXI-DMA和PS的數據流連接是通過HP0傳輸,而控製流通過GP0傳輸。
5.上麵連接在不做任何改動的情況下有問題(主要是XPS的bug),需要一項項手動修改。首先是HP0的地址區間報錯,可以先點Zynq標簽,然後單擊HP0綠線,在彈出的配置對話框中將HP0的地址區間改為我們ZED Board 上DDR2區間0x00000000~0x1FFFFFFF,像圖3一樣。
圖3 修正bug1
在較高版本軟件ISE14.5中,這個bug已經修複,不需要改。
第二個bug就是AXI-DMA和my_stream_ip的連線問題。本來都是Stream 接口,按理說是標準接口,不應該有差異。但事實就是這樣,XPS界麵掩飾的問題層出不窮。我們右擊my_stream_ip,選擇View MPD,將內容改為:
BEGIN my_stream_ip ## Peripheral Options OPTION IPTYPE = PERIPHERAL OPTION IMP_NETLIST = TRUE OPTION HDL = VERILOG ## Bus Interfaces BUS_INTERFACE BUS=M_AXIS, BUS_STD=AXIS, BUS_TYPE=INITIATOR BUS_INTERFACE BUS=S_AXIS, BUS_STD=AXIS, BUS_TYPE=TARGET ## Parameters PARAMETER C_S_AXIS_PROTOCOL = GENERIC, DT = string, TYPE = NON_HDL, ASSIGNMENT= CONSTANT, BUS = S_AXIS PARAMETER C_S_AXIS_TDATA_WIDTH = 32, DT = integer, TYPE = NON_HDL, ASSIGNMENT =CONSTANT, BUS = S_AXIS PARAMETER C_M_AXIS_PROTOCOL = GENERIC, DT = string, TYPE = NON_HDL, ASSIGNMENT= CONSTANT, BUS = M_AXIS PARAMETER C_M_AXIS_TDATA_WIDTH = 32, DT = integer, TYPE = NON_HDL, ASSIGNMENT =CONSTANT, BUS = M_AXIS ## Peripheral ports PORT ACLK = "", DIR=I, SIGIS=CLK, BUS=M_AXIS:S_AXIS PORT ARESETN = ARESETN, DIR=I, INITIALVAL = VCC PORT S_AXIS_TREADY = TREADY, DIR=O, BUS=S_AXIS PORT S_AXIS_TDATA = TDATA, DIR=I, VEC=[31:0], BUS=S_AXIS PORT S_AXIS_TLAST = TLAST, DIR=I, BUS=S_AXIS PORT S_AXIS_TVALID = TVALID, DIR=I, BUS=S_AXIS PORT M_AXIS_TVALID = TVALID, DIR=O, BUS=M_AXIS PORT M_AXIS_TDATA = TDATA, DIR=O, VEC=[31:0], BUS=M_AXIS PORT M_AXIS_TLAST = TLAST, DIR=O, BUS=M_AXIS PORT M_AXIS_TREADY = TREADY, DIR=I, BUS=M_AXIS PORT M_AXIS_TKEEP = TKEEP, DIR=O, VEC=[3:0], BUS=M_AXIS END
這裏存在兩個問題:一個是ARESETN,在連接時AXI-DMA上沒有合適的引腳與之相連,默認接地。這裏顯式聲明接VCC。另一個問題是TKEEP信號,在我的博客文章《AXI-Stream調試日記(三)》裏說過了,這裏加上這個引腳,才能準確地將數據發回AXI-DMA。
保存MPD文件,關閉。再次右擊my_stream_ip,選擇Browse HDL Sources,打開my_stream_ip.v(或my_stream_ip.vhd),添加TKEEP信號並設置TLAST信號。
module my_stream_ip ( ACLK, ARESETN, S_AXIS_TREADY, S_AXIS_TDATA, S_AXIS_TLAST, S_AXIS_TVALID, M_AXIS_TVALID, M_AXIS_TDATA, M_AXIS_TLAST, M_AXIS_TREADY, M_AXIS_TKEEP ); input ACLK; input ARESETN; output S_AXIS_TREADY; input [31 :0] S_AXIS_TDATA; input S_AXIS_TLAST; input S_AXIS_TVALID; output M_AXIS_TVALID; output [31 :0] M_AXIS_TDATA; output M_AXIS_TLAST; input M_AXIS_TREADY; output [3:0] M_AXIS_TKEEP; localparamNUMBER_OF_INPUT_WORDS = 8; localparamNUMBER_OF_OUTPUT_WORDS = 8; localparam Idle =3'b100; localparam Read_Inputs = 3'b010; localparam Write_Outputs = 3'b001; reg [2:0] state; reg [31:0] sum; reg [NUMBER_OF_INPUT_WORDS -1:0] nr_of_reads; reg [NUMBER_OF_OUTPUT_WORDS - 1:0] nr_of_writes; assign S_AXIS_TREADY =(state == Read_Inputs); assign M_AXIS_TVALID = (state == Write_Outputs); assign M_AXIS_TDATA = sum; assign M_AXIS_TLAST = (nr_of_writes == 1); assign M_AXIS_TKEEP = 4'b1111; always @(posedge ACLK) begin // process The_SW_accelerator if(!ARESETN) // Synchronous reset (active low) begin state <= Idle; nr_of_reads <= 0; nr_of_writes <=0; sum <= 0; end else case (state) Idle: if (S_AXIS_TVALID== 1) begin state <= Read_Inputs; nr_of_reads <= NUMBER_OF_INPUT_WORDS - 1; sum <= 0; end Read_Inputs: if(S_AXIS_TVALID == 1) begin sum <= sum + S_AXIS_TDATA; if (nr_of_reads == 0) begin state <= Write_Outputs; nr_of_writes <= NUMBER_OF_OUTPUT_WORDS - 1; end else nr_of_reads <= nr_of_reads - 1; end Write_Outputs: if(M_AXIS_TREADY == 1) begin if (nr_of_writes == 0) state <= Idle; else nr_of_writes <= nr_of_writes - 1; end endcase end endmodule
到這裏修正了已知的所有bug。VHDL代碼見我的博客文章https://www.eeforum.com/附件,或通過郵件聯係我獲取。完成上述更改後,點XPS菜單Project->Rescan User Repositories,實現用戶配置更新。
6.點Port標簽,引腳連接。這裏重點是將所有帶CLK字樣的都連接到PS7_FCLK_CLK0.如圖4所示。
圖4 PORT標簽信號線連接
7.點擊Addresses標簽,看看AXI-DMA是否分配了控製端口地址
圖5 地址分配
注意,如果你的axi_dma_0的地址和圖中不一樣,那麼在後麵軟件編寫時一定要修改成你的地址。
8.點Project->DesignRule Check;沒錯時,點Hardware->Generate Netlist,完成後關閉XPS。
9.在PlanAhead中完成綜合、實現、生成Bit等步驟[12]。其實上一步已經完成了綜合,所以這一步速度就會非常快。
10 導出SDK工程。建立Helloworld工程。將Helloworld.c裏麵的內容改為如下代碼。
#include <stdio.h> #include <stdlib.h> #include "platform.h" #include "xil_cache.h" //必須包含該頭文件,實現cache操作 #define sendram ((int *)0x10000000) //發送緩衝區 #define recvram ((int *)0x10001000) //接收緩衝區 #define sizeofbuffer 32 void print(char *str); #define WITH_SG 0 #define AXI_DMA_BASE 0x40400000 #define MM2S_DMACR 0 #define MM2S_DMASR 1 #if WITH_SG #define MM2S_CURDESC 2 #define MM2S_TAILDESC 4 #else #define MM2S_SA 6 #define MM2S_LENGTH 10 #endif #define S2MM_DMACR 12 #define S2MM_DMASR 13 #if WITH_SG #define S2MM_CURDESC14 #define S2MM_TAILDESC16 #else #define S2MM_DA 18 #define S2MM_LENGTH 22 #endif void debug_axi_dma_register(unsigned int *p) { printf("MM2S_DMACR = 0x%x\n",*(p+MM2S_DMACR)); printf("MM2S_DMASR = 0x%x\n",*(p+MM2S_DMASR)); #if WITH_SG printf("MM2S_CURDESC = 0x%x\n",*(p+MM2S_CURDESC)); printf("MM2S_TAILDESC = 0x%x\n",*(p+MM2S_TAILDESC)); #else printf("MM2S_SA = 0x%x\n",*(p+MM2S_SA)); printf("MM2S_LENGTH = 0x%x\n",*(p+MM2S_LENGTH)); #endif printf("S2MM_DMACR =0x%x\n",*(p+S2MM_DMACR)); printf("S2MM_DMACSR =0x%x\n",*(p+S2MM_DMASR)); #if WITH_SG printf("S2MM_CURDESC =0x%x\n",*(p+S2MM_CURDESC)); printf("S2MM_TAILDESC= 0x%x\n",*(p+S2MM_TAILDESC)); #else printf("S2MM_DA =0x%x\n",*(p+S2MM_DA)); printf("S2MM_LENGTH =0x%x\n",*(p+S2MM_LENGTH)); #endif } void init_axi_dma_simple(unsigned int * p) { *(p+MM2S_DMACR) = 0x04; //reset send axi dma while(*(p+MM2S_DMACR)&0x04); *(p+S2MM_DMACR) =0x04; //reset send axi dma while(*(p+S2MM_DMACR)&0x04); *(p+MM2S_DMACR)=1; while((*(p+MM2S_DMASR)&0x01)); *(p+S2MM_DMACR)=1; while((*(p+S2MM_DMASR)&0x01)); *(p+MM2S_SA) = (unsigned int )sendram; *(p+S2MM_DA) =(unsigned int )recvram; Xil_DCacheFlushRange((u32)sendram,sizeofbuffer); //將cache內容同步到DDR2 *(p+S2MM_LENGTH) =sizeofbuffer;//sizeof(recvram); *(p+MM2S_LENGTH) = sizeofbuffer;//sizeof(sendram); while(!(*(p+MM2S_DMASR)&0x1000)); //wait for send ok } void init_sendbuffer() { int i; for(i=0;i< sizeofbuffer/4;i++) { sendram[i]=i*2; } } void show_recvbuffer() { int i; printf("Recv contents are:\n"); for(i=0;i< sizeofbuffer/4;i++) { printf("%d\t",recvram[i]); } printf("\r\n"); } void show_sendbuffer() { int i; printf("Send contents are:\n"); for(i=0;i< sizeofbuffer/4;i++) { printf("%d\t",sendram[i]); } printf("\r\n"); } int main() { unsigned int status=0; int rxlen; init_platform(); init_sendbuffer(); init_axi_dma_simple((unsignedint *)AXI_DMA_BASE); printf("Hello World\n\rPlease input data:"); while(1) { scanf("%x",&status); printf("Got 0x%x\n",status); debug_axi_dma_register((unsigned int *)AXI_DMA_BASE); if(status==0) { break; } } show_sendbuffer(); Xil_DCacheInvalidateRange((u32)recvram,sizeofbuffer); //將DDR2內容同步到cache show_recvbuffer(); cleanup_platform(); return 0; }
保存,等待生成elf。然後連接板子,下載bit文件,Run App,打開串口終端,等待輸出。由圖6可見結果正確。
圖6 程序輸出
最終實現的my_stream_ip對外接口如下圖所示。其中“M_AXIS”開頭的信號線表示為AXI_Stream主機信號線,而“S_AXIS”開頭的信號線表示為AXI_Stream從機信號線。自動生成的代碼中沒有M_AXIS_TKEEP信號,根據AXI4_Stream協議,這會導致該模塊作為主機時發送的數據一直處於無效狀態,影響數據傳輸。我們在my_stream_ip中添加了該信號,並使之有效,從而能夠獲得正確的處理數據。
圖7 my_stream_ip對外接口
其中 Xil_DCacheFlushRange()和Xil_DCacheInvalidateRange()兩個函數均在"xil_cache.h"中聲明,用於將cache內容同步到DDR2或相反的操作。之前由於不了解cache,導致程序一直得不到正確的結果,總是懷疑硬件問題,後來通過forums.xilinx.com看到了相關的帖子才明白這一點,在此感謝論壇上國內外的技術大牛為社區提供的支持。
最後更新:2017-04-03 16:48:33
上一篇:
java中float,double利用BigDecimal運算
下一篇:
Android: html in strings.xml
Mybatis中傳參包There is no getter for property named 'XXX' in 'class java.lang.String'
大公司最喜歡問的Java集合類麵試題
《 Java並發編程從入門到精通》第5章 多線程之間交互:線程閥
Java秘術:用枚舉構建一個狀態機
mvn dependency的兩個命令
使用hibernate自動在MYSQL中創建表,極其簡單,改下配置文件。
intent flags標記
基於Android手機的聲波通信源代碼
梭子魚為其Web安全網關增加萬兆接口
java語言的演化——讀JavaOne ppt筆記