FC2カウンター FPGAの部屋 Genesys_ZU
fc2ブログ

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

FPGAの部屋

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

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる9(ディスプレイに出力できない)

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる8(マイナス・スラックを解消する)”の続き。

ラプラシアン・フィルタを”High Performance Imaging”の Vivado 2019.1 プロジェクトに追加し、前回はそのマイナス・スラックを解消した。今回は、実機で動作を確認したが、動作しなかった。昨日中、いろいろと試していたのだが、動作しなかった。

最初にやることは、AXI4-Stream Switch IP の設定を行って、ラプラシアン・フィルタを通さずにスルーにして、画像がDisplayPort に出力されるか見ることだ。
まずは、 xparameters.h を見た。AXIS_SWITCH を探した。
genasys_zu_filter_44_200830.png

AXIS_SWITCH_0 も AXIS_SWITCH_1 もアドレスマップされている。
AXIS_SWITCH_0 から AXIS_SWITCH_1 にスルーになるように設定した。

     // axis_switch0
     Xil_Out32(((uint32_t)XPAR_AXIS_SWITCH_0_BASEADDR+(uint32_t)0x40), 0);
     Xil_Out32(((uint32_t)XPAR_AXIS_SWITCH_0_BASEADDR+(uint32_t)0x44), 0x80000000);
     Xil_Out32(XPAR_AXIS_SWITCH_0_BASEADDR, 0x2); // Comit registers

     // axis_switch1
     Xil_Out32(((uint32_t)XPAR_AXIS_SWITCH_1_BASEADDR+(uint32_t)0x40), 0);
     Xil_Out32(XPAR_AXIS_SWITCH_1_BASEADDR, 0x2); // Comit registers


これで、ディスプレイにカメラ画像を出力できなかった。

Vivado Analyzer を使用して、データを見たところ、v_gamma_lut_0 の m_axis_video 出力と同じデータが axis_switch_0 と axis_switch_1 に流れているのが確認できた。
genasys_zu_filter_45_200830.png

また ila を追加して、PS の dp_live_video_in にデータが流れているが確認できたが、やはりディスプレイに出力できない。

追加した axis_switch_0 と axis_switch_1 、ラプラシアン・フィルタ IP を除けば、カメラ画像をディスプレイに出力することができている。とっても謎だ?

ラプラシアン・フィルタ IP をスルー出力できるように変更して、ガンマ補正 IP と VDMA の間に入れてみたが、やはりダメだった。
現在は、ラプラシアン・フィルタ IP を VDMA の後に入れて見たが、やはり、同様にディスプレイにカメラ画像が出力できなかった。

現在のラプラシアン・フィルタ IP のソースコードを示す。

// lap_filter_RBG10.cpp
// 2020/08/24 by marsee
// RBG 10ビットずつ
// 2020/08/30: 引数に pass を追加。pass = 0 の時入力をスルー出力する
//

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

#include "lap_filter_RBG10.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);

int lap_filter_rbg10(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int pass){
#pragma HLS INTERFACE s_axilite port=pass
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE s_axilite port=return

    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;

    unsigned int line_buf[2][HORIZONTAL_PIXEL_WIDTH];
#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<VERTICAL_PIXEL_WIDTH; y++){
        Loop3 : for (int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#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<<20)+(lap_fil_val<<10)+lap_fil_val; // RBG同じ値を入れる

            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 == (HORIZONTAL_PIXEL_WIDTH-1))    // 行の最後で TLAST をアサートする
                lap.last = 1;
            else
                lap.last = 0;

            if (pass == 0){ // 画像をそのまま出力
                lap.data = pix.data;
                lap.last = pix.last;
                lap.user = pix.user;
            }

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

    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 にした
// 2020/08/24 : RBG 10ビットずつとした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    g = rgb & 0x3ff;
    b = (rgb>>10) & 0x3ff;
    r = (rgb>>20) & 0x3ff;

    y_f = 306*r + 601*g + 117*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に1024倍した
    y = y_f >> 10; // 1024で割る

    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 = -y;
    else if (y>1023)
        y = 1023;
    return(y);
}


なお、カメラ画像は 1280 x 720 の画像だったので、ヘッダファイルを変更している。

// lap_filter_RBG10.h
// 2020/08/24 by marsee
//

#define HORIZONTAL_PIXEL_WIDTH 1280
#define VERTICAL_PIXEL_WIDTH 720

