SSブログ

AXIの勉強(その2.2) [AXI]

書き込みの依存関係です。
3チャンネルあるので長くなりますが、読み込みと同じように分けてみます。

Write transaction dependencies
Image 066.png

1• the master must not wait for the slave to assert AWREADY or WREADY before asserting AWVALID or WVALID
2• the slave can wait for AWVALID or WVALID, or both before asserting AWREADY
3• the slave can assert AWREADY before AWVALID or WVALID, or both, are asserted
4• the slave can wait for AWVALID or WVALID, or both, before asserting WREADY
5• the slave can assert WREADY before AWVALID or WVALID, or both, are asserted

実はAWとWの2チャンネルの依存関係が絡んでるここが結構面倒です。

1 マスターのAWVALIDやWVALIDは、AWREADYとWREADYに依存してはならない。
2,3 スレーブのAWREADYは、AWVALIDかWVALID、もしくは両方に依存しても、しなくても良い。
4,5 スレーブのWREADYは、AWVALIDかWVALID、もしくは両方に依存しても、しなくても良い。

「1」の制約は
・AWシーケンサーはAW自身のREADYだけでなく、WのREADYも参照してはならない
・WシーケンサーはW自身のREADYだけでなく、AWのREADYも参照してはならない
という2つの制約になっています。

なぜか。
書き込みなのでマスターのAWとWはソースですが、AWとWの順番には特に決まりはなく、アドレスが先でそれからデータ、先にデータを出してからアドレスを送る、もしくは同時、でかまいません。
・ マスターはAWとW、どちらを先に出すか、自由に選べる
・ スレーブはAWとW、どちらを先に受け取るか、自由に選べる
という状況です。相手がどちらを先だと想定しているのかが不明です。

この状態で、
・ もしマスターのWVALIDがAWREADYに依存していて (これが違反)
・ マスターはAW→Wの順番で送る
・ スレーブはW→AWの順番で来ると思ってる
という場合、マスターがAWREADY待ち、スレーブがWVALID待ち、となりデッドロックします。

普通アドレスが先でデータが後でしょう、って事で
AWVALID <= '1';
if (AWREADY = '1') then
 AWVALID <= '0'; next_state;
WVALID <= '1';
if (WREADY = '1') then
 WVALID <= '0'; next_state;

こんなマスター回路が思いつきます。当たり前な回路に見えますが、 WVALIDがAWREADYに依存しているので、実はこれ違反です。ただ、世の中、アドレスが先だと想定してるスレーブがほとんどなので、大体問題なくつながっちゃいます。
データが来るまでアドレスは受け付けない、というひねくれた(けど仕様に反してはいない)スレーブが、この回路につながるとデッドロックします。例えばこんな回路。
if (WVALID = '1') then
 WREADY <= '1';
 AWREADY <= '1';  データが送られ始めてからアドレスを受け取る

AWVALIDかWVALIDを動作開始のトリガーと考えると、そんなにひねくれて見えませんよね。2,3,4,5の項目から、これは正しい動作です。


この制約は多分、アドレスとデータを同時に転送できるようにするために作られたんだと思います。データを先に転送するような回路は、もちろん作っても良いですけど、不自然ですよね。

2,3,4,5の制約は、今までと同じく、VALID側を制約して、READY側は自由にすし、デッドロックを回避する、という項目です。


6• the slave must wait for both WVALID and WREADY to be asserted before asserting BVALID the slave must also wait for WLAST to be asserted before asserting BVALID, because the write response, BRESP, must be signaled only after the last data transfer of a write transaction

ちょっと長めですが、要するに
・全てのデータ転送を終えてから、BVALIDをアサートすること。
という制約です。リードのAR→Rの制約と似ています。

注意点としてはWLASTも見ている所です。今まではペイロードの中身がタイミングに影響することはなかったのですが、「全ての」が関係してきます。AXIはバースト転送が基本なので、Wチャンネルの転送が何回あるかはペイロードの中身を見ないとわかりません(転送が1回でも1バーストです)。WREADY&WVALID&WLASTが揃ったのを確認してから、BVALIDをアサート、レスポンスのステートに入ります。


7• the slave must not wait for the master to assert BREADY before asserting BVALID
8• the master can wait for BVALID before asserting BREADY
9• the master can assert BREADY before BVALID is asserted.

これは単純。Bチャンネルのデッドロック防止です。


AXI4になると書き込みの依存関係の図に、矢印が2本増えます。
Image 067.png

項目はほとんど同じですが、、「6」だけ異なります。
• the slave must wait for AWVALID, AWREADY, WVALID, and WREADY to be asserted before asserting BVALID
the slave must also wait for WLAST to be asserted before asserting BVALID because the write response, BRESP must be signaled only after the last data transfer of a write transaction

これは
・アドレス転送と、全てのデータ転送を終えてから、BVALIDをアサートすること。
という内容になります。アドレス転送が条件に入ってます。

実はAXI3だと、アドレス転送を完了させないままレスポンスを返せるようになってます。制約がありませんので。AWREADYをずっと返さずにデータ転送を終えて、レスポンスを返して、それからアドレス転送を完了する、という順番でも良かったわけです。
まぁ、普通に考えればおかしな動作ですが。制約されてない以上、あり得る動作です。

