FC2カウンター FPGAの部屋 2017年01月
fc2ブログ

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

SDx 2016.3 のプラグマによるハードウェアと性能の違い5

SDx 2016.3 のプラグマによるハードウェアと性能の違い4”の続き。

SDSoC 勉強会SDSoC勉強会_170128_スライド「SDx 2016.3のプラグマによるハードウェアと性能」の18ページの性能比較表で「プラグマ無し」と「SEQUENTIAL」のハードウェア実行時間がばらつくのは、メモリ領域を malloc() で取っているからではないか?という指摘を頂いた。malloc() でとるとページ(4kバイト)ごとに不連続なメモリを使用している可能性がある。ページごとに、スワップアウトしているかどうか?を確かめてスワップアウトされていたらスワップインする必要がある。つまり、ページごとに余計な処理をする必要がある。それで、遅くもなっているし、時間の変動も起こるのではないか?という指摘だった。
それならば、sds_alloc() でCMA 領域に連続メモリ領域を確保すれば大丈夫なはずだ。なお、ソフトウェアはMMU を通っているので、MMUで論理アドレスー物理アドレス変換が自動的に行われるので、CPUから論理アドレスに書けばページが違っていても問題ない。(論点が微妙に違うのはご容赦ください)

さて、前回のSDSoC の lap_filter2 プロジェクトは memcpy() を使った実装を上書きしてしまったので、新たに lap_filter1 プロジェクトを作成した。
SDx_2016_3_2_1_170130.png

今回は、最初から hw_rd_bmp と hw_lapd にメモリ領域を確保する際に、sds_alloc() を使用した。
SDx_2016_3_2_2_170130.png

これで、プラグマ無しにしてRelesase でビルドを行った。
SDx_2016_3_2_3_170130.png

MicroSD カードに sd_card フォルダの内容を書いて、ZYBO の電源ONした。
Linux が立ち上がった。cd /mnt し、 ./lap_filter1.elf を起動した。結果を示す。
SDx_2016_3_2_4_170130.png

ソフトウェア実行時間がたま~にばらつくときがあるのだが、ハードウェア実行時間は安定した。やはり、malloc() で取るとハードウェア実行時間は安定しないようだ。
明らかにソフトウェア実行時間がおかしい値を除いたハードウェア実行時間の5回の平均は、1047 us だった。明らかにおかしい値を除いたソフトウェア実行時間の5回の平均は、854 us だった。

次に、

#pragma SDS data access_pattern(cam_fb:SEQUENTIAL, lap_fb:SEQUENTIAL)

プラグマを使用した場合について、やってみよう。今回もハードウェアに関するメモリ領域は引き続き、sds_alloc() を使用している。
SDx_2016_3_2_5_170130.png

Relesase でビルドを行った。
MicroSD カードに sd_card フォルダの内容を書いて、ZYBO の電源ONした。
Linux が立ち上がった。cd /mnt し、 ./lap_filter1.elf を起動した。結果を示す。
SDx_2016_3_2_6_170130.png

こちらも、ソフトウェア実行時間がたま~にばらつくときがあるのだが、ハードウェア実行時間は安定した。やはり、malloc() で取るとハードウェア実行時間は安定しないようだ。
明らかにソフトウェア実行時間がおかしい値を除いたハードウェア実行時間の5回の平均は、969 us だった。プラグマ無しよりもこちらのほうがハードウェア実行時間が短い。明らかにおかしい値を除いたソフトウェア実行時間の5回の平均は、843 us だった。

ソフトウェアのメモリ領域の確保も sds_alloc() にしてみよう。これでどうなるだろうか?
SDx_2016_3_2_7_170130.png

Relesase でビルドを行った。
MicroSD カードに sd_card フォルダの内容を書いて、ZYBO の電源ONした。
Linux が立ち上がった。cd /mnt し、 ./lap_filter1.elf を起動した。結果を示す。
SDx_2016_3_2_8_170130.png

ソフトウェア実行時間もばらつく頻度が多くなった気がする。ハードウェア実行時間もたまにばらついている。

プラグマ無しで、ソフトウェアのメモリ領域の確保も sds_alloc() にしてみた。
SDx_2016_3_2_9_170130.png

Relesase でビルドを行った。
MicroSD カードに sd_card フォルダの内容を書いて、ZYBO の電源ONした。
Linux が立ち上がった。cd /mnt し、 ./lap_filter1.elf を起動した。結果を示す。
SDx_2016_3_2_10_170130.png

やはり、ソフトウェア実行時間もばらつく頻度が多くなった気がする。
  1. 2017年01月31日 05:00 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

FPGAマガジン No.16にVivado HLSのAXI4 Masterアクセス編を書きました

FPGAマガジン No.16 にVivado HLSのAXI4 Masterアクセス編を書きました。

FPGAマガジン No.16 の78ページから111ページまで「高位合成ツールVivado HLS 特設記事」として、Vivado HLSのAXI4 Masterアクセスについて書きました。
「第1章 アルゴリズム通りに記述したフィルタCプログラムをハードウェア化してみよう」では、3x3 のピクセルをDDR3 SDRAM からそのまま読んできた時の性能を確認しています。

「第2章 ライン・バッファ/バースト転送/最適化指示子を駆使した高速化テクニック」では、段階を踏んで、第1章のC ソースコードを変更したり、指示子を追加したりして性能を向上させていきます。そして、最終的には、性能を第1章の25倍まで引き上げています。

実機のテストはありませんが、(そうするとページ数が膨大に。。。)性能向上を実感できる記事になっていると思います。
以前書いたプレゼン資料を大幅にアップデートして、わかりやすく書いたつもりです。ぜひ読んでみてください。

また、Vivado HLS による AXI4-Lite の使用方法を書いたFPGAマガジン No.15 もお勧めです。 AXI4-Lite はレジスタのアクセスなどで使用するので、覚えておいた方が良いです。AXI4-Lite をアクセスするときに使用できるドライバの使用方法についても書いてあります。
  1. 2017年01月29日 07:56 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

SDSoC 勉強会

今日は、SDSoC 勉強会に行ってきました。
猛者ばかりなので、プレゼンするのに戦々恐々としていましたが、割とうまくできたかな?
資料はSlideShareにアップしました。タイトルは、「SDx 2016.3のプラグマによるハードウェアと性能」です。
疑問の点も指摘して頂いて、納得しました。後で検証してみたいと思います。

他の方のプレゼンも、とっても濃い話ばかりで、楽しめました。
懇親会でも楽しく過ごすことができました。
幹事さん、参加された皆さん、楽しかったです。ありがとうございました。
  1. 2017年01月28日 22:58 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

Xilinx/PYNQのプロジェクトを生成した4(タイミングエラーの解決)

Xilinx/PYNQのプロジェクトを生成した3(タイミングエラーの解析2)”の続き。

前回は、Report CDC で異なるクロック・ドメイン間のデータのやり取りの状況を解析した。今回はその問題となるパスを確認して解決してみよう。

最初の system_i/iop3/arduino_io_switch_0/inst/arduino_io_switch_v1_0_S_AXI_inst/slv_reg2_reg[14]/C から system_i/tracebuffer_arduino/trace_cntrl_0/inst/ap_CS_fsm_reg[7]/D へのパスを解析してみよう。

iop3 はここにある。
pynq-master_29_170125.png

pynq-master_30_170125.png

ipp3 をダブルクリックして開くと、arduino_io_switch_0 があった。
pynq-master_31_170125.png

さて、行先を見てみよう。Unknown のパスの 1, 2 番目の tracebuffer_arduino と tracebuffer_pmods が上下に配置されている。
pynq-master_32_170125.png

system_i/iop3/arduino_io_switch_0 から system_i/tracebuffer_arduino へのデータパスがあるかどうか?を見てみた。ある。。。
pynq-master_33_170125.png

2, 3 番目の iop1/mb1_gpio を見てみよう。iop1 をダブルクリックして開いた。
pynq-master_34_170125.png

pynq-master_35_170125.png

iop1/mb1_gpio から tracebuffer_pmods へのデータパスがあるかどうか?確かめてみた。やはり、あるね。。。
pynq-master_36_170125.png

arduino_io_switch_0 はプロジェクト内にローカルに存在するIP の様なので、IP の中身を見ることができる。
arduino_io_switch_0 を右クリックし、右クリックメニューからEdit in IP Packager を選択する。
pynq-master_37_170125.png

Edit in IP Packager ダイアログが表示された。OK ボタンをクリックした。
pynq-master_38_170125.png

arduino_io_switch_0 プロジェクトが表示された。
pynq-master_39_170125.png

slv_reg2 には S_AXI_WDATA が代入されていた。

そのarduino_io_switch_0 の slv_reg2 が接続されている先は、tracebaffer_arduino の A_TDATA だが、これはVivado HLSで生成された trace_cntrl_0 に直接接続されている。これは問題があるな。。。
pynq-master_40_170125.png

tracebuffer_pmods も同じだった。
pynq-master_41_170125.png

tracebaffer_arduino や tracebuffer_pmods は clk_fpga_3 クロックを使用している。
pynq-master_42_170125.png

Xilinx/PYNQのプロジェクトを生成した2(タイミングエラーの解析1)”でも、clk_fpga_3 のタイミングエラーは最大 - 2.258 ns でかなり大きい。よって、これを clk_fpga_0 にしてしまえば、全部解決すると思う。

PS からPL に回っているクロックを見てみよう。
pynq-master_43_170125.png

clk_fpga_0 は 100 MHz で、 clk_fpga_3 は166.667 MHz だった。

PS の FCLK_CLK3 ピンをクリックして、そこで右クリックし、右クリックメニューからDisconnect Pin を選択して、PS の FCLK_CLK3 ピンだけをクロックネットから外す。配線を消してしまうと、clk_fpga_3 のクロック配線をすべて削除することになり大変なことになってしまう。
次にPS の FCLK_CLK0 ピンからPS の FCLK_CLK3 ピンに接続されていたクロックネットに配線した。
pynq-master_44_170125.png

次に、ルーターとプレーサーにもう少し頑張ってもらうために、Flow Navigator のImplementation → Implementation Settings をクリックして、出てきたProject Settings ダイアログで、Strategy にPerformance_ExtraTimingOpt を選択した。
pynq-master_45_170125.png

これで、もう一度、論理合成、インプリメント、ビットストリームの生成を行った。
レポートを示す。
pynq-master_46_170126.png

うまくタイミングメットしたようだ。
  1. 2017年01月26日 05:08 |
  2. PYNQ
  3. | トラックバック:0
  4. | コメント:0

Xilinx/PYNQのプロジェクトを生成した3(タイミングエラーの解析2)

Xilinx/PYNQのプロジェクトを生成した2(タイミングエラーの解析1)”の続き。

前回はタイミング・レポートを100個まで表示した。今回は、Report CDC を確認してみた。

Tools メニューからTiming -> Report CDC... を選択した。
pynq-master_19_170122.png

Report CDC ダイアログが表示された。
Clocks の From の ... ボタンをクリックした。
pynq-master_20_170122.png

Choose Start Clocks ダイアログが表示された。
Find ボタンをクリックして、Results の中から clk_fpga_0 を選択し、右向き→をクリックした。
pynq-master_21_170122.png

右のSelected に clk_fpga_0 が入った。これでOK なので、Set ボタンをクリックした。
pynq-master_23_170122.png

Report CDC ダイアログに戻ると、From に get_clocks clk_fpga_0 が入った。
pynq-master_24_170122.png

To も同様に clk_fpga_3 を設定した。
pynq-master_25_170122.png

これで OK ボタンをクリックした。
すると、Vivado の下に、Report CDC が表示された。
Endpoints が 152 個あって、Safe が 148 個、Unknown が 4 個あることがわかる。
pynq-master_26_170122.png

Endpoints のパスを示す。一番下がUnknown だ。
pynq-master_27_170122.png
  1. 2017年01月24日 05:01 |
  2. PYNQ
  3. | トラックバック:0
  4. | コメント:0

Xilinx/PYNQのプロジェクトを生成した2(タイミングエラーの解析1)

Xilinx/PYNQのプロジェクトを生成した1”の続き。

前回はちょっとミスったが、PYNQのプロジェクトを生成することができた。今回はそのタイミングエラーを解析してみよう。

最初に、左端のFlow Navigator からImplementation を展開して、Open Implemented Design も展開して、Report Timing Summary をクリックする。
pynq-master_28_170122.png

Report Timing Summary ダイアログが表示されるので、Maximum number of paths per clock or path group を 100 に変更しよう。
pynq-master_12_170122.png

それとは別にCritical Messages ダイアログが表示されていた。
pynq-master_13_170122.png

system_i/video/rgb2dvi_0/U0/SerialClkは、マスタクロックaxi_dynclk_0_PXL_CLK_Oからの論理パスを持っていないということのようだな?でも、rgb2dvi IP のSerialClk は出力の5倍の周波数を入れてシリアライズする重要なクロックのはずなんだけど?

さて、画面の下にTiming Summary が表示された。
pynq-master_14_170122.png

Timing Summary をフローティングして最大化した。
pynq-master_15_170122.png

Intra-Clock が赤くなっている。これはそのクロック内のタイミングを示している。赤はタイミング違反だ。
clk_fpga_0 のタイミングエラーは小さいので、プレーサーやルーターに頑張ってもらっても大丈夫だと思う。

clk_fpga_1 のタイミングエラーも小さいので、プレーサーやルーターに頑張ってもらっても大丈夫そうだ。
pynq-master_16_170122.png

clk_fpga_3 のタイミングエラーは最大 - 2.258 ns でかなり大きい。制約の周期は 6 ns = 166.67 MHz なので、100 MHz くらいにできるのならば、そのほうが良いと思う。
pynq-master_17_170122.png

次は、Inter-Clock Paths だけど、clk_fpga_0 から clk_fpga_3 へのクロックパスにエラーが出ている。
pynq-master_18_170122.png

次の回でこのパスを検証してみよう。
  1. 2017年01月23日 05:50 |
  2. PYNQ
  3. | トラックバック:0
  4. | コメント:0

本能寺ホテル(映画)を見てきました

今日は奥さんと本能寺ホテル(映画)を見てきました。
綾瀬はるかのとぼけた味が生きている楽しい映画でしたよ。
  1. 2017年01月22日 21:38 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

Xilinx/PYNQのプロジェクトを生成した1

ikwzm さんの”PYNQ-Z1 の ビットストリームを再ビルドしたときのタイミング違反を無くす”を見ながら、Xilinx/PYNQのプロジェクトを生成してみた。

(注) この記事はbase.tcl を動作させるディレクトリを間違っていました。Xilinx/PYNQによると/Pynq-Z1/vivado/base でbase.tcl をどうさせるそうです。でも、TCLスクリプト・ファイルは動作させるディレクトリにおいてほしいですね。。。
これでうまく行っているので、このままで行くことにします。

Xilinx/PYNQのプロジェクトをZIP ファイルでダウンロードした。

PYNQ-master.zip の PYNQ-master フォルダの下を、C:\Users\Masaaki\Documents\Vivado\PYNQ\PYNQ-m フォルダの下にコピーした。

C:\Users\Masaaki\Documents\Vivado\PYNQ\PYNQ-m\Pynq-Z1\bitstream\base.tcl を開いて編集した。
23行目のset scripts_vivado_version 2016.1 を set scripts_vivado_version 2016.2 に変更した。
pynq-master_3_170122.png

Vivado 2016.2 を起動して、TCLスクリプト・ウインドウを開き、

cd c:/Users/Masaaki/Documents/Vivado/PYNQ/PYNQ-m/Pynq-Z1/bitstream/
source base.tcl

コマンドを入力した。
pynq-master_5_170122.png

audio_direct IP でエラー発生。
pynq-master_6_170122.png

エラー内容を示す。

