四畳半テクノポリス

コロナのストレスで気が狂い、D進した院生

VIVADOのAXI4Streamを読む:Master編

アドベントカレンダーにはリングオシレータについて書くとの宣言していたのですが、最近後輩指導のために書いていたVivadoのテンプレートからAXI4Streamの記事がいい感じだったのでこちらの記事を掲載させていただくことにしました。

AXIStream

VivadoのIP PackagerでAXI-Streamのインタフェースを持ったIPコアを作成します。Tools->Create and Package IPを選択します。

IPパッケージ

このツールではAXIインタフェースを持ったIPのテンプレートを自動的に生成してくれます。create a new AXI4 peripheralを選択してください。

AXI4ペリフェラルを選択

IPに任意のインタフェースを生成することができるのでAXI4-StreamのMasterのインタフェースを生成します。

インタフェースにAXI4-StreamのMasterを追加

ここでEditを選択するとIPの編集ように新たにウィンドウが立ち上がります。私は毎回パッケージ化することが面倒くさいので、再利用予定が無ければ生成されたテンプレートである[パッケージ名]_M00_AXIS.v[パッケージ名].vをメインのプロジェクトのディレクトリにコピーしてしまいますが、IPの編集ウィンドウでシミュレーションを行うこともできます。

パッケージを編集

 VIVADOが生成するAXIStreamテンプレート

とりあえず生成されたAXI4-Streamのテンプレートを示します。ついでにテンプレート中のコメントもガバガバ翻訳ですが訳しました。見たい人は次の行を開いてください。

開く