AXI4ではこれが制約され、マスターがアドレスとデータ全ての転送を完了するまで、スレーブはレスポンスを返せなくなってます。

スレーブ側の制約です。AXI3のスレーブをAXI4のマスターに接続するときに注意が必要で、ペイロードの内容を変換する以外に、この書き込みの依存関係を満足させる必要があります。

AXIの勉強(その2.1) [AXI]

続いてリードの依存関係。

Read transaction dependencies

1• the master must not wait for the slave to assert ARREADY before asserting ARVALID
2• the slave can wait for ARVALID to be asserted before it asserts ARREADY
3• the slave can assert ARREADY before ARVALID is asserted

4• the slave must wait for both ARVALID and ARREADY to be asserted before it asserts RVALID to indicate that valid data is available

5• the slave must not wait for the master to assert RREADY before asserting RVALID
6• the master can wait for RVALID to be asserted before it asserts RREADY
7• the master can assert RREADY before RVALID is asserted.
Image 065.png
なんかいっぱいありますが、分けてみるとわかりやすいかと思います。

まず上3つ。1,2,3。
・マスターのARVALIDは、ARREADYに依存してはならない
・スレーブはARVALIDを待ってからARREADYをアサートしても良い
・スレーブはARVALIDより前にARREADYをアサートしても良い
前の記事のソース・シンクの関係と同じです。ARチャンネルのデッドロックを防止する制約です。

下3つ。5,6,7。
・スレーブのRVALIDは、RREADYに依存してはならない
・マスターはRVALIDを待ってからRREADYをアサートしても良い
・マスターはRVALIDより前にRREADYをアサートしても良い
Rチャンネルのデッドロック防止です。

真ん中の一つ。4。
・スレーブはARREADYとARVALID両方がアサートされるのを待ってからRVALIDをアサートすること

ARREADYとARVALID両方がアサートされるということは、ARの転送が完了したということです。
ARVALIDはマスターがドライブしているので、スレーブ側のRのソースシーケンサーの制約になります。つまり、
・スレーブはARVALIDが来ないうちにRVALIDをアサートしてはならない
・スレーブはARREADYを先に返してからRVALIDをアサートすること
という制約です。

要は
・アドレス情報が与えられてもいないのに余計な情報を勝手に送るな
・アドレス情報を受け取ったことを、まずマスターに知らせてから、データを返せ
という制約です。1つ目はまあ良いですよね。

2つ目、例えばスレーブがARREADYを返さずに、ARの情報をゆっくり解析する回路にしたとします。これ自体は正しい動作です。
if (ARVALID) then
 1クロック目:AR.addr評価
 2クロック目:AR.length評価
 3クロック目:
 ・・・・
 nクロック目:ARREADY <= '1';

この途中でデータを返すのを違反としています。
if (ARVALID) then
 1クロック目:AR.addr評価
 2クロック目:AR.length評価
 3クロック目:RVALID <= '1';   違反
 4クロック目:if (RREADY) then ここでデッドロック
 ・・・・
 nクロック目:ARREADY <= '1';

なぜか。
例えばこれにつながるマスターがこんな風に順番に処理する回路の場合です。あり得る回路です。
アドレス処理ステート
 ARVALID <= '1';
 ↑クロック
 if (ARREADY = '1') then ここでデッドロック
  ARVALID <= '0';
データ処理ステート
 if (ARVALID = '1') then
  RREADY <= '1'

・マスターのARシーケンサーがARREADYを確認してからデータの受信処理を行う
・スレーブのRシーケンサーがARREADYをアサートする前にRREADYの評価を行う
この2つがつながるとデッドロックします。

実際の犯人はRREADYを評価していることで、RVALIDをアサートしたこと自体ではないですが。RVALIDを操作したということは、スレーブがデータ処理ステートに入ったことを意味します。けどマスターがアドレス処理のステートのままだと、RREADYが返ってこないかも知れません。

この、スレーブがデータ処理ステートに入ったのに、マスターがアドレス処理ステートのところで止まったまま、となるのを防ぐための制約で、マスター・スレーブともアドレス処理ステートを完了させてからデータ処理ステートに移行しましょう、そのために、スレーブはARREADYを先に返してからRVALIDをアサートすること、という決まり事です。

下のような違反の回路、AXIに慣れてないうちだと、意外と作りがちな回路です。
ARVALIDをアドレスストローブ信号、ARREADYをウェイト制御信号、と認識してるとこんな回路になります。パケット式じゃない、普通のタイミング方式のバスに慣れてる人がはまりやすいパターンです。


AXIはただのインターフェースの仕様で、どんなコーディングしてるのかわからない相手が接続されます。デッドロックのパターンを決めておかないとエラいことになります。なので、こんな制約が存在します。

AXIの勉強(その2) [AXI]

何年ぶりでしょう(笑)

簡単にタイミングの依存関係について書いてみます。
元になる資料はARMからダウンロードできる「IHI0022E_amba_axi_and_ace_protocol_spec.pdf」です。Issue Eが多分最新版。