CRITICAL WARNING: [BD 41-52] Could not find the abstraction definition specified by the vlnv: digilentinc.com:interface:tmds_rtl:1.0
CRITICAL WARNING: [BD 41-181] Type specified by the VLNV: 'digilentinc.com:interface:tmds_rtl:1.0', cannot be found. Interface port: 'hdmi_in' cannot be created
CRITICAL WARNING: [BD 41-52] Could not find the abstraction definition specified by the vlnv: digilentinc.com:interface:tmds_rtl:1.0
CRITICAL WARNING: [BD 41-181] Type specified by the VLNV: 'digilentinc.com:interface:tmds_rtl:1.0', cannot be found. Interface port: 'hdmi_out' cannot be created
ERROR: [BD 5-390] IP definition not found for VLNV: xilinx.com:user:audio_direct:1.0
ERROR: [Common 17-39] 'create_bd_cell' failed due to earlier errors.


audio_direct IP などが見えないみたいだ。
Vivado 2016,2 のTcl Shell でやってみても同様にエラーだった。
pynq-master_7_170122.png
pynq-master_8_170122.png  

IPをリポジトリに登録する set_property ip_repo_paths を探すと 49 行目にあった。パスが間違っている。../ip じゃなくて ../vivado/ip のはずだ。修正を行った。49行目 set_property ip_repo_paths ../ip [current_project] を set_property ip_repo_paths ../vivado/ip [current_project] に変更。

これで base.tcl を実行すると、ブロックデザインは完成したが、3391行目の add_files -fileset constrs_1 -norecurse ./src/constraints/top.xdcでエラー発生。またパスが間違っている。正しくは、add_files -fileset constrs_1 -norecurse ../vivado/base/src/constraints/top.xdc だ。
pynq-master_9_170122.png

同様に、3394行目も add_files -norecurse ./src/top.v を add_files -norecurse ../vivado/base/src/top.v に修正する必要がある。
これで、もう一度、Vivado 2016.2 を閉じて、生成したプロジェクトを消去し、もう一度、Vivado 2016.2 を立ち上げた。
TCLスクリプト・ウインドウを開いて、先ほどの cd と source コマンドを入れると、ブロックデザインを作って、論理合成、インプリメント、ビットストリームの生成を行った。が、しかし、 3409行目でエラー発生。これもパスが間違っていて、file copy -force ./base/base.runs/impl_1/top.bit ../../bitstream/base.bit は file copy -force ./base/base.runs/impl_1/top.bit ./base.bit だろうと思う。(まだ、動かしていない)
pynq-master_10_170122.png

結果のレポートを示す。やはり、タイミングがメットしていない。
pynq-master_11_170122.png
  1. 2017年01月22日 12:09 |
  2. PYNQ
  3. | トラックバック:0
  4. | コメント:0

SDSoCのチュートリアルをやってみた4(ハードウェア/ソフトウェア イベントのトレース)

SDSoCのチュートリアルをやってみた3(タスクのパイプライン処理最適化)”の続き。

SDSoC 環境ユーザー ガイド SDSoC 環境の概要 UG1028 (v2016.2) 2016 年 7 月 13 日”の66ページの”第7章  チュートリアル : ハードウェア/ソフトウェアイベントのトレース”をやってみよう。
SDSoC Environment Tutorial: Introduction UG1028 (v2016.3) November 30, 2016”では、43ページの”Lab 7: Hardware Debug”だった。

さて、zc702 のスタンドアロン・プロジェクト zc702_test をMatrix Multiplication テンプレートで作成した。
SDx_v2016_3_tut_39_170119.png

ZYBO の空のスタンドアロン・プロジェクト mmult_trace を作成した。
zc702_test のソースファイルを mmult_trace にコピー&ペーストした。
mmult_accel 関数をハードウェア関数に登録した。
SDx_v2016_3_tut_40_170119.png

mmult_accel.h の #define N 32 を #define N 16 に変更した。(ZYBO ではリソースが足りないため)
SDx_v2016_3_tut_41_170119.png

mmult.cpp の #define NUM_TESTS 1024 を 10 に変更した。これはトレースるのにあまり大きな数だとトレースしきれないからだろう?
SDx_v2016_3_tut_42_170119.png

Enable event tracing にチェックを入れてからビルド・ボタンをクリックしてビルドした。
SDx_v2016_3_tut_43_170119.png

ビルドが終了した。成功だ。
SDx_v2016_3_tut_44_170119.png

トレースするので、mmult_trace を右クリックし、右クリックメニューからRun As -> 4 Trace Applicatin (SDSoC Debugger) を選択した。
SDx_v2016_3_tut_45_170119.png

すると、ZYBO がコンフィグレーションされて、ソフトウェアが走ったようだ。この前までにZYBO をJTAG モードにして電源ON して多く必要がある。
トレース結果が表示された。
SDx_v2016_3_tut_46_170119.png

現在の画面で右下のアクティブなウインドウをダブルクリックして最大化した。
SDx_v2016_3_tut_47_170119.png

チュートリアルよりも大分まばらだが、トレース・イベントがグラフィカルに表示されている。
オレンジ色がソフトウェア・イベント、緑色がアクセラレータ・イベント、青色がデータ転送・イベントだそうだ。
しかしなぜこんなに間が空いてしまっているのだろうか?無駄な気がするが?その他のソフトウェア処理が重いのだろうか?これではあまり性能が上がらないと思う。

左のProject Explorer をよく見ると、mmult_trace_Traces フォルダができていた。その中のTrace[1] にSDSoC_AXI_Trace_1-19_15-18 が出来ていた。その下のAXI Event Analysis -> AXI Status View が上の図だ。Tmf Statistics Analysis -> Statistics をダブルクリックして開けてみた。
SDx_v2016_3_tut_49_170119.png

統計情報のようだ。

AXI Status View をイベント・テキストの下に持ってきた。これでチュートリアルの図と同じになった。
SDx_v2016_3_tut_50_170119.png

最後にVivado プロジェクトの結果のレポートを示す。
SDx_v2016_3_tut_51_170120.png

リソースがかなり消費されている。

ブロックデザインを示す。
SDx_v2016_3_tut_52_170120.png

トレース用のIP が多い。
  1. 2017年01月20日 04:46 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoCのチュートリアルをやってみた3(タスクのパイプライン処理最適化)

SDSoCのチュートリアルをやってみた2(システム パフォーマンスの見積もり)”の続き。

SDSoC Environment Tutorial: Introduction UG1028 (v2016.3) November 30, 2016”の33ページ”Chapter 5 Accelerator Optimization”をやってみよう。
なお、”SDSoC 環境ユーザー ガイド SDSoC 環境の概要 UG1028 (v2016.2) 2016 年 7 月 13 日”では、63 ページの”第6章 チュートリアル : タスクのパイプライン処
理最適化”となる。

最初にZYBO 用のファイルは無いのだが、zc702 のファイルを加工してみよう。
ZYBO 用の空の async_wait_zybo プロジェクトを作成した。system configuration は Linuxとした。

C:\HDL\Xilinx\SDx\2016.3\samples\mmult_pipelined から mmult.cpp, mmult_accel.cpp, mmult_accel.h を async_wait_zybo プロジェクトの src フォルダにコピー&ペーストした。
SDx_v2016_3_tut_25_170119.png

Active build configuration を Release に設定し、HW function に mmult_accel を指定して、ビルド・ボタンをクリックした。
SDx_v2016_3_tut_26_170119.png

エラーが出た。やはり、Zynq-7010 ではリソースが足りないようだ。
SDx_v2016_3_tut_27_170119.png

mmult_accel.h を開いて #define N 32 を #define N 16 に書き換えた。
SDx_v2016_3_tut_28_170119.png

もう一度、ビルド・ボタンをクリックしてビルドを行ったら成功した。
SDx_v2016_3_tut_29_170119.png

このチュートリアルでは、Figure 2 に示すように通常はシーケンシャルにうハードウェアのアクセラレーションを行う。
SDx_v2016_3_tut_30_170119.png
SDSoC Environment Tutorial: Introduction UG1028 (v2016.3) November 30, 2016”の35ページのFigure 2: Sequential Execution of Matrix Multiply Calls を引用

パイプラインされたアクセレーターの実行のために、async(id), と wait(id) プラグマを使用してコードを書き直す必要があるそうだ。
その記述を示す。
SDx_v2016_3_tut_31_170119.png

pipeline_depth がパイプラインの数を示していて、最初にパイプライン数のmmult_accel() を発行して、その後は1つのパイプラインが終わったら、次のmmult_accel() を発行する。発行が終了したら、SDS wait(1) でパイプライン数だけの完了を待つ。そのようなプログラムになっているようだ。
その概念図は、Figure 3 に示されている。
SDx_v2016_3_tut_32_170119.png
SDSoC Environment Tutorial: Introduction UG1028 (v2016.3) November 30, 2016”の36ページのFigure 3: Pipelined Execution of Matrix Multiply Calls を引用

パイプライン実行のためには、引数の配列をコピーするためのマルチバッファが必要とのことだ。これは、mmult_accel.cpp の float _A[N][N], _B[N][N] のことだろうと思っている。
SDx_v2016_3_tut_33_170119.png

ビルドした SDカードのイメージをZYBO にSFTP して起動した。
./async_wait_zybo 1 を実行した。これは順次実行だ。
SDx_v2016_3_tut_34_170119.png

値が安定しない。SW/HW が 1.05 倍から2.73 倍でばらついている。

./async_wait_zybo 2 を実行した。これは2つのパイプラインによるパイプライン実行だ。
SDx_v2016_3_tut_35_170119.png

やはり、こちらも値が安定しない。

残念な結果になってしまったが、Vivado のレポートを示す。
SDx_v2016_3_tut_36_170119.png

Vivado のブロックデザインを示す。
SDx_v2016_3_tut_37_170119.png

Vivado HLSを示す。
SDx_v2016_3_tut_38_170119.png
  1. 2017年01月19日 12:32 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

Zybot をステレオカメラにする3(Zybot 改造中)

Zybot をステレオカメラに改造中です。

ZYBO を2枚搭載して、それぞれにカメラを付けてZYBO 同士のHDMI コネクタをケーブルで接続しました。片方ZYBO のカメラの画像をもう一方のZYBO に転送して、以前、やっていたステレオカメラにします。カメラ用のマウントは3Dプリンタで作製した”Zybot をステレオカメラにする2(カメラ・マウントの作製)”のマウントを使用しています。
Zybot_1_170118.jpg

Zybot_2_170118.jpg

Zybot_3_170118.jpg

バッテリーは今まではリチウムイオン電池のジャンプスターター1個でしたが、+5Vのモバイルバッテリーを追加して、ZYBOの電源などはそこから取ろうと思っています。
問題は無線LANで、今までWLI-UTX-AG300を使っていて、これにスイッチングハブを付けてZYBO 2台のLANコネクタに接続してみたのですが、通信できませんでした。WLI-UTX-AG300を2つというのも考えたのですが、大きすぎます。そこで、ZYBOのUSB コネクタに付けられないかな?と思い、WN-G150U を買ってみました。果たしてZYBO が無線LAN親機につながるでしょうか?
超ド級の迫力のZybot になってしまいました。。。
果たして走るのでしょうか?
  1. 2017年01月18日 16:10 |
  2. Zybot
  3. | トラックバック:0
  4. | コメント:0

SDSoCのチュートリアルをやってみた2(システム パフォーマンスの見積もり)

SDSoCのチュートリアルをやってみた1(システムのデバック)”の続き。

前回は、ソフトウェアとハードウェア両方を含んだスタンドアロンのシステムをSDSoCのデバックモードでデバックすることができた。今回は、”SDSoC 環境ユーザー ガイド SDSoC 環境の概要 UG1028 (v2016.2) 2016 年 7 月 13 日”の54 ページの”チュートリアル : システム パフォーマンスの見積もり”をやってみよう。

これもZynq のボードを使用するので、ZYBOをパソコンにUSBケーブルで接続し、ZYBO の電源をON しておく。

SDxプロジェクトは前回の debug_test_bm を使用する。

debug_test_bm プロジェクトを展開して project.sdx をダブルクリックして開く。
Estimate performance にチェックを入れて、トンカチ ボタンをクリックしてデバックでビルドする。
SDx_v2016_3_tut_19_170115.png

Performance and resource estimation report for the 'debug_test_bm' project が表示された。
SDx_v2016_3_tut_20_170115.png

Click Here をクリックした。
Run application to get its performance ダイアログが表示された。
SDx_v2016_3_tut_21_170115.png

ZYBO がコンフィギュレーションされて、ソフトウェアだけのバージョンが実行されるそうだ。ハードウェアの見積もりと比較されて表示される。
SDx_v2016_3_tut_22_170115.png

main の性能差は2.25 倍ハードウェア・アクセラレーションした場合が速い。
main 関数の中の madd 関数は、ハードウェア・アクセラレーションした場合が、79.32 倍速いという結果になった。

上の図で、Summary の最初のPeformance estimates for 'main' function になっているが、これをmain 以外にすることもできるそうだ。これは、project.sdx の Root function を書き換えると変更できるそうだ。その場合は、Clean project を行ってから、ビルドするそうだ。(”SDSoC Environment Tutorial Introduction UG1028 (v2016.3) November 30, 2016”の 20 ページの”Changing Scope of Overall Speedup Comparison”を参照した)
SDx_v2016_3_tut_23_170115.png

今のビルドはデバックで行っているが、リリースでビルドした場合の性能差を示す。
SDx_v2016_3_tut_24_170115.png

main の性能差は1.78 倍ハードウェア・アクセラレーションした場合が速い。
main 関数の中の madd 関数は、ハードウェア・アクセラレーションした場合が、15.09 倍速いという結果になった。

やはり、デバックよりもリリースの方がハードウェアとソフトウェアの性能差が縮まっている。
  1. 2017年01月17日 05:06 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoCのチュートリアルをやってみた1(システムのデバック)

SDSoCのデバック方法やパフォーマンス測定のただし方法などを知らなかったので、SDSoCのチュートリアルをやってみることにした。引用するのは、”SDSoC 環境ユーザー ガイド SDSoC 環境の概要 UG1028 (v2016.2) 2016 年 7 月 13 日”で、”SDSoC Environment Tutorial Introduction UG1028 (v2016.3) November 30, 2016”も参考にしながら進めていこう。
UG1028 の日本語訳は意味不明のところがあったので、英語版も大いに参考にさせて頂いた。

それでは、SDSoCのデバック方法からやってみよう。
日本語版UG1028 の 46 ページの”チュートリアル : システムのデバッグ”をやってSDSoCのデバック方法を体験した。
SDx 2016.3 を使用して、プロジェクトはスタンドアロン・プロジェクトを使用し、ZYBO 実機を使用してデバックする。

Micro USB コードでパソコンと接続した。

JTAG 起動モードとし、ZYBO を電源ON した。

SDSoCで File メニューからNew → Xilinx SDx Project を選択した。

Create a New SDxProject ダイアログで、Project name に debug_test_bm と入力した。
SDx_v2016_3_tut_1_170115.png

Choose Hardware Platform で、zybo を選択した。(私のChoose Hardware Platform には、カスタム・プラットフォームを入れているので、他の人よりも数が多い)
SDx_v2016_3_tut_2_170115.png

Choose Software Platform and Target CPU で System configuration を Standalone OS(Zynq 7000) に設定した。
SDx_v2016_3_tut_3_170115.png

Templates で、Matrix Multiplication and Addition (area reduced) を選択した。Finish ボタンをクリックした。
SDx_v2016_3_tut_4_170115.png

debug_test_bm プロジェクトが生成された。すでにハードウェアにオフロードする関数も設定されている。この環境でデバックすることができるようだ。
SDx_v2016_3_tut_5_170115.png

debug_test_bm プロジェクトを右クリックして、右クリックメニューから Build Project を選択した。
SDx_v2016_3_tut_6_170115.png

ビルドが成功した。ビルド後の debug_test_bm プロジェクトのDebug フォルダの debug_test_bm.elf を右クリックし、右クリックメニューからDebug As -> Launch on Hardware (SDSoC Debugger) を選択した。
SDx_v2016_3_tut_7_170115.png

パースペクティブをデバックに変更するというおなじみのダイアログが出た。Yes ボタンをクリックした。
SDx_v2016_3_tut_8_170115.png

SDx がデバックモードになって、main 関数の最初の行で止まっていた。
なお、この時点で、ZYBO はコンフィギュレーションされて、DONE のLEDが点灯している。
SDx_v2016_3_tut_9_170115.png

