アドベントカレンダーにはリングオシレータについて書くとの宣言していたのですが、最近後輩指導のために書いていたVivadoのテンプレートからAXI4Streamの記事がいい感じだったのでこちらの記事を掲載させていただくことにしました。
AXIStream
VivadoのIP PackagerでAXI-Streamのインタフェースを持ったIPコアを作成します。Tools->Create and Package IPを選択します。
このツールではAXIインタフェースを持ったIPのテンプレートを自動的に生成してくれます。create a new AXI4 peripheralを選択してください。
IPに任意のインタフェースを生成することができるので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を使った柔軟な開発が実現できる。