文章目录
- 一、FDMA简介
- 二、读写操作时序
- 2.1 写时序
- 2.2 读时序
- 三、FDMA源码分析
- 四、源码仿真验证
- 4.1 FDMA控制代码
- 4.2 系统框图
- 4.3 仿真结果
- 4.3.1 写通道
- 4.3.2 读通道
- 五、使用FDMA控制BRAM读写测试
- 5.1 系统框图
- 5.2 读写数据控制模块
- 5.3 仿真结果
- 5.4 下板验证
- 六、使用FDMA控制DDR3读写测试
- 6.1 BD系统框图
- 6.2 ddr读写数据控制程序
- 6.3 顶层框图
- 6.4 仿真结果
- 6.5 下板验证
- 参考
一、FDMA简介
FDMA (Fast Direct Memory Access)是米联客的基于 AXI4 总线协议定制的一个 DMA 控制器,对 AXI4-FULL 总线接口进行了封装,同时定义了简单的 APP 接口提供用户调用 AXI4 总线实现数据交互。有了这个 IP 我们可以统一实现用 FPGA 代码直接读写 PL DDR 或者 ZYNQ/ZYNQMP SOC PS 的 DDR 或者 BRAM。
二、读写操作时序
2.1 写时序
以下是FDMA的写操作时序图:
fdma_wready是用户给的,设置为1表示让FDMA准备好。当 fdma_wbusy=0 的时候代表 FDMA 的总线非忙,可以进行一次新的 FDMA 传输;这时候拉高 fdma_wreq=1,同时给出 本次fdma 突发传输的起始地址和本次需要传输的数据大小fdma_wsize(以 bytes 为单位)。当 fdma_wvalid=1 的时候需要给出有效的数据,写入 AXI 总线。当最后一个数写完后,fdma_wvalid 和fdma_wbusy 变为 0。
AXI4 总线最大的 burst lenth 是 256,而经过封装后,用户接口的 fdma_size 可以任意大小的,fdma ip 内部代码控制每次 重新计算AXI4 总线的 Burst 长度,
2.2 读时序
以下是FDMA的读操作时序图:
fdma_wready是用户给的,设置为1表示让FDMA准备好。当 fdma_rbusy=0 的时候代表 FDMA 的总线非忙,可以进行一次新的 FDMA 传输;这时候拉高 fdma_rreq=1,同时给出 本次fdma 突发传输的起始地址和本次需要传输的数据大小fdma_rsize(以 bytes 为单位)。当 fdma_rvalid=1 的时候表示读出来的数据有效。当最后一个数读完后,fdma_rvalid 和fdma_rbusy 变为 0。
三、FDMA源码分析
以下是FDMA源码,添加了个人理解的部分信号的注释;如果注释不对,还请指出:
/*******************************MILIANKE*******************************
*Company : MiLianKe Electronic Technology Co., Ltd.
*WebSite:https://www.milianke.com
*TechWeb:https://www.uisrc.com
*tmall-shop:https://milianke.tmall.com
*jd-shop:https://milianke.jd.com
*taobao-shop1: https://milianke.taobao.com
*Create Date: 2023/03/23
*Module Name:
*File Name:
*Description:
*The reference demo provided by Milianke is only used for learning.
*We cannot ensure that the demo itself is free of bugs, so users
*should be responsible for the technical problems and consequences
*caused by the use of their own products.
*Copyright: Copyright (c) MiLianKe
*All rights reserved.
*Revision: 3.2
*Signal description
*1) I_ input
*2) O_ output
*3) IO_ input output
*4) S_ system internal signal
*5) _n activ low
*6) _dg debug signal
*7) _r delay or register
*8) _s state mechine
*********************************************************************/
/*********uiFDMA(AXI-FAST DMA Controller)基于AXI总线的自定义内存控制器***********
--1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨,读写对称
--2.fdma控制信号,简化了AXI总线的控制,根据I_fdma_wsize和I_fdma_rsize可以自动完成AXI总线的控制,完成数据的搬运
--3版本号说明
--1.0 初次发布
--2.0 修改型号定义,解决1.0版本中,last信号必须连续前一个valid的bug
--3.0 修改,AXI-burst最大burst 256
--3.1 修改可以设置AXI burst长度
--3.2 解决3.1版本中,当总的burst长度是奇数的时候出现错误,修改端口命名规则,设置I代表了输入信号,O代表了输出信号
*********************************************************************/
`timescale 1ns / 1ns
module uiFDMA#
(
parameter integer M_AXI_ID_WIDTH = 3 , //ID,demo中没用到
parameter integer M_AXI_ID = 0 , //ID,demo中没用到
parameter integer M_AXI_ADDR_WIDTH = 32 , //内存地址位宽
parameter integer M_AXI_DATA_WIDTH = 128 , //AXI总线的数据位宽
parameter integer M_AXI_MAX_BURST_LEN = 64 //AXI总线的burst 大小,对于AXI4,支持任意长度(ps:AXI4协议最大支持256,如果这数据大于256,则分多次突发),对于AXI3以下最大16
)
(
input wire [M_AXI_ADDR_WIDTH-1 : 0] I_fdma_waddr ,//FDMA写通道地址
input I_fdma_wareq ,//FDMA写通道写请求
input wire [15 : 0] I_fdma_wsize ,//FDMA写通道一次突发的传输数据个数
output O_fdma_wbusy ,//FDMA处于BUSY状态,AXI总线正在写操作
input wire [M_AXI_DATA_WIDTH-1 :0] I_fdma_wdata ,//FDMA写数据
output wire O_fdma_wvalid ,//FDMA写数据有效
input wire I_fdma_wready ,//让FDMA写准备好,用户可以写数据
input wire [M_AXI_ADDR_WIDTH-1 : 0] I_fdma_raddr ,// FDMA读通道地址
input I_fdma_rareq ,// FDMA读通道请求
input wire [15 : 0] I_fdma_rsize ,// FDMA读通道一次突发的传输数据个数
output O_fdma_rbusy ,// FDMA处于BUSY状态,AXI总线正在读操作
output wire [M_AXI_DATA_WIDTH-1 :0] O_fdma_rdata ,// FDMA读数据
output wire O_fdma_rvalid ,// FDMA读数据有效
input wire I_fdma_rready ,// 让FDMA读准备好,用户可以读数据
//以下为AXI总线信号
input wire M_AXI_ACLK ,
input wire M_AXI_ARESETN ,
output wire [M_AXI_ID_WIDTH-1 : 0] M_AXI_AWID ,
output wire [M_AXI_ADDR_WIDTH-1 : 0] M_AXI_AWADDR ,
output wire [7 : 0] M_AXI_AWLEN ,
output wire [2 : 0] M_AXI_AWSIZE ,
output wire [1 : 0] M_AXI_AWBURST ,
output wire M_AXI_AWLOCK ,
output wire [3 : 0] M_AXI_AWCACHE ,
output wire [2 : 0] M_AXI_AWPROT ,
output wire [3 : 0] M_AXI_AWQOS ,
output wire M_AXI_AWVALID ,
input wire M_AXI_AWREADY ,
output wire [M_AXI_ID_WIDTH-1 : 0] M_AXI_WID ,
output wire [M_AXI_DATA_WIDTH-1 : 0] M_AXI_WDATA ,
output wire [M_AXI_DATA_WIDTH/8-1 : 0] M_AXI_WSTRB ,
output wire M_AXI_WLAST ,
output wire M_AXI_WVALID ,
input wire M_AXI_WREADY ,
input wire [M_AXI_ID_WIDTH-1 : 0] M_AXI_BID ,
input wire [1 : 0] M_AXI_BRESP ,
input wire M_AXI_BVALID ,
output wire M_AXI_BREADY ,
output wire [M_AXI_ID_WIDTH-1 : 0] M_AXI_ARID ,
output wire [M_AXI_ADDR_WIDTH-1 : 0] M_AXI_ARADDR ,
output wire [7 : 0] M_AXI_ARLEN ,
output wire [2 : 0] M_AXI_ARSIZE ,
output wire [1 : 0] M_AXI_ARBURST ,
output wire M_AXI_ARLOCK ,
output wire [3 : 0] M_AXI_ARCACHE ,
output wire [2 : 0] M_AXI_ARPROT ,
output wire [3 : 0] M_AXI_ARQOS ,
output wire M_AXI_ARVALID ,
input wire M_AXI_ARREADY ,
input wire [M_AXI_ID_WIDTH-1 : 0] M_AXI_RID ,
input wire [M_AXI_DATA_WIDTH-1 : 0] M_AXI_RDATA ,
input wire [1 : 0] M_AXI_RRESP ,
input wire M_AXI_RLAST ,
input wire M_AXI_RVALID ,
output wire M_AXI_RREADY
);
//调用函数:计算数据位宽
function integer clogb2 (input integer bit_depth);
begin
for(clogb2=0; bit_depth>0; clogb2=clogb2+1)
bit_depth = bit_depth >> 1;
end
endfunction
localparam AXI_BYTES = M_AXI_DATA_WIDTH/8; //AXI传输一个数据的字节大小
localparam [3:0] MAX_BURST_LEN_SIZE = clogb2(M_AXI_MAX_BURST_LEN) -1; //突发大小的位宽
//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); //AXI4 写数据握手信号
reg [8 :0] wburst_len = 1 ; //本次写传输的axi burst长度,代码会自动计算每次axi传输的burst 长度
reg [8 :0] wburst_cnt = 0 ; //一次突发写中,写数据个数计数器(因为AXI4一次突发最多传输256)
reg [15:0] wfdma_cnt = 0 ; //总共本次传输中FDMA写数据个数计数器
reg axi_wstart_locked =0; //axi 传输进行中,lock住,用于时序控制
wire [15:0] axi_wburst_size = wburst_len * AXI_BYTES; //AXI4 计算一次突发传输,需要累加的地址长度
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的长度,等于实际突发长度-1
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 & I_fdma_wready; //写数据有效,这里必须设置I_fdma_wready有效
assign M_AXI_BREADY = 1'b1;
//----------------------------------------------------------------------------
//AXI4 FULL Write
assign axi_wdata = I_fdma_wdata;
assign O_fdma_wvalid = w_next;
reg fdma_wstart_locked = 1'b0;
wire fdma_wend; //fdma写结束信号
wire fdma_wstart; //fdma写开始信号
assign O_fdma_wbusy = fdma_wstart_locked ; //FDAM忙信号
//在整个写过程中fdma_wstart_locked将保持有效,直到本次FDMA写结束
always @(posedge M_AXI_ACLK)
if(M_AXI_ARESETN == 1'b0 || fdma_wend == 1'b1 )
fdma_wstart_locked <= 1'b0; //如果FDMA写完成,则拉低锁信号
else if(fdma_wstart)
fdma_wstart_locked <= 1'b1; //如果FDMA开始写,则拉高锁信号
//产生fdma_wstart信号,整个信号保持1个 M_AXI_ACLK时钟周期
assign fdma_wstart = (fdma_wstart_locked == 1'b0 && I_fdma_wareq == 1'b1); //当前FDMA非忙时,请求写时,拉高一个周期的写开始信号,然后锁住
//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;
reg 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) //拉高awvalid信号
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) //锁住后就一直拉高wvalid信号
axi_wvalid <= 1'b1;
else if(axi_wlast == 1'b1 || axi_wstart_locked == 1'b0) //直到失锁或者wlast拉高
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); //写到最后一个数据后,拉高last信号
//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 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); // 当读数据握手信号
reg [8 :0] rburst_len = 1 ; //读传输的axi burst长度,代码会自动计算每次axi传输的burst 长度
reg [8 :0] rburst_cnt = 0 ; //每次突发传输中的的读数据计数器
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; //让FDMA读数据准备好,这里必须设置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
endmodule
四、源码仿真验证
打开测试的BD文件,外部输入50M晶振时钟,通过锁相环产生100M时钟给AXI接口,外部通过普通用户接口FDMA_S_0来控制FDMA的读写,FDMA通过AXI4 控制从机进行读写。
4.1 FDMA控制代码
连线完成后,添加外部的控制程序,来产生读写地址,以及读写命令,控制程序代码如下:
`timescale 1ns / 1ns
module fdma_ctrl(
input clk ,
input rst_n ,
//write
input fdma_wbusy ,
output [31:0] fdma_waddr ,
output fdma_wareq ,
output [15:0] fdma_wsize ,
input fdma_wvalid ,
output [31:0] fdma_wdata ,
//read
input fdma_rbusy ,
output [31:0] fdma_raddr ,
output fdma_rareq ,
output [15:0] fdma_rsize ,
input fdma_rvalid ,
input [31:0] fdma_rdata
);
/***************function**************/
/***************parameter*************/
localparam IDEL = 4'b0001,
WRITE = 4'b0010,
WAITE_READ = 4'b0100,
READ = 4'b1000;
localparam BURST_LEN = 300;
localparam ADDR_INR = BURST_LEN * 4;
/***************port******************/
/***************mechine***************/
reg [3:0] cur_state ;
/***************reg*******************/
reg [31:0] r_fdma_waddr ;
reg r_fdma_wareq ;
reg [31:0] r_fdma_raddr ;
reg r_fdma_rareq ;
reg [31:0] r_fdma_wdata ;
/***************wire******************/
/***************component*************/
/***************assign****************/
assign fdma_waddr = r_fdma_waddr;
assign fdma_wareq = r_fdma_wareq;
assign fdma_wsize = BURST_LEN;
assign fdma_raddr = r_fdma_raddr;
assign fdma_rareq = r_fdma_rareq;
assign fdma_rsize = BURST_LEN;
assign fdma_wdata = r_fdma_wdata;
/***************always****************/
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
cur_state <= IDEL;
r_fdma_wareq <= 1'b0;
r_fdma_waddr <= 'd0;
r_fdma_wdata <= 32'd66;
r_fdma_raddr <= 'd0;
r_fdma_rareq <= 1'b0;
end
else
case (cur_state)
IDEL: begin
if(fdma_wbusy == 1'b0)
r_fdma_wareq <= 1'b1;
else
r_fdma_wareq <= 1'b0;
if((fdma_wbusy == 1'b1) &&(r_fdma_wareq == 1'b1))
cur_state <= WRITE;
else
cur_state <= IDEL;
end
WRITE:begin
if(fdma_wbusy == 1'b0)begin
r_fdma_wdata <= 32'd66;
r_fdma_waddr <= r_fdma_waddr + ADDR_INR;
cur_state <= WAITE_READ;
end
else if(fdma_wvalid)begin
r_fdma_wdata <= r_fdma_wdata + 1'b1;
cur_state <= WRITE;
end
else begin
r_fdma_wdata <= r_fdma_wdata;
cur_state <= WRITE;
end
end
WAITE_READ:begin
if(fdma_rbusy == 1'b0)
r_fdma_rareq <= 1'b1;
else
r_fdma_rareq <= 1'b0;
if((fdma_rbusy == 1'b1) &&(r_fdma_rareq == 1'b1))
cur_state <= READ;
else
cur_state <= WAITE_READ;
end
READ:begin
if(fdma_rbusy == 1'b0)begin
cur_state <= IDEL;
r_fdma_raddr <= r_fdma_raddr + ADDR_INR;
end
else begin
r_fdma_raddr <= r_fdma_raddr;
cur_state <= READ;
end
end
default: cur_state <= IDEL;
endcase
end
endmodule
4.2 系统框图
创建一个顶层文件,把FDMA控制程序以及封装好的BD程序连接起来,入下图所示:
4.3 仿真结果
4.3.1 写通道
创建TB文件,只需要给时钟和复位的激励即可,然后开始仿真,先观察写通道:
从仿真可以看出,本次fdma从地址0开始写,数据从66开始一直累加,直到写满300个。
写入最后一个数据365后,wbusy就拉低了,表示300个数已经成功写入。
4.3.2 读通道
在rbusy非忙时写入读地址,以及读突发长度为300。
过了一段时间,读数据从66开始累加的读出来
最后一个读数据是365,和之前写入的最后一个数据一致,因此本次fdma验证完成,接下来我们用fdma来控制BRAM和DDR。
五、使用FDMA控制BRAM读写测试
5.1 系统框图
新建一个工程,创建一个BD命名为:fdma_ctrl_bram。然后添加FDMA、AXI_BRAM_Controller、BMG、以及Processor_System_Reset然后按照下图所示连线:
5.2 读写数据控制模块
再创建一个bram_rw_ctrl模块,代码如下:
`timescale 1ns / 1ns
module bram_rw_ctrl(
input clk ,
input rst_n ,
//write
input fdma_wbusy ,
output [31:0] fdma_waddr ,
output fdma_wareq ,
output [15:0] fdma_wsize ,
input fdma_wvalid ,
output [127:0] fdma_wdata ,
//read
input fdma_rbusy ,
output [31:0] fdma_raddr ,
output fdma_rareq ,
output [15:0] fdma_rsize ,
input fdma_rvalid ,
input [127:0] fdma_rdata
);
/***************function**************/
/***************parameter*************/
localparam IDEL = 4'b0001,
WRITE = 4'b0010,
WAITE_READ = 4'b0100,
READ = 4'b1000;
localparam BURST_LEN = 512;
localparam ADDR_INR = BURST_LEN * 16;
/***************port******************/
/***************mechine***************/
reg [3:0] cur_state ;
/***************reg*******************/
reg [31:0] r_fdma_waddr ;
reg r_fdma_wareq ;
reg [31:0] r_fdma_raddr ;
reg r_fdma_rareq ;
reg [127:0] r_fdma_wdata ;
/***************wire******************/
/***************component*************/
/***************assign****************/
assign fdma_waddr = r_fdma_waddr;
assign fdma_wareq = r_fdma_wareq;
assign fdma_wsize = BURST_LEN;
assign fdma_raddr = r_fdma_raddr;
assign fdma_rareq = r_fdma_rareq;
assign fdma_rsize = BURST_LEN;
assign fdma_wdata = r_fdma_wdata;
/***************always****************/
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
cur_state <= IDEL;
r_fdma_wareq <= 1'b0;
r_fdma_waddr <= 'd0;
r_fdma_wdata <= 'd66;
r_fdma_raddr <= 'd0;
r_fdma_rareq <= 1'b0;
end
else
case (cur_state)
IDEL: begin
if(fdma_wbusy == 1'b0)
r_fdma_wareq <= 1'b1;
else
r_fdma_wareq <= 1'b0;
if((fdma_wbusy == 1'b1) &&(r_fdma_wareq == 1'b1))
cur_state <= WRITE;
else
cur_state <= IDEL;
end
WRITE:begin
if(fdma_wbusy == 1'b0)begin
r_fdma_wdata <= 'd66;
r_fdma_waddr <= r_fdma_waddr + ADDR_INR;
cur_state <= WAITE_READ;
end
else if(fdma_wvalid)begin
r_fdma_wdata <= r_fdma_wdata + 1'b1;
cur_state <= WRITE;
end
else begin
r_fdma_wdata <= r_fdma_wdata;
cur_state <= WRITE;
end
end
WAITE_READ:begin
if(fdma_rbusy == 1'b0)
r_fdma_rareq <= 1'b1;
else
r_fdma_rareq <= 1'b0;
if((fdma_rbusy == 1'b1) &&(r_fdma_rareq == 1'b1))
cur_state <= READ;
else
cur_state <= WAITE_READ;
end
READ:begin
if(fdma_rbusy == 1'b0)begin
cur_state <= IDEL;
r_fdma_raddr <= r_fdma_raddr + ADDR_INR;
end
else begin
r_fdma_raddr <= r_fdma_raddr;
cur_state <= READ;
end
end
default: cur_state <= IDEL;
endcase
end
endmodule
我们控制fdma的读写地址以及写数据,每次传输突发512个数据,写数据从66开始累加,写完一次突发后就启动读,然后打开仿真如下:
5.3 仿真结果
结果如上所示,当fdma检测到写使能时,拉高w_busy信号,然后从地址0开始写数据66,一直累加。
写最后一个数据是577符合预期(66+511=577),然后启动读,然后从地址0开始读数据,读数据从66开始累加的出来。
最后一个读出的数据是577然后又启动写,如此反复,因此使用fdma读写控制bram验证成功。
5.4 下板验证
我们分配好管脚后,添加ila核抓一下rw_ctrl模块的各个信号,然后下板观察:
下板后观察,读数据从66开始累加,最后一个读数据是577,下板也没问题。
六、使用FDMA控制DDR3读写测试
前面分析了FDMA的源码以及使用FDMA控制AXI接口的BRAM的读写,由于FDMA的读写操作都是基于AXI4总线的,因此同样可以使用FDMA控制AXI接口的MIG核来实现DDR3的读写。
6.1 BD系统框图
整体系统框图与fdma_ctrl_bram相同;新建一个工程,创建一个BD命名为:fdma_ctrl_ddr。然后添加FDMA、MIG、以及Processor_System_Reset;其中MIG核的配置在《详解DDR3原理以及使用Xilinx MIG IP核(app 接口)实现DDR3读写测试》中有详细的介绍,这里不全部展开,只配置重要的部分,配置部分如下:
把以上的IP都添加后联上线,系统框图如下所示:
6.2 ddr读写数据控制程序
我们需要一个控制程序来控制整个BD系统的读写,控制程序和上面BRAM控制代码一模一样,这也体现出米联客的FDMA模块的方便性,代码如下:
`timescale 1ns / 1ns
module ddr_rw_ctrl(
input clk ,
input rst_n ,
//write
input fdma_wbusy ,
output [31:0] fdma_waddr ,
output fdma_wareq ,
output [15:0] fdma_wsize ,
input fdma_wvalid ,
output [127:0] fdma_wdata ,
//read
input fdma_rbusy ,
output [31:0] fdma_raddr ,
output fdma_rareq ,
output [15:0] fdma_rsize ,
input fdma_rvalid ,
input [127:0] fdma_rdata
);
/***************function**************/
/***************parameter*************/
localparam IDEL = 4'b0001,
WRITE = 4'b0010,
WAITE_READ = 4'b0100,
READ = 4'b1000;
localparam BURST_LEN = 512;
localparam ADDR_INR = BURST_LEN * 16;
/***************port******************/
/***************mechine***************/
reg [3:0] cur_state ;
/***************reg*******************/
reg [31:0] r_fdma_waddr ;
reg r_fdma_wareq ;
reg [31:0] r_fdma_raddr ;
reg r_fdma_rareq ;
reg [127:0] r_fdma_wdata ;
/***************wire******************/
/***************component*************/
/***************assign****************/
assign fdma_waddr = r_fdma_waddr;
assign fdma_wareq = r_fdma_wareq;
assign fdma_wsize = BURST_LEN;
assign fdma_raddr = r_fdma_raddr;
assign fdma_rareq = r_fdma_rareq;
assign fdma_rsize = BURST_LEN;
assign fdma_wdata = r_fdma_wdata;
/***************always****************/
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
cur_state <= IDEL;
r_fdma_wareq <= 1'b0;
r_fdma_waddr <= 'd0;
r_fdma_wdata <= 'd66;
r_fdma_raddr <= 'd0;
r_fdma_rareq <= 1'b0;
end
else
case (cur_state)
IDEL: begin
if(fdma_wbusy == 1'b0)
r_fdma_wareq <= 1'b1;
else
r_fdma_wareq <= 1'b0;
if((fdma_wbusy == 1'b1) &&(r_fdma_wareq == 1'b1))
cur_state <= WRITE;
else
cur_state <= IDEL;
end
WRITE:begin
if(fdma_wbusy == 1'b0)begin
r_fdma_wdata <= 'd66;
r_fdma_waddr <= r_fdma_waddr + ADDR_INR;
cur_state <= WAITE_READ;
end
else if(fdma_wvalid)begin
r_fdma_wdata <= r_fdma_wdata + 1'b1;
cur_state <= WRITE;
end
else begin
r_fdma_wdata <= r_fdma_wdata;
cur_state <= WRITE;
end
end
WAITE_READ:begin
if(fdma_rbusy == 1'b0)
r_fdma_rareq <= 1'b1;
else
r_fdma_rareq <= 1'b0;
if((fdma_rbusy == 1'b1) &&(r_fdma_rareq == 1'b1))
cur_state <= READ;
else
cur_state <= WAITE_READ;
end
READ:begin
if(fdma_rbusy == 1'b0)begin
cur_state <= IDEL;
r_fdma_raddr <= r_fdma_raddr + ADDR_INR;
end
else begin
r_fdma_raddr <= r_fdma_raddr;
cur_state <= READ;
end
end
default: cur_state <= IDEL;
endcase
end
endmodule
6.3 顶层框图
新建一个顶层模块,把上面的BD和ddr_rw_ctrl模块连起来,代码如下:
module fdma_ctrl_ddr_top (
input clk,
//ddr_interface
output [13:0] DDR3_interface_addr ,
output [2 :0] DDR3_interface_ba ,
output DDR3_interface_cas_n ,
output [0 :0] DDR3_interface_ck_n ,
output [0 :0] DDR3_interface_ck_p ,
output [0 :0] DDR3_interface_cke ,
output [0 :0] DDR3_interface_cs_n ,
output [3 :0] DDR3_interface_dm ,
inout [31:0] DDR3_interface_dq ,
inout [3 :0] DDR3_interface_dqs_n ,
inout [3 :0] DDR3_interface_dqs_p ,
output [0 :0] DDR3_interface_odt ,
output DDR3_interface_ras_n ,
output DDR3_interface_reset_n ,
output DDR3_interface_we_n ,
output init_calib_complete
);
wire [31:0] FDMA_S_i_fdma_raddr ;
wire FDMA_S_i_fdma_rareq ;
wire [15:0] FDMA_S_i_fdma_rsize ;
wire [31:0] FDMA_S_i_fdma_waddr ;
wire FDMA_S_i_fdma_wareq ;
wire [127:0] FDMA_S_i_fdma_wdata ;
wire [15:0] FDMA_S_i_fdma_wsize ;
wire FDMA_S_o_fdma_rbusy ;
wire [127:0] FDMA_S_o_fdma_rdata ;
wire FDMA_S_o_fdma_rvalid ;
wire FDMA_S_o_fdma_wbusy ;
wire FDMA_S_o_fdma_wvalid ;
wire ui_clk ;
fdma_ctrl_ddr fdma_ctrl_ddr_i(
.DDR3_interface_addr (DDR3_interface_addr ),
.DDR3_interface_ba (DDR3_interface_ba ),
.DDR3_interface_cas_n (DDR3_interface_cas_n ),
.DDR3_interface_ck_n (DDR3_interface_ck_n ),
.DDR3_interface_ck_p (DDR3_interface_ck_p ),
.DDR3_interface_cke (DDR3_interface_cke ),
.DDR3_interface_cs_n (DDR3_interface_cs_n ),
.DDR3_interface_dm (DDR3_interface_dm ),
.DDR3_interface_dq (DDR3_interface_dq ),
.DDR3_interface_dqs_n (DDR3_interface_dqs_n ),
.DDR3_interface_dqs_p (DDR3_interface_dqs_p ),
.DDR3_interface_odt (DDR3_interface_odt ),
.DDR3_interface_ras_n (DDR3_interface_ras_n ),
.DDR3_interface_reset_n (DDR3_interface_reset_n ),
.DDR3_interface_we_n (DDR3_interface_we_n ),
.FDMA_S_i_fdma_raddr (FDMA_S_i_fdma_raddr ),
.FDMA_S_i_fdma_rareq (FDMA_S_i_fdma_rareq ),
.FDMA_S_i_fdma_rready (1'b1 ),
.FDMA_S_i_fdma_rsize (FDMA_S_i_fdma_rsize ),
.FDMA_S_i_fdma_waddr (FDMA_S_i_fdma_waddr ),
.FDMA_S_i_fdma_wareq (FDMA_S_i_fdma_wareq ),
.FDMA_S_i_fdma_wdata (FDMA_S_i_fdma_wdata ),
.FDMA_S_i_fdma_wready (1'b1 ),
.FDMA_S_i_fdma_wsize (FDMA_S_i_fdma_wsize ),
.FDMA_S_o_fdma_rbusy (FDMA_S_o_fdma_rbusy ),
.FDMA_S_o_fdma_rdata (FDMA_S_o_fdma_rdata ),
.FDMA_S_o_fdma_rvalid (FDMA_S_o_fdma_rvalid ),
.FDMA_S_o_fdma_wbusy (FDMA_S_o_fdma_wbusy ),
.FDMA_S_o_fdma_wvalid (FDMA_S_o_fdma_wvalid ),
.init_calib_complete_0 (init_calib_complete ),
.sys_clk (clk ),
.ui_clk (ui_clk )
);
ddr_rw_ctrl u_ddr_rw_ctrl(
.clk ( ui_clk ),
.rst_n ( init_calib_complete ),
.fdma_wbusy ( FDMA_S_o_fdma_wbusy ),
.fdma_waddr ( FDMA_S_i_fdma_waddr ),
.fdma_wareq ( FDMA_S_i_fdma_wareq ),
.fdma_wsize ( FDMA_S_i_fdma_wsize ),
.fdma_wvalid ( FDMA_S_o_fdma_wvalid ),
.fdma_wdata ( FDMA_S_i_fdma_wdata ),
.fdma_rbusy ( FDMA_S_o_fdma_rbusy ),
.fdma_raddr ( FDMA_S_i_fdma_raddr ),
.fdma_rareq ( FDMA_S_i_fdma_rareq ),
.fdma_rsize ( FDMA_S_i_fdma_rsize ),
.fdma_rvalid ( FDMA_S_o_fdma_rvalid ),
.fdma_rdata ( FDMA_S_o_fdma_rdata )
);
endmodule
6.4 仿真结果
DDR的仿真文件不需要自己编写,只需要打开设计例程,拿出DDR3的仿真模型和一些参数文件,具体操作参考《详解DDR3原理以及使用Xilinx MIG IP核(app 接口)实现DDR3读写测试》,然后打开仿真,如下所示:
DDR的仿真需要一定时间,大概再218us的时候,DDR初始化完成信号拉高,然后就启动ddr_rw_ctrl模块,我们先来看写部分:
我们一次写突发512个数据,当ddr初始化完成后,我们拉高写使能,以及写地址从0开始,然后数据从66开始写直到66+511=577为止,
写完后,拉高读使能以及读地址,过一段时间后,读数据从66开始出来,最后一个读数据是577。
所以我们可以使用同样的fdma来控制bram和ddr的读写,具有极强的通用性。
6.5 下板验证
我们分配好管脚后,添加ila核抓一下ddr_rw_ctrl模块的各个信号,然后下板观察:
最后一个读ddr数据是577,验证完成
参考
《米联客2024版PL-DDR缓存方案》