FC2カウンター FPGAの部屋 Vivado HLS のソースコードをSDx で試す1(memcpy() を使った第2段階のコード)

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

FPGAの部屋

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

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

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

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

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

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

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

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

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

に修正した。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return(y);
}

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

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


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

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

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

#include "bmp_header.h"

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

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

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

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

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

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

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

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

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

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

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

    return(0);
}

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

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

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

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

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

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

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

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

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

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

    return(y);
}

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

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


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

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック URL
http://marsee101.blog19.fc2.com/tb.php/3688-97571769
この記事にトラックバックする(FC2ブログユーザー)