FC2カウンター FPGAの部屋 2019年12月
FC2ブログ

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

FPGAの部屋

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

2019年を振り返る

今年 2019 年をざっと振り返ってみよう。

Ultra96のDisplay Port にカメラ画像を表示しようとしていた。”カメラ画像をDisplayPortに出力する9(アプリを作成、完成)”で完成した。

Ultra96のPMOD拡張ボードを作った。MIPI 拡張ボードのPMOD 拡張ボードを入れると 3 種類作成した。
Ultra96用PMOD拡張ボード12(改版した基板に部品を実装した)”これは、 2018 年 12 月だが、実際に使用したのは今年だった。
MIPIボード用PMOD拡張ボードをテストする1
Ultra96-V2 の高速、低速コネクタ用PMOD 拡張基板10(部品を実装)

Ultra96のMIPI拡張ボードを使ってDisplayPort に画像を出力しようとしたが、うまく行かなかった。残念。。。

Docker も使ってみた。Docker上でPetaLinux も動作させたが、アプリごとに環境を作っているとHDD がいくらあっても足りなそうなので、とりあえず Docker 使ってない。

Vivado HLS の OpenCV 対応も xfOpenCV になった。

DNN のFPGA 実装の Distillerhls4mlBinaryBrainkeras_compressor を試してみた。

Donkey Car も買ったな。。。Ultra96 を搭載するはずがまだやっていない。。。

SDxのプラットフォームを作ってみた。ベアメタル・アプリケーションはうまく行ったが、Linux アプリケーションはうまく行かなかった。

Vitisプラットフォーム、Vitis アプリケーション・プラットフォームを作った。現在進行系だ。

今年のイベントはなんと言っても私の定年退職だった。定年退職して、兼業がやりやすくなったので、HDLab さんでVivado HLS のセミナをやることにした。皆さんにお会いできるのが楽しみだ。
後は、四国しまなみ海道サイクリングだね。
ロードバイクも買っちゃたし、自転車のレースにも出た。来年も自転車に乗るぞ。。。

  1. 2019年12月31日 18:54 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する2(Vivado HLS 編 2)

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する1(Vivado HLS 編 1)”の続き。

前回は、Vitis のRTL カーネルとして実装するためのC++ のソースコードテストベンチを貼った。今回は、そのコードをVivado HLS 2019.2 でプロジェクトを作成して実装していこう。

最初に all_layers_template プロジェクトを示す。
RTL_kernel_9_191228.png

RTL_kernel_10_191228.png

このように層ごとにたくさんのファイルを入れてある。
なお、”Vitis のRTL カーネル”にあるように、Solution メニューから Solution Settings... を選択して、

config_interface -m_axi_addr64
config_sdx -target xocc

オプションを入れた。
RTL_kernel_17_191229.png

最初に C シミュレーションを行った。
(2020/01/02 :追記) C シミュレーションは all_layers_template_axim.cpp の extern "C" { } をコメントアウトしています。そうしなければ C シミュレーションがエラーになります。
RTL_kernel_11_191228.png

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
   Compiling ../../../all_layers_template_axim.cpp in debug mode
   Compiling ../../../hw_ip/conv_layer1.cpp in debug mode
   Compiling ../../../hw_ip/input_layer.cpp in debug mode
   Compiling ../../../hw_ip/max_pooling.cpp in debug mode
   Compiling ../../../hw_ip/output_layer.cpp in debug mode
   Compiling ../../../hw_ip/relu_affine1.cpp in debug mode
   Compiling ../../../hw_ip/relu_conv1.cpp in debug mode
   Generating csim.exe
hw_error: i = 25 output = 2 t_test_num = 1
sw_error: i = 25 output_soft = 2 t_test_num = 1
dot2[0] = -5.00000000   dot2_soft[0] = -3.77501726
dot2[1] = 0.00000000    dot2_soft[1] = -0.13269189
dot2[2] = 0.00000000    dot2_soft[2] = 1.61074853

hw_error: i = 30 output = 2 t_test_num = 1
sw_error: i = 30 output_soft = 2 t_test_num = 1
dot2[0] = -6.00000000   dot2_soft[0] = -4.67336369
dot2[1] = 0.00000000    dot2_soft[1] = 0.12951475
dot2[2] = 0.00000000    dot2_soft[2] = 1.71587336

sw_error: i = 31 output_soft = 2 t_test_num = 1
dot2[0] = -7.00000000   dot2_soft[0] = -5.31440449
dot2[1] = 0.00000000    dot2_soft[1] = 0.69655895
dot2[2] = 0.00000000    dot2_soft[2] = 1.00723171

sw_error: i = 35 output_soft = 2 t_test_num = 1
dot2[0] = -7.00000000   dot2_soft[0] = -5.15462875
dot2[1] = 0.00000000    dot2_soft[1] = 0.19586089
dot2[2] = 0.00000000    dot2_soft[2] = 1.79063916

sw_error: i = 36 output_soft = 2 t_test_num = 1
dot2[0] = -7.00000000   dot2_soft[0] = -5.64889669
dot2[1] = 1.00000000    dot2_soft[1] = 0.69646239
dot2[2] = 0.00000000    dot2_soft[2] = 1.09402716

sw_error: i = 40 output_soft = 2 t_test_num = 1
dot2[0] = -7.00000000   dot2_soft[0] = -5.31394196
dot2[1] = 0.00000000    dot2_soft[1] = 0.30034199
dot2[2] = 0.00000000    dot2_soft[2] = 1.52586949

sw_error: i = 41 output_soft = 2 t_test_num = 1
dot2[0] = -8.00000000   dot2_soft[0] = -5.94443941
dot2[1] = 0.00000000    dot2_soft[1] = 0.61903512
dot2[2] = 0.00000000    dot2_soft[2] = 1.28180122

sw_error: i = 42 output_soft = 2 t_test_num = 1
dot2[0] = -10.00000000  dot2_soft[0] = -7.44187164
dot2[1] = 1.00000000    dot2_soft[1] = 1.10615981
dot2[2] = 0.00000000    dot2_soft[2] = 1.35738707

sw_error: i = 45 output_soft = 2 t_test_num = 1
dot2[0] = -8.00000000   dot2_soft[0] = -5.92508411
dot2[1] = 0.00000000    dot2_soft[1] = 0.44851223
dot2[2] = 0.00000000    dot2_soft[2] = 1.43742454

sw_error: i = 46 output_soft = 2 t_test_num = 1
dot2[0] = -10.00000000  dot2_soft[0] = -7.76649952
dot2[1] = 1.00000000    dot2_soft[1] = 0.82863915
dot2[2] = 0.00000000    dot2_soft[2] = 1.88942850

sw_error: i = 47 output_soft = 2 t_test_num = 1
dot2[0] = -12.00000000  dot2_soft[0] = -9.50911713
dot2[1] = 1.00000000    dot2_soft[1] = 1.48399019
dot2[2] = 0.00000000    dot2_soft[2] = 1.85759318

hw_error: i = 75 output = 2 t_test_num = 1
sw_error: i = 75 output_soft = 2 t_test_num = 1
dot2[0] = -5.00000000   dot2_soft[0] = -4.04238653
dot2[1] = -1.00000000   dot2_soft[1] = -1.22402656
dot2[2] = 2.00000000    dot2_soft[2] = 3.36929369

hw_error: i = 76 output = 2 t_test_num = 1
sw_error: i = 76 output_soft = 2 t_test_num = 1
dot2[0] = -6.00000000   dot2_soft[0] = -4.09871578
dot2[1] = 0.00000000    dot2_soft[1] = -0.46985394
dot2[2] = 0.00000000    dot2_soft[2] = 1.61257589

hw_error: i = 80 output = 2 t_test_num = 1
sw_error: i = 80 output_soft = 2 t_test_num = 1
dot2[0] = -6.00000000   dot2_soft[0] = -4.33292818
dot2[1] = 0.00000000    dot2_soft[1] = -0.96692348
dot2[2] = 1.00000000    dot2_soft[2] = 2.98383069

hw_error: i = 81 output = 2 t_test_num = 1
sw_error: i = 81 output_soft = 2 t_test_num = 1
dot2[0] = -6.00000000   dot2_soft[0] = -4.40864801
dot2[1] = 0.00000000    dot2_soft[1] = -0.15780880
dot2[2] = 0.00000000    dot2_soft[2] = 1.26864278

hw_error: i = 85 output = 2 t_test_num = 1
sw_error: i = 85 output_soft = 2 t_test_num = 1
dot2[0] = -6.00000000   dot2_soft[0] = -4.16326904
dot2[1] = 0.00000000    dot2_soft[1] = -0.84592772
dot2[2] = 1.00000000    dot2_soft[2] = 2.42255425

sw_error: i = 86 output_soft = 2 t_test_num = 1
dot2[0] = -6.00000000   dot2_soft[0] = -4.36515617
dot2[1] = 0.00000000    dot2_soft[1] = -0.08813666
dot2[2] = 0.00000000    dot2_soft[2] = 0.97706115

hw_error: i = 90 output = 2 t_test_num = 1
sw_error: i = 90 output_soft = 2 t_test_num = 1
dot2[0] = -5.00000000   dot2_soft[0] = -4.02276182
dot2[1] = 0.00000000    dot2_soft[1] = -0.66237617
dot2[2] = 0.00000000    dot2_soft[2] = 1.72938108

sw_error: i = 91 output_soft = 2 t_test_num = 1
dot2[0] = -5.00000000   dot2_soft[0] = -3.85103607
dot2[1] = 0.00000000    dot2_soft[1] = -0.09844255
dot2[2] = 0.00000000    dot2_soft[2] = 0.42963967

sw_error: i = 95 output_soft = 2 t_test_num = 1
dot2[0] = -6.00000000   dot2_soft[0] = -4.07760668
dot2[1] = 0.00000000    dot2_soft[1] = -0.30057180
dot2[2] = 0.00000000    dot2_soft[2] = 0.90393031

hw_err_cnt = 8 sw_err_cnt = 20
WARNING: Hls::stream 'hls::stream<ap_axiu<32, 1, 1, 1> >.1' contains leftover data, which may result in RTL simulation hanging.
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


前にやった結果と同じなので、問題はないだろう。ちなみに 300 個やってハードウェアのエラーが 8 個、ソフトウェアのエラーが 20 個で、ハードウェアの正解確率は約 97 % 、ソフトウェアの正解確率は約 93 % だった。

次に、 C コードの合成を行った。
RTL_kernel_12_191228.png

レイテンシは max で 9176 クロックで 200 MHz なので、約 46 us かかっている。約 22,000 fps となる。

Export RTL を行った。Vivado synthesis, place and route にチェックを入れた。
RTL_kernel_13_191228.png

FF は 11.915 個だったが、合成時には、 15,866 個だったので、小さくなった。
LUT は 9,793 個とSRL の 550 個を足すと 10,343 個だったが、合成時には、 16, 774 個だった。

CP achieved post-implementation は 4.336 ns となっているので、問題はないだろう。

ip ディレクトリが作られ、kernel ディレクトリの下に kernel.xml が生成された。
RTL_kernel_16_191229.png

kernel.xml を示す。
RTL_kernel_15_191229.png

all_layers_dnn.xo が生成されている。
RTL_kernel_16_191229.png
  1. 2019年12月30日 04:00 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する1(Vivado HLS 編 1)

以前テンプレートを使用して、パラメータを変更できる形に書いた畳み込みニューラルネットワークをVivado HLS 2019.2 でIP にすることにした。Vitis のアクセラレーション・プラットフォームでやってみたいためだ。実はこのテンプレートを使用して、パラメータを変更できる形に書いた畳み込みニューラルネットワークをVitis のアクセラレーション・プロジェクトとして作成したところ、ビルドが通らなかったので、Vivado HLS でIP 化した後で、Vitis のRTL カーネルとして使ってみたいということだ。
Vivado HLS でIP 化すると、チューニングもしやすいし、C/RTL 協調シミュレーションで波形も確認できるのでとても良い。そこで、IP 化したVerilog HDL コードをRTL カーネルとしてVitis で使いたいと考えたわけだ。。。
今回は、Vitis のRTL カーネルと実装するためのC++ のソースコードテストベンチを貼っておく。

テンプレートで書いた CNN の記事を下に示す。
これはテンプレートで書いた各層をHLSストリームで接続するフレームワークになっている。テンプレートのパラメータを設定すれば、いろいろな CNN として使用することができるように設計されている(はず)。現在は、白線走行用の CNN のパラメータがテンプレートに設定されている。
今回は、入力層ー畳み込み層ーReluーMaxpoolingー全結合層ーReluー全結合層ー出力層だが、途中のHLSストリームの幅を合わせれば、何層でも接続することができる。ただし、余り多いとVivado HLSで合成できなくなる。
その場合は、HLSストリームで接続なので、途中で切って合成し、IP インテグレータでつなぐこともできる。
テンプレートで書いた畳み込 みニューラルネットワーク1(ソースコード)
テンプレートで書いた畳み込 みニューラルネットワーク2(C シミュレーションとC コードの合成)
テンプレートで書いた畳み込 みニューラルネットワーク2(C/RTL協調シミュレーションとExport RTL)

(2020/01/11:追記)新しいコードが”テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する3(Vivado HLS 編 3)”にあります。そちらをご覧ください。

このソースコードを、”Vitis のRTL カーネル”に沿って書き換えた。

ソースコードの all_layers_template_axim.cpp を示す。

// all_layers_template_axim.cpp
// 2018/05/10 by marsee
// 2019/12/28: VitisのRTLカーネルととして使用するためにall_layers_dnnを追加
//

#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include <hls_video.h>
#include <stdint.h>

#include "layer_general.h"
#include "all_layers_template.h"

int input_layer(hls::stream<ap_axiu<32,1,1,1> >&ins,
    hls::stream<ap_fixed_axis<9,1,1,1> >&outs);

int conv_layer1(hls::stream<ap_fixed_axis<9,1,1,1> >& ins,
    hls::stream<ap_fixed_axis<16,6,2,1> >& outs);

int relu_conv1(hls::stream<ap_fixed_axis<16,6,2,1> >& ins,
    hls::stream<ap_fixed_axis<16,6,2,1> >& outs);

int max_pooling(hls::stream<ap_fixed_axis<16,6,2,1> >& ins,
    hls::stream<ap_fixed_axis<16,6,2,1> >& outs);

int affine_layer1(hls::stream<ap_fixed_axis<16,6,2,1> >& ins,
    hls::stream<ap_fixed_axis<19,7,1,1> >& outs);

int relu_affine1(hls::stream<ap_fixed_axis<19,7,1,1> >& ins,
    hls::stream<ap_fixed_axis<19,7,1,1> >& outs);

int affine_layer2(hls::stream<ap_fixed_axis<19,7,1,1> >& ins,
    hls::stream<ap_fixed_axis<12,7,1,1> >& outs);

int output_layer(hls::stream<ap_fixed_axis<12,7,1,1> >& ins, output_type& output,
    out_affine_type dot2[NUMBER_OF_OUTPUT_LAYER]);

int all_layers(hls::stream<ap_axiu<32,1,1,1> >& ins, output_type& output,
    out_affine_type dot2[NUMBER_OF_OUTPUT_LAYER]){
//#pragma HLS INTERFACE s_axilite port=output
//#pragma HLS INTERFACE s_axilite port=dot2
//#pragma HLS ARRAY_PARTITION variable=dot2 complete dim=1
//#pragma HLS INTERFACE s_axilite port=return
//#pragma HLS INTERFACE axis register both port=ins
#pragma HLS DATAFLOW

    hls::stream<ap_fixed_axis<9,1,1,1> > outs_input_layer;
//#pragma HLS STREAM variable=outs_input_layer depth=560 dim=1
    hls::stream<ap_fixed_axis<16,6,2,1> > outs_conv_layer;
//#pragma HLS STREAM variable=outs_conv_layer depth=312 dim=1
    hls::stream<ap_fixed_axis<16,6,2,1> > outs_relu_conv1;
//#pragma HLS STREAM variable=outs_relu depth=312 dim=1
    hls::stream<ap_fixed_axis<16,6,2,1> > outs_max_pooling;
//#pragma HLS STREAM variable=outs_max_pooling depth=78 dim=1
    hls::stream<ap_fixed_axis<19,7,1,1> > outs_affine_layer1;
//#pragma HLS STREAM variable=outs_affine_layer1 depth=100 dim=1
    hls::stream<ap_fixed_axis<19,7,1,1> > outs_relu_affine1;
//#pragma HLS STREAM variable=outs_relu_affine1 depth=100 dim=1
    hls::stream<ap_fixed_axis<12,7,1,1> > outs_affine_layer2;
//#pragma HLS STREAM variable=outs_affine_layer2 depth=3 dim=1

    input_layer(ins, outs_input_layer);
    conv_layer1(outs_input_layer, outs_conv_layer);
    relu_conv1(outs_conv_layer, outs_relu_conv1);
    max_pooling(outs_relu_conv1, outs_max_pooling);
    affine_layer1(outs_max_pooling, outs_affine_layer1);
    relu_affine1(outs_affine_layer1, outs_relu_affine1);
    affine_layer2(outs_relu_affine1, outs_affine_layer2);
    output_layer(outs_affine_layer2, output, dot2);

    return(0);
}

extern "C" {
void all_layers_dnn(volatile uint32_t *inm, volatile uint32_t *output,
        volatile int32_t *dot2, int32_t x_size, int32_t y_size){
#pragma HLS INTERFACE m_axi port=inm offset = slave bundle = gmem
#pragma HLS INTERFACE m_axi port=output offset = slave bundle = gmem
#pragma HLS INTERFACE m_axi port=dot2 offset = slave bundle = gmem2
#pragma HLS INTERFACE s_axilite port = x_size bundle = control
#pragma HLS INTERFACE s_axilite port = y_size bundle = control
#pragma HLS INTERFACE s_axilite port = return bundle = control

#pragma HLS DATAFLOW
    hls::stream<ap_axiu<32, 1, 1, 1> > ins;
    ap_axiu<32,1,1,1> element;
    out_affine_type dot2_o[NUMBER_OF_OUTPUT_LAYER];
    output_type output_o;

    Loop_y: for(int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=10 max=10 avg=10
        Loop_x: for(int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=56 max=56 avg=56
#pragma HLS PIPELINE II=1
            element.data = ap_uint<32>(inm[x_size*y+x]);
            if(x==0 && y==0) // 最初のデータ
                element.user = 1;
            else
                element.user = 0;
            if(x==(x_size-1)) // 行の終了
                element.last = 1;
            else
                element.last = 0;

            ins << element;
        }
    }
    all_layers(ins, output_o, dot2_o);

    *output = uint32_t(output_o);

    Loop_dot2: for(int i=0; i<NUMBER_OF_OUTPUT_LAYER; i++){
#pragma HLS PIPELINE II=1
        dot2[i] = int32_t(dot2_o[i]);
    }
}
}


