日曜技術者のメモ

趣味でやった事のメモ書きです。

VivadoHLSで射影変換を実装してみた-3-

↓の続きで今回が多分射影変換の最終回

se.hatenablog.jp

原因追求

前回射影変換をお手軽に実装したがかなり遅かったのでまずは原因を確かめる。
と、言っても原因は↓のAXI HP3の波形を見れば明らか。
f:id:ginnyu-tei:20170716121548j:plain:w400
Write/Read共にシングルアクセスなので1pix読み書きに時間がかかっている。なのでピクセルクロックの3倍で動作させても遅いのである。

しかし、射影変換は座標を変換するのでアドレスが飛び飛びになる。これ自体はどうしようもないので他の方法で速度アップを図る。

クロックアップ(150MHz→250MHz)

まず、手抜き手軽な対応としてバス周りのクロックアップをやってみる。homography_dmaとHP3バス周りを250MHzまで上げてみた。
ちなみにhomography_dmaは300MHzまで合成できたが、PSからPLへ供給できるクロックが250MHzまでだったので250MHzにした。
その結果が約33fpsまで上がった。
f:id:ginnyu-tei:20170716211315j:plain:w400

ただし、AXI HP3の波形はあまり変わっていない。
f:id:ginnyu-tei:20170716215304j:plain:w400

ライトアクセス改善

次にライトアクセスを改善してみる。
上でも書いたとおり射影変換のアドレスが飛び飛びになるが今回の実装では、画像データライトは連続している。
そこで、homography_dmaを改造してライトはバースト転送にする。

f:id:ginnyu-tei:20170716214224j:plain
↑の様にリードしたデータを一旦FIFOにプッシュして別モジュールからバーストライトを実行する。
これでDRAMへの負荷軽減を期待している。

では、コードを記載していく。
まずはhomography_dma_write_fifoここはo_dataのm_axiをap_fifo変えるだけ。この様にI/Fを手軽に変更できるのも高位合成の良い所。

void homography_dma_write_fifo(
        unsigned int *i_data,
        unsigned int *o_data,
        unsigned int *addr
        ){
#pragma HLS INTERFACE ap_fifo depth=786432 port=addr
#pragma HLS INTERFACE m_axi depth=786432 port=i_data
#pragma HLS INTERFACE ap_fifo depth=786432 port=o_data

    unsigned int tmp;
    for (int i = 0 ; i < 1024*768; i++){
#pragma HLS PIPELINE
        tmp = *(addr + i);
        if (tmp > 786432){
            o_data[i] = 0;
        } else {
            o_data[i] = i_data[tmp];
        }
    }
}

次にfifo2axi_xgaのコード

void fifo2axi_xga(
        unsigned int *i_data,
        unsigned int *o_data
        ){
#pragma HLS INTERFACE ap_fifo depth=786432 port=i_data
#pragma HLS INTERFACE m_axi depth=786432 port=o_data

    for (int i = 0 ; i < 1024*768; i++){
        o_data[i] = i_data[i];
    }
}

ただfifoのデータをaxiに書き込むだけである。ここはバーストlengthを指定できないか色々やってみたが、この書き方が16バースト固定で一番速度が早かった。
Vivado HLSでCosimした結果が↓
f:id:ginnyu-tei:20170716223136j:plain:w400

実際にはFIFOにライトする速度の方が早いのでfifoにある程度データが溜まってからライトを開始するようにすれば効率よくAXIライト出来ると思う。
(これ書いてて思いついたが、ap_fifoのempty信号にAlmost Fullを使ってバーストlength分溜まったら信号を出す様にすれば必ず1バースト分は連続で出力できる。でも、1フーレムのデータ数と1バーストの転送量の関係によってはFIFOに半端なデータが残りそう)

これで必要なモジュールができたのでIP Integratorで配線した結果が↓
f:id:ginnyu-tei:20170717001902j:plain:w400

ブロックがかなり増えたのでCreate Hierarchyで階層化した。
この状態でZedboardでhomography_dma_write_fifoのap_doneを観察した結果が↓
f:id:ginnyu-tei:20170717004256j:plain:w400
約47FPSとかなり改善した。
実際はap_doneの後fifo2axi_xgaがまだ動いているのがパイプライン的な動作になっているのでFPSには影響しない(はず)。
(例えパイプライン動作でなくとも残っているは1バースト+α位で多少増えるだけ)
AXI HP3の波形を確認するとリード側はあまり変化していないが、ライト側はバースト転送になっているのがわかる。
f:id:ginnyu-tei:20170717010613j:plain:w400

さらなる高速化

以上で簡単にできそうな改良をして2倍位早くなった。
これ以上早くするアイディアはいくつかあるが実装に時間がかかりそうなのでメモだけしておく。

  1. ブロックRAMを使う
    フレームデータをDRAMでなくブロックRAMに格納する。これならRead/Writeが1サイクルで可能なので高速化できる。でも、ブロックRAMが多いFPGAじゃないと足りなくなる。
  2. 先にアドレス計算をしておく
    一度パラメータを決めたらしばらく変えなくて良いアプリケーション向けだが、先に1フレーム分座標を計算してバースト転送出来るように並び替えをする。
  3. 変換する範囲をなるだけ狭くする
    今回の様にフレーム分の一部のみ変換する場合はフレーム全て変換せず、変換前/変換後の各4座標から変換に必要な範囲だけを転送するようにする。
  4. ブロックサイズでリードする。
    変換する次のアドレスは今のアドレスの近くにある可能性が高いので8×8や16×16のサイズでリードしてデータを貯めておく。

とりあえず、この位思いついた。実装は3以外大変そうなので誰かこれを試してみてほしい。
また思いついたら書き足す。