米聯客 ZYNQ/SOC 精品教程 S03-CH01基於FDMA記憶體讀寫測試
軟體版本:VIVADO2017.4
作業系統:WIN10 64bit
硬體平臺:適用米聯客 ZYNQ系列開發板
米聯客(MSXBO)論壇:www.osrc.cn答疑解惑專欄開通,歡迎大家給我提問!!
1.1概述
FDMA是MSXBO(米聯客的)基於AXI4匯流排協議定製的一個DMA控制器。有了這個IP我們可以統一實現用FPGA程式碼直接讀寫PL的DDR或者ZYNQ PS的DDR。
如果用過ZYNQ的都知道,要直接操作PS的DDR 通常是DMA 或者VDMA,然而用過XILINX 的DMA IP 和VDMA IP,總有一種遺憾,那就是不夠靈活,還需要對暫存器配置,真是麻煩。對於我們搞FPGA的人來說,最喜歡直接了當,直接用FPGA程式碼搞定。現在XILINX 的匯流排介面是AXI4匯流排,那麼熟練自定義AXI4 IP掛到總線上就非常方便了。基於這個目的,本小編定義了一個基於AXI4 FULL MASTER的IP,暫且取名為MSXBO_FDMA。
通過這個IP我們可以方便地進行AXI4 FULL MASTER的操作,比如我們經常要讀寫DDR,那麼只要掛到AXI4總線上就可以利用這個IP實現。
以下是小編展示了一種在ZYNQ上的讀寫PS DDR測試方案。
1.2基於FDMA搭建的BD工程
首先看下基於FDMA搭建的BD工程,這裡使用的是ZYNQ IP搭建。
1.3 ZYNQ PS IP配置
和使用PL DDR MIG 不同,MZ7X是ZYNQ晶片只有PS有DDR 所以訪問DDR需要配置ZYNQ IP這樣配置好後就可以通過AXI匯流排範圍PS的DDR了。
配置輸入時鐘
配置DDR
僅配置HP介面
地址空間分配
1.3編寫FDMA測試程式碼
首先右擊BD 並且單擊
其次繼續右擊BD檔案,選擇Create HDL Wrapper
最後修改Wrapper並且新增測試程式碼,測試程式碼如下
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company:CZ123 MSXBO www.osrc.cn // Engineer: tjy // Create Date: 2019/04/02 12:39:25 // Design Name: // Module Name: fdma_top // Project Name: AXI_FDMA // Target Devices: // Tool Versions: VIVADO // Description: test DDR // Dependencies: // Revision: // Revision 0.01 - File Created // Additional Comments: ////////////////////////////////////////////////////////////////////////////////// module fdma_top( inout [14:0]DDR_addr, inout [2:0]DDR_ba, inout DDR_cas_n, inout DDR_ck_n, inout DDR_ck_p, inout DDR_cke, inout DDR_cs_n, inout [3:0]DDR_dm, inout [31:0]DDR_dq, inout [3:0]DDR_dqs_n, inout [3:0]DDR_dqs_p, inout DDR_odt, inout DDR_ras_n, inout DDR_reset_n, inout DDR_we_n, inout FIXED_IO_ddr_vrn, inout FIXED_IO_ddr_vrp, inout [53:0]FIXED_IO_mio, inout FIXED_IO_ps_clk, inout FIXED_IO_ps_porb, inout FIXED_IO_ps_srstb );
wire [0:0]ui_rstn; wire ui_clk; //-----------------fmda signals-------------------------------------- wire [31:0] pkg_wr_addr; (*mark_debug = "true"*) wire [31:0] pkg_wr_data; (*mark_debug = "true"*) (* KEEP = "TRUE" *) reg pkg_wr_areq; (*mark_debug = "true"*) wire pkg_wr_en; (*mark_debug = "true"*) wire pkg_wr_last; wire [31:0] pkg_wr_size; wire [31:0] pkg_rd_addr; (*mark_debug = "true"*) wire [31:0] pkg_rd_data; (*mark_debug = "true"*) (* KEEP = "TRUE" *) reg pkg_rd_areq; (*mark_debug = "true"*) wire pkg_rd_en; (*mark_debug = "true"*) wire pkg_rd_last; wire [31:0] pkg_rd_size; //--------------------------------------------------------------------- reg [31:0]pkg_wr_cnt; (*mark_debug = "true"*) (* KEEP = "TRUE" *) reg [31:0]pkg_rd_cnt; (*mark_debug = "true"*) (* KEEP = "TRUE" *) reg [1:0] T_S;
reg [31:0] pkg_addr;
parameter WRITE1 = 0; parameter WRITE2 = 1; parameter READ1 = 2; parameter READ2 = 3; //----------------- assign pkg_wr_size = 1024; assign pkg_rd_size = 1024;
assign pkg_wr_data = pkg_wr_cnt; (*mark_debug = "true"*) wire test_error; assign test_error = (pkg_rd_en && (pkg_rd_cnt != pkg_rd_data));
parameter DDR_BASE = (10*1024*1024);
assign pkg_wr_addr = pkg_addr+ DDR_BASE; assign pkg_rd_addr = pkg_addr+ DDR_BASE;
always @(posedge ui_clk) begin if(!ui_rstn)begin T_S <=0; pkg_wr_areq <= 1'b0; pkg_rd_areq <= 1'b0; pkg_wr_cnt<=0; pkg_rd_cnt<=0; pkg_addr<=0; end else begin case(T_S) WRITE1:begin if(pkg_addr>=32'd536870911) pkg_addr<=0; pkg_wr_areq <= 1'b1; T_S <= WRITE2; end WRITE2:begin pkg_wr_areq <= 1'b0; if(pkg_wr_last) begin T_S <= READ1; pkg_wr_cnt <= 32'd0; end else if(pkg_wr_en) begin pkg_wr_cnt <= pkg_wr_cnt + 1'b1; end end READ1:begin pkg_rd_areq <= 1'b1; T_S <= READ2; end READ2:begin pkg_rd_areq <= 1'b0; if(pkg_rd_last) begin T_S <= WRITE1; pkg_addr <= pkg_addr + 4096; pkg_rd_cnt <= 32'd0; end else if(pkg_rd_en) begin pkg_rd_cnt <= pkg_rd_cnt + 1'b1; end end endcase end end
system system_i (.DDR_addr(DDR_addr), .DDR_ba(DDR_ba), .DDR_cas_n(DDR_cas_n), .DDR_ck_n(DDR_ck_n), .DDR_ck_p(DDR_ck_p), .DDR_cke(DDR_cke), .DDR_cs_n(DDR_cs_n), .DDR_dm(DDR_dm), .DDR_dq(DDR_dq), .DDR_dqs_n(DDR_dqs_n), .DDR_dqs_p(DDR_dqs_p), .DDR_odt(DDR_odt), .DDR_ras_n(DDR_ras_n), .DDR_reset_n(DDR_reset_n), .DDR_we_n(DDR_we_n), .FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn), .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp), .FIXED_IO_mio(FIXED_IO_mio), .FIXED_IO_ps_clk(FIXED_IO_ps_clk), .FIXED_IO_ps_porb(FIXED_IO_ps_porb), .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
.pkg_wr_addr(pkg_wr_addr), .pkg_wr_data(pkg_wr_data), .pkg_wr_areq(pkg_wr_areq), .pkg_wr_en (pkg_wr_en), .pkg_wr_last(pkg_wr_last), .pkg_wr_size(pkg_wr_size),
.pkg_rd_addr(pkg_rd_addr), .pkg_rd_data(pkg_rd_data), .pkg_rd_areq(pkg_rd_areq), .pkg_rd_en (pkg_rd_en), .pkg_rd_last(pkg_rd_last), .pkg_rd_size(pkg_rd_size),
.ui_clk(ui_clk), .fdma_rstn(ui_rstn) );
endmodule |
以上程式碼中需要注意紅色部分程式碼,PS的DDR 地址需要偏移大約1MB ,這裡偏移10MB並且確保不和應用程式的DDR空間衝突。
1.5測試程式碼狀態機分析
- WRITE1狀態:為了測試整個DDR的儲存控制元件,所以先計算DDR大小,536870911正好是一片512MB DDR的大小。根據之前BD裡面FDMA的引數設定,一次AXI4 burst大小為256,那麼每次傳輸1024byte(256x32/8)。我們設定pkg_wr_size 和pkg_rd_size大小為1024,那麼這裡每次parket 傳輸大小為4096bytes(1024x32bit/8)。pkg_wr_size 和pkg_rd_size的大小是以32bit計算。pkg_wr_size 和pkg_rd_size大小的設定需要注意pkg_wr_size需要是FDMA裡面 AXI BURST LEN的整數倍。
- WRITE2狀態: WRITE1通過設定pkg_wr_areq為1持續1個時鐘週期,後進入WRITE2狀態機,並且啟動啟動一次FDMA的parket傳輸。當pkg_wr_last為1的時候表示一次parket傳輸傳輸結束。在WRITE2狀態機中使用了一個計數器,把計數器的值寫入到DDR中。
- READ1狀態:在READ1狀態設定pkg_rd_areq為1啟動一次FMDA的parket read 傳輸,之後進入READ2狀態。
- READ2狀態:當pkg_rd_las 為1代表一次read傳輸結束,並且地址空間地址增加。在READ2狀態,通過一個計數器計數並且對比和從DDR裡面讀出的資料,看是否有錯誤。
assign test_error = (pkg_rd_en && (pkg_rd_cnt != pkg_rd_data)); |
1.6測試結果
匯入硬體工程到SDK 並且新建一個helloworld程式,之後下載程式測試,如果讀者這個地方不清楚,可以先學習ZYNQ基礎方面的教程內容,掌握如何除錯ZYNQ。
可以看到上圖中error 訊號一直為0代表資料讀寫對比沒有錯誤。再看pkg_wr_en和pkg_rd_en訊號,可以看到他們是每次burst了4次每次的長度是256。證明我們的FDMA工作正常,測試程式工作正常。下面放大資料部分。
上圖是寫操作往記憶體裡面寫資料。
上圖是讀操作放大後看到的資料分別是計數器和從記憶體裡