AXIは、
axt_read_channel.pngaxi_write_channel.png
という5チャンネルでパケットのやりとりをしている感じなのですが、それぞれのチャネルがREADYとVALIDを持っていて個別にハンドシェイクを行う、という仕組みになっています。
けど、ハンドシェイクは個別ですけど、ちょっとは相手のことも考えないとダメよ、勝手に動くとデッドロックしますよという項目です。これが依存関係で、資料に書いてあります。A.3.3のRelationships between the channelsから始まる内容です。

A3.3.1 Dependencies between channel handshake signals
一番基本になる依存関係です。5系統それぞれにある、READYとVALIDの関係です。AXI Streamにも同じ制約がかかります。こんなことが書かれてます。
• the VALID signal of the AXI interface sending information must not be dependent on the READY signal of the AXI interface receiving that information
• an AXI interface that is receiving information can wait until it detects a VALID signal before it asserts its corresponding READY signal.
掻い摘まむと
・VALIDはREADYに依存してはならない(ソース側の制約)
・READYはVALIDに依存しても良い(シンク側の制約)
と言う事になります。
(マスター・スレーブと呼ぶとと5チャンネルを束ねたインターフェースを指します。だから英語の方ではちょっと持って回った言い方になってます。間際らしいので、ここでは個々のチャンネルのインターフェースをソース・シンクと呼んでます。ソースからシンクへデータが流れます。マスターは3つのソースと2つのシンク、スレーブは3つのシンクと2つのソースを持ちます。)

要するに、ソース側のシーケンサーを作るとき、
if (READY = '1') then
  VALID <= '1';
みたいな回路は作っちゃダメですよ、という制約になります。

シンク側は
if (VALID = '1') then
  READY <= '1';
という回路を作っても大丈夫です。

ソース側の制約な訳ですが、ダメな回路例と大丈夫な回路例をつないでみれば一目瞭然かと。ソースがREADY待ちで、シンクがVALID待ちとなり、デッドロックします。どちらかに制約をかける必要があり、AXIではVALID側を制約しています。

ちなみにシンク側ですが、この回路にしなければなりません、という’訳ではありません。VALIDに依存しない、
if (Im_free) then
  READY <= '1';
という回路にしても良いです。
VALIDに関係なく、自分の都合でREADYをアサートしてもかまいません。READY側の方が自由になっています。


こんなのがAXIの依存関係です。他のチャンネルとの依存関係も読んでいきましょう。

部品:AXIストリームレジスタ シミュレーション [AXI]

axis_sliceをシミュレーションしてみます。

テストパターン発生に先のaxis_source,axis_sinkを使います。で、早速変更してしまってます。Xilinxのisim(無償版)を試してみたのですが、「input wire integer ratio」の構文が通らなかったので、ratioを内部変数にしてます。そういえば元々この構文変だなぁと思いながらもModelSim(Altera版)では通っちゃってたので、まぁいっかで放ってた物でした。
「実体名.ratio<=値」の形で変更します。

axis_source、ソース側
module  axis_source
(
    input   wire  enable,   // enable
    input   wire  c_clk,    // Clock
    input   wire  c_rst,    // Reset
    input   wire  m_ready,  // Ready
    output  wire  m_valid   // Valid
);
    integer ratio;  // ratio

    reg   [3:0]  rand;
    reg   inhib;
    reg   valid;

    assign  m_valid = valid & ~inhib;

    always @(posedge c_clk) begin
        if (c_rst) begin
            valid   <= 1'b0;
            rand    <= 0;
            inhib   <= 1'b1;
        end
        else begin
            valid    <= enable;
            if (inhib | m_ready) begin
                rand    = $random;
                case (ratio)
                    default  : inhib    <= 0;
                    1        : inhib    <= &rand[3:0];
                    2        : inhib    <= &rand[2:0];
                    3        : inhib    <= &rand[1:0];
                    4        : inhib    <=  rand[0];
                    5        : inhib    <= |rand[1:0];
                    6        : inhib    <= |rand[2:0];
                    7        : inhib    <= |rand[3:0];
                endcase
            end
        end
    end
endmodule

axis_sink、シンク側
module    axis_sink
(
    input   wire  enable,   // enable
    input   wire  c_clk,    // Clock
    input   wire  c_rst,    // Reset
    output  wire  s_ready,  // Ready
    input   wire  s_valid   // Valid
);
    integer ratio;  // ratio

    reg   [3:0]  rand;
    reg   inhib;
    reg   ready;

    assign  s_ready = ready & ~inhib;

    always @(posedge c_clk) begin
        if (c_rst) begin
            ready    <= 1'b0;
            rand     <= 0;
            inhib    <= 1'b0;
        end
        else begin
            ready    <= enable;
            rand     = $random;
            case (ratio)
                default  : inhib    <= 0;
                1        : inhib    <= &rand[3:0];
                2        : inhib    <= &rand[2:0];
                3        : inhib    <= &rand[1:0];
                4        : inhib    <=  rand[0];
                5        : inhib    <= |rand[1:0];
                6        : inhib    <= |rand[2:0];
                7        : inhib    <= |rand[3:0];
            endcase
        end
    end
endmodule


axis_sliceテスト
`timescale 1ns / 1ps
`default_nettype none