結果を表示するためのターミナルをSDx で起動する。
Window メニューからShow View -> Others... を選択した。
SDx_v2016_3_tut_10_170115.png

Show View ダイアログで、Terminal の下のTerminal を選択した。
SDx_v2016_3_tut_11_170115.png

右下にTerminal ウインドウが追加された。
SDx_v2016_3_tut_12_170115.png

Connect アイコンをクリックして、Terminal Settings を表示させる。
Connection Type を Serial に、Port をCOM4 (これはそれぞれの環境によって異なる)に、Baud Rate を 115200 に設定した。
SDx_v2016_3_tut_13_170115.png

Tera Term を起動していたので、すでに使われていると出てしまっているが、Tera Term を落としたら接続できた。
SDx_v2016_3_tut_14_170115.png

Step Over アイコンをクリックした。
SDx_v2016_3_tut_15_170115.png

SDx_v2016_3_tut_16_170115.png

main.cpp の行が1行進んで、右の上の Variables ウインドウでも test_passed 変数が更新されて、黄色に変わっている。
SDx_v2016_3_tut_17_170115.png

Resume アイコンをクリックすると、ソフトウェアが終了して、結果が右下のTerminal ウインドウに表示された。
SDx_v2016_3_tut_18_170115.png

これでSDx 2016.3 でのデバック方法が分かった。ハードウェアとソフトウェアが協調して動作する環境でデバックすることができた。
  1. 2017年01月16日 05:35 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream向きのコードで書いたラプラシアンフィルタをVivado HLSでテスト3

AXI4-Stream向きのコードで書いたラプラシアンフィルタをVivado HLSでテスト2”の続き。

前回、lap_fb[] の最初で最後の行のオール 0 を書いたらDMA Write がシングル転送になってしまった。つまり、DMA Write の書き込みの順序をいじるとバーストしなくなるんじゃないか?という疑問がわく。そこで、DMA Read の順番も、DMA Write の順番も守るようにした。DMA Read と Write の順番を守るということは、y を行数+1 回すことにして、DMA Read は最後の1回の実行を中止し、DMA Write は最初の1回の実行を停止することにした。

Vivado HLS 2016.4 で lap_filter6 プロジェクトを作成した。
lap_filter4_17_160115.png

lap_filter6.c を貼っておく。

/* * lap_filter6.c * *  Created on: 2017/01/10 *      Author: Masaaki */

#include <stdio.h>
#include <string.h>

//#define HORIZONTAL_PIXEL_WIDTH    64
//#define VERTICAL_PIXEL_WIDTH    48
#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y(int rgb);