//#define HORIZONTAL_PIXEL_WIDTH 64
//#define VERTICAL_PIXEL_WIDTH 48

#define ALL_PIXEL_VALUE (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)


SDK での ラプラシアン・フィルタ IP の制御用コードを示す。

    XLap_filter_rbg10 lap_f_rbg10;
    XLap_filter_rbg10_Initialize(&lap_f_rbg10, 0);
    XLap_filter_rbg10_Set_pass(&lap_f_rbg10, 0); // 通過
    XLap_filter_rbg10_Start(&lap_f_rbg10);
    XLap_filter_rbg10_EnableAutoRestart(&lap_f_rbg10);


ラプラシアン・フィルタ IP の部分のブロックデザインを示す。
VDMA のすぐ後に入れてある。
genasys_zu_filter_46_200830.png
  1. 2020年08月31日 05:18 |
  2. Genesys_ZU
  3. | トラックバック:0
  4. | コメント:0

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる8(マイナス・スラックを解消する)

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる7(Vivado HLSでラプラシアン・フィルタを実装する4)”の続き。

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる7(Vivado HLSでラプラシアン・フィルタを実装する4)”でラプラシアン・フィルタを”High Performance Imaging”の Vivado 2019.1 プロジェクトに追加したのだが、やはりマイナス・スラックが出ている。今回は、これを解消したい。最初に何処でマイナス・スラックが出ているかを確認して、クロック間のタイミング違反だったら Clock Domain Closing 解析を行って問題ないようだったら False Path を設定しよう。

最初に、 Open Implementation Design を開いてタイミング違反を見た。
genasys_zu_filter_25_200828.png

Inter-Clock Paths にタイミング違反があるのが見える。

最初に clk_out1_desgin_1_clk_wiz_1_0 から clk_pl_0 のパスにタイミング違反があるようだ。
genasys_zu_filter_26_200828.png

2番めに clk_out1_desgin_1_clk_wiz_1_0 から mipi_phy_if_0_clk_p_FIFO_WRCLK_OUT のパスにタイミング違反がある。
genasys_zu_filter_27_200828.png

clk_pl_0 から clk_out1_design_1_clk_wiz_0_0 のパスにタイミング違反がある。
genasys_zu_filter_28_200828.png

これらのクロック間の処理が適切かどうかを確認する。
確認方法はVviado の Open Implementation Design を開いている状態で、Reports メニューから Timing -> Report CDC... を選択する。
Report CDC ダイアログが開く。
From に clk_out1_desgin_1_clk_wiz_1_0 を設定し、To に clk_pl_0 を設定した。
genasys_zu_filter_29_200828.png

Unsafe が 1 個あるが ila へのパスだから問題ない。
genasys_zu_filter_30_200828.png

次に、同様に、From に clk_out1_desgin_1_clk_wiz_1_0 を設定し、To に mipi_phy_if_0_clk_p_FIFO_WRCLK_OUT を設定した。
genasys_zu_filter_31_200828.png

このパスには Unsafe が無いので問題ない。
genasys_zu_filter_32_200828.png

最後に、From に clk_pl_0 を設定し、To に clk_out1_desgin_1_clk_wiz_0_0 を設定した。
genasys_zu_filter_33_200828.png

こちらは Unsafe がたんまりあるのだが、ila は関係ないし、リセット回路から vtc への Max Delay の Unsafe なので、(リセットは確かめてないけど、複数クロック・リセットしているだろうし)無視することにした。

更に、クロック間のタイミング・エラーも vtc へのが無いし大丈夫だろう。
genasys_zu_filter_35_200828.png

Open Implementation Design の Edit Timing Constrains をクリックして False Path を設定する。(今は False Path を設定するのが正規な方法ではないかも知れない?)
genasys_zu_filter_36_200828.png

まずは、 clk_out1_desgin_1_clk_wiz_1_0 から clk_pl_0 のパスに False Path を設定する。
genasys_zu_filter_37_200828.png

clk_out1_desgin_1_clk_wiz_1_0 から clk_pl_0 のパスに False Path を設定できた。
genasys_zu_filter_38_200828.png

clk_out1_desgin_1_clk_wiz_1_0 から mipi_phy_if_0_clk_p_FIFO_WRCLK_OUT のパスと clk_pl_0 から clk_out1_design_1_clk_wiz_0_0 のパスに False Path を設定することができた。
genasys_zu_filter_39_200828.png

io.xdc ファイルに制約をセーブした。

