08 AXI4-FULL-MASTER IP FDMA详解
软件版本:vitis2021.1(vivado2021.1)
操作系统:WIN10 64bit
硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA
登录"米联客"FPGA社区-www.uisrc.com视频课程、答疑解惑!
1概述
1:分析FDMA源码,掌握基于FDMA的APP接口实现AXI4-FULL总线接口的访问。
3:自定义AXI-FULL-Slave IP用于验证FDMA的工作情况。
2 FDMA源码分析
由于AXI4总线协议直接操作起来相对复杂一些,容易出错,因此我们封装一个简单的用户接口,间接操作AXI4总线会带来很多方便性。先看下我们计划设计一个怎么样的用户接口。
1:FDMA的写时序
2:FDMA的读时序
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 |
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个长度的数据。
4FDMA IP的封装
我先讲解如何封装FDMA IP,之后再分析源码。封装IP少不了源码,这里是利用已经编写好的uiFDMA.v进行封装。
选择Package your current project
按住shift全选后,右击弹出菜单后选择Create Interface Definition
设置完成,uisrc/03_ip/uifdma路径下多出2个文件,这个两个文件就是定义了自定义的总线接口。
5 saxi_full_mem IP
这个IP的源码可以基于axi-full-slave的模板简单修改就可以实现。找到以下路径,中saxi_full_v1_0_S00_AXI.v文件,并且对齐修改。
我们把修改后的代码命名为saxi_full_mem.v修改其中的部分代码,关键部分是memory部分定义。
我们来看下IP的接口参数设置:这里我们计划FDMA的读写长度是262,设置USER_NUM_MEM=300完全够用。
6创建FPGA图像化设计
可以看到本文的FDMA版本升级到3.2版本, 解决3.1版本中,当总的burst长度是奇数的时候出现错误,修改端口命名规则,设置I代表了输入信号,O代表了输出信号
7添加FDMA接口控制代码
`timescale 1ns / 1ps module fdma_axi_slave_test( input sysclk );
wire [31:0] fdma_raddr; reg fdma_rareq; wire fdma_rbusy; wire [127:0] fdma_rdata; wire [15:0] fdma_rsize; wire fdma_rvalid; wire [31:0] fdma_waddr; reg fdma_wareq; wire fdma_wbusy; wire [127: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 #10 sysclk = ~sysclk; endmodule |
9实验结果
FDMA写操作仿真波形图,一次完成的FDMA写操作时序图如下:
这里一次wburst_len_req多产生一次,但是结果却不影响,大家可以思考下。如何设计出来和我们之前绘制的波形图一样。
连续burst,自动管理burst长度,以及一次FDMA写传输结束时序
FDMA读操作仿真波形图,一次完成的FDMA读操作时序图如下:
这里一次rburst_len_req多产生一次,但是结果却不影响,大家可以思考下。如何设计出来和我们之前绘制的波形图一样。和写操作不同,可以看到读操作的等待较长时间后才获取到数据。
连续burst,自动管理burst长度,以及一次FDMA读传输结束时序
另外放到后可以看到rvalid不是连续的,这个读者也可以自己去优化saxi_ful_mem ip让这IP支持连续的rvalid
本文来米联客(milianke),作者:米联客(milianke),转载请注明原文链接:https://www.cnblogs.com/milianke/p/17936395.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)