【轉】AXI_Lite 總線詳解

目錄:
  · 1.前言
      · 3.1 AXI 總線概述
      · 3.2 AXI 接口介紹
      · 3.3 AXI 協議概述
      · 3.4 AXI 協議之握手協議
      · 3.5 突發式讀寫
   · 4 AXI4-Lite 詳解
      · 4.1 AXI4-Lite 源碼查看
      · 4.2 AXI-Lite 源碼分析
   · 6 加載到 SDK
   · 7 本章小結
 
 
1 前言
  ZYNQ擁有ARM+FPGA這個神奇的架構,那麼ARM和FPGA到底是如 何進行通訊的呢?本章經過剖析AXI總線源碼,來一探其中的祕密。
 
2 AXI 總線與 ZYNQ 的關係
  AXI(Advanced eXtensible Interface)本是由ARM公司提出的一種總線協議,Xilinx 從 6 系列的 FPGA 開始對 AXI 總線提供支持,此時 AXI 已經發展到了 AXI4 這個版本,因此當你用到 Xilinx 的軟件的時候看到的都是「AIX4」的 IP,如 Vivado 打包一個 AXI IP的時候,看到的都是 Create a new AXI4 peripheral。到了 ZYNQ 就更沒必要說了,AXI 總線更是應用普遍,雙擊查看 ZYNQ 的 IP 核的內部配置,隨處可見 AXI 的身影。
 
3 AXI 總線和 AXI 接口以及 AXI 協議
  總線、接口和協議,這三個詞經常被聯繫在一塊兒,可是咱們內心要明白他們的區別。總線是一組傳輸通道,是各類邏輯器件構成的傳輸數據的通道,通常由由數據線、地址線、控制線等構成。接口是一種鏈接標準,又經常被稱之爲物理接口。協議就是傳輸數據的規則。
3.1 AXI 總線概述
  在ZYNQ中有支持三種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總線具備相同的組成部分:
       (1)讀地址通道,包含ARVALID, ARADDR, ARREADY信號;
       (2)讀數據通道,包含RVALID, RDATA, RREADY, RRESP信號;
       (3)寫地址通道,包含AWVALID,AWADDR, AWREADY信號;
       (4)寫數據通道,包含WVALID, WDATA,WSTRB, WREADY信號;
       (5)寫應答通道,包含BVALID, BRESP, BREADY信號;
       (6)系統通道,包含:ACLK,ARESETN信號。
  AXI4總線和AXI4-Lite總線的信號也有他的命名特色:
    讀地址信號都是以AR開頭(A:address;R:read)
    寫地址信號都是以AW開頭(A:address;W:write)
    讀數據信號都是以R開頭(R:read)
    寫數據信號都是以W開頭(W:write)
    應答型號都是以B開頭(B:back(answer back))
  瞭解到總線的組成部分以及命名特色,那麼在後續的實驗中您將逐漸看到他們的身影。每一個信號的做用暫停不表,放在後面一一介紹。
  而AXI4-Stream總線的組成有:
    (1)ACLK信號:總線時鐘,上升沿有效;
    (2)ARESETN信號:總線復位,低電平有效
    (3)TREADY信號:從機告訴主機作好傳輸準備;
    (4)TDATA信號:數據,可選寬度32,64,128,256bit
    (5)TSTRB信號:每一bit對應TDATA的一個有效字節,寬度爲TDATA/8
    (6)TLAST信號:主機告訴從機該次傳輸爲突發傳輸的結尾;
    (7)TVALID信號:主機告訴從機數據本次傳輸有效;
    (8)TUSER信號 :用戶定義信號,寬度爲128bit。
  對於AXI4-Stream總線命名而言,除了總線時鐘和總線復位,其餘的信號線都是以T字母開頭,後面跟上一個有意義的單詞,看清這一點後,能幫助讀者記憶每一個信號線的意義。如TVALID = T+單詞Valid(有效),那麼讀者就應該馬上反應該信號的做用。每一個信號的具體做用,在後面分析源碼時再作分析。
 