もう一度、インプリメンテーション、ビットストリームの生成を行ったところ、マイナス・スラックが無くなった。
genasys_zu_filter_40_200828.png
  1. 2020年08月29日 10:54 |
  2. Genesys_ZU
  3. | トラックバック:0
  4. | コメント:0

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる7(Vivado HLSでラプラシアン・フィルタを実装する4)

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる6(Vivado HLSでラプラシアン・フィルタを実装する3)”の続き。

High Performance Imaging”の Vivado 2019.1 プロジェクトにラプラシアン・フィルタを入れたいということで、前回は、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行ってラプラシアン・フィルタ IP を作成した。今回は、Vivado HLS 2019.1 で作成したラプラシアン・フィルタ IP を Vivado 2019.1 のブロックデザインに追加した。

Vivado 2019.1 の diplay_port プロジェクトのディレクトリに lap_filter_rbg10 を作成し、 xilinx_com_hls_lap_filter_rbg10_1_0.zip を解凍して展開した。
genasys_zu_filter_17_200827.png

IP としてブロックデザインで使用するために lap_filter_rbg10 を IP Catalog に追加する。
Vivado 2019.1 の GUI で、左の Flow Navigator の IP Catalog をクリックする。

IP Catalog が表示されるので、そのウインドウで右クリックし、右クリックメニューから Add Repository... を選択した。
Repositories ダイアログが表示された。
lap_fitler_rbg10 ディレクトリを選択した。
genasys_zu_filter_18_200827.png

Add Repository ダイアログが表示された。
IP として lap_filter_rbg10 が登録された。
genasys_zu_filter_19_200827.png

IP Catalog にも lap_filter_rbg10 が登録された。
genasys_zu_filter_20_200827.png

ブロックデザインに AXI4-Stream Switch を 2 個、 lap_filter_rbg10 を 1 個、実装した。
1 つのパスは画像データをそのまま通して、もう 1 つのパスは lap_filter_rbg10 を通して出力する。
ブロックデザインを示す。
genasys_zu_filter_21_200827.png
genasys_zu_filter_22_200827.png

Address Editor を示す。
genasys_zu_filter_23_200827.png

論理合成、インプリメンテーション、ビットストリームの生成を行った。
結果を示す。
genasys_zu_filter_24_200827.png

しかし、回路は動作しているのだが、赤いマイナス・スラックがどうしても気になる。次回はこのマイナス・スラックを解析して、解消したい。
  1. 2020年08月28日 05:10 |
  2. Genesys_ZU
  3. | トラックバック:0
  4. | コメント:0

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる6(Vivado HLSでラプラシアン・フィルタを実装する3)

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる5(Vivado HLSでラプラシアン・フィルタを実装する2)”の続き。

High Performance Imaging”の Vivado 2019.1 プロジェクトにラプラシアン・フィルタを入れたいということで、 Gamma LUT の m_axis_video の出力に入れることにした。そのAXI4-Stream の画像フォーマットを調査した。
前回は、 Gamma LUT の m_axis_video の出力に入れるためのラプラシアン・フィルタの Vivado HLS 2019.2 プロジェクトを作成し、C シミュレーションを行った。今回は、その続きで、ラプラシアン・フィルタの C コードの合成、C/RTL 協調シミュレーション、Export RTL を行ってラプラシアン・フィルタ IP を作成した。

さて、C コードの合成を行った。結果を示す。
genasys_zu_filter_10_200826.png
genasys_zu_filter_11_200826.png

Latency が 3078 クロックとなっている。総ピクセル数は 64 x 48 ピクセル = 3072 ピクセルなので、 6 クロック余計なだけである。性能的には問題ない。
Loop2_Loop3 の Iteration Latency は 5 クロックで Initiation Interval が 1 クロックとなった。
さて、Analysis ウインドウを見てみよう。Loop2_Loop3 の Iteration Latency が 5 クロックというのが視覚的に分かる。
genasys_zu_filter_12_200826.png

C/RTL協調 シミュレーションを行った。
Latency は 3103 クロックだった。ここでも大きく増えてはいないので、問題ない。
genasys_zu_filter_13_200826.png

C/RTL協調 シミュレーションの波形を観察した。
genasys_zu_filter_14_200826.png

Export RTL を行った。結果を示す。
genasys_zu_filter_15_200826.png

