AXI でプロセッサとつながる IP コアを作る (4)

この記事は、AXI を使ってプロセッサと連携する回路の設計例について説明するコースの第4回です。今回からは、いよいよフル機能の AXI を扱っていきます……が、必ずしもその機能の全てを使うわけではありません。そのため、今回はまず AXI のインタフェースをより少ない種類の信号で制御する、インタフェース変換回路を設計してみます。

なお、第4回~第5回で使用する回路の SystemVerilog 記述は、GitHub からダウンロードできるようにしています。今回取り上げる回路記述に対応するファイルは、主にこのうちの stencil/hdl/AXI_fifo.sv です。

インタフェース変換回路の概要

典型的な専用計算回路の構成

今回からは、プロセッサ (PS) 側のソフトウェアでの計算の一部を FPGA (PL) 側の専用計算回路、つまり「コプロセッサ」に任せることによって、システムを高速化する例を考えていきます。第1回で、AXI-Lite は制御や少量のデータのやりとり向き、フル機能の AXI は大量のデータ転送向き、ということを説明しました。そのため、プロセッサからの制御や引数は AXI-Lite で受け取って、必要なデータは AXI で自発的に読み書きするというのが、典型的なコプロセッサの構成になります。

典型的な専用計算 (コプロセッサ) IP コアの構成。

上図は、このことを図で示したものです。このうち、AXI_ctrl は第2回で説明した AXI-Lite のインタフェース回路です。この回路は、プロセッサから送られた動作開始の指令 (GO) や引数をコプロセッサに渡すとともに、動作を継続中かどうか (RUN) や返り値をコプロセッサから受け取り、プロセッサから読み取れるようにします。

また、コプロセッサがデータを自発的に読み書きする際は、コプロセッサで複雑な AXI の信号を直接扱うのはなかなか大変です。そのため、あらかじめインタフェース変換回路などを作っておいて、コプロセッサからはより簡易なインタフェース (図中の緑色の矢印) で扱えるようにします。こうすることで、コプロセッサ部分の開発が容易になります。今回扱うのは、このインタフェース変換回路 (AXI_FIFO) です。

インタフェース変換回路で行うこと

下図に、今回用意するインタフェース変換回路のおおまかな設計方針を示します。AXI には AR、R、AW、W、B の5つのチャネルがありますが、これを読み出し・書き込みのリクエストとデータに整理します。また、読み出し・書き込みされるデータは、いったん内部の FIFO を経由してから送受信するものとします。FIFO は、コプロセッサとメモリとの間の短期的な通信頻度の変化に対する緩衝材の役割を果たします。

インタフェース変換回路のおおまかな設計方針。

リクエストとデータで扱う信号を下表にまとめます。なお、方向の → はコプロセッサからメモリへの方向、← はメモリからコプロセッサへの方向を示します。

種別信号名方向概要
リクエストADDR [31:0]転送の先頭アドレス
COUNT [15:0]先頭から何ワード転送するか
REQリクエストが有効なら ‘1’
BUSYリクエストを処理中なら ‘1’
データDATA [31:0]読← 書→送受信するデータ
VALID読← 書→データが有効なら ‘1’
READY読→ 書←データを受け入れ可能なら ‘1’

AXI (AXI-Lite を含む) には、転送 ID やアクセス権限、バイトごとの有効/無効の切り替え、読み書きの成否の通知などの機能もあります。ただ、実際のところフル機能の AXI を使う最大のモチベーションは、転送長を指定できることです。しかし、第1回でも説明したのですが、この転送長には、転送したいワード数 – 1 を指定しないといけない、最大でも256ワードしか指定できない、ページ境界 (4 KiB) をまたごうとすると転送が失敗する、などの面倒な点があります。

今回用意するインタフェース変換回路では、この面倒さを吸収し、かつ大量の転送をリクエスト1回で終わらせるように、転送長を16ビットで指定し、ページ境界の制約も取り払うこととします。