module    tb;
    parameter  stdw = 16;

    reg     c_clk;
    reg     c_rst;

    reg     enable;

    initial     c_clk <= 1'b0;
    always  #5  c_clk <= ~c_clk;

    initial begin
        c_rst       <= 1'b1;
        enable      <= 1'b0;
        #100;

        @(posedge c_clk);
        c_rst       <= 1'b0;
        #200;

        @(posedge c_clk);
        enable      <= 1'b0;
        @(posedge c_clk);
        src.ratio   <= 0;
        snk.ratio   <= 0;
        @(posedge c_clk);
        enable      <= 1'b1;
        #1000;

        @(posedge c_clk);
        enable      <= 1'b0;
        @(posedge c_clk);
        src.ratio   <= 5;
        snk.ratio   <= 3;
        @(posedge c_clk);
        enable      <= 1'b1;
        #2000;

        @(posedge c_clk);
        enable      <= 1'b0;
        @(posedge c_clk);
        src.ratio   <= 3;
        snk.ratio   <= 5;
        @(posedge c_clk);
        enable      <= 1'b1;
        #2000;

        $display("finish");
        $stop;
    end

    wire              ts1_ready;
    wire              ts1_valid;
    wire  [stdw-1:0]  ts1_data ;
    wire              ts2_ready;
    wire              ts2_valid;
    wire  [stdw-1:0]  ts2_data ;
    wire              ts3_ready;
    wire              ts3_valid;
    wire  [stdw-1:0]  ts3_data ;

    ///////////////////////////////
    axis_source
    src
    (
        .enable    (enable     ),
        .c_clk     (c_clk      ),
        .c_rst     (c_rst      ),
        .m_ready   (ts1_ready  ),
        .m_valid   (ts1_valid  ) 
    );

    reg     [stdw-1:0]    s_data;
    always @(posedge c_clk) begin
        if      (c_rst)                  s_data <= 0;
        else if (ts1_ready & ts1_valid)  s_data <= s_data + 1;
    end

    assign  ts1_data = s_data;


    ///////////////////////////////
    axis_sink
    snk
    (
        .enable    (enable     ),
        .c_clk     (c_clk      ),
        .c_rst     (c_rst      ),
        .s_ready   (ts3_ready  ),
        .s_valid   (ts3_valid  ) 
    );

    reg     [stdw-1:0]    d_data;
    always @(posedge c_clk) begin
        if      (c_rst)                  d_data <= 0;
        else if (ts3_ready & ts3_valid)  d_data <= d_data + 1;
    end

    always @(posedge c_clk) begin
        if (ts3_ready & ts3_valid) begin
            if (ts3_data != d_data) begin
                $display("error %x,%x", ts3_data, d_data);
                $stop;
            end
        end
    end


    ///////////////////////////////
    axis_slice    #(stdw)
    ts1_to_ts2 (
        c_clk, c_rst,                      // clk, rst
        ts1_ready, ts1_valid, ts1_data,    // sink
        ts2_ready, ts2_valid, ts2_data     // source
    );

    axis_slice    #(stdw)
    ts2_to_ts3 (
        c_clk, c_rst,                      // clk, rst
        ts2_ready, ts2_valid, ts2_data,    // sink
        ts3_ready, ts3_valid, ts3_data     // source
    );

endmodule