テストベンチの all_layers_template_axim_tb.cpp を示す。

// all_layers_template_axim_tb.cpp
// 2018/05/12 by marsee
// 2019/12/28: VitisのRTLカーネルととして使用するためにall_layers_dnnを追加
//

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <math.h>
#include <ap_axi_sdata.h>
#include <hls_video.h>
#include <vector>
#include <stdint.h>

#include "layer_general.h"
#include "all_layers_template.h"

#include "curve_data_0_100.h"
//#include "curve_data_2500_2600.h"
//#include "curve_data_5000_5100.h"

#define ALL_DATA_NUM   300
#define NUM_OF_KERNELS 2
#define COULMN_PIXELS 56
#define ROW_PIXELS 10
#define ALL_PIXELS 560
#define NUM_OF_OUTPUT 3

#define NUM_ITERATIONS  300 // C Simulation
//#define NUM_ITERATIONS    2 // C/RTL CoSimulation

void all_layers_dnn(volatile uint32_t *inm, volatile uint32_t *output,
        volatile int32_t *dot2, int32_t x_size, int32_t y_size);

int all_layers_soft(hls::stream<ap_axiu<32,1,1,1> >& ins, output_type& output,
        float dot2[NUMBER_OF_OUTPUT_LAYER]);

int main(){
    using namespace std;

    hls::stream<ap_axiu<32,1,1,1> > ins;
    hls::stream<ap_axiu<32,1,1,1> > ins_soft;
    output_type output_soft;
    uint32_t output;
    float dot2_soft[NUMBER_OF_OUTPUT_LAYER];
    ap_axiu<32,1,1,1> pix;
    int hw_err_cnt = 0;
    int sw_err_cnt = 0;

    vector<uint32_t> pixel(ROW_PIXELS * COULMN_PIXELS);
    vector<int32_t> dot2(NUMBER_OF_OUTPUT_LAYER);

    for(int i=0; i<NUM_ITERATIONS; i++){
        // ins に入力データを用意する
        for(int y=0; y<ROW_PIXELS; y++){
            for(int x=0; x<COULMN_PIXELS; x++){
                // 1 画面分のデータを ins、ins_soft に入力する
                pix.data = ap_uint<32>(t_train_256[i][y*COULMN_PIXELS+x]);
                pixel[y*COULMN_PIXELS+x] = uint32_t(t_train_256[i][y*COULMN_PIXELS+x]);

                if (x==0 && y==0)    // 最初のデータの時に TUSER を 1 にする
                    pix.user = 1;
                else
                    pix.user = 0;

                if (x == COULMN_PIXELS-1) // 行の最後でTLASTをアサートする
                    pix.last = 1;
                else
                    pix.last = 0;

                ins << pix;
                ins_soft << pix;
            }
        }

        all_layers_dnn(pixel.data(), &output, dot2.data(), COULMN_PIXELS, ROW_PIXELS);
        all_layers_soft(ins_soft, output_soft, dot2_soft);

        int t_test_num = 0;
        for(int m=0; m<NUMBER_OF_OUTPUT_LAYER; m++){
            if(t_test[i][m] == 1.0f){
                t_test_num = m;
                break;
            }
        }
        // out と out_soft を比較する
        /* cout << "output" << " = " << int(output) << " output_soft = " << int(output_soft) << endl;
        for(int j=0; j<NUMBER_OF_OUTPUT_LAYER; j++){
            cout << "dot2[" << j << "] = " << float(dot2[j]) << " dot2_soft[" << j << "] = " << dot2_soft[j] << endl;
        } */
        if(int(output) != t_test_num){
            cout << "hw_error: i = " << i << " output = " << int(output) << " t_test_num = " << t_test_num << endl;
            hw_err_cnt++;
            //return(1);
        }
        if(int(output_soft) != t_test_num){
            cout << "sw_error: i = "<< i << " output_soft = " << int(output_soft) << " t_test_num" " = " << t_test_num << endl;
            sw_err_cnt++;
            //return(1);
        }
        if(int(output) != t_test_num || int(output_soft) != t_test_num){
            for(int j=0; j<NUMBER_OF_OUTPUT_LAYER; j++){
                cout << "dot2[" << j << "] = " << fixed << setprecision(8) << float(dot2[j]) << "   dot2_soft[" << j << "] = " << dot2_soft[j] << endl;
            }
            cout << endl;
        }
    }
    cout << "hw_err_cnt = " << hw_err_cnt << " sw_err_cnt = " << sw_err_cnt << endl;

    return(0);
}

  1. 2019年12月29日 11:20 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS の GUI コマンド実行履歴を TCL 実行スクリプトにする

今のところ、私はほとんどVivado HLS をGUI で操作しているが、TCL スクリプトで動作させることもできる。
GUI でやった履歴を TCL スクリプトにする機能が Vivado HLS にはある。
それは、 solution? -> script.tcl だ。
RTL_kernel_6_191228.png

script.tcl は solution? ディレクトリの下にある。
RTL_kernel_7_191228.png

lap_filter_axis_dma.cpp を合成して、Export RTL した時の TCL スクリプトを貼っておく。

############################################################
## This file is generated automatically by Vivado HLS.
## Please DO NOT edit it.
## Copyright (C) 1986-2019 Xilinx, Inc. All Rights Reserved.
############################################################
open_project lap_filter_axis_dma
set_top lap_filter_axis_dma
add_files lap_filter_axis_dma/lap_filter_axis_dma.cpp
open_solution "solution1"
set_part {xczu3cg-sbva484-1-e}
create_clock -period 10 -name default
config_export -format ip_catalog -rtl verilog -vivado_optimization_level 0 -vivado_phys_opt none -vivado_report_level 0 -xo /home/masaaki/Vivado_HLS/Ultra96/test/lap_filter_axis_dma.xo
config_interface   -m_axi_addr64 -m_axi_offset off -register_io off -trim_dangling_port=0
config_sdx -target xocc
config_compile -name_max_length 80 -pipeline_loops 64
config_schedule -enable_dsp_full_reg
#source "./lap_filter_axis_dma/solution1/directives.tcl"
#csim_design
csynth_design
#cosim_design
export_design -rtl verilog -format ip_catalog -xo /home/masaaki/Vivado_HLS/Ultra96/test/lap_filter_axis_dma.xo


この TCL スクリプトを実行する時は、
vivado_hls -f script.tcl
とする。
  1. 2019年12月28日 17:09 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis のRTL カーネル

今まで、Vitis のアクセラレーション・プラットフォームのC/C++ カーネルを実装してきたが、RTL カーネルもやってみようと思う。RTL カーネルの作成条件について見ていこう。
なお、C/C++ カーネルを作っていると、性能やクリティカルパスなどの情報が無いかもしくは、分かりにくい。そこで、Vivado HLS でC/C++ コードを合成して RTLカーネルを作成して、それをVitis で使っても良いのでは?と思っている。

Vitis Unified Software Development Platform Documentation
Vitis Application Acceleration Development Flow DocumentationDeveloping ApplicationsRTL Kernels を見ていこう。

Vitisコア開発キットの各ハードウェアカーネルは、Xilinxオブジェクト(.xo)ファイルに個別にコンパイルされるそうだ。 .xo ファイルはVivado HLS 2019.2 で出力することができる。 .xo ファイルは、FPGA実行可能ファイル(xclbin)にリンクするためのアプリケーションプロジェクトに結合できるそうだ。

RTL Kernels の Table 1. RTL Kernel Interface and Port Requirements をChrome で翻訳したものを引用する。

表1. RTLカーネルインターフェイスとポートの要件ポートまたはインターフェース
RTL_kernel_1_191228.png

結局、Vitis のC/C++ カーネルを書く制約とほとんど一緒で、AXI4 Master を 64 ビット・アドレスにすれば良いということみたいだ。
Vivado HLS でAXI4 Master を 64 ビット・アドレスにするには、

config_interface -m_axi_addr64

オプションを入れれば良い。
GUI でやるには、Solution メニューから Solution Settings... を選択する。
左のペインのGeneral で、config_interface が無い場合は、Add... ボタンをクリックして、ある場合はEdit... ボタンをクリックして、
m_axi_addr64 のチェックボックスをチェックする。
RTL_kernel_2_191228.png

Table 2. Address Map、Table 3. Control (0x0)、Table 5. IP Interrupt Enable (0x8)、Table 6. IP Interrupt Status (0xC)はVivado HLS のVerilog HDL 出力のAXI4 Lite のレジスタマップそのままだ。
lap_filter_axis_dma.cpp のAXI4 Lite Slave のマップを示す。

//------------------------Address Info-------------------
// 0x00 : Control signals
//        bit 0  - ap_start (Read/Write/COH)
//        bit 1  - ap_done (Read/COR)
//        bit 2  - ap_idle (Read)
//        bit 3  - ap_ready (Read)
//        bit 7  - auto_restart (Read/Write)
//        others - reserved
// 0x04 : Global Interrupt Enable Register
//        bit 0  - Global Interrupt Enable (Read/Write)
//        others - reserved
// 0x08 : IP Interrupt Enable Register (Read/Write)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x0c : IP Interrupt Status Register (Read/TOW)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x10 : Data signal of inm
//        bit 31~0 - inm[31:0] (Read/Write)
// 0x14 : Data signal of inm
//        bit 31~0 - inm[63:32] (Read/Write)
// 0x18 : reserved
// 0x1c : Data signal of outm
//        bit 31~0 - outm[31:0] (Read/Write)
// 0x20 : Data signal of outm
//        bit 31~0 - outm[63:32] (Read/Write)
// 0x24 : reserved
// 0x28 : Data signal of x_size
//        bit 31~0 - x_size[31:0] (Read/Write)
// 0x2c : reserved
// 0x30 : Data signal of y_size
//        bit 31~0 - y_size[31:0] (Read/Write)
// 0x34 : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


Packaging the RTL Code as Vivado IP はVivado HLS でIP 化するならば問題ないはず。

Creating the Kernel Description XML File では kernel.xml を生成する必要がある。
Vivado HLS で kernel.xml を生成するためには、

config_sdx -target xocc

オプションを入れれば良い。
GUI でやるには、Solution メニューから Solution Settings... を選択する。
左のペインのGeneral で、 config_sdx が無い場合は、Add... ボタンをクリックして、ある場合はEdit... ボタンをクリックして、Parameters の target を選択して、xocc に変更する。
RTL_kernel_3_191228.png

これで、C コードの合成とExport RTL を行うと solution1/impl/kernel ディレクトリの下に、kernel.xml が生成された。
RTL_kernel_4_191228.png

ただし、トップの関数にはVitis のC/C++ カーネルを作成するための制約と同じルールを適用する必要があるようだ。
トップ関数の返り値は void で、関数全体を extern "C" { } で囲う必要がある。
RTL_kernel_8_191228.png

lap_filter_axis_dma.cpp を Vivado HLS 2019.2 で合成し、Export RTL した時の kernel.xml を示す。
RTL_kernel_5_191228.png
  1. 2019年12月28日 16:58 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン4

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン3”の続き。

前回は、Vitis 2019.2 で lap_filter_axis_dma プロジェクトを作成してUltra96-V2 で動作を確認することができた。今回は、実行時のプロファイルを取得してみよう。

Assistant ウインドウで lap_filter_axis_dma_system -> lap_filter_axis_dma -> Hardware を右クリックし右クリックメニューから、Run -> Run Configurations... を選択する。

左のペインのDebugger_lap_filter_axis_dma をクリックする。

Enable profiling をクリックする。

Generate timeline trace report を Yes に変更する。

Collect Data Transfer Trace を Fine にする。

Collect Stall Trace を All にする。

Apply ボタンをクリックする。

Run ボタンをクリックする。
lap_fitler_52_191227.png

Assistant ウインドウの lap_filter_axis_dma_system -> lap_filter_axis_dma -> Hardware の下に、Debugger_lap_filter_axis_dma -> Run Summary(xclbin) ができている。
lap_fitler_53_191227.png

Run Summary(xclbin) をダブルクリックして開く。
Vitis Analyzer が起動して Summary を表示する。
Profile Summary をクリックして表示した。
lap_fitler_54_191227.png

Appliction Timeline をクリックして表示した。
lap_fitler_55_191227.png

後ろのトランザクションを拡大した。
lap_fitler_56_191227.png

OpenCL のコマンドが表示されているのが見える。clEnqueueTask などが見える。
  1. 2019年12月27日 05:37 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン3

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン2”の続き。

前回は、バグを修正したソースコードを貼った。今回は、Vitis 2019.2 で lap_filter_axis_dma プロジェクトを作成してUltra96-V2 で動作を確認する。

Vitis 2019.2 で lap_filter_axis_dma プロジェクトを作成した。

Hardware をビルドしたところ警告がたくさん出た。
lap_fitler_35_191226.png

もう一度ビルドすると警告が消えた。
lap_fitler_36_191226.png

警告は、こんな感じで、自分の書いたコードじゃないようだ。

/media/masaaki/Ubuntu_Disk/tools/Xilinx/Vivado/2019.2/include/etc/ap_private.h:2143:27: 警告: ‘<anonymous>.ap_private<32, true>::VAL’ may be used uninitialized in this function [-Wmaybe-uninitialized]
             ? ((((int64_t)VAL) << (excess_bits)) >> (excess_bits))


Assistant ウインドウの lap_filter_axis_dma _system -> lap_filter_axis_dma -> Hardware を右クリックし右クリックメニューから Run -> Run Configurations... を選択する。

Run Configurations ダイアログが開く。
Single Application Debug をダブルクリックして、新しいコンフィギュレーションを開く。
lap_fitler_39_191225.png

lap_filter_axis_dma を作成した。現在の画面は Apply ボタンをクリックして確定させてある。
lap_fitler_40_191225.png

lap_fitler_41_191225.png

(なお、Configuration は一度作るとプロジェクトをビルドしてもバイナリが更新されないみたいだ。その場合は、一度削除して、再度作り直す必要があるようだ)

ここで、 Run ボタンをクリックする前に、zocl ドライバを insmod でロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
lap_fitler_38_191225.png

lap_filter_axis_dma コンフィギュレーションの Run ボタンをクリックすると、ホストアプリケーションとアクセラレーション用カーネルアプリケーションが実行された。
lap_fitler_42_191226.png

経過時間は約 560 us だった。前回よりも約 10 倍速い。
ターミナルのログを示す。
lap_fitler_43_191226.png

後 3 回実行した。
lap_fitler_44_191226.png

lap_fitler_45_191226.png

lap_fitler_46_191226.png

3 回の平均は約 430 us だった。こちらも前回よりも約 10 倍以上速い。

ラプラシアン・フィルタ処理結果のファイルを scp コマンドでホストマシンに持ってきて、確認した。
scp 192.168.3.23:/mnt/temp_lap.bmp /home/masaaki/temp
lap_fitler_47_191226.png

正常にラプラシアン・フィルタ処理されていた。
lap_fitler_48_191226.png
  1. 2019年12月26日 05:04 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン2

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン1”に、AXI4 Master DMA Read ー AXI4-Stream 入力, ラプラシアン・フィルタ処理, AXI4 Stream 出力 ー AXI4 Master DMA Write のラプラシアン・フィルタ処理のカーネルアプリケーションのソースコードを貼ったが、int の 64 ビット幅でバグでたので、int を int32_t に変更した。そして、ホストアプリケーションに getProfilingInfo()による時間計測を追加した。
今回はコードを貼っておく。

bmp_header.h のコードは、”Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン1”に貼ってあるので、それを参照のこと。

カーネルアプリケーションの lap_filter_axis_dma.cpp を貼っておく。

// lap_filter_axis_dma.cpp
// 2019/12/11 by marsee
//

#include <stdio.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include <stdint.h>

static void dma_read(volatile int32_t *inm, hls::stream<ap_axis<32,1,1,1> >& outs, int32_t x_size, int32_t y_size){
    ap_axis<32,1,1,1> pix;

    LOOP_DRY: for(int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        LOOP_DRX: for(int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            pix.data = inm[x_size*y+x];
            if(x==0 || y==0)
                pix.user = 1;
            else
                pix.user = 0;
            if(x==(x_size-1))
                pix.last = 1;
            else
                pix.last = 0;
            outs << pix;
        }
    }
}

static void dma_write(hls::stream<ap_axis<32,1,1,1> >& ins, volatile int32_t *outm, int32_t x_size, int32_t y_size){
    ap_axis<32,1,1,1> pix;

    LOOP_DWY: for(int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        LOOP_DWX: for(int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            ins >> pix;
            outm[x_size*y+x] = pix.data;
        }
    }
}

// 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 にした
int32_t conv_rgb2y(int32_t rgb){
    int32_t r, g, b, y_f;
    int32_t 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
int32_t laplacian_fil(int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1,
        int32_t x1y1, int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2)
{
    int32_t 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);
}


static void
lap_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int32_t x_size, int32_t y_size){
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;

    int32_t line_buf[2][1920]; // supported HD resolution
#pragma HLS array_partition variable=line_buf block factor=2 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

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

    int32_t lap_fil_val;

    Loop1 : do {    // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        ins >> pix;
    } while(pix.user == 0);

    Loop2 : for (int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        Loop3 : for (int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix; // AXI4-Stream からの入力

            Loop4 : for (int k=0; k<3; k++){
                Loop5 : 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];

            int32_t y_val = conv_rgb2y(pix.data);
            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.data = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

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

            if (x==0 && y==0) // 最初のデータでは、TUSERをアサートする
                lap.user = 1;
            else
                lap.user = 0;

            if (x == (x_size-1))    // 行の最後で TLAST をアサートする
                lap.last = 1;
            else
                lap.last = 0;

            outs << lap;    // AXI4-Stream へ出力
        }
    }
}

extern "C" {
void lap_filter_axis_dma(volatile int32_t *inm, volatile int32_t *outm, int32_t x_size, int32_t y_size){
#pragma HLS INTERFACE m_axi port = inm offset = slave bundle = gmem
#pragma HLS INTERFACE m_axi port = outm offset = slave bundle = gmem
#pragma HLS INTERFACE s_axilite port = x_size bundle = control
#pragma HLS INTERFACE s_axilite port = y_size bundle = control
#pragma HLS INTERFACE s_axilite port = return bundle = control
#pragma HLS dataflow
    hls::stream<ap_axis<32,1,1,1> > in_stream;
    hls::stream<ap_axis<32,1,1,1> > out_stream;
#pragma HLS STREAM variable = in_stream depth = 32
#pragma HLS STREAM variable = out_stream depth = 32

    dma_read(inm, in_stream, x_size, y_size);
    lap_filter_axis(in_stream, out_stream, x_size, y_size);
    dma_write(out_stream, outm, x_size, y_size);
}
}