残りの REQ、BUSY、VALID、READY については、それぞれ AXI の AxVALID、AxREADY (の反転)、xVALID、xREADY に対応します。x は読み出しならば R、書き込みならば W です。他にも AXI には様々な信号がありますが、それらには定数をあてはめるか、単に無視することとします。

インタフェース変換回路の設計と実装

読み出しチャネルの信号変換

それでは、実際の信号変換を行う部分の設計と、対応する SystemVerilog 記述を見ていきましょう。まずは、下図に読み出しチャネルの変換にかかるブロック図を示します。

インタフェース変換回路の読み出しチャネル。

読み出し・書き込みにかかわらず、変換部の主要部分は3つの要素に分かれています。

  • 転送長変換: コプロセッサからのリクエストを、AXI の AR チャネルの複数のリクエストへと適切に切り分ける。
  • 転送数管理: データ受信待ちのリクエストがいくつ残っているかを管理する。
  • FIFO とその制御: データ受信のための FIFO とその制御を行う。

転送長変換の部分では、コプロセッサから新しいリクエストが到着すると、そのアドレス ADDR と転送長 COUNT をもとに、AXI における転送長の計算を行います。先に述べた転送長の制限 (最大256ワード、かつページ境界をまたがない) を考慮すると、求める転送長 read_length は、

read_length = min(COUNT, 256, ページ境界までのワード数)

で計算できます。この値に1を減じたものが ARLEN になります。ARADDR に ADDR をセットし、AXI の AR チャネルにリクエストを送信します。

また、ADDR に転送バイト数 (= 転送長 x 4) を加えたものを次のアドレス read_next、COUNT から転送長を引いたものを残りの転送長 read_rest として、それぞれ保存しておきます。そして、次回以降の転送長の計算は、ADDR、COUNT のかわりに read_next、read_rest を使って行います。最終的に、read_rest がゼロになれば、リクエストの切り分けが完了します。

以上に対応する記述は、AXI_fifo.sv の 82~96 行目、116~137 行目に見られます。転送長の計算は、記述の前半に示す trans_length 関数にまとめています。