`timescale 1 ns / 1 ps

    module myip_v1_0_M00_AXIS #
    (
        // ユーザーはここにパラメータを追加します

        // ユーザーパラメータここまで
        // この行以降のパラメータを変更しないでください

        //S_AXISアドレスバスの幅です。スレーブはC_M_AXIS_TDATA_WIDTHで指定されたビット幅のアドレスで読み書きされます。
        parameter integer C_M_AXIS_TDATA_WIDTH = 32,
        // C_M_START_COUNTは、トランザクションを開始/発行する前にマスター側が待機するクロックサイクル数です。
        parameter integer C_M_START_COUNT  = 32
    )
    (
        // ユーザーはここにポートを追加してください

        // ユーザーのポートはここまでです
        // この行以降のポートは変更しないでください

        // グローバルポート
        input wire  M_AXIS_ACLK,
        // リセット
        input wire  M_AXIS_ARESETN,
        //  マスターストリームポート。TVALID は、マスターが転送の準備ができていることを表します。TVALIDとTREADYの両方がアサート(1になる)されると転送が行われます。
        output wire  M_AXIS_TVALID,
        // TDATA はマスターからスレーブにデータを転送するための主要な経路です。
        output wire [C_M_AXIS_TDATA_WIDTH-1 : 0] M_AXIS_TDATA,
        // バイト・イネーブル。該当するバイトが有効かどうかを示す。
        output wire [(C_M_AXIS_TDATA_WIDTH/8)-1 : 0] M_AXIS_TSTRB,
        // パケットの境界を表します。(パケット終了時には1になります)
        output wire  M_AXIS_TLAST,
        // TREADY は現在のサイクルにおいてスレーブ側がデータの受け入れが可能なことを表します。
        input wire  M_AXIS_TREADY
    );
    // 転送データの総数です。byte数ではなくワード数(転送サイクル回数)
    localparam NUMBER_OF_OUTPUT_WORDS = 8;                                               
                                                                                         
    // clogb2関数は整数の値を返します。               
    // 入力された値は2を低としたlogを計算しceilした値を返します。                                           
    function integer clogb2 (input integer bit_depth);                                   
      begin                                                                              
        for(clogb2=0; bit_depth>0; clogb2=clogb2+1)                                      
          bit_depth = bit_depth >> 1;                                                    
      end                                                                                
    endfunction                                                                          
                                                                                         
    // WAIT_COUNT_BITS はcounterのビット幅です。                             
    localparam integer WAIT_COUNT_BITS = clogb2(C_M_START_COUNT-1);                      
                                                                                         
    // FIFOのアドレスを計算するために、アドレスのビット幅を計算します、.  
    localparam bit_num  = clogb2(NUMBER_OF_OUTPUT_WORDS);                                
                                                                                         
    // ステートマシンの状態を宣言します                                               
    // 制御ステートマシンはFIFOへの入力ストリーミングデータの書き込みを監視し、
    // FIFOからストリーミングデータを出力します。
    parameter [1:0] IDLE = 2'b00,        // 初期化・アイドルステート               
                                                                                         
                    INIT_COUNTER  = 2'b01, // カウンタの初期化ステート、一度
                                    // カウンタがC_M_START_COUNT までカウントすると,        
                                    // ステートマシンは SEND_STREAMへ遷移します。     
                    SEND_STREAM   = 2'b10; // このステートでは
                                         // ストリームデータはM_AXIS_TDATAを介して出力されます。   
    // ステートマシンの状態変数                                                            
    reg [1:0] mst_exec_state;                                                            
    // FIFOの読み出しポインタです。                                                  
    reg [bit_num-1:0] read_pointer;                                                      

    // AXI Stream の内部信号
    //wait counter.転送前にマスターが側が通信準備で待つ
    reg [WAIT_COUNT_BITS-1 : 0]    count;
    //転送するデータが準備できていることを表します。
    wire      axis_tvalid;
    //axis_tvalidを1サイクル遅らせるためのレジスタです。
    reg   axis_tvalid_delay;
    //転送するデータ(パケット)の終わりを表します。
    wire      axis_tlast;
    //axis_tlastを1サイクル遅らせるためのレジスタです。
    reg   axis_tlast_delay;
    //FIFO implementation signals
    reg [C_M_AXIS_TDATA_WIDTH-1 : 0]   stream_data_out;
    wire      tx_en;
    //FIFOデータをすべて送りだした時に朝―とされます。
    reg   tx_done;


    // I/O ピンと内部信号の接続

    assign M_AXIS_TVALID = axis_tvalid_delay;
    assign M_AXIS_TDATA  = stream_data_out;
    assign M_AXIS_TLAST  = axis_tlast_delay;
    assign M_AXIS_TSTRB  = {(C_M_AXIS_TDATA_WIDTH/8){1'b1}};


    // 制御用のステートマシンです。                             
    always @(posedge M_AXIS_ACLK)                                             
    begin                                                                     
      if (!M_AXIS_ARESETN)                                                    
      // 同期リセット(active low)                                       
        begin                                                                 
          mst_exec_state <= IDLE;                                             
          count    <= 0;                                                      
        end                                                                   
      else                                                                    
        case (mst_exec_state)                                                 
          IDLE:                                                               
            // ストリーミングデータの存在を示すスレーブは                          
            // tvalidがアサ―トされると、                         
            // スレーブはtdataの受信を開始します。       
            //if ( count == 0 )                                                 
            //  begin                                                           
                mst_exec_state  <= INIT_COUNTER;                              
            //  end                                                             
            //else                                                              
            //  begin                                                           
            //    mst_exec_state  <= IDLE;                                      
            //  end                                                             
                                                                              
          INIT_COUNTER:                                                       
            // ストリーミングデータの存在を示すスレーブは                          
            // tvalidがアサ―トされると、                         
            // スレーブはtdataの受信を開始します。                           
            if ( count == C_M_START_COUNT - 1 )                               
              begin                                                           
                mst_exec_state  <= SEND_STREAM;                               
              end                                                             
            else                                                              
              begin                                                           
                count <= count + 1;                                           
                mst_exec_state  <= INIT_COUNTER;                              
              end                                                             
                                                                              
          SEND_STREAM:                                                        
            // サンプルデザインは マスターが出力のdataを駆動し 
            // FIFOとスレーブをFIFOから、A_AXIS_DATAのストアが完了すると
            // ストリーム機能が開始されます    (何言ってるかよくわかりませんがこのステートでは通信が実行されます)
            if (tx_done)                                                      
              begin                                                           
                mst_exec_state <= IDLE;                                       
              end                                                             
            else                                                              
              begin                                                           
                mst_exec_state <= SEND_STREAM;                                
              end                                                             
        endcase                                                               
    end                                                                       


    //tvalid の生成
    //axis_tvalid はステートマシンの状態変数(mst_exec_state)がSEND_STREAM かつ
    //read_pointerがNUMBER_OF_OUTPUT_WORDS以下の時アサ―トされます。
    assign axis_tvalid = ((mst_exec_state == SEND_STREAM) && (read_pointer < NUMBER_OF_OUTPUT_WORDS));
                                                                                                   
    // AXI tlast の生成
    // axis_tlast はread_pointerがNUMBER_OF_OUTPUT_WORDS-1のときアサートされます。          
    // (0 to NUMBER_OF_OUTPUT_WORDS-1) 
    assign axis_tlast = (read_pointer == NUMBER_OF_OUTPUT_WORDS-1);                                
                                                                                                   
                                                                                                   
    // axis_tvalid と axis_tlast を1サイクル遅延させます。
    // これにより M_AXIS_TDATAの遅延と一致させます。                                                        
    always @(posedge M_AXIS_ACLK)                                                                  
    begin                                                                                          
      if (!M_AXIS_ARESETN)                                                                         
        begin                                                                                      
          axis_tvalid_delay <= 1'b0;                                                               
          axis_tlast_delay <= 1'b0;                                                                
        end                                                                                        
      else                                                                                         
        begin                                                                                      
          axis_tvalid_delay <= axis_tvalid;                                                        
          axis_tlast_delay <= axis_tlast;                                                          
        end                                                                                        
    end                                                                                            


    //read_pointer pointer

    always@(posedge M_AXIS_ACLK)                                               
    begin                                                                            
      if(!M_AXIS_ARESETN)                                                            
        begin                                                                        
          read_pointer <= 0;                                                         
          tx_done <= 1'b0;                                                           
        end                                                                          
      else                                                                           
        if (read_pointer <= NUMBER_OF_OUTPUT_WORDS-1)                                
          begin                                                                      
            if (tx_en)                                                               
              // FIFOが有効な時   
              // 1データ読み出すごとにread_pointerを1づつインクリメントします.                                   
              begin                                                                  
                read_pointer <= read_pointer + 1;                                    
                tx_done <= 1'b0;                                                     
              end                                                                    
          end                                                                        
        else if (read_pointer == NUMBER_OF_OUTPUT_WORDS)                             
          begin                                                                      
            // NUMBER_OF_OUTPUT_WORDS 個のストリームデータが出力されたときtx_doneがアサ―トされます
            // tx_doneがアサ―トされます
            tx_done <= 1'b1;                                                         
          end                                                                        
    end                                                                              


    //tx_en :FIFO の読み出し有効

    assign tx_en = M_AXIS_TREADY && axis_tvalid;   
                                                         
        // 転送対象のデータをFIFOから取り出します。       
        always @( posedge M_AXIS_ACLK )                  
        begin                                            
          if(!M_AXIS_ARESETN)                            
            begin                                        
              stream_data_out <= 1;                      
            end                                          
          else if (tx_en)// && M_AXIS_TSTRB[byte_index]  
            begin                                        
              stream_data_out <= read_pointer + 32'b1;   
            end                                          
        end                                              

    // ここにユーザーロジックを追加

    // ユーザーロジックここまで

    endmodule

ステートマシン

次のコードが通信全体を管理するステートマシンの抜粋です。

 // Control state machine implementation                             
    always @(posedge M_AXIS_ACLK)                                             
    begin                                                                     
      if (!M_AXIS_ARESETN)                                                    
      // Synchronous reset (active low)                                       
        begin                                                                 
          mst_exec_state <= IDLE;                                             
          count    <= 0;                                                      
        end                                                                   
      else                                                                    
        case (mst_exec_state)                                                 
          IDLE:                                                               
            // The slave starts accepting tdata when                          
            // there tvalid is asserted to mark the                           
            // presence of valid streaming data                               
            //if ( count == 0 )                                                 
            //  begin                                                           
                mst_exec_state  <= INIT_COUNTER;                              
            //  end                                                             
            //else                                                              
            //  begin                                                           
            //    mst_exec_state  <= IDLE;                                      
            //  end                                                             
                                                                              
          INIT_COUNTER:                                                       
            // The slave starts accepting tdata when                          
            // there tvalid is asserted to mark the                           
            // presence of valid streaming data                               
            if ( count == C_M_START_COUNT - 1 )                               
              begin                                                           
                mst_exec_state  <= SEND_STREAM;                               
              end                                                             
            else                                                              
              begin                                                           
                count <= count + 1;                                           
                mst_exec_state  <= INIT_COUNTER;                              
              end                                                             
                                                                              
          SEND_STREAM:                                                        
            // The example design streaming master functionality starts       
            // when the master drives output tdata from the FIFO and the slave
            // has finished storing the S_AXIS_TDATA                          
            if (tx_done)                                                      
              begin                                                           
                mst_exec_state <= IDLE;                                       
              end                                                             
            else                                                              
              begin                                                           
                mst_exec_state <= SEND_STREAM;                                
              end                                                             
        endcase                                                               
    end  

それぞれのステートは次のような働きをします。

  • IDLE:初期状態のステートです。サンプルプログラムだと強制的にINIT_COUNTERに遷移するようになっています。
  • INIT_COUNTER:通信の初期化を行うステートです。countをインクリメントし続けて C_M_START_COUNT - 1に達したときにSEND_STREAMに遷移します。
  • SEND_STREAM:データ送信のステートです。tx_doneがアサ―トされる(通信が完了する)まで、SEND_STREAMのステートを維持します。

ステートマシン

プログラム中のステートマシンを上の図にまとめました。実質的にAXI-Streamに関わるのはSEND_STREAMだけなので、次からSEND_STREAMで何が起きていくかを記述していきます。

 AXI4-Streamのインタフェース

生成されたIPコアのインタフェースを見ると次のコードのようなポートが生成されていると思います。

// Global ports
input wire  M_AXIS_ACLK,
// 
input wire  M_AXIS_ARESETN,
// Master Stream Ports. TVALID indicates that the master is driving a valid transfer, A transfer takes place when both TVALID and TREADY are asserted. 
output wire  M_AXIS_TVALID,
// TDATA is the primary payload that is used to provide the data that is passing across the interface from the master.
output wire [C_M_AXIS_TDATA_WIDTH-1 : 0] M_AXIS_TDATA,
// TSTRB is the byte qualifier that indicates whether the content of the associated byte of TDATA is processed as a data byte or a position byte.
output wire [(C_M_AXIS_TDATA_WIDTH/8)-1 : 0] M_AXIS_TSTRB,
// TLAST indicates the boundary of a packet.
output wire  M_AXIS_TLAST,
// TREADY indicates that the slave can accept a transfer in the current cycle.
input wire  M_AXIS_TREADY

各々のポートの意味はこんな感じです。これら名称のポートがモジュールに存在するとVIVADOは自動でポートを認識してブロックデザインでAXI4-Streamにより結線することが可能になります。

信号名 Masterから見た方向 機能 ビット幅
M_AXIS_ACLK input クロック 1
M_AXIS_ARESETN input リセット 1
M_AXIS_TVALID output マスター側送信準備完了 1
M_AXIS_TDATA output データ word
M_AXIS_TSTRB output バイト選択 word/8
M_AXIS_TLAST output パケットの境界線 1
M_AXIS_TREADY input スレーブ受信可能 1

M_AXIS_TVALIDとM_AXIS_TREADYのハンドシェイク

AXI4-Streamの通信開始時にはまずM_AXIS_TVALIDかM_AXIS_TREADYのどちらかが先行して、もしくは同時に立ち上がります。

 tvalid

外部に接続されたtvalid信号は次のようにアサインされている。

assign M_AXIS_TVALID  = axis_tvalid_delay;
assign M_AXIS_TLAST  = axis_tlast_delay;`

