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

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

FPGAの部屋

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

ビデオ信号にAXI4 Stream版のラプラシアンフィルタを通して画像出力1(準備編)

まずは過去記事をまとめよう。

ZYBOのHDMI入力をVGA出力に出力する4(実機テスト)”でZYBO1 のHDMI 出力から、ケーブルにカメラ画像信号を載せて、ZYBO2 のHDMI 入力から入力した画像信号をVGA出力して、カメラ画像を表示することに成功した。

ZYBOのHDMI入力画像にラプラシアンフィルタ処理を行う1(構想編)
ZYBOのHDMI入力画像にラプラシアンフィルタ処理を行う2(高位合成)
Vivado HLS 2014.4 でHLSストリームを使用してビデオ信号を入出力する
Vivado HLS 2014.4 でHLSストリームを使用してビデオ信号を入出力する2”でビデオ信号をVivado HLS で直接、ラプラシアンフィルタ処理後のビデオ信号に変換しようとしたが、なかなかうまく行かなかった。

今回は、”Vivado HLS 2014.4 でAXI4-Stream版ラプラシアンフィルタIP を作製する1(C++ ソースコードの公開)”のAXI4-Stream版ラプラシアンフィルタIP を使用して、ビデオ信号から AXI4 Stream に変換して、ラプラシアンフィルタ処理を行い、その出力の AXI4 Stream をビデオ信号に変換する。このIP は久しぶりにHDL で書いてみようと思う。

まずは、AXI4 Steram版ラプラシアンフィルタを少し改造する必要がある。制御用のAXI4 Lite Slave を除いて、ap_none プロトコルの lap_fil_enable ポートを追加する。そして、 return も ap_ctrl_none にする。ap_ctrl_none にすると C/RTL コシミュレーションが出来ないので、現在のAXI4 Steram版ラプラシアンフィルタで入力ストリームから出力ストリームまでの遅延を確認しておこう。

AXI4 Steram版ラプラシアンフィルタの C/RTLコシミュレーションを行って、Vivado を立ちあげて、TCL Console に次のコマンドを入力した。

cd C:/Users/Masaaki/Documents/Vivado_HLS/lap_filter_axis2_2014_4/solution1/sim/verilog/
current_fileset
open_wave_database lap_filter_axis.wdb
open_wave_config lap_filter_axis.wcfg

シミュレーション波形を下に示す。
dvi2lap2vga_14_150830.png

ins_TUSER から out_TUSER が 7 クロック遅れているのが分かった。
ins_TUSERが 1 になった次のクロックで、ins_TREADY が 1 クロックだけ、0 になってしまう。この時には、ストリーム・データを受け取れないということなので、LUT使用のFIFO を入れる必要がある。

とりあえずは、AXI4 Stream 版のラプラシアンフィルタIP を修正しよう。
  1. 2015年08月31日 04:26 |
  2. ZYBO
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2014.4 でHLSストリームを使用してビデオ信号を入出力する2

Vivado HLS 2014.4 でHLSストリームを使用してビデオ信号を入出力する”の続き。

前回は、Vivado HLSのHLSストリームを使用して、ビデオ信号からラプラシアンフィルタ処理をして、ビデオ信号として出力するC++のソフトウェアを作った。今回はテストベンチを作ってシミュレーション、CからHDLへ合成しようと思う。

Cシミュレーションを行った結果を下に示す。2つ空読みしているのだが、シミュレーションとしては成功した。
temp_lap.bmp にも A という文字のエッジが出力されていた。
dvi2lap2vga_9_150829.png

CからHDLへ合成した。クロック周期の制約が 10 ns に対して、24.6 ns になってしまった。
dvi2lap2vga_10_150829.png

Scheduling におけるワーニングが影響しているかもしれない。下にScheduling におけるワーニングメッセージを示す。

@I [HLS-10] ----------------------------------------------------------------
@I [HLS-10] -- Scheduling module 'lap_filter_rgb' 
@I [HLS-10] ----------------------------------------------------------------
@I [SCHED-11] Starting scheduling ...
@I [SCHED-61] Pipelining loop 'Loop 1'.
@W [SCHED-64] Unable to schedule the loop exit test ('icmp' operation ('tmp')) in the first II cycles (II = 1).
@W [SCHED-64] Unable to schedule the loop exit test ('icmp' operation ('tmp')) in the first II cycles (II = 2).
@W [SCHED-64] Unable to schedule the loop exit test ('icmp' operation ('tmp')) in the first II cycles (II = 3).
@W [SCHED-64] Unable to schedule the loop exit test ('icmp' operation ('tmp')) in the first II cycles (II = 4).
@I [SCHED-61] Pipelining result: Target II: 1, Final II: 5, Depth: 6.
@W [SCHED-21] Estimated clock period (24.6ns) exceeds the target (target clock period: 10ns, clock uncertainty: 1.25ns, effective delay budget: 8.75ns).
@W [SCHED-21] The critical path consists of the following:
    'load' operation ('line_buf_0_load', lap_filter_rgb_14_4/lap_filter_rgb.cpp:80) on array 'line_buf[0]' (2.39 ns)
    'add' operation ('tmp3', lap_filter_rgb_14_4/lap_filter_rgb.cpp:169->lap_filter_rgb_14_4/lap_filter_rgb.cpp:89) (2.44 ns)
    'add' operation ('sum2_i', lap_filter_rgb_14_4/lap_filter_rgb.cpp:169->lap_filter_rgb_14_4/lap_filter_rgb.cpp:89) (1.97 ns)
    'sub' operation ('sum3_neg_i', lap_filter_rgb_14_4/lap_filter_rgb.cpp:169->lap_filter_rgb_14_4/lap_filter_rgb.cpp:89) (1.97 ns)
    'sub' operation ('tmp_i', lap_filter_rgb_14_4/lap_filter_rgb.cpp:169->lap_filter_rgb_14_4/lap_filter_rgb.cpp:89) (2.44 ns)
    'sub' operation ('tmp_5_i', lap_filter_rgb_14_4/lap_filter_rgb.cpp:169->lap_filter_rgb_14_4/lap_filter_rgb.cpp:89) (2.44 ns)
    'sub' operation ('tmp_6_i', lap_filter_rgb_14_4/lap_filter_rgb.cpp:169->lap_filter_rgb_14_4/lap_filter_rgb.cpp:89) (2.44 ns)
    'sub' operation ('y', lap_filter_rgb_14_4/lap_filter_rgb.cpp:169->lap_filter_rgb_14_4/lap_filter_rgb.cpp:89) (2.44 ns)
    'mul' operation ('y_i88_op', lap_filter_rgb_14_4/lap_filter_rgb.cpp:169->lap_filter_rgb_14_4/lap_filter_rgb.cpp:89) (6.08 ns)
@I [SCHED-11] Finished scheduling.


C/RTL Co-Simulation を行った。Dump Trace を all にして実行した。成功したが、やはり空行を2つ読んでしまっている。
こちらも temp_lap.bmp にも A という文字のエッジが出力されていた.
dvi2lap2vga_11_150829.png

C/RTL Co-Simulation を行った波形を見るために Vivado 2014.4 を立ちあげて、Tcl Console に次のコマンドを入力した。

cd c:/Users/Masaaki/Documents/Vivado_HLS/ZYBO/lap_filter_rgb_14_4/solution1/sim/verilog
current_fileset
open_wave_database lap_filter_rgb.wdb
open_wave_config lap_filter_rgb.wcfg

シミュレーション波形が表示された。
dvi2lap2vga_12_150829.png

シミュレーション波形を拡大すると、5クロックで1ピクセルを処理していた。これでは使えない。
dvi2lap2vga_13_150829.png

やはり、ラプラシアンフィルタはAXI4 Stream版を使用して、回りは久しぶりに Verilog HDL で書いてみようか?久しく書いていないので、忘れているかもしれない?

最後に、lap_fil_rgb_tb.cpp を貼っておく。

//
// lap_fil_rgb_tb.cpp
// 2015/08/22 by marsee
//

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

#include "lap_filter_rgb.h"
#include "bmp_header.h"

#define LOOP_COUNT 5

void lap_filter_rgb(ap_uint<1> lap_fil_enable, hls::stream<vid_in<24> >& video_in, hls::stream<RGB<24> >& video_out);

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);
void lap_filter_rgb_soft(ap_uint<1> lap_fil_enable, hls::stream<vid_in<24> >& video_in, hls::stream<RGB<24> >& video_out);

