文章目录
- 概述
- [1 AXI 总线协议介绍](#1 AXI 总线协议介绍)
-
- [1.1 AXI 总线概述](#1.1 AXI 总线概述)
- [1.2 AXI-4 总线信号功能](#1.2 AXI-4 总线信号功能)
-
- [1.2.2 时钟和复位](#1.2.2 时钟和复位)
- [1.2.2 写地址通道信号](#1.2.2 写地址通道信号)
- [1.2.3 写数据通道信号](#1.2.3 写数据通道信号)
- [1.2.4 写响应信号](#1.2.4 写响应信号)
- [1.2.5 读地址通道信号](#1.2.5 读地址通道信号)
- [1.2.6 读数据通道信号](#1.2.6 读数据通道信号)
- [1.3 数据有效的情况](#1.3 数据有效的情况)
- [1.4 突发式读写](#1.4 突发式读写)
-
- [1.4.1 突发式写时序图](#1.4.1 突发式写时序图)
- [1.4.2 突发式读的时序图](#1.4.2 突发式读的时序图)
- [1.5 AXI4 数据路由及缓存机制](#1.5 AXI4 数据路由及缓存机制)
-
- [1.5.1 Modifiable 和 Non-modifiable transaction](#1.5.1 Modifiable 和 Non-modifiable transaction)
- [1.5.2 AXI4 存储类型参数描述](#1.5.2 AXI4 存储类型参数描述)
- [2 FDMA源码分析](#2 FDMA源码分析)
-
- [2.1 FDMA 的写时序](#2.1 FDMA 的写时序)
- [2.2 FDMA 的读时序](#2.2 FDMA 的读时序)
- [2.3 FDMA 的 AXI4-Master 写操作](#2.3 FDMA 的 AXI4-Master 写操作)
- [2.4 FDMA 的 AXI4-Master 读操作](#2.4 FDMA 的 AXI4-Master 读操作)
- [3 FDMA IP的封装](#3 FDMA IP的封装)
- [4 总结](#4 总结)
概述
FDMA 是米联客的基于 AXI4 总线协议定制的一个 DMA 控制器。 本文将对FDMA(Fast Direct MemoryAccess)这一IP进行详细介绍和使用说明。
我们基于这个IP可以统一实现用FPGA代码直接读写PL的DDR或者ZYNQ/ZYNQ MP SOC PS的DDR或者BRAMFDMA IP CORE已经广泛应用于ZYNQSOC/Artix7/Kintex7/ultrascale/ultrascale+系列FPGA/SOC。
如果用过ZYNQ/ZYNQMPSOC的都知道,要直接操作PS的DDR通常是DMA或者VDMA,然而用过XILINX的DMA IP和VDMA IP,总有一种遗憾,那就是不够灵活,还需要对寄存器配置,真是麻烦。XILINX 的总线接口是 AXI4 总线,自定义AXI4 IP挂到总线上就能实现对内存地址空间的读写访问。因此,我们只要掌握AXI4协议就能完成不管是PS还是PL DDR的读写操作。
本文的目的:
1:分析 FDMA 源码,掌握基于FDMA的APP接口实现AXI4-FULL总线接口的访问。
2:掌握自定义总线接口封装方法。
PS:目前米联客已经将FDMA版本更新至FDMA5.1,但整体思路还是一直没变。感兴趣的可以自己去官网下载了解。
1 AXI 总线协议介绍
1.1 AXI 总线概述
在 XIINX FPGA 的软件工具vivado以及相关IP中有支持三种AXI总线,拥有三种AXI接口,当然用的都是AXI协议。其中三种AXI总线分别为:
AXI4:(For high-performance memory-mapped requirements.)主要面向高性能地址映射通信的需求,是面向地址映射的接口,允许最大256轮的数据突发传输;
AXI4-Lite:(For simple, low-throughput memory-mapped communication )是一个轻量级的地址映射单次传输接口,占用很少的逻辑单元。
AXI4-Stream:(For high-speed streaming data.)面向高速流数据传输;去掉了地址项, 允许无限制的数据突发传输规模。
由于AXI4和AXI4-Lite信号大部分一样,以下只介绍AXI4信号。另外对于AXI4-Stream协议不再本文中介绍,后面有单独介绍的文章。
1.2 AXI-4 总线信号功能
1.2.2 时钟和复位
| 信号 | 方向 | 描述 |
|---|---|---|
| ACLK | 时钟源 | 全局时钟信号 |
| ARESETn | 复位源 | 全局复位信号, 低有效 |
1.2.2 写地址通道信号


1.2.3 写数据通道信号


1.2.4 写响应信号

1.2.5 读地址通道信号

1.2.6 读数据通道信号


1.3 数据有效的情况
AXI4所采用的是一种READY,VALID握手通信机制,简单来说主从双方进行数据通信前,有一个握手的过程。传输源产生VLAID信号来指明何时数据或控制信息有效。而目地源产生READY信号来指明已经准备好接受数据或控制信息。传输发生在VALID和READY信号同时为高的时候。VALID和READY信号的出现有三种关系。
(1)VALID先变高READY后变高。时序图如下:

在箭头处信息传输发生。
(2)READY先变高VALID后变高。时序图如下:

同样在箭头处信息传输发生。
(3)VALID和READY信号同时变高。时序图如下:

在这种情况下,信息传输立马发生,如图箭头处指明信息传输发生。
1.4 突发式读写
1.4.1 突发式写时序图


这一过程的开始时,主机发送地址和控制信息到写地址通道中,然后主机发送每一个写数据到写数据通道中。当主机发送最后一个数据时,WLAST信号就变为高。当设备接收完所有数据之后他将一个写响应发送回主机来表明写事务完成。
1.4.2 突发式读的时序图


当地址出现在地址总线后,传输的数据将出现在读数据通道上。设备保持VALID为低直到读数据有效。为了表明一次突发式读写的完成,设备用RLAST信号来表示最后一个被传输的数据。
1.5 AXI4 数据路由及缓存机制

在上图中,我们描述了典型的AXI总线互联方式,经过AXI-interconnect可以完成多个 MASTER和多个SLAVE之间的互联。比如对于XILINX的SOC来说,内存有PS的DDR也有 PL的DDR,还有基于AXI4总线的其他IP,比如PCIE的XDMA IP等,都可以通过AXI-interconnect实现高效的互联,这样各个外设之间的数据就能非常方便的基于AXI4总线实现共享访问。
这里我们有必要了解下AXI4总线中的缓存机制, 主要是ARCACHE3:0和AWCAHE3:0的设置。由于关于这两个参数我们并没有查阅到非常详尽的应用说明,这里米联客的教程以我们查阅的资料和我们自己的理解介绍这两个参数。
1.5.1 Modifiable 和 Non-modifiable transaction
当设置AxCACHE1 = 0,则是Non-modifiable transaction,Non-modifiable transaction不能被拆分成多个transaction传输,也不能合并transaction传输。
在Non-modifiable transaction中以下信号不能修改。

当设置AxCACHE1 = 1,则是modifiable transaction,modifiable transaction能被拆分成多个transaction传输,也能合并transaction传输,因此 AxADDR、AxSIZE、AxLEN、AxBURST可以被改变。
1.5.2 AXI4 存储类型参数描述




2 FDMA源码分析
由于AXI4总线协议直接操作起来相对复杂一些,容易出错,因此我们封装一个简单的用户接口,间接操作 AXI4总线会带来很多方便性。先看下我们计划设计一个怎么样的用户接口。
2.1 FDMA 的写时序

fdma_wready设置为1,当fdma_wbusy=0的时候代表FDMA的总线非忙,可以进行一次新的FDMA传输,这个时候可以设置fdma_wreq=1,同时设置fdma burst的起始地址和 fdma_wsize本次需要传输的数据大小(以 bytes为单位)。当fdma_wvalid=1的时候需要给出有效的数据,写入AXI总线。当最后一个数写完后,fdma_wvalid和fdma_wbusy变为0。
AXI4总线最大的burst lenth是256,而经过封装后,用户接口的fdma_size可以任意大小的,fdma ip内部代码控制每次AXI4总线的Burst 长度,这样极大简化了AXI4总线协议的使用。
2.2 FDMA 的读时序

fdma_rready设置为1,当fdma_rbusy=0的时候代表FDMA的总线非忙,可以进行一次新的FDMA传输,这个时候可以设置fdma_rreq=1,同时设置fdma burst的起始地址和fdma_rsize本次需要传输的数据大小(以bytes为单位)。当 fdma_rvalid=1的时候需要给出有效的数据,写入AXI总线。当最后一个数写完后,fdma_rvalid和fdma_rbusy变为0。
同样对于AXI4总线的读操作,AXI4总线最大的 burst lenth是256,而经过封装后,用户接口的fdma_size可以任意大小的,fdma ip内部代码控制每次AXI4总线的Burst长度,这样极大简化了AXI4总线协议的使用。
2.3 FDMA 的 AXI4-Master 写操作
以下代码中我们给出axi4-master写操作的代码分析注释
c
//AXI4 FULL Write
assign axi_wdata = I_fdma_wdata;
assign O_fdma_wvalid = w_next;
reg fdma_wstart_locked = 1'b0;
wire fdma_wend;
wire fdma_wstart;
assign O_fdma_wbusy = fdma_wstart_locked ;
//在整个写过程中fdma_wstart_locked将保持有效,直到本次FDMA写结束
always @(posedge M_AXI_ACLK)
if(M_AXI_ARESETN == 1'b0 || fdma_wend == 1'b1 )
fdma_wstart_locked <= 1'b0;
else if(fdma_wstart)
fdma_wstart_locked <= 1'b1;
//产生fdma_wstart信号,整个信号保持1个 M_AXI_ACLK时钟周期
assign fdma_wstart = (fdma_wstart_locked == 1'b0 && I_fdma_wareq == 1'b1);
//AXI4 write burst lenth busrt addr ------------------------------
//当fdma_wstart信号有效,代表一次新的FDMA传输,首先把地址本次fdma的burst地址寄存到axi_awaddr作为第一次axi burst的地址。如果fdma的数据长度大于256,那么当axi_wlast有效的时候,自动计算下次axi的burst地址
always @(posedge M_AXI_ACLK)
if(fdma_wstart)
axi_awaddr <= I_fdma_waddr;
else if(axi_wlast == 1'b1)
axi_awaddr <= axi_awaddr + axi_wburst_size ;
//AXI4 write cycle -----------------------------------------------
//axi_wstart_locked_r1, axi_wstart_locked_r2信号是用于时序同步
reg axi_wstart_locked_r1 = 1'b0, axi_wstart_locked_r2 = 1'b0;
always @(posedge M_AXI_ACLK)begin
axi_wstart_locked_r1 <= axi_wstart_locked;
axi_wstart_locked_r2 <= axi_wstart_locked_r1;
end
// axi_wstart_locked的作用代表一次axi写burst操作正在进行中。
always @(posedge M_AXI_ACLK)
if((fdma_wstart_locked == 1'b1) && axi_wstart_locked == 1'b0)
axi_wstart_locked <= 1'b1;
else if(axi_wlast == 1'b1 || fdma_wstart == 1'b1)
axi_wstart_locked <= 1'b0;
//AXI4 addr valid and write addr-----------------------------------
always @(posedge M_AXI_ACLK)
if((axi_wstart_locked_r1 == 1'b1) && axi_wstart_locked_r2 == 1'b0)
axi_awvalid <= 1'b1;
else if((axi_wstart_locked == 1'b1 && M_AXI_AWREADY == 1'b1)|| axi_wstart_locked == 1'b0)
axi_awvalid <= 1'b0;
//AXI4 write data---------------------------------------------------
always @(posedge M_AXI_ACLK)
if((axi_wstart_locked_r1 == 1'b1) && axi_wstart_locked_r2 == 1'b0)
axi_wvalid <= 1'b1;
else if(axi_wlast == 1'b1 || axi_wstart_locked == 1'b0)
axi_wvalid <= 1'b0;//
//AXI4 write data burst len counter----------------------------------
always @(posedge M_AXI_ACLK)
if(axi_wstart_locked == 1'b0)
wburst_cnt <= 'd0;
else if(w_next)
wburst_cnt <= wburst_cnt + 1'b1;
assign axi_wlast = (w_next == 1'b1) && (wburst_cnt == M_AXI_AWLEN);
//fdma write data burst len counter----------------------------------
reg wburst_len_req = 1'b0;
reg [15:0] fdma_wleft_cnt =16'd0;
// wburst_len_req信号是自动管理每次axi需要burst的长度
always @(posedge M_AXI_ACLK)
wburst_len_req <= fdma_wstart|axi_wlast;
// fdma_wleft_cnt用于记录一次FDMA剩余需要传输的数据数量
always @(posedge M_AXI_ACLK)
if( fdma_wstart )begin
wfdma_cnt <= 1'd0;
fdma_wleft_cnt <= I_fdma_wsize;
end
else if(w_next)begin
wfdma_cnt <= wfdma_cnt + 1'b1;
fdma_wleft_cnt <= (I_fdma_wsize - 1'b1) - wfdma_cnt;
end
//当最后一个数据的时候,产生fdma_wend信号代表本次fdma传输结束
assign fdma_wend = w_next && (fdma_wleft_cnt == 1 );
//一次axi最大传输的长度是256因此当大于256,自动拆分多次传输
always @(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN == 1'b0)begin
wburst_len <= 1;
end
else if(wburst_len_req)begin
if(fdma_wleft_cnt[15:MAX_BURST_LEN_SIZE] >0)
wburst_len <= M_AXI_MAX_BURST_LEN;
else
wburst_len <= fdma_wleft_cnt[MAX_BURST_LEN_SIZE-1:0];
end
else wburst_len <= wburst_len;
end
以上代码我们进行了详细的注释性分析。 以下给出FDMA写操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,如果需要MAX_BURST_LEN_SIZE设置了最大值 256, 那么2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。

2.4 FDMA 的 AXI4-Master 读操作
以下代码中我们给出axi4-master读操作的代码分析注释
c
//fdma axi read----------------------------------------------
reg [M_AXI_ADDR_WIDTH-1 : 0] axi_araddr =0 ; //AXI4 读地址
reg axi_arvalid =1'b0; //AXI4读地有效
wire axi_rlast ; //AXI4 读LAST信号
reg axi_rready = 1'b0;//AXI4读准备好
wire r_next = (M_AXI_RVALID && M_AXI_RREADY);// 当valid ready信号都有效,代表AXI4数据传输有效
reg [8 :0] rburst_len = 1 ; //读传输的axi burst长度,代码会自动计算每次axi传输的burst 长度
reg [8 :0] rburst_cnt = 0 ; //每次axi bust的计数器
reg [15:0] rfdma_cnt = 0 ; //fdma的读数据计数器
reg axi_rstart_locked =0; //axi 传输进行中,lock住,用于时序控制
wire [15:0] axi_rburst_size = rburst_len * AXI_BYTES; //axi 传输的地址长度计算
assign M_AXI_ARID = M_AXI_ID; //读地址ID,用来标志一组写信号, M_AXI_ID是通过参数接口定义
assign M_AXI_ARADDR = axi_araddr;
assign M_AXI_ARLEN = rburst_len - 1; //AXI4 burst的长度
assign M_AXI_ARSIZE = clogb2((AXI_BYTES)-1);
assign M_AXI_ARBURST = 2'b01; //AXI4的busr类型INCR模式,地址递增
assign M_AXI_ARLOCK = 1'b0; //不使用cache,不使用buffer
assign M_AXI_ARCACHE = 4'b0010;
assign M_AXI_ARPROT = 3'h0;
assign M_AXI_ARQOS = 4'h0;
assign M_AXI_ARVALID = axi_arvalid;
assign M_AXI_RREADY = axi_rready&&I_fdma_rready; //读数据准备好,这里必须设置I_fdma_rready有效
assign O_fdma_rdata = M_AXI_RDATA;
assign O_fdma_rvalid = r_next;
//AXI4 FULL Read-----------------------------------------
reg fdma_rstart_locked = 1'b0;
wire fdma_rend;
wire fdma_rstart;
assign O_fdma_rbusy = fdma_rstart_locked ;
//在整个读过程中fdma_rstart_locked将保持有效,直到本次FDMA写结束
always @(posedge M_AXI_ACLK)
if(M_AXI_ARESETN == 1'b0 || fdma_rend == 1'b1)
fdma_rstart_locked <= 1'b0;
else if(fdma_rstart)
fdma_rstart_locked <= 1'b1;
//产生fdma_rstart信号,整个信号保持1个 M_AXI_ACLK时钟周期
assign fdma_rstart = (fdma_rstart_locked == 1'b0 && I_fdma_rareq == 1'b1);
//AXI4 read burst lenth busrt addr ------------------------------
//当fdma_rstart信号有效,代表一次新的FDMA传输,首先把地址本次fdma的burst地址寄存到axi_araddr作为第一次axi burst的地址。如果fdma的数据长度大于256,那么当axi_rlast有效的时候,自动计算下次axi的burst地址
always @(posedge M_AXI_ACLK)
if(fdma_rstart == 1'b1)
axi_araddr <= I_fdma_raddr;
else if(axi_rlast == 1'b1)
axi_araddr <= axi_araddr + axi_rburst_size ;
//AXI4 r_cycle_flag-------------------------------------
//axi_rstart_locked_r1, axi_rstart_locked_r2信号是用于时序同步
reg axi_rstart_locked_r1 = 1'b0, axi_rstart_locked_r2 = 1'b0;
always @(posedge M_AXI_ACLK)begin
axi_rstart_locked_r1 <= axi_rstart_locked;
axi_rstart_locked_r2 <= axi_rstart_locked_r1;
end
// axi_rstart_locked的作用代表一次axi读burst操作正在进行中。
always @(posedge M_AXI_ACLK)
if((fdma_rstart_locked == 1'b1) && axi_rstart_locked == 1'b0)
axi_rstart_locked <= 1'b1;
else if(axi_rlast == 1'b1 || fdma_rstart == 1'b1)
axi_rstart_locked <= 1'b0;
//AXI4 addr valid and read addr-----------------------------------
always @(posedge M_AXI_ACLK)
if((axi_rstart_locked_r1 == 1'b1) && axi_rstart_locked_r2 == 1'b0)
axi_arvalid <= 1'b1;
else if((axi_rstart_locked == 1'b1 && M_AXI_ARREADY == 1'b1)|| axi_rstart_locked == 1'b0)
axi_arvalid <= 1'b0;
//AXI4 read data---------------------------------------------------
always @(posedge M_AXI_ACLK)
if((axi_rstart_locked_r1 == 1'b1) && axi_rstart_locked_r2 == 1'b0)
axi_rready <= 1'b1;
else if(axi_rlast == 1'b1 || axi_rstart_locked == 1'b0)
axi_rready <= 1'b0;//
//AXI4 read data burst len counter----------------------------------
always @(posedge M_AXI_ACLK)
if(axi_rstart_locked == 1'b0)
rburst_cnt <= 'd0;
else if(r_next)
rburst_cnt <= rburst_cnt + 1'b1;
assign axi_rlast = (r_next == 1'b1) && (rburst_cnt == M_AXI_ARLEN);
//fdma read data burst len counter----------------------------------
reg rburst_len_req = 1'b0;
reg [15:0] fdma_rleft_cnt =16'd0;
// rburst_len_req信号是自动管理每次axi需要burst的长度
always @(posedge M_AXI_ACLK)
rburst_len_req <= fdma_rstart | axi_rlast;
// fdma_rleft_cnt用于记录一次FDMA剩余需要传输的数据数量
always @(posedge M_AXI_ACLK)
if(fdma_rstart )begin
rfdma_cnt <= 1'd0;
fdma_rleft_cnt <= I_fdma_rsize;
end
else if(r_next)begin
rfdma_cnt <= rfdma_cnt + 1'b1;
fdma_rleft_cnt <= (I_fdma_rsize - 1'b1) - rfdma_cnt;
end
//当最后一个数据的时候,产生fdma_rend信号代表本次fdma传输结束
assign fdma_rend = r_next && (fdma_rleft_cnt == 1 );
//axi auto burst len caculate-----------------------------------------
//一次axi最大传输的长度是256因此当大于256,自动拆分多次传输
always @(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN == 1'b0)begin
rburst_len <= 1;
end
else if(rburst_len_req)begin
if(fdma_rleft_cnt[15:MAX_BURST_LEN_SIZE] >0)
rburst_len <= M_AXI_MAX_BURST_LEN;
else
rburst_len <= fdma_rleft_cnt[MAX_BURST_LEN_SIZE-1:0];
end
else rburst_len <= rburst_len;
end
以上代码我们进行了详细的注释性分析。FDMA的读写代码高度对称, 以上源码和以下波形图都和写操作类似,理解起会提高很多效率。
以下给出FDMA读操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例, 如果需要MAX_BURST_LEN_SIZE设置了最大值256,那么2次AXI4 BURST才能完成, 第一次传输256个长度数据, 第二次传输6个长度的数据。

3 FDMA IP的封装
这部分可参考博主之前的总结: FPGA应用技巧之Vivado自定义IP创建与使用
4 总结
以上就是AXI4-FULL-MASTER IP FDMA的基本介绍,后续会继续输出关于FDMA IP的仿真验证、读写axi-bram、读写DDR等实际应用方案介绍。敬请期待。

不要忘记关注我,点赞收藏,我会为你带来更多优质内容!