これらの信号の生成過程を追う。

_delayとついていることから分かるようにこれらの信号は生成された信号を1クロック分遅れさせている。 axis_tvalid 信号はステートマシンの状態がSEND_STREAMの時に有効化される

//tvalid generation
//axis_tvalid is asserted when the control state machine's state is SEND_STREAM and
//number of output streaming data is less than the NUMBER_OF_OUTPUT_WORDS.
assign axis_tvalid = (mst_exec_state == SEND_STREAM);                                                                  

tlast信号は転送サイズよりカウンタが一つ小さい数値となった時だけ1になる。

// AXI tlast generation                                                                        
// axis_tlast is asserted number of output streaming data is NUMBER_OF_OUTPUT_WORDS-1          
// (0 to NUMBER_OF_OUTPUT_WORDS-1)                                                             
assign axis_tlast = (read_pointer == NUMBER_OF_OUTPUT_WORDS-1); 

この信号は次のように通信クロックの立ち上がり分遅らせるようになっている。

 // Delay the axis_tvalid and axis_tlast signal by one clock cycle                              
    // to match the latency of M_AXIS_TDATA                                                        
    always @(posedge M_AXIS_ACLK)                                                                  
    begin                                                                                          
      if (!M_AXIS_ARESETN)                                                                         
        begin                                                                                      
          axis_tvalid_delay <= 1'b0;                                                               
          axis_tlast_delay <= 1'b0;                                                                
        end                                                                                        
      else                                                                                         
        begin                                                                                      
          axis_tvalid_delay <= axis_tvalid;                                                        
          axis_tlast_delay <= axis_tlast;                                                          
        end                                                                                        
    end       

 転送