`default_nettype wire

[src] --ts1--> [ts1_to_ts2] --ts2--> [ts2_to_ts3] --ts3--> [snk]
と数珠つなぎになってます。ソース側で発生させるデータ列はカウント値、シンク側でも同じように発生させて、同じ順番になってなければエラーです。
※'X'や'Z'は見てないので、テストとしては不十分ですが…
最初はソース、シンクとも100%。途中でソースの帯域<シンクの帯域、ソースの帯域>シンクの帯域、と変化させています。

isim起動のバッチファイルとプロジェクトファイルの例。コマンドラインから「isim.bat」で実行です。
tb.prj、プロジェクト
verilog  work  ../axis_slice.v
verilog  work  ../axis_txgen.v
verilog  work  tb.v
isim.bat、実行バッチ
fuse work.tb -prj tb.prj -o tb.exe
tb.exe -gui -wdb tb.wdb

結果。まずは全体。最後まで行ってるので途中データが化けるようなことは起きて無いようです。
axis_slice_sim1.png
切り替わりのあたり。波形を見てみると、validは上流→下流に、readyは下流→上流に伝搬してるのがわかります。思った通りの動作、のはず。
axis_slice_sim0.png

ちなみにこのモジュール、「深さ1の小さなFIFO+出力レジスタ」です。後でFIFOも作りますが、「深さ1」と「深さ2以上」に大きな壁があるので分けて記述しました。
色々使えます。何度か紹介してる「周波数を出すために経路を分ける」という使い方や、非同期のループを回避するのにも使います。例えばreadyとvalidが組み合わせ回路のモジュール同士をつなぐとこうなる可能性があります。
async_loop.png
この場合、間にaxis_sliceを入れて解決します。毎回注意するのも面倒なので、非同期のモジュールはaxis_sliceでサンドイッチするようにしてます。逆に言うと気にすることなく組み合わせ回路で作れます。マルチプレクサなどは組み合わせ回路で記述してしまった方が楽だったりします。

「AXIにはデータだけじゃなくIDやLastとかあるよ?どうするの?」という話。
AXIは"ready"と"valid"と"それ以外の荷物"という関係です。なので、こう書けます。
axis_slice    #(IDの幅+データの幅+…)
sync (
    c_clk, c_rst,                                // clk, rst
    s_ready, s_valid, {s_id, s_last, s_data},    // sink
    m_ready, m_valid, {m_id, m_last, m_data}     // source
);
ここはverilog記述が楽ですね~VHDLやSystemVerilogだったら構造体使うかな?

注意点としてはレイテンシは増えることです。多くの場合周波数は上げられるので、スループットは上がります。どちら重視かはシステム次第です。

部品:AXIシミュレーション用モジュール [AXI]

作ったaxis_sliceを早速テストしたいところですが、いろんなパターンのreadyやvalidを作るのはめんどくさいです困難を極めます。

手を抜きます。

souce、マスター側
module    axis_source
(
    input  wire integer ratio,     // ratio
    input  wire         enable,    // enable
    input  wire         c_clk,     // Clock
    input  wire         c_rst,     // Reset
    input  wire         m_ready,   // Ready
    output wire         m_valid    // Valid
);
    reg     [3:0]  rand;
    reg     inhib;
    reg     valid;

    assign  m_valid = valid & ~inhib;

    always @(posedge c_clk) begin
        if (c_rst) begin
            valid    <= 1'b0;
            rand     <= 0;
            inhib    <= 1'b1;
        end
        else begin
            valid    <= enable;
            if (inhib | m_ready) begin
                rand     = $random;
                case (ratio)
                    default    : inhib    <= 0;
                    1        : inhib    <= &rand[3:0];
                    2        : inhib    <= &rand[2:0];
                    3        : inhib    <= &rand[1:0];
                    4        : inhib    <=  rand[0];
                    5        : inhib    <= |rand[1:0];
                    6        : inhib    <= |rand[2:0];
                    7        : inhib    <= |rand[3:0];
                endcase
            end
        end
    end
endmodule

sink、スレーブ側
module    axis_sink
(
    input  wire integer ratio,     // ratio
    input  wire         enable,    // enable
    input  wire         c_clk,     // Clock
    input  wire         c_rst,     // Reset
    output wire         s_ready,   // Ready
    input  wire         s_valid    // Valid
);
    reg     [3:0]  rand;
    reg     inhib;
    reg     ready;

    assign  s_ready = ready & ~inhib;

    always @(posedge c_clk) begin

        if (c_rst) begin
            ready    <= 1'b0;
            rand     <= 0;
            inhib    <= 1'b0;
        end
        else begin
            ready    <= enable;
            rand     = $random;
            case (ratio)
                default    : inhib    <= 0;
                1        : inhib    <= &rand[3:0];
                2        : inhib    <= &rand[2:0];
                3        : inhib    <= &rand[1:0];
                4        : inhib    <=  rand[0];
                5        : inhib    <= |rand[1:0];
                6        : inhib    <= |rand[2:0];
                7        : inhib    <= |rand[3:0];
            endcase
        end
    end

endmodule//

souceはvalidを、sinkはreadyを、ランダムでトグルさせます。テストデータの生成とチェックは別のテストベンチ側でやります。タイミングを生成するだけのモジュール。
有効の比率をratioで変えてて、0は100%、常に有効、1~7につれて有効の比率が下がります。4が50%ぐらい、たぶん。
こんな感じで使います。
`timescale 1ns / 1ps
`default_nettype none

module    tb;
    parameter    dw    = 16;

    reg     c_clk;
    reg     c_rst;

    reg     enable;

    integer s_ratio;
    integer d_ratio;

    initial      c_clk <= 1'b0;
    always   #5  c_clk <= ~c_clk;

    initial begin
        c_rst    <= 1'b1;
        enable   <= 1'b0;
        #100;

        @(posedge c_clk);
        c_rst    <= 1'b0;
        #200;

        /* 両方とも100% */
        @(posedge c_clk);
        enable   <= 1'b0;
        @(posedge c_clk);
        s_ratio  <= 0;
        d_ratio  <= 0;
        @(posedge c_clk);
        enable   <= 1'b1;
        #500;

        /* 送り側が多め */
        @(posedge c_clk);
        enable   <= 1'b0;
        @(posedge c_clk);
        s_ratio  <= 3;
        d_ratio  <= 5;
        @(posedge c_clk);
        enable   <= 1'b1;
        #500;

        /* 受け側が多め */
        @(posedge c_clk);
        enable   <= 1'b0;
        @(posedge c_clk);
        s_ratio  <= 5;
        d_ratio  <= 3;
        @(posedge c_clk);
        enable   <= 1'b1;
        #500;

        $display("finish");
        $stop;
    end

    wire            ts_ready;
    wire            ts_valid;
    wire  [dw-1:0]  ts_data ;

    ///////////////////////////////
    reg     [dw-1:0]    s_data;
    always @(posedge c_clk) begin
        if      (c_rst)                s_data <= 0;
        else if (ts_ready & ts_valid)  s_data <= s_data + 1;
    end

    assign  ts_data = s_data;

    ///////////////////////////////
    axis_source
    source
    (
        .ratio    (s_ratio   ),
        .enable   (enable    ),
        .c_clk    (c_clk     ),
        .c_rst    (c_rst     ),
        .m_ready  (ts_ready  ),
        .m_valid  (ts_valid  ) 
    );


    ///////////////////////////////
    axis_sink
    sink
    (
        .ratio    (d_ratio   ),
        .enable   (enable    ),
        .c_clk    (c_clk     ),
        .c_rst    (c_rst     ),
        .s_ready  (ts_ready  ),
        .s_valid  (ts_valid  ) 
    );

endmodule

`default_nettype wire

