赞
踩
AXI4-Stream在AXI4家族中相对来说比较简单,本文主要回答两个问题:
(1)AXI4-Stream 传输的数据流都包含什么?
(2)AXI4-Stream 的接口信号有哪些?master和slave是如何握手的?
数据流
AXI4-Stream传输的数据流包含三种类型:data type、position type、null type。
数据流的结构可以有很多种,例如:可以只传数据,也即都是data type,不包含position type和null type;也可以将data type 和 null type 混着传输;还可以将position type 和 data type混着传输。当然三者混着传输也没有问题。
那么问题来了,数据流传输类型有三种,在传输的过程中如何分辨这三种类型呢?AXI4-Stream的接口信号可以帮助我们进行区分,接下来看看都有哪些接口信号。
接口信号
先来一图尽览:
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同为高时开始传输数据。
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的情况是不能使用的。
TLAST信号,用来表示一个包的结尾。例如发送大小为32字节的包,在发送第32个字节的时候,可以把TLAST信号拉高,来表示这个包发送完了。
TID和TDEST信号:当我们在同一个接口传输不同数据流时有用,一般来讲,AXIS4-Stream Interconnect Core IP可以帮助我们完成这个过程。TUSER信号用来传输一些额外的信息。
参考
[1] ug1037-vivado-axi-reference-guide.pdf
[2] IHI0051A_amba4_axi4_stream_v1_0_protocol_spec.pdf
使用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
使用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
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
`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));
endmodule
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核。
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 总线,可以发送和接收连续的数据流,无需地址。
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。应用程序必须设置缓冲区地址和长度字段以启动相应通道中的传输。
直接寄存器模式(散点聚集引擎已被禁用)提供了一种配置,用于在MM2S和S2MM通道上执行简单的DMA传输,这需要更少的FPGA资源利用率。通过访问DMACR、源地址或目标地址和长度寄存器来启动传输。当传输完成时,一个DMASR.IOC_Irq为关联的通道断言,如果启用,将产生一个中断输出。
MM2S通道的DMA操作按如下顺序设置并启动:
S2MM通道的DMA操作按以下顺序启动:
在SDK软件编程中,可以直接调用XIlinx封装好的发送/读取函数来直接进行数据传输,无需自己写驱动。
带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核。
在实际应用中,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工程中进行以下操作:
配置PL端时钟。打开PS配置页面的Clock Configuration 页面,展开 PL-Fabric Clocks,可以看到默认勾选 FCLK_CLK0,且时钟频率为 50MHz,这里我们将其修改为 100MHz。
开启HP接口。点击左侧的 PS-PL Configuration 页面,然后在右侧展开 General 下的 HP Slave AXI Interface,可以看到有 4 个 HP 接口,这里我们只用其中的 S AXI HP0 interface,DATA WIDTH 保持默认即可。
打开PL中断,DMA在传输完成后通过发送中断通知CPU,因此需要开启PL到PS的中断。点击左侧的 Interrupts 页面,勾选右侧的 Fabric interrupts 并展开,勾选 PL-PS Interrupt Ports 下的IRQ_F2P[15:0]。
添加AXI_DMA 模块。
添加AXI_Stream Data FIFO 模块
该 IP 保持默认设置即可。添加该 IP 核的重点不是了解该 IP 核如何使用,而是知道该 IP 核带有 AXI Stream 接口。在以后的实际使用中,需要封装自定义 IP 核时要注意这一点。
添加Concat IP模块
该 IP 保持默认设置即可。添加该 IP 核的重点不是了解该 IP 核如何使用,而是知道该 IP 核带有 AXI Stream 接口。在以后的实际使用中,需要封装自定义 IP 核时要注意这一点。
自动进行连接,连接FIFO和DMA,连接中断。最终生成的BlockDesign连接图如下所示,
然后依次执行“Generate Output Products”和“Create HDL Wrapper”,生成顶层HDL模块。
生成Bitstream文件并导出到SDK。
整体代码如下。参考《正点原子领航版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); }
在代码的第 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端代码工作流程总结如下。
当使用了多个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; }
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;
}
setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID)函数详细代码如下所示。
//建立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; }
发送数据中断处理函数 tx_intr_handler解析:
通过AXI_DMA向PL中FIFO发送或者接收数据,可以调用Xilinx封装好的函数XAxiDma_SimpleTransfer()即可。目的或者源缓存的地址需要4K对齐,传输长度没有限制,小于或者大于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);
注意:
status = check_data(MAX_PKT_LEN, TEST_START_VALUE);
if (status != XST_SUCCESS) {
xil_printf("Data check failed\r\n");
goto Done;
}
在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了,然后再重新自动分配地址就可以了。
向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此类地址。
FIFO的输出端口未使能t_keep信号,而DMA写入端口有该信号。该信号作用是标明单次进入DMA的数据每个字节是否有效,因此传输时要拉高此信号。
假设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模式下可能可以获取实际传输的数据长度,待测试)
axi_dma模块S2MM传输的全部数据量对于其S_AXIS_S2MM端口而言必须是1个完整的Packet,完成的标志即s_axis_s2mm_tlast出现高电平,如果此时数据量不足PS传输指令的数据量,传输仍然结束并且通过dma的buffer length寄存器返回已传输的数据量。
即从AXIS DATA FIFO中读取数据,是一次性将FIFO中写入的全部数据都读出才结束(TLAST信号产生才结束)!
问题:可能无法实现一边写入FIFO,一边读取FIFO这样的功能场景。
在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中。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。