int main(){
    using namespace std;

    hls::stream<vid_in<24> > ins;
    hls::stream<RGB<24> > outs;
    hls::stream<vid_in<24> > ins_soft;
    hls::stream<RGB<24> > outs_soft;
    vid_in<24> pix;
    RGB<24> vals;
    RGB<24> 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;
    int i, j;

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

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

    // 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] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    // ins に入力データを用意する
    for(int i=0; i<LOOP_COUNT; i++){    // Vertical Sync
           pix.rgb_pData = 0;
         pix.rgb_pVDE = 0;
         pix.rgb_pHSync = 0;
         pix.rgb_pVSync = 1;
        ins << pix;
        ins_soft << pix;
    }
    for(int i=0; i<LOOP_COUNT; i++){    // Vertical back porch
           pix.rgb_pData = 0;
         pix.rgb_pVDE = 0;
         pix.rgb_pHSync = 0;
         pix.rgb_pVSync = 0;
        ins << pix;
        ins_soft << pix;
    }

    for(j=0; j < bmpihr.biHeight; j++){
        for(i=0; i < bmpihr.biWidth; i++){
            pix.rgb_pData = (ap_int<32>)rd_bmp[(j*bmpihr.biWidth)+i];
            pix.rgb_pVDE = 1;
            pix.rgb_pHSync = 0;
            pix.rgb_pVSync = 0;
            ins << pix;
            ins_soft << pix;
        }
        for(int i=0; i<LOOP_COUNT; i++){    // Horizontal front porch
               pix.rgb_pData = 0;
             pix.rgb_pVDE = 0;
             pix.rgb_pHSync = 0;
             pix.rgb_pVSync = 0;
            ins << pix;
            ins_soft << pix;
        }
        for(int i=0; i<LOOP_COUNT; i++){    // Horizontal Sync
               pix.rgb_pData = 0;
             pix.rgb_pVDE = 0;
             pix.rgb_pHSync = 1;
             pix.rgb_pVSync = 0;
            ins << pix;
            ins_soft << pix;
        }
        for(int i=0; i<LOOP_COUNT; i++){    // Horizontal back porch
               pix.rgb_pData = 0;
             pix.rgb_pVDE = 0;
             pix.rgb_pHSync = 0;
             pix.rgb_pVSync = 0;
            ins << pix;
            ins_soft << pix;
        }
    }
    for(i=0; i < bmpihr.biWidth; i++){    // No Display, Horizontal Sync
        pix.rgb_pData = 0;
        pix.rgb_pVDE = 0;
        pix.rgb_pHSync = 0;
        pix.rgb_pVSync = 0;
        ins << pix;
        ins_soft << pix;
    }
    for(int i=0; i<LOOP_COUNT; i++){    // Horizontal front porch
           pix.rgb_pData = 0;
         pix.rgb_pVDE = 0;
         pix.rgb_pHSync = 0;
         pix.rgb_pVSync = 0;
        ins << pix;
        ins_soft << pix;
    }
    for(int i=0; i<LOOP_COUNT; i++){    // Horizontal Sync
           pix.rgb_pData = 0;
         pix.rgb_pVDE = 0;
         pix.rgb_pHSync = 1;
         pix.rgb_pVSync = 0;
        ins << pix;
        ins_soft << pix;
    }
    for(int i=0; i<LOOP_COUNT; i++){    // Horizontal back porch
           pix.rgb_pData = 0;
         pix.rgb_pVDE = 0;
         pix.rgb_pHSync = 0;
         pix.rgb_pVSync = 0;
        ins << pix;
        ins_soft << pix;
    }
    for(int i=0; i<LOOP_COUNT; i++){    // Vertical Sync
           pix.rgb_pData = 0;
         pix.rgb_pVDE = 0;
         pix.rgb_pHSync = 0;
         pix.rgb_pVSync = 1;
        ins << pix;
        ins_soft << pix;
    }


    lap_filter_rgb(1, ins, outs);
    lap_filter_rgb_soft(1, ins_soft, outs_soft);

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    cout << endl;
    cout << "outs" << endl;
    for(j=0; j < bmpihr.biHeight; j++){
        for(i=0; i < bmpihr.biWidth; ){
            outs >> vals;
            outs_soft >> vals_soft;
            if (vals.vid_pData != vals_soft.vid_pData || vals.vid_pVDE != vals_soft.vid_pVDE ||
                vals.vid_pHSync != vals_soft.vid_pHSync || vals.vid_pVSync != vals_soft.vid_pVSync){
                printf("ERROR HW and SW results mismatch i = %ld, j = %ld\n", i, j);
                printf("HW : Data = %x, VDE = %x, HSYNC = %X, VSYNC = %x\n", (int)vals.vid_pData, (int)vals.vid_pVDE, (int)vals.vid_pHSync, (int)vals.vid_pVSync);
                printf("SW : Data = %x, VDE = %x, HSYNC = %X, VSYNC = %x\n", (int)vals_soft.vid_pData, (int)vals_soft.vid_pVDE, (int)vals_soft.vid_pHSync, (int)vals_soft.vid_pVSync);
                return(1);
            }
            if (!vals.vid_pVDE)
                continue;
            unsigned int val = (unsigned int)vals.vid_pData;
            hw_lapd[(j*bmpihr.biWidth)+i] = (int)val;
            i++;
        }
    }

    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(char), 2, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

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

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

    return 0;
}

void lap_filter_rgb_soft(ap_uint<1> lap_fil_enable, hls::stream<vid_in<24> >& video_in, hls::stream<RGB<24> >& video_out){
    enum  {idle, vsync_assert, v_back_porch, h_video, h_front_porch, hsync_assert, h_back_porch, v_front_porch, vsync_assert2};
    int cstate = idle;
    int cstateb = idle;

    unsigned int line_buf[2][HORIZONTAL_PIXEL_WIDTH];

    int pix_mat[3][3];

    int lap_fil_val;
    int x=0, y=0;
    int y_val;
    int val;
    int first_h_video;

    vid_in<24> pix;
    RGB<24> lap;

    while(cstate != vsync_assert2){
        if(!(cstate==h_video && (cstateb==v_back_porch || cstateb==h_back_porch))){ // v_back_porch と h_back_porch から h_videoになった時はストリームをReadしない
            video_in >> pix;
            first_h_video = 0;
        } else
            first_h_video = 1;
        cstateb = cstate;
       switch (cstate){
        case idle :
            if (pix.rgb_pVSync)
                cstate = vsync_assert;
            x = 0; y = 0;
            lap_fil_val = pix.rgb_pData;
            break;
        case vsync_assert :
            if (!pix.rgb_pVSync)
                cstate = v_back_porch;
            lap_fil_val = pix.rgb_pData;
            break;
        case v_back_porch :
            if (pix.rgb_pVDE){
                cstate = h_video;
                lap_fil_val = 0;
            } else
                lap_fil_val = pix.rgb_pData;
            break;
        case h_video :
            if (!pix.rgb_pVDE){
                cstate = h_front_porch;
                lap_fil_val = pix.rgb_pData;
            } else {
                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];

                y_val = conv_rgb2y_soft((unsigned int)pix.rgb_pData);
                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_fil_val = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB蜷後§蛟、繧貞?・繧後k

                if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので0とする
                    lap_fil_val = 0;
                x++;
                if (first_h_video)
                    continue;
            }
            break;
        case h_front_porch:
            if (pix.rgb_pHSync)
                cstate = hsync_assert;
            lap_fil_val = pix.rgb_pData;
            break;
        case hsync_assert :
            if (!pix.rgb_pHSync)
                cstate = h_back_porch;
            lap_fil_val = pix.rgb_pData;
            break;
        case h_back_porch :
            if (pix.rgb_pHSync) // rgb_pVDE が来ないうちに rgb_pHSync が来たので表示期間終了
                cstate = v_front_porch;
            else if (pix.rgb_pVDE){
                cstate = h_video;
                y++;
                x = 0;
                lap_fil_val = 0;
            } else
                lap_fil_val = pix.rgb_pData;
            break;
        case v_front_porch :
            if (pix.rgb_pVSync)
                cstate = vsync_assert2;
            break;
        }
        if (cstate == h_video){
            if (lap_fil_enable)
                lap.vid_pData = (ap_uint<24>)lap_fil_val;
            else
                lap.vid_pData = pix.rgb_pData;
        }else
            lap.vid_pData = pix.rgb_pData;
        lap.vid_pHSync = pix.rgb_pHSync;
        lap.vid_pVDE = pix.rgb_pVDE;
        lap.vid_pVSync = pix.rgb_pVSync;

        video_out << lap;
    }
}

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

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

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

    return(y);
}

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

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

  1. 2015年08月29日 04:17 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2014.4 を使ったAXI4 Master版のラプラシアンフィルタ

Vivado HLS 2014.4 で合成したラプラシアンフィルタIPの高速化15(性能が最大になる設定を探る8、追加1)”で、tu1978さんとおるさんの協力(というか、コードを書いてもらちゃいましたが)の元に実現した最速のAXI4 Master版ラプラシアンフィルタだが、なかなかシミュレーションすることができなかった。

今回は、”Vivado HLSの INTERFACE m_axi ディレクティブのオプション”でわかったことを使って、CシミュレーションもC/RTL協調シミュレーションもできるようにした。
C++ソースコードは多少修正しているのだが、filter_line()関数をコールしていると、どうしてもC/RTL協調シミュレーションでハードウェアの値が 0 になってしまうという事態が発生してしまった。Cシミュレーションはソフトウェアとハードウェアの値が同一と言っているのだが、C/RTL協調シミュレーションには違うと言われてしまう。
解決方法としては、filter_line()関数の中身をトップのlap_filter_axim の中に統合した。こうするとC/RTL協調シミュレーション時にもハードウェアとソフトウェアの値は同一となる。
この状態でCからHDLへ合成すると、タイミング・エラーになってしまった。なので、PIPELINEディレクティブの位置を少し移動した。
次に、”Vivado HLSの INTERFACE m_axi ディレクティブのオプション”での Vivado HLS の AXI4 Master インターフェース生成用のディレクティブ INTERFACE m_axi ディレクティブの offset=slave も使用した。

テストベンチでラプラシアンフィルタに入力するデータは A という文字でそれがラプラシアンフィルタを通ってエッジになればOKというものだ。下に概要を示す。これは、Vivado HLS勉強会資料4(AXI Master編)の一部だ。
HLS_AXI4M_slave_1_150827.png

Vivado HLS 2014.4 の画面を示す。
HLS_AXI4M_slave_2_150827.png

Cシミュレーション結果を示す。
HLS_AXI4M_slave_5_150827.png

CからHDLへの合成を行った。結果を示す。
HLS_AXI4M_slave_3_150827.png

HLS_AXI4M_slave_4_150827.png

C/RTL Co-Simulation を行った。Dump Trace を all にして実行した。成功した。
HLS_AXI4M_slave_6_150827.png

VivadoでC/RTL Co-Simulation を見た。64 ピクセル x 48 行のラプラシアンフィルタ処理は約 112 us で終了した。
HLS_AXI4M_slave_7_150827.png

1行Read する間隔は 2.33 us だった。ARLEN, AWLEN の値は0x3f なので、64 バースト転送となる。
HLS_AXI4M_slave_8_150827.png

AXI4 Lite Slaveのアドレスマップを示す。

// AXILiteS
// 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 ap_return
//        bit 31~0 - ap_return[31:0] (Read)
// 0x18 : Data signal of cam_fb_offset
//        bit 31~0 - cam_fb_offset[31:0] (Read/Write)
// 0x1c : reserved
// 0x20 : Data signal of lap_fb_offset
//        bit 31~0 - lap_fb_offset[31:0] (Read/Write)
// 0x24 : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

                    prev[2] = current[2];
                    current[2] = next[2];
                    next[2] = conv_rgb2y(line_buf[tl][x+1]);
#pragma HLS pipeline
                    lap_fil_val = laplacian_fil(prev[0], current[0], next[0],
                                                prev[1], current[1], next[1],
                                                prev[2], current[2], next[2]);
                }
                lap_buf[x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる
            }
        }
#ifndef __SYNTHESIS__
        printf("write back:%d\n", y);
#endif
        memcpy((int*)&lap_fb[y*(HORIZONTAL_PIXEL_WIDTH)], (const int*)lap_buf, HORIZONTAL_PIXEL_WIDTH*sizeof(int));

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

    return(0);
}

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

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

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

    return(y);
}

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

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

  1. 2015年08月27日 06:39 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLSの INTERFACE m_axi ディレクティブのオプション

Vivado HLS の AXI4 Master インターフェース生成用のディレクティブ INTERFACE m_axi ディレクティブにオプションが増えていた。
それは、offset オプションで Vivado Design Suite ユーザー ガイド 高位合成 UG902 (v2014.1) には記載がないが、Vivado Design Suite ユーザー ガイド 高位合成 UG902 (v2014.3) の 156 ページに記載がある。
offset オプションの設定値をVivado Design Suite ユーザー ガイド 高位合成 UG902 (v2014.3) の 156 ページから引用する。

• off : オフセッ トアドレス を適用しません。 これがデフォルトです。
• direct : アドレス オフセット を適用する ために 32 ビット ポート を追加します。
• slave : アドレス オフセット を適用する ために AXI4-Lite インターフェイス内に 32 ビット ポート を追加します。

という訳で、わざわざオフセット用の入力ポートを追加する必要が無くなった。
使い方は後日、ブログに書いておこうと思う。