この例では直結。本来は間にテスト対象を挟んでデータのチェックを行わせます。

追記。
input wire integer ratio
この構文がisimで通らないので、変更しました。変更したソースは次の「部品:AXIストリームレジスタ、シミュレーション」に。

部品:AXIストリームレジスタ続き [AXI]

前回、
「値が有効を示す"valid"ももう一本増えます。」
って終わりましたが、いきなり訂正。増えません。

いや、正確には「必要だと思って作り始めたら実はいらなかったので消した」です。
ソースコード見直したらコメント残してましたよ。えらいぞ>過去の自分。でももうちょっとわかりやすく書こうな。
それにしてもまぁ自分の記憶の当てにならないこと。こういう文章書くと見直しになります。

どうせなので増やして設計始めた所から書いてみます。構成としてはこんな感じ。未接続のレジスタの値を制御することになります。
axis_slice.png回路3

状態数を数えます。t_valid, m_ready, m_valid, s_ready, s_validの5本なので32個です。制御対象はt_valid, m_valid, s_ready、データレジスタはt_dataとm_dataです。これらをとりあえず羅列します。あまり頭使ってません。
{t_valid, m_ready, m_valid, s_ready, s_valid} -> {tv,mr,mv,sr,sv}
{t_data, m_data, s_data} -> {td,md,sd}
tvmrmvsrsvtvmvsrtdmd
0 x 0 0 0 0 0 1 never1
0 x 0 0 1 0 0 1 never1
0 x 0 1 0 0 0 1 受信待ち
0 x 0 1 1 0 1 1 sdm_data受信
0 0 1 0 0 0 1 1 never1
0 0 1 0 1 0 1 1 never1
0 0 1 1 0 0 1 1 受信待ち
0 0 1 1 1 1 1 0sd t_data受信。これ以上は受け取れない
0 1 1 0 0 0 0 1 never1
0 1 1 0 1 0 0 1 never1
0 1 1 1 0 0 0 1 m_data送信。空になる
0 1 1 1 1 0 1 1 sdm_data送信と同時に受信
1 x 0 0 0 0 1 1 tdnever12
1 x 0 0 1 0 1 1 tdnever12
1 x 0 1 0 0 1 1 tdnever2
1 x 0 1 1 1 1 0sdtdnever2
1 0 1 0 0 1 1 0 空き待ち
1 0 1 0 1 1 1 0 空き待ち
1 0 1 1 0 illegal
1 0 1 1 1 illegal
1 1 1 0 0 0 1 1 tdm_data送信。t_data送信準備
1 1 1 0 1 0 1 1 tdm_data送信。t_data送信準備
1 1 1 1 0 illegal
1 1 1 1 1 illegal

しばらく眺めます。…この表をそのままcase文にしてもそれはそれで良いかな…無駄なロジックがあるかもしれませんが、今のシリコンからしたら微々たるものとして、もしくはコンパイラががんばるとして「完成」としても良さそうです。物を作ることが目的で最適化が必須とは限りません。さらに、全ての状態を洗い出してるので不定状態がないという安心感も。illegalの所はシミュレーションで$stopさせるとか。

ちゃんと眺めます(笑)
・出力側を見ると、常にt_valid = not s_readyです。これは「t_dataレジスタが埋まってたらもうデータは受け取れない」と読めば当たり前です。ですので、増やしたt_valid信号はいらないのでした。けど思考補助にはなってます。
・t_valid=0もしくはm_valid=0の時、つまりどちらかのレジスタが空いているときに、s_readyを0に(受信拒否)にする理由はありません。この状態も必要ありません(never1の部分)。
・t_valid=1かつm_valid=0の条件、m_dataが空いてるのにt_dataだけにデータが存在するという状態はあり得ないので、この項目は全部消えます(never2の部分)。
・t_valid=1かつm_valid=1、両方のレジスタが埋まってる状態でs_readyを1にするのは違反です(illegalの部分)。s_validが1になったらデータロストを起こします。同時にm_ready=1ならシフトレジスタ動作になってロストしませんが、そのためにはs_readyを組み合わせ回路にする必要があります。

表の不要部分を削除します。
mrmvsrsvmvsrtdmd
x 0 0 x 0 1 リセット状態
x 0 1 0 0 1 受信待ち
x 0 1 1 1 1 sdm_data受信
0 1 1 0 1 1 受信待ち
0 1 1 1 1 0sd t_data受信。これ以上は受け取れない
1 1 1 0 0 1 m_data送信。空になる
1 1 1 1 1 1 sdm_data送信と同時に受信
0 1 0 0 1 0 空き待ち
0 1 0 1 1 0 空き待ち
1 1 0 0 1 1 tdm_data送信。t_data送信準備
1 1 0 1 1 1 tdm_data送信。t_data送信準備

