07AXI4-FULL-MASTER IP FDMA詳解(AXI4總線實戰)


軟件版本:vitis2020.2(vivado2020.2)

操作系統:WIN10 64bit

硬件平台:適用XILINX A7/K7/Z7/ZU/KU系列FPGA(米聯客(milianke)MZU07A-EG硬件開發平台)

登錄"米聯客"FPGA社區-www.uisrc.com視頻課程、答疑解惑!

7.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工程師發現,因此給了我們很多寶貴意見。借此2020版本教程更新發布之際,我們也對FDMA1.0版本升級到FDMA2.0版本。目前最新的版本是3.0版本,對2.0版本優化了自動burst的長度,提高了效率。

uiFDMA2.0/3.0新增特性:

1:支持多個FDMA IP同時掛帶AXI-interconnect總線,同時工作

2:支持自動計算沒錯AXI-Burst長度,使用起來非常簡單,只需要給出需要burst的長度。

從本文開始,我們從多個應用方案來演示FDMA的用途。

本文實驗目的:

1:分析FDMA源碼,掌握基於FDMA的APP接口實現AXI4-FULL總線接口的訪問。

2:掌握自定義總線接口封裝方法

3:自定義AXI-FULL-Slave IP用於驗證FDMA的工作情況。

7.2AXI總線協議介紹

7.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協議不再本文中接收,后面有單獨介紹的文章。

7.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從機

寫突發大小,給出每次突發傳輸的字節數支持1248163264128

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從機

讀突發大小,給出每次突發傳輸的字節數支持1248163264128

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從機

表明從機可以接收寫數據

7.2.3數據有效的情況