tx_enはreadyとvalidが両方有効な場合なので通信が有効であるということです。この二つがアサ―トされているときだけデータの転送が行われます。 tream_data_outは出力に接続されている。

サンプルでは出力に読み出しポインタが接続されており、リセット時には1を、それ以外では読み出しポインタ+1が出力される。。

//FIFO read enable generation 

assign tx_en = M_AXIS_TREADY && axis_tvalid;   
                                                         
// Streaming output data is read from FIFO       
always @( posedge M_AXIS_ACLK )                  
    begin                                            
      if(!M_AXIS_ARESETN)                            
        begin                                        
          stream_data_out <= 1;                      
        end                                          
      else if (tx_en)// && M_AXIS_TSTRB[byte_index]  
        begin                                        
          stream_data_out <= read_pointer + 32'b1;   
        end                                          
    end                                              
// Add user

read_pointer を制御しているは下記のようになっている、リセット時にはtx_doneとread_pointerは初期化されread_pointer <= NUMBER_OF_OUTPUT_WORDS-1かつtx_enが立っている状態の時だけread_pointerをインクリメントさせる。そして、read_pointer == NUMBER_OF_OUTPUT_WORDSに達するとtx_doneをアサートさせるようになっている。

    //read_pointer pointer

    always@(posedge M_AXIS_ACLK)                                               
    begin                                                                            
      if(!M_AXIS_ARESETN)                                                            
        begin                                                                        
          read_pointer <= 0;                                                         
          tx_done <= 1'b0;                                                           
        end                                                                          
      else                                                                           
        if (read_pointer <= NUMBER_OF_OUTPUT_WORDS-1)                                
          begin                                                                      
            if (tx_en)                                                               
              // FIFOが有効な時   
              // 1データ読み出すごとにread_pointerを1づつインクリメントします.                                   
              begin                                                                  
                read_pointer <= read_pointer + 1;                                    
                tx_done <= 1'b0;                                                     
              end                                                                    
          end                                                                        
        else if (read_pointer == NUMBER_OF_OUTPUT_WORDS)                             
          begin                                                                      
            // NUMBER_OF_OUTPUT_WORDS 個のストリームデータが出力されたときtx_doneがアサ―トされます
            // tx_doneがアサ―トされます
            tx_done <= 1'b1;                                                         
          end                                                                        
    end                                                                              

 どうやって改造してゆけば良いのか?

