軟件版本: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從機 |
寫突發大小,給出每次突發傳輸的字節數支持1、2、4、8、16、32、64、128 |
AWBURST |
主機to從機 |
突發類型: 2'b00 FIXED:突發傳輸過程中地址固定,用於FIFO訪問 2'b01 INCR :增量突發,傳輸過程中,地址遞增。增加量取決AxSIZE的值。 2'b10 WRAP:回環突發,和增量突發類似,但會在特定高地址的邊界處回到低地址處。回環突發的長度只能是2,4,8,16次傳輸,傳輸首地址和每次傳輸的大小對齊。最低的地址整個傳輸的數據大小對齊。回環邊界等於(AxSIZE*AxLEN) 2'b11 Reserved |
AWLOCK |
主機to從機 |
總線鎖信號,可提供操作的原子性 |
AWCACHE |
主機to從機 |
內存類型,表明一次傳輸是怎樣通過系統的 |
AWPROT |
主機to從機 |
保護類型,表明一次傳輸的特權級及安全等級 |
AWQOS |
主機to從機 |
質量服務QoS |
AWREGION |
主機to從機 |
區域標志,能實現單一物理接口對應的多個邏輯接口 |
AWUSER |
主機to從機 |
用戶自定義信號 |
AWVALID |
主機to從機 |
有效信號,表明此通道的地址控制信號有效 |
AWREADY |
從機to主機 |
表明"從"可以接收地址和對應的控制信號 |
2:寫數據通道信號:
信號名 |
方向 |
描述 |
WID |
主機to從機 |
一次寫傳輸的ID tag |
WDATA |
主機to從機 |
寫數據 |
WSTRB |
主機to從機 |
WSTRB[n:0]對應於對應的寫字節,WSTRB[n]對應WDATA[8n+7:8n]。WVALID為低時,WSTRB可以為任意值,WVALID為高時,WSTRB為高的字節線必須指示有效的數據。 |
WLAST |
主機to從機 |
表明此次傳輸是最后一個突發傳輸 |
WUSER |
主機to從機 |
用戶自定義信號 |
WVALID |
主機to從機 |
寫有效,表明此次寫有效 |
WREADY |
從機to主機 |
表明從機可以接收寫數據 |
寫響應信號:
信號名 |
方向 |
描述 |
BID |
從機to主機 |
寫響應ID tag |
BRESP |
從機to主機 |
寫響應,表明寫傳輸的狀態 |
BUSER |
從機to主機 |
用戶自定義 |
BVALID |
從機to主機 |
寫響應有效 |
BREADY |
主機to從機 |
表明主機能夠接收寫響應 |
3:讀地址通道信號:
信號 |
方向 |
描述 |
ARID |
主機to從機 |
讀地址ID,用來標志一組寫信號 |
ARADDR |
主機to從機 |
讀地址,給出一次讀突發傳輸的讀地址 |
ARLEN |
主機to從機 |
ARLEN[7:0]決定讀傳輸的突發長度。AXI3只支持1~16次的突發傳輸(Burst_length=AxLEN[3:0]+1),AXI4擴展突發長度支持INCR突發類型為1~256次傳輸,對於其他的傳輸類型依然保持1~16次突發傳輸(Burst_Length=AxLEN[7:0]+1)。 burst傳輸具有如下規則: wraping burst ,burst長度必須是2,4,8,16 burst不能跨4KB邊界 不支持提前終止burst傳輸 |
ARSIZE |
主機to從機 |
讀突發大小,給出每次突發傳輸的字節數支持1、2、4、8、16、32、64、128 |
ARBURST |
主機to從機 |
突發類型: 2'b00 FIXED:突發傳輸過程中地址固定,用於FIFO訪問 2'b01 INCR :增量突發,傳輸過程中,地址遞增。增加量取決AxSIZE的值。 2'b10 WRAP:回環突發,和增量突發類似,但會在特定高地址的邊界處回到低地址處。回環突發的長度只能是2,4,8,16次傳輸,傳輸首地址和每次傳輸的大小對齊。最低的地址整個傳輸的數據大小對齊。回環邊界等於(AxSIZE*AxLEN) 2'b11 Reserved |
ARLOCK |
主機to從機 |
總線鎖信號,可提供操作的原子性 |
ARCACHE |
主機to從機 |
內存類型,表明一次傳輸是怎樣通過系統的 |
ARPROT |
主機to從機 |
保護類型,表明一次傳輸的特權級及安全等級 |
ARQOS |
主機to從機 |
質量服務QoS |
ARREGION |
主機to從機 |
區域標志,能實現單一物理接口對應的多個邏輯接口 |
ARUSER |
主機to從機 |
用戶自定義信號 |
ARVALID |
主機to從機 |
有效信號,表明此通道的地址控制信號有效 |
ARREADY |
從機to主機 |
表明"從"可以接收地址和對應的控制信號 |
4:讀數據通道信號:
信號名 |
方向 |
描述 |
RID |
從機to主機 |
一次讀傳輸的ID tag |
RDATA |
從機to主機 |
讀數據 |
RRESP |
從機to主機 |
讀響應,表明讀傳輸的狀態 |
RLAST |
從機to主機 |
表明此次傳輸是最后一個突發傳輸 |
RUSER |
從機to主機 |
用戶自定義信號 |
RVALID |
從機to主機 |
寫有效,表明此次寫有效 |
RREADY |
主機to從機 |
表明從機可以接收寫數據 |
7.2.3數據有效的情況
AXI4所采用的是一種READY,VALID握手通信機制,簡單來說主從雙方進行數據通信前,有一個握手的過程。傳輸源產生VLAID信號來指明何時數據或控制信息有效。而目地源產生READY信號來指明已經准備好接受數據或控制信息。傳輸發生在VALID和READY信號同時為高的時候。VALID和READY信號的出現有三種關系。
-
VALID先變高READY后變高。時序圖如下:
在箭頭處信息傳輸發生。
-
READY先變高VALID后變高。時序圖如下:
同樣在箭頭處信息傳輸發生。
-
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;//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
以上代碼我們進行了詳細的注釋性分析。以下給出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; //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個長度的數據。
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