当前位置:   article > 正文

ZYNQ7000 PL与PS交互(三): AXI_Stream协议配合AXI HP接口、AXI_DMA进行内存映射到流数据转换_pl与ps通过dma交互时如何将pl段的数据变成stream流

pl与ps通过dma交互时如何将pl段的数据变成stream流

一、AXI_Stream协议

AXI4-Stream在AXI4家族中相对来说比较简单,本文主要回答两个问题:

(1)AXI4-Stream 传输的数据流都包含什么?

(2)AXI4-Stream 的接口信号有哪些?master和slave是如何握手的?

1.1 AXI_Stream协议简介

  1. 数据流

    AXI4-Stream传输的数据流包含三种类型:data type、position type、null type。

    • data type是最有意义的数据;
    • position type 作为占位符使用,可以用来表征date type 的相对位置;
    • null type不包含任何有用的信息。

    数据流的结构可以有很多种,例如:可以只传数据,也即都是data type,不包含position type和null type;也可以将data type 和 null type 混着传输;还可以将position type 和 data type混着传输。当然三者混着传输也没有问题。

    全都是data type

    data type 和 position type 混搭

    那么问题来了,数据流传输类型有三种,在传输的过程中如何分辨这三种类型呢?AXI4-Stream的接口信号可以帮助我们进行区分,接下来看看都有哪些接口信号。

  2. 接口信号

    先来一图尽览:

    ACLK和ARESETn信号,不必多说,时钟和复位信号;

    接下来是TVALID和TREADY信号,这两个信号作为握手信号,分别从master 和 slave发出。如何握手呢?想想一下,你和别人握手的过程,大概分为三个阶段:双方心里进行某种心理活动、伸手、握手上下抖动;AXI4-Stream上握手也大体经历这三个过程。

    (1)双方心里进行某种心理活动:相当于 master 和 slave 在自己内部搞事情,搞完自己的事情才能闲下来去握手,master搞完事情发出 TVALID high 信号,slave 搞完事情发出TREADY high 信号。

    (2)伸手的过程其实比较讲究,现实生活中也是。你和老板握手,可能老板先伸手、也可能你先伸手、也可能你俩同时伸手。master和slave也是如此,可能 TVALID high 信号比TREADY high 早,可能TVALID比TREADY晚,也可能同时出现。

    (3)握手上下抖动:当你们两个都抬起了手,接下来就是隆重的握手阶段,在握手的时候,你可以感受到老板的手那么有力量,那么厚重…master和slave在TVALID 和 TREADY同为高时开始传输数据。

    TVALID 先高为敬

    TREADY 先高为敬

    TVALID TREADY 同时变高

    TDATA不多说了,它就是数据流。前文中提到在数据流中有三种类型,分别为data type、position type和null type,那么在传输的时候如何区分呢?要靠TKEEP和TSTRB。

    为了便于说明,假设n为数据总线的字节数,即TDATA的字节数 ,可以把TDATA表示为TDATA[(8*n-1):0],TKEEP和TSTRB有n位,和TDATA上的每个字节一一对应,​**二者共同表示TDATA上每个字节的类型**。举个例子,n = 2、TDATA = 0x0036、TKEEP = 2’b01、 TSTRB= 2’b01。由于TKEEP和TSTRB高位为0,那么TDATA的高阶字节为null type;由于TKEEP和TSTRB地位为1,那么TDATA的低阶字节为data type。

    有一点需要注意,TKEEP为0、TSTRB为1的情况是不能使用的。

    TKEEP 和 TSTRB 表示数据流中字节类型

    TLAST信号,用来表示一个包的结尾。例如发送大小为32字节的包,在发送第32个字节的时候,可以把TLAST信号拉高,来表示这个包发送完了。

    TID和TDEST信号:当我们在同一个接口传输不同数据流时有用,一般来讲,AXIS4-Stream Interconnect Core IP可以帮助我们完成这个过程。TUSER信号用来传输一些额外的信息。

  3. 参考

    [1] ug1037-vivado-axi-reference-guide.pdf

    [2] IHI0051A_amba4_axi4_stream_v1_0_protocol_spec.pdf

1.2 AXI_Stream master官方代码

使用Vivaod自带的IP封装工具,生成带AXI_Stream master接口的源码,如下:
M_AXIS_TSTRB接口信号可以等同于M_AXIS_TKEEP信号使用。

`timescale 1 ns / 1 ps

	module swp_rxfifo_write #
	(
		// Users to add parameters here

		// User parameters ends
		// Do not modify the parameters beyond this line
	
		// Width of S_AXIS address bus. The slave accepts the read and write addresses of width C_M_AXIS_TDATA_WIDTH.
		// S_AXIS 地址总线宽度
		parameter integer C_M_AXIS_TDATA_WIDTH	= 32,
		// Start count is the number of clock cycles the master will wait before initiating/issuing any transaction.
		// Master 在启动 transaction 前等待的时钟周期
		parameter integer C_M_START_COUNT	= 32
	)
	(
		// Users to add ports here

		// User ports ends
		// Do not modify the ports beyond this line

		// Global ports
		input wire  M_AXIS_ACLK,
		// 
		input wire  M_AXIS_ARESETN,
		// Master Stream Ports. TVALID indicates that the master is driving a valid transfer, A transfer takes place when both TVALID and TREADY are asserted. 
		output wire  M_AXIS_TVALID,   // TVALID 指示 Master 准备好了
		// TDATA is the primary payload that is used to provide the data that is passing across the interface from the master.
		output wire [C_M_AXIS_TDATA_WIDTH-1 : 0] M_AXIS_TDATA,    // TDATA 有效的数据
		// TSTRB is the byte qualifier that indicates whether the content of the associated byte of TDATA is processed as a data byte or a position byte.
		output wire [(C_M_AXIS_TDATA_WIDTH/8)-1 : 0] M_AXIS_TSTRB,    // TSTRB 判断作为数据还是地址
		// TLAST indicates the boundary of a packet.
		output wire  M_AXIS_TLAST,   // TLAST 指示 packet 的边界
		// TREADY indicates that the slave can accept a transfer in the current cycle.
		input wire  M_AXIS_TREADY    // TREADY 指示 Slave 准备好了
	);
	// Total number of output data   
	// 总输出数据的数量 (与 FIFO 的大小有关)                                              
	localparam NUMBER_OF_OUTPUT_WORDS = 128;                                               
	                                                                                     
	// function called clogb2 that returns an integer which has the                      
	// value of the ceiling of the log base 2.         
	// 函数clogb2,返回以2为底的对数上限整数(也就是说输入参数是几bit)                                  
	function integer clogb2 (input integer bit_depth);                                   
	  begin                                                                              
	    for(clogb2=0; bit_depth>0; clogb2=clogb2+1)                                      
	      bit_depth = bit_depth >> 1;                                                    
	  end                                                                                
	endfunction                                                                          
	                                                                                     
	// WAIT_COUNT_BITS is the width of the wait counter.  
	// 等待计数器的宽度                                 
	localparam integer WAIT_COUNT_BITS = clogb2(C_M_START_COUNT-1);                      
	                                                                                     
	// bit_num gives the minimum number of bits needed to address 'depth' size of FIFO.  
	// bit_num 给出了处理FIFO的“深度”大小所需的最小位数
	localparam bit_num  = clogb2(NUMBER_OF_OUTPUT_WORDS);                                
	                                                                                     
	// Define the states of state machine                                                
	// The control state machine oversees the writing of input streaming data to the FIFO,
	// and outputs the streaming data from the FIFO        
	// 状态机的状态定义                                                
	// 控制状态机监督输入流数据写入FIFO,并从FIFO输出流数据                              
	parameter [1:0] IDLE = 2'b00,        // 初始化状态  This is the initial/idle state               
	                                                                                     
	                INIT_COUNTER  = 2'b01,  初始化计数状态,当计数到 C_M_START_COUNT 时转换状态到 SEND_STREAM
									    //This state initializes the counter, once   
	                                	// the counter reaches C_M_START_COUNT count,        
	                                	// the state machine changes state to SEND_STREAM     
	                SEND_STREAM   = 2'b10; // 通过 M_AXIS_TDATA 输出数据流
										 // In this state the                          
	                                     // stream data is output through M_AXIS_TDATA   
	// // 状态寄存器 State variable                                                                    
	reg [1:0] mst_exec_state;                                                            
	// Example design FIFO read pointer       
	// FIFO 读指针                                            
	reg [bit_num-1:0] read_pointer;                                                      

	// AXI Stream internal signals
	//wait counter. The master waits for the user defined number of clock cycles before initiating a transfer.
	reg [WAIT_COUNT_BITS-1 : 0] 	count;
	//streaming data valid
	wire  	axis_tvalid;
	//streaming data valid delayed by one clock cycle
	reg  	axis_tvalid_delay;
	//Last of the streaming data 
	wire  	axis_tlast;
	//Last of the streaming data delayed by one clock cycle
	reg  	axis_tlast_delay;
	//FIFO implementation signals
	reg [C_M_AXIS_TDATA_WIDTH-1 : 0] 	stream_data_out;
	wire  	tx_en;
	//The master has issued all the streaming data stored in FIFO
	reg  	tx_done;


	// I/O Connections assignments

	assign M_AXIS_TVALID	= axis_tvalid_delay;
	assign M_AXIS_TDATA		= stream_data_out;
	assign M_AXIS_TLAST		= axis_tlast_delay;
	assign M_AXIS_TSTRB		= {(C_M_AXIS_TDATA_WIDTH/8){1'b1}};


	// Control state machine implementation                             
	always @(posedge M_AXIS_ACLK)                                             
	begin                                                                     
	  if (!M_AXIS_ARESETN)                                                    
	  // Synchronous reset (active low)                                       
	    begin                                                                 
	      mst_exec_state <= IDLE;                                             
	      count    <= 0;                                                      
	    end                                                                   
	  else                                                          
	    case (mst_exec_state)                                                 
	      IDLE:                                                               
	        // The slave starts accepting tdata when                          
	        // there tvalid is asserted to mark the                           
	        // presence of valid streaming data                               
	        //if ( count == 0 )                                                 
	        //  begin                                                           
	            mst_exec_state  <= INIT_COUNTER;                              
	        //  end                                                             
	        //else                                                              
	        //  begin                                                           
	        //    mst_exec_state  <= IDLE;                                      
	        //  end                                                             
	                                                                          
	      INIT_COUNTER:                                                       
	        // The slave starts accepting tdata when                          
	        // there tvalid is asserted to mark the                           
	        // presence of valid streaming data                               
	        if ( count == C_M_START_COUNT - 1 )                               
	          begin                                                           
	            mst_exec_state  <= SEND_STREAM;         // 计数够时间后跳转到下一个状态 SEND_STREAM                               
	          end                                                             
	        else                                                              
	          begin       
				                                             
	            count <= count + 1;                                           
	            mst_exec_state  <= INIT_COUNTER;                              
	          end                                                             
	                                                                          
	      SEND_STREAM:                                                        
	        // The example design streaming master functionality starts       
	        // when the master drives output tdata from the FIFO and the slave
	        // has finished storing the S_AXIS_TDATA                          
	        if (tx_done)                                                      
	          begin                                                           
	            mst_exec_state <= IDLE;     //发送完成后回到 IDLE 状态                                  
	          end                                                             
	        else                                                              
	          begin                                                           
	            mst_exec_state <= SEND_STREAM;                                
	          end                                                             
	    endcase                                                             
	end                                                                       


	//tvalid generation
	//axis_tvalid is asserted when the control state machine's state is SEND_STREAM and
	//number of output streaming data is less than the NUMBER_OF_OUTPUT_WORDS.
	assign axis_tvalid = ((mst_exec_state == SEND_STREAM) && (read_pointer < NUMBER_OF_OUTPUT_WORDS));
	                                                                                               
	// AXI tlast generation                                                                        
	// axis_tlast is asserted number of output streaming data is NUMBER_OF_OUTPUT_WORDS-1          
	// (0 to NUMBER_OF_OUTPUT_WORDS-1)                                                             
	assign axis_tlast = (read_pointer == NUMBER_OF_OUTPUT_WORDS-1);                                
	                                                                                               
	                                                                                               
	// Delay the axis_tvalid and axis_tlast signal by one clock cycle                              
	// to match the latency of M_AXIS_TDATA                                                        
	always @(posedge M_AXIS_ACLK)                                                                  
	begin                                                                                          
	  if (!M_AXIS_ARESETN)                                                                         
	    begin                                                                                      
	      axis_tvalid_delay <= 1'b0;                                                               
	      axis_tlast_delay <= 1'b0;                                                                
	    end                                                                                        
	  else                                                                                         
	    begin                                                                                      
	      axis_tvalid_delay <= axis_tvalid;                                                        
	      axis_tlast_delay <= axis_tlast;                                                          
	    end                                                                                        
	end                                                                                            


	//read_pointer pointer

	always@(posedge M_AXIS_ACLK)                                               
	begin                                                                            
	  if(!M_AXIS_ARESETN)                                                            
	    begin                                                                        
	      read_pointer <= 0;                                                         
	      tx_done <= 1'b0;                                                           
	    end                                                                          
	  else                                                                           
	    if (read_pointer <= NUMBER_OF_OUTPUT_WORDS-1)                                
	      begin                                                                      
	        if (tx_en)                                                               
	          // read pointer is incremented after every read from the FIFO          
	          // when FIFO read signal is enabled.                                   
	          begin                                                                  
	            read_pointer <= read_pointer + 1;                                    
	            tx_done <= 1'b0;                                                     
	          end                                                                    
	      end                                                                        
	    else if (read_pointer == NUMBER_OF_OUTPUT_WORDS)                             
	      begin                                                                      
	        // tx_done is asserted when NUMBER_OF_OUTPUT_WORDS numbers of streaming data
	        // has been out.                                                         
	        tx_done <= 1'b1;                                                         
	      end                                                                        
	end                                                                              


	//FIFO read enable generation 

	assign tx_en = M_AXIS_TREADY && axis_tvalid;   
	                                                     
	    // Streaming output data is read from FIFO       
	    always @( posedge M_AXIS_ACLK )                  
	    begin                                            
	      if(!M_AXIS_ARESETN)                            
	        begin                                        
	          stream_data_out <= 1;                      
	        end                                          
	      else if (tx_en)// && M_AXIS_TSTRB[byte_index]  
	        begin                                        
	          stream_data_out <= read_pointer + 32'b1;   
	        end                                          
	    end                                              

	// Add user logic here

	// User logic ends

	endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239

1.3 AXI_Stream slave官方代码

使用Vivaod自带的IP封装工具,生成带AXI_Stream slave接口的源码,如下:

`timescale 1 ns / 1 ps
 
	module axis_s_v1_0_S00_AXIS #
	(
		/*
			用户可以自定义参数
		*/
 
		//AXIS数据位宽
		parameter integer C_S_AXIS_TDATA_WIDTH	= 32
	)
	(
		/*
			用户可以在此自定义端口
		*/
 
		input wire  S_AXIS_ACLK,                    			  //时钟信号
		input wire  S_AXIS_ARESETN,               			      //复位信号
		output wire  S_AXIS_TREADY,              			      //ready信号,代表从机准备好了
		input wire [C_S_AXIS_TDATA_WIDTH-1 : 0] S_AXIS_TDATA,     //数据信号
		input wire [(C_S_AXIS_TDATA_WIDTH/8)-1 : 0] S_AXIS_TSTRB, //数据修饰符,辨别字节类型	
		input wire  S_AXIS_TLAST,								  //last信号,拉高代表是传输中的最后一个字节
		input wire  S_AXIS_TVALID                                 //ready信号,代表从机准备好了
	);
 
	//函数:以2为低求对数,用于计算位宽
	function integer clogb2 (input integer bit_depth);
	  begin
	    for(clogb2=0; bit_depth>0; clogb2=clogb2+1)
	      bit_depth = bit_depth >> 1;
	  end
	endfunction
 
	localparam NUMBER_OF_INPUT_WORDS  = 8;                      //输入数据个数
	localparam bit_num  = clogb2(NUMBER_OF_INPUT_WORDS-1);      //输入数据的位宽
 
	//状态机定义
	parameter [1:0] IDLE = 1'b0,                                //初始状态
	                WRITE_FIFO  = 1'b1;                         //读状态
 
	wire  	axis_tready;                                        //ready信号
 
	reg mst_exec_state;                                         //状态寄存器
 
	genvar byte_index;                                          //字节索引
 
	wire fifo_wren;                                             //FIFO写使能
	reg fifo_full_flag;                                         //FIFO满标志
	reg [bit_num-1:0] write_pointer;                            //FIFO写指针
	reg writes_done;                                            //写满标志
 
	assign S_AXIS_TREADY	= axis_tready;                    
 
	//状态机
	always @(posedge S_AXIS_ACLK) 
	begin  
	  if (!S_AXIS_ARESETN) 
	    begin
	      mst_exec_state <= IDLE;
	    end  
	  else
	    case (mst_exec_state)
	      IDLE: 
	          if (S_AXIS_TVALID)
	            begin
	              mst_exec_state <= WRITE_FIFO;
	            end
	          else
	            begin
	              mst_exec_state <= IDLE;
	            end
	      WRITE_FIFO: 
	        if (writes_done)
	          begin
	            mst_exec_state <= IDLE;
	          end
	        else
	          begin
	            mst_exec_state <= WRITE_FIFO;
	          end
 
	    endcase
	end
 
	//ready信号赋值,写状态+读指针写于等于接收数据总个数
	assign axis_tready = ((mst_exec_state == WRITE_FIFO) && (write_pointer <= NUMBER_OF_INPUT_WORDS-1));
 
	//写指针,写完成信号
	always@(posedge S_AXIS_ACLK)
	begin
	  if(!S_AXIS_ARESETN)
	    begin
	      write_pointer <= 0;
	      writes_done <= 1'b0;
	    end  
	  else
	    if (write_pointer <= NUMBER_OF_INPUT_WORDS-1)
	      begin
	        if (fifo_wren)
	          begin
	            write_pointer <= write_pointer + 1;
	            writes_done <= 1'b0;
	          end
	          if ((write_pointer == NUMBER_OF_INPUT_WORDS-1)|| S_AXIS_TLAST)
	            begin
	              writes_done <= 1'b1;
	            end
	      end  
	end
	//FIFO写使能信号
	assign fifo_wren = S_AXIS_TVALID && axis_tready;
	//例化4个宽为8,深度为8的二维数组stream_data_fifo,用来充当FIFO,每个FIFO依次写入数据的0-7;8-15;16-23;24-31位
	generate 
	  for(byte_index=0; byte_index<= (C_S_AXIS_TDATA_WIDTH/8-1); byte_index=byte_index+1)
	  begin:FIFO_GEN
	    reg  [(C_S_AXIS_TDATA_WIDTH/4)-1:0] stream_data_fifo [0 : NUMBER_OF_INPUT_WORDS-1];
		//写入FIFO数据
	    always @( posedge S_AXIS_ACLK )
	    begin
	      if (fifo_wren)// && S_AXIS_TSTRB[byte_index])
	        begin
	          stream_data_fifo[write_pointer] <= S_AXIS_TDATA[(byte_index*8+7) -: 8];
	        end  
	    end  
	  end		
	endgenerate
	/* 
		实现用户逻辑
	*/
	endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130