もう1つ、Vivado HLS の AXI4 Master インターフェース生成用のディレクティブ INTERFACE m_axi ディレクティブのオプションについて書いておく。
それは、depth オプションだ。このオプションはC/RTL 協調シミュレーションの時に意味があるようだ。この値を変えて高位合成しても結果は変わらない。
Vivado Design Suite ユーザー ガイド 高位合成 UG902 (v2014.3)の 134 ページに記載がある。それによると、

depth オプションの値が小さすぎると、 C/RTL 協調シミュレーシ ョンがデッドロック状態にな り ます。

とのことだ。
この設定値は、結局、画像だったら総ピクセル数に設定するのが良いようだ。
例えば、64 ピクセル x 48 ライン = 3078 ピクセルの画像を C/RTL 協調シミュレーションする場合には、

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

このように設定するのが良いようだ。
  1. 2015年08月26日 09:49 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2014.4 でHLSストリームを使用してビデオ信号を入出力する

ZYBOのHDMI入力画像にラプラシアンフィルタ処理を行う2(高位合成)”で lap_filter_rgb.cpp を作ったが、このファイルのシミュレーションをどうやってやるのか考えていた。
lap_filter_rgb.cpp には、HSYNCやVSYNC、VDEやpDATAなどのスティミュラスを与える必要がある。与えるためにはテストベンチに制御を残しておく必要があるが、lap_filter_rgb.cpp をコールしてからどうやって、それらのスティミュラスを作ろうか?スレッド立てるのも方法だろうし、スティミュラスをすべて配列に保存しておいて、lap_filter_rgb.cpp に読んでもらっても良いかも?

調べてみるとVivado HLS にHLSストリームというライブラリがあって、ストリームとして扱えるようになっている。この例はAXI4 Streamで使っている。今回は、独自のtemplate を作って、ビデオ信号の入出力を試みることにした。なお、HLSストリームライブラリについては、”Vivado Design Suite ユーザー ガイド 高位合成 UG902 (v2014.3) 2014 年 10 月 1 日”の 221 ページのHLSストリームライブラリを参照下さい。

最初に、lap_filter_rgb.h に書いた template を紹介するために、lap_filter_rgb.h を貼っておく。
(2015/08/26:修正)

// lap_filter_rgb.h
// 2015/08/22

//#define HORIZONTAL_PIXEL_WIDTH    800
//#define VERTICAL_PIXEL_WIDTH    600

#ifndef __LAP_FILTER_RGB__
#define __LAP_FILTER_RGB__

#include "ap_int.h"

#define HORIZONTAL_PIXEL_WIDTH    64
#define VERTICAL_PIXEL_WIDTH    48

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

template<int Data>
    struct vid_in{
        ap_uint<Data>    rgb_pData;
        ap_uint<1>        rgb_pHSync;
        ap_uint<1>        rgb_pVSync;
        ap_uint<1>        rgb_pVDE;
    };

template<int Data>
    struct RGB{
        ap_uint<Data>    vid_pData;
        ap_uint<1>        vid_pHSync;
        ap_uint<1>        vid_pVSync;
        ap_uint<1>        vid_pVDE;
};

#endif


vid_in と RGB の2つのtemplate を定義している。これは、Vivado のブロックデザインの信号そのままだ。
dvi2lap2vga_1_150821.png

これに合わせてHLSストリームで lap_filter_rgb.cpp を書きなおした。
Vivado HLS 2014.4 で高位合成を行ったところエラーになった。
dvi2lap2vga_5_150823.png

エラー内容を示す。

@E [XFORM-801] Stream port 'video_in.V.rgb_pData.V' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:16) has invalid interface mode 'ap_none' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:19:1). Stream port only supports ap_hs, ap_fifo and axis modes.
@E [XFORM-801] Stream port 'video_in.V.rgb_pHSync.V' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:16) has invalid interface mode 'ap_none' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:19:1). Stream port only supports ap_hs, ap_fifo and axis modes.
@E [XFORM-801] Stream port 'video_in.V.rgb_pVSync.V' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:16) has invalid interface mode 'ap_none' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:19:1). Stream port only supports ap_hs, ap_fifo and axis modes.
@E [XFORM-801] Stream port 'video_in.V.rgb_pVDE.V' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:16) has invalid interface mode 'ap_none' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:19:1). Stream port only supports ap_hs, ap_fifo and axis modes.
@E [XFORM-801] Stream port 'video_out.V.vid_pData.V' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:16) has invalid interface mode 'ap_none' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:18:1). Stream port only supports ap_hs, ap_fifo and axis modes.
@E [XFORM-801] Stream port 'video_out.V.vid_pHSync.V' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:16) has invalid interface mode 'ap_none' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:18:1). Stream port only supports ap_hs, ap_fifo and axis modes.
@E [XFORM-801] Stream port 'video_out.V.vid_pVSync.V' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:16) has invalid interface mode 'ap_none' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:18:1). Stream port only supports ap_hs, ap_fifo and axis modes.
@E [XFORM-801] Stream port 'video_out.V.vid_pVDE.V' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:16) has invalid interface mode 'ap_none' (lap_filter_rgb_14_4/lap_filter_rgb.cpp:18:1). Stream port only supports ap_hs, ap_fifo and axis modes.
@E [HLS-70] Synthesizability check failed.


HLSストリームは、ap_none じゃダメで、ap_hs, ap_fifo, axis のどれかにしろとのことだ。ap_none が入出力数も少ないし、ノンブロッキングなんで良いと思ったのだが。。。HLSストリームを ap_hs に変更して、高位合成を行った。
今度は高位合成が成功した。
dvi2lap2vga_6_150824.png

dvi2lap2vga_7_150824.png

生成された Verilog HDL ファイルを見ると、ストリームの入出力の信号すべてに ap_vld と ap_ack が付いていた。
dvi2lap2vga_8_150824.png

ap_vld には常に 1 を、ap_ack は無視すればビデオ信号の入出力ができると思うが、ブロックデザインでつなぐのが面倒だ。どうしようか?考え中。

lap_filter_rgb.cpp を貼っておく。なお、間違っているかもしれない。まだシミュレーションしていないし。
(2015/08/26:修正) (2015/08/28:修正)

//
// lap_filter_rgb.cpp
// 2015/08/21 by marsee
//

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

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

void lap_filter_rgb(ap_uint<1> lap_fil_enable, hls::stream<vid_in<24> >& video_in, hls::stream<RGB<24> >& video_out){
#pragma HLS INTERFACE ap_ctrl_hs port=return
#pragma HLS INTERFACE ap_hs register port=video_out
#pragma HLS INTERFACE ap_hs register port=video_in
#pragma HLS INTERFACE ap_none register port=lap_fil_enable

    enum  {idle, vsync_assert, v_back_porch, h_video, h_front_porch, hsync_assert, h_back_porch, v_front_porch, vsync_assert2};
    int cstate = idle;
    int cstateb = idle;

    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;
    int x=0, y=0;
    int y_val;
    int val;
    int first_h_video;

    vid_in<24> pix;
    RGB<24> lap;

    while(cstate != vsync_assert2){
#pragma HLS PIPELINE rewind
        if(!(cstate==h_video && (cstateb==v_back_porch || cstateb==h_back_porch))){ // v_back_porch と h_back_porch から h_videoになった時はストリームをReadしない
            video_in >> pix;
            first_h_video = 0;
        } else
            first_h_video = 1;
        cstateb = cstate;
        switch (cstate){
        case idle :
            if (pix.rgb_pVSync)
                cstate = vsync_assert;
            x = 0; y = 0;
            lap_fil_val = pix.rgb_pData;
            break;
        case vsync_assert :
            if (!pix.rgb_pVSync)
                cstate = v_back_porch;
            lap_fil_val = pix.rgb_pData;
            break;
        case v_back_porch :
            if (pix.rgb_pVDE){
                cstate = h_video;
                lap_fil_val = 0;
            } else
                lap_fil_val = pix.rgb_pData;
            break;
        case h_video :
            if (!pix.rgb_pVDE){
                cstate = h_front_porch;
                lap_fil_val = pix.rgb_pData;
            } else {
                for (int k=0; k<3; k++){
                    for (int m=0; m<2; m++){
#pragma HLS UNROLL
                        pix_mat[k][m] = pix_mat[k][m+1];
                    }
                }
                pix_mat[0][2] = line_buf[0][x];
                pix_mat[1][2] = line_buf[1][x];

                y_val = conv_rgb2y((unsigned int)pix.rgb_pData);
                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_fil_val = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB蜷後§蛟、繧貞?・繧後k

                if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので0とする
                    lap_fil_val = 0;
                x++;
                if (first_h_video)
                    continue;
            }
            break;
        case h_front_porch:
            if (pix.rgb_pHSync)
                cstate = hsync_assert;
            lap_fil_val = pix.rgb_pData;
            break;
        case hsync_assert :
            if (!pix.rgb_pHSync)
                cstate = h_back_porch;
            lap_fil_val = pix.rgb_pData;
            break;
        case h_back_porch :
            if (pix.rgb_pHSync) // rgb_pVDE が来ないうちに rgb_pHSync が来たので表示期間終了
                cstate = v_front_porch;
            else if (pix.rgb_pVDE){
                cstate = h_video;
                y++;
                x = 0;
                lap_fil_val = 0;
            } else
                lap_fil_val = pix.rgb_pData;
            break;
        case v_front_porch :
            if (pix.rgb_pVSync)
                cstate = vsync_assert2;
            break;
        }
        if (cstate == h_video){
            if (lap_fil_enable)
                lap.vid_pData = (ap_uint<24>)lap_fil_val;
            else
                lap.vid_pData = pix.rgb_pData;
        }else
            lap.vid_pData = pix.rgb_pData;
        lap.vid_pHSync = pix.rgb_pHSync;
        lap.vid_pVDE = pix.rgb_pVDE;
        lap.vid_pVSync = pix.rgb_pVSync;

        video_out << lap;
    }
}

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

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

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

    return(y);
}

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

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

  1. 2015年08月24日 05:28 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

ZYBOのHDMI入力画像にラプラシアンフィルタ処理を行う2(高位合成)

ZYBOのHDMI入力画像にラプラシアンフィルタ処理を行う1(構想編)”の続き。

ZYBOの xc7z010clg400-1 のVivado HLS 2014.4 プロジェクトを作製して、lap_filter_rgb.cpp を作った。
ZYBOのHDMI入力画像にラプラシアンフィルタ処理を行う1(構想編)”に関数の定義部分を載せたが、それからap_uint<1> lap_fil_enable を追加した。

void lap_filter_rgb(ap_uint<1> lap_fil_enable, ap_uint<24> rgb_pData, ap_uint<1> rgb_pHSync, ap_uint<1> rgb_pVSync, ap_uint<1> rgb_pVDE,
ap_uint<24> *vid_pData, ap_uint<1> *vid_pHSync, ap_uint<1> *vid_pVSync, ap_uint<1> *vid_pVDE);

lap_fil_enable が 1 の時にはラプラシアンフィルタ処理、0 の時には通常のビデオ信号が出力されるはずだ。
dvi2lap2vga_2_150823.png