不要とした部分の一つが残ってますが、これをリセット状態とします。
「受信待ち」をリセット状態としても良さそうですが、m_validはともかく、s_readyが1になるのは問題です。全てのモジュールが同時にリセット解除されるという保証が無く、こちらのリセット解除の前に前段のリセットが解除されvalidが1にされた場合、データが失われます。s_readyはリセット中は0で解除後に1にします。
ほか、網掛けの「受信待ち」「空き待ち」は信号が動かないので記述する必要はありません。結果記述する条件は7個となります。
これをHDL化。
module    axis_slice
#(
    parameter  dw = 0        // Data Width
)
(
    // common
    input  wire          c_clk,      // clock
    input  wire          c_rst,      // reset
    // stream-in, sink,  slave
    output wire          s_ready,    // ready
    input  wire          s_valid,    // valid
    input  wire [dw-1:0] s_data ,    // data
    // stream-out, source, master
    input  wire          m_ready,    // ready
    output wire          m_valid,    // valid
    output reg  [dw-1:0] m_data      // data
);
    reg    [dw-1:0] t_data;
    reg    [2:1]    mvsr;
    assign {m_valid,s_ready} = mvsr;

    always @(posedge c_clk) begin
        casex ({c_rst,m_ready,m_valid,s_ready,s_valid})
            5'b1xxxx : begin  mvsr <= 2'b00;                     end
            5'b0x00x : begin  mvsr <= 2'b01;                     end
            5'b0x011 : begin  mvsr <= 2'b11;  m_data <= s_data;  end
            5'b00111 : begin  mvsr <= 2'b10;  t_data <= s_data;  end
            5'b01110 : begin  mvsr <= 2'b01;                     end
            5'b01111 : begin  mvsr <= 2'b11;  m_data <= s_data;  end
            5'b0110x : begin  mvsr <= 2'b11;  m_data <= t_data;  end
        endcase
    end
endmodule
表をそのままcaseにしただけ。
ただし、この記述にするときはちゃんとコメントや文章残しましょう。でないと後から見ると「ナニコレ?」状態になります。if~then~elseで書き直せばソースでもある程度意味がわかる記述になりますが、やっぱりコメントは残しましょう。

詠み人知らずの格言:昨日の自分は別の人。


追記
・c_rstもcase文の条件に入れてしまってます。普通の書き方(if (c_rst) ~ else case ~)にしてもリソース、速度ともに変わらなかったので(ISE S6でテスト)そのままにしてます。条件によっては変わるかも。
・「m_data <= s_data;」の記述を動作に関係ない所(例えば5'b01110のライン)に書くと、データラッチ条件のデコードビットが減って回路が小さく速くなる可能性があります。S6では変わりませんでした。
合成結果見たところ、がっつり最適化してくれてるようです。ありがたや~

追記その2
この回路、意外と入力のファンアウトが多いです。それが原因の問題には今のところ当たったことは無いですが、もし当たったら厄介そうです。
入力もレジスタ化するのは結構難儀なので、当たったときに考えようかと…(笑)

部品:AXIストリームレジスタ [AXI]

AXIストリームの単純なリピーターを作ってみます。
プロトコルでは無く、タイミングを考えるレイヤーです。

受け取ったデータをラッチしてそのまま出力するだけ。(その1)で経路を分割するときにレジスタを入れる、という話がありましたが、経路が長くなったときに周波数を出すために入れるレジスタにもなります。
単純なようですが、意外と奥が深いです。そしてこの部品がAXI設計時の要となります。これを作っておかないと色々行き詰まる事が後からわかりました。
経路を分ける、切るという意味合いで、DecoupleとかSliceとか呼んでます。
宣言はこんな感じ。outputのreg/wireは場合によって変わります。ホントはsouce、sinkという名前にしたいところですが、同じ「s」なので、slave、masterとなってます。
module  axis_slice
#(
  parameter  dw = 0    // Data Width
)
(
  // common
  input  wire          c_clk,    // clock
  input  wire          c_rst,    // reset
  // stream-in, sink, write, slave
  output reg           s_ready,  // ready
  input  wire          s_valid,  // valid
  input  wire [dw-1:0] s_data ,  // data
  // stream-out, source, read, master
  input  wire          m_ready,  // ready
  output reg           m_valid,  // valid
  output reg  [dw-1:0] m_data    // data
);
  /* 本体 */
endmodule

AXIのハンドシェイクのタイミングです。
axis_handshake.pngタイミング
"ready"と"valid"の組み合わせなので、4パターンあります。
validready状態
00アイドル
01アイドル(転送催促?)
10ウェイト
11転送成立
[valid,ready]="00"と"01"は一緒にして良いかも。

この波形からまず思いつくのはこんな回路。
slice01.png回路1
assign	s_ready	= m_ready;

always @(posedge c_clk) begin
	if (c_rst) begin
		m_valid	<= 1'b0;
	end
	else if (s_ready) begin
		m_valid	<= s_valid;
		m_data	<= s_data;
	end
