米联客FDMA IP在安路FPGA上实现DDR视频缓存(带源码)

1 概述

本系统中,主要使用了安路PH1A系列FPGA PH1A180SFG676 FPGA芯片,该芯片具有最大226个GPIO、210K 等效LUT4s、600个DSP、129Kbit ERAM、16个PLL、2组MIPI 4Lane 或者1组MIPI 8lane CSI ,单lane速度高达2.5Gbps,具备高速Serdes速度可达12.5G可以支持PCIE3.0、HDMI4K、12G-SDI等高速应用,该FPGA还可以支持DDR3或者DDR4内存颗粒。

本系统方案中硬件使用了米联客基于PH1A系列FPGA PH1A180SFG676 FPGA芯片和米联客CAM001-CS500摄像头。

数据缓存采用米联客自研的AXI-FDMA及AXI-FDMA_DBUF IP,作用是将图像送入DDR中进行缓存之后再显示出来。

2 系统框图

3 方案介绍

3.1 uiFDMA IP分析

AXI-FDMA IP是米联客的基于AXI4总线协议定制的一个DMA控制器。本文对AXI4-FULL总线接口进行了封装,同时定义了简单的APP接口提供用户调用AXI4总线实现数据交互。这个IP 我们命名为FDMA(Fast Direct Memory Access)。

有了这个IP我们可以统一实现用FPGA代码直接读写PL的DDR,并且支持包括XILINX、安路等具有AXI接口的DDR控制或者BRAM等。

AXI-FDMA IP参数端口可以设置AXI4的最大burst长度,当挂在支持MUX的IP上,可以在多个FDMA同时使用的时候,通过设置合理的burst长度,来优化总线上某个通路同一时刻独占AXI4总线的时间。

3.1.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总线协议的使用。

以下给出FDMA写操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,如果需要MAX_BURST_LEN_SIZE 设置了最大值256,那么2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。

3.1.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总线协议的使用。

以上代码我们进行了详细的注释性分析。FDMA的读写代码高度对称,以上源码和以下波形图都和写操作类似,理解起会提高很多效率。

以下给出FDMA读操作源码部分的时序图。下图中一次传输以传输262个长度的数据为例,如果需要MAX_BURST_LEN_SIZE 设置了最大值256,那么2次AXI4 BURST才能完成,第一次传输256个长度数据,第二次传输6个长度的数据。

3.1.3 AXI-FDMA控制器源码

/*********uiFDMA(AXI-FAST DMA Controller)基于AXI总线的自定义内存控制器***********

--版本号3.1

--1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨,读写对称

--2.fdma控制信号,简化了AXI总线的控制,根据I_fdma_wsize和I_fdma_rsize可以自动完成AXI总线的控制,完成数据的搬运

*********************************************************************/

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,支持任意长度,对于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写通道一次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读通道一次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;

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);//当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 & 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;

wire    fdma_wstart;

assign   O_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 && I_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 <= 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, 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 <= 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);// 当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&&I_fdma_rready; //读数据准备好,这里必须设置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

3.2 uidbuf IP分析

FDMA-DBUF IP代码采用“对称设计”方法,读写代码对称,好处是代码结构清晰,读写过程一致,代码效率高,更加容易维护。

3.2.1: FDMA-DBUF写状态机

为了配合AXI-FDMA IP发送数据到PS,我们写了一个uifdmadbuf ip,通过这个IP把用户编写的数据时序,转为AXI-FMDA接口数据流。该IP支持视频格式的帧同步,每一帧都进行同步,也支持没有帧同步的数据流方式传输。

3.2.3: FDMA-DBUF读状态机

读数据的过程和写数据的过程是对称的,状态机如下:

为了配合AXI-FDMA IP发送数据到FPGA,我们写了一个uifdmadbuf ip,通过这个IP把用户编写的数据时序,转为AXI-FMDA接口数据流。该IP支持视频格式的帧同步,每一帧都进行同步,也支持没有帧同步的数据流方式传输。

3.2.4 uidbuf源码