高位合成を行った。結果を示す。
dvi2lap2vga_3_150823.png

dvi2lap2vga_4_150823.png

lap_filter_rgb.cpp を貼っておく。なお、まだシミュレーションしていないので、修正することが十分にあると思うので、ご注意を。
今回はHDLに似せたつもりで、ステートマシン記述で書いてある。
いまのところ、lap_filter_rgb.h はインクルードはしてあるが使っていない。

//
// lap_filter_rgb.cpp
// 2015/08/21 by marsee
//

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

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

void lap_filter_rgb(ap_uint<1> lap_fil_enable, ap_uint<24> rgb_pData, ap_uint<1> rgb_pHSync, ap_uint<1> rgb_pVSync, ap_uint<1> rgb_pVDE,
    ap_uint<24> *vid_pData, ap_uint<1> *vid_pHSync, ap_uint<1> *vid_pVSync, ap_uint<1> *vid_pVDE){
#pragma HLS INTERFACE ap_ctrl_none register port=return
#pragma HLS INTERFACE ap_none register port=vid_pVDE
#pragma HLS INTERFACE ap_none register port=vid_pVSync
#pragma HLS INTERFACE ap_none register port=vid_pHSync
#pragma HLS INTERFACE ap_none register port=vid_pData
#pragma HLS INTERFACE ap_none register port=rgb_pVDE
#pragma HLS INTERFACE ap_none register port=rgb_pVSync
#pragma HLS INTERFACE ap_none register port=rgb_pHSync
#pragma HLS INTERFACE ap_none register port=rgb_pData
#pragma HLS INTERFACE ap_none register port=lap_fil_enable

    enum  {idle, vsync_assert, v_back_porch, h_video, h_front_porch, hsync_assert, h_back_porch, v_front_porch};
    int cstate = idle;

    unsigned int pix;
    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;
    int x, y;
    int y_val;

    // 一応、初期化
    *vid_pHSync=0; *vid_pVSync=0; *vid_pVDE=0; *vid_pData=0;

    while(cstate != v_front_porch){
#pragma HLS PIPELINE rewind
        switch (cstate){
        case idle :
            if (rgb_pHSync)
                *vid_pHSync = 1;
            else
                *vid_pHSync = 0;

            if (rgb_pVSync){
                *vid_pVSync = 1;
                cstate = vsync_assert;
            }
            x = 0; y = 0;
            break;
        case vsync_assert :
            if (rgb_pHSync)
                *vid_pHSync = 1;
            else
                *vid_pHSync = 0;

            if (!rgb_pVSync){
                *vid_pVSync = 0;
                cstate = v_back_porch;
            }
            break;
        case v_back_porch :
            if (rgb_pHSync)
                *vid_pHSync = 1;
            else
                *vid_pHSync = 0;

            if (rgb_pVDE){
                pix = (unsigned int)rgb_pData;
                cstate = h_video;
            }
            break;
        case h_video :
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                pix = (unsigned int)rgb_pData;

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

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

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

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2], 
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap_fil_val = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB蜷後§蛟、繧貞?・繧後k

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

            if (lap_fil_enable)
                *vid_pData = lap_fil_val;
            else
                *vid_pData = pix;
            *vid_pVDE = 1;

            if (!rgb_pVDE){
                *vid_pVDE = 0;
                cstate = h_front_porch;
            }
            x++;
            break;
        case h_front_porch:
            if (rgb_pHSync){
                *vid_pHSync = 1;
                cstate = hsync_assert;
            }
            break;
        case hsync_assert :
            if (!rgb_pHSync){
                *vid_pHSync = 0;
                cstate = h_back_porch;
            }
            break;
        case h_back_porch :
            if (rgb_pHSync){ // rgb_pVDE が来ないうちに rgb_pHSync が来たので表示期間終了
                *vid_pHSync = 1;
                cstate = v_front_porch;
            } else if (rgb_pVDE){
                pix = (unsigned int)rgb_pData;
                y++;
                cstate = h_video;
            }
            break;
        }
    }
}

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

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

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

    return(y);
}

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

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

  1. 2015年08月23日 04:56 |
  2. ZYBO
  3. | トラックバック:0
  4. | コメント:0

ZYBOのHDMI入力画像にラプラシアンフィルタ処理を行う1(構想編)

ZYBOのHDMI入力をVGA出力に出力する4(実機テスト)”でHDMI端子から入力した画像をVGAポートに出力することができた。
今回は、RGB信号からラプラシアンフィルタ処理をしてVGAポートに出力してみよう。
RGB信号からラプラシアンフィルタ処理をしてRGB信号として出力する部分はVivado HLS で書いてみることにする。

下のブロックデザインで RGBインターフェースと vid_in インターフェースの信号をVivado HLS 2014.4 の入出力ポートとして定義して、その間でラプラシアンフィルタ処理を行うことにする。
dvi2lap2vga_1_150821.png

void lap_filter_rgb(ap_uint<24> rgb_pData, ap_uint<1> rgb_pHSync, ap_uint<1> rgb_pVSync, ap_uint<1>rgb_pVDE,
ap_uint<24> *vid_pData, ap_uint<1> *vid_pHSync, ap_uint<1> *vid_pVSync, ap_uint<1> *vid_pVDE){


タイミングもすべてVivado HLS で書こうとしたが、とても難しい。出力タイミングはHDLで書いたほうがよっぽど楽だと思うが、あえてVivado HLSでトライしてみたいと思う。
以前、ビデオ出力のCソースコードは書いたことがあるが、その時はタイミングを全部自分で生成していた。今回は、入力された信号タイミングに合わせて出力タイミングを調整するため、以前よりも難しいと感じた。
  1. 2015年08月21日 05:09 |
  2. ZYBO
  3. | トラックバック:0
  4. | コメント:0

ZYBOのHDMI入力をVGA出力に出力する4(実機テスト)

”ZYBOのHDMI入力をVGA出力に出力する3(バグフィックス?)”の続き。

SDSoCをやっていたので、ずいぶんと前になってしまったが、”ZYBOのHDMI入力をVGA出力に出力する3(バグフィックス?)”で、ZYBOのHDMI入力からVGA出力に出力する回路が出来上がったので、動かしてみようと思う。

HDMIのソースは以前から使っている Ubuntu 14.04 LTSを載せた ZYBO がHDMI 出力することができるので、それをHDMI のソースにして、もう1つのZYBO に dvi2vga_test のビットストリームをダウンロードして、HDMI 入力を Ubuntu の動作しているZYBO のHDMI 出力に接続して、VGA出力から画像を出そうという計画だ。

下の写真のように、ピンクの四角で囲ったZYBO 同士をピンクの矢印のHDMI ケーブルで接続している。
dvi2vga_35_150818.jpg
上のカメラ付きのZYBO が Ubuntu 14.04 LTS が載っていて、カメラの画像をVGA とHDMI に 800x600解像度で出力している。下のZYBO でHDMI を入力して、VGAポートに画像を出力できれば完成だ。

もうすでにプロジェクトはビットストリームの生成まで終了しているので、FPGAをコンフィグレーションすればよい。

Program and Debug の Hardware Manager -> Open Target をクリックして、ダイアログを表示させる。
dvi2vga_29_150818.png

2つZYBO が接続されているので、一旦、1つのZYBO を抜いてどちらがスレーブ側か確かめてあるので、迷わずに Digilent/210279651860A を選択した。
dvi2vga_30_150818.png

Summary が表示され、Finsih ボタンをクリックした。
dvi2vga_31_150818.png

Digilent/210279651860A がオープンされた。

Program device から xc7z010_1 を選択した。
dvi2vga_32_150818.png

Program Device ダイアログが表示された。

Program ボタンをクリックして、FPGAにビットストリームをダウンロードする。
dvi2vga_33_150818.png

でも、2つのZYNQ共にClose になってしまった。
dvi2vga_34_150818.png

もう一度、Digilent/210279651860A をオープンして、ZYNQ をコンフィグレーションが出来たが、スレーブのZYBOから画像は出力されなかった。

そのうちにVivado 2015.2 が落ちてしまったので、もう一度、立ちあげてコンフィグレーションしようとしたが、今度は、Open Target で ZYNQがオープンできなくなってしまった。

Open Target して、登録されたDigilent/210279651860A の設定を選ぶと、2つのエラーが出てしまう。
dvi2vga_36_150819.png

とりあえず、Z:\test\dvi2vga_test\dvi2vga_test.hw\hw_1 フォルダの hw.xml を見たが、よくわからないので、hw_org.xml に名前を変更した。
Tcl Console から open_hw を実行した。
もう一度、Open Target して、登録されたDigilent/210279651860A の設定を選んだ。
すると、うまく行きました。現在は、念の為、マスタ側のZYBO は起動していない。
dvi2vga_37_150819.png

Z:\test\dvi2vga_test\dvi2vga_test.hw\hw_1 フォルダには新しい hw.xml が生成されていた。
dvi2vga_38_150819.png

これで、もう一度、ビットストリームをダウンロードした。
ワーニングが出ているが、一応成功。
ZYBO の DONE も点灯した。
dvi2vga_39_150819.png

マスタ側の ZYBO を起動して、(Ubuntu 14.04 LTS) Apps ディレクトリに移動して、カメラ出力のコマンド ./cam_disp_uio を実行したら、なんと、スレーブ側のVGAポートからマスタ側のカメラ画像が出ました。。。やった~~~。とっても嬉しいです。。。
dvi2vga_40_150819.jpg

これで、ZYBO を連動させて計算されたりする基礎ができた。。。
  1. 2015年08月18日 05:37 |
  2. ZYBO
  3. | トラックバック:0
  4. | コメント:0

進撃の巨人(映画)を見てきました

今日は昨日に引き続いて、映画を見てきました。進撃の巨人です。人間が次々と巨人に食われて、グロかったですが、面白かったです。続きもあるので、それも見ようと思います。
  1. 2015年08月16日 20:19 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

FPGAの部屋のまとめサイトの更新(2015年8月16日)

FPGAの部屋のまとめサイトを更新しました。

SDSoC を追加して、今日までの記事を各カテゴリに追加しました。
  1. 2015年08月16日 07:12 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:0

「日本のいちばん長い日」を見てきました(映画)

今日は「日本のいちばん長い日」(映画、音注意)を見てきました。
第2次世界大戦の終戦時の事を描いた映画です。昭和天皇の苦悩がよく描けていたと思います。本木さん、演技が上手いと思いました。
  1. 2015年08月15日 20:20 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

2015年のお盆

今年のお盆は、お盆前の8月10日夜に近所の組合内のおばあさんが亡くなったという知らせを受けて、11日はお葬式の準備、12日はお葬式で忙しかったです。お葬式の担当は帳場で、香典やお見舞い、米代を受け取って香典帳にお名前と金額を記入する仕事でした。普段からパソコンで書類を書いていて、ほとんど字を書いたことが無い私は酷な仕事でした。案の定、手が動かなくて酷い字になってしまいました。お葬式があった家の人に申し訳ない気持ちでいっぱいでした。
(なお、うちの方では、10軒位(うちの組合は少なくなって8軒ですが)で組合を結成していまして、お葬式の時などに当事者として助け合っています)

13日からのお盆になっても、奥さんの実家が新盆で、奥さんは13日、14日は泊まり込みでお手伝いに行ってました。なかなか忙しいお盆です。
今日、15日はお袋の実家にお盆参りに行ってきます。

Vivado HLS勉強会を9月14日(月)、15日(火)に行うことになりました。大学内の院生や先生方に参加して頂いて、Vivado HLS便利だから、使える所は使いましょうよ。という勉強会です。実施方法は、単元ごとに簡単に説明してから、やってもらうというハンズオン方式です。説明だけしていると眠くなちゃうので、全部ハンズオンにしました。今のところ、基礎編、レジスタの挿入とPIPELINEディレクティブ、AXI4 Lite Slave, AXI4 Master編が出来ていて、スライド500ページ以上です。いま、AXI4 Stream編を書き始めたところです。間に合うと思います。。。たぶん。

FPGAの部屋のブログでは、最近はSDSoCをやって来ました。AXI4 Masterでハードウェアにしても、BRAMの数でエラーになってしまいます。BRAMは関係ないはずなんですが。。。このエラーを出さないオプションを探していますが、なかなか見つかりません。
後は、SDSoCでAXI4 Stream実装を試したいと思っています。しかし、SDSoCの前にやっていて実機テストするだけになっているHDMIのデータ転送の回路があるので、そっちを先にやろうと思います。
それよりもまずは、FPGAの部屋のブログ記事をまとめたいですね。

  1. 2015年08月15日 04:09 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した8(ハードウェア化5)

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した7(ハードウェア化4)”の続き。

前回はハードウェア化したラプラシアンフィルタ処理時間は 3.29 倍速くなったが、まだソフトウェア処理よりも遅かった。
次に、Vivado HLS で tu1978 さんが書き換えてくれたコードで試してみた。C++だと、テストベンチがC だったからか? extern "C" しても うまく行かなかったので、Cに書き換えて使用した。

前回と同じワークスペースに lap_filter3 プロジェクトを作製した。
SDSoC_115_150812.png

lap_filter_axim をハードウェア化に指定してある。

実機のZYBOに入れてテストした時のTera Termを示す。
SDSoC_114_150812.png

ハードウェアでのラプラシアンフィルタ処理時間は 153 us で、ソフトウェアでのラプラシアンフィルタ処理時間は299 us だった。ハードウェアでのラプラシアンフィルタ処理時間はソフトウェアでのラプラシアンフィルタ処理時間よりも 0.517 倍に短くなった。つまり性能は 1.95 倍になった。

D:\SDSoC\Examples\ZYBO\lap_filter3\SDRelease\_sds\p0\ipi の Vivado のプロジェクトを開いて見てみた。
Summary を見ると、前回よりもDSP リソースが増えている。BRAM はあまり使っていない。
SDSoC_116_150813.png

最後に laplacian_fitler3.c を示す。

/* * laplacian_fitter3.c * *  Created on: 2015/08/12 *      Author: Masaaki, tu1978 */

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

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

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

void filter_line(unsigned int* lap_buf, unsigned int* fl, unsigned int* sl, unsigned int* tl){
    int lap_fil_val;
    int prev[3],current[3],next[3];    // 0->1ライン目, 1->2ライン目, 2->3ライン目, prev->1pixel前, current->現在, next->次pixel
#pragma HLS array_partition variable=prev complete dim=0
#pragma HLS array_partition variable=current complete dim=0
#pragma HLS array_partition variable=next complete dim=0

    int x;

    next[0] = conv_rgb2y(fl[0]);
    next[1] = conv_rgb2y(sl[0]);
    next[2] = conv_rgb2y(tl[0]);

    for (x = 0; x < HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS pipeline
        if (x == 0 || x == HORIZONTAL_PIXEL_WIDTH-1){
            lap_fil_val = 0;

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

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

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

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

            prev[2] = current[2];
            current[2] = next[2];
            next[2] = conv_rgb2y(tl[x+1]);
            lap_fil_val = laplacian_fil(prev[0], current[0], next[0],
                                        prev[1], current[1], next[1],
                                        prev[2], current[2], next[2]);
        }
        lap_buf[x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val; // RGB同じ値を入れる
    }
}

//int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE], int width, int height)
int lap_filter_axim(int *cam_fb, int *lap_fb, int width, int height)
{
#pragma HLS INTERFACE m_axi port=cam_fb depth=800 offset=slave
#pragma HLS INTERFACE m_axi port=lap_fb depth=800 offset=slave
#pragma HLS INTERFACE s_axilite port=return

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

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

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

        if (y == 1){
#ifndef __SYNTHESIS__
            printf("copy 3 lines\n");
#endif
            for (a=0; a<3; a++){
 // 3ライン分
                memcpy(line_buf[a], (unsigned int*)(&cam_fb[a*(width)]), width*sizeof(int));
            }
        }else// 最初のラインではないので、1ラインだけ読み込む。すでに他の2ラインは読み込まれている
            memcpy(line_buf[tl], (unsigned int*)(&cam_fb[(y+1)*(width)]), width*sizeof(int));
        }
        filter_line(lap_buf, line_buf[fl], line_buf[sl], line_buf[tl]);
#ifndef __SYNTHESIS__
        printf("write back:%d\n", y);
#endif
        memcpy((unsigned int*)(&lap_fb[y*width]), (unsigned int*)lap_buf, width*sizeof(int));

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

    // 最初と最後のラインは0にする
    for (x = 0; x < HORIZONTAL_PIXEL_WIDTH; x++)
        lap_buf[x] = 0;
    memcpy((unsigned int*)&lap_fb[0], (unsigned int*)lap_buf, width*sizeof(int));
    memcpy((unsigned int*)&lap_fb[width*(height-1)], (unsigned int*)lap_buf, width*sizeof(int));

    return(0);
}

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

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

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

    return(y);
}

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

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


  1. 2015年08月13日 05:33 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoC のエディタに行番号を表示させる