基本的にステートマシンに従って改造してゆけば良い、readyとvalidが両方1であるタイミングにおいて、stream_data_outレジスタもしくはM_AXIS_TDATAワイヤーにストリームデータを書き込まれるようにしてゆけばよい。

そしてデータ数であるがテンプレート上ではNUMBER_OF_OUTPUT_WORDSで指定されている。転送したいデータ数が固定であればこのlocalparam自体を直接書き換えてゆけばよい。もし、転送したいデータ数が可変であれば、NUMBER_OF_OUTPUT_WORDSと自体をレジスタに書き換え、localparam bit_num を十分な大きさを持つレジスタに書き換えてしまえばよい。

私がAXI4Streamでセンサからデータを読み出す場合以下のような構成の回路を記述することが多い。先ほども述べたように、AXI4Streamではマスター側で1回のデータ転送数はあらかじめ決定しておく必要があるが、これはスレーブ側であるPS側でDMA受け入れを許可する際にも同様のサイズを指定する必要がある。この転送サイズがどの程度のサイズが適切なのかは使用するアプリケーションのレイテンシや、バッファサイズ、必要な転送速度によって変化する。また、信号処理回路を含むモジュールであれば、フィルタなどのパラメータをソフトウェアで変更できる方がいちいち論理合成をする必要がなくなり、迅速な開発につながる。このような設定を行うAXI4Liteのインタフェースを別途設置し、転送数や信号処理回路の設定、バッファにたまっているデータの数などを監視できるようにすることで、AXI4Streamを使った柔軟な開発が実現できる。