CP achieved post-implementation は 5.792 ns で目標値を満たしている。
lap_filter_rbg10 IP は lap_filter_RBG10/solution1/impl/ip にある。 xilinx_com_hls_lap_filter_rbg10_1_0.zip が圧縮されている IP となる。
genasys_zu_filter_16_200827.png
  1. 2020年08月27日 04:50 |
  2. Genesys_ZU
  3. | トラックバック:0
  4. | コメント:0

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる5(Vivado HLSでラプラシアン・フィルタを実装する2)

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる4(Vivado HLSでラプラシアン・フィルタを実装する1)”の続き。

High Performance Imaging”の Vivado 2019.1 プロジェクトにラプラシアン・フィルタを入れたいということで、 Gamma LUT の m_axis_video の出力に入れることにした。そのAXI4-Stream の画像フォーマットを調査した。
今回は、 Gamma LUT の m_axis_video の出力に入れるためのラプラシアン・フィルタの Vivado HLS 2019.2 プロジェクトを作成し、C シミュレーションを行った。

ヘッダ・ファイルの lap_filter_RBG10.h を示す。

// lap_filter_RBG10.h
// 2020/08/24 by marsee
//

//#define HORIZONTAL_PIXEL_WIDTH    1920
//#define VERTICAL_PIXEL_WIDTH    1080

#define HORIZONTAL_PIXEL_WIDTH    64
#define VERTICAL_PIXEL_WIDTH    48

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)


ラプラシアン・フィルタのソースコードの lap_filter_RBG10.cpp を示す。

// lap_filter_RBG10.cpp
// 2020/08/24 by marsee
// RBG 10ビットずつ
//

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

#include "lap_filter_RBG10.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);

int lap_filter_rbg10(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs){
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE s_axilite port=return

    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;

    unsigned int line_buf[2][HORIZONTAL_PIXEL_WIDTH];
#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<VERTICAL_PIXEL_WIDTH; y++){
        Loop3 : for (int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#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<<20)+(lap_fil_val<<10)+lap_fil_val; // RBG同じ値を入れる

            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 == (HORIZONTAL_PIXEL_WIDTH-1))    // 行の最後で TLAST をアサートする
                lap.last = 1;
            else
                lap.last = 0;

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

    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 にした
// 2020/08/24 : RBG 10ビットずつとした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    g = rgb & 0x3ff;
    b = (rgb>>10) & 0x3ff;
    r = (rgb>>20) & 0x3ff;

    y_f = 306*r + 601*g + 117*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に1024倍した
    y = y_f >> 10; // 1024で割る

    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 = -y;
    else if (y>1023)
        y = 1023;
    return(y);
}


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

// lap_filter_RBG10_tb.cpp
// 2020/08/24 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include <ap_axi_sdata.h>

#include "lap_filter_RBG10.h"
#include "bmp_header.h"

int lap_filter_rbg10(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs);

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_rbg10_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int width, int height);

#define CLOCK_PERIOD 10

int main()
{
    using namespace std;

    hls::stream<ap_axis<32,1,1,1> > ins;
    hls::stream<ap_axis<32,1,1,1> > ins_soft;
    hls::stream<ap_axis<32,1,1,1> > outs;
    hls::stream<ap_axis<32,1,1,1> > outs_soft;
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> vals;
    ap_axis<32,1,1,1> vals_soft;

    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *hw_lapd;
    int blue, green, red;

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

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

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

    // ins に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
        pix.user = 0;
        pix.data = i;
        ins << 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 << pix;
            ins_soft << pix;
        }
    }

    lap_filter_rbg10(ins, outs);
    lap_filter_rbg10_soft(ins_soft, outs_soft, bmpihr.biWidth, bmpihr.biHeight);

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    cout << endl;
    cout << "outs" << endl;
    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            outs >> vals;
            outs_soft >> vals_soft;
            ap_int<32> val = vals.data;
            ap_int<32> val_soft = vals_soft.data;

            hw_lapd[(j*bmpihr.biWidth)+i] = (int)val;

            if (val != val_soft){
                printf("ERROR HW and SW results mismatch i = %ld, j = %ld, HW = %d, SW = %d\n", i, j, (int)val, (int)val_soft);
                return(1);
            }
            if (vals.last)
                cout << "AXI-Stream is end" << endl;
        }
    }
    cout << "Success HW and SW results match" << endl;
    cout << endl;

    // ハードウェアのラプラシアンフィルタの結果を 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 (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            green = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0x3ff)>>2;
            blue = ((hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 10) & 0x3ff)>>2;
            red = ((hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>> 20) & 0x3ff)>>2;

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

    return 0;
}

