自作RTLにAXI4-Liteインタフェース(M側)を追加する方法 (2) IPコアAXI-UART Liteのレジスタに書き込む方法

AXI4
スポンサーリンク

本記事の概要

概要

本記事では、AXI4-LiteインタフェースをもつカスタムIPとXilinx社が提供するIP”AXI UART Lite”とを組み合わせて、”Hello World”文字列をシリアル出力させました。
シリアル出力を行うためのロジック回路をProgrammable Logic (PL)に作成し、RTLシミュレーションを行いました。

Xilinx社では、IPコアのインタフェースをAXI4プロトコルで共通化することによって、システム開発での生産性・互換性・柔軟性を高めています。

自作で作成したRTLモジュールを既存のIPと接続するためには、自作RTLモジュールにAXIプロトコルの入出力インターフェースを追加しなくてはいけません。
しかし、実際にゼロからIPにAXIプロトコルの入出力インターフェースを追加するのは大変です。

そこで、前回の記事では、Xilinx社が提供するVivadoのIPパッケージャーという機能を用いて、自作のカスタムIPにAXIプロトコルの入出力インターフェースを追加する方法について解説しました。

今回の記事では、前回の記事を応用し、Hello Worldをシリアル出力させるロジック回路をProgrammable Logic (PL)上に作成しました。

外部にシリアル出力させるIPとして、AXI UART LiteというIPコアが用意されています。しかし、このIPの入力には、AXI4-Liteポートしか用意されていません。

https://japan.xilinx.com/support/documentation/ip_documentation/axi_uartlite/v2_0/pg142-axi-uartlite.pdfより抜粋


そこで、IPパッケージャーを用いて、AXI UART Liteと接続するためのカスタムIPを作成しました。

本記事の対象読者:以下の状況に直面している方
  • 自作RTLモジュールの接続先IPにAXI4インタフェースしか用意されていないような場合
  • カスタムIPに制御レジスタやステータスレジスタを設け、プロセッサからそれらを参照したい場合

AXI4プロトコルの動作について学ぶには以下の書籍がおすすめです。入門者向けにAXI4プロトコルの動作は、「FPGAプログラミング大全」に非常にわかりやすく解説されています。

目標

本記事全体の目標は次の通りです:

自作のRTLモジュールにAXI4-Liteインタフェース(M側)を追加する

今回は、カスタムIPからAXI4-Liteを通じて、XilinxのIP”AXI UART Lite”のデータレジスタに値を書き込み、文字列“Hello world”を外部PCに出力させてみました。

カスタムIPのカスタマイズ

カスタムIPの動作を図示しました。
カスタムIPは、トリガー信号を受信したら、送信用データをIP”AXI-UART Lite”のTx FIFOに書き込みます。
問題なく書き込みが終われば、”AXI UART Lite”のIPがTx FIFOの入力をシリアル信号に変換して、外部に出力します。

前回の記事で解説したとおり、IPパッケージャーで自動生成を行った直後のカスタムIPは、「S側IPへの書き込み処理」「S側IPからの読み出し処理」、「書き込み値と読み出し値の判定処理」の3つの処理を行うロジックで構成されています。

ユーザーロジックでは、この3つのロジックをステートマシンを使って順次切り替えて実行していました。
図解すると下の図のようになります。

一方で、今回のカスタムIPでは、IP”AXI-UART Lite”のTransmit Data FIFOにデータを書き込むだけでよいので、上記の3つのロジックのうち、「S側IPへの書き込み処理」のみを行えばよいと考えました。
また、書き込み処理は、“C_M_TRANSACTIONS_NUM”で指定した回数分だけ行っていましたが、今回は一つのレジスタに書き込むだけなので1回だけ行えばよいです。

そこで、ユーザーロジックを以下のように変更しました。
変更箇所のソースコードをトグルボックスの中に入れています。ご興味のある方は+マークをクリックしてご覧いただければと思います。

