http://blog.csdn.net/kkk584520/article/details/9290069原文地址
下面讲一个例子,来加深对上面介绍内容的理解。笔者使用的软件版本为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代码见我的博客文章http://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看到了相关的帖子才明白这一点,在此感谢论坛上国内外的技术大牛为社区提供的支持。