int lap_filter_rbg10_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<<20)+(lap_fil_val<<10)+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 == (HORIZONTAL_PIXEL_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;

    g = rgb & 0x3ff;
    b = (rgb>>10) & 0x3ff;
    r = (rgb>>20) & 0x3ff;

    y_f = 306*r + 601*g + 117*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に1024倍した
    y = y_f >> 10; // 1024で割る

    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 = -y;
    else if (y>1023)
        y = 1023;
    return(y);
}


bmp_header.h は”ikwzm さんの”Ultra96/Ultra96-V2 向け Debian GNU/Linux で XRT(Xilinx Runtime) を動かす”をやってみる8(ソーベル・フィルタ編1)”を参照ください。

さて、Vivado HLS 2019.1 で lap_filter_RBG10 プロジェクトを作成した。
New Vivado HLS Project ダイアログの Project Configuration 画面を示す。
genasys_zu_filter_1_200824.png

New Vivado HLS Project ダイアログの Solution Configuration 画面を示す。
Clock Period は 6.667 ns つまり 150 MHz にして、Prat は xczu3eg-sfvc784-1-e に設定した。
genasys_zu_filter_2_200824.png

Vivado HLS 2019.1 で lap_filter_RBG10 プロジェクトを示す。
genasys_zu_filter_4_200825.png

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

成功した。問題ないようだ。
lap_filter_RBG10/solution1/csim/build ディレクトリを示す。
genasys_zu_filter_6_200825.png

temp_lap.bmp を見るとエッジが表示されているのが分かる。
なお、今回はシャープにエッジが出ていない様に見えるが、これは、今までは、マイナス側のエッジを切ってしまっていたが、今回は絶対値としたことで、マイナス側のエッジが表示されているからだ。
具体的には、 laplacian_fil() 関数の
if (y<0)
  y = -y;
の部分となる。以前は
 y = 0;
と書いてあった。
  1. 2020年08月26日 04:48 |
  2. Genesys_ZU
  3. | トラックバック:0
  4. | コメント:0

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる4(Vivado HLSでラプラシアン・フィルタを実装する1)

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる3(Vivado 2019.2 編その2)”の続き。

High Performance Imaging”の Vivado 2019.1 プロジェクトは 2019.2 に変換することができなかった。最初から構築して見る方法もあるが、とりあえずは 2019.1 のままで使用しよう。Vivado 2019.1 のプロジェクトに今までいろいろと実装してきたフィルタを実装したい。最初にラプラシアン・フィルタを実装してみよう。

ラプラシアン・フィルタを入れる場所だが、カメラからの MIPI 信号をを受信して、Sensor Demosaic IP に入ってベイヤパターンを RBG 信号に変換後にガンマ補正をする Gamma LUT IP に入るが、その出力にラプラシアン・フィルタを入れようと思う。下の図の Gamma LUT の m_axis_video の出力に入れよう。
genasys_zu_filter_3_200825.png

さて、画像のフォーマットだが、Gamma LUT IP のパラメータを見ると、Sample per Clock が 1 クロックで、 Maximum Data Width が 10 ビットになっている。
genasys_zu_filter_7_200825.png

Gamma LUT v1.0 LogiCORE IP Product Guide PG285 December 6, 2019”の 11 , 12 ページの”Video Data”を見ると画像データのフォーマットが分かる。
12 ページの Figure 2-2: Dual Pixels per Clock, 10 bits per Component Mapping for RGB を引用する。
genasys_zu_filter_9_200825.png

これは Sample per Clock が 2 クロックで、 Maximum Data Width が 10 ビットの場合なので、Sample per Clock が 1 クロックで、 Maximum Data Width が 10 ビットの場合は、AXI4-Stream のデータ幅は 32 ビットでその内の [29:20] が Red , [19:10] が Blue, [9:0] が Green となる。 [31:30] は 0 パッディングだ。

Gamma LUT v1.0 LogiCORE IP Product Guide PG285 December 6, 2019”の 16 ページの VIDEO_FORMAT (0x0020) Register を引用する。

VIDEO_FORMAT (0x0020) Register
This register specifies the video format of the AXI4-Stream Video data.
• 0x0 RGB video format
• 0x1 YUV 4:4:4 video format


RGB と YUV の設定があるが、SDK の helloworld.c の XV_gamma_lut_Set_HwReg_video_format() で 0 を入れているので、RGB 確定だ。
genasys_zu_filter_8_200825.png