3.2 AXI 接口介紹
  三種AXI接口分別是:
  AXI-GP接口(4個):是通用的AXI接口,包括兩個32位主設備接口和兩個32位從設備接口,用過改接口能夠訪問PS中的片內外設。
  AXI-HP接口(4個):是高性能/帶寬的標準的接口,PL模塊做爲主設備鏈接(從下圖中箭頭能夠看出)。主要用於PL訪問PS上的存儲器(DDR和On-Chip RAM)
  AXI-ACP接口(1個):是ARM多核架構下定義的一種接口,中文翻譯爲加速器一致性端口,用來管理DMA之類的不帶緩存的AXI外設,PS端是Slave接口。咱們能夠雙擊查看ZYNQ的IP核的內部配置,就能發現上述的三種接口,圖中已用紅色方框標記出來,咱們能夠清楚的看出接口鏈接與總線的走向:
 
3.3 AXI 協議概述
  講到協議不可能說是撇開總線單講協議,由於協議的制定也是要創建在總線構成之上的。雖說AXI4,AXI4-Lite,AXI4-Stream都是AXI4協議,可是各自細節上仍是不一樣的。
  總的來講,AXI總線協議的兩端能夠分爲分爲主(master)、從(slave)兩端,他們之間通常須要經過一個AXI Interconnect相鏈接,做用是提供將一個或多個AXI主設備鏈接到一個或多個AXI從設備的一種交換機制。當咱們添加了zynq以及帶AXI的IP後再進行自動連線時vivado會自動幫咱們添加上這個IP,你們應該是不陌生了。AXI Interconnect的主要做用是,當存在多個主機以及從機器時,AXIInterconnect負責將它們聯繫並管理起來。因爲AXI支持亂序發送,亂序發送須要主機的ID信號支撐,而不一樣的主機發送的ID可能相同,而AXI Interconnect解決了這一問題,他會對不一樣主機的ID信號進行處理讓ID變得惟一。
  AXI協議將讀地址通道,讀數據通道,寫地址通道,寫數據通道,寫響應通道分開,各自通道都有本身的握手協議。每一個通道互不干擾卻又彼此依賴。這也是AXI高效的緣由之一。 
 
3.4 AXI 協議之握手協議
  AXI4 所採用的是一種 READY,VALID 握手通訊機制,簡單來講主從雙方進行數據通訊前,有一個握手的過程。傳輸源產生 VLAID 信號來指明什麼時候數據或控制信息有效。而目地源產生 READY 信號來指明已經準備好接受數據或控制信息。傳輸發生在 VALID和 READY 信號同時爲高的時候。VALID 和 READY 信號的出現有三種關係。
   (1) VALID 先變高 READY 後變高。時序圖以下:
在箭頭處信息傳輸發生。
   (2) READY 先變高 VALID 後變高。時序圖以下:
一樣在箭頭處信息傳輸發生。
   (3) VALID 和 READY 信號同時變高。時序圖以下: 
  在這種狀況下,信息傳輸立馬發生,如圖箭頭處指明信息傳輸發生。須要強調的是,AXI的五個通道,每一個通道都有握手機制,接下來咱們就來分析一下AXI-Lite的源碼來更深刻的瞭解AXI機制。 
 
3.5 突發式讀寫
一、突發式讀的時序圖以下:
 
當地址出如今地址總線後,傳輸的數據將出如今讀數據通道上。設備保持 VALID 爲低直到讀數據有效。爲了代表一次突發式讀寫的完成,設備用 RLAST 信號來表示最後一個被傳輸的數據。
二、 突發式寫時序圖以下: 
這一過程的開始時,主機發送地址和控制信息到寫地址通道中,而後主機發送每個寫數據到寫數據通道中。當主機發送最後一個數據時,WLAST 信號就變爲高。當設備接收完全部數據以後他將一個寫響應發送回主機來代表寫事務完成。
 
4 AXI4-Lite 詳解
4.1 AXI4-Lite 源碼查看
  Step1:要看到AXI-Lite的源碼,咱們先要自定義一個AXI-Lite的IP,新建工程以後,選擇,菜單欄->Tools->Creat and Package IP:
Step2:選擇Next
Step3:選擇Create AXI4 Peripheral,而後Next:
Step4:給模塊命名,保存,而後Next
Step5:注意這裏接口類型選擇Lite,選擇Next:
Step6:選擇Edit IP,點擊Finish:
Step7:此後,Vivado會新建一個工程,專門編輯該IP,經過該工程,咱們就能夠看到Vivado爲咱們生成的AXI-Lite的操做源碼: 
 