// 転送長を求めるための関数
function logic [8:0] trans_length(
    logic [31:0] addr,
    logic [15:0] count);
    logic [15:0] len, to_pageend;
    len        = count;
    to_pageend = 16'h0400 - {6'h00, addr[11:2]};
    if (len >= 16'h100) begin // 最大 256 ワード
        len = 16'h100;
    end
    if (len >= to_pageend) begin // ページ境界を跨ぐ場合
        len = to_pageend;
    end
    return len[8:0];
endfunction

また、記述の後半は AR チャネルへのリクエスト送信関係のロジックとなります。

if (~ AXI_M_ARVALID) begin // リクエストなし -> 新しいリクエストの到着待ち
    if (READ_REQ) begin 
        read_length = trans_length(READ_ADDR, READ_COUNT);
        n_araddr    = READ_ADDR;
        n_arlen     = read_length - 1'b1;
        n_arvalid   = 1'b1;
        n_read_next = READ_ADDR + (read_length << 2);
        n_read_rest = READ_COUNT - read_length;
    end
end else begin // リクエストあり -> 既存リクエストの受理待ち
    if (AXI_M_ARREADY) begin 
        if (read_rest == 16'd0) begin
            n_arvalid   = 1'b0;
        end else begin
            read_length = trans_length(read_next, read_rest);
            n_araddr    = read_next;
            n_arlen     = read_length - 1'b1;
            n_read_next = read_next + (read_length << 2);
            n_read_rest = read_rest - read_length;
        end
    end
end

転送数管理の部分は、全てのデータ転送が完了しているかどうかの判定に用いられます。データ受信待ちのリクエストを数え、これが0であれば、読み出しが完了していると判定します。

この部分は一種のアップダウンカウンタになります。カウンタがインクリメントされる条件は AR チャネルの通信が成立する (ARVALID と ARREADY がともに ‘1’) こと、デクリメントされる条件は R チャネルで最後のワードの通信が成立する (RLAST、RVALID、RREADY がいずれも ‘1’) ことです。

これに対応する記述は、138~143 行目に記載されています。なお、両方の条件が同時に成立する場合は、インクリメントとデクリメントは相殺されます。

if (AXI_M_ARVALID & AXI_M_ARREADY) begin
    n_read_reqs = n_read_reqs + 1'b1;
end
if (AXI_M_RVALID & AXI_M_RREADY & AXI_M_RLAST) begin
    n_read_reqs = n_read_reqs - 1'b1;
end

データの FIFO に対しては、AXI の R チャネルの通信が成立したときには書き込み、コプロセッサ側との通信が成立した時には読み出しが行われます。書き込み・読み出しが可能であるかは、それぞれ FIFO が満杯でないか、空でないかをチェックすることで行なえます。

対応する記述は 167~182 行目です。FIFO の満杯 (read_full)、空 (read_empty) を示す信号の否定が、それぞれ RREADY と VALID に与えられています。

assign AXI_M_RREADY = ~ read_full;
assign READ_VALID   = ~ read_empty;

fifo #(
        .WIDTH(32),
        .SIZE (1024))
    read_fifo (
        .CLK     (ACLK),
        .RST     (~ ARESETN),
        .DATA_W  (AXI_M_RDATA),
        .DATA_R  (READ_DATA),
        .WE      (AXI_M_RVALID),
        .RE      (READ_READY),
        .EMPTY   (read_empty),
        .FULL    (read_full),
        .SOFT_RST(1'b0));

書き込みチャネルの信号変換

次は書き込みチャネルです。下図に書き込みチャネルの変換にかかるブロック図を示します。

インタフェース変換回路の書き込みチャネル。

転送長変換・FIFO制御にかかるロジックは、ほとんど読み出しチャネルのものと変わりません。大きく異なるのは転送数管理の部分です。なぜなら、読み出しではデータの終端を示す RLAST を受け取るだけで済みましたが、書き込みでは自分から WLAST を送信しなければならないからです。

そのため、AW チャネルで送った転送長 AWLEN を記憶しておくための FIFO を別に用意します。なお、この転送長の FIFO が満杯のときにも、転送長変換のロジックは中断するようにしておきます。また、転送長の FIFO が空の場合、WLAST をいつ付加してよいかわからないため、W チャネルでのデータの転送は行われません。

W チャネルでデータが転送されるとき、この FIFO から転送長を1つ取り出します。取り出した転送長が1 (AWLEN が 0) の場合には、即座にそのデータに WLAST を付加します。そうでない場合には、取り出した転送長をカウンタに格納し、W チャネルのデータ転送のたびにカウンタをデクリメントします。カウンタがゼロになるとき、そのデータに WLAST を付加します。

ここまで説明したロジックは、304~318 行目に記述されています。転送長が記憶された FIFO には wl_fifo という名前がついています。

    if (wfirst) begin // 最初のデータ: wl_fifo から取り出したデータの値で判定
        AXI_M_WLAST = (wl_fifo_out == 8'h00);
        if (AXI_M_WREADY & AXI_M_WVALID) begin
            wl_fifo_re  = 1'b1;
            n_wfirst    = (wl_fifo_out == 8'h00);
            n_wl_rest   = wl_fifo_out;
        end
    end else begin // それ以外: カウンタ wl_rest の値で判定
        AXI_M_WLAST = (wl_rest == 8'h01);
        if (AXI_M_WREADY & AXI_M_WVALID) begin
            n_wfirst    = (wl_rest == 8'h01);
            n_wl_rest   = wl_rest - 1'b1;
        end
    end
end

次回予告

今回は、フル機能の AXI の、特に転送長に関する機能を使いやすくするためのインタフェース回路の設計例について説明しました。今回の内容は次回まとめて総括するので、要点の列挙はありません。

次回は、このインタフェース回路を用いたコプロセッサの設計例として、ステンシル計算コプロセッサを取り上げ、その設計とシステムへの組み込み、性能比較を行っていきます。

愛知⼯業⼤学 藤枝直輝

タイトルとURLをコピーしました