ホストアプリケーションの lap_filter_axis_dma_host.cpp を貼っておく。これも、OpenCL シーケンスは、Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用している。
temp.bmp ファイルを読み込んで、ハードウェアで、ラプラシアン・フィルタ処理を行う。そして、ソフトウェアのラプラシアン・フィルタ処理結果と比較して、temp_lap.bmp ファイルに結果を書き込む。

// lap_filter_axis_dma_host.cpp
// 2019/12/12 by marsee
// 2019/12/25 : getProfilingInfo()による時間計測を追加
//

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用します
// https://github.com/Xilinx/Vitis-Tutorials/blob/master/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp
#define CL_HPP_CL_1_2_DEFAULT_BUILD
#define CL_HPP_TARGET_OPENCL_VERSION 120
#define CL_HPP_MINIMUM_OPENCL_VERSION 120
#define CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY 1
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <CL/cl2.hpp>
#include <iostream>
#include <fstream>
#include <CL/cl_ext_xilinx.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

#include "bmp_header.h"

int laplacian_fil_soft(int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1, int32_t x1y1, int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2);
int32_t conv_rgb2y_soft(int32_t rgb);
int32_t lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int32_t width, int32_t height); // software

static const std::string error_message =
    "Error: Result mismatch:\n"
    "i = %d CPU result = %d Device result = %d\n";

//Some Library functions to be used.
template <typename T>
struct aligned_allocator
{
  using value_type = T;
  T* allocate(std::size_t num)
  {
    void* ptr = nullptr;
    if (posix_memalign(&ptr,4096,num*sizeof(T)))
      throw std::bad_alloc();
    return reinterpret_cast<T*>(ptr);
  }
  void deallocate(T* p, std::size_t num)
  {
    free(p);
  }
};


#define OCL_CHECK(error,call)                                       \
    call;                                                           \
    if (error != CL_SUCCESS) {                                      \
      printf("%s:%d Error calling " #call ", error code is: %d\n",  \
              __FILE__,__LINE__, error);                            \
      exit(EXIT_FAILURE);                                           \
    }

namespace xcl {
std::vector<cl::Device> get_devices(const std::string& vendor_name) {

    size_t i;
    cl_int err;
    std::vector<cl::Platform> platforms;
    OCL_CHECK(err, err = cl::Platform::get(&platforms));
    cl::Platform platform;
    for (i  = 0 ; i < platforms.size(); i++){
        platform = platforms[i];
        OCL_CHECK(err, std::string platformName = platform.getInfo<CL_PLATFORM_NAME>(&err));
        if (platformName == vendor_name){
            std::cout << "Found Platform" << std::endl;
            std::cout << "Platform Name: " << platformName.c_str() << std::endl;
            break;
        }
    }
    if (i == platforms.size()) {
        std::cout << "Error: Failed to find Xilinx platform" << std::endl;
        exit(EXIT_FAILURE);
    }

    //Getting ACCELERATOR Devices and selecting 1st such device
    std::vector<cl::Device> devices;
    OCL_CHECK(err, err = platform.getDevices(CL_DEVICE_TYPE_ACCELERATOR, &devices));
    return devices;
}

std::vector<cl::Device> get_xil_devices() {
    return get_devices("Xilinx");
}

char* read_binary_file(const std::string &xclbin_file_name, unsigned &nb)
{
    std::cout << "INFO: Reading " << xclbin_file_name << std::endl;

    if(access(xclbin_file_name.c_str(), R_OK) != 0) {
        printf("ERROR: %s xclbin not available please build\n", xclbin_file_name.c_str());
        exit(EXIT_FAILURE);
    }
    //Loading XCL Bin into char buffer
    std::cout << "Loading: '" << xclbin_file_name.c_str() << "'\n";
    std::ifstream bin_file(xclbin_file_name.c_str(), std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);
    return buf;
}
};

int main(int argc, char* argv[])
{
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int32_t blue, green, red;
    const char* xclbinFilename;
    hls::stream<ap_axis<32,1,1,1> > ins_soft;
    hls::stream<ap_axis<32,1,1,1> > outs_soft;
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> vals_soft;

    if (argc==2) {
        xclbinFilename = argv[1];
        std::cout <<"Using FPGA binary file specfied through the command line: " << xclbinFilename << std::endl;
    }
    else {
        xclbinFilename = "../lap_filter_axim.xclbin";
        std::cout << "No FPGA binary file specified through the command line, using:" << xclbinFilename <<std::endl;
    }

    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(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    std::vector<int32_t,aligned_allocator<int32_t>> rd_bmp(bmpihr.biWidth * bmpihr.biHeight);
    std::vector<int32_t,aligned_allocator<int32_t>> hw_lapd(bmpihr.biWidth * bmpihr.biHeight);
    size_t size_in_bytes = (bmpihr.biWidth * bmpihr.biHeight) * sizeof(int32_t);

    // 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);

    std::vector<cl::Device> devices = xcl::get_xil_devices();
    cl::Device device = devices[0];
    devices.resize(1);


    // Creating Context and Command Queue for selected device
    cl::Context context(device);
    cl::CommandQueue q(context, device, CL_QUEUE_PROFILING_ENABLE);

    // Load xclbin
    std::cout << "Loading: '" << xclbinFilename << "'\n";
    std::ifstream bin_file(xclbinFilename, std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    unsigned nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);

    // Creating Program from Binary File
    cl::Program::Binaries bins;
    bins.push_back({buf,nb});
    cl::Program program(context, devices, bins);

    // This call will get the kernel object from program. A kernel is an
    // OpenCL function that is executed on the FPGA.
    cl::Kernel krnl_lap_filter(program,"lap_filter_axis_dma");

    // These commands will allocate memory on the Device. The cl::Buffer objects can
    // be used to reference the memory locations on the device.
    cl::Buffer rd_bmp_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            size_in_bytes, rd_bmp.data());
    cl::Buffer hw_lapd_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            size_in_bytes, hw_lapd.data());

    // Data will be transferred from system memory over PCIe to the FPGA on-board
    // DDR memory.
    q.enqueueMigrateMemObjects({rd_bmp_buf},0/* 0 means from host*/);

    //set the kernel Arguments
    krnl_lap_filter.setArg(0,rd_bmp_buf);
    krnl_lap_filter.setArg(1,hw_lapd_buf);
    krnl_lap_filter.setArg(2,bmpihr.biWidth);
    krnl_lap_filter.setArg(3,bmpihr.biHeight);

    cl::Event event;
    uint64_t lapf_start, lapf_end;

    //Launch the Kernel
    q.enqueueTask(krnl_lap_filter, NULL, &event);

    // The result of the previous kernel execution will need to be retrieved in
    // order to view the results. This call will transfer the data from FPGA to
    // source_results vector

    q.enqueueMigrateMemObjects({hw_lapd_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();

    // 時間計測
    event.getProfilingInfo<uint64_t>(CL_PROFILING_COMMAND_START, &lapf_start);
    event.getProfilingInfo<uint64_t>(CL_PROFILING_COMMAND_END, &lapf_end);
    auto lapf_time = lapf_end - lapf_start;
    printf("lap_filter_axis_dma: %lu ns\n", lapf_time);

    // ソフトウェアとハードウェアのチェック
    // ins_soft に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
       pix.user = 0;
        pix.data = int32_t(i);
        ins_soft << pix;
    }

    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            pix.data = rd_bmp[(j*bmpihr.biWidth)+i];

            if (j==0 && i==0)   // 最初のデータの時に TUSER を 1 にする
                pix.user = 1;
            else
                pix.user = 0;

            if (i == bmpihr.biWidth-1) // 行の最後でTLASTをアサートする
                pix.last = 1;
            else
                pix.last = 0;

            ins_soft << pix;
        }
    }
    lap_filter_axis_soft(ins_soft, outs_soft, bmpihr.biWidth, bmpihr.biHeight); // ソフトウェアのラプラシアン・フィルタ

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            outs_soft >> vals_soft;
            if (hw_lapd[y*bmpihr.biWidth+x] != vals_soft.data){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, int(hw_lapd[y*bmpihr.biWidth+x]), int(vals_soft.data));
                //return(1);
            }
        }
    }
    printf("Success HW and SW results match\n");

    // ハードウェアのラプラシアンフィルタの結果を 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(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(uint32_t), 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);

    return(0);
}

int lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int32_t width, int32_t height){
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;
    int32_t **line_buf;
    int32_t pix_mat[3][3];
    int32_t lap_fil_val;
    int32_t i;

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

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

    do {    // user が 1になった時にフレームがスタートする
        ins >> pix;
    } while(pix.user == 0);

    for (int y=0; y<height; y++){
        for (int x=0; x<width; x++){
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix; // AXI4-Stream からの入力

            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];

            int32_t y_val = conv_rgb2y_soft(pix.data);
            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_soft(    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.data = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

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

            if (x==0 && y==0) // 最初のデータでは、TUSERをアサートする
                lap.user = 1;
            else
                lap.user = 0;

            if (x == (width-1))    // 行の最後で TLAST をアサートする
                lap.last = 1;
            else
                lap.last = 0;

            outs << lap;    // AXI4-Stream へ出力
        }
    }

    for (i=0; i<2; i++)
        free(line_buf[i]);
    free(line_buf);

    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 にした
int32_t conv_rgb2y_soft(int32_t rgb){
    int32_t r, g, b, y_f;
    int32_t 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
int32_t laplacian_fil_soft(int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1, int32_t x1y1,
        int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2)
{
    int32_t 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. 2019年12月26日 04:03 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

FPGAの部屋のまとめサイトの更新(2019年12月24日)

FPGAの部屋のまとめサイトを更新しました。 ”Vitis”、”Vitis_HLS”、”Karuta”のカテゴリを追加して、2019 年 12 月 24 日までの記事を更新しました。
  1. 2019年12月24日 21:51 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:0

ultra96v2_min2プラットフォームのPeteLinux 2019.2 が動作するUltra96-V2 にSFTP できた2

ultra96v2_min2プラットフォームのPeteLinux 2019.2 が動作するUltra96-V2 にSFTPできた”で、Ubuntu 18.04 の scp コマンドで、Ubuntu 18.04 の自分のパソコンから Ultra96-V2 のPetaLinux 2019.2 にファイルをアップロードすることができた。今回は、Ultra96-V2 のPetaLinux 2019.2 から Ubuntu 18.04 の自分のパソコンにファイルをダウンロードしてみよう。これは、ラプラシアン・フィルタ処理した画像ファイルを確認するのに使用する。

Ubuntu 18.04 で sudo su で root になっているとする。

Ultra96-V2 の PetaLinux 2019.2 で、
cd /mnt
ls

すると、

root@ultra96v2_min2:/mnt# ls
lap_filter_axim.xclbin laplacian_filter1.exe temp_lap.bmp test.bmp test2.bmp


ラプラシアン・フィルタ処理した結果の画像ファイル temp_lap.bmp があるのが分かる。

Ubuntu 18.04 で
scp 192.168.3.23:/mnt/temp_lap.bmp /home/masaaki/temp
を実行した。
2回パスワードを入れると、画像ファイル(temp_lap.bmp)をダウンロードすることができた。
SFTP_3_191223.png

SFTP_4_191223.png

ダウンロードした temp_lap.bmp ファイルを示す。
SFTP_5_191223.png

SFTP_6_191223.png

これで、Vitis アプリケーションを起動した結果も確認することができるようになった。
  1. 2019年12月23日 05:01 |
  2. PetaLinux
  3. | トラックバック:0
  4. | コメント:0

ultra96v2_min2プラットフォームのPeteLinux 2019.2 が動作するUltra96-V2 にSFTP できた

ultra96v2_min2プラットフォームのPeteLinux 2019.2 が動作するUltra96-V2 にSFTP ができました。

ssh を利用してリモートマシンへ SFTP する scp というコマンドがあることが分かった。(「scp – リモートマシンにファイルコピー - Linuxコマンド」参照)
これでやってみようということで、Ultra96-V2 の電源をON して自分のUbuntu 18.04 のパソコンで scp してみた。
scp -r xrc.log 192.168.3.23:/
私のホーム・ディレクトリにある xrc.log というファイルを試しに Ultra96-V2 に送ってみようということだ。
そうすると、

masaaki@192.168.3.23's password:

と聞かれたが、Ultra96-V2 には masaaki というアカウントは作成していない。しかもUltra96-V2 にも root のパスワードも設定されていないということで、Ultra96-V2 のPetaLinux の root アカウントにパスワードを設定した。パスワードは root だ。

root@ultra96v2_min2:~# passwd
New password:
Retype new password:


自分のUbuntu 18.04 のパソコンで root アカウントに移動した。
sudo su
もう一度、scp -r xrc.log 192.168.3.23:/したら、一回パスワードを入れ直しはしたが、SFTP できた。

root@masaaki-H110M4-M01:/home/masaaki# scp -r xrc.log 192.168.3.23:/
The authenticity of host '192.168.3.23 (192.168.3.23)' can't be established.
RSA key fingerprint is SHA256:fgrPyO8FeroMd3/Q+/SDXL1Ejai1vz+P6SdmSGJpM7s.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.3.23' (RSA) to the list of known hosts.
root@192.168.3.23's password: 
Permission denied, please try again.
root@192.168.3.23's password: 
xrc.log                                       100%  685    57.5KB/s   00:00    


Ultra96-V2 側で見ても xrc.log が / ディレクトリに入っているのが分かる。

root@ultra96v2_min2:~# cd /
root@ultra96v2_min2:/# ls
bin boot dev etc home lib media mnt proc run sbin sys tmp usr var xrc.log


SFTP_1_191222.png

SFTP_2_191222.png
  1. 2019年12月22日 08:53 |
  2. PetaLinux
  3. | トラックバック:0
  4. | コメント:0

laplacian_filter1_host.cpp に時間計測コードを追加2

laplacian_filter1_host.cpp に時間計測コードを追加”の続き。

前回は、OpenCL の getProfilingInfo() を使用した時間計測を試みたがセグメンテーション・フォールトで出来なかった。そこで、 gettimeofday() を使用した時間計測コードを作成して時間計測した。ツィッターで @KSuzukiii さんに「enqueueTaskに event渡してないのが気になりました。」というコメントをいただいたので、調べてみるとたしかにそうだった。コードを修正して、やってみると成功したので、OpenCL の getProfilingInfo() を使用した時間計測を行った。

OpenCL の getProfilingInfo() を使用した時間計測のコードを示す。

    cl::Event event;
    uint64_t lapf_start, lapf_end;

    //Launch the Kernel
    q.enqueueTask(krnl_lap_filter, NULL, &event);

    // The result of the previous kernel execution will need to be retrieved in
    // order to view the results. This call will transfer the data from FPGA to
    // source_results vector

    q.enqueueMigrateMemObjects({hw_lapd_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();

    // 時間計測
    event.getProfilingInfo<uint64_t>(CL_PROFILING_COMMAND_START, &lapf_start);
    event.getProfilingInfo<uint64_t>(CL_PROFILING_COMMAND_END, &lapf_end);
    auto lapf_time = lapf_end - lapf_start;
    printf("laplacian_filter1: %lu ns\n", lapf_time);


enqueueTask()に event を渡すようにした。
更に、”Ultra96-V2 をUSB-LAN 変換アダプタでネットワークに接続”でUltra96-V2 がネットワークに接続されたので、Vitis からラプラシアン・フィルタ処理を行ってみよう。

Vitis 2019.2 の laplacian_filter1 アプリケーション・プロジェクトを示す。
lap_fitler_19_191222.png

まずは、前準備をする。まだ、SFTP でUltra96-V2 にファイルをアップロードできていないため、MicroSD カードの rootfs の /mnt に test.bmp をコピーした。lap_filter_axim.xclbin, laplacian_filter1.exe はアップロード出来ているので、Vitis は Ultra96-V2 にファイルをアップロードできているのだが。。。
lap_fitler_20_191222.png

lap_fitler_21_191222.png

MicroSD カードを Ultra96-V2 に挿入して電源ON した。
ifconfig を行うと、ネットワークに接続されていることが分かる。
lap_fitler_22_191222.png

最初に、zocl ドライバを insmod でロードしておかないとダメなようなので、それを行う。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
lap_fitler_26_191222.png

Vitis 2019.2 の Assistant ウインドウで lalacian_fiter1_system -> Hardware を右クリックし右クリックメニューからRun -> Run Configuratio を選択した。
lap_fitler_30_191222.png

Debugger_laplacian_filter1 を作成した。
Connection は Ultra96-V2 の IP アドレスを入れた Remote を作成した。
lap_fitler_23_191222.png

lap_fitler_24_191222.png

Run ボタンをクリックしてアプリケーションを起動した。
結果が表示された。
経過時間は 5064440 ns だった。つまり約 5.1 ms だった。gettimeofday() で計測したのとほとんど変わらない。
lap_fitler_27_191222.png

後 3 回実行してみよう。
Vitis 2019.2 の Assistant ウインドウで lalacian_fiter1_system -> Hardware を右クリックし右クリックメニューからRun -> Debugger_laplacian_filter1 を選択した。
lap_fitler_31_191222.png

laplacian_filter1 アプリケーションが起動した。
lap_fitler_28_191222.png

同様に後 2 回実行した。
lap_fitler_29_191222.png

lap_fitler_32_191222.png

平均すると約 5.0 ms だった。

シリアル・コンソールには[drm] メッセージが表示されている。
lap_fitler_33_191222.png

/mnt ディレクトリには、lap_filter_axim.xclbin, laplacian_filter1.exe がアップロードされていた。
lap_fitler_34_191222.png
  1. 2019年12月22日 06:25 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

スター・ウォーズ/スカイウォーカーの夜明けを見てきました

今日は、公開日に休暇を取って奥さんとスター・ウォーズ/スカイウォーカーの夜明けを見てきました。
思えば、18歳、大学1年生のときに始まったスターウォーズ、友達と見に行きましたが、迫力に圧倒されてから 43 年。完結しましたね。考え深いです。。。
最後はあっけなく敵がやられた気がしますが、あんなものでしょう?
  1. 2019年12月20日 20:24 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

Ultra96-V2 をUSB-LAN 変換アダプタでネットワークに接続

Ultra96-V2 のPetaLinux 2019.2 で無線LAN を使用して、ネットワークに接続するためにドライバとファームウェアをインストールしようとしていたが、うまく行かなかった。そこでUSB-LAN 変換アダプタを使用して、ネットワークに接続することにした。
USB-LAN 変換アダプタは前に使用していてパソコンのリプレースのために余っていたPLANEX のGU-1000T を使用する。このUSB-LAN 変換アダプタをUltra96-V2 のUSB-A コネクタに接続して、有線LAN で接続しよう。

@ciniml さんがUSB ガジェットドライバを有効にすれば行けると教えてくれたので、patalinux-config -c kernel して調べてみよう。
(2019/12/20:追記) @ciniml さんは言ってらしたのは、ultra96側がUSBデバイスとして動作してPCからはネットワークインターフェースに見えるようにするUSBガジェットドライバの話だそうです。
patalinux-config -c kernel
lap_fitler_18_191219.png

Device Drivers > USB support > USB Gadget Support は有効になっていた。更に下のEthernet Gadget はM でRANDIS support も有効になっていた。デフォルトでこれなので、もしかするとUSB-LAN 変換アダプタを接続すればネットワークにつながるのか?という期待が出てきた。

Vitis アプリケーション・プラットフォームのいま使用しているラプラシアン・フィルタ処理用のMicroSD カードでUltra96-V2を起動して、USB-LAN 変換アダプタを接続すると自動的に認識して、DHCP でIP アドレスが割り振られた。やった〜。ネットワークに接続できた。
lap_fitler_16_191219.png

lap_fitler_17_191219.png

USB-LAN 変換アダプタを接続した時のログがこれだ。

root@ultra96v2_min2:~# [  103.197932] usb 1-1.1: new high-speed USB device number 4 using xhci-hcd
[  103.318712] usb 1-1.1: New USB device found, idVendor=0b95, idProduct=1780, bcdDevice= 0.01
[  103.327060] usb 1-1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  103.334370] usb 1-1.1: Product: GU-1000T
[  103.338288] usb 1-1.1: Manufacturer: PLANEX COM. Inc.
[  103.343336] usb 1-1.1: SerialNumber: 020707
[  103.774411] asix 1-1.1:1.0 eth0: register 'asix' at usb-xhci-hcd.0.auto-1.1, ASIX AX88178 USB 2.0 Ethernet, 00:22:cf:00:0c:37
[  104.610697] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready



その後、USB-LAN 変換アダプタに LAN ケーブルを接続した時にこの表示が出たので、ifconfig してみたが、この時には IPv4 のアドレスはまだ割り振られていなかった。

root@ultra96v2_min2:~# [  140.227180] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[  140.237517] asix 1-1.1:1.0 eth0: link up, 1000Mbps, full-duplex, lpa 0xC5E1
ifconfig
eth0      Link encap:Ethernet  HWaddr 00:22:CF:00:0C:37  
          inet6 addr: fe80::222:cfff:fe00:c37/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:21 errors:0 dropped:11 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:966 (966.0 B)  TX bytes:688 (688.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


しばらくして、ifconfig したら IPv4 のアドレスが割り振られていた。

root@ultra96v2_min2:~# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:22:CF:00:0C:37  
          inet addr:192.168.3.23  Bcast:192.168.3.255  Mask:255.255.255.0
          inet6 addr: 2400:2411:c841:f700:222:cfff:fe00:c37/64 Scope:Global
          inet6 addr: fe80::222:cfff:fe00:c37/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:90 errors:0 dropped:44 overruns:0 frame:0
          TX packets:52 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:8562 (8.3 KiB)  TX bytes:9737 (9.5 KiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


(2019/12/28:追記)PetaLinux のUSB-LAN 変換アダプタのドライバの設定方法が分かりました。
petalinux-config -c kernel
で、Device Drivers > Network device support > USB Network Adapters の中に、ASIX AX88179/178A USB 3.0/2.0 to Gigabit Ethernet Adapters に*が入ってました。疑問なのはASIX AX88179 で接続できなかったことです?
対応するUSB-LAN アダプタのところに*を入れてば良さそうです。
lap_fitler_58_191228.png
  1. 2019年12月20日 04:54 |
  2. PetaLinux
  3. | トラックバック:0
  4. | コメント:0

laplacian_filter1_host.cpp に時間計測コードを追加

最初にカーネルアプリケーションがAXI4 Master の laplacian_filter1_host.cpp をやってみて、次に、DMA Write - AXI4 Stream - DMA Readの lap_filter_axis_dma.cpp をやってみようとしている。この 2 つの実装をやってみることで、カーネルアプリケーションの書き方のバリエーションは示せるが、実機での性能差を示せていないのが、気がかりだった。そこで、時間計測コードを入れてみることにした。

時間計測の方法は、Xilinx の example では、Vitis_Accel_Examples/cpp_kernels/array_partition/src/host.cpp で使用されている。それは、OpenCL の getProfilingInfo() を使用した時間計測だった。私もコードを引用して試してみたのだが、どうしても実行するとセグメンテーション・フォールトになってしまう? コードを示す。

    cl::Event event;
    uint64_t lapf_start, lapf_end;

    //Launch the Kernel
    q.enqueueTask(krnl_lap_filter);

    // The result of the previous kernel execution will need to be retrieved in
    // order to view the results. This call will transfer the data from FPGA to
    // source_results vector

    q.enqueueMigrateMemObjects({hw_lapd_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();

    // 時間計測
    event.getProfilingInfo<uint64_t>(CL_PROFILING_COMMAND_START, &lapf_start);
    event.getProfilingInfo<uint64_t>(CL_PROFILING_COMMAND_END, &lapf_end);
    auto lapf_time = lapf_end - lapf_start;
    printf("laplacian_filter1: %23lu ns\n", lapf_time);


コンパイルは通るのだが。。。

そこで、正確ではないかも知れないが、従来から使用している gettimeofday() を使うことにした。
q.enqueueTask(krnl_lap_filter); から q.finish(); までの間に、gettimeofday() による時間計測を入れた。

    // 時間計測
    struct timeval start_time, end_time;
    gettimeofday(&start_time, NULL);

    //Launch the Kernel
    q.enqueueTask(krnl_lap_filter);

    // The result of the previous kernel execution will need to be retrieved in
    // order to view the results. This call will transfer the data from FPGA to
    // source_results vector

    q.enqueueMigrateMemObjects({hw_lapd_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();

    // 時間計測
    gettimeofday(&end_time, NULL);
    if (end_time.tv_usec < start_time.tv_usec) {
        printf("total time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec - 1, 1000000 + end_time.tv_usec - start_time.tv_usec);
    }
    else {
        printf("total time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec, end_time.tv_usec - start_time.tv_usec);
    }


このコードの変更を行って、もう一度ビルドした。
sd_card ディレクトリを見ると更新されているのは、laplacian_filter1.exe だけだった。
lap_fitler_11_191219.png

MircoSD カードの第1パーティションのファイルの内の laplacian_filter1.exe を交換し、Ultra96-V2 に入れて電源ON して実行してみたところ、最初はキャッシュが効いてないからか? 5.3 ms 程度の実行時間だったが、キャッシュが効くと、5.0 ms 程度の実行時間になるようだ。
lap_fitler_12_191219.png

lap_fitler_13_191219.png

lap_fitler_14_191219.png

ログを示す。

ultra96v2_min2 login: root
root@ultra96v2_min2:~# insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
[   35.365631] zocl: loading out-of-tree module taints kernel.
[   35.374889] [drm] Probing for xlnx,zocl
[   35.378842] [drm] FPGA programming device pcap founded.
[   35.384062] [drm] PR Isolation addr 0x0
[   35.384788] [drm] Initialized zocl 2018.2.1 20180313 for a0000000.zyxclmm_drm on minor 1
root@ultra96v2_min2:~# cd /run/media/mmcblk0p1/
root@ultra96v2_min2:/run/media/mmcblk0p1# export XILINX_XRT=/usr
root@ultra96v2_min2:/run/media/mmcblk0p1# ./laplacian_filter1.exe lap_filter_axim.xclbin 
Using FPGA binary file specfied through the command line: lap_filter_axim.xclbin
[   44.524112] [drm] Pid 2175 opened device
[   44.528070] [drm] Pid 2175 closed device
[   44.543051] [drm] Pid 2175 opened device
Found Platform
Platform Name: Xilinx
Loading: 'lap_filter_axim.xclbin'
[   44.837024] [drm] Finding IP_LAYOUT section header
[   44.837038] [drm] Section IP_LAYOUT details:
[   44.841914] [drm]   offset = 0x54fcf8
[   44.846176] [drm]   size = 0x58
[   44.849841] [drm] Finding DEBUG_IP_LAYOUT section header
[   44.852983] [drm] AXLF section DEBUG_IP_LAYOUT header not found
[   44.858288] [drm] Finding CONNECTIVITY section header
[   44.864196] [drm] Section CONNECTIVITY details:
[   44.869238] [drm]   offset = 0x54fd50
[   44.873763] [drm]   size = 0x1c
[   44.877474] [drm] Finding MEM_TOPOLOGY section header
[   44.880619] [drm] Section MEM_TOPOLOGY details:
[   44.885663] [drm]   offset = 0x54fc00
[   44.890184] [drm]   size = 0xf8
[   44.895496] [drm] No ERT scheduler on MPSoC, using KDS
[   44.904186] [drm] Fail to install CU 0 interrupt handler: -22. Fall back to polling mode.
[   44.912358] [drm] scheduler config ert(0)
[   44.912365] [drm]   cus(1)
[   44.916372] [drm]   slots(16)
[   44.919073] [drm]   num_cu_masks(1)
[   44.922030] [drm]   cu_shift(16)
[   44.925506] [drm]   cu_base(0xa0000000)
total time = 0.005286 sec
Success HW and SW results match
[   44.928725] [drm]   polling(1)
[   44.944898] [drm] zocl_free_userptr_bo: obj 0x0000000071419c19
[   44.951976] [drm] zocl_free_userptr_bo: obj 0x0000000087f37e9e
[   44.962066] [drm] Pid 2175 closed device
root@ultra96v2_min2:/run/media/mmcblk0p1# ./laplacian_filter1.exe lap_filter_axim.xclbin 
Using FPGA binary file specfied through the command line: lap_fil[   63.265894] [drm] Pid 2183 opened device
ter_axim.xclbin
[   63.274411] [drm] Pid 2183 closed device
[   63.280195] [drm] Pid 2183 opened device
Found Platform
Platform Name: Xilinx
Loading: 'lap_filter_axim.xclbin'
[   63.319807] [drm] The XCLBIN already loaded. Don't need to reload.
[   63.321429] [drm] Reconfiguration not supported
[   63.336946] [drm] User buffer is not physical contiguous
[   63.342258] [drm] zocl_free_userptr_bo: obj 0x00000000838768cc
[   63.343502] [drm] User buffer is not physical contiguous
total time = 0.005034 sec
Success HW and SW results match
[   63.354671] [drm] zocl_free_userptr_bo: obj 0x00000000c279b619
[   63.388743] [drm] Pid 2183 closed device
root@ultra96v2_min2:/run/media/mmcblk0p1# ./laplacian_filter1.exe lap_filter_axim.xclbin 
Using FPGA binary file specfied through the command line: lap_fil[   67.393815] [drm] Pid 2191 opened device
ter_axim.xclbin
[   67.402303] [drm] Pid 2191 closed device
[   67.408002] [drm] Pid 2191 opened device
Found Platform
Platform Name: Xilinx
Loading: 'lap_filter_axim.xclbin'
[   67.447900] [drm] The XCLBIN already loaded. Don't need to reload.
[   67.449501] [drm] Reconfiguration not supported
[   67.464983] [drm] User buffer is not physical contiguous
[   67.470296] [drm] zocl_free_userptr_bo: obj 0x00000000743cfe90
[   67.471283] [drm] User buffer is not physical contiguous
total time = 0.004995 sec
Success HW and SW results match
[   67.482428] [drm] zocl_free_userptr_bo: obj 0x00000000892f7931
[   67.493126] [drm] Pid 2191 closed device
root@ultra96v2_min2:/run/media/mmcblk0p1# ./laplacian_filter1.exe lap_filter_axim.xclbin 
Using FPGA binary file specfied through the command line: lap_fil[   77.145820] [drm] Pid 2199 opened device
ter_axim.xclbin
[   77.154301] [drm] Pid 2199 closed device
[   77.159842] [drm] Pid 2199 opened device
Found Platform
Platform Name: Xilinx
Loading: 'lap_filter_axim.xclbin'
[   77.199447] [drm] The XCLBIN already loaded. Don't need to reload.
[   77.201013] [drm] Reconfiguration not supported
[   77.216169] [drm] User buffer is not physical contiguous
[   77.221485] [drm] zocl_free_userptr_bo: obj 0x00000000743cfe90
[   77.222511] [drm] User buffer is not physical contiguous
total time = 0.005021 sec
Success HW and SW results match
[   77.233646] [drm] zocl_free_userptr_bo: obj 0x0000000055b3bfae
[   77.247102] [drm] Pid 2199 closed device


現在の laplacian_filter1_host.cpp を貼っておく。

// laplacian_filter1_host.cpp
// 2019/12/09 by marsee
// 2019/12/19 : gettimeofday()を使用した時間計測コードを追加した
//

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用します
// https://github.com/Xilinx/Vitis-Tutorials/blob/master/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp
#define CL_HPP_CL_1_2_DEFAULT_BUILD
#define CL_HPP_TARGET_OPENCL_VERSION 120
#define CL_HPP_MINIMUM_OPENCL_VERSION 120
#define CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY 1
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <CL/cl2.hpp>
#include <iostream>
#include <fstream>
#include <CL/cl_ext_xilinx.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.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(volatile int *cam_fb, volatile int *lap_fb);    // hardware
void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height); // software

static const std::string error_message =
    "Error: Result mismatch:\n"
    "i = %d CPU result = %d Device result = %d\n";

//Some Library functions to be used.
template <typename T>
struct aligned_allocator
{
  using value_type = T;
  T* allocate(std::size_t num)
  {
    void* ptr = nullptr;
    if (posix_memalign(&ptr,4096,num*sizeof(T)))
      throw std::bad_alloc();
    return reinterpret_cast<T*>(ptr);
  }
  void deallocate(T* p, std::size_t num)
  {
    free(p);
  }
};


#define OCL_CHECK(error,call)                                       \
    call;                                                           \
    if (error != CL_SUCCESS) {                                      \
      printf("%s:%d Error calling " #call ", error code is: %d\n",  \
              __FILE__,__LINE__, error);                            \
      exit(EXIT_FAILURE);                                           \
    }

namespace xcl {
std::vector<cl::Device> get_devices(const std::string& vendor_name) {

    size_t i;
    cl_int err;
    std::vector<cl::Platform> platforms;
    OCL_CHECK(err, err = cl::Platform::get(&platforms));
    cl::Platform platform;
    for (i  = 0 ; i < platforms.size(); i++){
        platform = platforms[i];
        OCL_CHECK(err, std::string platformName = platform.getInfo<CL_PLATFORM_NAME>(&err));
        if (platformName == vendor_name){
            std::cout << "Found Platform" << std::endl;
            std::cout << "Platform Name: " << platformName.c_str() << std::endl;
            break;
        }
    }
    if (i == platforms.size()) {
        std::cout << "Error: Failed to find Xilinx platform" << std::endl;
        exit(EXIT_FAILURE);
    }

    //Getting ACCELERATOR Devices and selecting 1st such device
    std::vector<cl::Device> devices;
    OCL_CHECK(err, err = platform.getDevices(CL_DEVICE_TYPE_ACCELERATOR, &devices));
    return devices;
}

std::vector<cl::Device> get_xil_devices() {
    return get_devices("Xilinx");
}

char* read_binary_file(const std::string &xclbin_file_name, unsigned &nb)
{
    std::cout << "INFO: Reading " << xclbin_file_name << std::endl;

    if(access(xclbin_file_name.c_str(), R_OK) != 0) {
        printf("ERROR: %s xclbin not available please build\n", xclbin_file_name.c_str());
        exit(EXIT_FAILURE);
    }
    //Loading XCL Bin into char buffer
    std::cout << "Loading: '" << xclbin_file_name.c_str() << "'\n";
    std::ifstream bin_file(xclbin_file_name.c_str(), std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);
    return buf;
}
};

int main(int argc, char* argv[])
{
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int blue, green, red;
    const char* xclbinFilename;

    if (argc==2) {
        xclbinFilename = argv[1];
        std::cout <<"Using FPGA binary file specfied through the command line: " << xclbinFilename << std::endl;
    }
    else {
        xclbinFilename = "../lap_filter_axim.xclbin";
        std::cout << "No FPGA binary file specified through the command line, using:" << xclbinFilename <<std::endl;
    }

    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(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    std::vector<int,aligned_allocator<int>> rd_bmp(bmpihr.biWidth * bmpihr.biHeight);
    std::vector<int,aligned_allocator<int>> hw_lapd(bmpihr.biWidth * bmpihr.biHeight);
    std::vector<int,aligned_allocator<int>> sw_lapd(bmpihr.biWidth * bmpihr.biHeight);
    size_t size_in_bytes = (bmpihr.biWidth * bmpihr.biHeight) * sizeof(int);

    // 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);

    std::vector<cl::Device> devices = xcl::get_xil_devices();
    cl::Device device = devices[0];
    devices.resize(1);


    // Creating Context and Command Queue for selected device
    cl::Context context(device);
    cl::CommandQueue q(context, device, CL_QUEUE_PROFILING_ENABLE);

    // Load xclbin
    std::cout << "Loading: '" << xclbinFilename << "'\n";
    std::ifstream bin_file(xclbinFilename, std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    unsigned nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);

    // Creating Program from Binary File
    cl::Program::Binaries bins;
    bins.push_back({buf,nb});
    cl::Program program(context, devices, bins);

    // This call will get the kernel object from program. A kernel is an
    // OpenCL function that is executed on the FPGA.
    cl::Kernel krnl_lap_filter(program,"lap_filter_axim");

    // These commands will allocate memory on the Device. The cl::Buffer objects can
    // be used to reference the memory locations on the device.
    cl::Buffer rd_bmp_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            size_in_bytes, rd_bmp.data());
    cl::Buffer hw_lapd_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            size_in_bytes, hw_lapd.data());

    // Data will be transferred from system memory over PCIe to the FPGA on-board
    // DDR memory.
    q.enqueueMigrateMemObjects({rd_bmp_buf},0/* 0 means from host*/);

    //set the kernel Arguments
    krnl_lap_filter.setArg(0,rd_bmp_buf);
    krnl_lap_filter.setArg(1,hw_lapd_buf);
    krnl_lap_filter.setArg(2,bmpihr.biWidth);
    krnl_lap_filter.setArg(3,bmpihr.biHeight);

    // 時間計測
    struct timeval start_time, end_time;
    gettimeofday(&start_time, NULL);

    //Launch the Kernel
    q.enqueueTask(krnl_lap_filter);

    // The result of the previous kernel execution will need to be retrieved in
    // order to view the results. This call will transfer the data from FPGA to
    // source_results vector

    q.enqueueMigrateMemObjects({hw_lapd_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();

    // 時間計測
    gettimeofday(&end_time, NULL);
    if (end_time.tv_usec < start_time.tv_usec) {
        printf("total time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec - 1, 1000000 + end_time.tv_usec - start_time.tv_usec);
    }
    else {
        printf("total time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec, end_time.tv_usec - start_time.tv_usec);
    }

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

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            if (hw_lapd[y*bmpihr.biWidth+x] != sw_lapd[y*bmpihr.biWidth+x]){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, hw_lapd[y*bmpihr.biWidth+x], sw_lapd[y*bmpihr.biWidth+x]);
                //return(1);
            }
        }
    }
    printf("Success HW and SW results match\n");

    // ハードウェアのラプラシアンフィルタの結果を 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(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(uint32_t), 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);

    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. 2019年12月19日 04:50 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン1

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタのAXI Master バージョンを作ったが、今回は、AXI4 Master DMA Read ー AXI4-Stream 入力, ラプラシアン・フィルタ処理, AXI4 Stream 出力 ー AXI4 Master DMA Write のラプラシアン・フィルタ処理のカーネルアプリケーションを作成する。

(2019/12/26:追記) このコードは int が 64 ビット幅だったことでバグがでています。新しいソースコードを”Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI4-Streamバージョン2”に貼っておきますので、そちらをご覧ください。

ソースコードを貼っておく。
bmp_header.h を示す。

// bmp_header.h
// BMP ファイルフォーマットから引用させて頂きました
// http://www.kk.iij4u.or.jp/~kondo/bmp/
//
// 2017/05/04 : takseiさんのご指摘によりintX_tを使った宣言に変更。takseiさんありがとうございました
//              変数の型のサイズの違いによってLinuxの64ビット版では動作しなかったためです
//              http://marsee101.blog19.fc2.com/blog-entry-3354.html#comment2808
//

#include <stdio.h>
#include <stdint.h>

// BITMAPFILEHEADER 14bytes
typedef struct tagBITMAPFILEHEADER {
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
} BITMAPFILEHEADER;

// BITMAPINFOHEADER 40bytes
typedef struct tagBITMAPINFOHEADER{
    uint32_t biSize;
    int32_t biWidth;
    int32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    int32_t biXPixPerMeter;
    int32_t biYPixPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImporant;
} BITMAPINFOHEADER;

typedef struct BMP24bitsFORMAT {
    uint8_t blue;
    uint8_t green;
    uint8_t red;
} BMP24FORMAT;


カーネルアプリケーションの lap_filter_axis_dma.cpp を貼っておく。

// lap_filter_axis_dma.cpp
// 2019/12/11 by marsee
//

#include <stdio.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

static void dma_read(volatile int *inm, hls::stream<ap_axis<32,1,1,1> >& outs, int x_size, int y_size){
    ap_axis<32,1,1,1> pix;

    LOOP_DRY: for(int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        LOOP_DRX: for(int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            pix.data = inm[x_size*y+x];
            if(x==0 || y==0)
                pix.user = 1;
            else
                pix.user = 0;
            if(x==(x_size-1))
                pix.last = 1;
            else
                pix.last = 0;
            outs << pix;
        }
    }
}

static void dma_write(hls::stream<ap_axis<32,1,1,1> >& ins, volatile int *outm, int x_size, int y_size){
    ap_axis<32,1,1,1> pix;

    LOOP_DWY: for(int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        LOOP_DWX: for(int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            ins >> pix;
            outm[x_size*y+x] = pix.data;
        }
    }
}

// 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);
}


static void
lap_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int x_size, int y_size){
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;

    unsigned int line_buf[2][1920]; // supported HD resolution
#pragma HLS array_partition variable=line_buf block factor=2 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

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

    int lap_fil_val;

    Loop1 : do {    // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        ins >> pix;
    } while(pix.user == 0);

    Loop2 : for (int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        Loop3 : for (int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix; // AXI4-Stream からの入力

            Loop4 : for (int k=0; k<3; k++){
                Loop5 : 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.data);
            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.data = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

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

            if (x==0 && y==0) // 最初のデータでは、TUSERをアサートする
                lap.user = 1;
            else
                lap.user = 0;

            if (x == (x_size-1))    // 行の最後で TLAST をアサートする
                lap.last = 1;
            else
                lap.last = 0;

            outs << lap;    // AXI4-Stream へ出力
        }
    }
}

extern "C" {
void lap_filter_axis_dma(volatile int *inm, volatile int *outm, int x_size, int y_size){
#pragma HLS INTERFACE m_axi port = inm offset = slave bundle = gmem
#pragma HLS INTERFACE m_axi port = outm offset = slave bundle = gmem
#pragma HLS INTERFACE s_axilite port = x_size bundle = control
#pragma HLS INTERFACE s_axilite port = y_size bundle = control
#pragma HLS INTERFACE s_axilite port = return bundle = control
#pragma HLS dataflow
    hls::stream<ap_axis<32,1,1,1> > in_stream;
    hls::stream<ap_axis<32,1,1,1> > out_stream;
#pragma HLS STREAM variable = in_stream depth = 32
#pragma HLS STREAM variable = out_stream depth = 32

    dma_read(inm, in_stream, x_size, y_size);
    lap_filter_axis(in_stream, out_stream, x_size, y_size);
    dma_write(out_stream, outm, x_size, y_size);
}
}


ホストアプリケーションの lap_filter_axis_dma_host.cpp を貼っておく。これも、OpenCL シーケンスは、Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用している。
temp.bmp ファイルを読み込んで、ハードウェアで、ラプラシアン・フィルタ処理を行う。そして、ソフトウェアのラプラシアン・フィルタ処理結果と比較して、temp_lap.bmp ファイルに結果を書き込む。

// lap_filter_axis_dma_host.cpp
// 2019/12/12 by marsee
//

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用します
// https://github.com/Xilinx/Vitis-Tutorials/blob/master/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp
#define CL_HPP_CL_1_2_DEFAULT_BUILD
#define CL_HPP_TARGET_OPENCL_VERSION 120
#define CL_HPP_MINIMUM_OPENCL_VERSION 120
#define CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY 1
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <CL/cl2.hpp>
#include <iostream>
#include <fstream>
#include <CL/cl_ext_xilinx.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.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_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int width, int height); // software

static const std::string error_message =
    "Error: Result mismatch:\n"
    "i = %d CPU result = %d Device result = %d\n";

//Some Library functions to be used.
template <typename T>
struct aligned_allocator
{
  using value_type = T;
  T* allocate(std::size_t num)
  {
    void* ptr = nullptr;
    if (posix_memalign(&ptr,4096,num*sizeof(T)))
      throw std::bad_alloc();
    return reinterpret_cast<T*>(ptr);
  }
  void deallocate(T* p, std::size_t num)
  {
    free(p);
  }
};


#define OCL_CHECK(error,call)                                       \
    call;                                                           \
    if (error != CL_SUCCESS) {                                      \
      printf("%s:%d Error calling " #call ", error code is: %d\n",  \
              __FILE__,__LINE__, error);                            \
      exit(EXIT_FAILURE);                                           \
    }

namespace xcl {
std::vector<cl::Device> get_devices(const std::string& vendor_name) {

    size_t i;
    cl_int err;
    std::vector<cl::Platform> platforms;
    OCL_CHECK(err, err = cl::Platform::get(&platforms));
    cl::Platform platform;
    for (i  = 0 ; i < platforms.size(); i++){
        platform = platforms[i];
        OCL_CHECK(err, std::string platformName = platform.getInfo<CL_PLATFORM_NAME>(&err));
        if (platformName == vendor_name){
            std::cout << "Found Platform" << std::endl;
            std::cout << "Platform Name: " << platformName.c_str() << std::endl;
            break;
        }
    }
    if (i == platforms.size()) {
        std::cout << "Error: Failed to find Xilinx platform" << std::endl;
        exit(EXIT_FAILURE);
    }

    //Getting ACCELERATOR Devices and selecting 1st such device
    std::vector<cl::Device> devices;
    OCL_CHECK(err, err = platform.getDevices(CL_DEVICE_TYPE_ACCELERATOR, &devices));
    return devices;
}

std::vector<cl::Device> get_xil_devices() {
    return get_devices("Xilinx");
}

char* read_binary_file(const std::string &xclbin_file_name, unsigned &nb)
{
    std::cout << "INFO: Reading " << xclbin_file_name << std::endl;

 if(access(xclbin_file_name.c_str(), R_OK) != 0) {
  printf("ERROR: %s xclbin not available please build\n", xclbin_file_name.c_str());
  exit(EXIT_FAILURE);
 }
    //Loading XCL Bin into char buffer
    std::cout << "Loading: '" << xclbin_file_name.c_str() << "'\n";
    std::ifstream bin_file(xclbin_file_name.c_str(), std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);
    return buf;
}
};

int main(int argc, char* argv[])
{
 long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int blue, green, red;
    const char* xclbinFilename;
 hls::stream<ap_axis<32,1,1,1> > ins_soft;
    hls::stream<ap_axis<32,1,1,1> > outs_soft;
 ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> vals_soft;

 if (argc==2) {
  xclbinFilename = argv[1];
  std::cout <<"Using FPGA binary file specfied through the command line: " << xclbinFilename << std::endl;
 }
 else {
  xclbinFilename = "../lap_filter_axim.xclbin";
  std::cout << "No FPGA binary file specified through the command line, using:" << xclbinFilename <<std::endl;
 }

 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(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

 // ピクセルを入れるメモリをアロケートする
    std::vector<int,aligned_allocator<int>> rd_bmp(bmpihr.biWidth * bmpihr.biHeight);
    std::vector<int,aligned_allocator<int>> hw_lapd(bmpihr.biWidth * bmpihr.biHeight);
    std::vector<int,aligned_allocator<int>> sw_lapd(bmpihr.biWidth * bmpihr.biHeight);
    size_t size_in_bytes = (bmpihr.biWidth * bmpihr.biHeight) * sizeof(int);

 // 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);

    std::vector<cl::Device> devices = xcl::get_xil_devices();
    cl::Device device = devices[0];
    devices.resize(1);


    // Creating Context and Command Queue for selected device
    cl::Context context(device);
    cl::CommandQueue q(context, device, CL_QUEUE_PROFILING_ENABLE);

    // Load xclbin
    std::cout << "Loading: '" << xclbinFilename << "'\n";
    std::ifstream bin_file(xclbinFilename, std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    unsigned nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);

    // Creating Program from Binary File
    cl::Program::Binaries bins;
    bins.push_back({buf,nb});
    cl::Program program(context, devices, bins);

    // This call will get the kernel object from program. A kernel is an
    // OpenCL function that is executed on the FPGA.
    cl::Kernel krnl_lap_filter(program,"lap_filter_axis_dma");

    // These commands will allocate memory on the Device. The cl::Buffer objects can
    // be used to reference the memory locations on the device.
    cl::Buffer rd_bmp_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            size_in_bytes, rd_bmp.data());
    cl::Buffer hw_lapd_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            size_in_bytes, hw_lapd.data());

    // Data will be transferred from system memory over PCIe to the FPGA on-board
    // DDR memory.
    q.enqueueMigrateMemObjects({rd_bmp_buf},0/* 0 means from host*/);

    //set the kernel Arguments
    krnl_lap_filter.setArg(0,rd_bmp_buf);
    krnl_lap_filter.setArg(1,hw_lapd_buf);
    krnl_lap_filter.setArg(2,bmpihr.biWidth);
    krnl_lap_filter.setArg(3,bmpihr.biHeight);

    //Launch the Kernel
    q.enqueueTask(krnl_lap_filter);

    // The result of the previous kernel execution will need to be retrieved in
    // order to view the results. This call will transfer the data from FPGA to
    // source_results vector

    q.enqueueMigrateMemObjects({hw_lapd_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();

 // ソフトウェアとハードウェアのチェック
    // ins_soft に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
       pix.user = 0;
      pix.data = i;
     ins_soft << pix;
    }

    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
         pix.data = (ap_int<32>)rd_bmp[(j*bmpihr.biWidth)+i];

            if (j==0 && i==0) // 最初のデータの時に TUSER を 1 にする
             pix.user = 1;
            else
             pix.user = 0;

            if (i == bmpihr.biWidth-1) // 行の最後でTLASTをアサートする
                pix.last = 1;
            else
                pix.last = 0;

            ins_soft << pix;
        }
    }
    lap_filter_axis_soft(ins_soft, outs_soft, bmpihr.biWidth, bmpihr.biHeight); // ソフトウェアのラプラシアン・フィルタ

 // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
 for (y=0; y<bmpihr.biHeight; y++){
  for (x=0; x<bmpihr.biWidth; x++){
   outs_soft >> vals_soft;
   if (hw_lapd[y*bmpihr.biWidth+x] != (int)vals_soft.data){
    printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, hw_lapd[y*bmpihr.biWidth+x], sw_lapd[y*bmpihr.biWidth+x]);
    //return(1);
   }
  }
 }
 printf("Success HW and SW results match\n");

 // ハードウェアのラプラシアンフィルタの結果を 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(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(uint32_t), 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);

 return(0);
}

int lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int width, int height){
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;
    unsigned int **line_buf;
    int pix_mat[3][3];
    int lap_fil_val;
    int i;

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

    // メモリをアロケートする
    for (i=0; i<2; 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);
        }
    }

    do {    // user が 1になった時にフレームがスタートする
        ins >> pix;
    } while(pix.user == 0);

    for (int y=0; y<height; y++){
        for (int x=0; x<width; x++){
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix; // AXI4-Stream からの入力

            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_soft(pix.data);
            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_soft(    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.data = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる

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

            if (x==0 && y==0) // 最初のデータでは、TUSERをアサートする
                lap.user = 1;
            else
                lap.user = 0;
            
            if (x == (width-1))    // 行の最後で TLAST をアサートする
                lap.last = 1;
            else
                lap.last = 0;

            outs << lap;    // AXI4-Stream へ出力
        }
    }

    for (i=0; i<2; i++)
        free(line_buf[i]);
    free(line_buf);

    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_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. 2019年12月18日 05:13 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI Masterバージョン2

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI Masterバージョン1”の続き。

前回は、ラプラシアン・フィルタをVitis 2019.2 アプリケーション・プロジェクトにして作成してみようということで、ソースコードを貼り付けた。今回は、Vitis 2019.2 のアプリケーション・プロジェクト laplacian_filter1 を作成し、ビルドしてUltra96-V2 の実機で確認してみよう。

Vitis 2019.2 のアプリケーション・プロジェクト laplacian_filter1 を示す。すでにビルド済みだ。
lap_fitler_5_191217.png

lap_fitler_15_191219.png

Vivado HLS のレポートを確認した。
lap_fitler_6_191217.png

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

sd_card ディレクトリの内容をMicroSD カードの第 1 パーティションに書き込んだ。temp.bmp も書き込んだ。
lap_fitler_8_191217.png

MicroSD カードの第 2 パーティションには、aarch64-xilinx-linux の Root FS を書き込んである。
MicroSD カードをUltra96-V2 に入れて電源ON した。
PetaLinux が起動した。
root ノーパスでログインした。
zocl ドライバを insmod でロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko

アプリケーションを起動した。
cd /run/media/mmcblk0p1/
export XILINX_XRT=/usr
./laplacian_filter1.exe lap_filter_axim.xclbin

lap_fitler_1_191215.png

lap_fitler_2_191215.png

”Success HW and SW results match”が表示されている。成功だ。。。

もう一度、
./laplacian_filter1.exe lap_filter_axim.xclbin
を起動した。
lap_fitler_3_191215.png

test.bmp をラプラシアン・フィルタ処理した temp_lap.bmp が生成されているのが分かる。うまく行った。
lap_fitler_4_191215.png

最後に実行時のログを貼っておく。

ultra96v2_min2 login: root
root@ultra96v2_min2:~# insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
[   28.073333] zocl: loading out-of-tree module taints kernel.
[   28.082652] [drm] Probing for xlnx,zocl
[   28.086609] [drm] FPGA programming device pcap founded.
[   28.091836] [drm] PR Isolation addr 0x0
[   28.092614] [drm] Initialized zocl 2018.2.1 20180313 for a0000000.zyxclmm_drm on minor 1
root@ultra96v2_min2:~# cd /run/media/mmcblk0p1/
root@ultra96v2_min2:/run/media/mmcblk0p1# export XILINX_XRT=/usr
root@ultra96v2_min2:/run/media/mmcblk0p1# ls                              
BOOT.BIN    bl31.elf  lap_filter_axim.xclbin  pmufw.elf   test.bmp    zynqmp_fsbl.elf
README.txt  image.ub  laplacian_filter1.exe   system.dtb  u-boot.elf
root@ultra96v2_min2:/run/media/mmcblk0p1# ./laplacian_filter1.exe lap_filter_axim.xclbin 
Using FPGA binary file specfied through the command line: lap_filter_axim.xclbin
[   62.189732] [drm] Pid 2178 opened device
[   62.193690] [drm] Pid 2178 closed device
[   62.208798] [drm] Pid 2178 opened device
Found Platform
Platform Name: Xilinx
Loading: 'lap_filter_axim.xclbin'
[   62.509368] [drm] Finding IP_LAYOUT section header
[   62.509381] [drm] Section IP_LAYOUT details:
[   62.514256] [drm]   offset = 0x54fcf8
[   62.518516] [drm]   size = 0x58
[   62.522358] [drm] Finding DEBUG_IP_LAYOUT section header
[   62.525488] [drm] AXLF section DEBUG_IP_LAYOUT header not found
[   62.530798] [drm] Finding CONNECTIVITY section header
[   62.536714] [drm] Section CONNECTIVITY details:
[   62.541762] [drm]   offset = 0x54fd50
[   62.546282] [drm]   size = 0x1c
[   62.549942] [drm] Finding MEM_TOPOLOGY section header
[   62.553073] [drm] Section MEM_TOPOLOGY details:
[   62.558122] [drm]   offset = 0x54fc00
[   62.562645] [drm]   size = 0xf8
[   62.567888] [drm] No ERT scheduler on MPSoC, using KDS
[   62.576560] [drm] Fail to install CU 0 interrupt handler: -22. Fall back to polling mode.
[   62.584730] [drm] scheduler config ert(0)
[   62.584737] [drm]   cus(1)
[   62.588736] [drm]   slots(16)
[   62.591436] [drm]   num_cu_masks(1)
[   62.594395] [drm]   cu_shift(16)
[   62.597870] [drm]   cu_base(0xa0000000)
[   62.601089] [drm]   polling(1)
[   62.610520] [drm] User buffer is not physical contiguous
Success HW and SW results match
[   62.618874] [drm] zocl_free_userptr_bo: obj 0x00000000df398605
[   62.623867] [drm] zocl_free_userptr_bo: obj 0x00000000801cae2b
[   62.635883] [drm] Pid 2178 closed device
root@ultra96v2_min2:/run/media/mmcblk0p1# ls
BOOT.BIN    bl31.elf  lap_filter_axim.xclbin  pmufw.elf   temp_lap.bmp u-boot.elf
README.txt  image.ub  laplacian_filter1.exe   system.dtb  test.bmp zynqmp_fsbl.elf
root@ultra96v2_min2:/run/media/mmcblk0p1# ./laplacian_filter1.exe lap_filter_axim.xclbin 
Using FPGA binary file specfied through the command line: lap_fil[  148.170198] [drm] Pid 2187 opened device
ter_axim.xclbin
[  148.178611] [drm] Pid 2187 closed device
[  148.184305] [drm] Pid 2187 opened device
Found Platform
Platform Name: Xilinx
Loading: 'lap_filter_axim.xclbin'
[  148.224073] [drm] The XCLBIN already loaded. Don't need to reload.
[  148.225513] [drm] Reconfiguration not supported
[  148.240983] [drm] User buffer is not physical contiguous
[  148.246291] [drm] zocl_free_userptr_bo: obj 0x0000000008414e96
[  148.247253] [drm] User buffer is not physical contiguous
Success HW and SW results match
[  148.258399] [drm] zocl_free_userptr_bo: obj 0x00000000f402229f
[  148.266672] [drm] Pid 2187 closed device
root@ultra96v2_min2:/run/media/mmcblk0p1# ls
BOOT.BIN    bl31.elf  lap_filter_axim.xclbin  pmufw.elf   temp_lap.bmp u-boot.elf
README.txt  image.ub  laplacian_filter1.exe   system.dtb  test.bmp zynqmp_fsbl.elf

  1. 2019年12月17日 04:52 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクト ラプラシアン・フィルタAXI Masterバージョン1

前回の square Vitis 2019.2 アプリケーション・プロジェクトで、自作(といってOpenCL シーケンス部分はほとんど引用だが)Vitis 2019.2 アプリケーション・プロジェクトを初めて作ったが、今回は、いつも題材にしているラプラシアン・フィルタをVitis 2019.2 アプリケーション・プロジェクトにして作成してみよう。

ソースコードを貼っておく。
bmp_header.h を示す。

// bmp_header.h
// BMP ファイルフォーマットから引用させて頂きました
// http://www.kk.iij4u.or.jp/~kondo/bmp/
//
// 2017/05/04 : takseiさんのご指摘によりintX_tを使った宣言に変更。takseiさんありがとうございました
//              変数の型のサイズの違いによってLinuxの64ビット版では動作しなかったためです
//              http://marsee101.blog19.fc2.com/blog-entry-3354.html#comment2808
//

#include <stdio.h>
#include <stdint.h>

// BITMAPFILEHEADER 14bytes
typedef struct tagBITMAPFILEHEADER {
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
} BITMAPFILEHEADER;

// BITMAPINFOHEADER 40bytes
typedef struct tagBITMAPINFOHEADER{
    uint32_t biSize;
    int32_t biWidth;
    int32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    int32_t biXPixPerMeter;
    int32_t biYPixPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImporant;
} BITMAPINFOHEADER;

typedef struct BMP24bitsFORMAT {
    uint8_t blue;
    uint8_t green;
    uint8_t red;
} BMP24FORMAT;



カーネルアプリケーションの laplacian_filter1.cpp を示す。AXI4 Master のVivado HLS 記述だ。

// laplacian_filter1.cpp
// 2019/12/16 by marsee

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

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);

extern "C" {
void lap_filter_axim(volatile int *cam_fb, volatile int *lap_fb, int x_size, int y_size)
{
#pragma HLS INTERFACE m_axi depth=3072 port=cam_fb offset=slave bundle=gmem
#pragma HLS INTERFACE m_axi depth=3072 port=lap_fb offset=slave bundle=gmem
#pragma HLS INTERFACE s_axilite port = x_size bundle = control
#pragma HLS INTERFACE s_axilite port = y_size bundle = control
#pragma HLS INTERFACE s_axilite port=return bundle=control

    int line_buf[3][1920]; // supported HD resolution
    int x, y;
    int lap_fil_val;
    int a, b;
    int fl, sl, tl;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    Loop0: for (y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        Loop1: for (x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
            if (y==0 || y==y_size-1){ // 縦の境界の時の値は0とする
                lap_fil_val = 0;
            }else if (x==0 || x==x_size-1){ // 横の境界の時も値は0とする
                lap_fil_val = 0;
            }else{
                if (y == 1 && x == 1){ // 最初のラインの最初のピクセルでは2ライン分の画素を読み出す
                    Loop3: for (a=0; a<2; a++){ // 2ライン分
                        Loop4: for (b=0; b<x_size; b++){ // ライン
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
                            line_buf[a][b] = cam_fb[(a*x_size)+b];
                            line_buf[a][b] = conv_rgb2y(line_buf[a][b]);
                        }
                    }
                }
                if (x == 1) {    // ラインの最初なので、2つのピクセルを読み込む
                    Loop5: for (b=0; b<2; b++){ // ライン
                        line_buf[(y+1)%3][b] = cam_fb[((y+1)*x_size)+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)*x_size)+(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*x_size)+x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
            // printf("x = %d  y = %d", x, y);
        }
     }
}
}

// 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);
}



最後にホストアプリケーションの laplacian_filter1_host.cpp を示す。これも、OpenCL シーケンスは、Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用している。
temp.bmp ファイルを読み込んで、ハードウェアで、ラプラシアン・フィルタ処理を行う。そして、ソフトウェアのラプラシアン・フィルタ処理結果と比較して、temp_lap.bmp ファイルに結果を書き込む。

// laplacian_filter1_host.cpp
// 2019/12/09 by marsee
//

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用します
// https://github.com/Xilinx/Vitis-Tutorials/blob/master/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp
#define CL_HPP_CL_1_2_DEFAULT_BUILD
#define CL_HPP_TARGET_OPENCL_VERSION 120
#define CL_HPP_MINIMUM_OPENCL_VERSION 120
#define CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY 1
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <CL/cl2.hpp>
#include <iostream>
#include <fstream>
#include <CL/cl_ext_xilinx.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.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(volatile int *cam_fb, volatile int *lap_fb);    // hardware
void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height); // software

static const std::string error_message =
    "Error: Result mismatch:\n"
    "i = %d CPU result = %d Device result = %d\n";

//Some Library functions to be used.
template <typename T>
struct aligned_allocator
{
  using value_type = T;
  T* allocate(std::size_t num)
  {
    void* ptr = nullptr;
    if (posix_memalign(&ptr,4096,num*sizeof(T)))
      throw std::bad_alloc();
    return reinterpret_cast<T*>(ptr);
  }
  void deallocate(T* p, std::size_t num)
  {
    free(p);
  }
};


#define OCL_CHECK(error,call)                                       \
    call;                                                           \
    if (error != CL_SUCCESS) {                                      \
      printf("%s:%d Error calling " #call ", error code is: %d\n",  \
              __FILE__,__LINE__, error);                            \
      exit(EXIT_FAILURE);                                           \
    }

namespace xcl {
std::vector<cl::Device> get_devices(const std::string& vendor_name) {

    size_t i;
    cl_int err;
    std::vector<cl::Platform> platforms;
    OCL_CHECK(err, err = cl::Platform::get(&platforms));
    cl::Platform platform;
    for (i  = 0 ; i < platforms.size(); i++){
        platform = platforms[i];
        OCL_CHECK(err, std::string platformName = platform.getInfo<CL_PLATFORM_NAME>(&err));
        if (platformName == vendor_name){
            std::cout << "Found Platform" << std::endl;
            std::cout << "Platform Name: " << platformName.c_str() << std::endl;
            break;
        }
    }
    if (i == platforms.size()) {
        std::cout << "Error: Failed to find Xilinx platform" << std::endl;
        exit(EXIT_FAILURE);
    }

    //Getting ACCELERATOR Devices and selecting 1st such device
    std::vector<cl::Device> devices;
    OCL_CHECK(err, err = platform.getDevices(CL_DEVICE_TYPE_ACCELERATOR, &devices));
    return devices;
}

std::vector<cl::Device> get_xil_devices() {
    return get_devices("Xilinx");
}

char* read_binary_file(const std::string &xclbin_file_name, unsigned &nb)
{
    std::cout << "INFO: Reading " << xclbin_file_name << std::endl;

    if(access(xclbin_file_name.c_str(), R_OK) != 0) {
        printf("ERROR: %s xclbin not available please build\n", xclbin_file_name.c_str());
        exit(EXIT_FAILURE);
    }
    //Loading XCL Bin into char buffer
    std::cout << "Loading: '" << xclbin_file_name.c_str() << "'\n";
    std::ifstream bin_file(xclbin_file_name.c_str(), std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);
    return buf;
}
};

int main(int argc, char* argv[])
{
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int blue, green, red;
    const char* xclbinFilename;

    if (argc==2) {
        xclbinFilename = argv[1];
        std::cout <<"Using FPGA binary file specfied through the command line: " << xclbinFilename << std::endl;
    }
    else {
        xclbinFilename = "../lap_filter_axim.xclbin";
        std::cout << "No FPGA binary file specified through the command line, using:" << xclbinFilename <<std::endl;
    }

    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(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    std::vector<int,aligned_allocator<int>> rd_bmp(bmpihr.biWidth * bmpihr.biHeight);
    std::vector<int,aligned_allocator<int>> hw_lapd(bmpihr.biWidth * bmpihr.biHeight);
    std::vector<int,aligned_allocator<int>> sw_lapd(bmpihr.biWidth * bmpihr.biHeight);
    size_t size_in_bytes = (bmpihr.biWidth * bmpihr.biHeight) * sizeof(int);

    // 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);

    std::vector<cl::Device> devices = xcl::get_xil_devices();
    cl::Device device = devices[0];
    devices.resize(1);


    // Creating Context and Command Queue for selected device
    cl::Context context(device);
    cl::CommandQueue q(context, device, CL_QUEUE_PROFILING_ENABLE);

    // Load xclbin
    std::cout << "Loading: '" << xclbinFilename << "'\n";
    std::ifstream bin_file(xclbinFilename, std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    unsigned nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);

    // Creating Program from Binary File
    cl::Program::Binaries bins;
    bins.push_back({buf,nb});
    cl::Program program(context, devices, bins);

    // This call will get the kernel object from program. A kernel is an
    // OpenCL function that is executed on the FPGA.
    cl::Kernel krnl_lap_filter(program,"lap_filter_axim");

    // These commands will allocate memory on the Device. The cl::Buffer objects can
    // be used to reference the memory locations on the device.
    cl::Buffer rd_bmp_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            size_in_bytes, rd_bmp.data());
    cl::Buffer hw_lapd_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            size_in_bytes, hw_lapd.data());

    // Data will be transferred from system memory over PCIe to the FPGA on-board
    // DDR memory.
    q.enqueueMigrateMemObjects({rd_bmp_buf},0/* 0 means from host*/);

    //set the kernel Arguments
    krnl_lap_filter.setArg(0,rd_bmp_buf);
    krnl_lap_filter.setArg(1,hw_lapd_buf);
    krnl_lap_filter.setArg(2,bmpihr.biWidth);
    krnl_lap_filter.setArg(3,bmpihr.biHeight);

    //Launch the Kernel
    q.enqueueTask(krnl_lap_filter);

    // The result of the previous kernel execution will need to be retrieved in
    // order to view the results. This call will transfer the data from FPGA to
    // source_results vector

    q.enqueueMigrateMemObjects({hw_lapd_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();

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

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            if (hw_lapd[y*bmpihr.biWidth+x] != sw_lapd[y*bmpihr.biWidth+x]){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, hw_lapd[y*bmpihr.biWidth+x], sw_lapd[y*bmpihr.biWidth+x]);
                //return(1);
            }
        }
    }
    printf("Success HW and SW results match\n");

    // ハードウェアのラプラシアンフィルタの結果を 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(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(uint32_t), 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);

    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. 2019年12月16日 20:54 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクト square その3

Vitis 2019.2 アプリケーション・プロジェクト square その2”の続き。

前回は、Vitis 2019.2 のアクセラレーション用アプリケーション・プロジェクトの spuare_u96v2 を作成して、ビルドした。今回は、square_u96v2 に使用された Vivado HLS , Vivado のレポートなどを見て、Ultra96-V2 実機で動作を確認しよう。

square_u96v2_system -> square_u96v2 -> Hardware -> square.xclbin.link_summary をダブルクリックする。
square_u96v2_13_191215.png

Vitis Analyzer が立ち上がる。
square -> HLS Synthesis をクリックすると square の Vivado HLS のレポートが表示された。
square_u96v2_14_191215.png

square は square_stream, read_dma, write_dma で構成されている。(ここでは read_dma7 になっているが、コードは read_dma なのだがどうしてだろうか?)個別の関数でのレポートを見ることができるので、見ていこう。

square_stream のVivado HLS レポートを示す。
square_u96v2_15_191215.png

read_dma のVivado HLS レポートを示す。
square_u96v2_16_191215.png

write_dma のVivado HLS レポートを示す。
square_u96v2_17_191215.png

次に、Vivado のプロジェクトを見てみよう。
square_u96v2_system -> square_u96v2 -> Hardware -> square.build -> link -> vivado -> vpl -> prj -> prj.xpr をダブルクリックするとVivado 2019.2 が立ち上がる。
square_u96v2_18_191215.png

square_u96v2_19_191215.png

次に、ultra96v2_min2 ブロックデザインを見てみよう。
square_u96v2_20_191215.png

次にUltra96-V2 の実機で確認してみる。

square_u96v2/Hardware/sd_card の ファイルを MicroSD カードの第 1 パーティションに書き込む。
square_u96v2_21_191215.png

MicroSD カードの第 2 パーティションには、aarch64-xilinx-linux の Root FS を書き込んである。
MicroSD カードをUltra96-V2 に入れて電源ON した。
PetaLinux が起動した。
root ノーパスでログインした。
zocl ドライバを insmod でロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko

アプリケーションを起動した。
cd /run/media/mmcblk0p1/
export XILINX_XRT=/usr
./square_u96v2.exe square.xclbin

square_u96v2_22_191215.png

square_u96v2_23_191215.png

TEST PASSED が表示されて成功した。

ログを示す。

PetaLinux 2019.2 ultra96v2_min2 /dev/ttyPS0

ultra96v2_min2 login: root
root@ultra96v2_min2:~# insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
[   39.736119] zocl: loading out-of-tree module taints kernel.
[   39.745419] [drm] Probing for xlnx,zocl
[   39.749374] [drm] FPGA programming device pcap founded.
[   39.754594] [drm] PR Isolation addr 0x0
[   39.755457] [drm] Initialized zocl 2018.2.1 20180313 for a0000000.zyxclmm_drm on minor 1
root@ultra96v2_min2:~# cd /run/media/mmcblk0p1/
root@ultra96v2_min2:/run/media/mmcblk0p1# export XILINX_XRT=/usr
root@ultra96v2_min2:/run/media/mmcblk0p1# ls
BOOT.BIN    bl31.elf  laplacian_filter1  square.xclbin    system.dtb  zynqmp_fsbl.elf
README.txt  image.ub  pmufw.elf   square_u96v2.exe  u-boot.elf
root@ultra96v2_min2:/run/media/mmcblk0p1# ./square_u96v2.exe square.xclbin 
Using FPGA binary file specfied through the command line: square.xclbin
[   73.290186] [drm] Pid 2179 opened device
[   73.294141] [drm] Pid 2179 closed device
[   73.309073] [drm] Pid 2179 opened device
Found Platform
Platform Name: Xilinx
Loading: 'square.xclbin'
[   73.596375] [drm] Finding IP_LAYOUT section header
[   73.596389] [drm] Section IP_LAYOUT details:
[   73.601246] [drm]   offset = 0x54fcf8
[   73.605513] [drm]   size = 0x58
[   73.609174] [drm] Finding DEBUG_IP_LAYOUT section header
[   73.612303] [drm] AXLF section DEBUG_IP_LAYOUT header not found
[   73.617611] [drm] Finding CONNECTIVITY section header
[   73.623523] [drm] Section CONNECTIVITY details:
[   73.628567] [drm]   offset = 0x54fd50
[   73.633088] [drm]   size = 0x1c
[   73.636749] [drm] Finding MEM_TOPOLOGY section header
[   73.639879] [drm] Section MEM_TOPOLOGY details:
[   73.644921] [drm]   offset = 0x54fc00
[   73.649441] [drm]   size = 0xf8
[   73.654808] [drm] No ERT scheduler on MPSoC, using KDS
[   73.663522] [drm] Fail to install CU 0 interrupt handler: -22. Fall back to polling mode.
[   73.671693] [drm] scheduler config ert(0)
[   73.671701] [drm]   cus(1)
[   73.675707] [drm]   slots(16)
[   73.678410] [drm]   num_cu_masks(1)
[   73.681377] [drm]   cu_shift(16)
[   73.684856] [drm]   cu_base(0xa0000000)
TEST PASSED
[   73.688070] [drm]   polling(1)
[   73.698695] [drm] zocl_free_userptr_bo: obj 0x00000000c9f7c20e
[   73.702853] [drm] zocl_free_userptr_bo: obj 0x00000000e1416682
[   73.712823] [drm] Pid 2179 closed device


もう一度、
./square_u96v2.exe square.xclbin
を実行した。
square_u96v2_24_191215.png
  1. 2019年12月15日 05:03 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクト square その2

Vitis 2019.2 アプリケーション・プロジェクト square その1”の続き。

Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用しまくりで、Vitis 2019.2 のアプリケーション・プロジェクトの square を作成して、ホストアプリケーションとカーネルアプリケーションのコードを貼った。
今回は、spuare のアクセラレーション・アプリケーション・プロジェクトを作成して、ビルドしていこう。

まずは、Vitis 2019.2 を起動して、spuare のアクセラレーション・アプリケーション・プロジェクトを作成する。

Vitis 2019.2 のFile メニュー -> New -> Application Project... を選択する。
New Application Project ダイアログの Create a New Application Project 画面で Project name に square_u96v2 と入力する。
square_u96v2_1_191211.png

Platform 画面では、ultra96v2_min2 を選択した。
square_u96v2_2_191211.png

Domain 画面では、Linux しかプラットフォームに無いので、そのままとする。
square_u96v2_3_191211.png

Template 画面では、Empty Application を選択して、Finish ボタンをクリックした。
square_u96v2_4_191211.png

square アプリケーション・プロジェクトが作成された。
ホストアプリケーションとカーネルアプリケーションはすでにファイルがあるので、ファイルをインポートする。
square_u96v2_system -> square_u96v2 -> src ディレクトリを右クリックし右クリックメニューから Import Source... を選択した。
square_u96v2_5_191211.png

Import Soruces ダイアログが表示された。
Browse... ボタンをクリックした。
square_u96v2_6_191211.png

Import from directory ダイアログが表示された。
インポートしたいファイルがあるディレクトリを指定して、OK ボタンをクリックした。
square_u96v2_7_191211.png

Import Soruces ダイアログの square_host.cpp と square.cpp にチェックを入れて、Finish ボタンをクリックした。
square_u96v2_8_191211.png

square_u96v2_system -> square_u96v2 -> src ディレクトリに square_host.cpp と square.cpp が入った。
square_u96v2_9_191211.png

Hardware Function に square() を追加しよう。
Add Hardware Function ボタンをクリックした。
Add Hardware Function ダイアログが表示された。
square(int *,int*,int) -squrae.cpp を選択して OK ボタンをクリックした。
square_u96v2_10_191211.png

Hardware Function に square() が入った。
Active build configuration を Hardware にして、ビルドのトンカチ・ボタンをクリックした。
square_u96v2_11_191211.png

ビルドが終了した。
square_u96v2_12_191215.png
  1. 2019年12月15日 03:26 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクト square その1

自分で作ったと言うか、Xilinx のサンプルを手直しした Viits 2019.2 アプリケーションプロジェクトの square を書いておく。
ホストアプリケーションは自分で作ったというのはおこがましいほど、Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用しまくりで恥ずかしいのだが、自分でリファクタリングも決まりきった手順なので難しいので、そのまま使わせてもらうことにする。
カーネルアプリケーションの方も、引用はしているが、時分で書いた部分が多い。
そういえば、何をするソフトウェアか書いてなかったので、書いておくと 2 乗するソフトウェアということになる。

ホストアプリケーションの square_host.cpp を示す。
0 〜 9 までの 10 個のデータを 2 乗するソフトウェアとなっている。

// square_host.cpp
// 2019/12/11 by marsee
//

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用します
// https://github.com/Xilinx/Vitis-Tutorials/blob/master/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp
#define CL_HPP_CL_1_2_DEFAULT_BUILD
#define CL_HPP_TARGET_OPENCL_VERSION 120
#define CL_HPP_MINIMUM_OPENCL_VERSION 120
#define CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY 1
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS

#include <vector>
#include <CL/cl2.hpp>
#include <iostream>
#include <fstream>
#include <CL/cl_ext_xilinx.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>

static const std::string error_message =
    "Error: Result mismatch:\n"
    "i = %d CPU result = %d Device result = %d\n";

//Some Library functions to be used.
template <typename T>
struct aligned_allocator
{
  using value_type = T;
  T* allocate(std::size_t num)
  {
    void* ptr = nullptr;
    if (posix_memalign(&ptr,4096,num*sizeof(T)))
      throw std::bad_alloc();
    return reinterpret_cast<T*>(ptr);
  }
  void deallocate(T* p, std::size_t num)
  {
    free(p);
  }
};


#define OCL_CHECK(error,call)                                       \
    call;                                                           \
    if (error != CL_SUCCESS) {                                      \
      printf("%s:%d Error calling " #call ", error code is: %d\n",  \
              __FILE__,__LINE__, error);                            \
      exit(EXIT_FAILURE);                                           \
    }

namespace xcl {
std::vector<cl::Device> get_devices(const std::string& vendor_name) {

    size_t i;
    cl_int err;
    std::vector<cl::Platform> platforms;
    OCL_CHECK(err, err = cl::Platform::get(&platforms));
    cl::Platform platform;
    for (i  = 0 ; i < platforms.size(); i++){
        platform = platforms[i];
        OCL_CHECK(err, std::string platformName = platform.getInfo<CL_PLATFORM_NAME>(&err));
        if (platformName == vendor_name){
            std::cout << "Found Platform" << std::endl;
            std::cout << "Platform Name: " << platformName.c_str() << std::endl;
            break;
        }
    }
    if (i == platforms.size()) {
        std::cout << "Error: Failed to find Xilinx platform" << std::endl;
        exit(EXIT_FAILURE);
    }

    //Getting ACCELERATOR Devices and selecting 1st such device
    std::vector<cl::Device> devices;
    OCL_CHECK(err, err = platform.getDevices(CL_DEVICE_TYPE_ACCELERATOR, &devices));
    return devices;
}

std::vector<cl::Device> get_xil_devices() {
    return get_devices("Xilinx");
}

char* read_binary_file(const std::string &xclbin_file_name, unsigned &nb)
{
    std::cout << "INFO: Reading " << xclbin_file_name << std::endl;

    if(access(xclbin_file_name.c_str(), R_OK) != 0) {
        printf("ERROR: %s xclbin not available please build\n", xclbin_file_name.c_str());
        exit(EXIT_FAILURE);
    }
    //Loading XCL Bin into char buffer
    std::cout << "Loading: '" << xclbin_file_name.c_str() << "'\n";
    std::ifstream bin_file(xclbin_file_name.c_str(), std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);
    return buf;
}
};
// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用終了

#define DATA_SIZE 10

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを自分用に変更して引用します
int main(int argc, char* argv[])
{
    const char* xclbinFilename;

    if (argc==2) {
        xclbinFilename = argv[1];
        std::cout <<"Using FPGA binary file specfied through the command line: " << xclbinFilename << std::endl;
    }
    else {
        xclbinFilename = "../lap_filter_axim.xclbin";
        std::cout << "No FPGA binary file specified through the command line, using:" << xclbinFilename <<std::endl;
    }

    std::vector<int,aligned_allocator<int>> in_data(DATA_SIZE);
    std::vector<int,aligned_allocator<int>> square_data(DATA_SIZE);
    size_t size_in_bytes = (DATA_SIZE) * sizeof(int);
    
    // input data
    for(int i=0; i<DATA_SIZE; i++){
        in_data[i] = i;
        square_data[i] = 0;
    }
    
    std::vector<cl::Device> devices = xcl::get_xil_devices();
    cl::Device device = devices[0];
    devices.resize(1);


    // Creating Context and Command Queue for selected device
    cl::Context context(device);
    cl::CommandQueue q(context, device, CL_QUEUE_PROFILING_ENABLE);

    // Load xclbin
    std::cout << "Loading: '" << xclbinFilename << "'\n";
    std::ifstream bin_file(xclbinFilename, std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    unsigned nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);

    // Creating Program from Binary File
    cl::Program::Binaries bins;
    bins.push_back({buf,nb});
    cl::Program program(context, devices, bins);

    // This call will get the kernel object from program. A kernel is an
    // OpenCL function that is executed on the FPGA.
    cl::Kernel krnl_square(program,"square");

    // These commands will allocate memory on the Device. The cl::Buffer objects can
    // be used to reference the memory locations on the device.
    cl::Buffer ind_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            size_in_bytes, in_data.data());
    cl::Buffer squared_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            size_in_bytes, square_data.data());

    // Data will be transferred from system memory over PCIe to the FPGA on-board
    // DDR memory.
    q.enqueueMigrateMemObjects({ind_buf},0/* 0 means from host*/);

    //set the kernel Arguments
    krnl_square.setArg(0,ind_buf);
    krnl_square.setArg(1,squared_buf);
    krnl_square.setArg(2,DATA_SIZE);
 
    //Launch the Kernel
    q.enqueueTask(krnl_square);

    // The result of the previous kernel execution will need to be retrieved in
    // order to view the results. This call will transfer the data from FPGA to
    // source_results vector

    q.enqueueMigrateMemObjects({squared_buf},CL_MIGRATE_MEM_OBJECT_HOST);

    q.finish();

    // Compare the results
    int error = 0;
    for(int i=0; i<DATA_SIZE; i++){
        if(square_data[i] != i*i){
            std::cout << "Error: i = " << i << " i^2 = " << i*i << " square_data = " << square_data[i] << std::endl;
            error = 1;
        }else{
            //std::cout << "i = " << i << " i^2 = " << i*i << " square_data = " << square_data[i] << std::endl;
        }
    }
    
    std::cout << "TEST " << (error ? "FAILED" : "PASSED") << std::endl;
    return (error ? EXIT_FAILURE : EXIT_SUCCESS);
}


カーネルアプリケーションの square.cpp を示す。
カーネルは DMA Read - HLS Stream にする read_dma() と HLS Stream 入力 - 2 乗 - HLS Stream する square_stream() 、そして HLS Stream - DMA Write する write_dma() の 3 個の関数から構成されていて、それらの 3 個の関数を #pragma HLS dataflow で並列動作させている。

// square.cpp
// 2019/12/11 by marsee
//

// Vitis_Accel_Examples/cpp_kernels/dataflow_stream/src/adder.cpp を参考にしています
// https://github.com/Xilinx/Vitis_Accel_Examples/blob/master/cpp_kernels/dataflow_stream/src/adder.cpp

#include <hls_stream.h>

static void read_dma(int *inm, hls::stream<int> &outs, int size){
    LOOP_RDMA: for(int i=0; i<size; i++){
#pragma HLS PIPELINE II=1
#pragma HLS LOOP_TRIPCOUNT min=10 max=10
        outs.write(inm[i]);
    }
}

static void square_stream(hls::stream<int> &ins, hls::stream<int> &outs, int size){
    LOOP_SQAURE_ST: for(int i=0; i<size; i++){
#pragma HLS PIPELINE II=1
#pragma HLS LOOP_TRIPCOUNT min=10 max=10
        int tmp = ins.read();
        outs.write(tmp * tmp);
    }
}

static void write_dma(hls::stream<int> &ins, int *outm, int size){
    LOOP_WDMA: for(int i=0; i<size; i++){
#pragma HLS PIPELINE II=1
#pragma HLS LOOP_TRIPCOUNT min=10 max=10
        outm[i] = ins.read();
    }
}

extern "C" {
void square(int *inm, int *outm, int size){
#pragma HLS INTERFACE m_axi port = inm offset = slave bundle = gmem
#pragma HLS INTERFACE m_axi port = outm offset = slave bundle = gmem
#pragma HLS INTERFACE s_axilite port = size bundle = control
#pragma HLS INTERFACE s_axilite port = return bundle = control

    static hls::stream<int> ins;
    static hls::stream<int> outs;
#pragma HLS STREAM variable = ins depth = 32
#pragma HLS STREAM variable = outs depth = 32

#pragma HLS dataflow    
    read_dma(inm, ins, size);
    square_stream(ins, outs, size);
    write_dma(outs, outm, size);
}
}

  1. 2019年12月14日 04:24 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクトの C/C++ カーネル・コード作成手順

前回は、”Vitis 2019.2 アプリケーション・プロジェクトのホスト・コード作成手順”でしたが、今回は、Vitis 2019.2 アプリケーション・プロジェクトの C/C++ カーネル・コード作成手順をまとめていきたいと思う。

今回参照するのは、”Vitis 統合ソフトウェア プラットフォームの資料 アプリケーション アクセラレーション開発 UG1393 (v2019.2) 2019 年 11 月 11 日”の 68 ページの”第 7 章 C/C ++ カーネル”

カーネルはVivado HLS で高位合成される C/C++ のコードとなる。ただしVitis 用のルールがある。
Vitis_Accel_Examples/cpp_kernels/dataflow_stream/src/adder.cpp を参考にして自分で書いた square.cpp の C++ カーネルを例に説明する。

extern "C" {
void square(int *inm, int *outm, int size){
#pragma HLS INTERFACE m_axi port = inm offset = slave bundle = gmem
#pragma HLS INTERFACE m_axi port = outm offset = slave bundle = gmem
#pragma HLS INTERFACE s_axilite port = size bundle = control
#pragma HLS INTERFACE s_axilite port = return bundle = control

    static hls::stream<int> ins;
    static hls::stream<int> outs;
#pragma HLS STREAM variable = ins depth = 32
#pragma HLS STREAM variable = outs depth = 32

#pragma HLS dataflow    
    read_dma(inm, ins, size);
    square_stream(ins, outs, size);
    write_dma(outs, outm, size);
}
}


C/C++ カーネルの書き方

1. extern "C" { } でハードウェア化する関数を囲む

2. ハードウェア化する関数の戻り値の型を void にする(int にするとビルドできてもカーネルの結果が 0 だった)

3. インターフェースは AXI4 Master とする(複数のAXI4 インターフェースを使用しても良い。複数のAXI4 インターフェースを使用する場合は bundle で変更する bundle = gmem)
#pragma HLS INTERFACE m_axi port = inm offset = slave bundle = gmem

4. スカラー値を設定するインターフェースの AXI4 Lite は 1 つにする必要がある(bundle = control)
#pragma HLS INTERFACE s_axilite port = size bundle = control

5. 使用できるデータ型
  int、float、double などの通常の C 言語のデータ型
  任意精度整数型(ap_int<11> aaa, ap_uint<12> bbb)
  任意精度固定小数点データ型(ap_fixed<9, 1, AP_TRN_ZERO, AP_SAT>、ap_fixed<4, 3, AP_TRN_ZERO, AP_WRAP>)

こんなところかな?後は一般的なVivado HLS の書き方でOK

なお、 square() では、
read_dma(inm, ins, size); がDMA Read して HLS ストリームに変換する関数
square_stream(ins, outs, size); がHLS ストリームを入力したデータを 2 乗して HLS ストリームで出力する関数
write_dma(outs, outm, size); が HLS ストリームを DMA Write する関数
の 3 つの関数を並列動作させるために dataflow 指示子を追加してある。このように書くのがお勧め。
  1. 2019年12月13日 04:15 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 アプリケーション・プロジェクトのホスト・コード作成手順

Vitis 2019.2 アプリケーション・プロジェクトのホスト・コードの作成手順をまとめてみよう。

参考にするのは、
Vitis 統合ソフトウェア プラットフォームの資料 アプリケーション アクセラレーション開発 UG1393 (v2019.2) 2019 年 11 月 11 日” 52 ページ、第6章 ホスト アプリケーション
Vitis Unified Software Development Platform Documentation”、”
Developing Applications”、”Host Application
The OpenCL C++ Wrapper API Version: 1.2.6 Document Revision: 02

Vitis 統合ソフトウェア プラットフォームの資料 アプリケーション アクセラレーション開発 UG1393 (v2019.2) 2019 年 11 月 11 日”と”Host Application”は C 言語でのホストアプリケーションの解説、”The OpenCL C++ Wrapper API Version: 1.2.6 Document Revision: 02”は C++ でのホストアプリケーションの解説だ。

次に、OpenCL でカーネルを起動して、アプリケーションを起動する手順を示す。
なお、Vitis-Tutorials の mixing-c-rtl-kernels の host_step1.cpp のコードを一部引用する。

1. std::vector<int,aligned_allocator<int>> で 4096 バイトに境界を揃えたバッファを用意する
std::vector<int,aligned_allocator<int>> source_a(DATA_SIZE, 10);

2. ザイリンクス社のプラットフォームを特定する
xcl::get_xil_devices() の中にコードが含まれている(platform.getInfo<CL_PLATFORM_NAME>(&err))

3. プラットフォームのデバイスを特定する
std::vector<cl::Device> devices = xcl::get_xil_devices();

4. デバイスのIDを使ってコンテキストを作成する
cl::Context context(device);

5. コマンドキューを作成する
cl::CommandQueue q(context, device, CL_QUEUE_PROFILING_ENABLE);

6. ハードウェア .xclbin を読み込む。
std::ifstream bin_file(xclbinFilename, std::ifstream::binary);

7. ".xclbin"ファイルからプログラムをビルドする
cl::Program program(context, devices, bins);

8. プログラムからカーネル・オブジェクトを取得する(カーネルをFPGA で実行する)
cl::Kernel krnl_vector_add(program,"krnl_vadd");

9. cl::Buffer オブジェクトを使用してデバイスにメモリを割り当てる
cl::Buffer buffer_a(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY, size_in_bytes, source_a.data());

10. 入力データをFPGA用のメモリに転送する(組み込みの場合はメモリのアドレスを用意しているだけなのか?)
q.enqueueMigrateMemObjects({buffer_a,buffer_b},0/* 0 means from host*/);

11. カーネルの引数を用意する
krnl_vector_add.setArg(0,buffer_a);

12. カーネルを起動する
q.enqueueTask(krnl_vector_add);

13. カーネル実行の結果を取り出す
q.enqueueMigrateMemObjects({buffer_result},CL_MIGRATE_MEM_OBJECT_HOST);

14. 終了
q.finish();

  1. 2019年12月12日 21:28 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 のアプリケーション・プロジェクトの作り方3

Vitis 2019.2 のアプリケーション・プロジェクトの作り方2”の続き。

自分でアプリケーション・プロジェクトを作り、自分でアクセラレーションする関数を指定してビルドしていないので、それをやってみようと思うということで、前回は、ビルド後のSummary を観察した。
今回は、出来上がったSD_CARD イメージをMicroSD カードに書いて、実際にUltra96-V2 でテストしてみよう。

Vitis 2019.2 の左下の Assistant ウインドウで vitis_ctut_systerm -> vitis_ctut -> Hardware -> SD Card Image を右クリックし右クリックメニューから Open -> Open in File Browser を選択した。
Vitis_tub_38_191211.png

すると vitis_ctut/Hardware/sd_card ディレクトリが開く。
Vitis_tub_39_191211.png

このディレクトリの内容を、MircroSD カードの第 1 パーティションに書き込む。
Vitis_tub_33_191211.png

Root FS はすでに書いてあるので、Ultra96-V2 に挿入して、電源ON した。
PetaLinux が起動した。
root ノーパスでログインした。
zocl ドライバを insmod でロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko

アプリケーションを起動した。
cd /run/media/mmcblk0p1/
export XILINX_XRT=/usr
./vitis_ctut.exe binary_container_1.xclbin

Vitis_tub_34_191211.png

Vitis_tub_35_191211.png

TEST WITH ONE KERNEL PASSED と表示された。成功だ。

2 回目やってみた。
./vitis_ctut.exe binary_container_1.xclbin
Vitis_tub_36_191211.png

3 回目。
./vitis_ctut.exe binary_container_1.xclbin
Vitis_tub_37_191211.png
  1. 2019年12月11日 04:27 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 のアプリケーション・プロジェクトの作り方2

Vitis 2019.2 のアプリケーション・プロジェクトの作り方1”の続き。

自分でアプリケーション・プロジェクトを作り、自分でアクセラレーションする関数を指定してビルドしていないので、それをやってみようと思うということで、前回は、Xilinx 社のGitHub の Xilinx/Vitis-Tutorials の Mixing C++ and RTL Kernels のソースコードを使用してVitis 2019.2 のプロジェクトを作成し、ビルドする手順を行うことができた。今回は、ビルド後のSummary を見ていこう。

Vitis のビルド後のSummary を見るのに便利なのは、binary_container_1.xclbin.link_summary を見ることのようだ。
Vitis 2019.2 の左上のウインドウ Explorer の vitis_ctut_system -> vitis_ctut -> Hardware -> binary_container_1.xclbin.link_summary をダブルクリックすると、Vitis Analyzer が起動する。
Vitis_tub_16_191210.png

Vitis_tub_17_191210.png

binary_contaianer_1 (Hardware) のSummary が表示されている。

左ウインドウの項目をクリックしていく。

System Diagram をクリックした。
Vitis_tub_18_191210.png

これは、binary_contaianer_1 (Hardware) の接続ブロック図?かな?
Vivado のブロックデザインを見てみると、確かに Zynq UltraScale+ MPSoC の S_AXI_HP0_FPD にAXI4 Master のポートが接続されている。
Vitis_tub_32_191210.png

Platform Diagram をクリックした。
Vitis_tub_19_191210.png

これは、AXI4 Master が HP ポートで接続されていて、AXI4 Lite Slave がHPC ポートで接続されているという図かな?

System Estimate をクリックした。
Vitis_tub_20_191210.png

これはVivado HLS のSummary のようだ。

System Guidance をクリックした。
Vitis_tub_21_191210.png

Timing Summary をクリックした。
Vitis_tub_22_191210.png

これはVivado のタイミングのSummary のようだ。

Utilization をクリックした。
Vitis_tub_24_191210.png

リソース使用量の表だった。

Logs をクリックした。
Vitis_tub_25_191210.png


次に、krnl_vadd に移る。
Vitis_tub_26_191210.png

Kernel Estimate をクリックした。
Vitis_tub_27_191210.png

現状では、カーネルが 1 つなので、System Estimate と同じだけど、カーネルが 2 つになると、カーネルごとの Estimate 情報を表示するのだろう?

Kernel Guidance をクリックした。
Vitis_tub_28_191210.png

HLS Syntyesis では Vivado HLS のレポートがそのまま表示された。
Vitis_tub_29_191210.png

Logs をクリックした。
最初のログは、krnl_vadd.steps のログだった。
Vitis_tub_30_191210.png

krnl_vadd.steps のプルダウンメニューを選択し、viado_hls に変更すると、vivado_hls のログが表示された。
Vitis_tub_31_191210.png
  1. 2019年12月10日 04:55 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis 2019.2 のアプリケーション・プロジェクトの作り方1

Ultra96-V2 のVitis アクセラレーション・プラットフォームを作成したが、それを使用して自分のアプリケーション・プロジェクトを動作させてみよう。そのために、まだVitis 2019.2 でサンプル・プロジェクトしかやっていないので、自分でアプリケーション・プロジェクトを作り、自分でアクセラレーションする関数を指定してビルドしていないので、それをやってみようと思う。

最初に自分で一からアプリケーションのソースコードを書くのは負担が重いので、Xilinx 社のGitHub の Xilinx/Vitis-Tutorials の Mixing C++ and RTL Kernels のソースコードを使用してVitis 2019.2 のプロジェクトを作成していこう。

まずは、”Ultra96-V2 の Vitis アクセラレーション・プラットフォームの作り方3(Vitis プラットフォーム作成)”で作成した ultra96v2_min2 Vitis 2019.2 アクセラレーション・プラットフォームをVitis 2019.2 から使用できるようにしよう。
Vitis 2019.2 のインストール・ディレクトリ(私の場合は、 tools/Xilinx/Vitis/2019.2)の下の platform ディレクトリに ultra96v2_min2/images/linux/ultra96v2_min2_pkg/pfm/wksp1/ultra96v2_min2/export/ultra96v2_min2 ディレクトリをコピーする。
Vitis_tub_1_191208.png

これで、Vitis 2019.2 のプラットフォームとして使用できる。

Vitis 2019.2 を立ち上げて、 workspace を指定する。
Vitis_tub_2_191208.png

Vitis 2019.2 が立ち上がった。
Vitis_tub_3_191208.png

ここからアプリケーション・プロジェクトを作成する。
File メニューから New -> Application Project ... を選択する。
New Application Project ダイアログの Create Application Project 画面が立ち上がる。
Project name に vitis_ctut と入力した。
Vitis_tub_4_191208.png

Platform 画面で、自分で作成した ultra96v2_min2 プラットフォームを選択することができるので、選択した。
Vitis_tub_5_191208.png

Domain 画面は、OS として linux が選択されているので、そのままとする。(というかプラットフォームで Domain が linux しか作っていないので、選べない)
Vitis_tub_6_191208.png

Templates 画面で、Empty Application を選択して、Finish ボタンをクリックする。
Vitis_tub_7_191208.png

vitis_ctut アプリケーション・プロジェクトが作成された。(スクリーンショットをトルのを忘れました。。。)

左上のExplorer 画面の vitis_ctut_system -> vitis_ctut -> src を右クリックし、右クリックメニューから New -> File を選択した。
New File ダイアログが表示された。
File name に host_step1.cpp と入力し、Finish ボタンをクリックする。(これがホストのコード)
Vitis_tub_8_191208.png

host_step1.cpp を新規作成後に、Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host ディレクトリから host_step1.cpp のコードをコピー&ペーストした。

左上のExplorer 画面の vitis_ctut_system -> vitis_ctut -> src を右クリックし、右クリックメニューから New -> File を選択した。
New File ダイアログが表示された。
File name に kernel_vadd.cpp と入力し、Finish ボタンをクリックする。(これがカーネルのコード)
Vitis_tub_9_191208.png

kernel_vadd.cpp を新規作成後に、Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/kernel_cpp ディレクトリから krnl_vadd.cpp のコードをコピー&ペーストした。

Vitis 2019.2 の様子を示す。
Vitis_tub_10_191208.png

左上のExplorer 画面の vitis_ctut_system -> vitis_ctut の vitis_ctut.prj をクリックする。
Application Project Settings 画面が表示される。
Add Hardware Functions... ボタンをクリックする。
Vitis_tub_11_191208.png

Add Hardware Functions ダイアログが表示された。
Matching items には、krnl_vadd のみ表示されて、選択されているので、そのまま OK ボタンをクリックする。
Vitis_tub_12_191208.png

krnl_vadd が Hardware Functions に入った。
Vitis_tub_13_191208.png

Active build configuration を Hardware に変更して、build ボタンをクリックする。
Vitis_tub_14_191208.png

ビルドが終了して、sd_card イメージが生成された。
Vitis_tub_15_191208.png
  1. 2019年12月09日 05:23 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

『御前山サイクリングフェスティバル』に出場します

今日は『御前山サイクリングフェスティバル』に参加します。

ロードバイク買って初めて大会に出場するのですが、とっても寒そうです。まだノーマルのペダルでビンディングペダルにもしていないのですが、大丈夫でしょうか?ちょっと不安です。。。高低差が 100 m なので登れるかどうか?も不安ですが、何とかなるでしょう?
楽しんでこようと思います。

また、雪の予想でしたが、雪は降らなそうなので良かったです。
出場するのは、3時間サイクルリレー(男子・女子・混合)のトリオチーム (3名)です。チームの他の方々は女性ですが、凄い方々なので、迷惑かけ内容に頑張ってきます。なお、交通規制が許可が降りなかったので、レースじゃなくなったそうです。

御前山まで車で、下道で行ってきました。約 1 時間 30 分くらい、約 65 km でした。
御前山ダムはとても風光明媚なロックフェラーダムで、ダムや周りの紅葉を見るだけでも心が安らぎます。
さて、コースは最初の高低差 100 m の登りがとても疲れましたが、何とか休まないで登り切れました。後は下りも含めて快適なコースでしたよ。私は 5 週しました。1 週が 6.1 km なので、30 km くらい走りましたね。無料の豚汁、パスタ、御前山ラーメン、デザートが無料で食べ放題だったので、楽しめました。ラーメン 2 杯とパンナコッタ食べました。
ただ、とっても寒かったです。。。
cycling_191208.jpg
  1. 2019年12月07日 04:07 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

array_patition example をultra96v2_min2 プラットフォームでやってみよう

Vitis アクセラレーション・プラットフォームの ultra96v2_min2 ができたので、array_patition example をやってみよう。
array_patition example は”Vitis_Accel_Examples を Ultra96V2 のプラットフォームでやってみる1(array_partition)”でやっているので、結果が出ている。これは、動作クロックが 100 MHz だったが、今回の動作クロックは 200 MHz にしてあるので、どのくらい性能向上したか?を確かめてみよう。

Vitis でarray partition のアプリケーション・プロジェクトを作成した。
Vitis_Platform2_87_191201.png

Hardware をビルドして sd_card ディレクトリの内容をMicroSD カードの第 1 パーティションにコピーした。MicroSD カードの第 2 パーティションには、Petalinux でビルドし sdk.sh で生成した aarch64-xilinx-linux ディレクトリの内容をコピーした。
MicroSD カードを Ultra96-V2 に入れて電源ON した。

root ノーパスでログインした。
zocl ドライバを insmod でロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko

アプリケーションを起動した。
cd /run/media/mmcblk0p1/
export XILINX_XRT=/usr
./ultra96v2_arrayp.exe matmul.xclbin

Vitis_Platform2_89_191204.png

どうやら
matmul: が 631730 ns で、 matmul: partition が 63230 ns のようだ。
前回、ultra96v2_min プラットフォームで実行した時は、matmul: が 927760 ns で、 matmul: partition が 134180 ns だったので、速くなっている。

matmul: は、927760 / 631730 ≒ 1.47 倍
matmul: partition は 134180 / 63230 ≒ 2.12 倍


になった。matmul: partition が 2 倍以上になっているのはクロック周波数以外の条件が効いているのかも知れない?

更に数回実行してみた。
Vitis_Platform2_90_191204.png

Vitis_Platform2_91_191204.png

2 回目以降は、うるさい [drm] メッセージが表示されなくなって見やすい。
  1. 2019年12月06日 05:27 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

AXI4 MasterとAXI4 Streamインターフェースがあって、AXI4 Masterにmemcpy()を使用しているVivado HLSの回路

AXI4 Master インターフェースと AXI4 Stream インターフェースを持っているVivado HLS で作成した回路でAXI4 Master に memcpy() を使用していると合成時のレポートがでないということをツィッターで聞いた。ツィッターでつぶやいていたのは、 @lp6m2 さんで、聞いていみると、Xilinx のCommunity Forums にも、”Is it possible to set internal BRAM values using AXI4 protocol in HLS IP using AXI-Stream protocol?”で質問しているそうだ。

私も試してみることにした。ソースコードはmemcpy() の代わりに for() 文でコピーする行も追加させていただいた。今はコメントアウトしてある。bram_setparam_test.cpp を示す。

#include "hls_video.h"
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include <iostream>
#include <string.h>

void bram_setparam_test(bool mode, int *inputparam,
        hls::stream<ap_axiu<32,1,1,1> >& instream, hls::stream<ap_axiu<32,1,1,1> >& outstream){
#pragma HLS INTERFACE axis port = instream
#pragma HLS INTERFACE axis port = outstream
#pragma HLS INTERFACE s_axilite port = mode
#pragma HLS INTERFACE m_axi port = inputparam offset=slave depth=100
#pragma HLS INTERFACE s_axilite port = mode
#pragma HLS INTERFACE s_axilite port = return
    static int internal_param[100];
#pragma HLS RESOURCE variable=internal_param core=RAM_1P_BRAM
    if(mode == true){
        //AXI4 set parameter mode
        memcpy(internal_param, inputparam, sizeof(int) * 100);
        /*for(int i=0; i<100; i++){
#pragma HLS PIPELINE II=1
            internal_param[i] = inputparam[i];
        }*/
    }else{
        //AXI4-stream MODE
        for(int i = 0; i < 100; i++){
#pragma HLS PIPELINE II=1
            int inval = instream.read().data;
            ap_axiu<32,1,1,1> outval;
            outval.data = inval * internal_param[i];
            outval.last = (i == 99 ? 1 : 0);
            outstream.write(outval);
        }
    }
}


自分なりにテストベンチのbram_setparam_test_tb.cpp を作ったので、それを示す。

// bram_setparam_test_tb.cpp
// 2019/12/04 by marsee
//

#include "hls_video.h"
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include <iostream>
#include <string.h>

void bram_setparam_test(bool mode, int* inputparam,
        hls::stream<ap_axiu<32,1,1,1> >& instream, hls::stream<ap_axiu<32,1,1,1> >& outstream);

int inputparam[100];

int main(){
    using namespace std;

    hls::stream<ap_axiu<32,1,1,1> > ins;
    hls::stream<ap_axiu<32,1,1,1> > outs;
    ap_axiu<32,1,1,1> streamd;
    ap_axiu<32,1,1,1> vals;

    for(int i=0; i<100; i++){
        inputparam[i] = i;
    }

    bram_setparam_test(true, inputparam, ins, outs);

    for(int i=0; i<100; i++){
        streamd.data = i;
        if(i == 0)
            streamd.user = 1;
        else
            streamd.user = 0;
        if(i == 100-1)
            streamd.last = 1;
        else
            streamd.last = 0;
        ins << streamd;
    }

    bram_setparam_test(false, inputparam, ins, outs);

    for(int i=0; i<100; i++){
        outs >> vals;
        if((int)vals.data != i*i){
            fprintf(stderr, "Error: i = %d, i^2 = %d; outs = %d\n", i, i*i, (int)vals.data);
            exit(1);
        }
        printf("i = %d, i^2 = %d; outs = %d\n", i, i*i, (int)vals.data);
    }
    return(0);
}


Vivado HLS 2019.2 のプロジェクトを示す。
Vivado_HLS_MS_memcpy_2_191205.png

C シミュレーションを行って成功した。結果を示す。
Vivado_HLS_MS_memcpy_1_191205.png

C コードの合成を行った。この結果はスカスカだった。レポートが表示されない。
Vivado_HLS_MS_memcpy_3_191205.png

C/RTL 協調シミュレーションを行った。結果を示す。
Vivado_HLS_MS_memcpy_4_191205.png

波形を見ると、正常に動作しているようだ。
Vivado_HLS_MS_memcpy_5_191205.png

Vivado_HLS_MS_memcpy_6_191205.png

Export RTL もそれらしく動いている。
Vivado_HLS_MS_memcpy_7_191205.png

レポートの結果にも問題は無いようだ。

次に、memcpy() をコメントアウトして、for() を活かしてみよう。

 if(mode == true){
  //AXI4 set parameter mode
  //memcpy(internal_param, inputparam, sizeof(int) * 100);
  for(int i=0; i<100; i++){
#pragma HLS PIPELINE II=1
   internal_param[i] = inputparam[i];
  }
 }else{


これでC コードの合成を行った。結果を示す。今度はちゃんとレポートが出た。
Vivado_HLS_MS_memcpy_8_191205.png

C/RTL 協調シミュレーションのレイテンシも memcpy() の場合と同一だ。
Vivado_HLS_MS_memcpy_9_191205.png

Export RTL を示す。memcpy() とあまり変わりがない。
Vivado_HLS_MS_memcpy_10_191205.png

今までも memcpy() は使っていたので、それだけの原因では無いと思う。
AXI4 Master を memcpy() でやっている時に、AXI4 Stream インターフェースを使ったことは無いので、その時にC コードの合成のレポートが消えてしまうのかも知れない?
なお、Vivado HLS 2018.3 でも、Vivado HLS 2017.4 でも、同様にC コードの合成のレポートが消えてしまう。
  1. 2019年12月05日 05:44 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Ultra96-V2 の Vitis アクセラレーション・プラットフォームの作り方4(Vitis アプリケーション・プロジェクトの作成)

Ultra96-V2 の Vitis アクセラレーション・プラットフォームの作り方3(Vitis プラットフォーム作成)”の続き。

Vitis のアクセラレーション・プラットフォームを作成できたので、それを使用してアプリケーション・プロジェクトを作成し、Ultra96-V2 で動作させてみよう。

Vitis 2019.2 のGUI で、New -> Application Project... を選択する。
Vitis_Platform2_72_191201.png

New Application Project ダイアログの Create a New Application Project 画面で、Project Name に ultra96v2_app1 を入力した。
Vitis_Platform2_73_191201.png

Platform では、今回作成したプラットフォームの ultra96v2_min2 を選択した。Flow を見ると Embedded Acceleration と表示されていることが分かる。これは、アクセラレーションができるプラットフォームということらしい?
Vitis_Platform2_74_191201.png

Domain 画面はディフォルトのままとした。
Vitis_Platform2_75_191201.png

Template 画面は、最初は下図の様には項目がなかったが、Download ボタンをクリックして、チュートリアルをダウンロードするとサンプル・プロジェクトが増えた。その内の最初から入っている Vector Addition を選択して、Finish ボタンをクリックした。
Vitis_Platform2_76_191201.png

ultra96v2_app1 アプリケーション・プロジェクトが作成された。
Vitis_Platform2_77_191201.png

Qemu を設定してないので、無駄とは思うがEmulation-SW をビルドしてみよう。
Assistant ウインドウの ultra96v2_app1_system を展開して、 ultra96v2_app1 -> Emulation -SW を右クリックし、右クリックメニューから Build を選択した。
Vitis_Platform2_78_191201.png

エラーになった。
Vitis_Platform2_79_191201.png

Emulation-HW もビルドしてみたが、同様にエラーになった。
Vitis_Platform2_80_191201.png

Hardware をビルドすると、こちらは成功した。
sd_card のディレクトリもできている。
Vitis_Platform2_81_191201.png

Hardware -> binary_container_1 -> SD Card Image を右クリックし、右クリックメニューから Open -> Open File Browser を選択すると、ファイル・ブラウザで sd_card ディレクトリが表示された。
Vitis_Platform2_82_191201.png

sd_card ディレクトリをMicroSD カードの第 1 パーティションにコピーした。
Vitis_Platform2_84_191201.png

MicroSD カードの第 2 パーティションには、PetaLinux で生成した ultra96v2_min2/images/linux/ultra96v2_min2_pkg/pfm/sysroots/aarch64-xilinx-linux ディレクトリの内容を書き込んだ。
(注意: Vitis プラットフォームのRoot FS を使用するとLinux が起動しません)
Vitis_Platform2_84_191201.png

MicroSD カードをUltra96V2 に入れて、電源ON するとLinux が起動した。
root ノーパスでログインした。
zocl ドライバを insmod でロードする。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
Vitis_Platform2_85_191201.png

アプリケーションを起動する。
cd /run/media/mmcblk0p1/
export XILINX_XRT=/usr
./ultra96v2_app.exe bianry_container_1.xclbin

Vitis_Platform2_86_191201.png

TEST_PASSED が表示され、アクセラレーションされたアプリケーションの実行に成功した。
  1. 2019年12月03日 05:21 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0
»