[X]关闭

米联客-XILINX-H3_CZ08_7100] FPGA_AXI总线入门连载-07AXI4-FULL-MASTER IP FDMA 详解

文档创建者:FPGA课程
浏览次数:78
最后更新:2024-10-14
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 3-FPGA AXI 总线入门
本帖最后由 FPGA课程 于 2024-10-14 18:21 编辑

​软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!

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工程师发现,因此给了我们很多宝贵意见。借此2024版本教程更新发布之际,我们也对FDMA1.0版本升级到FDMA3.2版本。解决3.1版本中,当总的burst长度是奇数的时候出现错误,修改端口命名规则,设置I代表了输入信号,O代表了输出信号。
从本文开始,我们从多个应用方案来演示FDMA的用途。
本文实验目的:
1:分析FDMA源码,掌握基于FDMA的APP接口实现AXI4-FULL总线接口的访问。
2:掌握自定义总线接口封装方法
3:自定义AXI-FULL-Slave IP用于验证FDMA的工作情况。

2AXI 总线协议介绍
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 协议不再本文中接收, 后面有单独介绍的文章。
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 从机
                        
                        
表明从机可以接收写数据
                        
2.3 数据有效的情况
AXI4 所采用的是一种 READY ,VALID 握手通信机制,简单来说主从双方进行数据通信前,有一个握手的过  程。传输源产生 VLAID 信号来指明何时数据或控制信息有效。而目地源产生 READY 信号来指明已经准备好接受  数据或控制信息。传输发生在 VALID 和 READY 信号同时为高的时候。VALID 和 READY 信号的出现有三种关系。
(4) VALID 先变高 READY 后变高。时序图如下:

4b243b4a1f2143588472b1cae78a44d0.jpg
在箭头处信息传输发生。
(5) READY 先变高 VALID 后变高。时序图如下:
15a20f2adf604b00aa64a60ca05b72b1.jpg
同样在箭头处信息传输发生。
(6) VALID 和 READY 信号同时变高。时序图如下:
1a32af25973d4d39a13592314b3f9d20.jpg
在这种情况下,信息传输立马发生,如图箭头处指明信息传输发生。
2.4 突发式读写
1:突发式写时序图
5e8db606f1be45bbb81671a67553e567.jpg