4.2 AXI-Lite 源碼分析
  當打開頂層文件的時,映入眼簾的是一堆AXI的信號,這些信號是否似曾相識?
input wire s00_axi_aclk, input wire s00_axi_aresetn, input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_awaddr, input wire [2 : 0] s00_axi_awprot, input wire s00_axi_awvalid, output wire s00_axi_awready, input wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_wdata, input wire [(C_S00_AXI_DATA_WIDTH/8)-1 : 0] s00_axi_wstrb, input wire s00_axi_wvalid, output wire s00_axi_wready, output wire [1 : 0] s00_axi_bresp, output wire s00_axi_bvalid, input wire s00_axi_bready, input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_araddr, input wire [2 : 0] s00_axi_arprot, input wire s00_axi_arvalid, output wire s00_axi_arready, output wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_rdata, output wire [1 : 0] s00_axi_rresp, output wire s00_axi_rvalid, input wire  s00_axi_rready

 沒錯筆者曾在《AXI總線概述》這節中提到了他們,此次經過源碼分析再次隆重介紹它們。 html

  Vivado爲咱們生成的AXI-Lite的操做源碼,是一個例子,我只須要讀懂他,而後稍加修改,就能夠爲咱們所用。咱們先來看一段WDATA相關的代碼: 
always @( posedge S_AXI_ACLK ) begin
  if ( S_AXI_ARESETN == 1'b0 )
    begin slv_reg0 <= 0; slv_reg1 <= 0; slv_reg2 <= 0; slv_reg3 <= 0; end
  else begin
    if (slv_reg_wren) begin
        case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 2'h0:
            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin
                // Respective byte enables are asserted as per write strobes // Slave register 0 slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end  
          2'h1:
            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin
                // Respective byte enables are asserted as per write strobes // Slave register 1 slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end  
          2'h2:
            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin
                // Respective byte enables are asserted as per write strobes // Slave register 2 slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end  
          2'h3:
            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin
                // Respective byte enables are asserted as per write strobes // Slave register 3 slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end  
          default : begin slv_reg0 <= slv_reg0; slv_reg1 <= slv_reg1; slv_reg2 <= slv_reg2; slv_reg3 <= slv_reg3; end
        endcase
      end
  end
end
  這段程序的做用是,當PS那邊向AXI4-Lite總線寫數據時,PS這邊負責將數據接收到寄存器slv_reg。而slv_reg寄存器有0~3共4個。至於賦值給哪個由axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]決定,根據宏定義其實就是由axi_awaddr[3:2] (寫地址中不只包含地址,並且包含控制位,這裏的[3:2]就是控制位)決定賦值給哪一個slv_reg。
  PS調用寫函數時,若是不作地址偏移的話,axi_awaddr[3:2]的值默認是爲0的,舉個例子,若是咱們自定義的IP的地址被映射爲0x43C00000,那麼咱們Xil_Out32(0x43C00000,Value)寫的就是slv_reg0的值。若是地址偏移4位,如Xil_Out32(0x43C00000 + 4,Value) 寫的就是slv_reg1的值,依次類推。
  分析時只關注slv_reg0(其餘結構上也是如出一轍的): 
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end

  其中,C_S_AXI_DATA_WIDTH的宏定義的值爲32,也就是數據位寬,S_AXI_WSTRB就是寫選通訊號,S_AXI_WDATA就是寫數據信號。緩存

  存在於for循環中的最關鍵的一句:架構

    slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];app

  當byte_index = 0的時候這句話就等價於:函數

    slv_reg0[7:0] <= S_AXI_WDATA[7:0];源碼分析

  當byte_index = 1的時候這句話就等價於:性能

    slv_reg0[15:8] <= S_AXI_WDATA[15:8];學習

  當byte_index = 2的時候這句話就等價於:測試

    slv_reg0[23:16] <= S_AXI_WDATA[23:16];ui

  當byte_index = 3的時候這句話就等價於:

    slv_reg0[31:24] <= S_AXI_WDATA[31:24];

  也就是說,只有當寫選通訊號爲1時,它所對應S_AXI_WDATA的字節纔會被讀取。

  讀懂了這段話以後,咱們就知道了,若是咱們想獲得PS寫到總線上的數據,咱們只須要讀取slv_reg0的值便可。

  那若是,咱們想寫數據到總線讓PS讀取該數據,咱們該怎麼作呢?咱們繼續來看有關RADTA讀數據代碼:

