XILINX AXI_Lite 總線詳解

本文轉載自FPGA之家

12.1前言

ZYNQ擁有ARM+FPGA這個神奇的架構,那麼ARM和FPGA究竟是如何進行通信的呢?本章通過剖析AXI總線源碼,來一探其中的祕密。

12.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的身影。

12.3 AXI總線和AXI接口以及AXI協議

總線、接口和協議,這三個詞常常被聯繫在一起,但是我們心裏要明白他們的區別。

總線是一組傳輸通道,是各種邏輯器件構成的傳輸數據的通道,一般由由數據線、地址線、控制線等構成。接口是一種連接標準,又常常被稱之爲物理接口。

協議就是傳輸數據的規則。

12.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(有效),那麼讀者就應該立刻反應該信號的作用。每個信號的具體作用,在後面分析源碼時再做分析

12.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核的內部配置,就能發現上述的三種接口,圖中已用紅色方框標記出來,我們可以清楚的看出接口連接與總線的走向:

12.3.3 AXI協議概述

講到協議不可能說是撇開總線單講協議,因爲協議的制定也是要建立在總線構成之上的。雖然說AXI4,AXI4-Lite,AXI4-Stream都是AXI4協議,但是各自細節上還是不同的。

總的來說,AXI總線協議的兩端可以分爲分爲主(master)、從(slave)兩端,他們之間一般需要通過一個AXI Interconnect相連接,作用是提供將一個或多個AXI主設備連接到一個或多個AXI從設備的一種交換機制。當我們添加了zynq以及帶AXI的IP後再進行自動連線時vivado會自動幫我們添加上這個IP,大家應該是不陌生了。

AXI Interconnect的主要作用是,當存在多個主機以及從機器時,AXI Interconnect負責將它們聯繫並管理起來。由於AXI支持亂序發送,亂序發送需要主機的ID信號支撐,而不同的主機發送的ID可能相同,而AXI Interconnect解決了這一問題,他會對不同主機的ID信號進行處理讓ID變得唯一。

AXI協議將讀地址通道,讀數據通道,寫地址通道,寫數據通道,寫響應通道分開,各自通道都有自己的握手協議。每個通道互不干擾卻又彼此依賴。這也是AXI高效的原因之一。

12.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機制。

12.3.5 突發式讀寫

1、突發式讀的時序圖如下:

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

2、 突發式寫時序圖如下:

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

12.4 AXI4-Lite詳解

12.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的操作源碼:

12.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總線概述》這節中提到了他們,這次通過源碼分析再次隆重介紹它們。

地址通道

數據通道

     

ARVALID

讀地址有效。此信號表明該信道此時能有效讀出地址和控制信息

RVALID

讀數據有效。此信號表明該信道此時能有效讀出數據

   

ARADDR

讀地址

RDATA

讀數據

   

ARREADY

讀地址準備好了。該信號指示從器件準備好接受一個地址和相關聯的控制信號

RREADY

讀數據準備好了。該信號指示從器件準備好接收數據

   

ARPROT

保護類型。這個信號表示該事務的特權和安全級別,並確定是否該事務是一個數據存取或指令的訪問

RRESP

讀取響應。這個信號表明讀事務處理的狀態。

   

地址通道

數據通道

應答通道

   

AWVALID

寫地址有效。這個信號表示該主信令有效的寫地址和控制信息。

WVALID

寫有效。這個信號表示有效的寫數據和選通信號都可用。

BVALID

寫響應有效。此信號表明寫命令的有效寫入響應。

AWADDR

寫地址

WDATA

寫數據

BREADY

響應準備。該信號指示在主主機可以接受一個響應信號

AWREADY

寫地址準備好了。該信號指示從器件準備好接受一個地址和相關聯的控制信號

WSTRB

寫選通。這個信號表明該字節通道持有效數據。每一bit對應WDATA一個字節

BRESP

寫響應。這個信號表示寫事務處理的狀態。

AWPROT

寫通道保護類型。這個信號表示該事務的特權和安全級別,並確定是否該事務是一個數據存取或指令的訪問

WREADY

寫準備好了。該信號指示從器件可以接受寫數據。

   

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];

當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];

當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即可。

12.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文件。

12.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亮起。

12.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接口。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章