92e9ffcc82504ae8a26a407b21aadb0b.jpg
这一过程的开始时,主机发送地址和控制信息到写地址通道中,然后主机发送每一个写数据到写数据通道中。 当主机发送最后一个数据时,WLAST 信号就变为高。当设备接收完所有数据之后他将一个写响应发送回主机来表 明写事务完成。
2:突发式读的时序图
8ee840071d5c42d4ae7f6fe7d05efd5a.jpg
当地址出现在地址总线后,传输的数据将出现在读数据通道上。设备保持 VALID 为低直到读数据有效。为了 表明一次突发式读写的完成,设备用RLAST 信号来表示最后一个被传输的数据。
3 FDMA源码分析
由于AXI4总线协议直接操作起来相对复杂一些,容易出错,因此我们封装一个简单的用户接口,间接操作AXI4总线会带来很多方便性。先看下我们计划设计一个怎么样的用户接口。
1:FDMA的写时序
d0d6c68b89e0429e9db2522a7f67935d.jpg
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的读时序
546096f75f5d41e291355b0815c633f5.jpg
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:FDMAAXI4-Master写操作
以下代码中我们给出axi4-master写操作的代码分析注释
  1. //fdma axi write----------------------------------------------
  2. reg     [M_AXI_ADDR_WIDTH-1 : 0]  axi_awaddr  =0; //AXI4 写地址
  3. reg                                    axi_awvalid = 1'b0; //AXI4 写地有效
  4. wire    [M_AXI_DATA_WIDTH-1 : 0]  axi_wdata   ; //AXI4 写数据
  5. wire                                   axi_wlast   ; //AXI4 写LAST信号
  6. reg                                    axi_wvalid  = 1'b0; //AXI4 写数据有效
  7. wire                                   w_next= (M_AXI_WVALID & M_AXI_WREADY);//当valid ready信号都有效,代表AXI4数据传输有效
  8. reg   [8 :0]                     wburst_len  = 1  ; //写传输的axi burst长度,代码会自动计算每次axi传输的burst 长度
  9. reg   [8 :0]                     wburst_cnt  = 0  ; //每次axi bust的计数器
  10. reg   [15:0]                     wfdma_cnt   = 0  ;//fdma的写数据计数器
  11. reg                              axi_wstart_locked  =0;  //axi 传输进行中,lock住,用于时序控制
  12. wire  [15:0] axi_wburst_size   =    wburst_len * AXI_BYTES;//axi 传输的地址长度计算

  13. assign M_AXI_AWID        = M_AXI_ID; //写地址ID,用来标志一组写信号, M_AXI_ID是通过参数接口定义
  14. assign M_AXI_AWADDR     = axi_awaddr;
  15. assign M_AXI_AWLEN      = wburst_len - 1;//AXI4 burst的长度
  16. assign M_AXI_AWSIZE      = clogb2(AXI_BYTES-1);
  17. assign M_AXI_AWBURST    = 2'b01;//AXI4的busr类型INCR模式,地址递增
  18. assign M_AXI_AWLOCK     = 1'b0;
  19. assign M_AXI_AWCACHE    = 4'b0010;//不使用cache,不使用buffer
  20. assign M_AXI_AWPROT     = 3'h0;
  21. assign M_AXI_AWQOS      = 4'h0;
  22. assign M_AXI_AWVALID    = axi_awvalid;
  23. assign M_AXI_WDATA      = axi_wdata;
  24. assign M_AXI_WSTRB      = {(AXI_BYTES){1'b1}};//设置所有的WSTRB为1代表传输的所有数据有效
  25. assign M_AXI_WLAST      = axi_wlast;
  26. assign M_AXI_WVALID     = axi_wvalid & fdma_wready;//写数据有效,这里必须设置fdma_wready有效
  27. assign M_AXI_BREADY     = 1'b1;
  28. //----------------------------------------------------------------------------  
  29. //AXI4 FULL Write
  30. assign  axi_wdata        = fdma_wdata;
  31. assign  fdma_wvalid      = w_next;
  32. reg     fdma_wstart_locked = 1'b0;
  33. wire    fdma_wend;
  34. wire    fdma_wstart;
  35. assign   fdma_wbusy = fdma_wstart_locked ;
  36. //在整个写过程中fdma_wstart_locked将保持有效,直到本次FDMA写结束
  37. always @(posedge M_AXI_ACLK)
  38.     if(M_AXI_ARESETN == 1'b0 || fdma_wend == 1'b1 )
  39.         fdma_wstart_locked <= 1'b0;
  40.     else if(fdma_wstart)
  41.         fdma_wstart_locked <= 1'b1;                                
  42. //产生fdma_wstart信号,整个信号保持1个  M_AXI_ACLK时钟周期
  43. assign fdma_wstart = (fdma_wstart_locked == 1'b0 && fdma_wareq == 1'b1);   
  44.         
  45. //AXI4 write burst lenth busrt addr ------------------------------
  46. //当fdma_wstart信号有效,代表一次新的FDMA传输,首先把地址本次fdma的burst地址寄存到axi_awaddr作为第一次axi burst的地址。如果fdma的数据长度大于256,那么当axi_wlast有效的时候,自动计算下次axi的burst地址
  47. always @(posedge M_AXI_ACLK)
  48.     if(fdma_wstart)   
  49.         axi_awaddr <= fdma_waddr;
  50.     else if(axi_wlast == 1'b1)
  51.         axi_awaddr <= axi_awaddr + axi_wburst_size ;                    
  52. //AXI4 write cycle -----------------------------------------------
  53. //axi_wstart_locked_r1, axi_wstart_locked_r2信号是用于时序同步
  54. reg axi_wstart_locked_r1 = 1'b0, axi_wstart_locked_r2 = 1'b0;
  55. always @(posedge M_AXI_ACLK)begin
  56.     axi_wstart_locked_r1 <= axi_wstart_locked;
  57.     axi_wstart_locked_r2 <= axi_wstart_locked_r1;
  58. end
  59. // axi_wstart_locked的作用代表一次axi写burst操作正在进行中。
  60. always @(posedge M_AXI_ACLK)
  61.     if((fdma_wstart_locked == 1'b1) &&  axi_wstart_locked == 1'b0)
  62.         axi_wstart_locked <= 1'b1;
  63.     else if(axi_wlast == 1'b1 || fdma_wstart == 1'b1)
  64.         axi_wstart_locked <= 1'b0;
  65.         
  66. //AXI4 addr valid and write addr-----------------------------------
  67. always @(posedge M_AXI_ACLK)
  68.      if((axi_wstart_locked_r1 == 1'b1) &&  axi_wstart_locked_r2 == 1'b0)
  69.          axi_awvalid <= 1'b1;
  70.      else if((axi_wstart_locked == 1'b1 && M_AXI_AWREADY == 1'b1)|| axi_wstart_locked == 1'b0)
  71.          axi_awvalid <= 1'b0;      
  72. //AXI4 write data---------------------------------------------------        
  73. always @(posedge M_AXI_ACLK)
  74.     if((axi_wstart_locked_r1 == 1'b1) &&  axi_wstart_locked_r2 == 1'b0)
  75.         axi_wvalid <= 1'b1;
  76.     else if(axi_wlast == 1'b1 || axi_wstart_locked == 1'b0)
  77.         axi_wvalid <= 1'b0;//   
  78. //AXI4 write data burst len counter----------------------------------
  79. always @(posedge M_AXI_ACLK)
  80.     if(axi_wstart_locked == 1'b0)
  81.         wburst_cnt <= 'd0;
  82.     else if(w_next)
  83.         wburst_cnt <= wburst_cnt + 1'b1;   
  84.             
  85. assign axi_wlast = (w_next == 1'b1) && (wburst_cnt == M_AXI_AWLEN);
  86. //fdma write data burst len counter----------------------------------
  87. reg wburst_len_req = 1'b0;
  88. reg [15:0] fdma_wleft_cnt =16'd0;

  89. // wburst_len_req信号是自动管理每次axi需要burst的长度
  90. always @(posedge M_AXI_ACLK)
  91.         wburst_len_req <= fdma_wstart|axi_wlast;

  92. // fdma_wleft_cnt用于记录一次FDMA剩余需要传输的数据数量  
  93. always @(posedge M_AXI_ACLK)
  94.     if( fdma_wstart )begin
  95.         wfdma_cnt <= 1'd0;
  96.         fdma_wleft_cnt <= fdma_wsize;
  97.     end
  98.     else if(w_next)begin
  99.         wfdma_cnt <= wfdma_cnt + 1'b1;  
  100.         fdma_wleft_cnt <= (fdma_wsize - 1'b1) - wfdma_cnt;
  101.     end
  102. //当最后一个数据的时候,产生fdma_wend信号代表本次fdma传输结束
  103. assign  fdma_wend = w_next && (fdma_wleft_cnt == 1 );
  104. //一次axi最大传输的长度是256因此当大于256,自动拆分多次传输
  105. always @(posedge M_AXI_ACLK)begin
  106.      if(wburst_len_req)begin
  107.         if(fdma_wleft_cnt[15:8] >0)  wburst_len <= 256;
  108.         else
  109.             wburst_len <= fdma_wleft_cnt[7:0];
  110.      end
  111.      else wburst_len <= wburst_len;
  112. end
复制代码

以上代码我们进行了详细的注释性分析。以下给出FDMA写操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,需要2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。
47e78111340b4b928a629e2e58dd9edf.jpg
4:FDMAAXI4-Master读操作
以下代码中我们给出axi4-master读操作的代码分析注释
  1. //fdma axi read----------------------------------------------
  2. reg     [M_AXI_ADDR_WIDTH-1 : 0]    axi_araddr =0   ; //AXI4 读地址
  3. reg                         axi_arvalid  =1'b0; //AXI4读地有效
  4. wire                        axi_rlast   ; //AXI4 读LAST信号
  5. reg                         axi_rready  = 1'b0;//AXI4读准备好
  6. wire                              r_next      = (M_AXI_RVALID && M_AXI_RREADY);// 当valid ready信号都有效,代表AXI4数据传输有效
  7. reg   [8 :0]                        rburst_len  = 1  ; //读传输的axi burst长度,代码会自动计算每次axi传输的burst 长度
  8. reg   [8 :0]                        rburst_cnt  = 0  ; //每次axi bust的计数器
  9. reg   [15:0]                       rfdma_cnt   = 0  ; //fdma的读数据计数器
  10. reg                               axi_rstart_locked =0; //axi 传输进行中,lock住,用于时序控制
  11. wire  [15:0] axi_rburst_size   =   rburst_len * AXI_BYTES; //axi 传输的地址长度计算  

  12. assign M_AXI_ARID       = M_AXI_ID; //读地址ID,用来标志一组写信号, M_AXI_ID是通过参数接口定义
  13. assign M_AXI_ARADDR     = axi_araddr;
  14. assign M_AXI_ARLEN      = rburst_len - 1; //AXI4 burst的长度
  15. assign M_AXI_ARSIZE     = clogb2((AXI_BYTES)-1);
  16. assign M_AXI_ARBURST    = 2'b01; //AXI4的busr类型INCR模式,地址递增
  17. assign M_AXI_ARLOCK     = 1'b0; //不使用cache,不使用buffer
  18. assign M_AXI_ARCACHE    = 4'b0010;
  19. assign M_AXI_ARPROT     = 3'h0;
  20. assign M_AXI_ARQOS      = 4'h0;
  21. assign M_AXI_ARVALID    = axi_arvalid;
  22. assign M_AXI_RREADY     = axi_rready&&fdma_rready; //读数据准备好,这里必须设置fdma_rready有效
  23. assign fdma_rdata       = M_AXI_RDATA;   
  24. assign fdma_rvalid      = r_next;   

  25. //AXI4 FULL Read-----------------------------------------   

  26. reg     fdma_rstart_locked = 1'b0;
  27. wire    fdma_rend;
  28. wire    fdma_rstart;
  29. assign   fdma_rbusy = fdma_rstart_locked ;
  30. //在整个读过程中fdma_rstart_locked将保持有效,直到本次FDMA写结束
  31. always @(posedge M_AXI_ACLK)
  32.     if(M_AXI_ARESETN == 1'b0 || fdma_rend == 1'b1)
  33.         fdma_rstart_locked <= 1'b0;
  34.     else if(fdma_rstart)
  35.         fdma_rstart_locked <= 1'b1;                                
  36. //产生fdma_rstart信号,整个信号保持1个  M_AXI_ACLK时钟周期
  37. assign fdma_rstart = (fdma_rstart_locked == 1'b0 && fdma_rareq == 1'b1);   
  38. //AXI4 read burst lenth busrt addr ------------------------------
  39. //当fdma_rstart信号有效,代表一次新的FDMA传输,首先把地址本次fdma的burst地址寄存到axi_araddr作为第一次axi burst的地址。如果fdma的数据长度大于256,那么当axi_rlast有效的时候,自动计算下次axi的burst地址
  40. always @(posedge M_AXI_ACLK)
  41.     if(fdma_rstart == 1'b1)   
  42.         axi_araddr <= fdma_raddr;
  43.     else if(axi_rlast == 1'b1)
  44.         axi_araddr <= axi_araddr + axi_rburst_size ;                                                
  45. //AXI4 r_cycle_flag-------------------------------------   
  46. //axi_rstart_locked_r1, axi_rstart_locked_r2信号是用于时序同步
  47. reg axi_rstart_locked_r1 = 1'b0, axi_rstart_locked_r2 = 1'b0;
  48. always @(posedge M_AXI_ACLK)begin
  49.     axi_rstart_locked_r1 <= axi_rstart_locked;
  50.     axi_rstart_locked_r2 <= axi_rstart_locked_r1;
  51. end
  52. // axi_rstart_locked的作用代表一次axi读burst操作正在进行中。
  53. always @(posedge M_AXI_ACLK)
  54.     if((fdma_rstart_locked == 1'b1) &&  axi_rstart_locked == 1'b0)
  55.         axi_rstart_locked <= 1'b1;
  56.     else if(axi_rlast == 1'b1 || fdma_rstart == 1'b1)
  57.         axi_rstart_locked <= 1'b0;
  58. //AXI4 addr valid and read addr-----------------------------------  
  59. always @(posedge M_AXI_ACLK)
  60.      if((axi_rstart_locked_r1 == 1'b1) &&  axi_rstart_locked_r2 == 1'b0)
  61.          axi_arvalid <= 1'b1;
  62.      else if((axi_rstart_locked == 1'b1 && M_AXI_ARREADY == 1'b1)|| axi_rstart_locked == 1'b0)
  63.          axi_arvalid <= 1'b0;      
  64. //AXI4 read data---------------------------------------------------     
  65. always @(posedge M_AXI_ACLK)
  66.     if((axi_rstart_locked_r1 == 1'b1) &&  axi_rstart_locked_r2 == 1'b0)
  67.         axi_rready <= 1'b1;
  68.     else if(axi_rlast == 1'b1 || axi_rstart_locked == 1'b0)
  69.         axi_rready <= 1'b0;//   
  70. //AXI4 read data burst len counter----------------------------------
  71. always @(posedge M_AXI_ACLK)
  72.     if(axi_rstart_locked == 1'b0)
  73.         rburst_cnt <= 'd0;
  74.     else if(r_next)
  75.         rburst_cnt <= rburst_cnt + 1'b1;            
  76. assign axi_rlast = (r_next == 1'b1) && (rburst_cnt == M_AXI_ARLEN);
  77. //fdma read data burst len counter----------------------------------
  78. reg rburst_len_req = 1'b0;
  79. reg [15:0] fdma_rleft_cnt =16'd0;
  80. // rburst_len_req信号是自动管理每次axi需要burst的长度  
  81. always @(posedge M_AXI_ACLK)
  82.         rburst_len_req <= fdma_rstart | axi_rlast;  
  83. // fdma_rleft_cnt用于记录一次FDMA剩余需要传输的数据数量         
  84. always @(posedge M_AXI_ACLK)
  85.     if(fdma_rstart )begin
  86.         rfdma_cnt <= 1'd0;
  87.         fdma_rleft_cnt <= fdma_rsize;
  88.     end
  89.     else if(r_next)begin
  90.         rfdma_cnt <= rfdma_cnt + 1'b1;  
  91.         fdma_rleft_cnt <= (fdma_rsize - 1'b1) - rfdma_cnt;
  92.     end
  93. //当最后一个数据的时候,产生fdma_rend信号代表本次fdma传输结束
  94. assign  fdma_rend = r_next && (fdma_rleft_cnt == 1 );
  95. //axi auto burst len caculate-----------------------------------------
  96. //一次axi最大传输的长度是256因此当大于256,自动拆分多次传输
  97. always @(posedge M_AXI_ACLK)begin
  98.      if(rburst_len_req)begin
  99.         if(fdma_rleft_cnt[15:8] >0)  
  100.             rburst_len <= 256;
  101.         else
  102.             rburst_len <= fdma_rleft_cnt[7:0];
  103.      end
  104.      else rburst_len <= rburst_len;
  105. end
复制代码


以上代码我们进行了详细的注释性分析。FDMA的读写代码高度对称,以上源码和以下波形图都和写操作类似,理解起会提高很多效率。
以下给出FDMA写操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,需要2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。
fc7043437d3840e2b04f70be0fe1a064.jpg
4FDMAIP的封装
我先讲解如何封装FDMA IP,之后再分析源码。封装IP少不了源码,这里是利用已经编写好的uiFDMA.v进行封装。
默认的源码路径在配套的工程uisrc/uifdma路径下
34d4a5069b6b40a78c459bf4df659393.jpg
创建一个新的空的fpga工程
ec9d2018129947c6a47e8fd589406d50.jpg
0dd6da8ff3ea4e0d8d36c74555d23e7e.jpg
448bfb39eb0e4fdc96af14710d2e6620.jpg
9c2feb8d59724f508fdaa243d09239bc.jpg
添加uiFDMA.v源码
c92e9092cc1f41c4b13ea2da9528c684.jpg
93c5efa9fa5e4fc8a1b9f650c5c78782.jpg
c5b15f79859748469afe0d62ead5e175.jpg
创建IP
da6a71e2729542d2a9f8be1ede6842ad.jpg
44c91d78d8564303b50c5d48477e5c52.jpg
选择Package your current project
490927611dbf4434a8b2495af1e8299a.jpg
94e455470f624b1eb47e8bac2bc8d0d0.jpg
5e35c4843649447e8d13be00666ec704.jpg
c9866f8667fd4d53849453300e43655a.jpg
按住shift全选后,右击弹出菜单后选择Create Interface Definition
888146df90174ec8801cffd40dbe30f1.jpg
接口定义为slave,命名为FDMA
90c54cb1d96943a4a243fb81904144db.jpg
设置完成,uisrc/03_ip/uifdma路径下多出2个文件,这个两个文件就是定义了自定义的总线接口。
566b1a2dc1564e199b1ebb49019d2d6b.jpg
现在可以看到封装后的总线
7d6b8da724584b95a545845a3cd40e33.jpg
建议把名字改简洁一些
d14119d25d934e6c8d30b5ac7664b64e.jpg

可以看到封装好的接口,更加美观
710976030e7b4a0d8bc0e4c2caba1a36.jpg

31ced7c56e4248e99086589feee31fac.jpg
5saxi_full_memIP
这个IP的源码可以基于axi-full-slave的模板简单修改就可以实现。找到以下路径,中saxi_full_v1_0_S00_AXI.v文件,并且对齐修改。
1286eadb2fff42ee9dd76bb71a79316c.jpg
我们把修改后的代码命名为saxi_full_mem.v修改其中的部分代码,关键部分是memory部分定义。
7ac997390a2a428fa46a2af62166eb11.jpg
修改的这部分代码支持Memory的任意长度设置(FPGA内部RAM会消耗资源),其中参数USER_NUM_MEM用于定义RAM的长度,我们一次FDMA的burst长度应该小于等于USER_NUM_MEM这个参数。
我们来看下IP的接口参数设置:这里我们计划FDMA的读写长度是262,设置USER_NUM_MEM=300完全够用。
f1ba7852e7b441d69b992244a26e2c81.jpg
6创建FPGA图像化设计
66be27ea11e74c309370f67c10a701d3.jpg

设置IP路径
b944deb01f84435ea0119ed2533add4b.jpg

22bad2398c4042868010979087604ae4.jpg
添加已经创建好的IP
7a99ff4899dd4cfdb08a230f4a2b21de.jpg
输入关键词fdma,在最后可以看到,双击添加Ip
b57dd85510c64eeba6555dd79d236a8b.jpg

可以看到本文的FDMA版本升级到3.2版本, 解决3.1版本中,当总的burst长度是奇数的时候出现错误,修改端口命名规则,设置I代表了输入信号,O代表了输出信号
e7db85fe1ffe4e5da0b502fbcf4305f6.jpg
完成连线
继续添加剩余IP

abdc0d295f46410fb7fb57c570b14009.jpg

ff7ce22f5df84674abed83c30b1408b4.jpg
a4908b44f4a4438898389d9f53d87194.jpg
设置IP参数
fb63cc0c791c45b08f88bd8b1362e8dd.jpg
e7afce06dec44c1a9e16f9f82bf30ea9.jpg
46d45969676a4eb4bfc6e55eb2996e6f.jpg
7849c53ec0fc4e1b9d758224901cee32.jpg
完成连线
设置地址分配:
5ed5714f1dff4ac59265d98fa839d8c3.jpg
7添加FDMA接口控制代码
629d20d1182d47be831cc427d79bec2f.jpg

aee3e2ce4ce04cc784bf3146d138bf13.jpg
baf30a5bf2b345dda2c87859cadc3560.jpg
添加完成后如下图:
527a9fbb07b745ecaa04177b1923e761.jpg
fdma_axi_slave_test.v源码如下

  1. `timescale 1ns / 1ps
  2. module fdma_axi_slave_test(
  3.   input sysclk
  4. );

  5. wire [31:0]   fdma_raddr;
  6. reg           fdma_rareq;
  7. wire          fdma_rbusy;
  8. wire [31:0]   fdma_rdata;
  9. wire [15:0]   fdma_rsize;
  10. wire          fdma_rvalid;
  11. wire [31:0]   fdma_waddr;
  12. reg           fdma_wareq;
  13. wire          fdma_wbusy;
  14. wire [31:0]   fdma_wdata;
  15. wire [15:0]   fdma_wsize;
  16. wire          fdma_wvalid;
  17. wire          ui_clk;

  18. parameter TEST_MEM_SIZE   = 32'd4*20;
  19. parameter FDMA_BURST_LEN  = 16'd262;
  20. parameter ADDR_MEM_OFFSET = 0;
  21. parameter ADDR_INC = 0;

  22. parameter WRITE1 = 0;
  23. parameter WRITE2 = 1;
  24. parameter WAIT   = 2;
  25. parameter READ1  = 3;
  26. parameter READ2  = 4;

  27. reg [127: 0] t_data;
  28. reg [31: 0] fdma_waddr_r;
  29. reg [2  :0] T_S = 0;

  30. assign fdma_waddr = fdma_waddr_r + ADDR_MEM_OFFSET;
  31. assign fdma_raddr = fdma_waddr;

  32. assign fdma_wsize = FDMA_BURST_LEN;
  33. assign fdma_rsize = FDMA_BURST_LEN;
  34. assign fdma_wdata ={t_data,t_data,t_data,t_data};
  35.   
  36.   
  37. //delay reset
  38. reg [8:0] rst_cnt = 0;
  39. always @(posedge ui_clk)
  40.     if(rst_cnt[8] == 1'b0)
  41.          rst_cnt <= rst_cnt + 1'b1;
  42.      else
  43.          rst_cnt <= rst_cnt;

  44. always @(posedge ui_clk)begin
  45.     if(rst_cnt[8] == 1'b0)begin
  46.         T_S <=0;   
  47.         fdma_wareq  <= 1'b0;
  48.         fdma_rareq  <= 1'b0;
  49.         t_data<=0;
  50.         fdma_waddr_r <=0;      
  51.     end
  52.     else begin
  53.         case(T_S)      
  54.         WRITE1:begin
  55.             if(fdma_waddr_r==TEST_MEM_SIZE) fdma_waddr_r<=0;
  56.                 if(!fdma_wbusy)begin
  57.                     fdma_wareq  <= 1'b1;
  58.                     t_data  <= 0;
  59.                 end
  60.                 if(fdma_wareq&&fdma_wbusy)begin
  61.                     fdma_wareq  <= 1'b0;
  62.                     T_S         <= WRITE2;
  63.                 end
  64.         end
  65.         WRITE2:begin
  66.             if(!fdma_wbusy) begin
  67.                  T_S <= WAIT;
  68.                  t_data  <= 32'd0;
  69.             end
  70.             else if(fdma_wvalid) begin
  71.                 t_data <= t_data + 1'b1;
  72.             end
  73.         end
  74.         WAIT:begin//not needed
  75.             T_S <= READ1;
  76.         end
  77.         READ1:begin
  78.             if(!fdma_rbusy)begin
  79.                 fdma_rareq  <= 1'b1;
  80.                 t_data   <= 0;
  81.             end
  82.             if(fdma_rareq&&fdma_rbusy)begin
  83.                  fdma_rareq  <= 1'b0;
  84.                  T_S         <= READ2;
  85.             end
  86.         end
  87.         READ2:begin
  88.             if(!fdma_rbusy) begin
  89.                  T_S <= WRITE1;
  90.                  t_data  <= 32'd0;
  91.                  fdma_waddr_r  <= fdma_waddr_r + ADDR_INC;//128/8=16
  92.             end
  93.             else if(fdma_rvalid) begin
  94.                 t_data <= t_data + 1'b1;
  95.             end
  96.         end   
  97.         default:
  98.             T_S <= WRITE1;     
  99.         endcase
  100.     end
  101.   end
  102.   
  103. wire test_error = (fdma_rvalid && (t_data[15:0] != fdma_rdata[15:0]));


  104.   system system_i
  105.        (.FDMA_S_0_i_fdma_raddr(fdma_raddr),
  106.         .FDMA_S_0_i_fdma_rareq(fdma_rareq),
  107.         .FDMA_S_0_o_fdma_rbusy(fdma_rbusy),
  108.         .FDMA_S_0_o_fdma_rdata(fdma_rdata),
  109.         .FDMA_S_0_i_fdma_rready(1'b1),
  110.         .FDMA_S_0_i_fdma_rsize(fdma_rsize),
  111.         .FDMA_S_0_o_fdma_rvalid(fdma_rvalid),
  112.         .FDMA_S_0_i_fdma_waddr(fdma_waddr),
  113.         .FDMA_S_0_i_fdma_wareq(fdma_wareq),
  114.         .FDMA_S_0_o_fdma_wbusy(fdma_wbusy),
  115.         .FDMA_S_0_i_fdma_wdata(fdma_wdata),
  116.         .FDMA_S_0_i_fdma_wready(1'b1),
  117.         .FDMA_S_0_i_fdma_wsize(fdma_wsize),
  118.         .FDMA_S_0_o_fdma_wvalid(fdma_wvalid),   
  119.         .sysclk(sysclk),
  120.         .ui_clk(ui_clk)
  121.         );        

  122. endmodule
复制代码

以上代码中调用的system.bd的图形代码接口。在状态机中,每次写262个长度32bit的数据,再读出来判断数据是否正确。
​​ 8仿真文件
添加仿真文件
bdb196c65d954ff6b3336dab294b4b4b.jpg
添加完成后:
f94159d1a6914245a2d695fcda09caec.jpg
仿真文件非常简单,只要提供时钟激励就可以。
  1. `timescale 1ns / 1ps
  2. module fdma_axi_slave_test_tb();

  3. reg sysclk;

  4. fdma_axi_slave_test fdma_axi_slave_test_inst
  5. (
  6.     .sysclk(sysclk)

  7. );

  8. initial begin
  9.      sysclk  = 0;
  10.      #100;
  11. end

  12.     always #5 sysclk = ~sysclk;   

  13. endmodule
复制代码


9实验结果
FDMA写操作仿真波形图,一次完成的FDMA写操作时序图如下:
这里一次wburst_len_req多产生一次,但是结果却不影响,大家可以思考下。如何设计出来和我们之前绘制的波形图一样。
a3d6c4c21bc64a918dd6ee967137f151.jpg
一次FDMA写传输的起始时序
cc5a8f89bc23435abe77737b2877bbb4.jpg
连续burst,自动管理burst长度,以及一次FDMA写传输结束时序
15186ad6c45e4a29a500c200bce0f53c.jpg

FDMA读操作仿真波形图,一次完成的FDMA读操作时序图如下:
这里一次rburst_len_req多产生一次,但是结果却不影响,大家可以思考下。如何设计出来和我们之前绘制的波形图一样。和写操作不同,可以看到读操作的等待较长时间后才获取到数据。
0504356d1ec7418f96466d26c4b964dc.jpg
一次FDMA读传输的起始时序
d249eea0b20f40f28744475b1e3a8450.jpg

连续burst,自动管理burst长度,以及一次FDMA读传输结束时序
另外放到后可以看到rvalid不是连续的,这个读者也可以自己去优化saxi_ful_mem ip让这IP支持连续的rvalid
76c3f843bc4d4f8aa1444975484c9227.jpg
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则