软件版本:vitis2020.2(vivado2020.2)
操作系统:WIN10 64bit
硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA(米联客(milianke)MZU07A-EG硬件开发平台)
登录"米联客"FPGA社区-www.uisrc.com视频课程、答疑解惑!
7.1概述
FDMA是米联客的基于AXI4总线协议定制的一个DMA控制器。本文对AXI4-FULL总线接口进行了封装,同时定义了简单的APP接口提供用户调用AXI4总线实现数据交互。这个IP 我们命名为FDMA(Fast Direct Memory Access)。
有了这个IP我们可以统一实现用FPGA代码直接读写PL的DDR或者ZYNQ/ZYNQMP SOC PS的DDR或者BRAM。FDMA IP CORE 已经广泛应用于ZYNQ SOC/Artix7/Kintex7 FPGA,同样适用于ultrascale/ultrascale+系列FPGA/SOC。
如果用过ZYNQ/ZYNQMP的都知道,要直接操作PS的DDR 通常是DMA 或者VDMA,然而用过XILINX 的DMA IP 和VDMA IP,总有一种遗憾,那就是不够灵活,还需要对寄存器配置,真是麻烦。XILINX 的总线接口是AXI4总线,自定义AXI4 IP挂到总线上就能实现对内存地址空间的读写访问。因此,我们只要掌握AXI4协议就能完成不管是PS还是PL DDR的读写操作。
米联客封装的AXI4总线协议命名位uiFDMA,自动2018年第一版本发布后,就引起了很多FPGA工程师的兴趣,并且得到了广大FPGA工程师的好评,但是FDMA1.0版本还是有一些局限和BUG,再实际的应用中被FPGA工程师发现,因此给了我们很多宝贵意见。借此2020版本教程更新发布之际,我们也对FDMA1.0版本升级到FDMA2.0版本。目前最新的版本是3.0版本,对2.0版本优化了自动burst的长度,提高了效率。
uiFDMA2.0/3.0新增特性:
1:支持多个FDMA IP同时挂带AXI-interconnect总线,同时工作
2:支持自动计算没错AXI-Burst长度,使用起来非常简单,只需要给出需要burst的长度。
从本文开始,我们从多个应用方案来演示FDMA的用途。
本文实验目的:
1:分析FDMA源码,掌握基于FDMA的APP接口实现AXI4-FULL总线接口的访问。
2:掌握自定义总线接口封装方法
3:自定义AXI-FULL-Slave IP用于验证FDMA的工作情况。
7.2AXI总线协议介绍
7.2.1AXI总线概述
在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协议不再本文中接收,后面有单独介绍的文章。
7.2.2AXI-4总线信号功能
1:时钟和复位
信号 |
方向 |
描述 |
ACLK |
时钟源 |
全局时钟信号 |
ARESETn |
复位源 |
全局复位信号,低有效 |
写地址通道信号:
信号 |
方向 |
描述 |
AWID |
主机to从机 |
写地址ID,用来标志一组写信号 |
AWADDR |
主机to从机 |
写地址,给出一次写突发传输的写地址 |
AWLEN |
主机to从机 |
AWLEN[7:0]决定写传输的突发长度。AXI3只支持1~16次的突发传输(Burst_length=AxLEN[3:0]+1),AXI4扩展突发长度支持INCR突发类型为1~256次传输,对于其他的传输类型依然保持1~16次突发传输(Burst_Length=AxLEN[7:0]+1)。 burst传输具有如下规则: wraping burst ,burst长度必须是2,4,8,16 burst不能跨4KB边界 不支持提前终止burst传输 |
AWSIZE |
主机to从机 |
写突发大小,给出每次突发传输的字节数支持1、2、4、8、16、32、64、128 |
AWBURST |
主机to从机 |
突发类型: 2'b00 FIXED:突发传输过程中地址固定,用于FIFO访问 2'b01 INCR :增量突发,传输过程中,地址递增。增加量取决AxSIZE的值。 2'b10 WRAP:回环突发,和增量突发类似,但会在特定高地址的边界处回到低地址处。回环突发的长度只能是2,4,8,16次传输,传输首地址和每次传输的大小对齐。最低的地址整个传输的数据大小对齐。回环边界等于(AxSIZE*AxLEN) 2'b11 Reserved |
AWLOCK |
主机to从机 |
总线锁信号,可提供操作的原子性 |
AWCACHE |
主机to从机 |
内存类型,表明一次传输是怎样通过系统的 |
AWPROT |
主机to从机 |
保护类型,表明一次传输的特权级及安全等级 |
AWQOS |
主机to从机 |
质量服务QoS |
AWREGION |
主机to从机 |
区域标志,能实现单一物理接口对应的多个逻辑接口 |
AWUSER |
主机to从机 |
用户自定义信号 |
AWVALID |
主机to从机 |
有效信号,表明此通道的地址控制信号有效 |
AWREADY |
从机to主机 |
表明"从"可以接收地址和对应的控制信号 |
2:写数据通道信号:
信号名 |
方向 |
描述 |
WID |
主机to从机 |
一次写传输的ID tag |
WDATA |
主机to从机 |
写数据 |
WSTRB |
主机to从机 |
WSTRB[n:0]对应于对应的写字节,WSTRB[n]对应WDATA[8n+7:8n]。WVALID为低时,WSTRB可以为任意值,WVALID为高时,WSTRB为高的字节线必须指示有效的数据。 |
WLAST |
主机to从机 |
表明此次传输是最后一个突发传输 |
WUSER |
主机to从机 |
用户自定义信号 |
WVALID |
主机to从机 |
写有效,表明此次写有效 |
WREADY |
从机to主机 |
表明从机可以接收写数据 |
写响应信号:
信号名 |
方向 |
描述 |
BID |
从机to主机 |
写响应ID tag |
BRESP |
从机to主机 |
写响应,表明写传输的状态 |
BUSER |
从机to主机 |
用户自定义 |
BVALID |
从机to主机 |
写响应有效 |
BREADY |
主机to从机 |
表明主机能够接收写响应 |
3:读地址通道信号:
信号 |
方向 |
描述 |
ARID |
主机to从机 |
读地址ID,用来标志一组写信号 |
ARADDR |
主机to从机 |
读地址,给出一次读突发传输的读地址 |
ARLEN |
主机to从机 |
ARLEN[7:0]决定读传输的突发长度。AXI3只支持1~16次的突发传输(Burst_length=AxLEN[3:0]+1),AXI4扩展突发长度支持INCR突发类型为1~256次传输,对于其他的传输类型依然保持1~16次突发传输(Burst_Length=AxLEN[7:0]+1)。 burst传输具有如下规则: wraping burst ,burst长度必须是2,4,8,16 burst不能跨4KB边界 不支持提前终止burst传输 |
ARSIZE |
主机to从机 |
读突发大小,给出每次突发传输的字节数支持1、2、4、8、16、32、64、128 |
ARBURST |
主机to从机 |
突发类型: 2'b00 FIXED:突发传输过程中地址固定,用于FIFO访问 2'b01 INCR :增量突发,传输过程中,地址递增。增加量取决AxSIZE的值。 2'b10 WRAP:回环突发,和增量突发类似,但会在特定高地址的边界处回到低地址处。回环突发的长度只能是2,4,8,16次传输,传输首地址和每次传输的大小对齐。最低的地址整个传输的数据大小对齐。回环边界等于(AxSIZE*AxLEN) 2'b11 Reserved |
ARLOCK |
主机to从机 |
总线锁信号,可提供操作的原子性 |
ARCACHE |
主机to从机 |
内存类型,表明一次传输是怎样通过系统的 |
ARPROT |
主机to从机 |
保护类型,表明一次传输的特权级及安全等级 |
ARQOS |
主机to从机 |
质量服务QoS |
ARREGION |
主机to从机 |
区域标志,能实现单一物理接口对应的多个逻辑接口 |
ARUSER |
主机to从机 |
用户自定义信号 |
ARVALID |
主机to从机 |
有效信号,表明此通道的地址控制信号有效 |
ARREADY |
从机to主机 |
表明"从"可以接收地址和对应的控制信号 |
4:读数据通道信号:
信号名 |
方向 |
描述 |
RID |
从机to主机 |
一次读传输的ID tag |
RDATA |
从机to主机 |
读数据 |
RRESP |
从机to主机 |
读响应,表明读传输的状态 |
RLAST |
从机to主机 |
表明此次传输是最后一个突发传输 |
RUSER |
从机to主机 |
用户自定义信号 |
RVALID |
从机to主机 |
写有效,表明此次写有效 |
RREADY |
主机to从机 |
表明从机可以接收写数据 |
7.2.3数据有效的情况
AXI4所采用的是一种READY,VALID握手通信机制,简单来说主从双方进行数据通信前,有一个握手的过程。传输源产生VLAID信号来指明何时数据或控制信息有效。而目地源产生READY信号来指明已经准备好接受数据或控制信息。传输发生在VALID和READY信号同时为高的时候。VALID和READY信号的出现有三种关系。
-
VALID先变高READY后变高。时序图如下:
在箭头处信息传输发生。
-
READY先变高VALID后变高。时序图如下:
同样在箭头处信息传输发生。
-
VALID和READY信号同时变高。时序图如下:
在这种情况下,信息传输立马发生,如图箭头处指明信息传输发生。
7.2.4突发式读写
1:突发式写时序图
这一过程的开始时,主机发送地址和控制信息到写地址通道中,然后主机发送每一个写数据到写数据通道中。当主机发送最后一个数据时,WLAST信号就变为高。当设备接收完所有数据之后他将一个写响应发送回主机来表明写事务完成。
2:突发式读的时序图
当地址出现在地址总线后,传输的数据将出现在读数据通道上。设备保持VALID为低直到读数据有效。为了表明一次突发式读写的完成,设备用RLAST信号来表示最后一个被传输的数据。
7.3FDMA源码分析
由于AXI4总线协议直接操作起来相对复杂一些,容易出错,因此我们封装一个简单的用户接口,间接操作AXI4总线会带来很多方便性。先看下我们计划设计一个怎么样的用户接口。
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: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总线协议的使用。
3:FDMA的AXI4-Master写操作
以下代码中我们给出axi4-master写操作的代码分析注释
//fdma axi write----------------------------------------------
reg [M_AXI_ADDR_WIDTH-1 : 0] axi_awaddr =0; //AXI4 写地址
reg axi_awvalid = 1'b0; //AXI4 写地有效
wire [M_AXI_DATA_WIDTH-1 : 0] axi_wdata ; //AXI4 写数据
wire axi_wlast ; //AXI4 写LAST信号
reg axi_wvalid = 1'b0; //AXI4 写数据有效
wire w_next = (M_AXI_WVALID & M_AXI_WREADY);//当valid ready信号都有效,代表AXI4数据传输有效
reg [8 :0] wburst_len = 1 ; //写传输的axi burst长度,代码会自动计算每次axi传输的burst 长度
reg [8 :0] wburst_cnt = 0 ; //每次axi bust的计数器
reg [15:0] wfdma_cnt = 0 ;//fdma的写数据计数器
reg axi_wstart_locked =0; //axi 传输进行中,lock住,用于时序控制
wire [15:0] axi_wburst_size = wburst_len * AXI_BYTES;//axi 传输的地址长度计算
assign M_AXI_AWID = M_AXI_ID; //写地址ID,用来标志一组写信号, M_AXI_ID是通过参数接口定义
assign M_AXI_AWADDR = axi_awaddr;
assign M_AXI_AWLEN = wburst_len - 1;//AXI4 burst的长度
assign M_AXI_AWSIZE = clogb2(AXI_BYTES-1);
assign M_AXI_AWBURST = 2'b01;//AXI4的busr类型INCR模式,地址递增
assign M_AXI_AWLOCK = 1'b0;
assign M_AXI_AWCACHE = 4'b0010;//不使用cache,不使用buffer
assign M_AXI_AWPROT = 3'h0;
assign M_AXI_AWQOS = 4'h0;
assign M_AXI_AWVALID = axi_awvalid;
assign M_AXI_WDATA = axi_wdata;
assign M_AXI_WSTRB = {(AXI_BYTES){1'b1}};//设置所有的WSTRB为1代表传输的所有数据有效
assign M_AXI_WLAST = axi_wlast;
assign M_AXI_WVALID = axi_wvalid & fdma_wready;//写数据有效,这里必须设置fdma_wready有效
assign M_AXI_BREADY = 1'b1;
//----------------------------------------------------------------------------
//AXI4 FULL Write
assign axi_wdata = fdma_wdata;
assign fdma_wvalid = w_next;
reg fdma_wstart_locked = 1'b0;
wire fdma_wend;
wire fdma_wstart;
assign 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 && 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 <= 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 <= fdma_wsize;
end
else if(w_next)begin
wfdma_cnt <= wfdma_cnt + 1'b1;
fdma_wleft_cnt <= (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(wburst_len_req)begin
if(fdma_wleft_cnt[15:8] >0) wburst_len <= 256;
else
wburst_len <= fdma_wleft_cnt[7:0];
end
else wburst_len <= wburst_len;
end
以上代码我们进行了详细的注释性分析。以下给出FDMA写操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,需要2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。
4:FDMA的AXI4-Master读操作
以下代码中我们给出axi4-master读操作的代码分析注释
//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&&fdma_rready; //读数据准备好,这里必须设置fdma_rready有效
assign fdma_rdata = M_AXI_RDATA;
assign fdma_rvalid = r_next;
//AXI4 FULL Read-----------------------------------------
reg fdma_rstart_locked = 1'b0;
wire fdma_rend;
wire fdma_rstart;
assign 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 && 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 <= 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 <= fdma_rsize;
end
else if(r_next)begin
rfdma_cnt <= rfdma_cnt + 1'b1;
fdma_rleft_cnt <= (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(rburst_len_req)begin
if(fdma_rleft_cnt[15:8] >0)
rburst_len <= 256;
else
rburst_len <= fdma_rleft_cnt[7:0];
end
else rburst_len <= rburst_len;
end
以上代码我们进行了详细的注释性分析。FDMA的读写代码高度对称,以上源码和以下波形图都和写操作类似,理解起会提高很多效率。
以下给出FDMA写操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,需要2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。
7.4FDMA IP的封装
我先讲解如何封装FDMA IP,之后再分析源码。封装IP少不了源码,这里是利用已经编写好的uiFDMA.v进行封装。
默认的源码路径在配套的工程uisrc/uifdma路径下
创建一个新的空的fpga工程
添加uiFDMA.v源码
创建IP
选择Package your current project
按住shift全选后,右击弹出菜单后选择Create Interface Definition
接口定义为slave,命名为FDMA_S
设置完成,uisrc/03_ip/uifdma路径下多出2个文件,这个两个文件就是定义了自定义的总线接口。
现在可以看到封装后的总线
建议把名字改简洁一些
可以看到封装好的接口,更加美观
7.5saxi_full_mem IP
这个IP的源码可以基于axi-full-slave的模板简单修改就可以实现。找到以下路径,中saxi_full_v1_0_S00_AXI.v文件,并且对齐修改。
我们把修改后的代码命名为saxi_full_mem.v修改其中的部分代码,关键部分是memory部分定义。
修改的这部分代码支持Memory的任意长度设置(FPGA内部RAM会消耗资源),其中参数USER_NUM_MEM用于定义RAM的长度,我们一次FDMA的burst长度应该小于等于USER_NUM_MEM这个参数。
我们来看下IP的接口参数设置:这里我们计划FDMA的读写长度是262,设置USER_NUM_MEM=300完全够用。
7.6创建FPGA图像化设计
设置IP路径
添加已经创建好的IP
输入关键词fdma,在最后可以看到,双击添加Ip
可以看到本文的FDMA版本升级到3.0版本,相比2.0而言,优化了burst传输效率
完成连线
继续添加剩余IP
设置IP参数
完成连线
设置地址分配:
7.7添加FDMA接口控制代码
添加完成后如下图:
fdma_axi_slave_test.v源码如下
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
/*
Company : Liyang Milian Electronic Technology Co., Ltd.
Brand: (milianke)
Technical forum:uisrc.com
taobao: https://milianke.taobao.com https://osrc.taobao.com
jd:https://milianke.jd.com
Create Date: 2021/04/25
Module Name: fdma_axi_slave_test
Description:
Copyright: Copyright (c) milianke
Revision: 1.0
Signal description:
1) _i input
2) _o output
3) _n activ lowpai
4) _dg debug signal
5) _r delay or register
6) _s state mechine
*/
//////////////////////////////////////////////////////////////////////////////////
module fdma_axi_slave_test(
input sysclk_p,
input sysclk_n
);
wire [31:0] fdma_raddr;
reg fdma_rareq;
wire fdma_rbusy;
wire [31:0] fdma_rdata;
wire [15:0] fdma_rsize;
wire fdma_rvalid;
wire [31:0] fdma_waddr;
reg fdma_wareq;
wire fdma_wbusy;
wire [31:0] fdma_wdata;
wire [15:0] fdma_wsize;
wire fdma_wvalid;
wire ui_clk;
parameter TEST_MEM_SIZE = 32'd4*20;
parameter FDMA_BURST_LEN = 16'd262;
parameter ADDR_MEM_OFFSET = 0;
parameter ADDR_INC = 0;
parameter WRITE1 = 0;
parameter WRITE2 = 1;
parameter WAIT = 2;
parameter READ1 = 3;
parameter READ2 = 4;
reg [31: 0] t_data;
reg [31: 0] fdma_waddr_r;
reg [2 :0] T_S = 0;
assign fdma_waddr = fdma_waddr_r + ADDR_MEM_OFFSET;
assign fdma_raddr = fdma_waddr;
assign fdma_wsize = FDMA_BURST_LEN;
assign fdma_rsize = FDMA_BURST_LEN;
assign fdma_wdata ={t_data,t_data,t_data,t_data};
//delay reset
reg [8:0] rst_cnt = 0;
always @(posedge ui_clk)
if(rst_cnt[8] == 1'b0)
rst_cnt <= rst_cnt + 1'b1;
else
rst_cnt <= rst_cnt;
always @(posedge ui_clk)begin
if(rst_cnt[8] == 1'b0)begin
T_S <=0;
fdma_wareq <= 1'b0;
fdma_rareq <= 1'b0;
t_data<=0;
fdma_waddr_r <=0;
end
else begin
case(T_S)
WRITE1:begin
if(fdma_waddr_r==TEST_MEM_SIZE) fdma_waddr_r<=0;
if(!fdma_wbusy)begin
fdma_wareq <= 1'b1;
t_data <= 0;
end
if(fdma_wareq&&fdma_wbusy)begin
fdma_wareq <= 1'b0;
T_S <= WRITE2;
end
end
WRITE2:begin
if(!fdma_wbusy) begin
T_S <= WAIT;
t_data <= 32'd0;
end
else if(fdma_wvalid) begin
t_data <= t_data + 1'b1;
end
end
WAIT:begin//not needed
T_S <= READ1;
end
READ1:begin
if(!fdma_rbusy)begin
fdma_rareq <= 1'b1;
t_data <= 0;
end
if(fdma_rareq&&fdma_rbusy)begin
fdma_rareq <= 1'b0;
T_S <= READ2;
end
end
READ2:begin
if(!fdma_rbusy) begin
T_S <= WRITE1;
t_data <= 32'd0;
fdma_waddr_r <= fdma_waddr_r + ADDR_INC;//128/8=16
end
else if(fdma_rvalid) begin
t_data <= t_data + 1'b1;
end
end
default:
T_S <= WRITE1;
endcase
end
end
wire test_error = (fdma_rvalid && (t_data[15:0] != fdma_rdata[15:0]));
//ila_0 ila_dbg (
// .clk(ui_clk),
// .probe0({fdma_wdata[15:0],fdma_wareq,fdma_wvalid,fdma_wbusy}),
// .probe1({fdma_rdata[15:0],t_data[15:0],fdma_rvalid,fdma_rbusy,T_S,test_error})
//);
system system_i
(.FDMA_S_0_fdma_raddr(fdma_raddr),
.FDMA_S_0_fdma_rareq(fdma_rareq),
.FDMA_S_0_fdma_rbusy(fdma_rbusy),
.FDMA_S_0_fdma_rdata(fdma_rdata),
.FDMA_S_0_fdma_rready(1'b1),
.FDMA_S_0_fdma_rsize(fdma_rsize),
.FDMA_S_0_fdma_rvalid(fdma_rvalid),
.FDMA_S_0_fdma_waddr(fdma_waddr),
.FDMA_S_0_fdma_wareq(fdma_wareq),
.FDMA_S_0_fdma_wbusy(fdma_wbusy),
.FDMA_S_0_fdma_wdata(fdma_wdata),
.FDMA_S_0_fdma_wready(1'b1),
.FDMA_S_0_fdma_wsize(fdma_wsize),
.FDMA_S_0_fdma_wvalid(fdma_wvalid),
.sysclk_clk_n(sysclk_n),
.sysclk_clk_p(sysclk_p),
.ui_clk(ui_clk)
);
endmodule
以上代码中调用的system.bd的图形代码接口。在状态机中,每次写262个长度32bit的数据,再读出来判断数据是否正确。
7.8仿真文件
添加仿真文件
添加完成后:
仿真文件非常简单,只要提供时钟激励就可以。
module fdma_axi_slave_test_tb();
reg sysclk_p;
fdma_axi_slave_test fdma_axi_slave_test_inst
(
.sysclk_p(sysclk_p),
.sysclk_n(~sysclk_p)
);
initial begin
sysclk_p = 0;
#100;
end
always #10 sysclk_p = ~sysclk_p;
endmodule
7.9实验结果
FDMA写操作仿真波形图,一次完成的FDMA写操作时序图如下:
这里一次wburst_len_req多产生一次,但是结果却不影响,大家可以思考下。如何设计出来和我们之前绘制的波形图一样。
一次FDMA写传输的起始时序
连续burst,自动管理burst长度,以及一次FDMA写传输结束时序
FDMA读操作仿真波形图,一次完成的FDMA读操作时序图如下:
这里一次rburst_len_req多产生一次,但是结果却不影响,大家可以思考下。如何设计出来和我们之前绘制的波形图一样。和写操作不同,可以看到读操作的等待较长时间后才获取到数据。
一次FDMA读传输的起始时序
连续burst,自动管理burst长度,以及一次FDMA读传输结束时序
另外放到后可以看到rvalid不是连续的,这个读者也可以自己去优化saxi_ful_mem ip让这IP支持连续的rvalid