SDSoC のエディタに行番号を表示させる方法を書いておく。

Window メニューから Preferences を選択する

Preferences ダイアログで、General -> Editors -> Text Editors を選択する。

Show line numbers にチェックを入れる
SDSoC_112_150812.png

OKボタンをクリックすると、行番号が表示された。
SDSoC_113_150812.png
  1. 2015年08月12日 21:28 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した7(ハードウェア化4)

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した6(ハードウェア化3)”の続き。

前回は、ハードウェア化する関数をAXI4 Master ポート入出力にする方法を発見して、ラプラシアンフィルタのハードウェア、ソフトウェアで実装した時の性能を確認した。その結果は、約4.5倍、ハードウェアの方が遅くなってしまった。

今回はハードウェア化する関数 lap_filter_axim() の書き方をVivado HLS でやったように memcpy() で書き換えてどのくらいハードウェアが速くなるのかやってみた。

前回と同じワークスペースに lap_filter1 プロジェクトを作製した。
SDSoC_110_150810.png

bmp_header.h, lap_filter_tb.c をコピーした。

laplacian_filter1.c を作ってコピーした。

初めに、バグ取りのために、すべてソフトウェアでビルドしてみた。実機のZYBOに入れてテストした時のTera Termを示す。
SDSoC_106_150810.png
ソフトウェアとしても、今回のラプラシアンフィルタの方が速い。ハードウェア用だが、ソフトウェアでのラプラシアンフィルタ処理時間は 254 us, ソフトウェアでのラプラシアンフィルタ処理時間は 302 us だった。ハードウェア用のラプラシアンフィルタ処理時間はソフトウェアのラプラシアンフィルタ処理時間の 254 us / 302 us ≒ 0.841 倍になった。よって、性能は 1.19 倍になった。

次に、lap_filter_axim() 関数をハードウェアとして登録してビルドした。実機のZYBOに入れてテストした時のTera Termを示す。
SDSoC_107_150810.png
ハードウェアでのラプラシアンフィルタ処理時間は 410 us, ソフトウェアでのラプラシアンフィルタ処理時間は 299 us だった。ハードウェア用のラプラシアンフィルタ処理時間はソフトウェアのラプラシアンフィルタ処理時間の 410 us / 299 us ≒ 1.37 倍になった。よって、性能は 0.72 倍になった。まだハードウェアの方が遅いが性能差はずいぶんと縮まった。
ハードウェア同士の性能を比較してみよう。前回のハードウェアのラプラシアンフィルタ処理時間は 1.35 ms なので、1350 us / 410 us ≒ 3.29 倍速くなった。但し、今回の lap_filter_axim() は、memcpy() と一部 PIPELINE ディレクティブを使用している。
D:\SDSoC\Examples\ZYBO\lap_filter1\SDRelease\_sds\p0\ipi の Vivado のプロジェクトを開いて見てみた。
Summary を見ると、前回よりもBRAM リソースが増えている。
SDSoC_109_150810.png

ブロックデザインは相変わらず、Vivado HLSのAXI4 Master AXI4 Interconnect を通って、PSのAXI ACPポートに入っているが見えす。
SDSoC_108_150810.png

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

// laplacian_filter1.c
// lap_filter_axim()

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

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

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

//int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE], int width, int height)
int lap_filter_axim(int *cam_fb, int *lap_fb, int width, int height)
{
#pragma HLS INTERFACE m_axi port=cam_fb depth=800 offset=slave
#pragma HLS INTERFACE m_axi port=lap_fb depth=800 offset=slave
#pragma HLS INTERFACE s_axilite port=return

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

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

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

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

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

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

    return(y);
}

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

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


  1. 2015年08月11日 04:34 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:3

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した6(ハードウェア化3)

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した5(ハードウェア化2)”の続き。

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した4(ハードウェア化)”では、メモリ領域の確保を malloc() で行った。
SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した5(ハードウェア化2)”では、メモリの確保を sds_alloc() で行った。そうすると、ソフトウェアのラプラシアンフィルタ処理時間が安定しなかった。ハードウェアは sds_alloc(), ソフトウェアは malloc() で領域を確保したほうが良さそうだ。

それとは別に、いまのところハードウェアの配列の引数がVivado HLSでハードウェア化されるとBRAMへのインターフェースになっていて、ZYBOのBRAMを超える大きさの画像が扱えない。大きな画像が扱えるために、Vivado HLSで生成されるラプラシアンフィルタ処理用IPのポートをAXI4 Master にしようと思っているのだが、どうもうまく行かなかった。
SDSoC 環境ユーザー ガイド UG1027 (v2015.2) 2015 年 7 月 20 日”の42ページに、AXI4 Masterアクセスの例が SDSoC\2015.2\samples\hls_if\mmult_hls_aximm にあるということで見てみた。
それは、ハードウェア化する関数で、Vivado HLSの様に m_axiの pragma を使用してAXI4 Master ポートにする方法だ。このように修正してもやはりBRAMを超える大きさの画像は扱えない。それに、このやり方はユーザーズガイドによると推奨されていないようだ。
ユーザーズガイドによると、