// Output register or memory read data
always @( posedge S_AXI_ACLK ) begin
  if ( S_AXI_ARESETN == 1'b0 )
    begin axi_rdata <= 0; end
  else
    begin    
      // When there is a valid read address (S_AXI_ARVALID) with // acceptance of read address by the slave (axi_arready), // output the read dada
      if (slv_reg_rden) begin axi_rdata <= reg_data_out;     // register read data
        end   
    end
end

   觀察可知,當PS讀取數據時,程序會把reg_data_out複製給axi_rdata(RADTA讀數據)。咱們繼續追蹤reg_data_out:

always @(*) begin
      // Address decoding for reading registers
      case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 2'h0 : reg_data_out <= slv_reg0;
        2'h1 : reg_data_out <= slv_reg1;
        2'h2 : reg_data_out <= slv_reg2;
        2'h3 : reg_data_out <= slv_reg3;
        default : reg_data_out <= 0; endcase
end

  和前面分析的同樣此時經過判斷axi_awaddr[3:2]的值來判斷將那個值給reg_data_out上,一樣當PS調用讀取函數時,這裏axi_awaddr[3:2]默認是0,因此咱們只須要把slv_reg0替換成咱們本身數據,就可讓PS經過總線讀到咱們提供的數據。

  這裏可能有的讀者會問了,slv_reg0不是總線寫過來的數據嗎?由於筆者說過這個程序是Vivado爲咱們提供的例子,它這麼作無非是想驗證我寫出去的值和我讀進入的值相等。可是他怎麼寫確實會對初看代碼的人形成困擾。

  最後筆者提出一個問題,爲何寫通道要比讀通道多了一列應答通道,這是爲何呢?

  首先,你要知道這個應答信號是幹什麼用的?

  寫應答,主要是回覆主機你這個寫過程是沒有問題的,那讀爲何不須要這個過程呢?

 

  這時由於主機在讀取數據時,從機能夠直接經過讀數據通道給主機反饋信息,所以就沒有必要再來開闢一個單獨的應答通道了。

小結:

  若是咱們想讀AXI4_Lite總線上的數據時,只需關注slv_reg的數據,咱們可自行添加一段代碼,如:

reg [11:0]rlcd_rgb; always @( posedge S_AXI_ACLK ) begin
    if ( S_AXI_ARESETN == 1'b0 )
        begin rlcd_rgb <= 12'd0;
        end
    else
    begin rlcd_rgb <= slv_reg0[11:0]; end
end  
assign lcd_rgb = rlcd_rgb;

   若是咱們想對AXI4_Lite信號寫數據時,咱們只需修改對reg_data_out的賦值,如:

//寫總線測試修改!!!!!!!!!
wire[31:0]wlcd_xy;// = {10'd0,lcd_xy};
assign wlcd_xy = {10'd0,lcd_xy};
assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid; always @(*) begin
    // Address decoding for reading registers
    case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 2'h0 : reg_data_out <= wlcd_xy;//slv_reg0; 
      2'h1 : reg_data_out <= slv_reg1;
      2'h2 : reg_data_out <= slv_reg2;
      2'h3 : reg_data_out <= slv_reg3;
      default : reg_data_out <= 0; endcase
end

   最後強調下若是咱們自定義的IP的地址被映射爲0x43C00000,那麼咱們Xil_Out32(0x43C00000,Value)寫的就是slv_reg0的值。若是地址偏移4位,如Xil_Out32(0x43C00000 + 4,Value) 寫的就是slv_reg1的值,依次類推。

  目前這裏只有4個寄存器,那是由於以前選擇的是4個,其實咱們能夠定義的更多:

  在ps的頭文件裏能夠看到咱們自定義的IP的地址是有個範圍的
#define XPAR_ MYIPFREQUENCY_ 0_ S00_ AXI_ BASEADDR 0x43C00000
#define XPAR_ MYIPFREQUENCY_ 0_ S00_ AXI_ HIGHADDR 0x43C0FFFF

   理論上只要基地址 + 偏移量不要超過HIGHADDR便可。

 

5 觀察 AXI4-Lite 總線信號
  在第十章,咱們封裝了一個 AXI_Lite 的 GPIO,經過本章的分析,咱們在第十章工程的基礎上經過添加一個 ila 核的方式,來具體看看 AXI_Lite 總線的信號。
  Step1:作好第十章工程的備份,而後直接打開第十章的工程。 
  Step2:單擊 IP icon 添加 ila CORE
  
  Step3:雙擊打開 ILA CORE
  
  Step4:雙擊打開 ILA CORE
    General Options 設置以下
  
  Probe_Ports 設置以下,以後單擊 OK
  
  Step5:鏈接 Probe0 到 GPIO_LED。
  Step6:鏈接 CLK 接口到 FCLK_CLK0 接口
  Step7:選中 Processing_System7_0_axi_periph 和 GPIO_LITE_ML_0 之間的 S_AXI 總線。
  Step8:右擊選擇 Mark Debug 
  

  Step9:接下來依然是,右鍵單擊Block文件,文件選擇Generate the Output Products。

  Step10:繼續右鍵單擊Block文件,選擇Create a HDL wrapper,根據Block文件內容產生一個HDL 的頂層文件,並選擇讓vivado自動完成。

  Setp11:單擊Run Synthesis,若是有 Save 對話框彈出選擇保存。

  Setp12:綜合結束後選擇Synthesized Design option單擊 OK。

  Step13:在以下對話框中找到Unassigned debug nets(若是對話框沒有出現選擇 菜單->Window > Debug)

  

  Step14:右擊 Unassigned Debug Nets 選擇Set up Debug… 以後單擊 Next

  Step15:刪除紅色錯誤的信號而後單擊Next 到結束

  

  Step16:生成 Bit 文件。
 
6 加載到 SDK
  Step1:導出硬件。
  Step2:右擊工程,選擇 Debug as ->Debug configuration。
  Step3:選中 system Debugger,雙擊建立一個系統調試。 
  
  Step4:設置系統調試。
  
  Step5:回到 VIVADO 單擊 Open Target->Auto Connect
  
  Step6:加載完成後的界面 
  
  Step7:選擇菜單->window->Debugprobes 選擇 AXI_WVALID 和 AXI_AWVALID 作爲觸發信號

   

  Step8:設置觸發條件爲 1 

   

  Step9:設置觸發位置爲 512
  
  Step10:單擊箭頭所指向啓動觸發 

   

  Step11:進入等待觸發狀態 
  
  Step12:單擊運行 後 VIVADO HW_ILA2 窗口採集到波形輸出,能夠看到 AXI總線的工做時序。
  
  Step13:HW_ILA1 窗口採集到的數據是 GPIO_LED 的值爲 0x02,同時可觀察到開發板上的 LED2 亮起。
 
7 本章小結
  經過本章的學習,咱們首先得認識到總線和接口以及協議的區別,其次經過分析AXI4-Lite,AXI4-Stream,AXI4總線的從機代碼,對AXI協議有必定的認識,那麼在後面學習AXI的一些IP時就不會有恐懼的心理。
  最後,咱們再理一理AXI總線和AXI接口的關係。在ZYNQ中,支持AXI4-Lite,AXI4和AXI4-Stream三種總線協議,這前面已經說過了,要注意的是PS與PL之間的接口(AXI-GP接口,AXI-HP接口以及AXI-ACP接口)卻只支持AXI-Lite和AXI協議這兩種總線協議。也就是說PL這邊的AXI-Stream的接口是不能直接與PS對接的,須要通過AXI4或者AXI4-Lite的轉換。好比後面將用到的VDMA IP ,它就實現了在PL內部AXI4到AXI-Stream的轉換,VDMA利用的接口就是AXI-HP接口。
 
注:本博客轉自米聯客博客:S02_CH12_ AXI_Lite 總線詳解 https://www.cnblogs.com/milinker/p/6474706.html
相關文章
相關標籤/搜索