本帖最后由 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 后变高。时序图如下:
在箭头处信息传输发生。 (5) READY 先变高 VALID 后变高。时序图如下:
同样在箭头处信息传输发生。 (6) VALID 和 READY 信号同时变高。时序图如下:
在这种情况下,信息传输立马发生,如图箭头处指明信息传输发生。 2.4 突发式读写
1:突发式写时序图
这一过程的开始时,主机发送地址和控制信息到写地址通道中,然后主机发送每一个写数据到写数据通道中。 当主机发送最后一个数据时,WLAST 信号就变为高。当设备接收完所有数据之后他将一个写响应发送回主机来表 明写事务完成。 2:突发式读的时序图
当地址出现在地址总线后,传输的数据将出现在读数据通道上。设备保持 VALID 为低直到读数据有效。为了 表明一次突发式读写的完成,设备用RLAST 信号来表示最后一个被传输的数据。 3 FDMA源码分析由于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写操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,需要2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。
4:FDMA的AXI4-Master读操作以下代码中我们给出axi4-master读操作的代码分析注释
以上代码我们进行了详细的注释性分析。FDMA的读写代码高度对称,以上源码和以下波形图都和写操作类似,理解起会提高很多效率。 以下给出FDMA写操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,需要2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。
4FDMAIP的封装我先讲解如何封装FDMA IP,之后再分析源码。封装IP少不了源码,这里是利用已经编写好的uiFDMA.v进行封装。 默认的源码路径在配套的工程uisrc/uifdma路径下
创建一个新的空的fpga工程
添加uiFDMA.v源码
创建IP
选择Package your current project
按住shift全选后,右击弹出菜单后选择Create Interface Definition
接口定义为slave,命名为FDMA
设置完成,uisrc/03_ip/uifdma路径下多出2个文件,这个两个文件就是定义了自定义的总线接口。
现在可以看到封装后的总线
建议把名字改简洁一些
可以看到封装好的接口,更加美观
5saxi_full_memIP这个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完全够用。
6创建FPGA图像化设计
设置IP路径
添加已经创建好的IP
输入关键词fdma,在最后可以看到,双击添加Ip
可以看到本文的FDMA版本升级到3.2版本, 解决3.1版本中,当总的burst长度是奇数的时候出现错误,修改端口命名规则,设置I代表了输入信号,O代表了输出信号
完成连线 继续添加剩余IP
设置IP参数
完成连线 设置地址分配:
7添加FDMA接口控制代码
添加完成后如下图:
fdma_axi_slave_test.v源码如下
- `timescale 1ns / 1ps
- module fdma_axi_slave_test(
- input sysclk
- );
- 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 [127: 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]));
-
- system system_i
- (.FDMA_S_0_i_fdma_raddr(fdma_raddr),
- .FDMA_S_0_i_fdma_rareq(fdma_rareq),
- .FDMA_S_0_o_fdma_rbusy(fdma_rbusy),
- .FDMA_S_0_o_fdma_rdata(fdma_rdata),
- .FDMA_S_0_i_fdma_rready(1'b1),
- .FDMA_S_0_i_fdma_rsize(fdma_rsize),
- .FDMA_S_0_o_fdma_rvalid(fdma_rvalid),
- .FDMA_S_0_i_fdma_waddr(fdma_waddr),
- .FDMA_S_0_i_fdma_wareq(fdma_wareq),
- .FDMA_S_0_o_fdma_wbusy(fdma_wbusy),
- .FDMA_S_0_i_fdma_wdata(fdma_wdata),
- .FDMA_S_0_i_fdma_wready(1'b1),
- .FDMA_S_0_i_fdma_wsize(fdma_wsize),
- .FDMA_S_0_o_fdma_wvalid(fdma_wvalid),
- .sysclk(sysclk),
- .ui_clk(ui_clk)
- );
- endmodule
复制代码
以上代码中调用的system.bd的图形代码接口。在状态机中,每次写262个长度32bit的数据,再读出来判断数据是否正确。 8仿真文件添加仿真文件
添加完成后:
仿真文件非常简单,只要提供时钟激励就可以。 - `timescale 1ns / 1ps
- module fdma_axi_slave_test_tb();
- reg sysclk;
- fdma_axi_slave_test fdma_axi_slave_test_inst
- (
- .sysclk(sysclk)
- );
-
- initial begin
- sysclk = 0;
- #100;
- end
- always #5 sysclk = ~sysclk;
- endmodule
复制代码
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
|