米聯客 ZYNQ/SOC 精品教程 S02-CH19 利用BRAM進行PS與PL間資料互動
軟體版本:VIVADO2017.4
作業系統:WIN10 64bit
硬體平臺:適用米聯客 ZYNQ系列開發板
米聯客(MSXBO)論壇:www.osrc.cn答疑解惑專欄開通,歡迎大家給我提問!!
19.1 概述
本課介紹一種基於PL端BRAM的方式,進行PS和PS之間的資料互動。適用於在PS和PL之間傳輸少量,地址不連續,且長度不規則的資料,比如,配置引數,變數,控制資訊等。
19.2 基本原理
在本例程中,在PL端設計了1個4KB(位寬32,深度1024)的BRAM。首先,PS通過M_AXI_GP口向BRAM中1024個地址依次存入1024個32位的資料。PS每向BRAM完成寫入1個32位資料後通過AXI GPIO輸出1個上升沿訊號,PL捕獲上升沿後立即將PS寫入的32位資料讀出,然後加2,再存入原地址中。儲存完成後,PL通過AXI GPIO向PS輸入1個翻轉訊號,每翻轉1次,AXI GPIO便向PS觸發1次中斷。PS觸發中斷後,再從BRAM中讀出該資料,判斷是否被加了2,若不一致,則報錯。
以上過程重複1024次,便將BRAM的所有地址都進行了遍歷。之後,則不斷重複這個過程。
19.3 FPGA BD工程
VIVADO 2017.4開始可以使用axi_smc IP這個IP和AXI_interconnect IP功能類似,但是沒有AXI_interconnect IP強大,在一些高頻寬的要求下,還是要用AXI_interconnect IP,VIVADO2017.4自動連線會優先使用axi_smc IP。
以下是VIVADO 2016.4版本以前的自動聯絡,只會採用AXI_interconnect IP。所以從VIVADO2017.4開始我們多了axi_smc IP進行AXI4匯流排介面的互聯。
19.3.2 PS配置
PS的配置如下圖所示。使能M_AXI_GP0口,將FCLK_CLK0設為100MHz,使能PL至PS的中斷。
19.3.3 AXI BRAM Controller
AXI BRAM Controller是本例程中的關鍵,該IP核連線PS的M_AXI_GP0口和BRAM,完成AXI介面至BRAM介面的轉換。設定如下圖所示。
19.3.4 Block Memory Generator
新增BRAM,將BRAM設定為雙口RAM,將PORTA與AXI BRAM Controller連線,PS通過PORTA讀寫BRAM,另外,將PORTB引出至外部模組,PL通過PORTB讀寫BRAM。如下圖所示。
由於要與AXI BRAM Controller進行連線,BRAM介面的位寬固定為32位。BRAM的深度無法在該IP中進行設定,需要在所有模組連線完成後,在Address Editor裡對AXI BRAM Controller的地址範圍進行設定,本例程中設定為4KB。該地址範圍即對應BRAM的深度,如下圖所示。
例如,4KB=32bit×1024,因此BRAM的深度為1024。如下圖所示。
去掉Enable Safety Circuit
19.3.5 AXI GPIO
新增AXI GPIO,位寬設為2,使能中斷,將中斷輸出與PS的中斷介面連線,設定如下圖所示。其中GPIO0作為PS向PL輸出的PS BRAM寫入完成訊號,對於PL而言,上升沿有效。GPIO1作為PL向PS輸入的BRAM寫入完成訊號,該訊號為翻轉訊號,每次翻轉向PS產生1次中斷。
19.4邏輯設計
PL部分邏輯設計主要包括以下幾個過程:
檢測AXI GPIO輸出的GPIO0的上升沿;
若檢測到GPIO0的上升沿,則從BRAM的某1個地址中讀出1個PS寫入32位資料,然後加2,存入原地址中;
1個32資料儲存完畢後,將AXI GPIO的輸入訊號GPIO1進行翻轉,告知PS一次資料讀寫完成。
不斷迴圈上述過程,依次遍歷BRAM中0~4092的地址範圍。
module system_wrapper_BRAM( 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 [31:0]BRAM_PORTB_addr; wire BRAM_PORTB_clk; wire [31:0]BRAM_PORTB_din; wire [31:0]BRAM_PORTB_dout; wire BRAM_PORTB_en; wire BRAM_PORTB_rst; wire [3:0]BRAM_PORTB_we; wire [0:0]GPIO_tri_i_0; wire [1:1]GPIO_tri_i_1; wire [0:0]GPIO_tri_o_0; wire [1:1]GPIO_tri_o_1; wire [0:0]aresetn;
reg gpio_tri_o_0_reg; reg ps_bram_wr_done; reg pl_bram_wr_done; reg bram_en; reg [3:0] bram_we; reg [31:0] bram_addr; reg [31:0] bram_rd_data; reg [31:0] bram_wr_data; reg [2:0] state;
localparam BRAM_ADDRESS_HIGH = 32'd4096 - 32'd4;
always@(posedge FCLK_CLK0) begin if(!aresetn) gpio_tri_o_0_reg <= 1'b0; else gpio_tri_o_0_reg <= GPIO_tri_o_0; end
always@(posedge FCLK_CLK0) begin if(!aresetn) ps_bram_wr_done <= 1'b0; else if({gpio_tri_o_0_reg, GPIO_tri_o_0} == 2'b01) //gpio0 rising edge ps_bram_wr_done <= 1'b1; else ps_bram_wr_done <= 1'b0; end
always@(posedge FCLK_CLK0) begin if(!aresetn) begin bram_we <= 4'd0; bram_en <= 1'b0; bram_addr <= 32'd0; bram_rd_data <= 32'd0; bram_wr_data <= 32'd0; pl_bram_wr_done <= 1'b0; state <= 3'd0; end else begin case(state) 0: begin if(ps_bram_wr_done) begin bram_en <= 1'b1; bram_we <= 4'd0; state <= 1; end else begin state <= 0; bram_en <= 1'b0; bram_we <= 4'd0; bram_addr <= bram_addr; end end 1: begin bram_en <= 1'b0; state <= 2; end 2: begin bram_rd_data <= BRAM_PORTB_dout; state <= 3; end 3: begin bram_en <= 1'b1; bram_we <= 4'hf; bram_wr_data <= bram_rd_data + 2; pl_bram_wr_done <= ~pl_bram_wr_done; state <= 4; end 4: begin state <= 0; bram_en <= 1'b0; if(bram_addr == BRAM_ADDRESS_HIGH) bram_addr <= 32'd0; else bram_addr <= bram_addr + 32'd4; end default: state <= 0; endcase end end
assign BRAM_PORTB_en = bram_en; assign BRAM_PORTB_we = bram_we; assign BRAM_PORTB_rst = ~aresetn; assign BRAM_PORTB_clk = FCLK_CLK0; assign BRAM_PORTB_addr = bram_addr; assign BRAM_PORTB_din = bram_wr_data;
assign GPIO_tri_i_1 = pl_bram_wr_done; system system_i (.BRAM_PORTB_addr(BRAM_PORTB_addr), .BRAM_PORTB_clk(BRAM_PORTB_clk), .BRAM_PORTB_din(BRAM_PORTB_din), .BRAM_PORTB_dout(BRAM_PORTB_dout), .BRAM_PORTB_en(BRAM_PORTB_en), .BRAM_PORTB_rst(BRAM_PORTB_rst), .BRAM_PORTB_we(BRAM_PORTB_we), .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), .FCLK_CLK0(FCLK_CLK0), .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), .GPIO_tri_i({GPIO_tri_i_1,GPIO_tri_i_0}), .GPIO_tri_o({GPIO_tri_o_1,GPIO_tri_o_0}), .GPIO_tri_t(), .aresetn(aresetn)); |
19.4.1 BRAM讀時序
BRAM讀時序如下圖所示。
19.4.2 BRAM寫時序
BRAM寫時序如下圖所示。
19.5 PS程式設計
將提供例程中SDK工程的原始檔複製,並貼上到新建SDK工程。
19.5.1 main函式
main函式的完成的功能如下:
配置AXI GPIO及其中斷;
初始化中斷控制器及系統中斷;
依次向BRAM所對應的地址寫入1024個32位整型資料,每寫入1個數據等待PL的讀寫完成中斷來臨後繼續寫入下一個;
依次從BRAM所對應的地址讀出1024個32位整型資料,並判斷是否被加了2,若比對不一致則報錯。
19.5.2 GPIO輸入輸出
在main函式呼叫Gpiopl_init()函式初始化AXI GPIO,設定2個GPIO的方向,其中GPIO[0]為輸出,GPIO[1]為輸入。並將GPIO[0]置為0。每個GPIO的巨集定義在gpiopl_intr.h中,如下所示。
#define PS_BRAM_MASK 0x00000001 #define PL_INTR_MASK 0x00000002 |
通過Gpiopl_Setup_Intr_System()初始化並使能AXI GPIO的輸入中斷,GPIO[1]的輸入發生1次改變將觸發1次中斷,GpioplIntrHandler()為GPIO的中斷函式。
GpioplIntrHandler()函式將PL讀寫BRAM完成中斷標誌位pl_bram_flag置1,該訊號將在PS寫入BRAM時使用。
19.5.3 BRAM資料寫入
每一個迴圈PS向BRAM寫入1024個32bit整型資料,每次迴圈後將下一次寫入的1024個數據都加1。因此,第1次寫入BRAM的資料為0~1023,第2次寫入的為1~1024,第3次為2~1025,以此類推。BRAM寫入通過如下函式完成:
Xil_Out32(XPAR_BRAM_0_BASEADDR + 4*i, write_data);
由於在ZYNQ中最小可定址單元為位元組,因此1個32位資料需佔用4個地址,每次寫入的地址都需要加4。
每次寫入完成後,拉高GPIO0,通過如下程式碼完成:
XGpio_DiscreteWrite(&Gpio, 1, PS_BRAM_MASK);
然後,PS等待PL完成BRAM中該地址32bit資料的讀寫,PL翻轉GPIO1使AXI GPIO產生中斷,程式碼如下所示:
while(!pl_bram_flag);
pl_bram_flag = 0;
PL完成BRAM讀寫後,PS拉低GPIO0,通過如下程式碼完成:
XGpio_DiscreteWrite(&Gpio, 1, ~PS_BRAM_MASK);
迴圈1024次,完成1024個數據的寫入。
19.5.4 BRAM資料讀出
當PS完成1024個數據的寫入,此時PL也完成了1024個數據的讀出、加2、寫入工作。因此,PS需要依次將1024個數據讀出進行比對,驗證PL給每個資料加2的正確性,因此,第1次讀出的1024個數據應該為2~1025,第2次為3~1026,第3次為4~1027,以此類推。若比對出現不一致,則通過串列埠進行報錯。程式碼如下:
for(i = 0; 4*i < (XPAR_BRAM_0_HIGHADDR - XPAR_BRAM_0_BASEADDR); i++) { read_data = Xil_In32(XPAR_BRAM_0_BASEADDR + 4*i); //xil_printf("data at address %d is %d\r\n", 4*i, read_data); /*compare data read form bram if they are add by 2*/ if(read_data != (i + j + 2)) xil_printf("error: data at address %d should be %d, but is %d\r\n", 4*i, (i + j + 2), read_data); }
|
其中,可通過xil_printf("data at address %d is %d\r\n", 4*i, read_data);檢視每一次從BRAM讀出的資料的具體值,若無需使用則可註釋掉。
19.6 程式測試
程式測試串列埠列印資訊如下圖所示。
第1次讀出的1024個數據。
第2次讀出的1024個數據。
第3次讀出的1024個數據。
後面不作一一列舉。
本課讀寫的速度相對較慢,讀者可以嘗試修改程式,實現批