int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE])
{
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE m_axi depth=480000 port=lap_fb
#pragma HLS INTERFACE m_axi depth=480000 port=cam_fb

    int line_buf[3][HORIZONTAL_PIXEL_WIDTH];
#pragma HLS array_partition variable=line_buf block factor=3 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    int lap_fil_val;
    int pix, lap;

    int pix_mat[3][3];
#pragma HLS array_partition variable=pix_mat complete

    for (int y=0; y<=VERTICAL_PIXEL_WIDTH; y++){
        for (int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE
            if (y < VERTICAL_PIXEL_WIDTH)
                pix = cam_fb[y*HORIZONTAL_PIXEL_WIDTH+x];

            for (int k=0; k<3; k++){
                for (int m=0; m<2; m++){
#pragma HLS UNROLL
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int y_val = conv_rgb2y(pix);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

            if (x<2 || y<2 || y==VERTICAL_PIXEL_WIDTH) // 行の最初の2列は無効データなので0とする、1行目も0、最後の行も0
                lap = 0;

            if (y != 0)
                lap_fb[(y-1)*HORIZONTAL_PIXEL_WIDTH+x] = lap;
        }
    }

    return 0;
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}


C シミュレーションを行った。
lap_filter4_18_160115.png

C コードの合成を行った。
lap_filter4_19_160115.png

やはり、Estimated が 11.37 ns でTarget を外れてしまっている。Latency は480821 で悪くはない。

C/RTL協調シミュレーションを行った。
lap_filter4_20_160115.png

1121430 クロックかかってしまった。だめだ、遅くなっている。

C/RTL協調シミュレーションの波形を示す。
DMA Read から。
lap_filter4_21_160115.png

ARLEN が 00 でシングル転送になってしまっている。

DMA Write を示す。
lap_filter4_22_160115.png

こちらも AWLEN が 00 でシングル転送だ。

どうやら、DMA を行う文に if 文を使用するとバーストを使えるかどうか?の判断ができなくてシングル転送になるのではないだろうか?
  1. 2017年01月15日 05:22 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream向きのコードで書いたラプラシアンフィルタをVivado HLSでテスト2

AXI4-Stream向きのコードで書いたラプラシアンフィルタをVivado HLSでテスト1”の続き。

前回は、AXI4-Stream向きのコードをAXI4 Master として実装して性能を評価したところ、理論的な限界性能だった。今回は、どのように書いたら理論的な限界性能が出るのかを探ってみる。とりあえず、Writeを変えてみよう。

現在は、0 を 2 行書いているが、これを今まで通りに、0 が 1 行書かれていて、ラプラシアンフィルタ結果、最後に 0 が 1 行という今までやってきたフォーマットで書いてみたい。そのために、lap_filter4.c の lap_fb[] に書き込むところで、最初に最後の行に 0 を書いて、その後は、0 行に 0 を書いて、その後、ラプラシアンフィルタ結果を書くことにした。
そのC ソースコードを lap_filter5.c とした。下に示す。

/* * lap_filter4.c * *  Created on: 2017/01/10 *      Author: Masaaki */

#include <stdio.h>
#include <string.h>

//#define HORIZONTAL_PIXEL_WIDTH    64
//#define VERTICAL_PIXEL_WIDTH    48
#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y(int rgb);

int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE])
{
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE m_axi depth=480000 port=lap_fb
#pragma HLS INTERFACE m_axi depth=480000 port=cam_fb

    int line_buf[3][HORIZONTAL_PIXEL_WIDTH];
#pragma HLS array_partition variable=line_buf block factor=3 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    int lap_fil_val;
    int pix, lap;

    int pix_mat[3][3];
#pragma HLS array_partition variable=pix_mat complete

    for (int y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        for (int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE
            pix = cam_fb[y*HORIZONTAL_PIXEL_WIDTH+x];

            for (int k=0; k<3; k++){
                for (int m=0; m<2; m++){
#pragma HLS UNROLL
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int y_val = conv_rgb2y(pix);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

            if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので0とする
                lap = 0;

            if (y == 0)
                lap_fb[(VERTICAL_PIXEL_WIDTH-1)*HORIZONTAL_PIXEL_WIDTH+x] = lap;
            else
                lap_fb[(y-1)*HORIZONTAL_PIXEL_WIDTH+x] = lap;
        }
    }

    return 0;
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}


lap_fitlter_tb.c も lap_filter5.c に対応するように書き換えた。
そこで、C シミュレーションを行った。なお、Console は C/RTL 協調シミュレーション終了時になっている。
lap_filter4_12_160114.png

C コードの合成を行った。
lap_filter4_13_160114.png

Estimated が 11.37 ns で 10 ns を満足していない。
Latency は 480019 クロックで良さそうだ。

C/RTL協調シミュレーションを行った。
lap_filter4_14_160114.png

1120051 クロックと 2 倍以上に増えてしまっている。なぜだろうか?

C/RTL協調シミュレーションの波形を見よう。
まずはRead から。
lap_filter4_15_160114.png

ARLEN は 0f で 16 バーストなのだが、RREADY が 0 , 1 を繰り返して遅くなってしまっている。

Write を示す。
lap_filter4_16_160114.png

AWLEN が 00 でシングル転送だ。先ほどのRead が遅い原因はこれのようだ。

ほんの少し書き換えただけで、最高性能にほど遠くなってしまった。
やはり、AXI4-Stream として書いたコード最高性能にならないのだろうか?DMA の部分に if などの条件が入っているとバースト長が 16 バーストにならないのだろうか?
つまり、読む順番と書く順番は守る必要があるのかな?

最後に、lap_filter_tb.c を貼っておく。

// Testbench of laplacian_filter.c
// lap_filter_tb.c
// BMPデータをハードウェアとソフトウェアで、ラプラシアン・フィルタを掛けて、それを比較する
// m_axi offset=slave version
// 2015/08/26 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include "bmp_header.h"

int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y_soft(int rgb);
int lap_filter_axim(int *cam_fb, int *lap_fb);    // hardware
void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height); // software

int main()
{
    int *s, *h;
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *hw_lapd, *sw_lapd;
    int blue, green, red;
    char blue_c, green_c, red_c;
    struct timeval start_time_hw, end_time_hw;
    struct timeval start_time_sw, end_time_sw;

    if ((fbmpr = fopen("test.bmp""rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.bmp by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(char), 2, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(long), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((hw_lapd =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_lapd memory\n");
        exit(1);
    }
    if ((sw_lapd =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate sw_lapd memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    //gettimeofday(&start_time_hw, NULL);
    lap_filter_axim((int *)rd_bmp, (int *)hw_lapd);    // ハードウェアのラプラシアン・フィルタ
    //gettimeofday(&end_time_hw, NULL);

    //gettimeofday(&start_time_sw, NULL);
    laplacian_filter_soft(rd_bmp, sw_lapd, bmpihr.biWidth, bmpihr.biHeight);    // ソフトウェアのラプラシアン・フィルタ
    //gettimeofday(&end_time_sw, NULL);

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0, h=hw_lapd, s=sw_lapd; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            if (*h != *s){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, *h, *s);
                return(1);
            } else {
                h++;
                s++;
            }
        }
    }
    printf("Success HW and SW results match\n");
    /*if (end_time_hw.tv_usec < start_time_hw.tv_usec) {        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec - 1, 1000000 + end_time_hw.tv_usec - start_time_hw.tv_usec);    } else {        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec, end_time_hw.tv_usec - start_time_hw.tv_usec);    }    if (end_time_sw.tv_usec < start_time_sw.tv_usec) {        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec - 1, 1000000 + end_time_sw.tv_usec - start_time_sw.tv_usec);    } else {        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec, end_time_sw.tv_usec - start_time_sw.tv_usec);    } */

    // ハードウェアのラプラシアンフィルタの結果を temp_lap.bmp へ出力する
    if ((fbmpw=fopen("temp_lap.bmp""wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.bmp by binary write mode\n");
        exit(1);
    }
    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

    // RGB データの書き込み、逆順にする
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    free(rd_bmp);
    free(hw_lapd);
    free(sw_lapd);

    return(0);
}

void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height)
{
    int line_buf[3][800];

    int lap_fil_val;
    int pix, lap;

    int pix_mat[3][3];

    for (int y=0; y<height; y++){
        for (int x=0; x<width; x++){
            pix = cam_fb[y*width+x];

            for (int k=0; k<3; k++){
                for (int m=0; m<2; m++){
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int y_val = conv_rgb2y(pix);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

            if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので0とする
                lap = 0;

            if (y == 0)
                lap_fb[(height-1)*width+x] = lap;
            else
                lap_fb[(y-1)*width+x] = lap;
        }
    }
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}

  1. 2017年01月14日 04:59 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

ZYBOのUbuntuでMicro SDのMS DOSの第1パーティションをマウントする

ZYBO で動作しているUbuntu でMS DOS(FAT フォーマット)第1パーティションをマウントできて書き換えられれば、Micro SDカードを外さなくてもBOOT.bin や devicetree.dtb などを書き換えて、再度ブートすることでハードウェアの環境を書き換えることができるだろう?ということでやってみた。

まずは、lsblk でMicro SDの各パーティションの情報を見た。
ZYBO_Ubuntu_mount_1_170113.png

マウント用のディレクトリ zybo_boot を /mnt の下に作成した。mkdir zybo_boot
ZYBO_Ubuntu_mount_2_170113.png

mmcblk0p1 がマウントしたいMS DOS のFATフォーマットのドライブのようだ。これの実体は /dev にあった。
ZYBO_Ubuntu_mount_3_170113.png

起動時に実行される /etc/rc.local にマウント コマンドを書く。 vi /etc/rc.local
ZYBO_Ubuntu_mount_4_170113.png

/etc/rc.local に mount -t vfat -o rw /dev/mmcblk0p1 /mnt/zybo_boot コマンドを書いた。
ZYBO_Ubuntu_mount_5_170113.png

これで、reboot すると、/mnt/zybo_boot にMicro SDカードの第1パーティションのドライブがマウントされた。
ZYBO_Ubuntu_mount_6_170113.png
  1. 2017年01月13日 17:30 |
  2. Linux
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream向きのコードで書いたラプラシアンフィルタをVivado HLSでテスト1

Vivado HLS のソースコードをSDx で試す4(AXI4-Stream向きのコード)”でAXI4-Stream向きのコードをAXI4 Master として実装して性能を評価したところ、とても高い性能だった。それは、DMAのWrite とRead が重なり合って実行しているとしか考えらない性能だった。となると、そのコードはVivado HLS で合成したときにDMAのWrite とRead が重なり合って実行されるのだろう。そのような状況には出会ったことがないので、さっそくVivado HLS でやってみた。

Vivado HLS のバージョンは、最新の 2016.4 を使用した。

まずはViavdo HLS 2016.4 で、ZYBO 用の lap_filter4 プロジェクトを作成した。
lap_filter4_1_160112.png

lap_filter4.c と lap_filter_tb.c をVivado HLS 用に書き換えて、Add Source した。

C シミュレーションを実行した。成功した。
lap_filter4_2_160112.png

C コードの合成を行った。
lap_filter4_3_160112.png

Estimated が 11.37 ns で 10 ns を満足していない。これはIP 化で論理合成レポートが赤だったら、Target を変更しての再合成を考えよう。
Latency が 480018 クロックだった。これは速い。800 x 600 ピクセルなので、ピクセルの総数は、480000 ピクセルとなるので、18 クロックしか余計にかかっていない。

次に、C/RTL協調シミュレーションを行った。
lap_filter4_4_160112.png

480048 クロックなので、やはり速い。

C/RTL協調シミュレーションの波形を見よう。
まずはRead から。
lap_filter4_5_160112.png

途切れなく、Read できている。途切れているのは、RLAST が 0 になっているのは半クロックで、動作に影響ないので、ずーと途切れなく連続バーストできている。

Write を示す。
lap_filter4_6_160112.png

Write はWVALID と WREADY が途切れなく 1 なので、連続バーストできている。
なるほど、これは凄い。こんな凄い性能が出せたんだ、Vivado HLS のAXI4 Master 。

IP 化を行う際に、Viavdo synthesis にチェックを入れて、論理合成後にどうなるか?のチェックを行った。
lap_filter4_7_160112.png

結果はTiming not met だった。
lap_filter4_8_160112.png

それでは、Target を 9 ns に変更して、もう一度、C コードの合成をしてみよう。
lap_filter4_9_160112.png

合成後のレポートはEstimated が 9.40 ns なので、これで 10 ns は大丈夫だろう。
lap_filter4_10_160112.png

以前の合成レポートと比べてみると、Iteration Latency が 8 クロックから 9 クロックになって、Latency が 1 クロック増えた。他は変更がない。パイプライン段数が 1 増えたので、レイテンシは 1 クロック増えたが、パイプラインされているので、影響はそれだけだ。

C/RTL協調シミュレーションは同じクロック数だった。
lap_filter4_11_160112.png

再度、論理合成レポート付きでIP 化を行った。
lap_filter4_12_160112.png

今度は成功。これで問題ないだろうと思う。

lap_fitlter4.c を貼っておく。

/* * lap_filter4.c * *  Created on: 2017/01/10 *      Author: Masaaki */

#include <stdio.h>
#include <string.h>

//#define HORIZONTAL_PIXEL_WIDTH    64
//#define VERTICAL_PIXEL_WIDTH    48
#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y(int rgb);

int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE])
{
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE m_axi depth=480000 port=lap_fb
#pragma HLS INTERFACE m_axi depth=480000 port=cam_fb

    int line_buf[3][HORIZONTAL_PIXEL_WIDTH];
#pragma HLS array_partition variable=line_buf block factor=3 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    int lap_fil_val;
    int pix, lap;

    int pix_mat[3][3];
#pragma HLS array_partition variable=pix_mat complete

    for (int y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        for (int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE
            pix = cam_fb[y*HORIZONTAL_PIXEL_WIDTH+x];

            for (int k=0; k<3; k++){
                for (int m=0; m<2; m++){
#pragma HLS UNROLL
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int y_val = conv_rgb2y(pix);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

            if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので0とする
                lap = 0;

            lap_fb[y*HORIZONTAL_PIXEL_WIDTH+x] = lap;
        }
    }

    return 0;
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}


lap_filter_tb.c を貼っておく。

// Testbench of laplacian_filter.c
// lap_filter_tb.c
// BMPデータをハードウェアとソフトウェアで、ラプラシアン・フィルタを掛けて、それを比較する
// m_axi offset=slave version
// 2015/08/26 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include "bmp_header.h"

int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y_soft(int rgb);
int lap_filter_axim(int *cam_fb, int *lap_fb);    // hardware
void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height); // software

int main()
{
    int *s, *h;
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *hw_lapd, *sw_lapd;
    int blue, green, red;
    char blue_c, green_c, red_c;
    struct timeval start_time_hw, end_time_hw;
    struct timeval start_time_sw, end_time_sw;

    if ((fbmpr = fopen("test.bmp""rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.bmp by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(char), 2, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(long), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((hw_lapd =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_lapd memory\n");
        exit(1);
    }
    if ((sw_lapd =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate sw_lapd memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    //gettimeofday(&start_time_hw, NULL);
    lap_filter_axim((int *)rd_bmp, (int *)hw_lapd);    // ハードウェアのラプラシアン・フィルタ
    //gettimeofday(&end_time_hw, NULL);

    //gettimeofday(&start_time_sw, NULL);
    laplacian_filter_soft(rd_bmp, sw_lapd, bmpihr.biWidth, bmpihr.biHeight);    // ソフトウェアのラプラシアン・フィルタ
    //gettimeofday(&end_time_sw, NULL);

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0, h=hw_lapd, s=sw_lapd; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            if (*h != *s){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, *h, *s);
                return(1);
            } else {
                h++;
                s++;
            }
        }
    }
    printf("Success HW and SW results match\n");
    /*if (end_time_hw.tv_usec < start_time_hw.tv_usec) {        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec - 1, 1000000 + end_time_hw.tv_usec - start_time_hw.tv_usec);    } else {        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec, end_time_hw.tv_usec - start_time_hw.tv_usec);    }    if (end_time_sw.tv_usec < start_time_sw.tv_usec) {        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec - 1, 1000000 + end_time_sw.tv_usec - start_time_sw.tv_usec);    } else {        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec, end_time_sw.tv_usec - start_time_sw.tv_usec);    } */

    // ハードウェアのラプラシアンフィルタの結果を temp_lap.bmp へ出力する
    if ((fbmpw=fopen("temp_lap.bmp""wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.bmp by binary write mode\n");
        exit(1);
    }
    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

    // RGB データの書き込み、逆順にする
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    free(rd_bmp);
    free(hw_lapd);
    free(sw_lapd);

    return(0);
}

void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height)
{
    int line_buf[3][800];

    int lap_fil_val;
    int pix, lap;

    int pix_mat[3][3];

    for (int y=0; y<height; y++){
        for (int x=0; x<width; x++){
            pix = cam_fb[y*width+x];

            for (int k=0; k<3; k++){
                for (int m=0; m<2; m++){
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int y_val = conv_rgb2y(pix);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

            if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので0とする
                lap = 0;

            lap_fb[y*width+x] = lap;
        }
    }
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}

  1. 2017年01月13日 04:51 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS のソースコードをSDx で試す4(AXI4-Stream向きのコード)

Vivado HLS のソースコードをSDx で試す3(AXI4-Stream向きのコード)”の続き。

前回は、Vivado HLS で使用していたAXI4-Stream のソースコードをSDSoC 用のサイドチャネル付きAXI4-Stream に対応するソースコードにうまく変換できなかったため、配列渡しとして書き換えた上でAXI4-Stream として実装するプラグマを与えたら、理論上の限界値に近い実行性能が出た。今回は、同じソースコードで、SDSoCのプラグマを変更して実行性能を見ていこう。最初にSDS data zero_copy を与えてみた。

lap_filter4.c にSDS data zero_copy を追加した。
SDx_v2016_3_134_170111.png

Relesase でビルドを行った。
成功したので、Vivado のレポートを見た。
SDx_v2016_3_135_170111.png

前回よりもリソース使用量が少ない。前回のレポートを示す。
SDx_v2016_3_127_170111.png

ブロックデザインを示す。
SDx_v2016_3_136_170111.png

lap_filter_axim はAXI4 Master となった。シンプルな構成になっている。

Vivado HLS のSynthesis Report を示す。
SDx_v2016_3_137_170111.png

次に、実際にZYBO で確かめてみよう。
workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt./lap_filter4.elf を実行した。
SDx_v2016_3_138_170111.png

ソフトウェアの実行時間の下5回の平均は、約 47.7 ms だった。
ハードウエアの実行時間の下5回の平均は、約 6.04 ms だった。
ハードウェアの実行時間/ソフトウェアの実行時間 ≒ 0.127倍、つまり、ハードウェアの性能はソフトウェアの約 7.90 倍ということになった。前回の約 8.61 倍ほどではないが、リソース使用量が少ないのに、相当な高性能だ。
こうなると、Vivado HLS で生成したAXI4 Master の回路はDMAのRead とWrite が重なり合っているとしか考えられないのだが、本当だろうか?一度、このコードのままVivado HLS 2016.3 でコードを合成してみよう。
  1. 2017年01月12日 05:33 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS のソースコードをSDx で試す3(AXI4-Stream向きのコード)

Vivado HLS のソースコードをSDx で試す2(memcpy() を使った第3段階のコード)”の続き。

次は、Vivado HLS で試したAXI4-Stream のソースコードを試してみようと思った。そこで、”hls::stream + axiu + SDSoC の実験”を参考にさせて頂いて、

#ifdef __SDSVHLS__

を定義したりして、いろいろと奮闘したのだが、力及ばずに、うまく行かなかった。
そこで考え直してみると、画像のピクセルがその順番でシーケンシャルで処理することができれば、

#pragma SDS data access_pattern(cam_fb:SEQUENTIAL, lap_fb:SEQUENTIAL)

を指定すれば、AXI4-Stream になるはずでは?ということで、cam_fb と lap_fb を配列にして、画像のピクセルが順番通りに処理できるように書いてみた。

SDx 2016.3 のプロジェクト名はlap_filter4 とした。
SDx_v2016_3_126_170110.png

lap_fitler_axim() 関数をハードウェア化に指定して、ビルドを行った。
Vivado のレポートを見た。
LUT が 41 % 、FF が 32 % でロジックが多い。DSP は 1 % と少ない。
SDx_v2016_3_127_170111.png

ブロックデザインを示す。
SDx_v2016_3_128_170111.png

Vivado HLS で合成されたlap_filter_axim_1 はFIFO インターフェースになっていた。ストリームの回路図になっているようだ。

Vivado HLS のSynthesis Report を示す。
SDx_v2016_3_129_170111.png

Latency は 480006 で、800 x 600 ピクセルの総数 480000 から 6 増えているだけだ。優秀と言えると思う。

次に、実際にZYBO で確かめてみよう。
workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt./lap_filter4.elf を実行した。
SDx_v2016_3_130_170111.png

ソフトウェアの実行時間の下5回の平均は、約 47.7 ms だった。
ハードウエアの実行時間の下5回の平均は、約 5.54 ms だった。
ハードウェアの実行時間/ソフトウェアの実行時間 ≒ 0.116 倍、つまり、ハードウェアの性能はソフトウェアの約 8.61 倍ということになった。
ハードウェアの実行時間の 5.54 ms はとっても速い。理論上の限界は、800 ピクセル X 600 行 X クロック周期 10 ns = 4.8 ms なので、5.54 / 4.8 ≒ 1.15 倍なので、理論限界値の 1.15 倍なので、とっても性能が出ていると言える。
4.8 ms は、Write のDMA とRead のDMA が重なり合って、Waitが 0 でDMA出来ている時に出る値なので、とっても凄い。

次に、AXI_HP ポートを使用したときにどのくらいの性能が出るか?確かめてみた。
次の2つのプラグマをlap_filter_axim() 関数の前に挿入した。

#pragma SDS data access_pattern(cam_fb:SEQUENTIAL, lap_fb:SEQUENTIAL)
#pragma SDS data sys_port(cam_fb:AFI, lap_fb:AFI)


Vivado の合成結果とVivado HLS の合成結果に違いは無い。

Vivado のブロックデザインを示す。使用しているポートがAXI_ACP から AXI_HP ポートに変更になっている。
SDx_v2016_3_131_170111.png

ZYBOで確かめてみた。
SDx_v2016_3_132_170111.png 

その結果、ソフトウェアの実行時間の下5回の平均は、約 47.4 ms だった。
ハードウエアの実行時間の下5回の平均は、約 16.0 ms だった。
ハードウェアの実行時間/ソフトウェアの実行時間 ≒ 0.338 倍、つまり、ハードウェアの性能はソフトウェアの約 2.96 倍ということになった。
先ほどのAXI_ACP ポートを使用するよりも遅くなっている。キャッシュのコントロールをしているからなのか?

最後にキャッシュをNON_CACHEABLE にしてやってみた。

#pragma SDS data access_pattern(cam_fb:SEQUENTIAL, lap_fb:SEQUENTIAL)
#pragma SDS data sys_port(cam_fb:AFI, lap_fb:AFI)
#pragma SDS data mem_attribute(cam_fb:NON_CACHEABLE, lap_fb:NON_CACHEABL)


ビルドを行った。

Vivado の合成結果、ブロックデザイン、Vivado HLS の合成結果に違いは無かった。

ZYBO 上で lap_filter4.elf を実行した。
SDx_v2016_3_133_170111.png

ソフトウェアの実行時間の下5回の平均は、約 46.9 ms だった。
ハードウエアの実行時間の下5回の平均は、約 16.1 ms だった。
ハードウェアの実行時間/ソフトウェアの実行時間 ≒ 0.343 倍、つまり、ハードウェアの性能はソフトウェアの約 2.91 倍ということになった。
キャッシュありと性能はほとんど違わない。どうしてだろうか?

最後にソースコードを貼っておく。

lap_filter4.c を貼っておく。

/* * lap_filter4.c * *  Created on: 2017/01/10 *      Author: Masaaki */

#include <stdio.h>
#include <string.h>

//#define HORIZONTAL_PIXEL_WIDTH    64
//#define VERTICAL_PIXEL_WIDTH    48
#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y(int rgb);

#pragma SDS data access_pattern(cam_fb:SEQUENTIAL, lap_fb:SEQUENTIAL)
//#pragma SDS data sys_port(cam_fb:AFI, lap_fb:AFI)
//#pragma SDS data mem_attribute(cam_fb:NON_CACHEABLE, lap_fb:NON_CACHEABLE)
int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE])
{
    int line_buf[3][HORIZONTAL_PIXEL_WIDTH];
#pragma HLS array_partition variable=line_buf block factor=3 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    int lap_fil_val;
    int pix, lap;

    int pix_mat[3][3];
#pragma HLS array_partition variable=pix_mat complete

    for (int y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        for (int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE
            pix = cam_fb[y*HORIZONTAL_PIXEL_WIDTH+x];

            for (int k=0; k<3; k++){
                for (int m=0; m<2; m++){
#pragma HLS UNROLL
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int y_val = conv_rgb2y(pix);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

            if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので0とする
                lap = 0;

            lap_fb[y*HORIZONTAL_PIXEL_WIDTH+x] = lap;
        }
    }

    return 0;
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}


lap_filter_tb.c を貼っておく。

// Testbench of laplacian_filter.c
// lap_filter_tb.c
// BMPデータをハードウェアとソフトウェアで、ラプラシアン・フィルタを掛けて、それを比較する
// m_axi offset=slave version
// 2015/08/26 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include "bmp_header.h"

int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y_soft(int rgb);
int lap_filter_axim(int *cam_fb, int *lap_fb);    // hardware
void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height); // software

int main()
{
    int *s, *h;
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *hw_lapd, *sw_lapd;
    int blue, green, red;
    char blue_c, green_c, red_c;
    struct timeval start_time_hw, end_time_hw;
    struct timeval start_time_sw, end_time_sw;

    if ((fbmpr = fopen("test.bmp""rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.bmp by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(char), 2, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(long), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int *)sds_alloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((hw_lapd =(int *)sds_alloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_lapd memory\n");
        exit(1);
    }
    if ((sw_lapd =(int *)sds_alloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate sw_lapd memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    gettimeofday(&start_time_hw, NULL);
    lap_filter_axim((int *)rd_bmp, (int *)hw_lapd);    // ハードウェアのラプラシアン・フィルタ
    gettimeofday(&end_time_hw, NULL);

    gettimeofday(&start_time_sw, NULL);
    laplacian_filter_soft(rd_bmp, sw_lapd, bmpihr.biWidth, bmpihr.biHeight);    // ソフトウェアのラプラシアン・フィルタ
    gettimeofday(&end_time_sw, NULL);

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0, h=hw_lapd, s=sw_lapd; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            if (*h != *s){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, *h, *s);
                return(1);
            } else {
                h++;
                s++;
            }
        }
    }
    printf("Success HW and SW results match\n");
    if (end_time_hw.tv_usec < start_time_hw.tv_usec) {
        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec - 11000000 + end_time_hw.tv_usec - start_time_hw.tv_usec);
    } else {
        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec, end_time_hw.tv_usec - start_time_hw.tv_usec);
    }
    if (end_time_sw.tv_usec < start_time_sw.tv_usec) {
        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec - 11000000 + end_time_sw.tv_usec - start_time_sw.tv_usec);
    } else {
        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec, end_time_sw.tv_usec - start_time_sw.tv_usec);
    }

    // ハードウェアのラプラシアンフィルタの結果を temp_lap.bmp へ出力する
    if ((fbmpw=fopen("temp_lap.bmp""wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.bmp by binary write mode\n");
        exit(1);
    }
    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

    // RGB データの書き込み、逆順にする
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    sds_free(rd_bmp);
    sds_free(hw_lapd);
    sds_free(sw_lapd);

    return(0);
}