#pragma SDS data mem_attribute(A:SEQUENTIAL)属性を使用し、sdscc で自動的に配列転送が AXI4-Stream チャネルにマップされるようにします。

ということで、直接ハードウェア化する関数にINTERFACEディレクティブを書くの禁じ手の様だ。
禁じ手を使っても、BRAMの容量の制限を超えたいのだが、うまく行かない。。。
とりあえず、このやり方での性能を追求してみる。

SDSoCでビルドして、SDカードに書いて、ZYBOで性能を確認してみた。
SDSoC_103_150810.png
直近5つのデータの平均はハードウェアのラプラシアンフィルタ処理時間が 1.35 ms, ソフトウェアのラプラシアンフィルタ処理時間が 299 us だった。約4.5倍、ハードウェアの方が遅くなってしまった。

D:\SDSoC\Examples\ZYBO\lap_filter2\SDRelease\_sds\p0\ipi の Vivado のプロジェクトを開いて見てみた。
Summary を見ると、以前よりも明らかにBRAM が使われいない。これでもBRAMの制限に引っかかるのか?
SDSoC_104_150810.png

ブロックデザインを見たところ lap_filter_axim_0 の m_axi_gmem 出力はAXI Interconnect のスレーブポートに接続されていて、AXI4 Master になっていることが分かった。
SDSoC_105_150810.png

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

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

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

#include "sds_lib.h"

#include "bmp_header.h"

#define HORIZONTAL_PIXEL_WIDTH    64
#define VERTICAL_PIXEL_WIDTH    48
//#define HORIZONTAL_PIXEL_WIDTH    800
//#define VERTICAL_PIXEL_WIDTH    600

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return(0);
}

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

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

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

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

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

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

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

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

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

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

    return(y);
}

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

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


laplacian_filter2.c を貼っておく。

// laplacian_filter2.c
// lap_filter_axim()

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

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

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

//int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE], int width, int height)
int lap_filter_axim(int *cam_fb, int *lap_fb, int width, int height)
{
#pragma HLS INTERFACE m_axi port=cam_fb depth=800 offset=slave
#pragma HLS INTERFACE m_axi port=lap_fb depth=800 offset=slave
#pragma HLS INTERFACE s_axilite port=return

    unsigned int line_buf[3][ALL_PIXEL_VALUE];
    unsigned int lap_buf[ALL_PIXEL_VALUE];
    int x, y, i;
    int lap_fil_val;
    int a, b;
    int fl, sl, tl;

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

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

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

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

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

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

    return(y);
}

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

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

  1. 2015年08月09日 07:57 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した5(ハードウェア化2)

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した4(ハードウェア化)”の続き。

前回はハードウェア化したほうが遅いという結果が出たが、今回はmalloc() をすべて sds_alloc() に、free() を sds_free() にして、性能を測ってみた。こうすると物理メモリが連続的に取得され、ハードウェアに有利になるはずだ。
SDSoC_93_150808.png

D:\SDSoC\Examples\ZYBO\lap_filter2\SDRelease\_sds\reports フォルダの data_motion.html を見ると、すべてシンプルDMAだった。
SDSoC_94_150808.png

D:\SDSoC\Examples\ZYBO\lap_filter2\SDRelease\_sds\p0\ipi に zybo.xpr つまりVivado のプロジェクトがあるので、開いてみた。
zybo ブロックデザインを開いてみた。
SDSoC_95_150808.png
やはり、DMAがSGモードになっていない。

Summary を見てみた。FFが多い。
SDSoC_96_150808.png

Address Editor を示す。
SDSoC_97_150808.png

D:\SDSoC\Examples\ZYBO\lap_filter2\SDRelease\_sds\vhls\lap_filter_axim には、Vivado HLS 2015.2 のプロジェクトがあった。
これをVivado HLS 2015.2 で開いた。
SDSoC_98_150808.png

ソースを見ると、私が書いた laplacian_filter2.c がそのままソースファイルとして登録されている。これじゃ、BRAMを使うのはあたりまえだ。これを何とか、直接、DDR3 SDRAMへアクセスが行くようにしたい。
SDSoC_99_150808.png

D:\SDSoC\Examples\ZYBO\lap_filter2\SDRelease\sd_card の内容をSDカードに書いて、ZYBOで試してみた。
./lap_filter2.elf を実行したところ、ハードウェアのラプラシアンフィルタ処理時間は 503 us 、ソフトウェアのラプラシアンフィルタ処理時間は安定しない。最低は 600 us で、最高は、14.1 ms程度だった。
SDSoC_100_150808.png

最後にSDSoC で生成されたLinux の起動メッセージを貼っておく。なお、一部省略してある。

U-Boot 2014.07-00005-gd2f850a (Jan 27 2015 - 13:33:33)

Board:  Xilinx Zynq
I2C:   ready
DRAM:  ECC disabled 512 MiB
MMC:   zynq_sdhci: 0
SF: Detected S25FL128S_64K with page size 256 Bytes, erase size 64 KiB, total 16 MiB
*** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
Net:   Gem.e000b000
Hit any key to stop autoboot:  0
Device: zynq_sdhci
Manufacturer ID: 74
OEM: 4a45
Name: USD
Tran Speed: 50000000
Rd Block Len: 512
SD version 3.0
High Capacity: Yes
Capacity: 7.5 GiB
Bus Width: 4-bit
reading uEnv.txt
** Unable to read file uEnv.txt **
Copying Linux from SD to RAM...
reading uImage
3488184 bytes read in 344 ms (9.7 MiB/s)
reading devicetree.dtb
8438 bytes read in 52 ms (158.2 KiB/s)
reading uramdisk.image.gz
5044192 bytes read in 472 ms (10.2 MiB/s)
## Booting kernel from Legacy Image at 02080000 ...
   Image Name:   Linux-3.19.0-xilinx-apf
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    3488120 Bytes = 3.3 MiB
   Load Address: 00008000
   Entry Point:  00008000
   Verifying Checksum ... OK
## Loading init Ramdisk from Legacy Image at 04000000 ...
   Image Name:
   Image Type:   ARM Linux RAMDisk Image (gzip compressed)
   Data Size:    5044128 Bytes = 4.8 MiB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
## Flattened Device Tree blob at 02000000
   Booting using the fdt blob at 0x2000000
   Loading Kernel Image ... OK
   Loading Ramdisk to 1e855000, end 1ed247a0 ... OK
   Loading Device Tree to 1e84f000, end 1e8540f5 ... OK

Starting kernel ...

Booting Linux on physical CPU 0x0
Linux version 3.19.0-xilinx-apf (yogeshc@xsjpsgv105) (gcc version 4.9.1 (Sourcery CodeBench Lite 2014.11-30) ) #22 SMP PREEMPT Mon Jul 6 17:59:10 PDT 2015
CPU: ARMv7 Processor [413fc090] revision 0 (ARMv7), cr=18c5387d
CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache
Machine model: Zynq ZYBO Development Board
cma: Reserved 256 MiB at 0x0e800000
Memory policy: Data cache writealloc
PERCPU: Embedded 9 pages/cpu @5fb5d000 s8128 r8192 d20544 u36864
Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 130048
Kernel command line: console=ttyPS0,115200 earlyprintk
PID hash table entries: 2048 (order: 1, 8192 bytes)
Dentry cache hash table entries: 65536 (order: 6, 262144 bytes)
Inode-cache hash table entries: 32768 (order: 5, 131072 bytes)
Memory: 244956K/524288K available (4708K kernel code, 251K rwdata, 1644K rodata, 220K init, 250K bss, 17188K reserved, 262144K cma-reserved, 0K highmem)
Virtual kernel memory layout:
    vector  : 0xffff0000 - 0xffff1000   (   4 kB)
    fixmap  : 0xffc00000 - 0xfff00000   (3072 kB)
    vmalloc : 0x60800000 - 0xff000000   (2536 MB)
    lowmem  : 0x40000000 - 0x60000000   ( 512 MB)
    pkmap   : 0x3fe00000 - 0x40000000   (   2 MB)
    modules : 0x3f000000 - 0x3fe00000   (  14 MB)
      .text : 0x40008000 - 0x4063c614   (6354 kB)
      .init : 0x4063d000 - 0x40674000   ( 220 kB)
      .data : 0x40674000 - 0x406b2f20   ( 252 kB)
       .bss : 0x406b2f20 - 0x406f1ab8   ( 251 kB)
Preemptible hierarchical RCU implementation.
        RCU restricting CPUs from NR_CPUS=4 to nr_cpu_ids=2.