end

"ready"がクロックイネーブルとして使われた、普通のパイプラインレジスタですね。これはこれで使います。DFFを複数段にしてn段のディレイとしても使います。
追記:この回路はAXIとしては使ってはいけません。デッドロックします。

しかし、経路分割としては不十分。"ready"が分割されてません。"ready"の経路が長いままでクリティカルパスになる可能性があります。また、"ready"がレジスタ出力かどうかは後段の"ready"出力によります。この回路では不明です。
あと、せっかくレジスタなのに、受け側が"ready=0"だと、レジスタが空いていても受け取れません。レジスタが空いてたら受け取ってく欲しいですよね。「レジスタが空いてる」=「"valid"が"0"」ですので、こうしてみます。
slice02.png回路2
assign	s_ready	= m_ready | ~m_valid;
/*他同じ*/

機能的には良さそうですが、この回路を入出力端で使ってはいけません。"ready"が組み合わせ出力です。色々弊害が起きます。他の回路でもこのパターンの回路を使うと、"ready"がクリティカルパスになります。"ready"が数珠つなぎになりますからね。

一工夫必要です。

感覚的には、使っちゃダメと言った回路2の方がAXIっぽいと思います。回路1では"valid"が"data"と同じ扱いですが、回路2では「レジスタ内のデータが有効だよ~」というフラグのような扱いになってます。考え方はこちらから発展してます。

"ready"をレジスタ出力にすると後段の"ready"入力から1クロック遅れます。後段が「待った」と言ってもそれが前段に伝わるのは1クロック後です。その間に前段の転送は成立してしまうので、どこかに保存しておかないといけません。ということで、「どこか」=もう一段データを保存するレジスタが必要になります。値が有効を示す"valid"ももう一本増えます。

続く

AXIの勉強(その1) [AXI]

そのいくつまで行くかは不明ですが。

参考にしているのはARM本家の仕様書Xilinxの資料など。まだベータですがAlteraのQsysも追いかけてます。
タイミングチャートの作成にはTimingChartViewerをお借りしてます。同期回路限定ですが便利に使わせていただいてます。ありがとうございます。

用語とか勝手に呼び替えてしまってるところがあります。適当に読み替えて下さい。

AXIのタイミングは、良く資料などでも出てきますが、
axi_readburst.pngリード
axi_writeburst.pngライト
という感じです。
なんか複雑そうですが、実はタイミングとして見ないとならないのは"ready"と"valid"だけです。もう一つのAXI規格の「AXI stream」もそうですが、"ready"と"valid"が両方とも"1"(有効)の時にデータ(荷物、ペイロード)が転送される、という仕組みになってます。この仕組みはAXI3もAXI4も同じです。
図中の"last"信号や、"id"、"size"、"burst"、"lock"などいろいろな信号がありますが、これら全て荷物、ペイロードとして扱います。"ready"と"valid"と"荷物"という関係です。
これに関してはARMの資料にも記述されてます。以下抜粋ですが、これもよく見かける図ですね。
axt_read_channel.pngリード
axi_write_channel.pngライト
この図だと、リードが2チャンネルで、ライトが3チャンネルとアンバランスな感じがしますが、リードのデータチャンネルにライトのレスポンスと同等の情報が載ってるので(OKAYなどを返してる)、行き交う情報の量はだいたい対称です。
届いた荷物の中身が重要であって、荷物がいつ届いたかに関してはあまり考えなくて良くなります。マスター側は荷物の準備が出来たら"valid"を有効にする、スレーブ側は荷物の受け入れ準備が出来たら"ready"を有効にする、両方が有効なら転送成立、という考え方です。
ちょっと図を変えるとその分野の人には見慣れた図になったりします。
axi_read_txfr.pngリード
axi_write_txfr.pngライト
ra、waはリードライトのアドレス、rd、wdはデータ、+lastはフラグを付加といった感じです。RS-232Cやネットワークとかの通信でもよく見る、データ通信プロトコルの図になりました。
AXIって物理的な通信経路が複数本あるだけで考え方はネットワークに近いと見た方が良いと私は思ってます。ユーザーが作るマスター・スレーブの回路は、このプロトコルを処理するシーケンサーという事になります。

これによる最大の利点、設計時にタイミングのことを考えなくて良い(タイミングとプロトコルを分けて考えられる)という点だと思います。例えばマスターとスレーブが離れた位置にあって、信号到達までに時間がかかり速度が出ない、という場合、通信経路にレジスタを入れて経路を分割してもプロトコル上は影響ありません。
axi_read_txfr2.pngリード、経路分割
途中にレジスタが入って経路が分割されても通信は成り立ちます。見た目には赤点線のように、到着時間が変わった=矢印の傾きが変わった、のと等価です。
レジスタが入ってるので、個々のチャンネルのタイミングは大きく変わってるはずです。が、AXIは届いた荷物の中身が重要で、届きさえすれば良いという仕組みですから、タイミングの違いは無視してかまわない事になります。これが設計をすごく楽にします。

個々のチャンネルはAXIのストリームと同じですから、次からはその辺の部品を作っていきたいと思います。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。