`timescale 1ns / 1ns

module uidbuf#(

parameter  integer                   VIDEO_ENABLE   = 1,//使能视频帧支持功能

parameter  integer                   ENABLE_WRITE   = 1,//使能写通道

parameter  integer                   ENABLE_READ    = 1,//使能读通道

parameter  integer                   AXI_DATA_WIDTH = 128,//AXI总线数据位宽

parameter  integer                   AXI_ADDR_WIDTH = 32, //AXI总线地址位宽

parameter  integer                   W_BUFDEPTH     = 2048, //写通道AXI设置FIFO缓存大小

parameter  integer                   W_DATAWIDTH    = 32,  //写通道AXI设置数据位宽大小

parameter  [AXI_ADDR_WIDTH -1'b1: 0] W_BASEADDR     = 0, //写通道设置内存起始地址

parameter  integer                   W_DSIZEBITS    = 24, //写通道设置缓存数据的增量地址大小,用于FDMA DBUF 计算帧缓存起始地址

parameter  integer                   W_XSIZE        = 1920, //写通道设置X方向的数据大小,代表了每次FDMA 传输的数据长度

parameter  integer                   W_XSTRIDE      = 1920, //写通道设置X方向的Stride值,主要用于图形缓存应用

parameter  integer                   W_YSIZE        = 1080, //写通道设置Y方向值,代表了进行了多少次XSIZE传输

parameter  integer                   W_XDIV         = 2, //写通道对X方向数据拆分为XDIV次传输,减少FIFO的使用

parameter  integer                   W_BUFSIZE      = 3, //写通道设置帧缓存大小,目前最大支持128帧,可以修改参数支持更缓存数

parameter  integer                   R_BUFDEPTH     = 2048, //读通道AXI设置FIFO缓存大小

parameter  integer                   R_DATAWIDTH    = 32, //读通道AXI设置数据位宽大小

parameter  [AXI_ADDR_WIDTH -1'b1: 0] R_BASEADDR     = 0, //读通道设置内存起始地址

parameter  integer                   R_DSIZEBITS    = 24, //读通道设置缓存数据的增量地址大小,用于FDMA DBUF 计算帧缓存起始地址

parameter  integer                   R_XSIZE        = 1920, //读通道设置X方向的数据大小,代表了每次FDMA 传输的数据长度

parameter  integer                   R_XSTRIDE      = 1920, //读通道设置X方向的Stride值,主要用于图形缓存应用

parameter  integer                   R_YSIZE        = 1080, //读通道设置Y方向值,代表了进行了多少次XSIZE传输

parameter  integer                   R_XDIV         = 2, //读通道对X方向数据拆分为XDIV次传输,减少FIFO的使用

parameter  integer                   R_BUFSIZE      = 3 //读通道设置帧缓存大小,目前最大支持128帧,可以修改参数支持更缓存数

)

(

input wire                                  I_ui_clk, //和FDMA AXI总线时钟一致

input wire                                  I_ui_rstn, //和FDMA AXI复位一致

//sensor input -W_FIFO--------------

input wire                                  I_W_clk, //用户写数据接口时钟

input wire                                  I_W_FS, //用户写数据接口同步信号,对于非视频帧一般设置为1

input wire                                  I_W_wren, //用户写数据使能

input wire     [W_DATAWIDTH-1'b1 : 0]       I_W_data, //用户写数据

output reg     [7   :0]                     O_W_sync_cnt =0, //写通道BUF帧同步输出

input  wire    [7   :0]                     I_W_buf, // 写通道BUF帧同步输入

output wire                                 O_W_full,

//----------fdma signals write-------      

output wire    [AXI_ADDR_WIDTH-1'b1: 0]     O_fdma_waddr, //FDMA写通道地址

output wire                                 O_fdma_wareq, //FDMA写通道请求

output wire    [15  :0]                     O_fdma_wsize, //FDMA写通道一次FDMA的传输大小                                    

input  wire                                 I_fdma_wbusy, //FDMA处于BUSY状态,AXI总线正在写操作  

output wire    [AXI_DATA_WIDTH-1'b1:0]      O_fdma_wdata, //FDMA写数据

input  wire                                 I_fdma_wvalid, //FDMA 写有效

output wire                                 O_fdma_wready, //FDMA写准备好,用户可以写数据

output reg     [7   :0]                     O_fmda_wbuf =0, //FDMA的写帧缓存号输出

output wire                                 O_fdma_wirq, //FDMA一次写完成的数据传输完成后,产生中断。  

//----------fdma signals read-------  

input  wire                                 I_R_clk, //用户读数据接口时钟

input  wire                                 I_R_FS, //用户读数据接口同步信号,对于非视频帧一般设置1

input  wire                                 I_R_rden, //用户读数据使能

output wire    [R_DATAWIDTH-1'b1 : 0]       O_R_data, //用户读数据

output reg     [7   :0]                     O_R_sync_cnt =0, //读通道BUF帧同步输出

input  wire    [7   :0]                     I_R_buf, //写通道BUF帧同步输入

output wire                                 O_R_empty,

output wire    [AXI_ADDR_WIDTH-1'b1: 0]     O_fdma_raddr, // FDMA读通道地址

output wire                                 O_fdma_rareq, // FDMA读通道请求

output wire    [15: 0]                      O_fdma_rsize, // FDMA读通道一次FDMA的传输大小                                    

input  wire                                 I_fdma_rbusy, // FDMA处于BUSY状态,AXI总线正在读操作    

input  wire    [AXI_DATA_WIDTH-1'b1:0]      I_fdma_rdata, // FDMA读数据

input  wire                                 I_fdma_rvalid, // FDMA 读有效

output wire                                 O_fdma_rready, // FDMA读准备好,用户可以读数据

output reg     [7  :0]                      O_fmda_rbuf =0, // FDMA的读帧缓存号输出

output wire                                 O_fdma_rirq // FDMA一次读完成的数据传输完成后,产生中断

);    

// 计算Log2

function integer clog2;

  input integer value;

  begin 

    for (clog2=0; value>0; clog2=clog2+1)

      value = value>>1;

    end 

  endfunction

//FDMA读写状态机的状态值,一般4个状态值即可

localparam S_IDLE  =  2'd0;  

localparam S_RST   =  2'd1;  

localparam S_DATA1 =  2'd2;  

localparam S_DATA2 =  2'd3;

// 通过设置通道使能,可以优化代码的利用率

generate  if(ENABLE_WRITE == 1)begin : FDMA_WRITE_ENABLE

localparam WFIFO_DEPTH = W_BUFDEPTH; //写通道FIFO深度

localparam W_WR_DATA_COUNT_WIDTH = clog2(WFIFO_DEPTH); //计算FIFO的写通道位宽

localparam W_RD_DATA_COUNT_WIDTH = clog2(WFIFO_DEPTH*W_DATAWIDTH/AXI_DATA_WIDTH);//clog2(WFIFO_DEPTH/(AXI_DATA_WIDTH/W_DATAWIDTH))+1;

localparam WYBUF_SIZE           = (W_BUFSIZE - 1'b1); //写通道需要完成多少次XSIZE操作

localparam WY_BURST_TIMES       = (W_YSIZE*W_XDIV); //写通道需要完成的FDMA burst 操作次数,XDIV用于把XSIZE分解多次传输

localparam FDMA_WX_BURST        = (W_XSIZE*W_DATAWIDTH/AXI_DATA_WIDTH)/W_XDIV; //FDMA BURST 一次的大小

localparam WX_BURST_ADDR_INC    = (W_XSIZE*(W_DATAWIDTH/8))/W_XDIV; //FDMA每次burst之后的地址增加

localparam WX_LAST_ADDR_INC     = (W_XSTRIDE-W_XSIZE)*(W_DATAWIDTH/8) + WX_BURST_ADDR_INC; //根据stride值计算出来最后一次地址

(*mark_debug = "true"*) (* KEEP = "TRUE" *) wire  W_wren_ri = I_W_wren;

assign                                  O_fdma_wready = 1'b1;

reg                                     O_fdma_wareq_r= 1'b0;

reg                                     W_FIFO_Rst=0;

(*mark_debug = "true"*) (* KEEP = "TRUE" *)wire                                    W_FS;

(*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [1 :0]                              W_MS=0;

reg [W_DSIZEBITS-1'b1:0]                W_addr=0;

(*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [15:0]                              W_bcnt=0;

(*mark_debug = "true"*) (* KEEP = "TRUE" *)wire[W_RD_DATA_COUNT_WIDTH-1'b1 :0]     W_rcnt;

(*mark_debug = "true"*) (* KEEP = "TRUE" *)reg                                     W_REQ=0;

(*mark_debug = "true"*) (* KEEP = "TRUE" *)reg [5 :0]                              wirq_dly_cnt =0;

reg [3 :0]                              wdiv_cnt =0;

reg [7 :0]                              wrst_cnt =0;

reg [7 :0]                              O_fmda_wbufn;

(*mark_debug = "true"*) (* KEEP = "TRUE" *) wire wirq= O_fdma_wirq;

assign O_fdma_wsize = FDMA_WX_BURST;

assign O_fdma_wirq = (wirq_dly_cnt>0);

assign O_fdma_waddr = W_BASEADDR + {O_fmda_wbufn,W_addr};//由于FPGA逻辑做乘法比较复杂,因此通过设置高位地址实现缓存设置

reg [1:0] W_MS_r =0;

always @(posedge I_ui_clk) W_MS_r <= W_MS;

//每次FDMA DBUF 完成一帧数据传输后,产生中断,这个中断持续60个周期的uiclk,这里的延迟必须足够ZYNQ IP核识别到这个中断

always @(posedge I_ui_clk) begin

    if(I_ui_rstn == 1'b0)begin

        wirq_dly_cnt <= 6'd0;

        O_fmda_wbuf <=0;

    end

    else if((W_MS_r == S_DATA2) && (W_MS == S_IDLE))begin

        wirq_dly_cnt <= 60;

        O_fmda_wbuf <= O_fmda_wbufn;

    end

    else if(wirq_dly_cnt >0)

        wirq_dly_cnt <= wirq_dly_cnt - 1'b1;

end

//帧同步,对于视频有效

fs_cap #

(

.VIDEO_ENABLE(VIDEO_ENABLE)

)

fs_cap_W0

(

 .I_clk(I_ui_clk),

 .I_rstn(I_ui_rstn),

 .I_vs(I_W_FS),

 .O_fs_cap(W_FS)

);

assign O_fdma_wareq = O_fdma_wareq_r;

//写通道状态机,采用4个状态值描述

 always @(posedge I_ui_clk) begin

    if(!I_ui_rstn)begin

        W_MS         <= S_IDLE;

        W_FIFO_Rst   <= 0;

        W_addr       <= 0;

        O_W_sync_cnt <= 0;

        W_bcnt       <= 0;

        wrst_cnt     <= 0;

        wdiv_cnt     <= 0;

        O_fmda_wbufn    <= 0;

        O_fdma_wareq_r <= 1'd0;

    end  

    else begin

      case(W_MS)

        S_IDLE:begin

          W_addr <= 0;

          W_bcnt <= 0;

          wrst_cnt <= 0;

          wdiv_cnt <=0;

          if(W_FS) begin //帧同步,对于非视频数据一般常量为1

            W_MS <= S_RST;

            if(O_W_sync_cnt < WYBUF_SIZE) //输出帧同步计数器

                O_W_sync_cnt <= O_W_sync_cnt + 1'b1;

            else 

                O_W_sync_cnt <= 0;  

          end

       end

       S_RST:begin//帧同步,对于非视频数据直接跳过,对于视频数据,会同步每一帧,并且复位数据FIFO

           O_fmda_wbufn <= I_W_buf;

           wrst_cnt <= wrst_cnt + 1'b1;

           if((VIDEO_ENABLE == 1) && (wrst_cnt < 40))

                W_FIFO_Rst <= 1;

           else if((VIDEO_ENABLE == 1) && (wrst_cnt < 100))

                W_FIFO_Rst <= 0;

           else if(O_fdma_wirq == 1'b0) begin

                W_MS <= S_DATA1;

           end

       end

        S_DATA1:begin //发送写FDMA请求

          if(I_fdma_wbusy == 1'b0 && W_REQ )begin

             O_fdma_wareq_r  <= 1'b1;

          end 

          else if(I_fdma_wbusy == 1'b1) begin

             O_fdma_wareq_r  <= 1'b0;

             W_MS    <= S_DATA2;

          end          

         end

        S_DATA2:begin //写有效数据

            if(I_fdma_wbusy == 1'b0)begin

                if(W_bcnt == WY_BURST_TIMES - 1'b1) //判断是否传输完毕

                    W_MS <= S_IDLE;

                else begin

                    if(wdiv_cnt < W_XDIV - 1'b1)begin//如果对XSIZE做了分次传输,一个XSIZE也需要XDIV次FDMA完成传输

                        W_addr <= W_addr +  WX_BURST_ADDR_INC;  //计算地址增量

                        wdiv_cnt <= wdiv_cnt + 1'b1;

                     end

                    else begin

                        W_addr <= W_addr + WX_LAST_ADDR_INC; //计算最后一次地址增量,最后一次地址根据stride 计算

                        wdiv_cnt <= 0;

                    end

                    W_bcnt <= W_bcnt + 1'b1;

                    W_MS    <= S_DATA1;

                end 

            end

         end

         default: W_MS <= S_IDLE;

       endcase

    end

 end 

//写通道的数据FIFO,采用了原语调用xpm_fifo_async fifo,当FIFO存储的数据阈值达到一定量,一般满足一次FDMA的burst即可发出请求

wire W_rbusy;

always@(posedge I_ui_clk)    

     W_REQ  <= (W_rcnt > FDMA_WX_BURST - 2)&&(~W_rbusy);

wfifo #(

.DATA_WIDTH_W(W_DATAWIDTH),

.DATA_WIDTH_R(AXI_DATA_WIDTH),

.ADDR_WIDTH_W(W_WR_DATA_COUNT_WIDTH),

.ADDR_WIDTH_R(W_RD_DATA_COUNT_WIDTH),

.AL_FULL_NUM(WFIFO_DEPTH-2),

.AL_EMPTY_NUM(2),

.SHOW_AHEAD_EN(1'b1) ,

.OUTREG_EN ("NOREG")

)

u_wfifo(

.rst((I_ui_rstn == 1'b0) || (W_FIFO_Rst == 1'b1)), //asynchronous port,active hight

.clkw(I_W_clk),  //write clock

.clkr(I_ui_clk),  //read clock

.we(I_W_wren),  //write enable,active hight

.di(I_W_data),  //write data

.re(I_fdma_wvalid),  //read enable,active hight

.dout(O_fdma_wdata),  //read data

//.valid(),  //read data valid flag

.full_flag(O_W_full),  //fifo full flag

//.empty_flag(),  //fifo empty flag

//.afull(),  //fifo almost full flag

//.aempty(),  //fifo almost empty flag

//.wrusedw(W_rcnt),  //stored data number in fifo

.rdusedw(W_rcnt) //available data number for read      

) ;

end

else begin : FDMA_WRITE_DISABLE

//----------fdma signals write-------      

assign O_fdma_waddr = 0;

assign O_fdma_wareq = 0;

assign O_fdma_wsize = 0;                                    

assign O_fdma_wdata = 0;

assign O_fdma_wready = 0;

assign O_fdma_wirq = 0;

assign O_W_full = 0;

end

endgenerate

generate  if(ENABLE_READ == 1)begin : FDMA_READ// 通过设置通道使能,可以优化代码的利用率

localparam RYBUF_SIZE           = (R_BUFSIZE - 1'b1); //读通道需要完成多少次XSIZE操作

localparam RY_BURST_TIMES       = (R_YSIZE*R_XDIV); //读通道需要完成的FDMA burst 操作次数,XDIV用于把XSIZE分解多次传输

localparam FDMA_RX_BURST        = (R_XSIZE*R_DATAWIDTH/AXI_DATA_WIDTH)/R_XDIV; //FDMA BURST 一次的大小

localparam RX_BURST_ADDR_INC    = (R_XSIZE*(R_DATAWIDTH/8))/R_XDIV; //FDMA每次burst之后的地址增加

localparam RX_LAST_ADDR_INC     = (R_XSTRIDE-R_XSIZE)*(R_DATAWIDTH/8) + RX_BURST_ADDR_INC; //根据stride值计算出来最后一次地址

localparam RFIFO_DEPTH = R_BUFDEPTH*R_DATAWIDTH/AXI_DATA_WIDTH;//R_BUFDEPTH/(AXI_DATA_WIDTH/R_DATAWIDTH);

localparam R_WR_DATA_COUNT_WIDTH = clog2(RFIFO_DEPTH); //读通道FIFO 输入部分深度

localparam R_RD_DATA_COUNT_WIDTH = clog2(R_BUFDEPTH); //写通道FIFO输出部分深度

assign                                  O_fdma_rready = 1'b1;

reg                                     O_fdma_rareq_r= 1'b0;

reg                                     R_FIFO_Rst=0;

wire                                    R_FS;

reg [1 :0]                              R_MS=0;

reg [R_DSIZEBITS-1'b1:0]                R_addr=0;

reg [15:0]                              R_bcnt=0;

wire[R_WR_DATA_COUNT_WIDTH-1'b1 :0]     R_wcnt;

reg                                     R_REQ=0;

reg [5 :0]                              rirq_dly_cnt =0;

reg [3 :0]                              rdiv_cnt =0;

reg [7 :0]                              rrst_cnt =0;

reg [7 :0]                              O_fmda_rbufn;

assign O_fdma_rsize = FDMA_RX_BURST;

assign O_fdma_rirq = (rirq_dly_cnt>0);

assign O_fdma_raddr = R_BASEADDR + {O_fmda_rbufn,R_addr};//由于FPGA逻辑做乘法比较复杂,因此通过设置高位地址实现缓存设置

reg [1:0] R_MS_r =0;

always @(posedge I_ui_clk) R_MS_r <= R_MS;

//每次FDMA DBUF 完成一帧数据传输后,产生中断,这个中断持续60个周期的uiclk,这里的延迟必须足够ZYNQ IP核识别到这个中断

always @(posedge I_ui_clk) begin

    if(I_ui_rstn == 1'b0)begin

        rirq_dly_cnt <= 6'd0;

        O_fmda_rbuf <=0;

    end

    else if((R_MS_r == S_DATA2) && (R_MS == S_IDLE))begin

        rirq_dly_cnt <= 60;

        O_fmda_rbuf <= O_fmda_rbufn;

    end

    else if(rirq_dly_cnt >0)

        rirq_dly_cnt <= rirq_dly_cnt - 1'b1;

end

//帧同步,对于视频有效

fs_cap #

(

.VIDEO_ENABLE(VIDEO_ENABLE)

)

fs_cap_R0

(

  .I_clk(I_ui_clk),

  .I_rstn(I_ui_rstn),

  .I_vs(I_R_FS),

  .O_fs_cap(R_FS)

);

assign O_fdma_rareq = O_fdma_rareq_r;

//读通道状态机,采用4个状态值描述

 always @(posedge I_ui_clk) begin

   if(!I_ui_rstn)begin

        R_MS          <= S_IDLE;

        R_FIFO_Rst   <= 0;

        R_addr       <= 0;

        O_R_sync_cnt <= 0;

        R_bcnt       <= 0;

        rrst_cnt     <= 0;

        rdiv_cnt      <= 0;

        O_fmda_rbufn    <= 0;

        O_fdma_rareq_r  <= 1'd0;

    end  

    else begin

      case(R_MS) //帧同步,对于非视频数据一般常量为1

        S_IDLE:begin

          R_addr <= 0;

          R_bcnt <= 0;

          rrst_cnt <= 0;

          rdiv_cnt <=0;

          if(R_FS) begin

            R_MS <= S_RST;

            if(O_R_sync_cnt < RYBUF_SIZE) //输出帧同步计数器,当需要用读通道做帧同步的时候使用

                O_R_sync_cnt <= O_R_sync_cnt + 1'b1;

            else 

                O_R_sync_cnt <= 0;  

          end

       end

       S_RST:begin//帧同步,对于非视频数据直接跳过,对于视频数据,会同步每一帧,并且复位数据FIFO

           O_fmda_rbufn <= I_R_buf;

           rrst_cnt <= rrst_cnt + 1'b1;

           if((VIDEO_ENABLE == 1) && (rrst_cnt < 40))

                R_FIFO_Rst <= 1;

           else if((VIDEO_ENABLE == 1) && (rrst_cnt < 100))

                R_FIFO_Rst <= 0;

           else if(O_fdma_rirq == 1'b0) begin

                R_MS <= S_DATA1;

           end

       end

       S_DATA1:begin 

         if(I_fdma_rbusy == 1'b0 && R_REQ)begin

            O_fdma_rareq_r  <= 1'b1;  

         end

         else if(I_fdma_rbusy == 1'b1) begin

            O_fdma_rareq_r  <= 1'b0;

            R_MS    <= S_DATA2;

         end        

        end

        S_DATA2:begin //写有效数据

            if(I_fdma_rbusy == 1'b0)begin

                if(R_bcnt == RY_BURST_TIMES - 1'b1) //判断是否传输完毕

                    R_MS <= S_IDLE;

                else begin

                    if(rdiv_cnt < R_XDIV - 1'b1)begin//如果对XSIZE做了分次传输,一个XSIZE也需要XDIV次FDMA完成传输

                        R_addr <= R_addr +  RX_BURST_ADDR_INC;  //计算地址增量

                        rdiv_cnt <= rdiv_cnt + 1'b1;

                     end

                    else begin

                        R_addr <= R_addr + RX_LAST_ADDR_INC; //计算最后一次地址增量,最后一次地址根据stride 计算

                        rdiv_cnt <= 0;

                    end

                    R_bcnt <= R_bcnt + 1'b1;

                    R_MS    <= S_DATA1;

                end 

            end

         end

         default:R_MS <= S_IDLE;

      endcase

   end

end 

//读通道的数据FIFO,采用了原语调用xpm_fifo_async fifo,当FIFO存储空间有足够空余,满足一次FDMA的burst即可发出请求

wire R_wbusy;

always@(posedge I_ui_clk)      

     R_REQ  <= (R_wcnt < FDMA_RX_BURST - 2)&&(~R_wbusy);

rfifo #(

.DATA_WIDTH_W(AXI_DATA_WIDTH),

.DATA_WIDTH_R(R_DATAWIDTH),

.ADDR_WIDTH_W(R_WR_DATA_COUNT_WIDTH),

.ADDR_WIDTH_R(R_RD_DATA_COUNT_WIDTH),

.AL_FULL_NUM(RFIFO_DEPTH-2),

.AL_EMPTY_NUM(2),

.SHOW_AHEAD_EN(1'b1) ,

.OUTREG_EN ("NOREG")

)

u_rfifo(

.rst((I_ui_rstn == 1'b0) || (R_FIFO_Rst == 1'b1)), //asynchronous port,active hight

.clkw(I_ui_clk),  //write clock

.clkr(I_R_clk),  //read clock

.we(I_fdma_rvalid),  //write enable,active hight

.di(I_fdma_rdata),  //write data

.re(I_R_rden),  //read enable,active hight

.dout(O_R_data),  //read data

//.valid(),  //read data valid flag

//.full_flag(),  //fifo full flag

.empty_flag(O_R_empty),  //fifo empty flag

//.afull(),  //fifo almost full flag

//.aempty(),  //fifo almost empty flag

.wrusedw(R_wcnt) //stored data number in fifo

//.rdusedw(W_rcnt) //available data number for read      

) ;

end

else begin : FDMA_READ_DISABLE

   

assign O_fdma_raddr = 0;

assign O_fdma_rareq = 0;

assign O_fdma_rsize = 0;                                    

//assign I_fdma_rdata = 0;

assign O_fdma_rready = 0;

assign O_fdma_rirq = 0;

assign O_R_empty   = 1'b0;

end

endgenerate

endmodule

3.3 Anlogic DDR IP使用方法

3.3.1 添加DDR 控制IP

选中正确的DDR型号、数据位宽、DDR时钟参数,这里使用默认的MT41J128M16JT-125的器件,更改位宽为64bit,(x32改为32即可)

设置支持Debug Control,支持用串口打印DDR信息

默认adc为x64的adc,在ddr IP目录中的ddr_ip.adc文件,可以直接读取后覆盖源文件。我们这里导入已经提供的DDR PIN脚约束,方便管理。

选择时钟位置,然后校对

  

单击OK后如下图,继续单击OK

单击YES

3.3.2 修改ddr_ip.v

修改ddr_ip_1.txt的路径,注意画红线位置

复制以下文件到DDR IP路径

alc_mc_enc.sv文件是安路官方提供的加密ddr控制器(MC),其中包括axi和native两种接口

4 FPGA工程

打开配套代码的FPGA工程如下:

5 实验演示

下图是使用米联客-安路AP102板卡实现MIPI相机方案演示图

5.1 硬件接线

5.2 实验结果

6 心得分享

期间在更新demo时还发现了uidbuf中的一个小问题,这个问题我们也花费很长时间才解决。

如下图所示,从MIPI IP出来的数据,每4个时钟拼接为64bit写入到uidbuf IP的FIFO中,而uidbuf IP中FIFO的读端口数据位宽为512bit(AP102的DDR的内部数据位宽是512位),因此需要写入8个64bit的数据才能从FIFO输出512bit的数据,也就是要经过32个MIPI时钟输出的数据到FIFO,FIFO的读端口才能输出一次512bit数据。

图1 . 数据传输示意图

但是现在会出现一个问题,原先uidbuf IP中设置的W_REQ是当读FIFO中的数据大于FDMA_WX_BURST - 2的时候发起一次FDMA的BURST。FDMA_WX_BURST的长度,我们这里假设FDMA_WX_BURST =15,在不考虑代码的效率问题时,由于读FIFO的时钟是200M(DDR IP中设置是4:1模式),那么只要15个200M时钟就能把存在FIFO中的所有数据读完,但是实际上,FIFO里面之前有效存储了14个512bit。那么最后一个512bit需要32个MIPI时钟才能准备好,所以会导致FIFO中最后一个数据读出的是错误的。

图2 . 原uifdma IP配置示意图

下图是数据进入uidubf IP的FIFO之前的位宽转换代码。

图3 . 位宽转换示意图

在此基础上我们做了如下改进,设置W_REQ当FIFO中的数据大于FDMA_WX_BURST - 1,也就是FIFO中的数据达到FDMA_WX_BURST - 1时再开始从FIFO读出,这样可以确保FIFO中数据不会读空从而导致错误。

图4 . uifdma IP配置修改示意图

  • 20
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
很抱歉,FPGA极客可能并没有公开发布针对 FDMA 的完整源代码。不过,以下是一个简单的伪代码示例,展示了如何使用 FDMA 对两个用户进行分频。您可以根据您的具体需求和实现细节来编写实际的源代码。 ``` // 定义频宽度 bandwidth = 1000; // 用户1的频率范围 user1_start_frequency = 0; user1_end_frequency = 400; // 用户2的频率范围 user2_start_frequency = 400; user2_end_frequency = 1000; // 创建用户1的频率分配 user1_frequencies = [i for i in range(user1_start_frequency, user1_end_frequency)]; // 创建用户2的频率分配 user2_frequencies = [i for i in range(user2_start_frequency, user2_end_frequency)]; // 将两个频率分配合并 frequency_allocation = user1_frequencies + user2_frequencies; // 将频率分配映射到时间域 time_allocation = []; for frequency in frequency_allocation: time_allocation.append(frequency / bandwidth); // 在时间域分配用户数据 user1_data = [1, 0, 1, 1, 0, 0, 1, 1]; user2_data = [0, 1, 1, 0, 1, 1, 0, 0]; // 将用户数据映射到频率分配 user1_frequency_data = []; user2_frequency_data = []; for i in range(len(user1_data)): user1_frequency_data.append(user1_frequencies[i % len(user1_frequencies)] * user1_data[i]); user2_frequency_data.append(user2_frequencies[i % len(user2_frequencies)] * user2_data[i]); // 将每个用户的频率数据加起来 total_frequency_data = [user1_frequency_data[i] + user2_frequency_data[i] for i in range(len(user1_frequency_data))]; // 将频率数据映射回时间域 total_time_data = []; for frequency_data in total_frequency_data: total_time_data.append(frequency_data / bandwidth); // 传输总时间数据 transmit(total_time_data); ``` 请注意,这只是一个伪代码示例,您需要根据您的具体需求和实现细节来编写实际的源代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值