下の図における、緑色のユーザーロジックの例と青色のユーザーロジックと呼んでいるコードの部分を変更しました。

    //--------------------------------
    //User Logic
    //--------------------------------

    //Write Addresses                                        
    always @(posedge M_AXI_ACLK) begin                                                     
        if (M_AXI_ARESETN == 0  || init_txn_pulse == 1'b1) begin                                                 
            axi_awaddr <= 32'h00000004;                                 
        end                                                      
    end       
                                                     
    // Write data generation                                      
    always @(posedge M_AXI_ACLK) begin                                                     
        if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1 )  begin                                                 
            axi_wdata <=  {24'b0, AXI_DATA};                    
        end                                                           
    end    

    //implement master command interface state machine                         
    always @ ( posedge M_AXI_ACLK)                                                    
    begin                                                                             
      if (M_AXI_ARESETN == 1'b0)                                                     
        begin                                                                         
        // reset condition                                                            
        // All the signals are assigned default values under reset condition          
          mst_exec_state  <= IDLE;                                            
          start_single_write <= 1'b0;                                                 
          write_issued  <= 1'b0; 
        end                                                                           
      else                                                                            
        begin                                                                         
         // state transition                                                          
          case (mst_exec_state)                                                       
                                                                                      
            IDLE:                                                             
            // This state is responsible to initiate 
            // AXI transaction when init_txn_pulse is asserted 
              if ( init_txn_pulse == 1'b1 )                                     
                begin                                                                 
                  mst_exec_state  <= INIT_WRITE;   
                end                                                                   
              else                                                                    
                begin                                                                 
                  mst_exec_state  <= IDLE;                                    
                end                                                                   
                                                                                      
            INIT_WRITE:                                                               
              if (writes_done)                                                        
                begin                                                                 
                  mst_exec_state <= IDLE;//                                      
                end                                                                   
              else                                                                    
                begin                                                                 
                  mst_exec_state  <= INIT_WRITE;                                      
                                                                                      
                    if (~axi_awvalid && ~axi_wvalid && ~M_AXI_BVALID && ~last_write && ~start_single_write && ~write_issued)
                      begin                                                           
                        start_single_write <= 1'b1;                                   
                        write_issued  <= 1'b1;                                        
                      end                                                             
                    else if (axi_bready)                                              
                      begin                                                           
                        write_issued  <= 1'b0;                                        
                      end                                                             
                    else                                                              
                      begin                                                           
                        start_single_write <= 1'b0; //Negate to generate a pulse      
                      end                                                             
                end                                                                   
                                                                    
             default :                                                                
               begin                                                                  
                 mst_exec_state  <= IDLE;                                     
               end                                                                    
          endcase                                                                     
      end                                                                             
    end //MASTER_EXECUTION_PROC                                                       
                                                                                      
    //Terminal write count                                                             
    always @(posedge M_AXI_ACLK)                                                      
    begin                                                                             
      if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)                                                         
        last_write <= 1'b0;                                                           
                                                                                      
      //The last write should be associated with a write address ready response       
      else if ((write_index == 1) && M_AXI_AWREADY)                
        last_write <= 1'b1;                                                           
      else                                                                            
        last_write <= last_write;                                                     
    end                                                                               
                                                                                      
    //Check for last write completion.                                                                   
    always @(posedge M_AXI_ACLK)                                                      
    begin                                                                             
      if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)                                                         
        writes_done <= 1'b0;                                                          
                                                                                      
        //The writes_done should be associated with a bready response                 
      else if (last_write && M_AXI_BVALID && axi_bready)                              
        writes_done <= 1'b1;                                                          
      else                                                                            
        writes_done <= writes_done;                                                   
    end     

なお、ユーザーポートには、AXI_DATAという8ビット長の入力ポートを追加しています。

        // Users to add ports here
        input wire [7:0]    AXI_DATA,     

レジスタへの書き込み

書き込み処理に関係する記述のみを残し、それ以外の読み出し処理と判定比較処理に関係する記述をすべて削除しました。

書き込み処理も、以下のコードのようにオフセットアドレス”32’h0000_0004”のみに、データを{24’b0, AXI_DATA}の1回だけ書き込むようにしています。

    //Write Addresses                                        
    always @(posedge M_AXI_ACLK) begin                                                     
        if (M_AXI_ARESETN == 0  || init_txn_pulse == 1'b1) begin                                                 
            axi_awaddr <= 32'h00000004;                                 
        end                                                      
    end       
                                                     
    // Write data generation                                      
    always @(posedge M_AXI_ACLK) begin                                                     
        if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1 )  begin                                                 
            axi_wdata <=  {24'b0, AXI_DATA};                    
        end                                                           
    end    

オフセットアドレス”32’h0000_0004”はIP”AXI UART Lite”のTx FIFOのオフセットアドレスです。

https://japan.xilinx.com/support/documentation/ip_documentation/axi_uartlite/v2_0/pg142-axi-uartlite.pdfより抜粋

このレジスタの下位8ビットにAXI_DATAという1バイト分の情報を書き込むことが可能です。

今回、最終的には、”Hello World”という文字列を順次、外部に伝送したいのですが、まずは1文字(1バイト)分だけ送ることができるようにしています。

次回の記事では、このカスタムIPに順次文字列を伝送するためのIPを紹介した後、実際にZybo上で動作確認をした結果をまとめます。

状態遷移図

ステートマシンは残したままにしましたが、以下のように変更しています。
書き込み処理が完了すれば、状態INIT_WRITEから状態IDLEに戻るようにして、INIT_READやCOMPAREの状態に遷移しないようにしています。

カスタムIPの再パッケージ

ラッパーファイルにも、ユーザーポートAXI_DATAを追加した後、カスタムIPの再パッケージを行いました。
パッケージの方法は以下の記事で解説した方法と同じです。

8ビットの信号をAXI-UART Liteに出力するためのIPが出来上がりました。
AXI_DATAという、Tx FIFOに書き込む1バイト分の信号を入力するためのポートを追加しています。

自作カスタムIPの機能とI/O
  • 機能:8ビット信号をAXI-UART LiteのTx FIFOレジスタに書き込む
  • 入力側
    • AXI_DATA:レジスタに書き込む8ビット信号
    • m00_axi_init_axi_txn:書き込みを開始するトリガー信号
    • m00_axi_aclk:クロック
    • m00_axi_aresetn:リセット
  • 出力側
    • M00_AXI:AXI-UART Liteに接続するAXI-LiteのM側ポート
    • m00_axi_error:使用していません
    • m00_axi_txn_done:使用していません

※m00_axi_errorとm00_axi_txn_doneは比較判定処理の結果と完了を示すためのポートでしたがが、カスタマイズで比較判定処理を削除したため、意味のある出力はなされません。

RTLシミュレーション

テストベンチ環境

RTLシミュレーションを行い、カスタムIPが正しく動作しているかどうかを見てみましょう。

シミュレーションを行うために、次のようなブロックデザインを作成しました。

AXI-UART Liteの設定は以下の通りです。Zyboのクロック125MHzを分周し100MHzにして入力しました。

また、テストベンチのコードは以下のようにしました。

`timescale 1ns / 1ps

module uart_tb();
//-----------------------------------------------------------------------
// clk and reset
//-----------------------------------------------------------------------    
    localparam  SYSSTEP         = 8         ;
    localparam  STEP            = 10        ;

    reg         SYSCLK      ;  //125MHz
    reg         SYSRST      ;  
    reg         TXNSTART    ;

    always begin
        SYSCLK = 0; #(SYSSTEP / 2);
        SYSCLK = 1; #(SYSSTEP / 2);
    end

    initial begin
        SYSRST = 0; #(SYSSTEP *10);
        SYSRST = 1; #(SYSSTEP);
        SYSRST = 0;
    end
    
    initial begin
        TXNSTART = 0; #(STEP * 10000);
        TXNSTART = 1; 
    end

//-----------------------------------------------------------------------
// instanciation of wrppaer file
//-----------------------------------------------------------------------  
    design_1_wrapper design_1_wrapper_inst(
         .RST           (SYSRST     )
        ,.sys_clock     (SYSCLK     )

        ,.AXI_DATA      (8'd10      )
        ,.BTN           (TXNSTART   )
        
        ,.UART_RXD_OUT  (   )
        ,.UART_TXD_IN   (   )
    );

endmodule

RTLシミュレーションの結果

シミュレーションの結果は以下のとおりです。
m00_axi_init_ai_txn(書き込みを開始するトリガー信号)の立ち上がりエッジで、AXIのトランザクションが開始されます。

その後、UART_TXD_INからシリアル信号(8’d10;2bit表記で00001010)がリトルエンディアン形式(下位ビットから順に送信)で出力され、問題なくTx FIFOに書き込みが行われたことが確認できます。
想定される動作を図にすると下のようになり、シミュレーションとも一致しています。

次に、AXIトランザクションの周辺を拡大してみましょう。

AWチャネルとWチャネルから、それぞれアドレス、データ信号とValid信号を送信しています。
S側IPからのready信号を受け取ってトランザクションが成立したら、今度はS側IPから返答していることがわかります。

ひがし

本記事では、M側のAXI4-Liteのカスタマイズの応用例として、AXI Lite-UARTに送信するためのカスタムIPを作成しました。
次回は、実際に文字列Hello Worldを送り、実機で動作確認をしてみようと思います。

最後までご覧いただきありがとうございました!

スポンサーリンク


次回の記事のリンク

参考:開発環境

環境
  • 開発用PC: Windows 10, 64bit
    • Vivado Design Suite – HLx Edition – 2020.2
    • Vitis コア開発キット – 2020.2
  • 開発用基板: Zybo Zynq-7010評価ボード(Board Rev.4)
    • Zynq XC7Z010-1CLG400C

コメント