void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height)
{
    int line_buf[3][800];

    int lap_fil_val;
    int pix, lap;

    int pix_mat[3][3];

    for (int y=0; y<height; y++){
        for (int x=0; x<width; x++){
            pix = cam_fb[y*width+x];

            for (int k=0; k<3; k++){
                for (int m=0; m<2; m++){
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int y_val = conv_rgb2y(pix);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

            if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので0とする
                lap = 0;

            lap_fb[y*width+x] = lap;
        }
    }
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}

  1. 2017年01月11日 05:11 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS のソースコードをSDx で試す2(memcpy() を使った第3段階のコード)

Vivado HLS のソースコードをSDx で試す1(memcpy() を使った第2段階のコード)”の続き。

前回はViavdo HLS のソースコードをわずかに変更しただけのAXI4 Master のラプラシアンフィルタをSDSoC でビルドすることができた。今回は前回のよりも性能が良いmemcpy() を使った第3段階のコードをSDSoC でビルドしてみよう。

lap_filter3 プロジェクトを作成し、”Vivado HLS勉強会4(AXI4 Master)を公開しました”のlap_filter_tb.c と laplacian_filter3.c の中身を SDSoC の lap_filter3 プロジェクトにコピーした。
volatile は付いているとエラーになるので外した。
SDx_v2016_3_121_170108.png

lap_fitler_axim() 関数をハードウェア化に指定して、ビルドを行った。
Vivado のレポートを見た。
BRAMは前回 13 % のところ、12 % で減っている。DSP は 6 % から 14 % で大幅に増えている。演算器を増やしているので当然そうなる。
SDx_v2016_3_122_170108.png

ブロックデザインを示す。これは前回と一緒だ。
SDx_v2016_3_123_170108.png

Vivado HLS のSynthesis Report を示す。
SDx_v2016_3_124_170108.png

タイミングはエラーになっているが、Vivado でインプリメントすると大丈夫のようだ。これで、動作周波数が取れない場合はどうすれば良いのかな?そうだ、SDSoC で動作周波数を設定するところがあったので、それを変更すれば良いのだろう?

次に、実際にZYBO で確かめてみよう。
workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt./lap_filter3.elf を実行した。
SDx_v2016_3_125_170108.png

ソフトウェアの実行時間の下5回の平均は、約 51.8 ms だった。
ハードウエアの実行時間の下5回の平均は、約 14.9 ms だった。
ハードウェアの実行時間/ソフトウェアの実行時間 ≒ 0.29 倍、つまり、ハードウェアの性能はソフトウェアの約 3.48 倍ということになった。
やはり、ハードウエアの実行時間のほうが短くなった。

laplacian_filter3.c を貼っておく。

// laplacian_filter3.c
// m_axi offset=slave version
// 2015/08/26
//

#include <stdio.h>
#include <string.h>

//#define HORIZONTAL_PIXEL_WIDTH    64
//#define VERTICAL_PIXEL_WIDTH    48
#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y(int rgb);

int lap_filter_axim(int *cam_fb, int *lap_fb)
{
    #pragma HLS INTERFACE s_axilite port=return

#pragma HLS INTERFACE m_axi depth=3072 port=cam_fb offset=slave
#pragma HLS INTERFACE m_axi depth=3072 port=lap_fb offset=slave

    int line_buf[3][HORIZONTAL_PIXEL_WIDTH];
#pragma HLS array_partition variable=line_buf block factor=3 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    int lap_buf[HORIZONTAL_PIXEL_WIDTH];
    int x, y;
    int lap_fil_val;
    int a, b;
    int fl, sl, tl;
    int line_sel;
    int prev[3],current[3],next[3];    // 0->1ライン目, 1->2ライン目, 2->3ライン目, prev->1pixel前, current->現在, next->次pixel
#pragma HLS array_partition variable=prev complete dim=0
#pragma HLS array_partition variable=current complete dim=0
#pragma HLS array_partition variable=next complete dim=0


    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    Loop0: for (y=0, line_sel=0; y<VERTICAL_PIXEL_WIDTH-1; y++){
        // 最初のライン, y=1 012, y=2 120, y=3 201, y=4 012
        switch(line_sel){
            case 1 :
                fl = 0; sl = 1; tl = 2;
                break;
            case 2 :
                fl = 1; sl = 2; tl = 0;
                break;
            case 3 :
                fl = 2; sl = 0; tl = 1;
                break;
            default :
                fl = 0; sl = 1; tl = 2;
        }

        if (y == 1){
            Loop1: for (a=0; a<3; a++){
 // 3ライン分
                memcpy(line_buf[a], (const int*)&cam_fb[a*(HORIZONTAL_PIXEL_WIDTH)], HORIZONTAL_PIXEL_WIDTH*sizeof(int));
            }
        }else// 最初のラインではないので、1ラインだけ読み込む。すでに他の2ラインは読み込まれている
            memcpy(line_buf[tl], (const int*)&cam_fb[(y+1)*(HORIZONTAL_PIXEL_WIDTH)], HORIZONTAL_PIXEL_WIDTH*sizeof(int));
        }
        if (y==0 || y==VERTICAL_PIXEL_WIDTH-1){
            Loop2: for(b=0; b<HORIZONTAL_PIXEL_WIDTH; b++){
                lap_buf[b] = 0;
            }
        } else {
            next[0] = conv_rgb2y(line_buf[fl][0]);
            next[1] = conv_rgb2y(line_buf[sl][0]);
            next[2] = conv_rgb2y(line_buf[tl][0]);

            Loop3: for (x = 0; x < HORIZONTAL_PIXEL_WIDTH; x++){
                if (x == 0 || x == HORIZONTAL_PIXEL_WIDTH-1){
                    lap_fil_val = 0;

                    current[0] = next[0];
                    next[0] = conv_rgb2y(line_buf[fl][1]);

                    current[1] = next[1];
                    next[1] = conv_rgb2y(line_buf[sl][1]);

                    current[2] = next[2];
                    next[2] = conv_rgb2y(line_buf[tl][1]);
                }else{
                    prev[0] = current[0];
                    current[0] = next[0];
                    next[0] = conv_rgb2y(line_buf[fl][x+1]);

                    prev[1] = current[1];
                    current[1] = next[1];
                    next[1] = conv_rgb2y(line_buf[sl][x+1]);

                    prev[2] = current[2];
                    current[2] = next[2];
                    next[2] = conv_rgb2y(line_buf[tl][x+1]);
#pragma HLS PIPELINE II=1
                    lap_fil_val = laplacian_fil(prev[0], current[0], next[0],
                                                prev[1], current[1], next[1],
                                                prev[2], current[2], next[2]);
                }
                lap_buf[x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる
            }
        }
        memcpy((int*)&lap_fb[y*(HORIZONTAL_PIXEL_WIDTH)], (const int*)lap_buf, HORIZONTAL_PIXEL_WIDTH*sizeof(int));

        line_sel++;
        if (line_sel > 3){
            line_sel = 1;
        }
    }

    return(0);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : line[sl]oat を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}

  1. 2017年01月09日 07:46 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS のソースコードをSDx で試す1(memcpy() を使った第2段階のコード)

これまでの4つのSDSoCの記事

SDx 2016.3 のプラグマによるハードウェアと性能の違い1
SDx 2016.3 のプラグマによるハードウェアと性能の違い2
SDx 2016.3 のプラグマによるハードウェアと性能の違い3
SDx 2016.3 のプラグマによるハードウェアと性能の違い4

と過去の記事(”SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した1(ソースの公開)”~”SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した8(ハードウェア化5)”まで、SDSoCのカテゴリを参照のこと)はlap_filter2.c のline_buf と lap_buf の2つのバッファの大きさを間違えていた。それで、Block RAM が大きくなりすぎて、Block RAM の容量をオーバーしていたことが分かった。

2 つのラインバッファはこう宣言されていたが、

unsigned int line_buf[3][ALL_PIXEL_VALUE];
unsigned int lap_buf[ALL_PIXEL_VALUE];

これだと、ALL_PIXEL_VALUEは画像すべてのピクセルということなので、取りすぎている。従って、

unsigned int line_buf[3][HORIZONTAL_PIXEL_WIDTH];
unsigned int lap_buf[HORIZONTAL_PIXEL_WIDTH];

に修正した。

これだと、800 x 600 ピクセルにしても問題なくビルドすることができた。しかも、Vivado HLS のINTERFACE 指示子を指定しても問題ないようだったので、ということはVivado HLS のプロジェクトをそのまま持ってきてもできるのか?という期待が高まってきた。

と言う訳で、”Vivado HLS勉強会4(AXI4 Master)を公開しました”のlap_filter_tb.c と laplacian_filter2.c の中身を SDSoC の lap_filter2 プロジェクトのファイルにコピーした。

まずは、volatile は付いているとエラーになるので外した。
SDx_v2016_3_105_170108.png

これで、ビルドすると正常終了は下のだが、Vivado を立ち上げると、lap_filter_axim_1 のAXI4 マスタポートが接続されていない。
SDx_v2016_3_106_170108.png

どうやら bundle オプションがダメなようなので、これを外した。
SDx_v2016_3_107_170108.png

ビルドして、ブロックデザインを見てみると、今度はちゃんとつながっていた。やはり、bundle オプションを付けてはいけないようだ。
SDx_v2016_3_109_170108.png

Vivado のレポートを示す。
SDx_v2016_3_108_170108.png

Vivado HLS のSynthesis Report を示す。
SDx_v2016_3_110_170108.png

かなりまともな結果になっているのではないだろうか?というかVivado HLS そのものなんだけどね。。。

次に、実際にZYBO で確かめてみよう。
workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt./lap_filter2.elf を実行した。
SDx_v2016_3_111_170108.png

残念ながら、sds_alloc を使ってね。という結果だった。

と言う訳で、lap_filter_tb.c の malloc() を sds_alloc() に変更した。
SDx_v2016_3_112_170108.png

ついでに free() も sds_free() に変更した。
SDx_v2016_3_113_170108.png

Vivado のレポートとブロックデザイン、Vivado HLS の合成レポートに変化はなかった。

ZYBO で動作を確認すると、今度は問題なく動作したが、ソフトウェアとハードウェアの実行時間が入っていなかった。
SDx_v2016_3_114_170108.png

このわずかの修正でVivado HLS のプロジェクトファイルがSDSoC で完成品になって動くということが分かった。衝撃的な事実だ。
これはとっても便利だと思う。うまくプラットフォームを作れれば、デバイスツリーを作る手間も、アプリケーションを作る手間も省くことができる。ただ、同じハードウェアを複数のソフトウェアで使いまわす方法がわからない。同じハードをコピーして、ソフト部分だけ変えれば良いのだが、ハードウェアを作り直しとなると時間がかかってやっていられなくなってしまう。

時間を計測するためにgettimeofday() 関連のコードを追加した。
SDx_v2016_3_115_170108.png

ビルドが終了した。Micro SD カードにコピー&ペーストするのだが、毎回ZYBO から取り出してリーダー・ライターに挿入するのが煩わしい。そこで ifconfig でIP アドレスを特定して、WinSCP でSFTP することにした。
SDx_v2016_3_116_170108.png

WinSCP でZYBO の IP アドレスにログインして、ビルドしたSD カードの内容をZYBO の/mnt 以下にコピー&ペーストした。
因みにID は root 、パスワードは root でログインできた。
SDx_v2016_3_117_170108.png

その結果、アップデートできたが、新規作成したディレクトリはZYBO の日付になってしまった。
SDx_v2016_3_118_170108.png

ブート用のファイルが書き換わったので、reboot したが、シャットダウンしたところで止まってしまった。シャットダウンだけして、電源OFF/ON の方が良さそうだ。

さて、ソフトウェアを実行すると、ハードウエアの実行時間とソフトウェアの実行時間が計測できた。
SDx_v2016_3_119_170108.png

ソフトウェアの実行時間の下5回の平均は、約 51.7 ms だった。
ハードウエアの実行時間の下5回の平均は、約 55.7 ms だった。
ハードウェアの実行時間/ソフトウェアの実行時間 ≒ 1.08 倍、つまり、ハードウェアの性能はソフトウェアの 0.928 倍ということになった。

ラプラシアンフィルタ処理後のファイル temp_lap.bmp を貼っておく。
SDx_v2016_3_120_170108.png

現在の lap_filter2.c を貼っておく。

// laplacian_filter2.c
// lap_filter_axim()
// m_axi offset=slave version
// 2015/08/26 by marsee
//

#include <stdio.h>
#include <string.h>

//#define HORIZONTAL_PIXEL_WIDTH    64
//#define VERTICAL_PIXEL_WIDTH    48
#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y(int rgb);

//#pragma SDS data zero_copy(cam_fb[0:ALL_PIXEL_VALUE])
//#pragma SDS data zero_copy(lap_fb[0:ALL_PIXEL_VALUE])
//#pragma SDS data copy(cam_fb[0:ALL_PIXEL_VALUE])
//#pragma SDS data copy(lap_fb[0:ALL_PIXEL_VALUE])
//#pragma SDS data access_pattern(cam_fb:RANDOM, lap_fb:RANDOM)
//#pragma SDS data access_pattern(cam_fb:SEQUENTIAL, lap_fb:SEQUENTIAL)
//#pragma SDS data sys_port(cam_fb:AFI, lap_fb:AFI)
int lap_filter_axim(int *cam_fb, int *lap_fb)
{
#pragma HLS INTERFACE s_axilite port=return

#pragma HLS INTERFACE m_axi depth=3072 port=cam_fb offset=slave
#pragma HLS INTERFACE m_axi depth=3072 port=lap_fb offset=slave

    int line_buf[3][HORIZONTAL_PIXEL_WIDTH];
    int lap_buf[HORIZONTAL_PIXEL_WIDTH];
    int x, y;
    int lap_fil_val;
    int a, b;
    int fl, sl, tl;
    int line_sel;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    Loop0: for (y=0, line_sel=0; y<VERTICAL_PIXEL_WIDTH; y++){
        // 最初のライン, y=1 012, y=2 120, y=3 201, y=4 012
        switch(line_sel){
            case 1 :
                fl = 0; sl = 1; tl = 2;
                break;
            case 2 :
                fl = 1; sl = 2; tl = 0;
                break;
            case 3 :
                fl = 2; sl = 0; tl = 1;
                break;
            default :
                fl = 0; sl = 1; tl = 2;
        }

        Loop1: for (x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
            if (y==0 || y==VERTICAL_PIXEL_WIDTH-1){ // 縦の境界の時の値は0とする
                lap_fil_val = 0;
            }else if (x==0 || x==HORIZONTAL_PIXEL_WIDTH-1){ // 横の境界の時も値は0とする
                lap_fil_val = 0;
            }else{
                 if (x == 1){ // ラインの最初でラインの画素を読み出す
                    if (y == 1){ // 最初のラインでは3ライン分の画素を読み出す
                        Loop2: for (a=0; a<3; a++){ // 3ライン分
                            memcpy(&line_buf[a][0], (const int*)&cam_fb[a*(HORIZONTAL_PIXEL_WIDTH)], HORIZONTAL_PIXEL_WIDTH*sizeof(int));
                            Loop3: for (b=0; b<HORIZONTAL_PIXEL_WIDTH; b++){
#pragma HLS PIPELINE
 // ライン
                                line_buf[a][b] = conv_rgb2y(line_buf[a][b]);    // カラーから白黒へ
                            }
                        }
                    } else { // 最初のラインではないので、1ラインだけ読み込む。すでに他の2ラインは読み込まれている
                         memcpy(line_buf[tl], (const int*)&cam_fb[(y+1)*(HORIZONTAL_PIXEL_WIDTH)], HORIZONTAL_PIXEL_WIDTH*sizeof(int));
                        Loop4: for (b=0; b<HORIZONTAL_PIXEL_WIDTH; b++){
#pragma HLS PIPELINE
 // ライン
                            line_buf[tl][b] = conv_rgb2y(line_buf[tl][b]);    // カラーから白黒へ
                        }
                    }
                }
                lap_fil_val = laplacian_fil(line_buf[fl][x-1], line_buf[fl][x], line_buf[fl][x+1], line_buf[sl][x-1], line_buf[sl][x], line_buf[sl][x+1], line_buf[tl][x-1], line_buf[tl][x], line_buf[tl][x+1]);
            }
            lap_buf[x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる
        }
        memcpy(&((int *)lap_fb)[y*(HORIZONTAL_PIXEL_WIDTH)], (const int*)lap_buf, HORIZONTAL_PIXEL_WIDTH*sizeof(int));

        line_sel++;
        if (line_sel > 3){
            line_sel = 1;
        }
    }
    return(1);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}


現在の lap_fitler_tb.c を貼っておく。

// Testbench of laplacian_filter.c
// lap_filter_tb.c
// BMPデータをハードウェアとソフトウェアで、ラプラシアン・フィルタを掛けて、それを比較する
// m_axi offset=slave version
// 2015/08/26 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include "bmp_header.h"

int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y_soft(int rgb);
int lap_filter_axim(int *cam_fb, int *lap_fb);    // hardware
void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height); // software

int main()
{
    int *s, *h;
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *hw_lapd, *sw_lapd;
    int blue, green, red;
    char blue_c, green_c, red_c;
    struct timeval start_time_hw, end_time_hw;
    struct timeval start_time_sw, end_time_sw;

    if ((fbmpr = fopen("test.bmp""rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.bmp by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(char), 2, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(long), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int *)sds_alloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((hw_lapd =(int *)sds_alloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_lapd memory\n");
        exit(1);
    }
    if ((sw_lapd =(int *)sds_alloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate sw_lapd memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    gettimeofday(&start_time_hw, NULL);
    lap_filter_axim((int *)rd_bmp, (int *)hw_lapd);    // ハードウェアのラプラシアン・フィルタ
    gettimeofday(&end_time_hw, NULL);

    gettimeofday(&start_time_sw, NULL);
    laplacian_filter_soft(rd_bmp, sw_lapd, bmpihr.biWidth, bmpihr.biHeight);    // ソフトウェアのラプラシアン・フィルタ
    gettimeofday(&end_time_sw, NULL);

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0, h=hw_lapd, s=sw_lapd; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            if (*h != *s){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, *h, *s);
                return(1);
            } else {
                h++;
                s++;
            }
        }
    }
    printf("Success HW and SW results match\n");
    if (end_time_hw.tv_usec < start_time_hw.tv_usec) {
        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec - 11000000 + end_time_hw.tv_usec - start_time_hw.tv_usec);
    } else {
        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec, end_time_hw.tv_usec - start_time_hw.tv_usec);
    }
    if (end_time_sw.tv_usec < start_time_sw.tv_usec) {
        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec - 11000000 + end_time_sw.tv_usec - start_time_sw.tv_usec);
    } else {
        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec, end_time_sw.tv_usec - start_time_sw.tv_usec);
    }

    // ハードウェアのラプラシアンフィルタの結果を temp_lap.bmp へ出力する
    if ((fbmpw=fopen("temp_lap.bmp""wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.bmp by binary write mode\n");
        exit(1);
    }
    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

    // RGB データの書き込み、逆順にする
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    sds_free(rd_bmp);
    sds_free(hw_lapd);
    sds_free(sw_lapd);

    return(0);
}