AXI4所采用的是一種READY,VALID握手通信機制,簡單來說主從雙方進行數據通信前,有一個握手的過程。傳輸源產生VLAID信號來指明何時數據或控制信息有效。而目地源產生READY信號來指明已經准備好接受數據或控制信息。傳輸發生在VALID和READY信號同時為高的時候。VALID和READY信號的出現有三種關系。

  1. VALID先變高READY后變高。時序圖如下:

    在箭頭處信息傳輸發生。

  2. READY先變高VALID后變高。時序圖如下:

    同樣在箭頭處信息傳輸發生。

  3. VALID和READY信號同時變高。時序圖如下:

    在這種情況下,信息傳輸立馬發生,如圖箭頭處指明信息傳輸發生。

    7.2.4突發式讀寫

    1:突發式寫時序圖

    這一過程的開始時,主機發送地址和控制信息到寫地址通道中,然后主機發送每一個寫數據到寫數據通道中。當主機發送最后一個數據時,WLAST信號就變為高。當設備接收完所有數據之后他將一個寫響應發送回主機來表明寫事務完成。

    2:突發式讀的時序圖

    當地址出現在地址總線后,傳輸的數據將出現在讀數據通道上。設備保持VALID為低直到讀數據有效。為了表明一次突發式讀寫的完成,設備用RLAST信號來表示最后一個被傳輸的數據。

    7.3FDMA源碼分析

    由於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 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;//AXI4busr類型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}};//設置所有的WSTRB1代表傳輸的所有數據有效

    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傳輸,首先把地址本次fdmaburst地址寄存到axi_awaddr作為第一次axi burst的地址。如果fdma的數據長度大於256,那么當axi_wlast有效的時候,自動計算下次axiburst地址

    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的作用代表一次axiburst操作正在進行中。

    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

    以上代碼我們進行了詳細的注釋性分析。以下給出FDMA寫操作源碼部分的時序圖。下圖中一次傳輸以傳輸262個長度的數據為例,需要2次AXI4 BURST才能完成,第一次傳輸256個長度數據,第二次傳輸6個長度的數據。

    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; //AXI4busr類型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傳輸,首先把地址本次fdmaburst地址寄存到axi_araddr作為第一次axi burst的地址。如果fdma的數據長度大於256,那么當axi_rlast有效的時候,自動計算下次axiburst地址

    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的作用代表一次axiburst操作正在進行中。

    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個長度的數據。

     

     

    7.4FDMA IP的封裝

    我先講解如何封裝FDMA IP,之后再分析源碼。封裝IP少不了源碼,這里是利用已經編寫好的uiFDMA.v進行封裝。

    默認的源碼路徑在配套的工程uisrc/uifdma路徑下

    創建一個新的空的fpga工程

    添加uiFDMA.v源碼

    創建IP

    選擇Package your current project

    按住shift全選后,右擊彈出菜單后選擇Create Interface Definition

    接口定義為slave,命名為FDMA_S

    設置完成,uisrc/03_ip/uifdma路徑下多出2個文件,這個兩個文件就是定義了自定義的總線接口。

    現在可以看到封裝后的總線

    建議把名字改簡潔一些

    可以看到封裝好的接口,更加美觀

    7.5saxi_full_mem IP

    這個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完全夠用。

    7.6創建FPGA圖像化設計

    設置IP路徑

     

    添加已經創建好的IP

    輸入關鍵詞fdma,在最后可以看到,雙擊添加Ip

    可以看到本文的FDMA版本升級到3.0版本,相比2.0而言,優化了burst傳輸效率

    完成連線

    繼續添加剩余IP

     

     

     

     

    設置IP參數

     

     


    完成連線

    設置地址分配:

    7.7添加FDMA接口控制代碼

     

    添加完成后如下圖:

    fdma_axi_slave_test.v源碼如下

     

    `timescale 1ns / 1ps

    //////////////////////////////////////////////////////////////////////////////////

    /*

    Company : Liyang Milian Electronic Technology Co., Ltd.

    Brand: (milianke)

    Technical forum:uisrc.com

    taobao: https://milianke.taobao.com https://osrc.taobao.com

    jd:https://milianke.jd.com

    Create Date: 2021/04/25

    Module Name: fdma_axi_slave_test

    Description:

    Copyright: Copyright (c) milianke

    Revision: 1.0

    Signal description:

    1) _i input

    2) _o output

    3) _n activ lowpai

    4) _dg debug signal

    5) _r delay or register

    6) _s state mechine

    */

    //////////////////////////////////////////////////////////////////////////////////

     

    module fdma_axi_slave_test(

    input sysclk_p,

    input sysclk_n

    );

     

    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 [31: 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]));

     

    //ila_0 ila_dbg (

    //    .clk(ui_clk),

    //    .probe0({fdma_wdata[15:0],fdma_wareq,fdma_wvalid,fdma_wbusy}),

    //    .probe1({fdma_rdata[15:0],t_data[15:0],fdma_rvalid,fdma_rbusy,T_S,test_error})

    //);

     

    system system_i

    (.FDMA_S_0_fdma_raddr(fdma_raddr),

    .FDMA_S_0_fdma_rareq(fdma_rareq),

    .FDMA_S_0_fdma_rbusy(fdma_rbusy),

    .FDMA_S_0_fdma_rdata(fdma_rdata),

    .FDMA_S_0_fdma_rready(1'b1),

    .FDMA_S_0_fdma_rsize(fdma_rsize),

    .FDMA_S_0_fdma_rvalid(fdma_rvalid),

    .FDMA_S_0_fdma_waddr(fdma_waddr),

    .FDMA_S_0_fdma_wareq(fdma_wareq),

    .FDMA_S_0_fdma_wbusy(fdma_wbusy),

    .FDMA_S_0_fdma_wdata(fdma_wdata),

    .FDMA_S_0_fdma_wready(1'b1),

    .FDMA_S_0_fdma_wsize(fdma_wsize),

    .FDMA_S_0_fdma_wvalid(fdma_wvalid),

    .sysclk_clk_n(sysclk_n),

    .sysclk_clk_p(sysclk_p),

    .ui_clk(ui_clk)

    );

     

    endmodule

    以上代碼中調用的system.bd的圖形代碼接口。在狀態機中,每次寫262個長度32bit的數據,再讀出來判斷數據是否正確。

    7.8仿真文件

    添加仿真文件

    添加完成后:

    仿真文件非常簡單,只要提供時鍾激勵就可以。

    module fdma_axi_slave_test_tb();

    reg sysclk_p;

    fdma_axi_slave_test fdma_axi_slave_test_inst

    (

    .sysclk_p(sysclk_p),

    .sysclk_n(~sysclk_p)

    );

     

    initial begin

    sysclk_p = 0;

    #100;

    end

     

    always #10 sysclk_p = ~sysclk_p;

    endmodule

     

     

     

    7.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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM