閱讀352 返回首頁    go 阿裏雲 go 技術社區[雲棲]


【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

  上一篇:go java中float,double利用BigDecimal運算
  下一篇:go Android: html in strings.xml