void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height)
{
    int **line_buf;
    int *lap_buf;
    int x, y, i;
    int lap_fil_val;
    int a, b;
    int fl, sl, tl;

    // line_buf の1次元目の配列をアロケートする
    if ((line_buf =(int **)malloc(sizeof(int *) * 3)) == NULL){
        fprintf(stderr, "Can't allocate line_buf[3][]\n");
        exit(1);
    }

    // メモリをアロケートする
    for (i=0; i<3; i++){
        if ((line_buf[i]=(int *)malloc(sizeof(int) * width)) == NULL){
            fprintf(stderr, "Can't allocate line_buf[%d]\n", i);
            exit(1);
        }
    }

    if ((lap_buf=(int *)malloc(sizeof(int) * (width))) == NULL){
        fprintf(stderr, "Can't allocate lap_buf memory\n");
        exit(1);
    }

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<height; y++){
        for (x=0; x<width; x++){
            if (y==0 || y==height-1){ // 縦の境界の時の値は0とする
                lap_fil_val = 0;
            }else if (x==0 || x==width-1){ // 横の境界の時も値は0とする
                lap_fil_val = 0;
            }else{
                if (y == 1 && x == 1){ // 最初のラインの最初のピクセルでは2ライン分の画素を読み出す
                    for (a=0; a<2; a++){ // 2ライン分
                        for (b=0; b<width; b++){ // ライン
                            line_buf[a][b] = cam_fb[(a*width)+b];
                            line_buf[a][b] = conv_rgb2y_soft(line_buf[a][b]);
                        }
                    }
                }
                if (x == 1) {    // ラインの最初なので、2つのピクセルを読み込む
                    for (b=0; b<2; b++){ // ライン
                        line_buf[(y+1)%3][b] = cam_fb[((y+1)*width)+b];
                        // (y+1)%3 は、使用済みのラインがに読み込む、y=2 の時 line[0], y=3の時 line[1], y=4の時 line[2]
                        line_buf[(y+1)%3][b] = conv_rgb2y_soft(line_buf[(y+1)%3][b]);
                    }
                }

                // 1つのピクセルを読み込みながらラプラシアン・フィルタを実行する
                line_buf[(y+1)%3][x+1] = cam_fb[((y+1)*width)+(x+1)];
                // (y+1)%3 は、使用済みのラインがに読み込む、y=2 の時 line[0], y=3の時 line[1], y=4の時 line[2]
                line_buf[(y+1)%3][x+1] = conv_rgb2y_soft(line_buf[(y+1)%3][x+1]);

                fl = (y-1)%3;    // 最初のライン, y=1 012, y=2 120, y=3 201, y=4 012
                sl = y%3;        // 2番めのライン
                tl = (y+1)%3;    // 3番目のライン
                lap_fil_val = laplacian_fil_soft(line_buf[fl][x-1], line_buf[fl][x], line_buf[fl][x+1], line_buf[sl][x-1], line_buf[sl][x], line_buf[sl][x+1], line_buf[tl][x-1], line_buf[tl][x], line_buf[tl][x+1]);
            }
            // ラプラシアンフィルタ・データの書き込み
            lap_fb[(y*width)+x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
        }
    }
    free(lap_buf);
    for (i=0; i<3; i++)
        free(line_buf[i]);
    free(line_buf);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}


  1. 2017年01月08日 09:59 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDx 2016.3 のプラグマによるハードウェアと性能の違い4

SDx 2016.3 のプラグマによるハードウェアと性能の違い3”の続き。

前回は、SDS data access_pattern にRANDOM を指定したときと、以前 SDS data zero_copy を指定したら sds_alloc() を使用する必要があるとのことなので、やってみた。今回は、ハードウェア化する関数 lap_filter_axim() の書き方をVivado HLS でやったように memcpy() で書き換えてどのくらいハードウェアが速くなるのかやってみた。

この記事は、”SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した7(ハードウェア化4)”の焼き直しなのだが、SDSoC の新しいバージョンでやってみることと、画像の大きさが大きくなっているという違いがある。さらにVivado HLS の結果を観察してみた。

まずは、lap_filter2.c を”SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した7(ハードウェア化4)”のmemcpy() を使用したバージョンに変更した。ただ単にコピー&ペーストしただけではなく、扱っている画像が違うので、そのあたりを修正しながらコピー&ペーストした。
SDx_v2016_3_95_170106.png

SDRelease でビルド後に生成された workspace\lap_filter2\SDRelease\_sds\p0\ipi のVivado 2016.3 プロジェクトを開いて結果を見た。
SDx_v2016_3_96_170106.png

BRAM の使用率が高く 68 % になっている。

ブロックデザインを示す。この辺りは変更がない。
SDx_v2016_3_97_170106.png

workspace\lap_filter2\SDRelease\_sds\vhls\lap_filter_axim にVivado HLS のプロジェクトがあるのでVivado HLS 2016.3 を起動して観察した。
SDx_v2016_3_98_170106.png

その結果、Source には lap_filter2.c がそのまま入っていた。これなら、Vivado HLS のプラグマ(指示子)が使える。

Synthesis Report を示す。
SDx_v2016_3_99_170106.png

レイテンシは?ばかりでTRIPCOUNT 指示子を指定する必要があるようだ。
BRAM を見ると、65 % を使用していて、Vivado HLS で全体で使用しているBRAM のほとんどを使用しているのがわかる。

次に、実際にZYBO で確かめてみよう。
workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt./lap_filter2.elf を実行した。
SDx_v2016_3_100_170106.png

その結果、ソフトウェアの実行時間は最初は943 us で少し時間がかかったがキャッシュがフィルされた2回目からは速くなっている。2回目からの5回の平均は 824 us だった。
ハードウェアの実行時間はソフトウェアの実行時間との実行時間の差がほとんどなくなった。2回目からの5回の平均は 942 us だった。
ハードウェアの実行時間/ソフトウェアの実行時間 = 約 1.14 倍、つまり、ハードウェアの性能はソフトウェアの約 0.87 倍ということになる。だいぶ差が縮まった。


さて、今のlap_filter2.c には、PIPELINE指示子が付加されているが、これを取ってしまうとどのくらいの性能低下があるかをやってみた。
PIPELINE指示子をコメントアウトした。
SDx_v2016_3_101_170106.png

リソース使用量に違いは無いみたいだ。パーセンテージでなく、絶対数で比べれば違いがあると思う。

Vivado HLS のSynthesis Report を示す。
SDx_v2016_3_103_170106.png

Loop 1.1.1.2、Loop1.1.3 のPipelined が no になっているのがわかる。

workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt./lap_filter2.elf を実行した。
SDx_v2016_3_104_170106.png

2回目からの5回のハードウェアの実行時間の平均は 1091 us だった。PIPELINE 指示子を入れたときの平均が 942 us なので、約 1.16 倍になった。

memcpy() を使用するために変更した lap_filter2.c を示す。

// laplacian_filter2.c
// lap_filter_axim()

#include <stdio.h>
#include <string.h>

//#define HORIZONTAL_PIXEL_WIDTH    64
//#define VERTICAL_PIXEL_WIDTH    48
#define HORIZONTAL_PIXEL_WIDTH    100
#define VERTICAL_PIXEL_WIDTH    75
//#define HORIZONTAL_PIXEL_WIDTH    200
//#define VERTICAL_PIXEL_WIDTH    150
//#define HORIZONTAL_PIXEL_WIDTH    800
//#define VERTICAL_PIXEL_WIDTH    600

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y(int rgb);

#pragma SDS data zero_copy(cam_fb)
#pragma SDS data zero_copy(lap_fb)
//#pragma SDS data access_pattern(cam_fb:RANDOM, lap_fb:RANDOM)
//#pragma SDS data access_pattern(cam_fb:SEQUENTIAL, lap_fb:SEQUENTIAL)
//#pragma SDS data sys_port(cam_fb:AFI, lap_fb:AFI)

int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE], int width, int height)
{
    unsigned int line_buf[3][ALL_PIXEL_VALUE];
    unsigned int lap_buf[ALL_PIXEL_VALUE];
    int x, y, i;
    int lap_fil_val;
    int a, b;
    int fl, sl, tl;
    int line_sel;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0, line_sel=0; y<height; y++){
        // 最初のライン, y=1 012, y=2 120, y=3 201, y=4 012
        switch(line_sel){
            case 1 :
                fl = 0; sl = 1; tl = 2;
                break;
            case 2 :
                fl = 1; sl = 2; tl = 0;
                break;
            case 3 :
                fl = 2; sl = 0; tl = 1;
                break;
            default :
                fl = 0; sl = 1; tl = 2;
        }
        for (x=0; x<width; x++){
            if (y==0 || y==height-1){ // 縦の境界の時の値は0とする
                lap_fil_val = 0;
            }else if (x==0 || x==width-1){ // 横の境界の時も値は0とする
                lap_fil_val = 0;
            }else{
                 if (x == 1){ // ラインの最初でラインの画素を読み出す
                    if (y == 1){ // 最初のラインでは3ライン分の画素を読み出す
                        for (a=0; a<3; a++){ // 3ライン分
                            memcpy(&line_buf[a][0], (const int*)(&cam_fb[a*(width)]), width*sizeof(int));
                            for (b=0; b<width; b++){
#pragma HLS PIPELINE
                                line_buf[a][b] = conv_rgb2y(line_buf[a][b]);    // カラーから白黒へ
                            }
                        }
                    } else { // 最初のラインではないので、1ラインだけ読み込む。すでに他の2ラインは読み込まれている
                         memcpy(line_buf[tl], (const int*)(&cam_fb[(y+1)*(width)]), width*sizeof(int));
                        for (b=0; b<width; b++){
#pragma HLS PIPELINE
                            line_buf[tl][b] = conv_rgb2y(line_buf[tl][b]);    // カラーから白黒へ
                        }
                    }
                }
                lap_fil_val = laplacian_fil(line_buf[fl][x-1], line_buf[fl][x], line_buf[fl][x+1], line_buf[sl][x-1], line_buf[sl][x], line_buf[sl][x+1], line_buf[tl][x-1], line_buf[tl][x], line_buf[tl][x+1]);
            }
            lap_buf[x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる
        }
        memcpy(&(lap_fb[y*width]), (const int*)(&lap_buf[0]), width*sizeof(int));

        line_sel++;
        if (line_sel > 3){
            line_sel = 1;
        }
    }
    return(0);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}

  1. 2017年01月07日 06:44 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

Zybot をステレオカメラにする2(カメラ・マウントの作製)

Zybot をステレオカメラにする1(カメラ・インターフェース基板の実装とテスト)”の続き。

ずいぶんと間が空いてしまったが、前回新しくI2Cリピーター付きのカメラ・インターフェース基板を作って、延長ケーブルを付けてカメラが正常に写った。今回は、カメラを付けたカメラ・インターフェース基板を搭載できるカメラ・マウントのモデルを3D CADで作成し、3Dプリンタで作製した。

最初に、FreeCAD でカメラ・インターフェース基板をZybot のベース・プレートに付けるためのカメラ・マウントのモデルを作成した。
stereo_cam_mounter_1_170105.png

次に、3Dプリンタで印刷した。充填率を 25 % にしたら、表面に細かい穴が空いてしまった。
stereo_cam_mounter_2_170106.jpg

次に充填率を 50 % にして印刷した。こちらは、細かい穴が目立たない。
stereo_cam_mounter_3_170106.jpg

2つ並べたところ。左が充填率 25 % で、右が充填率を 50 % だ。
stereo_cam_mounter_4_170106.jpg
stereo_cam_mounter_5_170106.jpg

やはり、充填率が 25 % だとボツボツなので、50 % のものをもう1つ作って使うことにした。

カメラ・インターフェース基板をカメラ・マウントに取り付けてみた。
stereo_cam_mounter_6_170106.jpg

こんな感じ。(Zybot のベース・プレートに付ける予定ですが、まだ付けていないです)
  1. 2017年01月06日 21:52 |
  2. Zybot
  3. | トラックバック:0
  4. | コメント:0

SDx 2016.3 のプラグマによるハードウェアと性能の違い3

SDx 2016.3 のプラグマによるハードウェアと性能の違い2”の続き。

前回は、SDS data access_pattern にSEQUENTIAL を指定したときのVivado のブロックデザインとその性能を調べた。今回は、SDS data access_pattern にRANDOM を指定したときと、以前 SDS data zero_copy を指定したら sds_alloc() を使用する必要があるとのことなので、やってみた。

lap_filter2.c の lap_filter_axim() の前に、

#pragma SDS data access_pattern(cam_fb:RANDOM, lap_fb:RANDOM)

を追加した。
SDx_v2016_3_87_170104.png

SDRelease でビルド後に生成された workspace\lap_filter2\SDRelease\_sds\p0\ipi のVivado 2016.3 プロジェクトを開いて結果を見た。
SDx_v2016_3_88_170104.png

このリソース使用量は、デフォルトの場合と同じだった。これがデフォルトの状態なのだと思う。

ブロックデザインを示す。
SDx_v2016_3_89_170104.png

デフォルトの場合と同じだ。これで検証は終わりにしよう。

次にSDS data zero_copy を指定したら sds_alloc() を使用する必要があるということなので、sds_alloc() を使用している”SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した6(ハードウェア化3)”の lap_filter_tb.c と一部を入れ替えた。
SDx_v2016_3_90_170105.png

lap_filter2.c の方には、SDS data zero_copy を記述した。
SDx_v2016_3_91_170105.png

SDRelease でビルド後に生成された workspace\lap_filter2\SDRelease\_sds\p0\ipi のVivado 2016.3 プロジェクトを開いて結果を見た。
SDx_v2016_3_92_170105.png

ブロックデザインを示す。
SDx_v2016_3_93_170105.png

workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt./lap_filter2.elf を実行した。
SDx_v2016_3_94_170105.png

この実装では、最初のハードウェアの実行時間が長いが、その後のハードウェアの実行時間の振れ幅が小さい。このくらいでないと使う気が起きないと思う。最初のハードウェアの実行では、キャッシュに読み込んでいないため実行時間が長くなったのかな?
ハードウェアの実行時間は約 3.23 ms で、ソフトウェアの実行時間は約 0.81 ms だった。
ハードウェアの実行時間/ソフトウェアの実行時間 = 約 4 倍、つまり、ハードウェアの性能はソフトウェアの 1/4 ということになる。

sds_alloc() を使用するように変更した lap_filter_tb.c を示す。