RCU: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=2
NR_IRQS:16 nr_irqs:16 16
L2C-310 erratum 769419 enabled
L2C-310 enabling early BRESP for Cortex-A9
L2C-310 full line of zeros enabled for Cortex-A9
L2C-310 ID prefetch enabled, offset 1 lines
L2C-310 dynamic clock gating enabled, standby mode enabled
L2C-310 cache controller enabled, 8 ways, 512 kB
L2C-310: CACHE_ID 0x410000c8, AUX_CTRL 0x76360001
slcr mapped to 60804000
zynq_clock_init: clkc starts at 60804100
Zynq clock init
sched_clock: 64 bits at 325MHz, resolution 3ns, wraps every 3383112499200ns
timer #0 at 60806000, irq=17
Console: colour dummy device 80x30
Calibrating delay loop... 1292.69 BogoMIPS (lpj=6463488)
pid_max: default: 32768 minimum: 301
Mount-cache hash table entries: 1024 (order: 0, 4096 bytes)
Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes)
CPU: Testing write buffer coherency: ok
CPU0: thread -1, cpu 0, socket 0, mpidr 80000000
Setting up static identity map for 0x475660 - 0x4756b8
CPU1: thread -1, cpu 1, socket 0, mpidr 80000001
Brought up 2 CPUs
SMP: Total of 2 processors activated (2591.94 BogoMIPS).
CPU: All CPU(s) started in SVC mode.
devtmpfs: initialized
VFP support v0.3: implementor 41 architecture 3 part 30 variant 9 rev 4
NET: Registered protocol family 16
DMA: preallocated 256 KiB pool for atomic coherent allocations
cpuidle: using governor ladder
cpuidle: using governor menu
hw-breakpoint: found 5 (+1 reserved) breakpoint and 1 watchpoint registers.
hw-breakpoint: maximum watchpoint size is 4 bytes.
zynq-ocm f800c000.ocmc: ZYNQ OCM pool: 256 KiB @ 0x60880000
vgaarb: loaded
SCSI subsystem initialized
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
media: Linux media interface: v0.10
Linux video capture interface: v2.00
pps_core: LinuxPPS API ver. 1 registered
pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <giometti@linux.it>
PTP clock support registered
EDAC MC: Ver: 3.0.0
Advanced Linux Sound Architecture Driver Initialized.
Switched to clocksource arm_global_timer
NET: Registered protocol family 2
TCP established hash table entries: 4096 (order: 2, 16384 bytes)
TCP bind hash table entries: 4096 (order: 3, 32768 bytes)
TCP: Hash tables configured (established 4096 bind 4096)
TCP: reno registered
UDP hash table entries: 256 (order: 1, 8192 bytes)
UDP-Lite hash table entries: 256 (order: 1, 8192 bytes)
NET: Registered protocol family 1
RPC: Registered named UNIX socket transport module.
RPC: Registered udp transport module.
RPC: Registered tcp transport module.
RPC: Registered tcp NFSv4.1 backchannel transport module.
Trying to unpack rootfs image as initramfs...
Freeing initrd memory: 4928K (5e855000 - 5ed25000)
hw perfevents: enabled with armv7_cortex_a9 PMU driver, 7 counters available
futex hash table entries: 512 (order: 3, 32768 bytes)
jffs2: version 2.2. (NAND) (SUMMARY)  c 2001-2006 Red Hat, Inc.
io scheduler noop registered
io scheduler deadline registered
io scheduler cfq registered (default)
dma-pl330 f8003000.dmac: Loaded driver for PL330 DMAC-241330
dma-pl330 f8003000.dmac:        DBUFF-128x8bytes Num_Chans-8 Num_Peri-4 Num_Events-16
e0001000.serial: ttyPS0 at MMIO 0xe0001000 (irq = 142, base_baud = 3125000) is a xuartps
console [ttyPS0] enabled
xdevcfg f8007000.devcfg: ioremap 0xf8007000 to 6086c000
[drm] Initialized drm 1.1.0 20060810
brd: module loaded
loop: module loaded
CAN device driver interface
libphy: MACB_mii_bus: probed
macb e000b000.ethernet eth0: Cadence GEM rev 0x00020118 at 0xe000b000 irq 143 (00:0a:35:00:01:22)
macb e000b000.ethernet eth0: attached PHY driver [Generic PHY] (mii_bus:phy_addr=e000b000.etherne:00, irq=-1)
e1000e: Intel(R) PRO/1000 Network Driver - 2.3.2-k
e1000e: Copyright(c) 1999 - 2014 Intel Corporation.
ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver
ehci-pci: EHCI PCI platform driver
usbcore: registered new interface driver usb-storage
mousedev: PS/2 mouse device common for all mice
i2c /dev entries driver
zynq-edac f8006000.memory-controller: ecc not enabled
Xilinx Zynq CpuIdle Driver started
Driver 'mmcblk' needs updating - please use bus_type methods
sdhci: Secure Digital Host Controller Interface driver
sdhci: Copyright(c) Pierre Ossman
sdhci-pltfm: SDHCI platform and OF driver helper
sdhci-arasan e0100000.sdhci: No vmmc regulator found
sdhci-arasan e0100000.sdhci: No vqmmc regulator found
mmc0: SDHCI controller on e0100000.sdhci [e0100000.sdhci] using ADMA
ledtrig-cpu: registered to indicate activity on CPUs
usbcore: registered new interface driver usbhid
usbhid: USB HID core driver
xlnk major 245
xlnk driver loaded
xlnk_pdev is not null
TCP: cubic registered
NET: Registered protocol family 17
can: controller area network core (rev 20120528 abi 9)
NET: Registered protocol family 29
can: raw protocol (rev 20120528)
can: broadcast manager protocol (rev 20120528 t)
can: netlink gateway (rev 20130117) max_hops=1
Registering SWP/SWPB emulation handler
drivers/rtc/hctosys.c: unable to open rtc device (rtc0)
ALSA device list:
  No soundcards found.
Freeing unused kernel memory: 220K (4063d000 - 40674000)
INIT: mmc0: new high speed SDHC card at address b368
mmcblk0: mmc0:b368 USD   7.45 GiB
 mmcblk0: p1 p2
version 2.88 booting
mkdir: can't create directory '/run/media/mmcblk0p1': No such file or directory
mount: mounting /dev/mmcblk0p1 on /run/media/mmcblk0p1 failed: No such file or directory
mkdir: can't create directory '/run/media/mmcblk0p2': No such file or directory
mount: mounting /dev/mmcblk0p2 on /run/media/mmcblk0p2 failed: No such file or directory
Creating /dev/flash/* device nodes
random: dd urandom read with 1 bits of entropy available
starting Busybox inet Daemon: inetd... done.
update-rc.d: /etc/init.d/run-postinsts exists during rc.d purge (continuing)
 Removing any system startup links for run-postinsts ...
  /etc/rcS.d/S99run-postinsts
INIT: Entering runlevel: 5
Configuring network interfaces... udhcpc (v1.23.1) started
Sending discover...
Sending discover...
Sending select for 192.168.3.63...
Lease of 192.168.3.63 obtained, lease time 86400
/etc/udhcpc.d/50default: Adding DNS 192.168.3.1
done.
Starting Dropbear SSH server: Generating key, this may take a while...
Public key portion is:

省略

dropbear.
Starting tcf-agent: OK

sh-4.3#



sds_alloc(), sds_free() を malloc(), free() に変更してやってみたところ、ソフトウェアのラプラシアンフィルタ処理時間は変動が無くなった。約 300 us 程度だった。ハードウェアのラプラシアンフィルタ処理時間は 588 us 位なので、やはり sds_alloc() に比べて時間が長くなっている。ハードウェアは sds_alloc(), ソフトウェアは malloc() で領域を確保したほうが良さそうだ。
SDSoC_101_150808.png
  1. 2015年08月08日 05:05 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:3

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した4(ハードウェア化)

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した3(ソフトウェアのみでテスト2)”の続き。

前回まで、ソフトウェアで性能の評価を行ったが、今回は、lap_filter_axim() をハードウェアにする。

project.sdsoc を開いて、Hardware Functions で Add Hardware Function の+記号をクリックする。

Select function for hardware acceleration ダイアログが開く。

lap_filter_axim(int *, int *, int, int) をクリックして、ハードウェア化する。
SDSoC_73_150803.png

Hardware Functions に lap_filter_axim が入った。
SDSoC_74_150803.png

左端の Project Explorer タブで、lap_filter2 を右クリックし、右クリックメニューから Clean Project を選択してから、Build Project を選択してビルドを行った。

make がエラーになった。
lap_filter2 -> SDRelease -> _sds -> repots -> sds_laplacian_filter2.log を見るとエラーに理由が分かった。
実は1回はハードウェア化できたのだが、800 x 600 にした時にエラーになるようになったようだ。
SDSoC_89_150806.png

エラーを下に示す。

ERROR: [SDSoC 0-0] Function "lap_filter_axim" argument "cam_fb" is mapped to RAM interface, but it's size is bigger than 16384. Please specify #pragma SDS data zero_copy(cam_fb) or #pragma SDS data access_pattern(cam_fb:SEQUENTIAL)



#pragma SDS data zero_copy の使い方は、”SDSoC 環境ユーザー ガイド UG1027 (v2015.2) 2015 年 7 月 20 日”の 21 ページの”第4章 : システム パフォーマンスの向上”の”コピーおよび共有メモリ セマンティクス”に書いてある。

lap_filter_axim() の前に次に示すようにpragma を付けた。

#pragma SDS data zero_copy(cam_fb[0:ALL_PIXEL_VALUE])
#pragma SDS data zero_copy(lap_fb[0:ALL_PIXEL_VALUE])
int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE], int width, int height)
{


もう一度、 Clean Project と Build Project を行った所、エラーになった。今度は、sds がエラーで、sds.log を見るとエラーの内容は次の理由だった。

ERROR: [SDSoC 0-0] Hardware function 'lap_filter_axim' BRAM resource requirement (1024) exceeds platform 'zybo' resource capacity (60)


ということで、ラプラシアンフィルタのワークスペースをBRAM に取るらしい。

次に、#pragma SDS data zero_copy の代わりに、#pragma SDS data access_pattern(cam_fb:SEQUENTIAL) を付けてみた。

#pragma SDS data access_pattern(cam_fb:SEQUENTIAL)
#pragma SDS data access_pattern(lap_fb:SEQUENTIAL)
int lap_filter_axim(int cam_fb[ALL_PIXEL_VALUE], int lap_fb[ALL_PIXEL_VALUE], int width, int height)
{

SDSoC_90_150806.png

これで、クリーンして、ビルドしても、やはり、

ERROR: [SDSoC 0-0] Hardware function 'lap_filter_axim' BRAM resource requirement (1024) exceeds platform 'zybo' resource capacity (60)

で止まっている。

DDR3 SDRAM に転送するにはどうやったら良いのだろうか?

とりあえず、小さいサイズだったらハードウェア化できたので、ピクセル値を元に戻してやってみる。

#define HORIZONTAL_PIXEL_WIDTH 64
#define VERTICAL_PIXEL_WIDTH 48
//#define HORIZONTAL_PIXEL_WIDTH 800
//#define VERTICAL_PIXEL_WIDTH 600

#define ALL_PIXEL_VALUE (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)


ビルドは成功した。
SDSoC_91_150806.png

SDカードに書いてZYBOの実機で試してみたところハードウェアでのラプラシアンフィルタの処理時間は590 us ソフトウェアでのラプラシアンフィルタの処理時間は 300 us 程度で、ハードウェアの方が遅かった。
SDSoC_92_150806.png
  1. 2015年08月06日 05:10 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した3(ソフトウェアのみでテスト2)

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した2(ソフトウェアのみでテスト)”の続き。

前回は、ラプラシアンフィルタのハードウェアとソフトウェアで動かすアプリケーションを作製した。但し、ハードウェアの実装はテストのため、まずはソフトウェアで実装し、ZYBOで試した。その際に、ラプラシアンフィルタの実装は、ハードウェア、ソフトウェアとも同じで、ソフトウェアの方が malloc() でメモリをアロケートしているので、ソフトウェアの遅いはずなのだが、速くなった。
これはキャッシュの問題かもしれないということで、ラプラシアンフィルタ処理の前にダミーのラプラシアンフィルタ処理を置いてみた。このダミーのラプラシアンフィルタ処理はハードウェア化の際には取り除く、なぜならばハードウェア化するとDMAでのデータ転送となるためキャッシュに読み込んでおく必要はないからだ。

下の図にダミーで入れたラプラシアンフィルタ処理を示す。
SDSoC_88_150806.png

これでビルドして、SDカードにコピーしてZYBOで試した結果を下に示す。
SDSoC_87_150805.png

HW(今のところソフトウェアです)は、58.4 ms 程度になった。前回は、61.4 ms だったので、3 ms 程度改善した。但しまだ 10 ms 程度の差がある。これは何故か?よくわからない。
考えられるとしたら、ラインバッファをスタックに取るより、ヒープに取ったほうが速いのか?
  1. 2015年08月06日 03:49 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した2(ソフトウェアのみでテスト)

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した1(ソースの公開)”の続き。

前回、SDSoC 2015.2 で、lap_filter2 プロジェクトを作製し、bmp_header.h, lap_fiter_tb.c, laplacian_filter2.c の3つのファイルをSource フォルダに入れた。

とりあえず、ソフトウェアのみでテストしてみよう。

トンカチの様な Build アイコンの右の下向き三角をクリックして、SDRelease を選択すると、ビルドが始まる。
SDSoC_80_150805.png

ビルドが終了して lap_fiter2.elf ができた。
SDSoC_81_150805.png

D:\SDSoC\Examples\ZYBO\lap_filter2\SDRelease\sd_card の内容を SDカードにコピーした。(ZYBO_boot_image.zipは元々あったファイルです)
test.bmp もコピーした。
SDSoC_82_150805.png

SDカードを取り外して、ZYBOに挿入し、電源ONでLinux が起動した。
./lap_filter2.elf を起動すると、ソフトウェアでのラプラシアンフィルタの処理時間と、ハードウェアでのラプラシアンフィルタの処理時間が表示された。
ls を実行すると、test_lap.bmp が生成されているのが分かる。
SDSoC_83_150805.png


(追記)
やはり、64 x 48 ピクセルだと実行時間が短いので、コメントを切り替えて、800 x 600 にしてもう一度、Build Project を行った。

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


D:\SDSoC\Examples\ZYBO\lap_filter2\SDRelease\sd_card の内容を SDカードにコピーした。

SDカードを取り外して、ZYBOに挿入し、電源ONでLinux が起動した。

./lap_filter2.elf を起動すると、ソフトウェアでのラプラシアンフィルタの処理時間と、ハードウェアでのラプラシアンフィルタの処理時間が表示された。
ls を実行すると、test_lap.bmp が生成されているのが分かる。
SDSoC_84_150805.png

ハードウェア(HW)(まだハードウェアにしていないけど)が 61.3 ms、ソフトウェア(SW)が 47.4 ms で、ラプラシアンフィルタの中身は同じコードでソフトウェアの方が malloc() をしている分遅いはずなのだが、キャッシュのせいだろうか?どちらもデータを一旦Read してからラプラシアンフィルタ処理に飛ぶようにしてみよう。
しかも、ソフトウェアでの処理時間は 47.4 ms と今まで一番速い。

なお、800 x 600 の原画像は、私のパソコン周辺だ。(test.bmp)
SDSoC_85_150805.jpg

ラプラシアンフィルタ処理をするとこうなる。(test_lap.bmp)
SDSoC_86_150805.jpg
  1. 2015年08月05日 05:14 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoC 2015.2 でハードウェアとソフトウェアのラプラシアンフィルタの性能を比較した1(ソースの公開)

SDSoC のチュートリアルをある程度やってきて、大体、雰囲気が分かったので、いつものラプラシアンフィルタを題材にハードウェアとソフトウェアの性能差を測ってみることにした。

SDSoCを立ちあげてCreate SDSoC Project をクリックした。
SDSoC_2_150726.png

New Project ダイアログで、Project name に lap_filter2 と入力して、Target の Platform を zybo にして、OSはデフォルトで Linux が選択されていた。Next > ボタンをクリックした。

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

プロジェクトが出来上がったので、Project Explorer の lap_filter_2 -> src に bmp_header.h, lap_fiter_tb.c, laplacian_filter2.c をドラッグ&ドロップした。
SDSoC_75_150803.png

全体の動作としては、64 x 48 ピクセルの test.bmp を読み込んで、ハードウェアとソフトウェア双方のラプラシアンフィルタで処理後の値を比較して、処理時間を計測する。更に、ハードウェアのラプラシアンフィルタの出力を test_lap.bmp ファイルとして書き込む。
laplacian_filter2.c がハードウェアにするラプラシアンフィルタで、ラプラシアンフィルタの出力1ピクセルごとに書き込む実装になっている。ハードウェアのラプラシアンフィルタとソフトウェアのラプラシアンフィルタの実装は同じだ。

それでは、bmp_header.h から貼っておく。これは、BMP ファイルフォーマット さんからBMPヘッダを引用させて頂きました。ありがとうございました。

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

#include <stdio.h>

#pragma once

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

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

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

#endif /* BMP_HEADER_H_ */


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

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

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