1.4 仿真测试

1.4.1 top

module top(
    input clk,
    input rst_n
    );
 
    wire [31:0] axis_tdata;
    wire [3:0]  axis_tstrb;
    wire        axis_tlast;
    wire        axis_tready;
    wire        axis_tvalid;
 
    axis_m_0 axis_m_u0
    (
     .m00_axis_tdata      (axis_tdata),
     .m00_axis_tstrb      (axis_tstrb), 
     .m00_axis_tlast      (axis_tlast),
     .m00_axis_tvalid     (axis_tvalid),
     .m00_axis_tready     (axis_tready),
     .m00_axis_aclk       (clk),
     .m00_axis_aresetn    (rst_n)
    );
 
    axis_s_0 axis_s_u0
    (
     .s00_axis_tdata      (axis_tdata),
     .s00_axis_tstrb      (axis_tstrb), 
     .s00_axis_tlast      (axis_tlast),
     .s00_axis_tvalid     (axis_tvalid),
     .s00_axis_tready     (axis_tready),
     .s00_axis_aclk       (clk),
     .s00_axis_aresetn    (rst_n)
    );
 
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

1.4.2 tb

`timescale 1ns / 1ps
 
module tb_top();
    reg clk,rst_n;
 
initial begin
    clk = 0;
    rst_n = 1;
#10
    rst_n = 0;
#10
    rst_n = 1;
end
 
always #5 clk <= ~clk;
 
top top_u1(.clk(clk),
           .rst_n(rst_n));
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

endmodule

1.4.3 结果波形

二、AXI_DMA IP

DMA 是一种快速的数据传送方式,通常用来传送数据量较多的数据块,它允许某些计算机内部的硬件子系统可以独立地直接读写系统内存,而不需中央处理器(CPU)介入处理。
PL 的 DMA 和 AXI_HP 接口的传输适用于大块数据的高性能传输,带宽高。该种传输方式的拓扑图如下(灰色填充的框图或红色边框圈出的框图)。
在这里插入图片描述

可以看到 DMA 的数据传输经S_AXI_HP 接口(以下简称 HP 接口)。ZYNQ 拥有 4 个 HP 接口,提供了 ZYNQ 内最大的总带宽。每一个 HP 接口都包含控制和数据 FIFO。这些 FIFO 为大数据量突发传输提供缓冲,让 HP接口成为理想的高速数据传输接口。对DMA的控制或配置通过M_AXI_GP接口,传输状态通过中断传达到 PS 的中断控制器。下面我们简单的介绍下 PL的DMA,即AXI DMA IP核。

2.1 AXI_DMA模块框图

AXI Direct Memory Access(AXI DMA)IP 内核在 AXI4 内存映射和 AXI4-Stream IP 接口之间提供高带宽直接储存访问。其可选的 scatter gather 功能还可以从基于处理器的系统中的中央处理单元(CPU)卸载数据移动任务。初始化、状态和管理寄存器通过 AXI4-Lite 从接口访问。核心的功能组成如下图所示:
在这里插入图片描述
AXI DMA 用到了三种总线,AXI4-Lite 用于对寄存器进行配置,AXI4 Memory Map 用于与内存交互,又分为 AXI4 Memory Map Read 和 AXI4 Memory Map Write 两个接口,一个是读一个是写。AXI4 Stream 接口用于对外设的读写,其中 AXI4 Stream Master(MM2S,Memory Map to Stream)用于对外设写,AXI4-Stream Slave(S2MM,Stream to Memory Map)用于对外设读。总之,在以后的使用中需要知道 AXI_MM2S 和AXI_S2MM 是存储器端映射的 AXI4 总线,提供对存储器(DDR3)的访问。AXIS_MM2S 和 AXIS_S2MM
是 AXI4-streaming 总线,可以发送和接收连续的数据流,无需地址。

2.2 工作模式

AXI DMA 提供 3 种模式,分别是 Direct Register 模式、Scatter/Gather 模式和 Cyclic DMA 模式,这里我
们简单的介绍下常用的 Direct Register 模式。
Direct Register DMA 模式也就是 Simple DMA。Direct Register 模式提供了一种配置,用于在 MM2S 和S2MM 通道上执行简单的 DMA 传输,这需要更少的 FPGA 资源。Simple DMA 允许应用程序在 DMA 和Device 之间定义单个事务。它有两个通道:一个从 DMA 到 Device,另一个从 Device 到 DMA。应用程序必须设置缓冲区地址和长度字段以启动相应通道中的传输。

2.3 寄存器

在这里插入图片描述
在这里插入图片描述

2.4 编程顺序

直接寄存器模式(散点聚集引擎已被禁用)提供了一种配置,用于在MM2S和S2MM通道上执行简单的DMA传输,这需要更少的FPGA资源利用率。通过访问DMACR、源地址或目标地址和长度寄存器来启动传输。当传输完成时,一个DMASR.IOC_Irq为关联的通道断言,如果启用,将产生一个中断输出。
MM2S通道的DMA操作按如下顺序设置并启动:

  1. 通过将运行/停止位设置为1来启动MM2S通道的运行(MM2S_DMACR.RS = 1)。停止位(DMASR.Halted)应该取消断言,指示MM2S通道正在运行。
  2. 如果需要,可以通过将1写入MM2S_DMACR.IOC_IrqEn和MM2S_DMACR.Err_IrqEn来启用中断。当AXI DMA配置为直接寄存器模式时,不使用延迟中断、延迟计数和阈值计数。
  3. 向MM2S_SA寄存器写入一个有效的源地址。如果将AXI DMA配置为大于32的地址空间,则对MM2S_SA MSB寄存器进行编程。如果AXI DMA没有为数据重新对齐而配置,则必须对齐一个有效的地址,或出现未定义的结果。被认为对齐或未对齐的是基于流数据宽度。当AXI_DMA在微模式中配置时,您有责任指定正确的地址。微DMA不考虑4K边界。
    例如,如果内存映射数据宽度= 32,如果数据位于字偏移(32位偏移),则为0x0、0x4、0x8、0xC,等等。如果启用了DRE,并且流数据宽度为< 128,那么源地址可以是任意字节偏移量。
  4. 在MM2S_LENGTH寄存器中写入要传输的字节数。写入的值为零没有影响。非零值会导致在MM2S AXI4接口上读取MM2S_LENGTH字节数并传输出MM2S。

S2MM通道的DMA操作按以下顺序启动:

  1. 通过将运行/停止位设置为1来启动S2MM通道的运行(S2MM_DMACR.RS = 1)。停止位(DMASR.Halted)应该取消断言,指示S2MM通道正在运行。
  2. 如果需要,可以通过将1写入S2MM_DMACR.IOC_IrqEn和S2MM_DMACR.Err_IrqEn来启用中断。当AXI DMA配置为直接寄存器模式时,不使用延迟中断、延迟计数和阈值计数。
  3. 向S2MM_DA寄存器写入一个有效的目标地址。如果将AXI DMA配置为地址空间大于32,则对S2MM_DA MSB寄存器进行编程。
  4. 如果没有为数据重新对齐配置AXI DMA,则必须对齐一个有效的地址,否则就会出现未定义的结果。被认为对齐或未对齐的是基于流数据宽度。
    例如,如果内存映射数据宽度=32,则如果数据位于字偏移(32位偏移),即0x0、0x4、0x4、0x8、0xC等等。如果启用了DRE和流媒体数据宽度< 128,那么目标地址可以是任何字节的偏移量。

在SDK软件编程中,可以直接调用XIlinx封装好的发送/读取函数来直接进行数据传输,无需自己写驱动。

三、AXI_Stream Data FIFO

带AXI Stream接口的FIFO,同样可以提供将满、将空标志信号。

FIFO模块能够为AXI4-Stream数据流提供临时存储(缓冲区),多用于以下两种情况:

  • 需要比寄存器更多的缓存单元

  • 存储和转发:主机上积累一定数量的字节后,再转发给从机(包模式)。

在IP catalog搜索,AXI4-STREAM DATA FIFO,双击出现其配置界面:
在这里插入图片描述
首先我们知道的是,在AXI协议中,数据通过写通道实现master到slave的传输,读通道实现slave到master的传输。因此,在FIFO IP核中,接收数据的端口S_AXIS用来将数据写入IP核,而发送数据的端口M_AXIS用来将数据读出IP核。

四、Vivado硬件工程建立(AXI DMA回环实验)

在实际应用中,DMA 一般与产生数据或需求数据的 IP 核相连接,该 IP 核可以是带有 Stream 接口的高速的 AD(模拟转数字)或 DA(数字转模拟) IP 核。不失一般性,在本次实验中,我们使用 AXI4 Stream Data FIFO IP 核来充当这类 IP 进行 DMA回环测试实验。
在这里插入图片描述

PS 开启 HP0 和 GP0 接口。AXI DMA 和 AXI4 Stream Data FIFO 在 PL 中实现。处理器通过 M_AXI_GP0接口与 AXI DMA 通信,以设置、启动和监控数据传输。数据传输通过 S_AXI_HP0 接口。AXI DMA 通过S_AXI_HP0 接口从 DDR3 中读取数据后发送给 AXI4 Stream Data FIFO,这种情况下 AXI4 Stream Data FIFO可以相当于带有 Stream 接口的高速 DA。AXI DMA 读取 AXI4 Stream Data FIFO 中的数据后通过 S_AXI_HP0接口写入 DDR3 的情形,AXI4 Stream Data FIFO 相当于带有 Stream 接口的高速 AD。

在Vivado工程中进行以下操作:

  1. 配置PL端时钟。打开PS配置页面的Clock Configuration 页面,展开 PL-Fabric Clocks,可以看到默认勾选 FCLK_CLK0,且时钟频率为 50MHz,这里我们将其修改为 100MHz。

  2. 开启HP接口。点击左侧的 PS-PL Configuration 页面,然后在右侧展开 General 下的 HP Slave AXI Interface,可以看到有 4 个 HP 接口,这里我们只用其中的 S AXI HP0 interface,DATA WIDTH 保持默认即可。

  3. 打开PL中断,DMA在传输完成后通过发送中断通知CPU,因此需要开启PL到PS的中断。点击左侧的 Interrupts 页面,勾选右侧的 Fabric interrupts 并展开,勾选 PL-PS Interrupt Ports 下的IRQ_F2P[15:0]。

  4. 添加AXI_DMA 模块。
    在这里插入图片描述

  5. 添加AXI_Stream Data FIFO 模块
    该 IP 保持默认设置即可。添加该 IP 核的重点不是了解该 IP 核如何使用,而是知道该 IP 核带有 AXI Stream 接口。在以后的实际使用中,需要封装自定义 IP 核时要注意这一点。

  6. 添加Concat IP模块
    该 IP 保持默认设置即可。添加该 IP 核的重点不是了解该 IP 核如何使用,而是知道该 IP 核带有 AXI Stream 接口。在以后的实际使用中,需要封装自定义 IP 核时要注意这一点。

  7. 自动进行连接,连接FIFO和DMA,连接中断。最终生成的BlockDesign连接图如下所示,
    在这里插入图片描述

  8. 然后依次执行“Generate Output Products”和“Create HDL Wrapper”,生成顶层HDL模块。

  9. 生成Bitstream文件并导出到SDK。

五、SDK程序设计(AXI DMA回环实验)

整体代码如下。参考《正点原子领航版ZYNQ嵌入式开发》

/***************************** Include Files *********************************/

#include "xaxidma.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xscugic.h"

/************************** Constant Definitions *****************************/

#define DMA_DEV_ID 			XPAR_AXIDMA_0_DEVICE_ID
#define RX_INTR_ID          XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
#define TX_INTR_ID          XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
#define INTC_DEVICE_ID      XPAR_SCUGIC_SINGLE_DEVICE_ID
#define DDR_BASE_ADDR       XPAR_PS7_DDR_0_S_AXI_BASEADDR   //0x00100000
#define MEM_BASE_ADDR       (DDR_BASE_ADDR + 0x1000000)     //0x01100000
#define TX_BUFFER_BASE      (MEM_BASE_ADDR + 0x00100000)    //0x01200000
#define RX_BUFFER_BASE      (MEM_BASE_ADDR + 0x00300000)    //0x01400000
#define RESET_TIMEOUT_COUNTER   10000    //复位时间
#define TEST_START_VALUE        0x0      //测试起始值
#define MAX_PKT_LEN             0x100    //发送包长度

/************************** Function Prototypes ******************************/

static int check_data(int length, u8 start_value);
static void tx_intr_handler(void *callback);
static void rx_intr_handler(void *callback);
static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
        u16 tx_intr_id, u16 rx_intr_id);
static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
        u16 rx_intr_id);

/************************** Variable Definitions *****************************/

static XAxiDma axidma;     //XAxiDma实例
static XScuGic intc;       //中断控制器的实例
volatile int tx_done;      //发送完成标志
volatile int rx_done;      //接收完成标志
volatile int error;        //传输出错标志

/************************** Function Definitions *****************************/

int main(void)
{
    int i;
    int status;
    u8 value;
    u8 *tx_buffer_ptr;
    u8 *rx_buffer_ptr;
    XAxiDma_Config *config;

    tx_buffer_ptr = (u8 *) TX_BUFFER_BASE;
    rx_buffer_ptr = (u8 *) RX_BUFFER_BASE;

    xil_printf("\r\n--- Entering main() --- \r\n");

    config = XAxiDma_LookupConfig(DMA_DEV_ID);
    if (!config) {
        xil_printf("No config found for %d\r\n", DMA_DEV_ID);
        return XST_FAILURE;
    }

    //初始化DMA引擎
    xil_printf("\r\n--- init DMA --- \r\n");
    status = XAxiDma_CfgInitialize(&axidma, config);
    if (status != XST_SUCCESS) {
        xil_printf("Initialization failed %d\r\n", status);
        return XST_FAILURE;
    }

    if (XAxiDma_HasSg(&axidma)) {
        xil_printf("Device configured as SG mode \r\n");
        return XST_FAILURE;
    }

    //建立中断系统
    xil_printf("\r\n--- init INTR --- \r\n");
    status = setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
    if (status != XST_SUCCESS) {
        xil_printf("Failed intr setup\r\n");
        return XST_FAILURE;
    }

    //初始化标志信号
    tx_done = 0;
    rx_done = 0;
    error = 0;

    value = TEST_START_VALUE;
    for (i = 0; i < MAX_PKT_LEN; i++) {
        tx_buffer_ptr[i] = value;
        value = (value + 1) & 0xFF;
    }

    Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN);   //刷新Data Cache

    //传送数据
    xil_printf("\r\n--- SEND DATA --- \r\n");
    status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) tx_buffer_ptr,
    MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }
    xil_printf("\r\n--- RECEIVE DATA --- \r\n");
    status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) rx_buffer_ptr,
    MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN);   //刷新Data Cache
    while (!tx_done && !rx_done && !error)
        ;
    //传输出错
    if (error) {
        xil_printf("Failed test transmit%s done, "
                "receive%s done\r\n", tx_done ? "" : " not",
                rx_done ? "" : " not");
        goto Done;
    }

    //传输完成,检查数据是否正确
    xil_printf("\r\n--- CHECK RECEIVE DATA --- \r\n");
    status = check_data(MAX_PKT_LEN, TEST_START_VALUE);
    if (status != XST_SUCCESS) {
        xil_printf("Data check failed\r\n");
        goto Done;
    }

    xil_printf("Successfully ran AXI DMA Loop\r\n");
    disable_intr_system(&intc, TX_INTR_ID, RX_INTR_ID);

    Done: xil_printf("--- Exiting main() --- \r\n");
    return XST_SUCCESS;
}


//检查数据缓冲区
static int check_data(int length, u8 start_value)
{
    u8 value;
    u8 *rx_packet;
    int i = 0;

    value = start_value;
    rx_packet = (u8 *) RX_BUFFER_BASE;
    for (i = 0; i < length; i++) {
    	if (rx_packet[i] != value) {
            xil_printf("Data error %d: %x/%x\r\n", i, rx_packet[i], value);
            return XST_FAILURE;
        }
        value = (value + 1) & 0xFF;
    }

    return XST_SUCCESS;
}

//DMA TX中断处理函数
static void tx_intr_handler(void *callback)
{
    int timeout;
    u32 irq_status;
    XAxiDma *axidma_inst = (XAxiDma *) callback;

    //读取待处理的中断
    irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DMA_TO_DEVICE);
    //确认待处理的中断
    XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);

    //Tx出错
    if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
        error = 1;
        XAxiDma_Reset(axidma_inst);
        timeout = RESET_TIMEOUT_COUNTER;
        while (timeout) {
            if (XAxiDma_ResetIsDone(axidma_inst))
                break;
            timeout -= 1;
        }
        return;
    }

    //Tx完成
    if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
        tx_done = 1;
}

//DMA RX中断处理函数
static void rx_intr_handler(void *callback)
{
    u32 irq_status;
    int timeout;
    XAxiDma *axidma_inst = (XAxiDma *) callback;

    irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);
    XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DEVICE_TO_DMA);

    //Rx出错
    if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
        error = 1;
        XAxiDma_Reset(axidma_inst);
        timeout = RESET_TIMEOUT_COUNTER;
        while (timeout) {
            if (XAxiDma_ResetIsDone(axidma_inst))
                break;
            timeout -= 1;
        }
        return;
    }

    //Rx完成
    if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
        rx_done = 1;
}

//建立DMA中断系统
//  @param   int_ins_ptr是指向XScuGic实例的指针
//  @param   AxiDmaPtr是指向DMA引擎实例的指针
//  @param   tx_intr_id是TX通道中断ID
//  @param   rx_intr_id是RX通道中断ID
//  @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
        u16 tx_intr_id, u16 rx_intr_id)
{
    int status;
    XScuGic_Config *intc_config;

    //初始化中断控制器驱动
    intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);
    if (NULL == intc_config) {
        return XST_FAILURE;
    }
    status = XScuGic_CfgInitialize(int_ins_ptr, intc_config,
            intc_config->CpuBaseAddress);
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    //设置优先级和触发类型
    XScuGic_SetPriorityTriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);
    XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);

    //为中断设置中断处理函数
    status = XScuGic_Connect(int_ins_ptr, tx_intr_id,
            (Xil_InterruptHandler) tx_intr_handler, axidma_ptr);
    if (status != XST_SUCCESS) {
        return status;
    }

    status = XScuGic_Connect(int_ins_ptr, rx_intr_id,
            (Xil_InterruptHandler) rx_intr_handler, axidma_ptr);
    if (status != XST_SUCCESS) {
        return status;
    }

    XScuGic_Enable(int_ins_ptr, tx_intr_id);
    XScuGic_Enable(int_ins_ptr, rx_intr_id);

    //启用来自硬件的中断
    Xil_ExceptionInit();
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
            (Xil_ExceptionHandler) XScuGic_InterruptHandler,
            (void *) int_ins_ptr);
    Xil_ExceptionEnable();

    //使能DMA中断
    XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
    XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);

    return XST_SUCCESS;
}

//此函数禁用DMA引擎的中断
static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
        u16 rx_intr_id)
{
    XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
    XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278

在代码的第 14 行,我们重新宏定义了 XPAR_PS7_DDR_0_S_AXI_BASEADDR,即 DDR3 的基址,打开 XPAR_PS7_DDR_0_S_AXI_BASEADDR 的定义处,我们可以看到 DDR3 的基址为 0x00100000。
从而 DMA 读取数据的起始地址 TX_BUFFER_BASE 为 0x01200000,写入到 DDR3 中的起始地址RX_BUFFER_BASE 为 0x01400000。代码第 19 行 TEST_START_VALUE 为测试起始值,此处我们将其设为0x0,也可以改为其它任意值。第 20 行的 MAX_PKT_LEN 是 DMA 传输的数据包的长度,此处为 0x100,即 256。
代码第 42 行的 main 函数是程序的主体。第 63 行的 XAxiDma_CfgInitialize 函数初始化 DMA 引擎,第75 行的 setup_intr_system 函数建立 DMA 中断系统。第 87~90 行向 DDR3 的指定地址写入数据,写入的第一个地址为 TX_BUFFER_BASE 即 0x01200000,值为 TEST_START_VALUE 即 0x0,写入的地址长度为MAX_PKT_LEN,即 0x100。DMA 从 TX_BUFFER_BASE 读取数据长度为 MAX_PKT_LE 的数据,然后写入到地址 RX_BUFFER_BAS 处。第 92 行的 Xil_DcacheFlushRange 函数刷新 Data Cache,以防 Data Cache缓存数据。从第 95 行到第 105 行配置并开启 DMA 传输数据。第 107 行再次刷新 Data Cache,由于 DDR3中的数据已经更新,但 Cache 中的数据并没有更新,CPU 如果想从 DDR3 中读取数据需要刷新 Data Cache。此处使用 Xil_DcacheFlushRange 函数,也可以使用 Xil_DcacheInvalidateRange 函数,使 Data Cache 指定范围的数据无效,函数调用方法相同。第 119 行的 check_data 函数检查当 DMA 传输完成后,写入的数据是否正确。第 126 行的 disable_intr_system 函数取消 DMA 中断。

============================================================================================

下面对以上代码进行分段解析:
AXI_DMA+AXI_Stream Data FIFO回环测试实验的PS端代码工作流程总结如下。

5.1 初始化DMA引擎

当使用了多个DMA时,需要分别进行初始化。

    config = XAxiDma_LookupConfig(DMA_DEV_ID);
    if (!config) {
        xil_printf("No config found for %d\r\n", DMA_DEV_ID);
        return XST_FAILURE;
    }


    xil_printf("\r\n--- init DMA --- \r\n");
    status = XAxiDma_CfgInitialize(&axidma, config);
    if (status != XST_SUCCESS) {
        xil_printf("Initialization failed %d\r\n", status);
        return XST_FAILURE;
    }

    if (XAxiDma_HasSg(&axidma)) {
        xil_printf("Device configured as SG mode \r\n");
        return XST_FAILURE;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

5.2 建立中断系统,初始化两个中断号

 status = setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
    if (status != XST_SUCCESS) {
        xil_printf("Failed intr setup\r\n");
        return XST_FAILURE;
    }
  • 1
  • 2
  • 3
  • 4
  • 5

setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID)函数详细代码如下所示。

  1. 初始化中断控制器驱动。
  2. 分别设置两个方向传输中断的优先级和触发类型(上升沿)。
  3. 连接和使能了两个方向传输中断的中断回调函数。
  4. 启用来自硬件的中断
  5. 使能AXI_DMA两个方向传输的中断。XAxiDma_IntrEnable(),配置DMA.CR寄存器为0x00007000,打开所有的中断使能( IOC_IrqEn、Dly_IrqEn、Err_IrqEn)。
//建立DMA中断系统
//  @param   int_ins_ptr是指向XScuGic实例的指针
//  @param   AxiDmaPtr是指向DMA引擎实例的指针
//  @param   tx_intr_id是TX通道中断ID
//  @param   rx_intr_id是RX通道中断ID
//  @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
        u16 tx_intr_id, u16 rx_intr_id)
{
    int status;
    XScuGic_Config *intc_config;

    //初始化中断控制器驱动
    intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);
    if (NULL == intc_config) {
        return XST_FAILURE;
    }
    status = XScuGic_CfgInitialize(int_ins_ptr, intc_config,
            intc_config->CpuBaseAddress);
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    //设置优先级和触发类型
    XScuGic_SetPriorityTriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);
    XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);

    //为中断设置中断处理函数
    status = XScuGic_Connect(int_ins_ptr, tx_intr_id,
            (Xil_InterruptHandler) tx_intr_handler, axidma_ptr);
    if (status != XST_SUCCESS) {
        return status;
    }

    status = XScuGic_Connect(int_ins_ptr, rx_intr_id,
            (Xil_InterruptHandler) rx_intr_handler, axidma_ptr);
    if (status != XST_SUCCESS) {
        return status;
    }

    XScuGic_Enable(int_ins_ptr, tx_intr_id);
    XScuGic_Enable(int_ins_ptr, rx_intr_id);

    //启用来自硬件的中断
    Xil_ExceptionInit();
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
            (Xil_ExceptionHandler) XScuGic_InterruptHandler,
            (void *) int_ins_ptr);
    Xil_ExceptionEnable();

    //使能DMA中断,两个方向上的传输中断可以单独使能。
    XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
    XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);

    return XST_SUCCESS;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

发送数据中断处理函数 tx_intr_handler解析:

  1. 通过XAxiDma_IntrGetIrq 函数读取DMASR状态寄存器中的中断位来判断发送了哪种中断;
  • 状态寄存器MM2S_DMASR.IOC_Irq置1表示发生了传输完成中断;
  • 状态寄存器MM2S_DMASR. Dly_Irq置1表示发生了传输超时中断;
  • 状态寄存器MM2S_DMASR. Err_Irq置1表示发生了传输错误中断;
  1. 然后使用 XAxiDma_IntrAckIrq 函数确认待处理的中断(通过向中断位写1,可以清除发生的中断)。
  2. 如果发现是接收出现错误的中断,则使用 XAxiDma_Reset 函数复位 DMA,并使用 XAxiDma_ResetIsDone 函数判断是否复位完成。
  3. 如果是传输完成的中断,则置位发送完成标志 tx_done。
    DMA RX 中断处理函数与此类似。

5.3 发送或者读取数据(XAxiDma_SimpleTransfer)

通过AXI_DMA向PL中FIFO发送或者接收数据,可以调用Xilinx封装好的函数XAxiDma_SimpleTransfer()即可。目的或者源缓存的地址需要4K对齐,传输长度没有限制,小于或者大于DMA设置的突发长度都可以。数据传输需要指定的参数有

  • InstancePtr:DMA驱动实例
  • BuffAddr:目的或者源缓存
  • Length:传输数据的长度
  • Direction:传输数据的方向,XAXIDMA_DMA_TO_DEVICE和XAXIDMA_DEVICE_TO_DMA。
/*****************************************************************************/
/**
 * This function does one simple transfer submission
 *
 * It checks in the following sequence:
 *	- if engine is busy, cannot submit
 *	- if engine is in SG mode , cannot submit
 *
 * @param	InstancePtr is the pointer to the driver instance
 * @param	BuffAddr is the address of the source/destination buffer
 * @param	Length is the length of the transfer
 * @param	Direction is DMA transfer direction, valid values are
 *			- XAXIDMA_DMA_TO_DEVICE.
 *			- XAXIDMA_DEVICE_TO_DMA.

 * @return
 *		- XST_SUCCESS for success of submission
 *		- XST_FAILURE for submission failure, maybe caused by:
 *		Another simple transfer is still going
 *		- XST_INVALID_PARAM if:Length out of valid range [1:8M]
 *		Or, address not aligned when DRE is not built in
 *
 * @note	This function is used only when system is configured as
 *		Simple mode.
 *
 *****************************************************************************/
u32 XAxiDma_SimpleTransfer(XAxiDma *InstancePtr, UINTPTR BuffAddr, u32 Length, int Direction);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

注意:

  1. 发送数据前,需要调用Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN); 将cache中的发送数据刷新到DDR3中。
  2. 接收数据后,需要调用Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN); 将DDR3中的接收数据刷新到cache中。

5.4 比对发送和接收的数据是否一致。

status = check_data(MAX_PKT_LEN, TEST_START_VALUE);
    if (status != XST_SUCCESS) {
        xil_printf("Data check failed\r\n");
        goto Done;
    }
  • 1
  • 2
  • 3
  • 4
  • 5

六、上板测试

在AXI_DMA的AXI_MM2S、AXI_S2MM、AXIS_MM2S、AXIS_S2MM四个接口上添加ILA在线观察,重新综合生成bitstream文件并导出。SDK感知到硬件文件发生了变化会重新进行编译。下载运行。
观察到的AXIS_S2MM和AXIS_MM2S的波形如下所示。PS端发送了9字节长度的数据,之后接收了9字节长度的数据。与波形显示一致。
同样可以通过波形查看AXI_Stream接口各个信号之间的时序关系。

在这里插入图片描述
tips:
当新增了PS的AXI HP或者AXI GP口之后,AXI外设的地址分配有可能会出现冲突,可以在Address Editor把出错的端口都unmap了,然后再重新自动分配地址就可以了。

七、AXI_DMA使用Tips

7.1 4K地址边界

向MM2S_SA寄存器写入一个有效的源地址。如果将AXI DMA配置为大于32的地址空间,则对MM2S_SA MSB寄存器进行编程。如果AXI DMA没有为数据重新对齐而配置,则必须对齐一个有效的地址,或出现未定义的结果。被认为对齐或未对齐的是基于流数据宽度。当AXI_DMA在微模式中配置时,您有责任指定正确的地址。微DMA不考虑4K边界。
例如,如果内存映射数据宽度= 32,如果数据位于字偏移(32位偏移),则为0x0、0x4、0x8、0xC等等,则是地址对齐的。如果启用了DRE,并且流数据宽度为< 128,那么源地址可以是任意字节偏移量。

即如果内存映射数据宽度32,则DDR数据的地址只能是0x0、0x4、0x8、0xC;不能为0x1、0x2、0x3、0x5、0x6、0x7、0x9、0xa此类地址。

7.2 DMA传输完成后不产生中断

7.2.1 现象

  1. 中断已接入ARM,且正确初始化。
  2. DMA寄存器正常读取,且工作正常。
  3. DMA读取FIFO结束后,不产生传输中断。

7.2.2 可能原因有两个

  1. FIFO的输出端口未使能t_keep信号,而DMA写入端口有该信号。该信号作用是标明单次进入DMA的数据每个字节是否有效,因此传输时要拉高此信号。
    在这里插入图片描述

  2. 假设ARM端配置的DMA传输长度(BYTE)为N,FIFO中包含M个数据。N<M。DMA IP核在启动传输以后在等待N个BYTE传输结束的时间后,在AXI_Stream接口上依然未等待到t_last信号,因此DMA IP核会进入报错状态,可以通过DMA的寄存器读取到该状态。当DMA的传输长度(从t_valid到t_last)为M时,就将N配置为大于等于M,当DMA传输到M时,遇到t_last会自动停止,并且可以通过读取寄存器获取到已传输的长度M。(SG模式下可能可以获取实际传输的数据长度,待测试)

7.2.3 注意事项

axi_dma模块S2MM传输的全部数据量对于其S_AXIS_S2MM端口而言必须是1个完整的Packet,完成的标志即s_axis_s2mm_tlast出现高电平,如果此时数据量不足PS传输指令的数据量,传输仍然结束并且通过dma的buffer length寄存器返回已传输的数据量。

即从AXIS DATA FIFO中读取数据,是一次性将FIFO中写入的全部数据都读出才结束(TLAST信号产生才结束)!
问题:可能无法实现一边写入FIFO,一边读取FIFO这样的功能场景。

  • 波形分析:
    先往AXIS DATA FIFO中写入128个数据(32bit),在PS端发送DMA传输,读取AXIS DATA FIFO中64个数据(32bit),此时由图可以看出,FIFO与DMA S2MM之间的STREAM数据流实际传输了128个数据,直到FIFO为空产生TLAST信号为止。而DMA的MM2S只传输了想要的64个数据。
    在这里插入图片描述

7.3 起始16字节数据丢失问题

在AXI_DMA连接的AXI Stream Data FIFO中有数据时,AXI_DMA会预期4个数据(4*4=16字节)。
原因1: axi_dma模块内部S2MM通道可以缓冲16字节的数据量,即复位结束后通过s_axis_s2mm_tready高电平可以收入16字节数据,如果数据源或者FIFO的复位与S2MM通道复位不同步,则axi_dma模块复位后数据可能丢失,而数据源却认为已经发送,导致数据量出错,从而S2MM传输错误,现象就是AXI DMA的前四个时钟数据丢失。
如下波形所示,当DMA复位结束后,FIFO中有数据存在,会进行16字节数据的预取。
在这里插入图片描述

解决方法:
S2MM传输的数据源和FIFO连接axi_dma模块的s2mm_prmry_reset_out_n复位信号(见Block Design中的紫色高亮信号线),理由如下:
在这里插入图片描述

AXI4-Stream Data FIFO模块的输入输出接口的Packet格式完全一样,即S_AXIS进入数据的Packet中Burst数目与M_AXIS送出数据的Packet中的Burst数目一样。于是数据源送出的Packet中的tlast的位置必须精确控制到与PS传输指令要求的数据量一致。

原因2:在系统复位后,AXI DMA的M_AXIS_tready会一直为高,止到FIFO的S_AXIS_tvalid拉高后再拉低。在M_AXIS_tready和S_AXIS_tvalid同时为高时,会进行握手传输4个周期数据,导致在DMA正式开始传输数据使,从5开始读取。
解决办法:FIFO数据写入模块增加一个写使能,它在接收到一个触发信号上升沿后,再按 AXI4-Stream 协议输出连续产生数据写入到FIFO中。
在这里插入图片描述

八、参考资料

  1. 【27 PS-PL交互之使用Xilinx的AXI_DMA IP核进行数据传输第二集】 https://www.bilibili.com/video/BV1s64y1V7LP/?share_source=copy_web&vd_source=be9cd82b77859096388538f24743c68c
  2. 《正点原子-领航者ZYNQ之嵌入式开发指南》
  3. 【SDK篇_67~71_ZYNQ中AXI DMA简介与使用【ZYNQ】+【DMA】+【Vivado】】 https://www.bilibili.com/video/BV1M54y1W7c2/?p=5&share_source=copy_web&vd_source=be9cd82b77859096388538f24743c68c
  4. http://t.csdnimg.cn/jlhBE
  5. http://t.csdnimg.cn/zZlpF
  6. https://blog.csdn.net/weixin_42278284/article/details/102409839
  7. https://fpga.eetrend.com/blog/2019/100045219.html
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/856868
推荐阅读
相关标签
  

闽ICP备14008679号