// Testbench of laplacian_filter.c
// BMPデータをハードウェアとソフトウェアで、ラプラシアン・フィルタを掛けて、それを比較する
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include "sds_lib.h"

#include "bmp_header.h"

//#define HORIZONTAL_PIXEL_WIDTH    64
//#define VERTICAL_PIXEL_WIDTH    48
#define HORIZONTAL_PIXEL_WIDTH    100
#define VERTICAL_PIXEL_WIDTH    75
//#define HORIZONTAL_PIXEL_WIDTH    200
//#define VERTICAL_PIXEL_WIDTH    150
//#define HORIZONTAL_PIXEL_WIDTH    800
//#define VERTICAL_PIXEL_WIDTH    600

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y_soft(int rgb);

int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE], int width, int height);    // hardware

void laplacian_filter_soft(volatile int *cam_fb, volatile int *lap_fb, long width, long height); // software

int main()
{
    int *s, *h;
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *hw_rd_bmp, *sw_rd_bmp, *hw_lapd, *sw_lapd;
    int blue, green, red;
    struct timeval start_time_hw, end_time_hw;
    struct timeval start_time_sw, end_time_sw;

    if ((fbmpr = fopen("test100x75.bmp""rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.bmp by binary read mode\n");
        exit(1);
    }

    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(char), 2, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(long), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((hw_rd_bmp =(int *)sds_alloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((sw_rd_bmp =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((hw_lapd =(int *)sds_alloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_lapd memory\n");
        exit(1);
    }
    //rd_bmp = (int *)sds_mmap((void *)(0x80000000), sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight), rd_bmp);
    //hw_lapd = (int *)sds_mmap((void *)(0x80000000+(ALL_PIXEL_VALUE*sizeof(int))), sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight), hw_lapd);
    if ((sw_lapd =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate sw_lapd memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            hw_rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
            sw_rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    //lap_filter_axim(rd_bmp, hw_lapd, (int)bmpihr.biWidth, (int)bmpihr.biHeight);    // ダミー実行(キャッシュを読み込む)

    gettimeofday(&start_time_hw, NULL);
    lap_filter_axim(hw_rd_bmp, hw_lapd, (int)bmpihr.biWidth, (int)bmpihr.biHeight);    // ハードウェアのラプラシアン・フィルタ
    gettimeofday(&end_time_hw, NULL);

    gettimeofday(&start_time_sw, NULL);
    laplacian_filter_soft(sw_rd_bmp, sw_lapd, bmpihr.biWidth, bmpihr.biHeight);    // ソフトウェアのラプラシアン・フィルタ
    gettimeofday(&end_time_sw, NULL);

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0, h=hw_lapd, s=sw_lapd; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            if (*h != *s){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, *h, *s);
                return(1);
            } else {
                h++;
                s++;
            }
        }
    }
    printf("Success HW and SW results match\n");
    if (end_time_hw.tv_usec < start_time_hw.tv_usec) {
        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec - 11000000 + end_time_hw.tv_usec - start_time_hw.tv_usec);
    }
    else {
        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec, end_time_hw.tv_usec - start_time_hw.tv_usec);
    }
    if (end_time_sw.tv_usec < start_time_sw.tv_usec) {
        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec - 11000000 + end_time_sw.tv_usec - start_time_sw.tv_usec);
    }
    else {
        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec, end_time_sw.tv_usec - start_time_sw.tv_usec);
    }

    // ハードウェアのラプラシアンフィルタの結果を temp_lap.bmp へ出力する
    if ((fbmpw=fopen("temp_lap.bmp""wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.bmp by binary write mode\n");
        exit(1);
    }
    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

    // RGB データの書き込み、逆順にする
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    if (hw_rd_bmp) sds_free(hw_rd_bmp);
    if (sw_rd_bmp) free(sw_rd_bmp);
    if (hw_lapd) sds_free(hw_lapd);
    if (sw_lapd) free(sw_lapd);

    return(0);
}

void laplacian_filter_soft(volatile int *cam_fb, volatile int *lap_fb, long width, long height)
{
    unsigned int **line_buf;
    unsigned int *lap_buf;
    int x, y, i;
    int lap_fil_val;
    int a, b;
    int fl, sl, tl;

    // line_buf の1次元目の配列をアロケートする
    if ((line_buf =(unsigned int **)malloc(sizeof(unsigned int *) * 3)) == NULL){
        fprintf(stderr, "Can't allocate line_buf[3][]\n");
        exit(1);
    }

    // メモリをアロケートする
    for (i=0; i<3; i++){
        if ((line_buf[i]=(unsigned int *)malloc(sizeof(unsigned int) * width)) == NULL){
            fprintf(stderr, "Can't allocate line_buf[%d]\n", i);
            exit(1);
        }
    }

    if ((lap_buf=(unsigned int *)malloc(sizeof(unsigned int) * (width))) == NULL){
        fprintf(stderr, "Can't allocate lap_buf memory\n");
        exit(1);
    }

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<height; y++){
        for (x=0; x<width; x++){
            if (y==0 || y==height-1){ // 縦の境界の時の値は0とする
                lap_fil_val = 0;
            }else if (x==0 || x==width-1){ // 横の境界の時も値は0とする
                lap_fil_val = 0;
            }else{
                if (y == 1 && x == 1){ // 最初のラインの最初のピクセルでは2ライン分の画素を読み出す
                    for (a=0; a<2; a++){ // 2ライン分
                        for (b=0; b<width; b++){ // ライン
                            line_buf[a][b] = cam_fb[(a*width)+b];
                            line_buf[a][b] = conv_rgb2y_soft(line_buf[a][b]);
                        }
                    }
                }
                if (x == 1) {    // ラインの最初なので、2つのピクセルを読み込む
                    for (b=0; b<2; b++){ // ライン
                        line_buf[(y+1)%3][b] = cam_fb[((y+1)*width)+b];
                        // (y+1)%3 は、使用済みのラインがに読み込む、y=2 の時 line[0], y=3の時 line[1], y=4の時 line[2]
                        line_buf[(y+1)%3][b] = conv_rgb2y_soft(line_buf[(y+1)%3][b]);
                    }
                }

                // 1つのピクセルを読み込みながらラプラシアン・フィルタを実行する
                line_buf[(y+1)%3][x+1] = cam_fb[((y+1)*width)+(x+1)];
                // (y+1)%3 は、使用済みのラインがに読み込む、y=2 の時 line[0], y=3の時 line[1], y=4の時 line[2]
                line_buf[(y+1)%3][x+1] = conv_rgb2y_soft(line_buf[(y+1)%3][x+1]);

                fl = (y-1)%3;    // 最初のライン, y=1 012, y=2 120, y=3 201, y=4 012
                sl = y%3;        // 2番めのライン
                tl = (y+1)%3;    // 3番目のライン
                lap_fil_val = laplacian_fil_soft(line_buf[fl][x-1], line_buf[fl][x], line_buf[fl][x+1], line_buf[sl][x-1], line_buf[sl][x], line_buf[sl][x+1], line_buf[tl][x-1], line_buf[tl][x], line_buf[tl][x+1]);
            }
            // ラプラシアンフィルタ・データの書き込み
            lap_fb[(y*width)+x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
        }
    }
    if(lap_buf) free(lap_buf);
    for (i=0; i<3; i++)
        if (line_buf[i]) free(line_buf[i]);
    if (line_buf) free(line_buf);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}

  1. 2017年01月06日 05:17 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDx 2016.3 のプラグマによるハードウェアと性能の違い2

SDx 2016.3 のプラグマによるハードウェアと性能の違い1”の続き。

前回は、ラプラシアンフィルタを使用して、デフォルトの場合とSDS data zero_copy プラグマを入れた状態でどのようにVivado のブロックデザインが異なるかとその性能を調べた。今回は、SDS data access_pattern にSEQUENTIAL を指定したときのVivado のブロックデザインとその性能を調べた。

まずは、lap_filter2.c の lap_filter_axim() の前に、

#pragma SDS data access_pattern(cam_fb:SEQUENTIAL, lap_fb:SEQUENTIAL)

を追加した。
SDx_v2016_3_77_170103.png

SDRelease でビルド後に生成された workspace\lap_filter2\SDRelease\_sds\p0\ipi のVivado 2016.3 プロジェクトを開いて結果を見た。
SDx_v2016_3_78_170103.png

前回のデフォルトの時と LUT, LUTRAM, FF は同じパーセンテージだが、BRAM が53 % から 33 % になっている。DSP も20 % が 13 % だった。

ブロックデザインを示す。
SDx_v2016_3_79_170104.png
SDx_v2016_3_80_170104.png

前回のデフォルトの時と同様の回路図だが、リソース使用量が違うので、どこか違うのだろうと思う。

workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt./lap_filter2.elf を実行した。
SDx_v2016_3_81_170104.png

性能的には前回のデフォルトの場合と変化がない。ソフトウェアに対してハードウェアが10倍程度遅いときもあれば、6倍程度遅いときもある。性能にむらがある。

性能にむらがあるのはACP ポートを使っているためかもしれないので、SDS data sys_port プラグマの AFI ポートを指定して、AXI_HP ポートを使うようにしてみよう。なお、AFI ポートはAXI_HP ポートのことを指すようだ。

lap_filter2.c の lap_filter_axim() の前に、

#pragma SDS data sys_port(cam_fb:AFI, lap_fb:AFI)

を追加した。
SDx_v2016_3_83_170104.png

SDRelease でビルド後に生成された workspace\lap_filter2\SDRelease\_sds\p0\ipi のVivado 2016.3 プロジェクトを開いて結果を見た。
SDx_v2016_3_84_170104.png

SDS data sys_port プラグマを指定する前と変化はない。

ブロックデザインを示す。
SDx_v2016_3_85_170104.png

使用しているPS のスレーブ・ポートがAXI_ACP ポートからAXI_HP ポートに変更になった。

workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt と ./lap_filter2.elf を実行した。
SDx_v2016_3_86_170104.png

やはり、性能的にはむらがあるのは変わらない。どうしてだろう?
  1. 2017年01月05日 04:49 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

特殊電子回路株式会社の基板年賀状が届きました

昨日、話題の特殊電子回路株式会社の基板年賀状が届きました。
なひたふさん、特殊電子回路株式会社で1回買っただけなのに申し訳ありません。ありがとうございます。
kiban_nengajyo_1_170104.jpg

kiban_nengajyo_2_170104.jpg

部品を購入して作ってみようと思います。
  1. 2017年01月04日 05:37 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:0

SDx 2016.3 のプラグマによるハードウェアと性能の違い1

おなじみのSDx 用ラプラシアンフィルタのプロジェクトを使用して、SDx 2016.3 のデータに関するプラグマを入れて、ハードウェアがどう変更されたのか?性能がどうなったのかを検証してみよう。

SDx 用ラプラシアンフィルタのプロジェクトを示す。lap_filter_axim() をハードウェアにするように指定してある。
SDx_v2016_3_65_170103.png

なお、ラプラシアンフィルタ処理を行う元画像は800 x 600 ピクセルの画像だと大きすぎて、プラグマを入れない状態でビルドできないので、100 x 75 ピクセルに縮小している。
SDx_v2016_3_82_170104.png

デフォルトの状態、つまり lap_filter_axim() のcam_fb, lap_fb の 2 つのポートに関するプラグマを入れない状態でビルドした。
SDRelease でビルド後に生成された workspace\lap_filter2\SDRelease\_sds\p0\ipi のVivado 2016.3 プロジェクトを開いて結果を見た。
SDx_v2016_3_67_170103.png

次にブロックデザインを示す。
SDx_v2016_3_68_170103.png
SDx_v2016_3_69_170103.png

Vivado HLS で合成された lap_filter_axim_1 があって、adapter(lap_filter_axim_1_if)が付いてる。さらに、axi_steram_router を通してDMA IP に接続されている。そしてそのDMAのPS 側の接続されているポートはS_AXI_ACP だった。

workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
SDx_v2016_3_70_170103.png

ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt ./lap_filter2.elf を実行した。
SDx_v2016_3_71_170103.png

どうもハードウェアの実行時間が定めらない。HW time は 4.5 ms 程度の場合も、 11.0 ms くらいの時もある。どうしてこんなに変動するのだろうか?

生成された temp_lap.bmp を見るとラプラシアンフィルタ処理がされているのがわかる。
SDx_v2016_3_72_170103.png

次は、SDS data zero_copy プラグマを入れてみよう。

#pragma SDS data zero_copy(cam_fb)
#pragma SDS data zero_copy(lap_fb)

を lap_filter_axim() の前に置いてみた。
SDx_v2016_3_73_170103.png

これでSDRelease でビルドを行った。SDx 2016.3 によって生成されたVivado 2016.3 を見た。結果を示す。
SDx_v2016_3_74_170103.png

Vivado 2016.3 のブロックデザインを示す。
SDx_v2016_3_75_170103.png

PS のM_AXI_GP0 からAXI Interconnect が接続されていて、そこに lap_filter_axim_1_if が接続され、Vivado HLS で生成された lap_filter_axim_1 が接続されている。
PS のM_AXI_GP0 でデータを供給するとなると性能がでないように思うがどうなのだろうか?

workspace\lap_filter2\SDRelease\sd_card の内容をMicro SD カードにコピーした。
ZYBO に挿入して電源ONした。
Linux が立ち上がった。
cd /mnt ./lap_filter2.elf を実行した。
SDx_v2016_3_76_170103.png

ERROR と表示されてしまった。zero copy datamover を使用するときは、sds_alloc でメモリを取る必要があるようだ。普通のmalloc() でメモリを取っている。malloc() だとMMU でのアドレス変換単位でアドレスが不連続になる可能性があるのでzero copy datamover はだめらしい。
後で、sds_alloc でメモリをアロケートして確かめてみよう。

最後の現在のソースコードを貼っておく。
まずは、bmp_header.h から。

/* * bmp_header.h * *  Created on: 2015/08/03 *      Author: Masaaki */
// bmp_header.h
// 2015/07/17 by Masaaki Ono
//
// BMP ファイルフォーマットから引用
// http://www.kk.iij4u.or.jp/~kondo/bmp/
//

#include <stdio.h>

#pragma once

#ifndef BMP_HEADER_H_
#define BMP_HEADER_H_
// TODO: プログラムに必要な追加ヘッダーをここで参照してください。
// BITMAPFILEHEADER 14bytes
typedef struct tagBITMAPFILEHEADER {
  unsigned short bfType;
  unsigned long  bfSize;
  unsigned short bfReserved1;
  unsigned short bfReserved2;
  unsigned long  bfOffBits;
} BITMAPFILEHEADER;

// BITMAPINFOHEADER 40bytes
typedef struct tagBITMAPINFOHEADER{
    unsigned long  biSize;
    long           biWidth;
    long           biHeight;
    unsigned short biPlanes;
    unsigned short biBitCount;
    unsigned long  biCompression;
    unsigned long  biSizeImage;
    long           biXPixPerMeter;
    long           biYPixPerMeter;
    unsigned long  biClrUsed;
    unsigned long  biClrImporant;
} BITMAPINFOHEADER;

typedef struct BMP24bitsFORMAT {
    unsigned char blue;
    unsigned char green;
    unsigned char red;
} BMP24FORMAT;

#endif /* BMP_HEADER_H_ */


次に、lap_filter2.c を示す。

// laplacian_filter2.c
// lap_filter_axim()

#include <stdio.h>
#include <string.h>

//#define HORIZONTAL_PIXEL_WIDTH    64
//#define VERTICAL_PIXEL_WIDTH    48
#define HORIZONTAL_PIXEL_WIDTH    100
#define VERTICAL_PIXEL_WIDTH    75
//#define HORIZONTAL_PIXEL_WIDTH    200
//#define VERTICAL_PIXEL_WIDTH    150
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y(int rgb);

//#pragma SDS data zero_copy(cam_fb)
//#pragma SDS data zero_copy(lap_fb)
int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE], int width, int height)
{
    unsigned int line_buf[3][ALL_PIXEL_VALUE];
    unsigned int lap_buf[ALL_PIXEL_VALUE];
    int x, y, i;
    int lap_fil_val;
    int a, b;
    int fl, sl, tl;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<height; y++){
        for (x=0; x<width; x++){
            if (y==0 || y==height-1){ // 縦の境界の時の値は0とする
                lap_fil_val = 0;
            }else if (x==0 || x==width-1){ // 横の境界の時も値は0とする
                lap_fil_val = 0;
            }else{
                if (y == 1 && x == 1){ // 最初のラインの最初のピクセルでは2ライン分の画素を読み出す
                    for (a=0; a<2; a++){ // 2ライン分
                        for (b=0; b<width; b++){ // ライン
                            line_buf[a][b] = cam_fb[(a*width)+b];
                            line_buf[a][b] = conv_rgb2y(line_buf[a][b]);
                        }
                    }
                }
                if (x == 1) {    // ラインの最初なので、2つのピクセルを読み込む
                    for (b=0; b<2; b++){ // ライン
                        line_buf[(y+1)%3][b] = cam_fb[((y+1)*width)+b];
                        // (y+1)%3 は、使用済みのラインがに読み込む、y=2 の時 line[0], y=3の時 line[1], y=4の時 line[2]
                        line_buf[(y+1)%3][b] = conv_rgb2y(line_buf[(y+1)%3][b]);
                    }
                }

                // 1つのピクセルを読み込みながらラプラシアン・フィルタを実行する
                line_buf[(y+1)%3][x+1] = cam_fb[((y+1)*width)+(x+1)];
                // (y+1)%3 は、使用済みのラインがに読み込む、y=2 の時 line[0], y=3の時 line[1], y=4の時 line[2]
                line_buf[(y+1)%3][x+1] = conv_rgb2y(line_buf[(y+1)%3][x+1]);

                fl = (y-1)%3;    // 最初のライン, y=1 012, y=2 120, y=3 201, y=4 012
                sl = y%3;        // 2番めのライン
                tl = (y+1)%3;    // 3番目のライン
                lap_fil_val = laplacian_fil(line_buf[fl][x-1], line_buf[fl][x], line_buf[fl][x+1], line_buf[sl][x-1], line_buf[sl][x], line_buf[sl][x+1], line_buf[tl][x-1], line_buf[tl][x], line_buf[tl][x+1]);
            }
            // ラプラシアンフィルタ・データの書き込み
            lap_fb[(y*width)+x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
        }
    }
    return 0;
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}