#include "bmp_header.h"

#define HORIZONTAL_PIXEL_WIDTH    64
#define VERTICAL_PIXEL_WIDTH    48
//#define HORIZONTAL_PIXEL_WIDTH    800
//#define VERTICAL_PIXEL_WIDTH    600

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

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

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

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

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

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

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

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

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

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

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

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

    return(0);
}

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

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

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

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

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

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

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

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

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

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

    return(y);
}

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

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


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

// laplacian_filter2.c
// lap_filter_axim()

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

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

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

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

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

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

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

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

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

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

    return(y);
}

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

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


なお、まだテストしていないので、間違っているかもしれない?

最後に test.bmp を示す。A という文字の画像だ。
SDSoC_76_150804.png

ソフトウェアのラプラシアンフィルタ関数 laplacian_filter_soft() とハードウェアのラプラシアンフィルタ関数 lap_filter_axim() を同じにしたかったのだが、ソフトウェアの方はBMPファイルのサイズによって、malloc() で画像の領域を取るため、ハードウェアでは許されなかった。しかも、intへのポインタでもダメで、確実に領域を確定する必要があった。これはハードウェアの使用リソースを決定してFPGAの内部構成を決定するために当たり前のことなのだが、より柔軟にできる可能性もあるのではないだろうか?
最大の画像サイズは決定する必要があるだろうが、その中のサイズの値はソフトウェアから値を与えてハードウェアで処理することもできそうだと思う。
  1. 2015年08月04日 05:26 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoC 2015.2 のZYBO用サンプルデザインをやってみる2(Matrix Multiplication)

SDSoC 2015.2 のZYBO用サンプルデザインをやってみる1(File IO Video Processing)”の続き。

前回は、画像のサンプルをやってみたが、SDカードに書いてLinux が立ち上がったのは、良かったけれど、入力するファイルが無いので、結局出来上がったアプリケーションの動作をテストすることができなかった。
今回は、ZC702 でもやってたMatrix Multiplication のサンプルをやることで、実機でテストすることを目指す。

Help メニューから Welcome をクリックして最初の画面を表示する。
Create SDSoC Project をクリックした。
SDSoC_2_150726.png

Project name に Example2 と入力して、Target の Platform を zybo にして、OSはデフォルトで Linux が選択されていた。Next > ボタンをクリックした。
SDSoC_58_150801.png

Matrix Multiplication (area reduced) を選択して、Finish ボタンをクリックした。
SDSoC_59_150801.png

Example2 プロンプトが作成された。
左端の Project Explorer タブで、Example2 を右クリックし、右クリックメニューから Build Project を選択して、ビルドを行った。SDDebug だった。
SDSoC_60_150802.png

D:\SDSoC\Examples\ZYBO\Example2\SDDebug\sd_card フォルダ の下のファイルを SDカードにコピーした。
SDSoC_61_150802.png

ZYBO のブートデバイスを SDカードにして電源を投入すると、Linuxが立ち上がって、Tera Termからコマンドプロンプトが見えた。設定は115200 bps, 8 bit, 1 stop bit だった。

cd /mnt で mnt ディレクトリに移動して、Example2.elf でソフトウェアを起動した。
SDSoC_62_150802.png
SWの cpu cycles が 988850 に対して、HW のcpu cycles が 1215356 だった。HWの方が遅いが、そもそもHW は組み込んでいない気がする?

project.sdsoc を開いて、Hardware Functions で Add Hardware Function の+記号をクリックする。

Select function for hardware acceleration ダイアログが開く。

mmult(float *, float *, float *) をクリックして、ハードウェア化する。
SDSoC_63_150802.png

Hardware Functions に mmult が入った。
SDSoC_64_150802.png

左端の Project Explorer タブで、Example2 を右クリックし、右クリックメニューから Clean Project を選択してから、Build Project を選択してビルドを行った。

Build が終了した。D:\SDSoC\Examples\ZYBO\Example2\SDDebug\_sds\p0\ipi の zybo.xpr を起動して、Vivado のプロジェクトを見てみた。
SDSoC_66_150802.png

Project Summary を見ると LUT を 85 %, DSP48 は 100 % を使用した大きなデザインだった。
SDSoC_65_150802.png

D:\SDSoC\Examples\ZYBO\Example2\SDDebug\sd_card フォルダ の下のファイルを SDカードにコピーして、実機で確認してみよう。

ZYBO のブートデバイスを SDカードにして電源を投入すると、Linuxが立ち上がって、Tera Termからコマンドプロンプトが見えた。設定は115200 bps, 8 bit, 1 stop bit だった。

cd /mnt で mnt ディレクトリに移動して、Example2.elf でソフトウェアを起動した。
SDSoC_67_150802.png
今回は、SWの cpu cycles が 944230 に対して、HW のcpu cycles が 32554だった。
HW の方が SW よりも 29 倍速いという結果になった。
ソフトウェアの方がハードウェアよりもチューニングが難しいと思っている。ソフトウェアは、OpenMPやNEON命令を使うことでもっとチューニングできると思う。

(2015/08/03:追記)
data_motion.html を下に示す。
sds_alloc() を使用しているので、すべてのDMAがシンプルDMAだった。
SDSoC_68_150803.png
  1. 2015年08月02日 05:10 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0

SDSoC 2015.2 のZYBO用サンプルデザインをやってみる1(File IO Video Processing)

今までやってきた SDSoC のチュートリアルは ZC702 ボード用なので、ZC702 ボードを持っていない私には実機動作ができない。
今回はZYBO用のサンプルデザインをやって、ZYBOで実機動作させてみようと思う。

SDSoC 2015.2 を立ち上げるとワークスペースを聞いてくる。D:\SDSoC\Examples\ZYBOをワークスペースのフォルダとして指定した。

SDSoC 2015.2 が立ち上がった。
Create SDSoC Project をクリックした。
SDSoC_2_150726.png

Target の Platform を zybo にして、OSはデフォルトで Linux が選択されていた。Next > ボタンをクリックした。
SDSoC_50_150801.png

Available Templates から File IO Video Processing を選択して、Finish ボタンをクリックした。
SDSoC_51_150801.png

左端の Project Explorer タブで、Example1 を右クリックし、右クリックメニューから Build Project を選択して、ビルドを行った。SDDebug だった。
SDSoC_52_150801.png

D:\SDSoC\Examples\ZYBO\Example1\SDDebug\sd_card の下のファイルを SDカードにコピーした。
SDSoC_53_150801.png

ZYBO のブートデバイスを SDカードにして電源を投入すると、Linuxが立ち上がって、Tera Termからコマンドプロンプトが見えた。設定は115200 bps, 8 bit, 1 stop bit だった。

/mnt/Example1.elf でソフトウェアを起動してみたが、yuv の画像を要求するようだ。
SDSoC_56_150801.png

ifconfig すると Ether も立ち上がっていたので、もう1つTera Termを立ちあげて、振られたIPに入ってみたらログインできた。IDは root, Passも root だった。
SDSoC_57_150801.png

Linux起動環境が自動的に構築されて、アプリケーションの起動環境も整えてくれるようだ。
  1. 2015年08月01日 05:11 |
  2. SDSoC
  3. | トラックバック:0
  4. | コメント:0