AXI4-Stream の画像データは 32 ビットでその内の [29:20] が Red , [19:10] が Blue, [9:0] が Green となる。 [31:30] は 0 パッディングということがわかったので、このデータ構造に合わせて、ラプラシアン・フィルタのデータ構造を作っていく。
  1. 2020年08月25日 04:15 |
  2. Genesys_ZU
  3. | トラックバック:0
  4. | コメント:0

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる3(Vivado 2019.2 編その2)

Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる2(Vivado 2019.2 編その1)”の続き。

”Genasys ZU で Adam Taylor さんの”High Performance Imaging”では、Vivado 2019.1 と SDK 2019.1 を使用したが、 Vivado 2019.2 と Vitis 2019.2 を使用して実装してみよう。ということで、前回は、Vivado 2019.2 を使用して、エラーがでる部分を修正しながら、論理合成、インプリメンテーション、ビットストリームの生成を行って、XSA ファイルを生成した。今回は、XSA ファイルを使用して、Vitis 2019.2 でアプリケーション・ソフトウェアをビルドして、実機確認してみよう。

Vivado 2019.2 の Tools メニューから Launch Vitis を選択して Vitis 2019.2 を立ち上げる。
Eclipse Launcher ダイアログの Select a directory as workspace が立ち上げるので、 Browse... ボタンをクリックして、ワークスペースを選択する。
genesys_zu_91_200819.png

Genesys_ZU_MIPI_PCAM/display_port.xpr/display_port/ ディレクトリの下に vitis_work ディレクトリを新規作成して、そこをワークスペースに選択した。
genesys_zu_92_200819.png

Eclipse Launcher ダイアログの Select a directory as workspace で、 Workspace に Genesys_ZU_MIPI_PCAM/display_port.xpr/display_port/vitis_work を指定して、Launch ボタンをクリックした。
genesys_zu_93_200819.png

Vitis 2019.2 が立ち上げる。
Create Application Project をクリックして、アプリケーション・プロジェクトを作成する。
genesys_zu_94_200819.png

New Application Project ダイアログが表示された。
Project name: に dispport2 と入力した。
genesys_zu_95_200819.png

Platform 画面では、Create a new platform form hardware (XSA) タブをクリックして、+ アイコンをクリックして、前回作成した XSA ファイルを指定する。
genesys_zu_96_200819.png

design_1_wrapper.xsa を選択した。
genesys_zu_97_200819.png

design_1_wrapper [custom] が追加された。
design_1_wrapper [custom] を選択して、Next > ボタンをクリックした。
genesys_zu_98_200819.png

Domain はデフォルトのままとした。
genesys_zu_99_200819.png

Templates では、Empty Application を選択して、Finish ボタンをクリックした。
genesys_zu_100_200819.png

design_1_wrapper プラットフォームと dispport2 アプリケーション・プロジェクトが生成された。
genesys_zu_101_200819.png

SDK にあるソフトウェアのソースコードをインポートした。
genesys_zu_102_200819.png

dispport2 プロジェクトの src にソフトウェアのソースコードがインポートされた。
genesys_zu_103_200819.png

トンカチマークをクリックして、ビルドした。
ビルドが失敗した。
genesys_zu_104_200819.png

これは、”Genasys ZU で Adam Taylor さんの”High Performance Imaging”をやってみる”と同じ状況だ。

Vitis 2019.2 の Explorer ウインドウで、dispport2_system -> dispport2 を選択して、右クリックし、右クリックメニューから Properties を選択する。
Properties for dispport2 ダイアログが開く。
C/C++ Build の Settings を開いて、ARM v8 gcc linker -> Libraries を選択して、Libraries (-l) の Add... ボタンをクリックする。

Enter Value ダイアログが開く。
m を入力する。OKボタンをクリックする。

Libraries (-l) に m が入力された。
OKボタンをクリックする。
genesys_zu_105_200820.png

ビルドが成功して、elf ファイルができた。
genesys_zu_106_200820.png

Genesys ZU の電源をON した。
FPGA をコンフィグレーションし、アプリケーション・ソフトウェアを起動するために、dispport2 プロジェクトを選択して、 Run ボタンをクリックした。

FPGA がコンフィグレーションされて、アプリケーション・ソフトウェアが起動したが、残念がらカメラの画像はディスプレイに表示されなかった。
  1. 2020年08月21日 05:31 |
  2. Genesys_ZU
  3. | トラックバック:0
  4. | コメント:0
»