最後に、lap_fiter_tb.c を示す。

// Testbench of laplacian_filter.c (lap_filter_tb.c)
// BMPデータをハードウェアとソフトウェアで、ラプラシアン・フィルタを掛けて、それを比較する
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include "bmp_header.h"

#define HORIZONTAL_PIXEL_WIDTH    100
#define VERTICAL_PIXEL_WIDTH    75
//#define HORIZONTAL_PIXEL_WIDTH    200
//#define VERTICAL_PIXEL_WIDTH    150
//#define HORIZONTAL_PIXEL_WIDTH    64
//#define VERTICAL_PIXEL_WIDTH    48
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y_soft(int rgb);
int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE], int width, int height);    // hardware
void laplacian_filter_soft(volatile int *cam_fb, volatile int *lap_fb, long width, long height); // software

int main()
{
    int *s, *h;
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *hw_lapd, *sw_lapd;
    int blue, green, red;
    struct timeval start_time_hw, end_time_hw;
    struct timeval start_time_sw, end_time_sw;
    
    if ((fbmpr = fopen("test100x75.bmp""rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.bmp by binary read mode\n");
        exit(1);
    }

    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(char), 2, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(long), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((hw_lapd =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_lapd memory\n");
        exit(1);
    }
    if ((sw_lapd =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate sw_lapd memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    gettimeofday(&start_time_hw, NULL);
    lap_filter_axim(rd_bmp, hw_lapd, (int)bmpihr.biWidth, (int)bmpihr.biHeight);    // ハードウェアのラプラシアン・フィルタ
    gettimeofday(&end_time_hw, NULL);

    gettimeofday(&start_time_sw, NULL);
    laplacian_filter_soft(rd_bmp, sw_lapd, bmpihr.biWidth, bmpihr.biHeight);    // ソフトウェアのラプラシアン・フィルタ
    gettimeofday(&end_time_sw, NULL);

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0, h=hw_lapd, s=sw_lapd; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            if (*h != *s){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, *h, *s);
                return(1);
            } else {
                h++;
                s++;
            }
        }
    }
    printf("Success HW and SW results match\n");
    if (end_time_hw.tv_usec < start_time_hw.tv_usec) {
        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec - 11000000 + end_time_hw.tv_usec - start_time_hw.tv_usec);
    }
    else {
        printf("lap_filter2 HW time = %ld.%06ld sec\n", end_time_hw.tv_sec - start_time_hw.tv_sec, end_time_hw.tv_usec - start_time_hw.tv_usec);
    }
    if (end_time_sw.tv_usec < start_time_sw.tv_usec) {
        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec - 11000000 + end_time_sw.tv_usec - start_time_sw.tv_usec);
    }
    else {
        printf("lap_filter2 SW time = %ld.%06ld sec\n", end_time_sw.tv_sec - start_time_sw.tv_sec, end_time_sw.tv_usec - start_time_sw.tv_usec);
    }

    // ハードウェアのラプラシアンフィルタの結果を temp_lap.bmp へ出力する
    if ((fbmpw=fopen("temp_lap.bmp""wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.bmp by binary write mode\n");
        exit(1);
    }
    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

    // RGB データの書き込み、逆順にする
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    if (rd_bmp) free(rd_bmp);
    if (hw_lapd) free(hw_lapd);
    if (sw_lapd) free(sw_lapd);

    return(0);
}

void laplacian_filter_soft(volatile int *cam_fb, volatile int *lap_fb, long width, long height)
{
    unsigned int **line_buf;
    unsigned int *lap_buf;
    int x, y, i;
    int lap_fil_val;
    int a, b;
    int fl, sl, tl;

    // line_buf の1次元目の配列をアロケートする
    if ((line_buf =(unsigned int **)malloc(sizeof(unsigned int *) * 3)) == NULL){
        fprintf(stderr, "Can't allocate line_buf[3][]\n");
        exit(1);
    }

    // メモリをアロケートする
    for (i=0; i<3; i++){
        if ((line_buf[i]=(unsigned int *)malloc(sizeof(unsigned int) * width)) == NULL){
            fprintf(stderr, "Can't allocate line_buf[%d]\n", i);
            exit(1);
        }
    }

    if ((lap_buf=(unsigned int *)malloc(sizeof(unsigned int) * (width))) == NULL){
        fprintf(stderr, "Can't allocate lap_buf memory\n");
        exit(1);
    }

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<height; y++){
        for (x=0; x<width; x++){
            if (y==0 || y==height-1){ // 縦の境界の時の値は0とする
                lap_fil_val = 0;
            }else if (x==0 || x==width-1){ // 横の境界の時も値は0とする
                lap_fil_val = 0;
            }else{
                if (y == 1 && x == 1){ // 最初のラインの最初のピクセルでは2ライン分の画素を読み出す
                    for (a=0; a<2; a++){ // 2ライン分
                        for (b=0; b<width; b++){ // ライン
                            line_buf[a][b] = cam_fb[(a*width)+b];
                            line_buf[a][b] = conv_rgb2y_soft(line_buf[a][b]);
                        }
                    }
                }
                if (x == 1) {    // ラインの最初なので、2つのピクセルを読み込む
                    for (b=0; b<2; b++){ // ライン
                        line_buf[(y+1)%3][b] = cam_fb[((y+1)*width)+b];
                        // (y+1)%3 は、使用済みのラインがに読み込む、y=2 の時 line[0], y=3の時 line[1], y=4の時 line[2]
                        line_buf[(y+1)%3][b] = conv_rgb2y_soft(line_buf[(y+1)%3][b]);
                    }
                }

                // 1つのピクセルを読み込みながらラプラシアン・フィルタを実行する
                line_buf[(y+1)%3][x+1] = cam_fb[((y+1)*width)+(x+1)];
                // (y+1)%3 は、使用済みのラインがに読み込む、y=2 の時 line[0], y=3の時 line[1], y=4の時 line[2]
                line_buf[(y+1)%3][x+1] = conv_rgb2y_soft(line_buf[(y+1)%3][x+1]);

                fl = (y-1)%3;    // 最初のライン, y=1 012, y=2 120, y=3 201, y=4 012
                sl = y%3;        // 2番めのライン
                tl = (y+1)%3;    // 3番目のライン
                lap_fil_val = laplacian_fil_soft(line_buf[fl][x-1], line_buf[fl][x], line_buf[fl][x+1], line_buf[sl][x-1], line_buf[sl][x], line_buf[sl][x+1], line_buf[tl][x-1], line_buf[tl][x], line_buf[tl][x+1]);
            }
            // ラプラシアンフィルタ・データの書き込み
            lap_fb[(y*width)+x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
        }
    }
    if(lap_buf) free(lap_buf);
    for (i=0; i<3; i++)
        if (line_buf[i]) free(line_buf[i]);
    if (line_buf) free(line_buf);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}

  1. 2017年01月04日 05:02 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoC のプラットフォームのお勉強6(ハードウェア・プラットフォームの完成)

SDSoC のプラットフォームのお勉強5(ハードウェア・プラットフォームのメタデータファイルを作る2)”の続き。

前回、ハードウェア・プラットフォームのメタデータファイルを作ることができた。今回は、ハードウェア・プラットフォームを完成させる。

なお、
最初に前回のハードウェア・プラットフォームのメタデータファイルはpfm_name での名前が zybo のままだったので、現在SDSoC に入っているのプラットフォームのzybo とかち合ってしまった。よって、”zybo”を”ZYBO_0_163_3”に変更した。ZYBO_0_163_6_pfm.tcl を示す。

# ZYBO_0_163_6_pfm.tcl
#
# ハードウェア・プラットフォームのメタデータファイルの生成
# 2017/01/01 by marsee
#

source -notrace C:/HDL/Xilinx/SDx/2016.3/scripts/vivado/sdsoc_pfm.tcl

set pfm [sdsoc::create_pfm ZYBO_0_163_6.hpfm]
sdsoc::pfm_name $pfm "marsee" "xd" "ZYBO_0_163_6" "1.0"
sdsoc::pfm_description $pfm "Zynq ZYBO camera"

sdsoc::pfm_clock $pfm FCLK_CLK0 processing_system7_0 0 true rst_processing_system7_0_100M
sdsoc::pfm_clock $pfm FCLK_CLK1 processing_system7_0 1 false proc_sys_reset_0
sdsoc::pfm_clock $pfm FCLK_CLK2 processing_system7_0 2 false proc_sys_reset_1
sdsoc::pfm_clock $pfm FCLK_CLK3 processing_system7_0 3 false proc_sys_reset_2

sdsoc::pfm_axi_port $pfm M_AXI_GP1 processing_system7_0 M_AXI_GP
sdsoc::pfm_axi_port $pfm S_AXI_HP0 processing_system7_0 S_AXI_HP

sdsoc::pfm_axis_port $pfm m_axis mt9d111_inf_axis_0 M_AXIS

for {set i 0} {$i < 16} {incr i} {
  sdsoc::pfm_irq $pfm In$i xlconcat_0
}

sdsoc::pfm_iodev $pfm s_axi_lite bitmap_disp_cntrler_axi_master_0 uio
sdsoc::pfm_iodev $pfm s_axi_lite bitmap_disp_cntrler_axi_master_1 uio
sdsoc::pfm_iodev $pfm s_axi_lite mt9d111_inf_axis_0 uio
sdsoc::pfm_iodev $pfm s_axi_AXILiteS PmodHB5_inf_left uio
sdsoc::pfm_iodev $pfm s_axi_AXILiteS1 PmodHB5_inf_left uio
sdsoc::pfm_iodev $pfm s_axi_AXILiteS PmodHB5_inf_right uio
sdsoc::pfm_iodev $pfm s_axi_AXILiteS1 PmodHB5_inf_right uio
sdsoc::pfm_iodev $pfm S_AXI axi_gpio_0 uio

sdsoc::generate_hw_pfm $pfm


書き換えたZYBO_0_163_6.hpfm の一部を貼っておく。

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!-- ZYBO_0_163_6.hpfm -->
<xd:repository xmlns:xd="http://www.xilinx.com/xd" xd:name="ZYBO_0_163_6" xd:library="xd" xd:version="1.0" xd:vendor="marsee">
  <xd:component xd:name="ZYBO_0_163_6" xd:library="xd" xd:version="1.0" xd:vendor="marsee" xd:type="platform" xd:BRAM="60" xd:DSP="80" xd:FF="35200" xd:LUT="17600">
    <xd:platformInfo>
      <xd:deviceInfo xd:name="xc7z010clg400-1" xd:architecture="zynq" xd:device="xc7z010" xd:package="clg400" xd:speedGrade="-1"/>
      <xd:registeredDevices xd:kio="0" xd:uio="8"/>
      <xd:description>Zynq ZYBO camera</xd:description>


これで、ハードウェア・プラットフォームのメタデータファイルの修正が終了した。
次に、プロジェクトをアーカイブする。
File メニューからArchive Project... を選択する。

Archive Project ダイアログが開いた。
SDx_v2016_3_46_170101.png

プロジェクトがアーカイブできて成功したというダイアログが出た。
SDx_v2016_3_47_170101.png

Platforms フォルダにZYBO_0_163_6.xpr.zip ができた。
SDx_v2016_3_48_170101.png

ZYBO_0_163_6.xpr.zip の中を見てみよう。
SDx_v2016_3_49_170101.png

まだソフトウェア・プラットフォームを作っていないが、とりあえず、ZYBO_0_163_6 フォルダを作成して、デフォルトのzybo のプラットフォームをコピーしてきた。
zybo.xpfm を ZYBO_0_163_6.xpfm に入れ替えた。
SDx_v2016_3_50_170101.png

hwフォルダに、生成されたハードウェア・プラットフォームのメタデータファイルZYBO_0_163_6.hpfm を入れた。
SDx_v2016_3_51_170101.png

hw\vivado フォルダにアーカイブされたVivado 2016.3 プロジェクトをコピー&ペーストして、ZYBO_0_163_3_pfm.tcl も入れた。
SDx_v2016_3_52_170101.png

これで、SDx Terminal 2016.3 を起動して、Z:\Platfroms\ZYBO_0_163_3\hw に cd した。

sds-pf-check ZYBO_0_163_6.hpfm を実行すると、validates が返ってきた。大丈夫なようだ。
SDx_v2016_3_53_170101.png

試しに、Z:\Platfroms\ZYBO_0_163_3 フォルダに cd して、sdscc -sds-pf-list を実行すると、それなりに表示されている。
SDx_v2016_3_54_170101.png

でも、まだソフトウェア・プラットフォームは生成していない。

現在のZYBO_0_163_6.xpfm を示す。

<?xml version="1.0" encoding="UTF-8"?>
<sdx:platform sdx:vendor="marsee"              sdx:library="sdx"              sdx:name="ZYBO_0_163_6"              sdx:version="1.0"              sdx:schemaVersion="1.0"              xmlns:sdx="http://www.xilinx.com/sdx">
  <sdx:description>ZYBO_0_163_6 Zynq-7000 SoC platform targeting the ZYBO board. MT9D111 Camera Interface, PWM, Motor Monitor, HDMI output, VGA output</sdx:description>

    <!-- Support multiple DSAs for multi-FPGA platforms -->
  <sdx:hardwarePlatforms>
    <sdx:hardwarePlatform sdx:path="hw" sdx:name="ZYBO_0_163_6.hpfm"/>
  </sdx:hardwarePlatforms>

  <sdx:softwarePlatforms>
    <sdx:softwarePlatform sdx:path="sw" sdx:name="zybo.spfm"/>
  </sdx:softwarePlatforms>

</sdx:platform>

  1. 2017年01月02日 08:22 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

あけましておめでとうございます

あけましておめでとうございます。
今年もFPGAの部屋のブログをよろしくお願いします。
今年最初はSDSoC を何とか理解しようと頑張ろうと思います。
今年もよろしくお願い致します。
  1. 2017年01月01日 07:30 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0