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

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

FPGAの部屋

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

Vivado HLSでラプラシアン・フィルタ式のみをaxi lite slaveモジュールにする1

前回の”ラプラシアン・フィルタのCソフトをVivado HLSで”では、今まで、ラプラシアン・フィルタのCソフトウェアとして動作していたCソフトウェアをVivado HLSでそのままハード化しようというものだったが、エラーが発生してコンパイルすることが出来なかった。
今回は、3x3のラプラシアン・フィルタの式のみを、以前Vivado HLSのサンプルしてやってみた axi lite を元に変更してみようと思う。多分、計算式がとっても簡単なので、ハードウェア化のメリットは無いどころか、ソフトウェアよりも遅くなるかもしれないがやってみようと思う。
(2013/10/05:注 図などを大幅に書き換えました)

以前やった Vivado HLS のaix liteサンプルの記事を示す。

Vivado HLSのExampleを試してみる1(axi_lite の生成)
Vivado HLSのExampleを試してみる2(シミュレーションと合成)
Vivado HLSのExampleを試してみる3(インターフェイス)
Vivado HLSのExampleを試してみる4(C/RTL Cosimulation)
Vivado HLSのExampleを試してみる5(IPにした)
Vivado HLSで作ったaxi_lite IPをテストした


Vivado HLSのプロジェクトをxc7z020clg484-1で作った。
image_process_Zed_linux_18_130930.png

Cのテストベンチを作製して、Cのシミュレーションを行った。結果は当然といえば当然に成功だった。
image_process_Zed_linux_19_131001.png

C Synthesis ボタンをクリックして、CからHDLへの合成を行った。成功した。
しかし、FFが 330個、LUTが 582個、使用されている。多い気がする。8を掛けるところは、3ビット左シフトすればよいのでは?更にレイテンシ 2 クロックで、インターバルが 3 クロックなので、これではダメだ。パイプラインにしないとね。
image_process_Zed_linux_20_131001.png

パイプラインにするために、PIPLINE Directive を入れよう。
右端のウインドウのDirective タブをクリックして、laplacian_filter の右クリックメニューから Insert Directive を選んだ。
image_process_Zed_linux_21_131001.png

Vivado HLS Directive Editor でPIPLINE Directive を選択して、Destination の Source File のラジオボタンをクリックした。OKボタンをクリックした。
image_process_Zed_linux_22_131001.png

#pragma HLS PIPELINE が追加された。
image_process_Zed_linux_23_131001.png

これで、C Synthesis ボタンをクリックして、CからHDLへの合成を行った。
レイテンシは 2 で変化がないが、インターバルが 1 になって、毎クロック、データを入力できるようになった。
FFは 491個で大幅に増えたが、LUTは 578個で少し減った。
image_process_Zed_linux_24_131001.png
  1. 2013年09月30日 05:53 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

ラプラシアン・フィルタのCソフトをVivado HLSで

ソフトウエアのプロファイリング4(ハードウェアと同じ方式)”のラプラシアン・フィルタのCソフトウェアをVivado HLSでコンパイルしてHDLコードの落として使ってみたい。そのため、Cプログラムを修正してVivado HLSでプロジェクトを作製してコンパイルしてみた。

ZedBoard用のZynq7020のプロジェクトを作製して、Cプログラムを作製して、mmap() や munmap() を削除していった。表示されるエラーが無くなったところで、C Synthesis ボタンを押してコンパイルしたところエラーが発生した。

エラーの内容を下に示す。
@E [SYNCHK-41] image_lap_fil.c:61: unsupported pointer reinterpretation from type 'int' to type 'i32*' on variable 'r_addr'.
@I [SYNCHK-10] 1 error(s), 0 warning(s).


image_process_Zed_linux_17_130929.png

アンサーを検索したら、”Vivado HLS 2013.1: workaround for unsupported pointer reinterpretation”などがあったが、ポインタの扱いがダメなのかもしれない。ラプラシアン・フィルタの演算部はとっても簡単で、これだけCでというわけにも行き難いので、とりあえずはAXI4-Stream を使ったHDLで普通に作ってみようか?
AXI4-Stream入力があって、ラプラシアン・フィルタを通してからAXI4-Streamの出力しよう。AXI VDMAを使ってAXI4-Streamラプラシアン・フィルタIPにAXI4-Stream で入力して、出力をもう一度ラプラシアン・フィルタ用のフレーム・バッファに書き込めば良い。

Vivado HLSについてはチュートリアルをやってみようかな?と思っている。

最後に、Vivado HLSでコンパイルしたCソースを下に示す。(ただし、エラーでコンパイルできません)

// laplacian_filter.c
// RGBをYに変換後にラプラシアンフィルタを掛ける。
// ピクセルのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 2013/09/16

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <ctype.h>
#include <sys/stat.h>
#include <unistd.h>

#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

#define BUFSIZE    1024

#define MEASURE_COUNT    5000

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 chkhex(char *str);

int image_lap_fil(unsigned int fb_addr, unsigned int bitmap_dc_reg_addr)
{
     int xy[3][3];
    char buf[BUFSIZE], *token;
    unsigned int read_num;
    unsigned int next_frame_addr;
    unsigned int val;
    int lap_fil_val;
    int x, y;
    unsigned int r_addr, w_addr;
    unsigned int r_buf, w_buf, bitmap_buf;
    unsigned int line_buf[3][HORIZONTAL_PIXEL_WIDTH];
    int a, b;
    int fl, sl, tl;

    // ラプラシアンフィルタの結果を入れておくフレーム・バッファ
    next_frame_addr = ((fb_addr + (ALL_PIXEL_VALUE*4)) & (~(int)(PAGE_SIZE-1))) + PAGE_SIZE;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        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 (y == 1 && x == 1){ // 最初のラインの最初のピクセルでは2ライン分の画素を読み出す
                    for (a=0; a<2; a++){ // 2ライン分
                        for (b=0; b<HORIZONTAL_PIXEL_WIDTH; b++){ // ライン
                            r_addr = fb_addr+((y+(a-1))*HORIZONTAL_PIXEL_WIDTH+b)*4;
                            line_buf[a][b] = *(volatile unsigned int *)r_addr;
                            line_buf[a][b] = conv_rgb2y(line_buf[a][b]);
                        }
                    }
                }
                if (x == 1) {    // ラインの最初なので、2つのピクセルを読み込む
                    for (b=0; b<2; b++){ // ライン
                        r_addr = fb_addr+((y+1)*HORIZONTAL_PIXEL_WIDTH+b)*4;
                        line_buf[(y+1)%3][b] = *(volatile unsigned int *)r_addr;
                        // (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つのピクセルを読み込みながらラプラシアン・フィルタを実行する
                r_addr = fb_addr+((y+1)*HORIZONTAL_PIXEL_WIDTH+(x+1))*4// ラプラシアン・フィルタに必要な最後のピクセルを読み込む
                line_buf[(y+1)%3][x+1] = *(volatile unsigned int *)r_addr;
                // (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]);
            }
            w_addr = next_frame_addr+(y*HORIZONTAL_PIXEL_WIDTH + x)*4;
             *(volatile unsigned int *)w_addr = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
            // printf("x = %d  y = %d", x, y);
        }
        a++;
    }

    // ラプラシアンフィルタの掛かった画像のスタートアドレスを bitmap-disp-cntrler-axi-master にセット
     *(volatile unsigned int *)bitmap_dc_reg_addr = next_frame_addr;

    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)
{
     return(abs(-x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2));
}

// 文字列が16進数かを調べる
int chkhex(char *str){
    while (*str != '\0'){
        if (!isxdigit(*str))
            return 0;
        str++;
    }
    return 1;
}

  1. 2013年09月29日 08:55 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

ソフトウエアのプロファイリング4(ハードウェアと同じ方式)

ソフトウエアのプロファイリング3(最適化)”で、ハードウェアの実装と近い形にしてきたが、更にハードウェアの実装に近づけることにする。

今度は、フレームの最初に2ラインを読んでから、2つピクセルを保存した後で、1つピクセルを読みながら、真ん中のラプラシアン・フィルタの値を計算していく。これで、”画像のエッジ検出6(3X3での方式の検討)”で実装したハードウェアと同じ方式になった。
実行したところ、”ソフトウエアのプロファイリング3(最適化)”よりも、更に 5msec ほど速くなった。(2013/10/08 注:gettimeofday()の使い方を間違っていたためマイナスの値が出たようだ)

zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 434225 usec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 433856 usec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 433856 usec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = -565756 usec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 433745 usec


更にハードウェア化の障害になるのは conv_rgb2y() 関数で使用している float だ。これを int に変換する。係数が小数なので、256倍して整数に変換し、結果を256で割ることにした。これで、ラプラシアン・フィルタを実行したところ、更に、45msec ほど速くなった。(2013/10/08 注:gettimeofday()の使い方を間違っていたためマイナスの値が出たようだ。測定値を貼り直す)

zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 0.391241 sec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 0.391315 sec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 0.392016 sec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 0.391776 sec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 0.391168 sec


Cソースコードを下に貼っておく。(2013/10/08 注:gettimeofday()の使い方を間違っていたためマイナスの値が出たようだ。Cソースを貼り直す)時間・時刻処理について(4)”を参照させて頂いた。

// laplacian_filter.c
// RGBをYに変換後にラプラシアンフィルタを掛ける。
// ピクセルのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 2013/09/16

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <ctype.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/kernel.h>

#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

#define BUFSIZE    1024

#define MEASURE_COUNT    5000

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 chkhex(char *str);
volatile unsigned *setup_io(off_t mapped_addr, unsigned int *buf_addr);
void Xil_DCacheInvalidateLine(unsigned int adr);
void Xil_DCacheFlushLine(unsigned int adr);

int main()
{
    FILE *fd;
    int xy[3][3];
    char buf[BUFSIZE], *token;
    unsigned int read_num;
    unsigned int bitmap_dc_reg_addr;
    volatile unsigned *bm_disp_cnt_reg;
    unsigned int fb_addr, next_frame_addr;
    unsigned int val;
    int lap_fil_val;
    int x, y;
    int *r_pixel, *w_pixel;
    unsigned int r_addr, w_addr;
    unsigned int r_addr_page, w_addr_page;
    unsigned int r_addr_page_pre=0, w_addr_page_pre=0;
    unsigned int r_addr_offset, w_addr_offset;
    unsigned int r_buf, w_buf, bitmap_buf;
    struct timeval start_time, temp1, temp2, end_time;
    unsigned int rmmap_cnt=0, wmmap_cnt=0;
    unsigned int line_buf[3][HORIZONTAL_PIXEL_WIDTH];
    int a, b;
    int fl, sl, tl;

    gettimeofday(&start_time, NULL);    // プログラム起動時の時刻を記録
    // fb_start_addr.txt の内容をパイプに入れる
    memset(buf, '\0', sizeof(buf)); // buf すべてに\0 を入れる
    // fb_start_addr.txt を開く
    fd = popen("cat /Apps/fb_start_addr.txt", "r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            sscanf(buf, "%x\n", &fb_addr);
        }
    }
    pclose(fd);

    // ラプラシアンフィルタの結果を入れておくフレーム・バッファ
    next_frame_addr = ((fb_addr + (ALL_PIXEL_VALUE*4)) & (~(int)(PAGE_SIZE-1))) + PAGE_SIZE;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        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 (y == 1 && x == 1){ // 最初のラインの最初のピクセルでは2ライン分の画素を読み出す
                    for (a=0; a<2; a++){ // 2ライン分
                        for (b=0; b<HORIZONTAL_PIXEL_WIDTH; b++){ // ライン
                            r_addr = fb_addr+((y+(a-1))*HORIZONTAL_PIXEL_WIDTH+b)*4;
                            r_addr_page = r_addr & (~(int)(PAGE_SIZE-1));
                            r_addr_offset = r_addr & ((int)(PAGE_SIZE-1));
                            if (r_addr_page != r_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                               if (r_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                                    munmap(r_pixel, BLOCK_SIZE);
                                    free((unsigned int *)r_buf);
                                }
                                r_pixel = setup_io((off_t)r_addr_page, &r_buf);
                                
                                rmmap_cnt++;
                                r_addr_page_pre = r_addr_page;
                            }
                            line_buf[a][b] = *(volatile int *)((unsigned int)r_pixel + r_addr_offset);
                            line_buf[a][b] = conv_rgb2y(line_buf[a][b]);
                        }
                    }
                }
                if (x == 1) {    // ラインの最初なので、2つのピクセルを読み込む
                    for (b=0; b<2; b++){ // ライン
                        r_addr = fb_addr+((y+1)*HORIZONTAL_PIXEL_WIDTH+b)*4;
                        r_addr_page = r_addr & (~(int)(PAGE_SIZE-1));
                        r_addr_offset = r_addr & ((int)(PAGE_SIZE-1));
                        if (r_addr_page != r_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                           if (r_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                                munmap(r_pixel, BLOCK_SIZE);
                                free((unsigned int *)r_buf);
                            }
                            r_pixel = setup_io((off_t)r_addr_page, &r_buf);
                                
                            rmmap_cnt++;
                            r_addr_page_pre = r_addr_page;
                        }
                        line_buf[(y+1)%3][b] = *(volatile int *)((unsigned int)r_pixel + r_addr_offset);
                        // (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つのピクセルを読み込みながらラプラシアン・フィルタを実行する
                r_addr = fb_addr+((y+1)*HORIZONTAL_PIXEL_WIDTH+(x+1))*4; // ラプラシアン・フィルタに必要な最後のピクセルを読み込む
                r_addr_page = r_addr & (~(int)(PAGE_SIZE-1));
                r_addr_offset = r_addr & ((int)(PAGE_SIZE-1));
                if (r_addr_page != r_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                   if (r_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                        munmap(r_pixel, BLOCK_SIZE);
                        free((unsigned int *)r_buf);
                    }
                    r_pixel = setup_io((off_t)r_addr_page, &r_buf);
                        
                    rmmap_cnt++;
                    r_addr_page_pre = r_addr_page;
                }
                line_buf[(y+1)%3][x+1] = *(volatile int *)((unsigned int)r_pixel + r_addr_offset);
                // (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]);
            }
            w_addr = next_frame_addr+(y*HORIZONTAL_PIXEL_WIDTH + x)*4;
            w_addr_page = w_addr & (~(int)(PAGE_SIZE-1));
            w_addr_offset = w_addr & ((int)(PAGE_SIZE-1));
            if (w_addr_page != w_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                if (w_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                    munmap(w_pixel, BLOCK_SIZE);
                    free((unsigned int *)w_buf);
                }
                w_pixel = setup_io((off_t)w_addr_page, &w_buf);
                wmmap_cnt++;
                w_addr_page_pre = w_addr_page;
            }
            *(volatile int *)((unsigned int)w_pixel + w_addr_offset) = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
            // printf("x = %d  y = %d", x, y);
        }
        a++;
    }
    munmap((unsigned int *)r_addr_page, BLOCK_SIZE);
    free((unsigned int *)r_buf);
    munmap((unsigned int *)w_addr_page, BLOCK_SIZE);
    free((unsigned int *)w_buf);

    // bitmap-disp-cntrler-axi-master のアドレスを取得
    memset(buf, '\0', sizeof(buf)); // buf すべてに\0 を入れる
    // ls /sys/devices/axi.0 の内容をパイプに入れる
    fd = popen("ls /sys/devices/axi.0", "r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            token = buf;
            if ((token=strtok(token, ".\n")) != NULL){
                do {
                    if (chkhex(token)){ // 16進数
                        sscanf(token, "%x", &val);
                    } else {
                        if (strcmp(token, "bitmap-disp-cntrler-axi-master") == 0)
                            bitmap_dc_reg_addr = val;
                    }
                }while((token=strtok(NULL, ".\n")) != NULL);
            }
        }
    }
    pclose(fd);

    // ラプラシアンフィルタの掛かった画像のスタートアドレスを bitmap-disp-cntrler-axi-master にセット
    bm_disp_cnt_reg = setup_io((off_t)bitmap_dc_reg_addr, &bitmap_buf);
    *bm_disp_cnt_reg = next_frame_addr;

    munmap((unsigned int *)bm_disp_cnt_reg, BLOCK_SIZE);
    free((unsigned int *)bitmap_buf);

    gettimeofday(&end_time, NULL);
    printf("rmmap_cnt = %d\n", rmmap_cnt);
    printf("wmmap_cnt = %d\n", wmmap_cnt);
    if (end_time.tv_usec < start_time.tv_usec) {
        printf("total time = %d.%d sec\n", end_time.tv_sec - start_time.tv_sec - 1, 1000000 + end_time.tv_usec - start_time.tv_usec);
    }
    else {
        printf("total time = %d.%d sec\n", end_time.tv_sec - start_time.tv_sec, end_time.tv_usec - start_time.tv_usec);
    }
    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)
{
     return(abs(-x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2));
}

//
// Set up a memory regions to access GPIO
//
volatile unsigned *setup_io(off_t mapped_addr, unsigned int *buf_addr)
// void setup_io()
{
    int  mem_fd;
    char *gpio_mem, *gpio_map;

   /* open /dev/mem */
   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("can't open /dev/mem \n");
      printf("mapped_addr = %x\n", mapped_addr);
      exit (-1);
   }

   /* mmap GPIO */

   // Allocate MAP block
   if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
      printf("allocation error \n");
      exit (-1);
   }
    *buf_addr = gpio_mem;    // mallocしたアドレスをコピー

   // Make sure pointer is on 4K boundary
   if ((unsigned long)gpio_mem % PAGE_SIZE)
     gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);

   // Now map it
   gpio_map = (unsigned char *)mmap(
      (caddr_t)gpio_mem,
      BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED|MAP_FIXED,
      mem_fd,
      mapped_addr
   );

   if ((long)gpio_map < 0) {
      printf("mmap error %d\n", (int)gpio_map);
      printf("mapped_addr = %x\n", mapped_addr);
      exit (-1);
   }

   close(mem_fd); // /dev/mem のクローズ

   // Always use volatile pointer!
   // gpio = (volatile unsigned *)gpio_map;
   return((volatile unsigned *)gpio_map);

} // setup_io

// 文字列が16進数かを調べる
int chkhex(char *str){
    while (*str != '\0'){
        if (!isxdigit(*str))
            return 0;
        str++;
    }
    return 1;
}

  1. 2013年09月27日 05:25 |
  2. Linux
  3. | トラックバック:0
  4. | コメント:0

ソフトウエアのプロファイリング3(最適化)

ソフトウエアのプロファイリング2(mmap(), munmap() の時間計測2)”の続き。

ラプラシアン・フィルタの実行時間を139秒から37秒に最適化してきたが(順番が逆になったけど、そこは勘弁して頂いて。。。)、今回は更に最適化を図る。mmap(), munmap() が遅いので、極力少なくすることを考える。
前回の計算式から mmap(), munmap() を除いた実行時間は約2秒だった。そこを目指すことになるが、それよりも早くなることもあるかも知れない。

今回の最適化は、ハードウェアの実装により近くなる。ハードウェアの実装は、”画像のエッジ検出6(3X3での方式の検討)”を参照して欲しい。ここでは、2ライン分のピクセルデータをライン・バッファに蓄えて3ライン分のピクセルデータが3つたまったところで真ん中のピクセルのラプラシアン・フィルタの出力値を計算している。ハードウェアはクロックごとにピクセルデータが来て、それを1フレーム分溜めておくのが難しい。更に溜めてもReadのレイテンシが発生してしまう。そこで、2ライン分はFPGA内部の速度の早いBlockRAMに溜めておいて、それを読み出しながら、現在カメラから来るピクセルデータをDFFで遅延させながらラプラシアン・フィルタを掛けるわけだ。クロックごとに処理するのが前提となっている。
ソフトウェアはと言うと、クロックごとに処理を完了するという、厳しい条件は無いが、なるべく早く処理したいという希望があるだろう。現在のカメラのフレーム・レートは15fpsなので、66.7msec ごとにラプラシアン・フィルタを掛けることができれば、実時間処理ができるということになる。

今回のラプラシアン・フィルタの実装は、3ライン分読んでからラプラシアン・フィルタを掛けるようにした。これで、ピクセルデータRead時のアドレスは連続するので、Readのmmap(), munmap() の数とWriteのmmap(), munmap() の数は等しくなるはずだ。最初は3ライン分のピクセルをReadするが、次からは後にReadした2ラインは使用できるので、1ラインだけのReadになる。

結果は値がマイナスになることがあるのだが、+の値を見ると約0.4秒となった。これは、”ソフトウエアのプロファイリング2(mmap(), munmap() の時間計測2)”で計算した約2秒よりも小さい。これは何故かと考えると、1ラインを連続的にReadしているので、キャシュラインをフィルするIO命令が連続的に発行されて、DDR3コントローラで連続的にバーストで読んでこられているのではないだろうか?読んだ値は少なくとも2ndキャッシュには入ると思うので、複数回読まれる値はキャッシュから読めるのではないだろうか?そのため、ラプラシアン・フィルタの実行時間本体が最適化されて速くなったのではないだろうか?これらはあくまでも推測で、想像の域を超えていないことをお断りしておく。PS内部をChipScope で見えればよいのだが?

下に、データを示す。-になってしまうのはなぜだろうか?

zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 439478 usec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 439385 usec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = -560927 usec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = -560688 usec
zynq> ./laplacian_filter.elf
rmmap_cnt = 469
wmmap_cnt = 469
total time = 438869 usec


Read用の mmap(), munmap() とWrite用の mmap(), munmap() の回数は同じ469回だった。 mmap(), munmap() の回数は、469回 x 2 x 28usec ≒ 26.3msec となり、全体の割合はそれほど大きく無くなった。

ラプラシアン・フィルタの実行時間は、139秒が39秒になって、更に0.4秒に最適化出来た。
下に現在のCソースコードを示す。

// laplacian_filter.c
// RGBをYに変換後にラプラシアンフィルタを掛ける。
// ピクセルのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 2013/09/16

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <ctype.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/kernel.h>

#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

#define BUFSIZE    1024

#define MEASURE_COUNT    5000

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 chkhex(char *str);
volatile unsigned *setup_io(off_t mapped_addr, unsigned int *buf_addr);
void Xil_DCacheInvalidateLine(unsigned int adr);
void Xil_DCacheFlushLine(unsigned int adr);

int main()
{
    FILE *fd;
    int xy[3][3];
    char buf[BUFSIZE], *token;
    unsigned int read_num;
    unsigned int bitmap_dc_reg_addr;
    volatile unsigned *bm_disp_cnt_reg;
    unsigned int fb_addr, next_frame_addr;
    unsigned int val;
    int lap_fil_val;
    int x, y;
    int *r_pixel, *w_pixel;
    unsigned int r_addr, w_addr;
    unsigned int r_addr_page, w_addr_page;
    unsigned int r_addr_page_pre=0, w_addr_page_pre=0;
    unsigned int r_addr_offset, w_addr_offset;
    unsigned int r_buf, w_buf, bitmap_buf;
    struct timeval start_time, temp1, temp2, end_time;
    unsigned int rmmap_cnt=0, wmmap_cnt=0;
    unsigned int line_buf[3][HORIZONTAL_PIXEL_WIDTH];
    int a, b;
    int fl, sl, tl;

    gettimeofday(&start_time, NULL);    // プログラム起動時の時刻を記録
    // fb_start_addr.txt の内容をパイプに入れる
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // fb_start_addr.txt を開く
    fd = popen("cat /Apps/fb_start_addr.txt""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            sscanf(buf, "%x\n", &fb_addr);
        }
    }
    pclose(fd);

    // ラプラシアンフィルタの結果を入れておくフレーム・バッファ
    next_frame_addr = ((fb_addr + (ALL_PIXEL_VALUE*4)) & (~(int)(PAGE_SIZE-1))) + PAGE_SIZE;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        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ライン分の画素を読み出す
                        for (a=0; a<3; a++){ // 3ライン分
                            for (b=0; b<HORIZONTAL_PIXEL_WIDTH; b++){ // ライン
                                r_addr = fb_addr+((y+(a-1))*HORIZONTAL_PIXEL_WIDTH+b)*4;
                                r_addr_page = r_addr & (~(int)(PAGE_SIZE-1));
                                r_addr_offset = r_addr & ((int)(PAGE_SIZE-1));
                                if (r_addr_page != r_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                                   if (r_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                                        munmap(r_pixel, BLOCK_SIZE);
                                        free((unsigned int *)r_buf);
                                    }
                                    r_pixel = setup_io((off_t)r_addr_page, &r_buf);
                                    
                                    rmmap_cnt++;
                                    r_addr_page_pre = r_addr_page;
                                }
                                line_buf[a][b] = *(volatile int *)((unsigned int)r_pixel + r_addr_offset);
                                line_buf[a][b] = conv_rgb2y(line_buf[a][b]);
                            }
                        }
                    } else { // 最初のラインではないので、1ラインだけ読み込む。すでに他の2ラインは読み込まれている
                           for (b=0; b<HORIZONTAL_PIXEL_WIDTH; b++){ // ライン
                            r_addr = fb_addr+((y+1)*HORIZONTAL_PIXEL_WIDTH+b)*4;
                            r_addr_page = r_addr & (~(int)(PAGE_SIZE-1));
                            r_addr_offset = r_addr & ((int)(PAGE_SIZE-1));
                            if (r_addr_page != r_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                               if (r_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                                    munmap(r_pixel, BLOCK_SIZE);
                                    free((unsigned int *)r_buf);
                                }
                                r_pixel = setup_io((off_t)r_addr_page, &r_buf);
                                    
                                rmmap_cnt++;
                                r_addr_page_pre = r_addr_page;
                            }
                            line_buf[(y+1)%3][b] = *(volatile int *)((unsigned int)r_pixel + r_addr_offset);
                            // (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]);
                        }
                    }
                }
                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]);
            }
            w_addr = next_frame_addr+(y*HORIZONTAL_PIXEL_WIDTH + x)*4;
            w_addr_page = w_addr & (~(int)(PAGE_SIZE-1));
            w_addr_offset = w_addr & ((int)(PAGE_SIZE-1));
            if (w_addr_page != w_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                if (w_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                    munmap(w_pixel, BLOCK_SIZE);
                    free((unsigned int *)w_buf);
                }
                w_pixel = setup_io((off_t)w_addr_page, &w_buf);
                wmmap_cnt++;
                w_addr_page_pre = w_addr_page;
            }
            *(volatile int *)((unsigned int)w_pixel + w_addr_offset) = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
            // printf("x = %d  y = %d", x, y);
        }
    }
    munmap((unsigned int *)r_addr_page, BLOCK_SIZE);
    free((unsigned int *)r_buf);
    munmap((unsigned int *)w_addr_page, BLOCK_SIZE);
    free((unsigned int *)w_buf);

    // bitmap-disp-cntrler-axi-master のアドレスを取得
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // ls /sys/devices/axi.0 の内容をパイプに入れる
    fd = popen("ls /sys/devices/axi.0""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            token = buf;
            if ((token=strtok(token, ".\n")) != NULL){
                do {
                    if (chkhex(token)){ // 16進数
                        sscanf(token, "%x", &val);
                    } else {
                        if (strcmp(token, "bitmap-disp-cntrler-axi-master") == 0)
                            bitmap_dc_reg_addr = val;
                    }
                }while((token=strtok(NULL, ".\n")) != NULL);
            }
        }
    }
    pclose(fd);

    // ラプラシアンフィルタの掛かった画像のスタートアドレスを bitmap-disp-cntrler-axi-master にセット
    bm_disp_cnt_reg = setup_io((off_t)bitmap_dc_reg_addr, &bitmap_buf);
    *bm_disp_cnt_reg = next_frame_addr;

    munmap((unsigned int *)bm_disp_cnt_reg, BLOCK_SIZE);
    free((unsigned int *)bitmap_buf);

    gettimeofday(&end_time, NULL);
    printf("rmmap_cnt = %d\n", rmmap_cnt);
    printf("wmmap_cnt = %d\n", wmmap_cnt);
    printf("total time = %d usec\n", end_time.tv_usec - start_time.tv_usec);
    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
//
int conv_rgb2y(int rgb){
    float r, g, b, y_f;
    int y;

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

    y_f = 0.299*r + 0.587*g + 0.114*b;
    y = (int)y_f;

    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)
{
     return(abs(-x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2));
}

//
// Set up a memory regions to access GPIO
//
volatile unsigned *setup_io(off_t mapped_addr, unsigned int *buf_addr)
// void setup_io()
{
    int  mem_fd;
    char *gpio_mem, *gpio_map;

   /* open /dev/mem */
   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("can't open /dev/mem \n");
      printf("mapped_addr = %x\n", mapped_addr);
      exit (-1);
   }

   /* mmap GPIO */

   // Allocate MAP block
   if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
      printf("allocation error \n");
      exit (-1);
   }
    *buf_addr = gpio_mem;    // mallocしたアドレスをコピー

   // Make sure pointer is on 4K boundary
   if ((unsigned long)gpio_mem % PAGE_SIZE)
     gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);

   // Now map it
   gpio_map = (unsigned char *)mmap(
      (caddr_t)gpio_mem,
      BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED|MAP_FIXED,
      mem_fd,
      mapped_addr
   );

   if ((long)gpio_map < 0) {
      printf("mmap error %d\n", (int)gpio_map);
      printf("mapped_addr = %x\n", mapped_addr);
      exit (-1);
   }

   close(mem_fd); // /dev/mem のクローズ

   // Always use volatile pointer!
   // gpio = (volatile unsigned *)gpio_map;
   return((volatile unsigned *)gpio_map);

// setup_io

// 文字列が16進数かを調べる
int chkhex(char *str){
    while (*str != '\0'){
        if (!isxdigit(*str))
            return 0;
        str++;
    }
    return 1;
}

  1. 2013年09月26日 05:36 |
  2. Linux
  3. | トラックバック:0
  4. | コメント:3

ソフトウエアのプロファイリング2(mmap(), munmap() の時間計測2)

前回前々回その1つ前の回とラプラシアン・フィルタのCソフトウェアの動作が不安定が事が多かった。そこで、フレーム・バッファのスタートアドレスがページ境界に合わせていないのが原因かもしれないと思い、Digilent Linux の起動時に、カメラ・コントローラやビットマップ・ディスプレイ・コントローラにフレーム・バッファの開始アドレスを指定する cam_disp3_linux.c プログラムを修正して、フレーム・バッファを4Kバイト境界から始まるように修正した。(修正した cam_disp3_linux.c のCソースコードについては、”ZedBoard Linux上でカメラの画像を処理する2(準備編2)”参照)

ZedBoard Linux上でカメラの画像を処理する6(ラプラシアンフィルタ4)”と”ZedBoard Linux上でカメラの画像を処理する7(ラプラシアンフィルタ5)”のCソースコードもフレーム・バッファの開始アドレスが4kバイトのページ境界になるように修正を行った。

さて本題に入る。”ソフトウエアのプロファイリング1(mmap(), munmap() の時間計測)”で、”ZedBoard Linux上でカメラの画像を処理する7(ラプラシアンフィルタ5)”のCソースコード使用時の mmap() の呼び出し回数を計算したが、それをCソースコードにカウンタを用意して検算してみた。
image_process_Zed_linux_15_130925.png

rmmap_cnt、wmmap_cntを用意して、mmap() を行った時に+1してみた。それをプログラムの最後で次のように表示してみた。


printf("rmmap_cnt = %d\n", rmmap_cnt);
printf("wmmap_cnt = %d\n", wmmap_cnt);


結果を下に示す。

rmmap_cnt = 4294836
wmmap_cnt = 480000
total time = 138 sec


やはり、ピクセルのReadの方が10倍近く多い。4294836 + 480000 = 4774836(回)で前回の計算とぴったり一致した。

次に、”ZedBoard Linux上でカメラの画像を処理する6(ラプラシアンフィルタ4)”の方は、ReadとWriteの2つの mmap() 領域を用意して、それぞれ現在取得している領域が外れた時に munmap() して、もう一度領域を取り直す様になっている。こっちのほうが mmap() の実行回数が、前回プロファイリングしたCソースコードよりも少ないはずだ。それを計測してみた。更に、munmap() とそれに続く free() にの最初の1回の実行時間も計測した。下に結果を示す。

rmmap_cnt = 1223626
wmmap_cnt = 469
munmap() time = 36 usec
total time = 37 sec


Readによるmmap() の実行回数は、1223626 / 4294836 = 0.285倍になった。1/4 に近い数字だ。
Writeによるmmap() の実行回数は、469 / 480000 = 0.000977倍になった。1/1000 に近い数字だ。
Readはどうしても3ライン分を参照するので、ページ境界を超えてしまうので、どうしても mmap() する数が多くなるが、Writeはページ境界まで、無駄なくWriteできるので、劇的に mmap() する数が減っている。
munmap() とそれに続く free() に実行時間は、38usec という結果だった。これは少し大きすぎるように感じた。そこで、下図の様に、gettimeofday() の実行時間を計測してみたが、0usec だった。
image_process_Zed_linux_16_130925.png

ここで2つの違ったCソースコードで計測できたので、mmap(), munmap() に掛かる時間を計算してみよう。
最初のCソースコードでは、4774836(回)のmmap(), munmap() を行い、全体の実行時間は 138秒だった。次のCソースコードでは、1224095(回)のmmap(), munmap() を行い、全体の実行時間は 37秒だった。これを使用して式を立てる。mmap(), munmap() 関連の実行時間を x として、それ以外の実行時間を y とすると、次の式が導ける。

4774836x + y = 138 ----- 式1
1224095x + y = 37 -----式2


式1と式2を解くと、x = 28.4 usec, y = 2.18 sec が導けた。
mmap(), munmap() 関連の実行時間が実測値と差がある。mmap(), munmap() 関連の実行時間以外の本来のラプラシアン・フィルタの実行時間は2秒程度だという結論に達した。

mmap(), munmap() 関連の実際の実行時間を測定してみた。10回測定すると平均が 62722 usec になってしまった。1回あたりの時間を取ってみた。

flag = 1, map_time = 626895
flag = 2, map_time = 55
flag = 3, map_time = 37
flag = 4, map_time = 37
flag = 5, map_time = 37
flag = 6, map_time = 37
flag = 7, map_time = 18
flag = 8, map_time = 37
flag = 9, map_time = 37
flag = 10, map_time = 37


最初が極端に長い。どうしてだろう?そのいうものなのか? 630msec 程度もかかっている。(追記)下のプログラムだと、最初は、 gettimeofday(&temp1, NULL); が実行されずに、 gettimeofday(&temp2, NULL);を実行しちゃうからですね。最初を除いて正解でした。

最初を除いて、5000回の平均を取ってみた。

map time = 29 usec


大体、計算値と合った。
なお、総実行時間は 37秒で変化がなかった。gettimeofday() を入れたコストは少なかったようだ。(total time = 37 sec)

下に計測時のCソースコードを示す。

// laplacian_filter.c
// RGBをYに変換後にラプラシアンフィルタを掛ける。
// ピクセルのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 2013/09/16

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <ctype.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/kernel.h>

#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

#define BUFSIZE    1024

#define MEASURE_COUNT    5000

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 chkhex(char *str);
volatile unsigned *setup_io(off_t mapped_addr, unsigned int *buf_addr);
void Xil_DCacheInvalidateLine(unsigned int adr);
void Xil_DCacheFlushLine(unsigned int adr);

int main()
{
    FILE *fd;
    int xy[3][3];
    char buf[BUFSIZE], *token;
    unsigned int read_num;
    unsigned int bitmap_dc_reg_addr;
    volatile unsigned *bm_disp_cnt_reg;
    unsigned int fb_addr, next_frame_addr;
    int x, y;
    unsigned int val;
    int i, j;
    int lap_fil_val;
    int *r_pixel, *w_pixel;
    unsigned int r_addr, w_addr;
    unsigned int r_addr_page, w_addr_page;
    unsigned int r_addr_page_pre=0, w_addr_page_pre=0;
    unsigned int r_addr_offset, w_addr_offset;
    unsigned int r_buf, w_buf, bitmap_buf;
    struct timeval start_time, temp1, temp2, end_time;
    unsigned int rmmap_cnt=0, wmmap_cnt=0;
    int flag=0;
    long map_time=0L;

    gettimeofday(&start_time, NULL);    // プログラム起動時の時刻を記録
    // fb_start_addr.txt の内容をパイプに入れる
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // fb_start_addr.txt を開く
    fd = popen("cat /Apps/fb_start_addr.txt""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            sscanf(buf, "%x\n", &fb_addr);
        }
    }
    pclose(fd);

    // ラプラシアンフィルタの結果を入れておくフレーム・バッファ
    next_frame_addr = ((fb_addr + (ALL_PIXEL_VALUE*4)) & (~(int)(PAGE_SIZE-1))) + PAGE_SIZE;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        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{
                for (j=-1; j<2; j++){
                    for (i=-1; i<2; i++){
                        r_addr = fb_addr+((y+j)*HORIZONTAL_PIXEL_WIDTH+(x+i))*4;
                        r_addr_page = r_addr & (~(int)(PAGE_SIZE-1));
                        r_addr_offset = r_addr & ((int)(PAGE_SIZE-1));
                        if (r_addr_page != r_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                           if (flag>1 && flag <= MEASURE_COUNT+1)
                                gettimeofday(&temp1, NULL);

                           if (r_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                                munmap(r_pixel, BLOCK_SIZE);
                                free((unsigned int *)r_buf);
                            }else{
                                flag++;
                            }

                            r_pixel = setup_io((off_t)r_addr_page, &r_buf);

                            if (flag>1 && flag <= MEASURE_COUNT+1){
                                gettimeofday(&temp2, NULL);
                                map_time = map_time + (temp2.tv_usec - temp1.tv_usec);
                                //printf("flag = %d, map_time = %d\n", flag, (temp2.tv_usec - temp1.tv_usec));
                                flag++;
                            }else{
                                flag++;
                            }

                            rmmap_cnt++;
                            r_addr_page_pre = r_addr_page;
                        }
                        xy[i+1][j+1] = *(volatile int *)((unsigned int)r_pixel + r_addr_offset);
                        xy[i+1][j+1] = conv_rgb2y(xy[i+1][j+1]);
                    }
                }
                lap_fil_val = laplacian_fil(xy[0][0], xy[1][0], xy[2][0], xy[0][1], xy[1][1], xy[2][1], xy[0][2], xy[1][2], xy[2][2]);
            }
            w_addr = next_frame_addr+(y*HORIZONTAL_PIXEL_WIDTH + x)*4;
            w_addr_page = w_addr & (~(int)(PAGE_SIZE-1));
            w_addr_offset = w_addr & ((int)(PAGE_SIZE-1));
            if (w_addr_page != w_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                if (w_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                    munmap(w_pixel, BLOCK_SIZE);
                    free((unsigned int *)w_buf);
                }
                w_pixel = setup_io((off_t)w_addr_page, &w_buf);
                wmmap_cnt++;
                w_addr_page_pre = w_addr_page;
            }
            *(volatile int *)((unsigned int)w_pixel + w_addr_offset) = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
            // printf("x = %d  y = %d", x, y);
        }
    }
    munmap((unsigned int *)r_addr_page, BLOCK_SIZE);
    free((unsigned int *)r_buf);
    munmap((unsigned int *)w_addr_page, BLOCK_SIZE);
    free((unsigned int *)w_buf);

    // bitmap-disp-cntrler-axi-master のアドレスを取得
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // ls /sys/devices/axi.0 の内容をパイプに入れる
    fd = popen("ls /sys/devices/axi.0""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            token = buf;
            if ((token=strtok(token, ".\n")) != NULL){
                do {
                    if (chkhex(token)){ // 16進数
                        sscanf(token, "%x", &val);
                    } else {
                        if (strcmp(token, "bitmap-disp-cntrler-axi-master") == 0)
                            bitmap_dc_reg_addr = val;
                    }
                }while((token=strtok(NULL, ".\n")) != NULL);
            }
        }
    }
    pclose(fd);

    // ラプラシアンフィルタの掛かった画像のスタートアドレスを bitmap-disp-cntrler-axi-master にセット
    bm_disp_cnt_reg = setup_io((off_t)bitmap_dc_reg_addr, &bitmap_buf);
    *bm_disp_cnt_reg = next_frame_addr;

    munmap((unsigned int *)bm_disp_cnt_reg, BLOCK_SIZE);
    free((unsigned int *)bitmap_buf);

    gettimeofday(&end_time, NULL);
    printf("rmmap_cnt = %d\n", rmmap_cnt);
    printf("wmmap_cnt = %d\n", wmmap_cnt);
    printf("map time = %d usec\n", map_time/MEASURE_COUNT);
    printf("total time = %d sec\n", end_time.tv_sec - start_time.tv_sec);
    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
//
int conv_rgb2y(int rgb){
    float r, g, b, y_f;
    int y;

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

    y_f = 0.299*r + 0.587*g + 0.114*b;
    y = (int)y_f;

    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)
{
     return(abs(-x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2));
}

//
// Set up a memory regions to access GPIO
//
volatile unsigned *setup_io(off_t mapped_addr, unsigned int *buf_addr)
// void setup_io()
{
    int  mem_fd;
    char *gpio_mem, *gpio_map;

   /* open /dev/mem */
   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("can't open /dev/mem \n");
      printf("mapped_addr = %x\n", mapped_addr);
      exit (-1);
   }

   /* mmap GPIO */

   // Allocate MAP block
   if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
      printf("allocation error \n");
      exit (-1);
   }
    *buf_addr = gpio_mem;    // mallocしたアドレスをコピー

   // Make sure pointer is on 4K boundary
   if ((unsigned long)gpio_mem % PAGE_SIZE)
     gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);

   // Now map it
   gpio_map = (unsigned char *)mmap(
      (caddr_t)gpio_mem,
      BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED|MAP_FIXED,
      mem_fd,
      mapped_addr
   );

   if ((long)gpio_map < 0) {
      printf("mmap error %d\n", (int)gpio_map);
      printf("mapped_addr = %x\n", mapped_addr);
      exit (-1);
   }

   close(mem_fd); // /dev/mem のクローズ

   // Always use volatile pointer!
   // gpio = (volatile unsigned *)gpio_map;
   return((volatile unsigned *)gpio_map);

// setup_io

// 文字列が16進数かを調べる
int chkhex(char *str){
    while (*str != '\0'){
        if (!isxdigit(*str))
            return 0;
        str++;
    }
    return 1;
}

  1. 2013年09月25日 06:03 |
  2. Linux
  3. | トラックバック:0
  4. | コメント:0

ソフトウエアのプロファイリング1(mmap(), munmap() の時間計測)

ZedBoard Linux上でカメラの画像を処理する7(ラプラシアンフィルタ5)”でラプラシアン・フィルタは完成したが、ソフトウェアの動作時間は139秒だった。何処に時間がかかっているかをプロファイリングしようと思う。使用するのは、gettimeofday() だ。”システムプログラム(第4週)”を参考にプログラムしていく。

laplacian_filter.c の最初と最後に、gettimeofday()を入れて、プログラム動作時間を計測すると同時に、mmap() で領域を確保するための時間を計測する。

laplacian_filter.c の最初と最後の部分を下に示す。最初の部分。


gettimeofday(&start_time, NULL); // プログラム起動時の時刻を記録


最後の部分。


gettimeofday(&end_time, NULL);
printf("mmap() time = %d usec\n", temp2.tv_usec - temp1.tv_usec);
printf("total time = %d sec\n", end_time.tv_sec - start_time.tv_sec);
return(0);
}


次に、mmap() の時間計測部分を下に示す。


if (x==1 && y==1 && j==-1 && i==-1)
gettimeofday(&temp1, NULL);

r_pixel = setup_io((off_t)r_addr_page, &r_buf);

if (x==1 && y==1 && j==-1 && i==-1)
gettimeofday(&temp2, NULL);


これで、ラプラシアン・フィルタのプログラムを走らせたところ、下のような結果が得られた。

mmap() time = 18 usec
total time = 139 sec


mmap() time は 18usec、total time は 139sec かかっている。

ラプラシアン・フィルタは800x600 画面の縦横それぞれ-2個少ない数の演算を行っているが、それごとに9回分、カメラ画像を取得するために mmap() を行っている。更に、フレーム・バッファにWriteするために 800x600 回分、mmap() を実行している。下に mmap() を実行した回数の式を示す。
798*598*9 + 800*600 = 4774836(回)
これに、18usec を掛けると、mmap() の実行時間が計算できる。それは、85,947,048usec となり、86秒ほど実行時間がかかっていることになる。

もっと、gettimeofday() で実行時間を計測したかったが、ラプラシアン・フィルタのソフトウェアを実行するとカーネルパニックになってしまうので、とりあえずはこのへんで止めざる負えない。
  1. 2013年09月24日 03:39 |
  2. Linux
  3. | トラックバック:0
  4. | コメント:0

ZedBoard Linux上でカメラの画像を処理する7(ラプラシアンフィルタ5)

”ZedBoard Linux上でカメラの画像を処理する6(ラプラシアンフィルタ4)”でラプラシアン・フィルタが出来たと思ったのだが、次にやってみたら、Linuxカーネルが落ちてしまった。どうやら偶然に出来たようだ。そこで、mmap() で同時に領域を取らないようにしてみた。つまり、ReadとWriteで同時に mmap() して領域を確保していたが、Readで mmap() を使用して、領域を確保したら、一度、munmap() してから、Write用に mmap() で領域を確保するように書き換えた。
Readするときにいちいち mmap() してからReadして、munmap() しているので、とっても遅い。今度は、ラプラシアン・フィルタリングを完了するまでに、139秒かかっている。

ラプラシアン・フィルタを掛けるためには3x3の画像のピクセルを使用して、真ん中の1点を演算する。(”画像のエッジ検出6(3X3での方式の検討)”参照)
下の図で説明すると、オレンジ色の四角の9点を使用して、黄緑色の1点を計算するのだが、9点のメモリを読みために、それぞれ mmap() してからReadして、munmap() している。つまり、9回、 mmap() と munmap() を繰り返している。その後、Writeのために mmap() で領域を確保し、そして、munmap() で領域を開放している。そのため、遅くなっていると考えられる。
image_process_Zed_linux_14_130923.png

下にCのソースコードを示す。
(2013/09/24:追記)今日は、このコードもカーネルパニックで動作しません。昨日は完全に動作していたんですが、何が悪いんでしょうか?
(2013/09/25:Cソースコード修正)フレーム・バッファの開始アドレスをページ境界に設定したら、うまく動作しているようです。

// laplacian_filter.c
// RGBをYに変換後にラプラシアンフィルタを掛ける。
// ピクセルのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 2013/09/16

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <ctype.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/kernel.h>

#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)
#define ALLC_SIZE (PAGE_SIZE * 469)    // 800*600*4 を超えるPAGE_SIZEの倍数

#define BUFSIZE    1024

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 chkhex(char *str);
volatile unsigned int *setup_io(off_t mapped_addr, unsigned int *buf_addr);
void Xil_DCacheInvalidateLine(unsigned int adr);
void Xil_DCacheFlushLine(unsigned int adr);

int main()
{
    FILE *fd;
    int xy[3][3];
    char buf[BUFSIZE], *token;
    unsigned int read_num;
    unsigned int bitmap_dc_reg_addr;
    volatile unsigned *bm_disp_cnt_reg;
    unsigned int fb_addr, next_frame_addr;
    int x, y;
    unsigned int val;
    int i, j;
    int lap_fil_val;
    int *r_pixel, *w_pixel;
    unsigned int r_addr, w_addr;
    unsigned int r_addr_page, w_addr_page;
    unsigned int r_addr_page_pre=0, w_addr_page_pre=0;
    unsigned int r_addr_offset, w_addr_offset;
    unsigned int r_buf, w_buf, bitmap_buf;
    
    // fb_start_addr.txt の内容をパイプに入れる
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // fb_start_addr.txt を開く
    fd = popen("cat /Apps/fb_start_addr.txt""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            sscanf(buf, "%x\n", &fb_addr);
        }
    }
    pclose(fd);
    
    // ラプラシアンフィルタの結果を入れておくフレーム・バッファ
    next_frame_addr = ((fb_addr + (ALL_PIXEL_VALUE*4)) & (~(int)(PAGE_SIZE-1))) + PAGE_SIZE;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        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{
                for (j=-1; j<2; j++){
                    for (i=-1; i<2; i++){
                        r_addr = fb_addr+((y+j)*HORIZONTAL_PIXEL_WIDTH+(x+i))*4;
                        r_addr_page = r_addr & (~(int)(PAGE_SIZE-1));
                        r_addr_offset = r_addr & ((int)(PAGE_SIZE-1));
                        r_pixel = setup_io((off_t)r_addr_page, &r_buf);

                        xy[i+1][j+1] = *(volatile int *)((unsigned int)r_pixel + r_addr_offset);
                        munmap(r_pixel, BLOCK_SIZE);
                        free((char *)r_buf);
                        xy[i+1][j+1] = conv_rgb2y(xy[i+1][j+1]);
                    }
                }
                lap_fil_val = laplacian_fil(xy[0][0], xy[1][0], xy[2][0], xy[0][1], xy[1][1], xy[2][1], xy[0][2], xy[1][2], xy[2][2]);
            }
            
            w_addr = next_frame_addr+(y*HORIZONTAL_PIXEL_WIDTH + x)*4;
            w_addr_page = w_addr & (~(int)(PAGE_SIZE-1));
            w_addr_offset = w_addr & ((int)(PAGE_SIZE-1));
            w_pixel = setup_io((off_t)w_addr_page, &w_buf);
            *(volatile int *)((unsigned int)w_pixel + w_addr_offset) = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
            munmap(w_pixel, BLOCK_SIZE);
            free((char *)w_buf);
            // printf("x = %d  y = %d", x, y);
        }
    }
    
    // bitmap-disp-cntrler-axi-master のアドレスを取得
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // ls /sys/devices/axi.0 の内容をパイプに入れる
    fd = popen("ls /sys/devices/axi.0""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            token = buf;
            if ((token=strtok(token, ".\n")) != NULL){
                do {
                    if (chkhex(token)){ // 16進数
                        sscanf(token, "%x", &val);
                    } else {
                        if (strcmp(token, "bitmap-disp-cntrler-axi-master") == 0)
                            bitmap_dc_reg_addr = val;
                    }
                }while((token=strtok(NULL, ".\n")) != NULL);
            }
        }
    }
    pclose(fd);
    
    // ラプラシアンフィルタの掛かった画像のスタートアドレスを bitmap-disp-cntrler-axi-master にセット
    bm_disp_cnt_reg = setup_io((off_t)bitmap_dc_reg_addr, &bitmap_buf);
    *bm_disp_cnt_reg = next_frame_addr;

    munmap((unsigned int *)bm_disp_cnt_reg, BLOCK_SIZE);
    free((char *)bitmap_buf);

    return(0);
}
                    
            
// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
//
int conv_rgb2y(int rgb){
    float r, g, b, y_f;
    int y;
    
    b = (float)(rgb & 0xff);
    g = (float)((rgb>>8) & 0xff);
    r = (float)((rgb>>16) & 0xff);
    
    y_f = 0.299*r + 0.587*g + 0.114*b;
    y = (int)y_f;
    
    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)
{
     return(abs(-x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2));
}

//
// Set up a memory regions to access GPIO
//
volatile unsigned int *setup_io(off_t mapped_addr, unsigned int *buf_addr)
// void setup_io()
{
    int  mem_fd;
    char *gpio_mem, *gpio_map;

   /* open /dev/mem */
   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("can't open /dev/mem \n");
      printf("mapped_addr = %x\n", (unsigned int)mapped_addr);
      exit (-1);
   }

   /* mmap GPIO */

   // Allocate MAP block
   if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
      printf("allocation error \n");
      exit (-1);
   }
    *buf_addr = (unsigned int)gpio_mem;    // mallocしたアドレスをコピー
    
   // Make sure pointer is on 4K boundary
   if ((unsigned long)gpio_mem % PAGE_SIZE)
     gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);

   // Now map it
   gpio_map = (unsigned char *)mmap(
      (caddr_t)gpio_mem,
      BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED|MAP_FIXED,
      mem_fd,
      mapped_addr
   );

   if ((long)gpio_map < 0) {
      printf("mmap error %d\n", (int)gpio_map);
      printf("mapped_addr = %x\n", (unsigned int)mapped_addr);
      exit (-1);
   }

   close(mem_fd); // /dev/mem のクローズ

   // Always use volatile pointer!
   // gpio = (volatile unsigned *)gpio_map;
   return((volatile unsigned *)gpio_map);

// setup_io

// 文字列が16進数かを調べる
int chkhex(char *str){
    while (*str != '\0'){
        if (!isxdigit(*str))
            return 0;
        str++;
    }
    return 1;
}

  1. 2013年09月23日 18:09 |
  2. ZedBoard
  3. | トラックバック:0
  4. | コメント:0

ZedBoard Linux上でカメラの画像を処理する6(ラプラシアンフィルタ4)

Digilent Linux 上でのラプラシアン・フィルタのCソフトウエア完成しました。やった~~~。嬉しいですね。。。
原因は、setup_io() で、malloc() で取ったメモリ領域を free() していないことでした。setup_io() は引用してきたんですが、かなり処理に抜けがありました。open() した"/dev/mem"も close() してなかったですね。
とにかくうまくいきました。ラプラシアン・フィルタの画像です。
image_process_Zed_linux_12_130922.jpg

これが元画像です。
image_process_Zed_linux_13_130922.jpg

ラプラシアン・フィルタの処理に36秒掛かっています。何も最適化処理はしていないですが、それでも遅すぎですね。これを少し最適化したいと思います。
そう言えば、キャッシュはインバリデートする必要があるかな?と思っていたんですが、何もしていなくても大丈夫ですね。
下に現在のラプラシアン・フィルタのCプログラムを貼っておきます。
(2013/09/23:追記)このCプログラムは1度は動作したのですが、その後、カーネルパニックになって2度と全部動作しませんでした。
(2013/09/25:Cソースコード修正)フレーム・バッファの開始アドレスをページ境界に設定したら、うまく動作しているようです。
実装の異なるCソースコードを”ZedBoard Linux上でカメラの画像を処理する7(ラプラシアンフィルタ5)”に貼ってあります。

// laplacian_filter.c
// RGBをYに変換後にラプラシアンフィルタを掛ける。
// ピクセルのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 2013/09/16

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <ctype.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/kernel.h>

#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

#define BUFSIZE    1024

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 chkhex(char *str);
volatile unsigned *setup_io(off_t mapped_addr, unsigned int *buf_addr);
void Xil_DCacheInvalidateLine(unsigned int adr);
void Xil_DCacheFlushLine(unsigned int adr);

int main()
{
    FILE *fd;
    int xy[3][3];
    char buf[BUFSIZE], *token;
    unsigned int read_num;
    unsigned int bitmap_dc_reg_addr;
    volatile unsigned *bm_disp_cnt_reg;
    unsigned int fb_addr, next_frame_addr;
    int x, y;
    unsigned int val;
    int i, j;
    int lap_fil_val;
    int *r_pixel, *w_pixel;
    unsigned int r_addr, w_addr;
    unsigned int r_addr_page, w_addr_page;
    unsigned int r_addr_page_pre=0, w_addr_page_pre=0;
    unsigned int r_addr_offset, w_addr_offset;
    unsigned int r_buf, w_buf, bitmap_buf;

    // fb_start_addr.txt の内容をパイプに入れる
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // fb_start_addr.txt を開く
    fd = popen("cat /Apps/fb_start_addr.txt""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            sscanf(buf, "%x\n", &fb_addr);
        }
    }
    pclose(fd);

    // ラプラシアンフィルタの結果を入れておくフレーム・バッファ
    next_frame_addr = ((fb_addr + (ALL_PIXEL_VALUE*4)) & (~(int)(PAGE_SIZE-1))) + PAGE_SIZE;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        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{
                for (j=-1; j<2; j++){
                    for (i=-1; i<2; i++){
                        r_addr = fb_addr+((y+j)*HORIZONTAL_PIXEL_WIDTH+(x+i))*4;
                        r_addr_page = r_addr & (~(int)(PAGE_SIZE-1));
                        r_addr_offset = r_addr & ((int)(PAGE_SIZE-1));
                        if (r_addr_page != r_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                            if (r_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                                munmap(r_pixel, BLOCK_SIZE);
                                free((unsigned int *)r_buf);
                            }
                            r_pixel = setup_io((off_t)r_addr_page, &r_buf);
                            r_addr_page_pre = r_addr_page;
                        }
                        xy[i+1][j+1] = *(volatile int *)((unsigned int)r_pixel + r_addr_offset);
                        xy[i+1][j+1] = conv_rgb2y(xy[i+1][j+1]);
                    }
                }
                lap_fil_val = laplacian_fil(xy[0][0], xy[1][0], xy[2][0], xy[0][1], xy[1][1], xy[2][1], xy[0][2], xy[1][2], xy[2][2]);
            }
            w_addr = next_frame_addr+(y*HORIZONTAL_PIXEL_WIDTH + x)*4;
            w_addr_page = w_addr & (~(int)(PAGE_SIZE-1));
            w_addr_offset = w_addr & ((int)(PAGE_SIZE-1));
            if (w_addr_page != w_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                if (w_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                    munmap(w_pixel, BLOCK_SIZE);
                    free((unsigned int *)w_buf);
                }
                w_pixel = setup_io((off_t)w_addr_page, &w_buf);
                w_addr_page_pre = w_addr_page;
            }
            *(volatile int *)((unsigned int)w_pixel + w_addr_offset) = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
            // printf("x = %d  y = %d", x, y);
        }
    }
    munmap((unsigned int *)r_addr_page, BLOCK_SIZE);
    free((unsigned int *)r_buf);
    munmap((unsigned int *)w_addr_page, BLOCK_SIZE);
    free((unsigned int *)w_buf);

    // bitmap-disp-cntrler-axi-master のアドレスを取得
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // ls /sys/devices/axi.0 の内容をパイプに入れる
    fd = popen("ls /sys/devices/axi.0""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            token = buf;
            if ((token=strtok(token, ".\n")) != NULL){
                do {
                    if (chkhex(token)){ // 16進数
                        sscanf(token, "%x", &val);
                    } else {
                        if (strcmp(token, "bitmap-disp-cntrler-axi-master") == 0)
                            bitmap_dc_reg_addr = val;
                    }
                }while((token=strtok(NULL, ".\n")) != NULL);
            }
        }
    }
    pclose(fd);

    // ラプラシアンフィルタの掛かった画像のスタートアドレスを bitmap-disp-cntrler-axi-master にセット
    bm_disp_cnt_reg = setup_io((off_t)bitmap_dc_reg_addr, &bitmap_buf);
    *bm_disp_cnt_reg = next_frame_addr;

    munmap((unsigned int *)bm_disp_cnt_reg, BLOCK_SIZE);
    free((unsigned int *)bitmap_buf);

    return(0);
}


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

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

    y_f = 0.299*r + 0.587*g + 0.114*b;
    y = (int)y_f;

    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)
{
     return(abs(-x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2));
}

//
// Set up a memory regions to access GPIO
//
volatile unsigned *setup_io(off_t mapped_addr, unsigned int *buf_addr)
// void setup_io()
{
    int  mem_fd;
    char *gpio_mem, *gpio_map;

   /* open /dev/mem */
   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("can't open /dev/mem \n");
      printf("mapped_addr = %x\n", mapped_addr);
      exit (-1);
   }

   /* mmap GPIO */

   // Allocate MAP block
   if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
      printf("allocation error \n");
      exit (-1);
   }
    *buf_addr = gpio_mem;    // mallocしたアドレスをコピー

   // Make sure pointer is on 4K boundary
   if ((unsigned long)gpio_mem % PAGE_SIZE)
     gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);

   // Now map it
   gpio_map = (unsigned char *)mmap(
      (caddr_t)gpio_mem,
      BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED|MAP_FIXED,
      mem_fd,
      mapped_addr
   );

   if ((long)gpio_map < 0) {
      printf("mmap error %d\n", (int)gpio_map);
      printf("mapped_addr = %x\n", mapped_addr);
      exit (-1);
   }

   close(mem_fd); // /dev/mem のクローズ

   // Always use volatile pointer!
   // gpio = (volatile unsigned *)gpio_map;
   return((volatile unsigned *)gpio_map);

// setup_io

// 文字列が16進数かを調べる
int chkhex(char *str){
    while (*str != '\0'){
        if (!isxdigit(*str))
            return 0;
        str++;
    }
    return 1;
}

  1. 2013年09月22日 05:52 |
  2. ZedBoard
  3. | トラックバック:0
  4. | コメント:0

ZedBoard Linux上でカメラの画像を処理する5(ラプラシアンフィルタ3)

ZedBoard Linux上でカメラの画像を処理する4(ラプラシアンフィルタ2)”で、ラプラシアン・フィルタのプログラムがSDK14.6を使用すればデバックできることがわかったので、デバックを行った。
いろいろと物理アドレスと論理アドレス変換について迷ったが、mmap()を使えば行けそうだと見込みがついて、ラプラシアン・フィルタをカメラ画像に掛けたのだが、まだ途中でmmap()に失敗してしまう。何故かは分からないが、mmap()を2つアクティブにしているからなのだろうか?
最初にラプラシアン・フィルタを掛けた画像を下に示す。途中で、mmap() が失敗してしまうまで、ラプラシアン・フィルタ結果が表示されている。
image_process_Zed_linux_9_130921.jpg

元の画像ファイルを下に示す。モデルが多少動いてしまったが、大体は合っていると思う。
image_process_Zed_linux_10_130921.jpg

SDK14.6の様子を下に示す。
image_process_Zed_linux_11_130921.png

現在のラプラシアン・フィルタのCソースコードを下に示す。何かバグを見つけたら知らせて欲しい。次は、同時期にアクティブになっている mmap() を1つにしようと思っている。mmap() が複数生きているとまずいことがあるのか?やってみたい。

// laplacian_filter.c
// RGBをYに変換後にラプラシアンフィルタを掛ける。
// ピクセルのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 2013/09/16

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <ctype.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/kernel.h>

#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600
#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

#define BUFSIZE    1024

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 chkhex(char *str);
volatile unsigned *setup_io(off_t mapped_addr);
void Xil_DCacheInvalidateLine(unsigned int adr);
void Xil_DCacheFlushLine(unsigned int adr);

int main()
{
    FILE *fd;
    int xy[3][3];
    char buf[BUFSIZE], *token;
    unsigned int read_num;
    unsigned int bitmap_dc_reg_addr;
    volatile unsigned *bm_disp_cnt_reg;
    unsigned int fb_addr, next_frame_addr;
    int x, y;
    unsigned int val;
    int i, j;
    int lap_fil_val;
    int *r_pixel, *w_pixel;
    unsigned int r_addr, w_addr;
    unsigned int r_addr_page, w_addr_page;
    unsigned int r_addr_page_pre=0, w_addr_page_pre=0;
    unsigned int r_addr_offset, w_addr_offset;
    
    // fb_start_addr.txt の内容をパイプに入れる
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // fb_start_addr.txt を開く
    fd = popen("cat /Apps/fb_start_addr.txt""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            sscanf(buf, "%x\n", &fb_addr);
        }
    }
    pclose(fd);
    
    // ラプラシアンフィルタの結果を入れておくフレーム・バッファ
    next_frame_addr = fb_addr + (ALL_PIXEL_VALUE*4);

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        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{
                for (j=-1; j<2; j++){
                    for (i=-1; i<2; i++){
                        r_addr = fb_addr+((y+j)*HORIZONTAL_PIXEL_WIDTH+(x+i))*4;
                        r_addr_page = r_addr & (~(int)(BLOCK_SIZE-1));
                        r_addr_offset = r_addr & ((int)(BLOCK_SIZE-1));
                        if (r_addr_page != r_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                            if (r_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                                munmap((unsigned int *)r_addr_page_pre, BLOCK_SIZE);
                            }
                            r_pixel = setup_io((off_t)r_addr_page);
                            r_addr_page_pre = r_addr_page;
                        }
                        xy[i+1][j+1] = *(volatile int *)((unsigned int)r_pixel + r_addr_offset);
                        xy[i+1][j+1] = conv_rgb2y(xy[i+1][j+1]);
                    }
                }
                lap_fil_val = laplacian_fil(xy[0][0], xy[1][0], xy[2][0], xy[0][1], xy[1][1], xy[2][1], xy[0][2], xy[1][2], xy[2][2]);
            }
            w_addr = next_frame_addr+(y*HORIZONTAL_PIXEL_WIDTH + x)*4;
            w_addr_page = w_addr & (~(int)(BLOCK_SIZE-1));
            w_addr_offset = w_addr & ((int)(BLOCK_SIZE-1));
            if (w_addr_page != w_addr_page_pre){    // 以前のページと違うのでunmmap してページの物理アドレスを取り直す
                if (w_addr_page_pre != 0){    // 初めの場合はmmap()していないので、munmap()しない
                    munmap((unsigned int *)w_addr_page_pre, BLOCK_SIZE);
                }
                w_pixel = setup_io((off_t)w_addr_page);
                w_addr_page_pre = w_addr_page;
            }
            *(volatile int *)((unsigned int)w_pixel + w_addr_offset) = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
            // printf("x = %d  y = %d", x, y);
        }
    }
    
    // bitmap-disp-cntrler-axi-master のアドレスを取得
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // ls /sys/devices/axi.0 の内容をパイプに入れる
    fd = popen("ls /sys/devices/axi.0""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            token = buf;
            if ((token=strtok(token, ".\n")) != NULL){
                do {
                    if (chkhex(token)){ // 16進数
                        sscanf(token, "%x", &val);
                    } else {
                        if (strcmp(token, "bitmap-disp-cntrler-axi-master") == 0)
                            bitmap_dc_reg_addr = val;
                    }
                }while((token=strtok(NULL, ".\n")) != NULL);
            }
        }
    }
    pclose(fd);
    
    // ラプラシアンフィルタの掛かった画像のスタートアドレスを bitmap-disp-cntrler-axi-master にセット
    bm_disp_cnt_reg = setup_io((off_t)bitmap_dc_reg_addr);
    *bm_disp_cnt_reg = next_frame_addr;

    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
//
int conv_rgb2y(int rgb){
    float r, g, b, y_f;
    int y;
    
    b = (float)(rgb & 0xff);
    g = (float)((rgb>>8) & 0xff);
    r = (float)((rgb>>16) & 0xff);
    
    y_f = 0.299*r + 0.587*g + 0.114*b;
    y = (int)y_f;
    
    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)
{
     return(abs(-x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2));
}

//
// Set up a memory regions to access GPIO
//
volatile unsigned *setup_io(off_t mapped_addr)
// void setup_io()
{
    int  mem_fd;
    char *gpio_mem, *gpio_map;

   /* open /dev/mem */
   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("can't open /dev/mem \n");
      printf("mapped_addr = %x\n", mapped_addr);
      exit (-1);
   }

   /* mmap GPIO */

   // Allocate MAP block
   if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
      printf("allocation error \n");
      exit (-1);
   }

   // Make sure pointer is on 4K boundary
   if ((unsigned long)gpio_mem % PAGE_SIZE)
     gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);

   // Now map it
   gpio_map = (unsigned char *)mmap(
      (caddr_t)gpio_mem,
      BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED|MAP_FIXED,
      mem_fd,
      mapped_addr
   );

   if ((long)gpio_map < 0) {
      printf("mmap error %d\n", (int)gpio_map);
      printf("mapped_addr = %x\n", mapped_addr);
      exit (-1);
   }

   close(mem_fd); // /dev/mem のクローズ

   // Always use volatile pointer!
   // gpio = (volatile unsigned *)gpio_map;
   return((volatile unsigned *)gpio_map);

// setup_io

// 文字列が16進数かを調べる
int chkhex(char *str){
    while (*str != '\0'){
        if (!isxdigit(*str))
            return 0;
        str++;
    }
    return 1;
}

  1. 2013年09月21日 21:00 |
  2. ZedBoard
  3. | トラックバック:0
  4. | コメント:0

ZedBoard Linux上でカメラの画像を処理する4(ラプラシアンフィルタ2)

”ZedBoard Linux上でカメラの画像を処理する3(ラプラシアンフィルタ1)”でどうしても、ラプラシアン・フィルタのソフトウェアで、fb_start_addr.txt をオープンすることが出来なかった。今回は、修正済みのカメラ画像表示用ソフトウェアの cam_disp3_linux.elf を予めSDカードに書き込んでから、ラプラシアン・フィルタのソフトウェアをSDKリモートデバッグすることにした。

まずは、修正済みのカメラ画像表示用ソフトウェアの cam_disp3_linux.elfをSDカードに書き込む。
一旦、SDKリモートデバッグを開始して、cam_disp3_linux.elf をZedBoard Linux の /Apps ディレクトリに送っておく。そうしてから、”ZedBoard Linux のフレームバッファにカメラ画像を表示12(SDカードのイメージ変更)”に書いたある手順に従って、SDカードに現在のファイルシステムを書き込んだ。
これで、ZedBoard Linux 起動時のカメラ画像の表示用ソフトウェアを新しい cam_disp3_linux.elf に置き換えた。
電源OFFしてから電源ONすると、/Apps ディレクトリに fb_start_addr.txt があるのが見えた。そのファイルに書いてあるのが、現在、カメラ画像を表示するに使用しているフレーム・バッファのアドレスだ。
image_process_Zed_linux_6_130919.png

これで、ラプラシアン・フィルタのプログラムをSDKリモートデバッグする環境は、ZedBoard Linux 起動時に整っている。ラプラシアン・フィルタのプログラムのSDKリモートデバッグを行った。(SDK14.4使用)
やはり、ステップ・オーバーするとTime Outになってしまう。
image_process_Zed_linux_5_130918.png

仕方がないので、return_camera_view.elf をデバックすることにした。
デバック・コンフィギュレーションを作製し、SDKリモートデバッグでデバックを開始した。ステップ・オーバーでプログラムの実行を進めたところうまく行った。
image_process_Zed_linux_7_130919.png

もしかしたら、SDKのプロジェクト名は小文字の必要があったか?
小文字のプロジェクト名、laplacian_filter にしてやってみてもダメだった。

次に、SDK14.6 にして、何度かビルドしてやってみたら、デバックができるようになった。
image_process_Zed_linux_8_130919.png

やはり、SDK14.4 がおかしいのかもしれない?
  1. 2013年09月19日 05:37 |
  2. ZedBoard
  3. | トラックバック:0
  4. | コメント:0

ZedBoard Linux上でカメラの画像を処理する3(ラプラシアンフィルタ1)

ラプラシアンフィルタを実行するCのソフトウェア(Laplacian_Filter.c)を作製した。動作を下に示す。(現在使用しているのはSDK14.4)

1.カメラの画像データを書き込んでいるフレーム・バッファからピクセルデータを読み込む。
2.ピクセルデータは上位ビットから{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bitsとなっている。
3.輝度成分をラプラシアンフィルタに掛けるためにRGBからY(輝度成分)を計算する。(int conv_rgb2y(int rgb))
4.9個のデータを集めて、ラプラシアンフィルタに通す。(laplacian_fil())
5.次のフレーム・バッファに当該位置に書き込む。
6.1フレーム分のラプラシアン・フィルタリングが終了したら、フレーム・バッファを次のバッファ領域に移してラプラシアンフィルタが掛かったデータを表示する。


このソフトウェアを実行すると、ラプラシアン・フィルタが掛かった画像が表示されるはずだ(まだ確かめていない)。このソフトウェアを実行すると、生のカメラの画像に戻ることが出来ない。そのため、元のカメラの画像に戻すソフトウェアを作製した。return_camera_view.c だ。
久しぶりにSDKでCプロジェクトを作製したので、Linux用のテンプレートを選べそこねて、standaloneを選んでしまったので、コンパイルできずに困ってしまった。Linux用のCプロジェクトの作り方を書いておく。

FileメニューからNew -> Application Project...を選択して、ダイアログが開いたところだ。
Target Software -> OS Platform を standalone から linux に変更する。
image_process_Zed_linux_3_130917.png

これで、インクルード・ファイルでエラーが出ることが無くなる。

早速、ラプラシアン・フィルタを試してみよう。例によって、SDKリモートデバッグでラプラシアン・フィルタのプログラムをZedBoard Linuxにアップロードして実行してみよう。おっと、その前に新しい cam_disp3_linux.elf をまだSDカードに書いてないので、 cam_disp3_linux.elf を実行しておく必要がある。

cam_disp3_linux.elf 実行後。/Apps ディレクトリに fb_start_addr.txt ができている。その中には、VGA画面にカメラ画像を出力するために使用してるDDR3 SDRAMのスタートアドレスが入ってる。
image_process_Zed_linux_4_130918.png

これで、ラプラシアン・フィルタのソフトウェアの実行環境が整ったので、実行してみよう。
SDKによるリモートデバッグのやり方は、”ZedBoard Linux のフレームバッファにカメラ画像を表示9(SDKリモートデバック)”を参照した。

やはり前回もそうだったが、fopen()するとSDKリモートデバッグで、実行がタイムアウトしてしまう。fopen()は使わないほうが良いかもしれない?下に fopen() を使ったCのコードを示す。

    // fb_start_addr.txt を開く
    if ((fd = fopen("/Apps/fb_start_addr.txt""rt")) == NULL){
        fprintf(stderr, "Can't Open /Apps/fb_start_addr.txt\n");
        exit(1);
    }
    fscanf(fd, "%x", fb_addr);
    fclose(fd);


Laplacian_Filter.c を open()を使って書き換えた。下にCのコードを示す。

    // fb_start_addr.txt を開く
    if ((rfile = open("fb_start_addr.txt", O_RDONLY)) == -1){
        fprintf(stderr, "Can't Open /Apps/fb_start_addr.txt\n");
        exit(1);
    }
    read_bytes = read(rfile, buf, 8);
    *(buf+read_bytes) = NULL;
    close(rfile);


やはり、open() を使用しても実行がタイムアウトしてしまう。仕方がないので、popen()にしてみたが、やはりSDKリモートデバッグ時にタイムアウトしてしまう。popen() のコードを下に示す。

    // fb_start_addr.txt を開く
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // fb_start_addr.txt の内容をパイプに入れる
    fd = popen("cat /Apps/fb_start_addr.txt""r");
    sscanf(buf, "%x", &fb_addr);
    pclose(fd);


SDKの画面を下に示す。
image_process_Zed_linux_5_130918.png

(2012/09/20:追記)
SDK14.6 だとラプラシアン・フィルタのプログラムのデバックができるようです。”ZedBoard Linux上でカメラの画像を処理する4(ラプラシアンフィルタ2)”参照。
  1. 2013年09月18日 05:35 |
  2. ZedBoard
  3. | トラックバック:0
  4. | コメント:0

ZedBoardの本を購入しました

ZedBoardの本を見つけたので、購入しました。でも中国語です。どうしよう~?w
なんとかなるのでは?と思っています。英語やソース、URLだけ見られればOKなのか?
古本ですが、中国語の辞書も買いました。これで何とか読めるでしょうか?
  1. 2013年09月17日 13:08 |
  2. ZedBoard
  3. | トラックバック:0
  4. | コメント:0

ZedBoard Linux上でカメラの画像を処理する2(準備編2)

ZedBoardでカメラ画像処理をするために、以前やってきたことを”ZedBoard Linux上でカメラの画像を処理する1(準備編)”でまとめた。
今回は、フレーム・バッファのアドレスをファイルに書いておくようにするのが目的だ。ZedBoardのLinux起動時に、Linuxのペンギンが写っている既存のフレームバッファをアドレスを取得して、カメラ・インターフェイスIPとビットマップ・ディスプレイ・コントローラIPのフレームバッファ・スタート・アドレス・レジスタにWrite するようなCソフトウェアを実行している。そのソフトウェアでフレーム・バッファの開始アドレスをfb_start_addr.txt というファイルに書いておくようにしようと思う。そのために起動時のCソフトウェアを変更した。
今のところ、SDKリモートデバッグ経由だが、ZedBoardのLinux上で起動して、fb_start_addr.txt に開始アドレスが入るのを確認できた。
image_process_Zed_linux_1_130916.png

0x19106800 番地がフレーム・バッファのスタートアドレスだ。アドレスが中途半端なのは、2頭のペンギンが表示されるように、多分Linuxが描画しているペンギンの描画領域を外すで、140ライン飛ばしているからだ。
dmesg コマンドで元のフレーム・バッファのアドレスを見ると、fbi->fix.smem_start = 0x19000000 となっていて、0x19000000 をフレーム・バッファに指定していることがわかる。
image_process_Zed_linux_2_130916.png

下に、変更した cam_disp3_linux.c を示す。元の cam_disp3_linux.c は、”ZedBoard Linux のフレームバッファにカメラ画像を表示15(Linux既存のFBを使用)”を参照のこと。
(2013/09/25:修正)フレーム・バッファのスタートアドレスを4Kバイトのページ境界に修正した。

// cam_disp3_linux.c
// 
// GPIOを1にして、カメラ表示回路を生かし、MT9D111の設定レジスタにRGB565を設定する
//
// 2013/02/11
// 2013/04/20 : カメラ・インターフェイスIPとビットマップ・ディスプレイ・コントローラIPのフレームバッファ・スタート・レジスタに
//                Linuxの既存のフレームバッファのアドレスをWrite するように変更。
//

#define XPAR_AXI_GPIO_0_BASEADDR        0x44000000
#define XPAR_AXI_IIC_MT9D111_BASEADDR    0x45000000

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <ctype.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <unistd.h>

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

#define BUFSIZE    1024

// I/O access

volatile unsigned *setup_io(off_t addr);
int chkhex(char *str);

volatile unsigned *cam_i2c_control_reg;
volatile unsigned *cam_i2c_status_reg;
volatile unsigned *cam_i2c_tx_fifo;
volatile unsigned *cam_i2c_rx_fifo;

void cam_i2c_init(void) {
    *cam_i2c_control_reg = 0x2// reset tx fifo
    *cam_i2c_control_reg = 0x1// enable i2c
}

void cam_i2x_write_sync(void) {
    // unsigned c;

    // c = *cam_i2c_rx_fifo;
    // while ((c & 0x84) != 0x80)
        // c = *cam_i2c_rx_fifo; // No Bus Busy and TX_FIFO_Empty = 1
    usleep(1000);
}

void cam_i2c_write(unsigned int device_addr, unsigned int write_addr, unsigned int write_data){
    *cam_i2c_tx_fifo = 0x100 | (device_addr & 0xfe);    // Slave IIC Write Address
    *cam_i2c_tx_fifo = write_addr;
    *cam_i2c_tx_fifo = (write_data >> 8)|0xff;            // first data
    *cam_i2c_tx_fifo = 0x200 | (write_data & 0xff);        // second data
    cam_i2x_write_sync();
}

int main()
{
    volatile unsigned *cam_i2c, *axi_gpio;
    volatile unsigned *axi_gpio_tri;
    FILE *fd;
    char buf[BUFSIZE], *token;
    char *str0x;
    unsigned int read_num;
    unsigned int mt9d111_reg_addr, bitmap_dc_reg_addr;
    volatile unsigned *mt9d111_inf_reg;
    volatile unsigned *bm_disp_cnt_reg;
    unsigned int fb_addr;
    unsigned int val;
    
    cam_i2c = setup_io((off_t)XPAR_AXI_IIC_MT9D111_BASEADDR);
    axi_gpio = setup_io((off_t)XPAR_AXI_GPIO_0_BASEADDR);
    
    cam_i2c_control_reg = cam_i2c + 0x40// 0x100番地
    cam_i2c_status_reg = cam_i2c + 0x41// 0x104番地
    cam_i2c_tx_fifo = cam_i2c + 0x42// 0x108番地
    cam_i2c_rx_fifo = cam_i2c + 0x43// 0x10C番地
    
    axi_gpio_tri = axi_gpio + 0x1// 0x4番地
    
    // フレームバッファのアドレスを取得
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // dmesg で、fbi->fix.smem_start の書いてある行をパイプに入れる
    fd = popen("dmesg | grep \"fbi->fix.smem_start\"""r");
    if (fd != NULL){ // fbi->fix.smem_start の値を抽出
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            if ((str0x = strstr(buf, "0x")) != NULL){
                str0x += 2;
                sscanf(str0x, "%x\n", &fb_addr);
            }
        }
    }
    pclose(fd);

    // mt9d111-inf-axi-master と  bitmap-disp-cntrler-axi-master のアドレスを取得
    memset(buf, '\0'sizeof(buf)); // buf すべてに\0 を入れる
    // ls /sys/devices/axi.0 の内容をパイプに入れる
    fd = popen("ls /sys/devices/axi.0""r");
    if (fd != NULL){
        read_num = fread(buf, sizeof(unsigned char), BUFSIZE, fd);
        if (read_num > 0){
            token = buf;
            if ((token=strtok(token, ".\n")) != NULL){
                do {
                    if (chkhex(token)){ // 16進数
                        sscanf(token, "%x", &val);
                    } else {
                        if (strcmp(token, "mt9d111-inf-axi-master") == 0)
                            mt9d111_reg_addr = val;
                        else if (strcmp(token, "bitmap-disp-cntrler-axi-master") == 0)
                            bitmap_dc_reg_addr = val;
                    }
                }while((token=strtok(NULL, ".\n")) != NULL);
            }
        }
    }
    pclose(fd);

    mt9d111_inf_reg = setup_io((off_t)mt9d111_reg_addr);
    bm_disp_cnt_reg = setup_io((off_t)bitmap_dc_reg_addr);

    // フレームバッファのアドレスをAXI4 Lite Slave 経由でレジスタにWrite
    fb_addr = ((fb_addr + (140 * 1920 * 4)) & (~(int)(PAGE_SIZE-1))) + PAGE_SIZE;
    // 2頭のペンギンが表示されるようにペンギンの描画領域を外す。140ライン飛ばす、更にページ境界に合わせる
    *bm_disp_cnt_reg = fb_addr;
    *mt9d111_inf_reg = fb_addr;

    // fb_start_addr.txt に fb_addr を書き込む
    sprintf(buf, "echo %x > /Apps/fb_start_addr.txt", fb_addr);
    system(buf);
    
    // GPIOに1を書いて、カメラ表示回路を動作させる
    *axi_gpio_tri = 0;    // set output
    *axi_gpio = 0x1;    // output '1'
    
    // CMOS Camera initialize, MT9D111
    cam_i2c_init();
    
    cam_i2c_write(0xba, 0xf00x1);        // IFP page 1 へレジスタ・マップを切り替える
    cam_i2c_write(0xba, 0x970x20);    // RGB Mode, RGB565
    
    return(0);
}

//
// Set up a memory regions to access GPIO
//
volatile unsigned *setup_io(off_t mapped_addr)
// void setup_io()
{
    int  mem_fd;
    char *gpio_mem, *gpio_map;

   /* open /dev/mem */
   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("can't open /dev/mem \n");
      exit (-1);
   }

   /* mmap GPIO */

   // Allocate MAP block
   if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
      printf("allocation error \n");
      exit (-1);
   }

   // Make sure pointer is on 4K boundary
   if ((unsigned long)gpio_mem % PAGE_SIZE)
     gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);

   // Now map it
   gpio_map = (unsigned char *)mmap(
      (caddr_t)gpio_mem,
      BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED|MAP_FIXED,
      mem_fd,
      mapped_addr
   );

   if ((long)gpio_map < 0) {
      printf("mmap error %d\n", (int)gpio_map);
      exit (-1);
   }

   close(mem_fd);
   // Always use volatile pointer!
   // gpio = (volatile unsigned *)gpio_map;
   return((volatile unsigned *)gpio_map);

// setup_io

// 文字列が16進数かを調べる
int chkhex(char *str){
    while (*str != '\0'){
        if (!isxdigit(*str))
            return 0;
        str++;
    }
    return 1;
}


なお、echoコマンドをsystem() コマンドで実行することで、fb_start_addr.txt ファイルを作成しているが、fopen() で、fb_start_addr.txt ファイルをオープンして、fprintf() でフレーム・バッファのスタートアドレスを書き込もうとしたら、ソフトウェアが強制終了されてしまった。これはなぜだろう?
これは後で、SDカードに書き込むことになるが、その際には、”ZedBoard Linux のフレームバッファにカメラ画像を表示12(SDカードのイメージ変更)”を参考にして、SDカードに書き込もうと思う。
  1. 2013年09月16日 05:20 |
  2. ZedBoard
  3. | トラックバック:0
  4. | コメント:0

ZedBoard Linux上でカメラの画像を処理する1(準備編)

ZedBoard Linux上でカメラ画像を処理しようと思う。まずは現状確認と手段の検討をしようと思う。

現在の状態は、というと。
1.GitHubのDigilent/linux-digilentから、linux-digilent のZipファイルをダウンロードしてビルドした。”ZedBoardのLinuxカーネルコンパイルのテスト

2.フレーム・バッファの領域を確保する部分に、printk を入れて、フレーム・バッファの開始番地をオープニング・メッセージに表示するようにした。”ZedBoard用Digilent Linuxの解析1(フレームバッファの領域確保)

3.xilinx.dts をDTCでコンパイルすることができたが、それをdevicetree_ramdisk.dtb をとしてSDカードに入れて使用するとエラーになった。axi_iic_mt9d111 の .dts 上の記述を、デフォルトのdevice_ramdisk.dts に付け加えた。これを、DTCでコンパイルすると無事にコンパイルすることができた。出来上がった devicetree_ramdisk.dtb をSDカードにコピーして、ZedBoardを立ち上げたところ、フレームバッファの領域を確保することができた。”DTCでDevice Tree をコンパイルする2

4.ls /sys/devices/axi.0 コマンドを実行すると、自分で作ったchar-rom-axi-lite がどのアドレスにマップされているかが分かった。”XPSプロジェクトにカスタムAX Slave Lite IPを追加してSysfsを見る

5.自作のカメラ・コントローラIPとビットマップ・ディスプレイ・コントローラIPに、フレーム・バッファのアドレスを設定する設定レジスタを新設して、それをAXI4 Lite Slave 経由でRead/Write できるようにした。その AXI4 Lite Slave のアドレスを xilinx.dts で設定し、DTCでコンパイルして、DTBとして使用し、Sysfsに登録できた。”AXI4 Master IP にAXI4 Lite Slave を追加5(Linuxを起動)

6.Linux起動時にLinuxのペンギンが写っている既存のフレームバッファをアドレスを取得して、カメラ・インターフェイスIPとビットマップ・ディスプレイ・コントローラIPのフレームバッファ・スタート・アドレス・レジスタにWrite するようなCソフトウェアを作製した。そのソフトウェアをLinux起動時に動作するようにrcSの記述に追加した。”ZedBoard Linux のフレームバッファにカメラ画像を表示15(Linux既存のFBを使用)

1.~6.の手順で、Linuxが起動している時に、MT9D111カメラ画像をVGAディスプレイに出力できるようになった。

次の目標は、自作のラプラシアン・フィルタとチェビシェフ(ソーベル or ゾーベル(Sobel))・フィルタをCプログラムとして記述して、VGAの画面に写すことを目標とする。

この時に注意すべきことは、カメラの画像はDDR3 SDRAM上に書いてあるが、ARMプロセッサは、それを認識しているとは限らないことだ。ARM(Cortex-A9)プロセッサは1次、2次キャッシュを持ち、DDR3 SDRAMもWrite Backキャッシングされているようだ。その場合、カメラが画像をDDR3 SDRAMに書いたからと言って、ARMに伝わっているとは限らない。DDR3 SDRAMのフレーム・バッファに相当するキャッシュ・ラインをインバリデートして、DDR3 SDRAMから画像データをキャッシュに読み込む必要がある。

ZedBoardにビットマップ・ディスプレイ・コントローラを追加した時には、ARMプロセッサからキャラクタのピクセルデータを書き込んでから、Xil_DCacheFlushLine() を行うと、キャラクタが表示できた。”ZedBoardにビットマップ・ディスプレイ・コントローラを追加する17(ARMのソフトからキャラクタを書けた2)

今回は、ARMプロセッサのWrite ではなく、Read なので、Xil_DCacheInvalidateLine() を使用することになると思われる。(OS and Libraries Document Collection UG643 March 20, 2013 のページ番号 49を参照のこと)

それを踏まえて、カメラから読んできた(この時にキャッシュをインバリデートしてからReadする)ピクセルデータをARMプロセッサのソフトウェアでフィルタを作り、フレーム・バッファの空いているスペースに書き込む。フィルタした画像データの先頭アドレスをディスプレイ・コントローラIPの設定レジスタに書き込んで、フィルタ画像を表示させるという手順だ。
  1. 2013年09月14日 17:10 |
  2. ZedBoard
  3. | トラックバック:0
  4. | コメント:0

2 Mega pixel Camera Module MT9D111 JPEG Out + HQ lens

eBay で注文した”2 Mega pixel Camera Module MT9D111 JPEG Out + HQ lens”が今日届きました。
HQ_lens_mt9d111_1_130912.jpg

HQ_lens_mt9d111_2_130912.jpg

使っていた日昇テクノロジーの MT9D111メガピクセルカメラモジュールとピン配置が一緒だったので、付け替えて電源ONしてディスプレイに表示してみました。しかし、カメラが重そうです。。。いよいよ、マウント用のスペーサを付ける必要がありそうです。そのためにカメラ・インターフェイス基板には取り付け用の穴を開けておきました。
HQ_lens_mt9d111_3_130912.jpg

画面に表示はするのですが、おかしな表示になりました。
ZedBoard_Linux_93_130215.jpg

これは、MT9D111がデフォルト状態の時の表示です。YUVモードになっていて、I2Cでレジスタを設定してRGB565に設定できていない状態ですね。

下に示すのが、日昇テクノロジーの MT9D111メガピクセルカメラモジュールの回路図の一部です。
HQ_lens_mt9d111_4_130912.png

5番ピンと6番ピンのSDAとSCLが10KΩでプルアップされています。

下に示すのが、今度購入した”2 Mega pixel Camera Module MT9D111 JPEG Out + HQ lens”の回路図の一部です。
HQ_lens_mt9d111_5_130912.png

5番ピンと6番ピンのSDAとSCLはプルアップされていないのが分かります。

これが原因かもしれません?早速PlanAhead で、FPGAの出力ピンでPull Upしてみましょう。
PlanAhead を開くと、mt9d111_Scl, mt9d111_Sda の Pull Type が None になっていました。
HQ_lens_mt9d111_6_130912.png

これを PULLUP に変更しました。
HQ_lens_mt9d111_7_130912.png

そして、インプリメント、ビットストリームを生成しました。
インプリメント・デザインを開くとmt9d111_Scl, mt9d111_Sda の Pull Type が PULLUP なのが見えますね。
HQ_lens_mt9d111_8_130912.png

ハードウェアをエクスポートして、SDKを立ちあげました。
Cleanして、コンパイルをし直しました。
HQ_lens_mt9d111_9_130912.png

それで、BOOT.binを作ってやってみましたが、ZedBoard の Done LED が点灯しなかったので、SDKをすべてクリアして、やり直しました。

cam_disp プロジェクトを作って、そこのプロジェクトの右クリックメニューから Create Boot Image を選択しました。
HQ_lens_mt9d111_10_130912.png

すると、FSBLを作っておくと、FSBL.elf と cam_disp.elf がダイアログに入っていて、Output folder もcam_disp\bootimage に設定されています。
HQ_lens_mt9d111_11_130912.png

後は、ビットストリームをAdd して、真ん中の位置に収めて、Create Image ボタンをクリックすると、cam_disp\bootimage に cam_disp.bin が出来ます。
HQ_lens_mt9d111_12_130912.png

それを、SDカードに書いて、BOOT.bin とリネームすればSDカードは出来上がりです。ZedBorad にそのSDカードを入れて、電源ONすると、カメラの画像が写りました。
HQ_lens_mt9d111_13_130912.jpg

ちゃちいレンズとHQレンズの画質の違いはよくわかりませんが、ピントを合わせるときの剛性が高い気がします。F1.2なので、夜の撮影に威力を発揮すると思います。
  1. 2013年09月12日 05:46 |
  2. CMOSイメージセンサ
  3. | トラックバック:0
  4. | コメント:3

Vivado Design Suite のチュートリアルをやってみた7(ジャーナルファイルからTclコマンド実行)

前の記事は、”Vivado Design Suite のチュートリアルをやってみた6(ジャーナルファイル)

前回は、”Vivado Design Suite チュートリアル デザイン フローの概要 UG888 (v2013.2) 2013 年 6 月 19 日”(以下、Vivadoチュートリアル)の21ページの”演習2 : プロジェクトデザインフローの使用”をやった後で、C:\Users\<ユーザー名>\AppData\Roaming\Xilinx\Vivadoのジャーナルファイル( vivado.jou)を見ると、GUIで行った操作のTclコマンドが書かれていた。
今日は、このTclコマンドを一部変更して、CUIで実行できるTclスクリプトファイルを作製して、動作させる。

Vivadoチュートリアルの43ページ、”手順 13 : ジャーナル ファイルからの Tcl スクリプトの作成”をやってみる。

なお、Tclコマンドに関しては、”Vivado Design Suite Tcl コマンド リファレンス ガイド UG835 (v 2013.2) 2013 年 6 月 19 日”を参照した。

手順 13 : ジャーナル ファイルからの Tcl スクリプトの作成

1.コメントを削除した。

2.start_gui を削除した。

3.”project_bft”を検索し、”project_bft_batch”にすべて置換した。2箇所置換した。

4.C:\Users\Masaaki\Documents\Vivado\Vivado_Tutorialフォルダに、run_bft.tcl として保存した。
vivado_jou_3_130911.png

run_bft.tclの内容を下に示す。

create_project project_bft_batch C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Tutorial_Created_Data/project_bft_batch -part xc7k70tfbg484-2
add_files {C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/FifoBuffer.v C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bft.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/async_fifo.v}
add_files -fileset sim_1 C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bft_tb.v
add_files C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib
set_property library bftLib [get_files {C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/round_4.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/round_3.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/round_2.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/round_1.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/core_transform.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/bft_package.vhdl}]
import_files -force
import_files -fileset constrs_1 -force -norecurse C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/bft_full.xdc
update_compile_order -fileset sources_1
update_compile_order -fileset sources_1
update_compile_order -fileset sim_1
synth_design -rtl -name rtl_1
launch_xsim -simset sim_1 -mode behavioral
close_sim
launch_runs synth_1
wait_on_run synth_1
launch_runs impl_1
wait_on_run impl_1
open_run impl_1
close_design
open_run synth_1 -name netlist_1
set_delay_model -interconnect estimated
report_timing_summary -delay_type max -report_unconstrained -check_timing_verbose -max_paths 10 -input_pins -name timing_1
report_power -results {power_1}
close_design
open_run impl_1
report_timing -delay_type min_max -max_paths 10 -sort_by group -input_pins -name timing_1
close_design
launch_runs impl_1 -to_step write_bitstream
wait_on_run impl_1


ジャーナルファイルでは、synth_design、opt_design、route_design などのTclコマンドではなく、launch_runs を使用していた。違いは、非プロジェクトモードではなく、プロジェクトモードで論理合成、インプリメントを行うコマンドだそうだ。

バッチ プロジェクト スクリプトの実行

5.コマンドプロンプトを開く。

6.cd C:\HDL\Xilinx\Vivado\2013.2 フォルダに移動して、settings64.bat を実行した。
vivado_jou_4_130911.png

7.Vivado_Tutorial ディレクトリに移動し、バッチ モードで Vivado ツールを起動した。

cd C:\Users\Masaaki\Documents\Vivado\Vivado_Tutorial
vivado -mode batch -source run_bft.tcl


vivado_jou_5_130911.png

8. run_bft.tcl のTclコマンドが実行された。確かにGUIでやっている時に比べて、メモリの使用量が少ない気がする。

9.Tclコマンドの実行が終了した。シミュレーションの画面は上がらなかった。
vivado_jou_6_130911.png

C:\Users\Masaaki\Documents\Vivado\Vivado_Tutorial\Tutorial_Created_Data\project_bft_batchフォルダの内容を下に示す。
vivado_jou_10_130911.png

10.、C:\Users\<ユーザー名>\AppData\Roaming\Xilinx\Vivadoフォルダのジャーナルファイル( vivado.jou)を確認した。run_bft.tclファイルと同じようだった。

手順 14 : デザイン ステータスの確認

Vivado IDE を起動し、先ほど作成した BFT バッチ プロジェクト (project_bft_batch.xpr) を開く。

11.Vivado 2013.2 を起動する。

12.Open Project をクリックした。
vivado_jou_7_130911.png

13.Browse Project をクリックした。
vivado_jou_8_130911.png

14.C:\Users\Masaaki\Documents\Vivado\Vivado_Tutorial\Tutorial_Created_Data\project_bft_batchフォルダの project_bft_batch.xpr を選択し、OKボタンをクリックした。
vivado_jou_9_130911.png

15.プロジェクトが立ち上がった。これでいろいろなレポートも見ることができる。
vivado_jou_11_130911.png
  1. 2013年09月11日 05:22 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0

Vivado Design Suite のチュートリアルをやってみた6(ジャーナルファイル)

前の記事は、”Vivado Design Suite のチュートリアルをやってみた5(Tclスクリプト5)

Vivado Design Suite チュートリアル デザイン フローの概要 UG888 (v2013.2) 2013 年 6 月 19 日”(以下、Vivadoチュートリアル)の43ページ、”手順 13 : ジャーナル ファイルからの Tcl スクリプトの作成”にも書かれているが、Vivado を使用すると、その使用したTclスクリプトをVivadoジャーナルファイル(vivado.jou) に書くそうだ。
その様子を、21ページの”演習2 : プロジェクトデザインフローの使用”をやりながら、観察してみようと思う。
デフォルトの vivado.jou のパスは、C:\Users\<ユーザー名>\AppData\Roaming\Xilinx\Vivado にある。私は、”演習2 : プロジェクトデザインフローの使用”の”手順 1 : プロジェクトの作成”のWindowsの場合の1.2.3.をやっていないので、デフォルトのパスに vivado.jou があると思う。

1.Vivado 2013.2を起動した時点で、vivado.jouが新しく生成され、start_gui コマンドが書かれている。
vivado_jou_1_130910.png

vivado_jou_2_130910.png

いつ書かれたかわからないまま、つまりNotepad++のエディタで開いていたvivado.jou はアップグレードされたにまま終わった。もう一度開いてみると、チュートリアルの”演習2 : プロジェクトデザインフローの使用”でやった手順が書かれていた。これをTclスクリプトとして実行すると、GUIでやったのと同じ手順がCUIでやることができるのだろう?やってみたい。

#-----------------------------------------------------------
# Vivado v2013.2 (64-bit)
# Build 272601 by xbuild on Sat Jun 15 11:27:26 MDT 2013
# Start of session at: Tue Sep 10 04:56:44 2013
# Process ID: 2412
# Log file: C:/Users/Masaaki/AppData/Roaming/Xilinx/Vivado/vivado.log
# Journal file: C:/Users/Masaaki/AppData/Roaming/Xilinx/Vivado\vivado.jou
#-----------------------------------------------------------
start_gui
create_project project_bft C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Tutorial_Created_Data/project_bft -part xc7k70tfbg484-2
add_files {C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/FifoBuffer.v C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bft.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/async_fifo.v}
add_files -fileset sim_1 C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bft_tb.v
add_files C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib
set_property library bftLib [get_files {C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/round_4.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/round_3.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/round_2.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/round_1.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/core_transform.vhdl C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/hdl/bftLib/bft_package.vhdl}]
import_files -force
import_files -fileset constrs_1 -force -norecurse C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Sources/bft_full.xdc
update_compile_order -fileset sources_1
update_compile_order -fileset sources_1
update_compile_order -fileset sim_1
synth_design -rtl -name rtl_1
launch_xsim -simset sim_1 -mode behavioral
close_sim
launch_runs synth_1
wait_on_run synth_1
launch_runs impl_1
wait_on_run impl_1
open_run impl_1
close_design
open_run synth_1 -name netlist_1
set_delay_model -interconnect estimated
report_timing_summary -delay_type max -report_unconstrained -check_timing_verbose -max_paths 10 -input_pins -name timing_1
report_power -results {power_1}
close_design
open_run impl_1
report_timing -delay_type min_max -max_paths 10 -sort_by group -input_pins -name timing_1
close_design
launch_runs impl_1 -to_step write_bitstream
wait_on_run impl_1


次の記事は、”Vivado Design Suite のチュートリアルをやってみた7(ジャーナルファイルからTclコマンド実行)
  1. 2013年09月10日 05:17 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0

FPGAリテラシー およびチュートリアルのページを更新

FPGAリテラシー およびチュートリアルのページを久しぶりに更新しました。
Vivado の初心者用チュートリアルを自分で書こうと思ったのですが、Xilinx社のチュートリアルがよく出来ているので、その紹介を追加しました。
なお、Vivado_Tutorial/Tutorial_Created_Dataは演習1で作られるので、演習2からやる人は、Tutorial_Created_Dataフォルダを作製する必要があります。
チュートリアル・デザインファイルについての説明は、7ページの”チュートリアル デザイン ファイルのディレクトリ”にあります。
  1. 2013年09月10日 04:25 |
  2. FPGAリテラシー及びチュートリアル
  3. | トラックバック:0
  4. | コメント:2

Vivado Design Suite のチュートリアルをやってみた5(Tclスクリプト5)

前の記事は、”Vivado Design Suite のチュートリアルをやってみた4(Tclスクリプト4)

今回は、”Vivado Design Suite チュートリアル デザイン フローの概要 UG888 (v2013.2) 2013 年 6 月 19 日”(以下、Vivadoチュートリアル)の17ページ、”手順 8 : デザイン チェックポイントを開く”からやってみる。

手順 8 : デザイン チェックポイントを開く

1.start_gui でVivado IDE を再び開いた。
Vivado_Tutorial_42_130908.png

2.Fileメニューから、Open Checkpoint をクリックした。
Vivado_Tutorial_43_130908.png

3.ダイアログで、post_route.dcp をクリックして、OKボタンをクリックした。
Vivado_Tutorial_44_130908.png

4.Open a Checkpoint ダイアログで、Yes ボタンをクリックした。
Vivado_Tutorial_45_130908.png

5.ルート後のチェックポイントが開いたが、あまり何処が変わったのかがわからない。下図は、Packageタブではなく、Deviceタブをクリックした状態だ。
Vivado_Tutorial_46_130908.png

手順 9 : インプリメンテーション結果の解析

6.Toolメニューから、Timing -> Report Timing をクリックした。配置後のタイミングデータを解析するそうだ。
Vivado_Tutorial_47_130908.png

7.Report Timing Summary ダイアログで、デフォルトのまま、OKボタンをクリックした。
Vivado_Tutorial_48_130908.png

8.タイミングを表示する画面が開いた。下のTiming ウインドウで一番上のパスをクリックしたところ、Deviceタブでは、そのパスのルートが開いた。左の Path Properties ウインドウでは、パスの遅延の状況が表示された。
Vivado_Tutorial_49_130908.png

9.左の Path Properties ウインドウをアンドックして、すべてのタイミング・レポートを表示した。
Vivado_Tutorial_50_130908.png

10.Deviceウインドウで、右クリックメニューから Schematic を選択した。
Vivado_Tutorial_51_130908.png

11.Schematic ウインドウが開いて、パスが表示されていた。
Vivado_Tutorial_52_130908.png

12.左のProperties からデータパスの1つのネットをクリックした。右のDeviceタブでは、そのネットが表示されているようだ。
Vivado_Tutorial_53_130908.png

13.Schematicウインドウでも、そのパスが表示されているようだ。
Vivado_Tutorial_54_130908.png

14.Toolメニューから、Timing -> Check Timing... をクリックした。

15.Check Timingダイアログが表示された。デフォルトのまま、OKボタンをクリックした。
Vivado_Tutorial_55_130908.png

16.下のウインドウに、Check Timingウインドウが開いた。ロジックをクリックすると、上のDeviceウインドウにロジックが表示され、プロパティも示された。
Vivado_Tutorial_56_130908.png

17.Toolメニューから、Timing -> Report Timing Summary... をクリックした。

18.Report Timing Summaryダイアログが表示された。デフォルトのまま、OKボタンをクリックした。
Vivado_Tutorial_57_130908.png

19.下のウインドウに、Report Timing Summaryウインドウが示された。Worst Nagative slace は 1.145ns、Worst Hold Slack は 0.073ns、Worst Pulse Width Slack は、2.100ns だった。
Vivado_Tutorial_58_130908.png

20.Toolメニューから、Timing -> Report Pulse Width... をクリックした。

21.Report Pulse Widthダイアログが表示された。デフォルトのまま、OKボタンをクリックした。
Vivado_Tutorial_59_130908.png

22.下のウインドウに Pulse Widthウインドウが表示された。Min Period、Low Pulse Width、High Pulse Width が表示されている。
Vivado_Tutorial_60_130908.png

21.Toolメニューから、Timing -> Create Slack Histogram... をクリックした。

22.Create Slack Histogramダイアログが表示された。デフォルトのまま、OKボタンをクリックした。
Vivado_Tutorial_61_130908.png

23.右上のウインドウに、Endpont Max Slacks ごとの Histogram が表示された。多分スラックが小さい順番に表示されていた。
Vivado_Tutorial_62_130908.png

24.ヒストグラムの1つをクリックすると、下にスラックの値別にリストされていた。便利かも?
Vivado_Tutorial_66_130908.png

25.Toolメニューから、Timing -> Report Clock Interaction... をクリックした。

26.Report Clock Interaction ダイアログが表示された。
Vivado_Tutorial_67_130908.png

27.Clock Interaction が表示された。デフォルトのまま、OKボタンをクリックした。
Vivado_Tutorial_68_130908.png

この色の意味は、”Vivado Design Suite ユーザー ガイド デザイン解析およびクロージャ テクニック UG906 (v2012.4) 2013 年 1 月 11 日”の45ページ、”クロックの相互関係レポートの詳細”に詳しく書いてある。要約する。

・黒-クロックドメイン間のクロックパスがない
・緑-クロックドメイン間のクロックパスが制約されている。
・赤-クロックドメイン間のパスすべてにユーザー定義のfalse_path またはクロック・グループ制約が適用されている
・黄-クロックドメイン間のパスの一部にユーザー定義のfalse_path またはクロック・グループ制約が適用されている


26.Toolメニューから、Timing -> Report Datasheet... をクリックした。

27.Report Datasheet が表示された。デフォルトのまま、OKボタンをクリックした。
Vivado_Tutorial_69_130908.png

28.下のウインドウに、Setup/Hold Timeのリストや出力の遅延などが一覧表示される。まさにデータシートの様に見ることが出来た。
Vivado_Tutorial_70_130908.png

手順 10 : Vivado ツールの終了

29.下のウインドウで、Tcl Console タブをクリックして

stop_gui


コマンドを入力した。
Vivado_Tutorial_71_130908.png

30.viado.log は、C:\Users\Masaaki\AppData\Roaming\Xilinx\Vivado にあった。
Vivado_Tutorial_72_130908.png

ログファイルには、Vivado のセッション中に実行された Tcl コマンドの履歴と結果が全部入っているそうだ。ジャーナルファイルには、Vivado のセッション中に実行された Tcl コマンドのみが入っているそうだ。

Vivado のGUIで行ったTclコマンドもジャーナルファイルにすべて記載されているので、それをコピー・アンド・ペーストして、Tclコマンドファイルを作って実行すれば、GUIで実行したコマンドを非プロジェクトモードで実行することができるそうだ。

”Vivado Design Suite のチュートリアルをやってみた6(ジャーナルファイル)”に続く。
  1. 2013年09月09日 04:31 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0

MFT2013落選

昨日、メールで通知が来て、MFT2013に落選しました。

今まで4年間、出展してきたんですが、自分の名前で出なかったのが行けなかったのでしょうか?とっても残念です。。。

MFT2013用に作製した象嵌アクリルサインの写真を載せます。いかがでしょうか?できれば感想を聞かせて下さい。
acrylic_signs_1_130907.jpg

第2弾も明日作る予定です。
  1. 2013年09月07日 21:14 |
  2. Make出展
  3. | トラックバック:0
  4. | コメント:0

Vivado Design Suite のチュートリアルをやってみた4(Tclスクリプト4)

前の記事は、”Vivado Design Suite のチュートリアルをやってみた3(Tclスクリプト3)”

今回は、、”Vivado Design Suite チュートリアル デザイン フローの概要 UG888 (v2013.2) 2013 年 6 月 19 日”(以下、Vivadoチュートリアル)の16ページ、”手順 7 : デザインのインプリメンテーション”からやってみる。

run_bft_batch.tcl の STEP#3 から STEP#4 までの Tclコマンドを実行した。

1.opt_design
コマンドの内容を下に引用する。

現在のネットリストを最適化します。デフォルトでは、リターゲット、定数伝搬、スイープ、およびブロック RAM の消費電力最適化が実行されます。

デザイン ネットリストをターゲット パーツ用に最適化します。最適化では、サードパーティ ツールからの合成済
みネットリストや合成中に最適化されなかったネットリストを向上できます。
このコマンドをインプリメンテーションでデザインを配置配線する前に実行し、デザインを最適化してネットリスト
を簡略化します。


Vivado_Tutorial_33_130907.png

2.power_opt_design
コマンドの内容を下に引用する。

高度なクロック ゲーティングを使用して、ダイナミック消費電力を最適化します。

フリップフロップのクロック イネーブルを利用してクロック ゲーティングを変更することにより、デザインのダイナミック消費電力を最適化します。クロック ゲーティング最適化はデザイン全体に対して自動的に実行され、デザインの動作が変化する可能性のある既存のロジックやクロックを変更せずに消費電力を削減します。


Vivado_Tutorial_34_130907.png

3.place_design
コマンドの内容を下に引用する。

ポートと最下位インスタンスを自動配置します。


つまり、ロジックリソースを配置する。このコマンドは時間がかかった。
Vivado_Tutorial_35_130907.png

4.phys_opt_design
コマンドの内容を下に引用する。

現在の配置済みネットリストを最適化します。

デザインの負のスラック パスに対してタイミング ドリブンの最適化を実行します。最適化が実行されるためには、パスの負のスラックがワースト ネガティブ スラック (WNS) に近いことが必要です。このコマンドは、place_design コマンドの後、route_design コマンドの前に実行してください。


Vivado_Tutorial_36_130907.png

5.write_checkpoint -force $outputDir/post_place
コマンドの内容を下に引用する。

現在のデザインのチェックポイントを書き出します。

デザインをデザイン プロセスの任意の段階で保存し、必要に応じてツールにすばやくインポートし直せるように
します。デザイン チェックポイント (DCP) には、ネットリスト、制約、およびインプリメント済みデザインの配置配線情報が含まれます。
チェックポイント ファイルをインポートするには、read_checkpoint コマンドを使用します。


Vivado_Tutorial_37_130907.png

C:\Users\Masaaki\Documents\Vivado\Vivado_Tutorial\Tutorial_Created_Data\bft_outputフォルダに、post_place.dcp が書かれた。

ここまでで、今回の実行したコマンドでのレポートは全く書かれていない。

6.report_timing_summary -file $outputDir/post_place_timing_summary.rpt
Vivado_Tutorial_38_130907.png

C:\Users\Masaaki\Documents\Vivado\Vivado_Tutorial\Tutorial_Created_Data\bft_outputフォルダに、post_place_timing_summary.rpt ファイルが書かれた。

7.route_design
コマンドの内容を下に引用する。

現在のデザインを配線します。


Vivado_Tutorial_39_130907.png

8.以下のコマンドを実行した。

opt_design
place_design
write_checkpoint -force $outputDir/post_place
report_timing_summary -file $outputDir/post_place_timing_summary.rpt
route_design
write_checkpoint -force $outputDir/post_route
report_timing_summary -file $outputDir/post_route_timing_summary.rpt
report_timing -sort_by group -max_paths 100 -path_type summary -file
$outputDir/post_route_timing.rpt
report_clock_utilization -file $outputDir/clock_util.rpt
report_utilization -file $outputDir/post_route_util.rpt
report_power -file $outputDir/post_route_power.rpt
report_drc -file $outputDir/post_imp_drc.rpt
write_verilog -force $outputDir/bft_impl_netlist.v
write_xdc -no_fixed_only -force $outputDir/bft_impl.xdc
write_bitstream -force $outputDir/bft.bit


これで、ビットストリームまで生成できたわけだ。
Vivado_Tutorial_40_130907.png

C:\Users\Masaaki\Documents\Vivado\Vivado_Tutorial\Tutorial_Created_Data\bft_outputフォルダに生成されているファイルを下に示す。
Vivado_Tutorial_41_130907.png

次の記事は、”Vivado Design Suite のチュートリアルをやってみた5(Tclスクリプト5)
  1. 2013年09月07日 05:03 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0

Vivado Design Suite のチュートリアルをやってみた3(Tclスクリプト3)

前の記事は、”Vivado Design Suite のチュートリアルをやってみた2(Tclスクリプト2)

、”Vivado Design Suite チュートリアル デザイン フローの概要 UG888 (v2013.2) 2013 年 6 月 19 日”(以下、Vivadoチュートリアル)の10ページの”手順 4 : Vivado IDE の起動”からやってみる。

手順 4 : Vivado IDE の起動

タイミング制約の定義
1.start_gui コマンドを入力した。
Vivado_Tutorial_15_130906.png

2.Vivado IDEが立ち上がった。
Vivado_Tutorial_16_130906.png

3.Deviceタブをクリックした。
Vivado_Tutorial_17_130906.png

手順 5 : タイミング制約と I/O 配置の定義

4.Windows メニューから Timing Constrains を選択した。
Vivado_Tutorial_18_130906.png

5.Timing Constrains ビューが立ち上がった。
Vivado_Tutorial_19_130906.png

Create Clockペインに、wbClk と bftClk という 2 つのクロック制約が定義されていた。それぞれ10nsec と 5nsec のピリオド制約が付加されていた。

6.Create Clockペインで右クリックして、右クリックメニューから Create Constrains を選択した。
Vivado_Tutorial_20_130906.png

7.Create Clock ダイアログが表示された。ここで、クロック制約を入力することになる。
Vivado_Tutorial_21_130906.png

8.とりあえず、クロック制約は作成しないので、Cancelボタンをクリックした。

I/O プランニング
9.LayoutメニューからI/O Planning を選択する。
Vivado_Tutorial_22_130906.png

10.I/Oプランニングの画面になった。
Vivado_Tutorial_23_130906.png

11.拡大すると、オレンジ色の四角の中にピン名が表示されている。オレンジ色の四角はピンが割り当てられていることを示す。

12.左下のピンをクリックした。(ピンクの四角)すると、下のウインドウにピン名と属性が表示される。
Vivado_Tutorial_24_130906.png

13.IO_L3Pに配置してある wbOutputData[8] をドラッグ・アンド・ドロップで、IO_L20Nに移す。
Vivado_Tutorial_25_130906.png

14.移動した。
Vivado_Tutorial_26_130906.png

手順 6 : 変更した制約のエクスポート

・Vivado では UCFファイルはサポートされていない。新しいSDCベースのXDCに変更された。PlanAhead の TclスクリプトでUCF からXDC に変換することができるそうだ。(”Vivado Design Suite 移行手法ガイド”の20ページ、PlanAhead ツールで UCF を XDC に変換 を参照)

・変更された XDC 制約ファイルを出力する。

15.ファイルメニューの Export -> Export Constraints をクリックした。
Vivado_Tutorial_27_130906.png

16.Exports Constrains ダイアログが開いた。Output file name の右脇の... ボタンをクリックした。
Vivado_Tutorial_28_130906.png

17.出てきたダイアログで、C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial/Tutorial_Created_Data/bft_outputファルダの bft_constrs_1.xdc にファイル名を設定した。

18.OKボタンをクリックした。

19.次に、15.と同様に Export Constraints をクリックして、Exports Constrains ダイアログを表示させた。

20.今度は、Export fixed location constrains only のチェックを外して、bft_constrs_2.xdc という名前を指定して、OKボタンをクリックした。
Vivado_Tutorial_29_130906.png

21.bft_constrs_1.xdc と bft_constrs_2.xdc の内容を比べてみたが、同じだった。
Vivado_Tutorial_30_130906.png

22.一番下のウインドウで、Tcl Console タブをクリックして、stop_gui コマンドを入力した。
Vivado_Tutorial_31_130906.png

23.TclシェルのTclプロンプトに戻った。
Vivado_Tutorial_32_130906.png

Vivado Design Suite のチュートリアルをやってみた4(Tclスクリプト4)”に続く。
  1. 2013年09月06日 05:05 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0

FCブログで半角スペースを保ったままテキストを表示

FC2ブログでテキストのログなどを貼ると半角スペースが削除されて見た目が悪くなってしまった。
今までSourceConverter アプリを使って、HTMLファイルに変換していたが、<pre>、</pre>(<>は半角)で囲めば良いことがわかった。

・<pre>、</pre>を使っていない例
+------------+------+-------+-----------+-------+
| Site Type | Used | Loced | Available | Util% |
+------------+------+-------+-----------+-------+
| BUFGCTRL | 2 | 0 | 32 | 6.25 |
| BUFIO | 0 | 0 | 24 | 0.00 |
| MMCME2_ADV | 0 | 0 | 6 | 0.00 |
| PLLE2_ADV | 0 | 0 | 6 | 0.00 |
| BUFMRCE | 0 | 0 | 12 | 0.00 |
| BUFHCE | 0 | 0 | 96 | 0.00 |
| BUFR | 0 | 0 | 24 | 0.00 |
+------------+------+-------+-----------+-------+

・<pre>、</pre>を使った例
+------------+------+-------+-----------+-------+
| Site Type | Used | Loced | Available | Util% |
+------------+------+-------+-----------+-------+
| BUFGCTRL | 2 | 0 | 32 | 6.25 |
| BUFIO | 0 | 0 | 24 | 0.00 |
| MMCME2_ADV | 0 | 0 | 6 | 0.00 |
| PLLE2_ADV | 0 | 0 | 6 | 0.00 |
| BUFMRCE | 0 | 0 | 12 | 0.00 |
| BUFHCE | 0 | 0 | 96 | 0.00 |
| BUFR | 0 | 0 | 24 | 0.00 |
+------------+------+-------+-----------+-------+

・<pre>、</pre>を使っていない例
module memory_8bit #(
parameter integer C_S_AXI_ADDR_WIDTH = 32,
parameter integer C_MEMORY_SIZE = 512 // Word (not byte)
)(
input wire clk,
input wire [C_S_AXI_ADDR_WIDTH-1:0] waddr,
input wire [7:0] write_data,
input wire write_enable,
input wire byte_enable,
input wire [C_S_AXI_ADDR_WIDTH-1:0] raddr,
output wire [7:0] read_data
);

・<pre>、</pre>を使った例
module memory_8bit #(
parameter integer C_S_AXI_ADDR_WIDTH = 32,
parameter integer C_MEMORY_SIZE = 512 // Word (not byte)
)(
input wire clk,
input wire [C_S_AXI_ADDR_WIDTH-1:0] waddr,
input wire [7:0] write_data,
input wire write_enable,
input wire byte_enable,
input wire [C_S_AXI_ADDR_WIDTH-1:0] raddr,
output wire [7:0] read_data
);

タブは8キャラクタになってしまうようだ。HDLファイルやCソースはHTMLに変換したほうが良さそうだ。
  1. 2013年09月05日 07:14 |
  2. FC2ブログ
  3. | トラックバック:0
  4. | コメント:0

Vivado Design Suite のチュートリアルをやってみた2(Tclスクリプト2)

前の記事は、”Vivado Design Suite のチュートリアルをやってみた1(Tclスクリプト)

前の記事で、”source run_bft_batch.tcl”を実行したら、すべての手順が完了して、ビットストリームを生成してしまった。これでは、、”Vivado Design Suite チュートリアル デザイン フローの概要 UG888 (v2013.2) 2013 年 6 月 19 日”(以下、Vivadoチュートリアル)に沿っていない。手順通りにやったつもりだが、ファイルが違っていたのか?

今回は、Vivado_Tutorial\Tutorial_Created_Dataフォルダを消去して、チュートリアルの通りにやり直してみたい。


手順 2 : サンプル デザインを使用して Vivado ツールを起動

1.Vivadoチュートリアルの”手順 2 : サンプル デザインを使用して Vivado ツールを起動”(8ページ)では、run_bft_batch.tcl の STEP#0 と STEP#1 のスクリプトをすべて実行するようだ。やって見た。

set outputDir ./Tutorial_Created_Data/bft_output
file mkdir $outputDir
read_vhdl -library bftLib [ glob ./Sources/hdl/bftLib/*.vhdl ]
read_vhdl ./Sources/hdl/bft.vhdl
read_verilog [ glob ./Sources/hdl/*.v ]
read_xdc ./Sources/bft_full.xdc


Vivado_Tutorial_10_130905.png

手順 3 : デザインの合成

2.下のTclコマンドを実行して、論理合成を行った。

synth_design -top bft -part xc7k70tfbg484-2 -flatten rebuilt



3.論理合成が終了した。
Vivado_Tutorial_11_130905.png

4.合成レポートはスクロールしても、途中で切れていて確認できない。なお、この状態では、Tutorial_Created_Data/bft_output フォルダにはファイルは何もない。

5.チェックポイントを作成し、レポートを出力させるコマンドを貼り付けた。

write_checkpoint -force $outputDir/post_synth
report_timing_summary -file $outputDir/post_synth_timing_summary.rpt
report_power -file $outputDir/post_synth_power.rpt


Vivado_Tutorial_12_130905.png

6.チェックポイントとレポートが作製されている。
Vivado_Tutorial_13_130905.png

7.パワーとタイミングだけでは寂しいので、report_utilizationコマンドで、リソース使用量をレポートしてみた。

report_utilization -file $outputDir/post_synth_util.rpt


Vivado_Tutorial_14_130905.png


Copyright 1986-1999, 2001-2013 Xilinx, Inc. All Rights Reserved.
-------------------------------------------------------------------------------------------------
| Tool Version : Vivado v.2013.2 (win64) Build 272601 Sat Jun 15 11:27:26 MDT 2013
| Date : Thu Sep 05 05:27:58 2013
| Host : running 64-bit Service Pack 1 (build 7601)
| Command : report_utilization -file ./Tutorial_Created_Data/bft_output/post_synth_util.rpt
| Design : bft
| Device : xc7k70t
| Design State : Synthesized
-------------------------------------------------------------------------------------------------

Utilization Design Information

Table of Contents
-----------------
1. Slice Logic
2. Memory
3. DSP
4. IO and GTX Specific
5. Clocking
6. Specific Feature
7. Primitives
8. Black Boxes
9. Instantiated Netlists

1. Slice Logic
--------------

+-------------------------+------+-------+-----------+-------+
| Site Type | Used | Loced | Available | Util% |
+-------------------------+------+-------+-----------+-------+
| Slice LUTs* | 1965 | 0 | 41000 | 4.79 |
| LUT as Logic | 1965 | 0 | 41000 | 4.79 |
| LUT as Memory | 0 | 0 | 13400 | 0.00 |
| Slice Registers | 1370 | 0 | 82000 | 1.67 |
| Register as Flip Flop | 1370 | 0 | 82000 | 1.67 |
| Register as Latch | 0 | 0 | 82000 | 0.00 |
| F7 Muxes | 32 | 0 | 20500 | 0.15 |
| F8 Muxes | 0 | 0 | 10250 | 0.00 |
+-------------------------+------+-------+-----------+-------+
* Warning! The Final LUT count, after physical optimizations and full implementation, is typically lower. Run opt_design after synthesis for a more realistic count.


2. Memory
---------

+-------------------+------+-------+-----------+-------+
| Site Type | Used | Loced | Available | Util% |
+-------------------+------+-------+-----------+-------+
| Block RAM Tile | 16 | 0 | 135 | 11.85 |
| RAMB36/FIFO* | 16 | 0 | 135 | 11.85 |
| RAMB36E1 only | 16 | | | |
| RAMB18 | 0 | 0 | 270 | 0.00 |
+-------------------+------+-------+-----------+-------+
* Note: Each Block RAM Tile only has one FIFO logic available and therefore can accommodate only one FIFO36E1 or one FIFO18E1. However, if a FIFO18E1 occupies a Block RAM Tile, that tile can still accommodate a RAMB18E1


3. DSP
------

+----------------+------+-------+-----------+-------+
| Site Type | Used | Loced | Available | Util% |
+----------------+------+-------+-----------+-------+
| DSPs | 64 | 0 | 240 | 26.66 |
| DSP48E1 only | 64 | | | |
+----------------+------+-------+-----------+-------+


4. IO and GTX Specific
----------------------

+-----------------------------+------+-------+-----------+-------+
| Site Type | Used | Loced | Available | Util% |
+-----------------------------+------+-------+-----------+-------+
| Bonded IOB | 71 | 71 | 285 | 24.91 |
| IOB Master Pads | 35 | | | |
| IOB Slave Pads | 33 | | | |
| Bonded IPADs | 0 | 0 | 14 | 0.00 |
| Bonded OPADs | 0 | 0 | 8 | 0.00 |
| GTXE2_CHANNEL | 0 | 0 | 4 | 0.00 |
| GTXE2_COMMON | 0 | 0 | 1 | 0.00 |
| IBUFGDS | 0 | 0 | 275 | 0.00 |
| IDELAYCTRL | 0 | 0 | 6 | 0.00 |
| IN_FIFO | 0 | 0 | 24 | 0.00 |
| OUT_FIFO | 0 | 0 | 24 | 0.00 |
| PHASER_REF | 0 | 0 | 6 | 0.00 |
| PHY_CONTROL | 0 | 0 | 6 | 0.00 |
| PHASER_OUT/PHASER_OUT_PHY | 0 | 0 | 24 | 0.00 |
| PHASER_IN/PHASER_IN_PHY | 0 | 0 | 24 | 0.00 |
| IDELAYE2/IDELAYE2_FINEDELAY | 0 | 0 | 300 | 0.00 |
| ODELAYE2/ODELAYE2_FINEDELAY | 0 | 0 | 100 | 0.00 |
| IBUFDS_GTE2 | 0 | 0 | 4 | 0.00 |
| ILOGIC | 0 | 0 | 285 | 0.00 |
| OLOGIC | 0 | 0 | 285 | 0.00 |
+-----------------------------+------+-------+-----------+-------+


5. Clocking
-----------

+------------+------+-------+-----------+-------+
| Site Type | Used | Loced | Available | Util% |
+------------+------+-------+-----------+-------+
| BUFGCTRL | 2 | 0 | 32 | 6.25 |
| BUFIO | 0 | 0 | 24 | 0.00 |
| MMCME2_ADV | 0 | 0 | 6 | 0.00 |
| PLLE2_ADV | 0 | 0 | 6 | 0.00 |
| BUFMRCE | 0 | 0 | 12 | 0.00 |
| BUFHCE | 0 | 0 | 96 | 0.00 |
| BUFR | 0 | 0 | 24 | 0.00 |
+------------+------+-------+-----------+-------+


6. Specific Feature
-------------------

+-------------+------+-------+-----------+-------+
| Site Type | Used | Loced | Available | Util% |
+-------------+------+-------+-----------+-------+
| BSCANE2 | 0 | 0 | 4 | 0.00 |
| CAPTUREE2 | 0 | 0 | 1 | 0.00 |
| DNA_PORT | 0 | 0 | 1 | 0.00 |
| EFUSE_USR | 0 | 0 | 1 | 0.00 |
| FRAME_ECCE2 | 0 | 0 | 1 | 0.00 |
| ICAPE2 | 0 | 0 | 2 | 0.00 |
| PCIE_2_1 | 0 | 0 | 1 | 0.00 |
| STARTUPE2 | 0 | 0 | 1 | 0.00 |
| XADC | 0 | 0 | 1 | 0.00 |
+-------------+------+-------+-----------+-------+


7. Primitives
-------------

+----------+------+
| Ref Name | Used |
+----------+------+
| LUT2 | 1427 |
| FDCE | 1152 |
| LUT6 | 370 |
| FDPE | 160 |
| LUT4 | 147 |
| LUT5 | 114 |
| LUT3 | 84 |
| CARRY4 | 80 |
| DSP48E1 | 64 |
| FDRE | 58 |
| LUT1 | 55 |
| IBUF | 37 |
| OBUF | 34 |
| MUXF7 | 32 |
| RAMB36E1 | 16 |
| BUFG | 2 |
+----------+------+


8. Black Boxes
--------------

+----------+------+
| Ref Name | Used |
+----------+------+


9. Instantiated Netlists
------------------------

+----------+------+
| Ref Name | Used |
+----------+------+


Vivado Design Suite のチュートリアルをやってみた3(Tclスクリプト3)”に続く。
  1. 2013年09月05日 05:34 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0

Vivado Design Suite のチュートリアルをやってみた1(Tclスクリプト)

Vivado のIP Integrator やIP Packager はとりあえず次のバージョンを待ちたいが、Vivado はチュートリアルをやってみてやり方を学びたいと思った。そこで、”Vivado Design Suite チュートリアル デザイン フローの概要 UG888 (v2013.2) 2013 年 6 月 19 日”をやってみることにした。使用するVivado のバージョンは、2013.2

プロジェクトモードと非プロジェクトモードがあって両方やるらしい。

非プロジェクトモードで明示的にTclコマンドを使用して、ソース ファイルは read_verilog、read_vhdl、read_edif、read_ip、およびread_xdc コマンドを使用して読み込まれるそうだ。

最初にチュートリアルデザインをフォルダに解凍する。

1.C:\HDL\Xilinx\Vivado\2013.2\examples のVivado_Tutorial.zip の中身を、C:\Users\Masaaki\Documents\Vivado にコピーした。
Vivado_Tutorial_1_130904.png

演習1 : 非プロジェクトデザインフローの使用(8ページ)
手順1で、Vivado_Tutorial/run_bft_batch.tclを確認したが、コメントアウトはされていなかった。

手順 2 : サンプル デザインを使用して Vivado ツールを起動(8ページ)

2.Vivado 2013.2 Tcl Shell を起動した。
Vivado_Tutorial_2_130904.png

Vivado_Tutorial_3_130904.png

3.C:\Users\Masaaki\Documents\Vivado\Vivado_Tutorial に移動した。

cd C:/Users/Masaaki/Documents/Vivado/Vivado_Tutorial


Vivado_Tutorial_4_130904.png

4.run_bft_batch.tcl スクリプトを実行した。

source run_bft_batch.tcl


Vivado_Tutorial_5_130904.png

5.スクリプトが終了した。
Vivado_Tutorial_6_130904.png

手順 3 : デザインの合成(9ページ)

チュートリアルでは、論理合成などをTclコマンドでやっていくことになっているが、実際のrun_bft_batch.tcl にはビットストリームを作るところまで、コメントアウト無しに tcl コマンドが書いてあったので、ビットストリームの生成まで終了していると思う。

6.C:\Users\Masaaki\Documents\Vivado\Vivado_Tutorial\Tutorial_Created_Data\bft_output に bft.bit があった。
Vivado_Tutorial_7_130904.png

・bft_impl.xdc には、タイミング制約、ピン固定はもちろんだが、ロジックの配置固定制約まで出力されている。
下に、タイミング制約、ピン固定制約の一部を引用する。

create_clock -period 10.000 -name wbClk [get_ports wbClk]
create_clock -period 5.000 -name bftClk [get_ports bftClk]
set_property PACKAGE_PIN P20 [get_ports {wbOutputData[23]}]
set_property PACKAGE_PIN V22 [get_ports {wbOutputData[9]}]
set_property PACKAGE_PIN E21 [get_ports {wbInputData[18]}]


下に、配置固定情報の一部を示す。

set_property BEL AFF [get_cells error_reg]
set_property BEL B6LUT [get_cells {egressLoop[4].egressFifo/buffer_fifo/xlnx_opt_LUT_infer_fifo.wr_addr_tmp_reg[2]_CE_cooolgate_en_gate_15_1}]
set_property BEL A5LUT [get_cells {egressLoop[4].egressFifo/buffer_fifo/infer_fifo.wr_addr_tmp_reg[9]_i_2__11}]
set_property BEL D6LUT [get_cells {egressLoop[4].egressFifo/buffer_fifo/infer_fifo.wr_addr_tmp_reg[9]_i_1__11}]
set_property BEL DFF [get_cells {egressLoop[4].egressFifo/buffer_fifo/infer_fifo.wr_addr_tmp_reg[9]}]


bft_impl.xdc は、Route 後のSTEP#4 の”write_xdc -no_fixed_only -force $outputDir/bft_impl.xdc”コマンドで出力されている。”-no_fixed_only”オプションは、”Vivado Design Suite Tcl コマンド リファレンス ガイド UG835 (v 2013.2) 2013 年 6 月 19 日”(以下、Tclガイドと略する)の1033ページによると、

配置が固定されているかどうかに関わらず、すべての配置をエクスポートします。デフォルトでは、固定された配置のみがエクスポートされます。


だそうだ。

・post_synth.dcp、post_place.dcp、post_route.dcp というファイルがあるが、これは、Design Check Point の略で、”デザインをデザイン プロセスの任意の段階で保存し、必要に応じてツールにすばやくインポートし直せるようにします。”とのことだ。write_checkpoint のTcl コマンドで書き出すことができる。(Tclガイド、1006ページ)
read_checkpoint コマンドで、Design Check Point を読み込むことができる。(Tclガイド、623ページ)

レポートは、9個のレポートファイルがある。
Vivado_Tutorial_8_130905.png

最後にネットリスト形式のVerilog HDLファイルのbft_impl_netlist.v が出力されている。その一部を示す。
Vivado_Tutorial_9_130905.png

ネットリスト形式のVerilog HDLが出力されていても遅延情報が書かれたSDFファイルが出力されていない。遅延シミュレーションを行う場合はそれらが必要となる。その場合は write_verilog コマンドに、-mode timesim と -sdf_anno オプションを指定し、、Verilog ネットリストに $sdf_annotate分を追加しておく。この場合は、-sdf_file は指定されていないので、SDF ファイルの名前は Verilog ファイルと同じになるそうだ。write_verilog コマンドの例を下に引用する。(Tclガイド、1029ページ)

write_verilog C:/Data/my_verilog.net -mode timesim -sdf_anno true


更に、write_sdf コマンドで、SDF遅延ファイルを生成する。(Tclガイド、1025ページ)

Vivado Design Suite のチュートリアルをやってみた2(Tclスクリプト2)”に続く。
  1. 2013年09月04日 05:49 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0

Vivado HLSで作ったaxi_master IPをテストした1(論理合成、インプリメント)

Vivado HLSのExample として試した axi_master IP をテストしてみた。

Vivado HLSで axi_master IP を作った手順をかいたブログ記事を下に示す。

Vivado HLSのAXI Master Exampleを試す1
Vivado HLSのAXI Master Exampleを試す2


Vivado を立ちあげて、”Vivado チュートリアル Designing with IP Lab3 (IP Packager)3”と同様な手順で、Vivadoプロジェクトを作成し、axi_master IP の Example を追加した。
Vivado_HLS_68_130902.png

Block Design を新規作成して、ポートを付けた。
Vivado_HLS_69_130902.png

Address Editor タブをクリックした。アドレスが割り当てられていなかった。
Vivado_HLS_70_130902.png

M_AXI_Aの右クリックメニューから Assign Address を選択した。
Vivado_HLS_71_130902.png

M_AXI_Aにアドレスがマップされた。
Vivado_HLS_72_130902.png

Block Design をセーブした。
このままだと、論理合成出来ないので、Block Design の axi_master_test の右クリックメニューから Create HDL Wrapper を選択して、HDL Wapper ファイルを作製する。
Vivado_HLS_73_130902.png

axi_master_test_wapper.v が作製された。
Vivado_HLS_74_130902.png

これで論理合成、インプリメントを行った。
論理合成は通ったが、インプリメントでエラーが発生した。AXI Master を持つデバイスを無理やり、そのままインスタンスしているので、しょうがないかもしれない。
Vivado_HLS_75_130902.png
  1. 2013年09月02日 04:19 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:0

Vivadoで配置されたロジックと配線を見る2

Vivadoで配置されたロジックと配線を見る1”の続き。

前回は、画面がFPGA Editor ぽくなったので、今回は、更に使い込んでみた。

・回路図からwbDataForInputReg_reg を選択した。
Vivado_FPGA_Editor_29_130901.png

・Device タブに行くと、wbDataForInputReg_reg が表示されて、ファンイン、ファンアウトの配線が表示されていた。(ファンイン、ファンアウトの配線表示は切ることもできる)
Vivado_FPGA_Editor_30_130901.png

・Device タブで、wbDataForInputReg_reg を拡大してみた。スライスの論理構造が見える。
Vivado_FPGA_Editor_31_130901.png

・wbDataForInputReg_reg の出力ネットを回路図から選択してみた。配線遅延を測ってみると3つのみ表示されて、後は0になっている。
Vivado_FPGA_Editor_22_130901.png

・Device ウインドウでは、沢山の経路が表示されている。かなりファンアウトが多い。(ファンアウトは25)
Vivado_FPGA_Editor_23_130901.png

・ingressLoop[?].ingressFifo に wbDataForInputReg ネットが行っている。
Vivado_FPGA_Editor_24_130901.png

・ingressLoop[5].ingressFifo を1階層展開してみた。
Vivado_FPGA_Editor_25_130901.png

・もう1層展開するとこうなった。これでやっと実際のロジックが見えた。
Vivado_FPGA_Editor_26_130901.png

・内部の階層の配線をクリックして配線遅延を見てみた。
Vivado_FPGA_Editor_27_130901.png

・配線遅延(Net Properties の Cell Pins)
Vivado_FPGA_Editor_28_130901.png

・Device ウインドウは、以前と同様だ。同じネットに接続されているので、当然といえば当然だが、すべての接続ネットの配線遅延が一括で見たい場合はどうするのだろう?
Vivado_FPGA_Editor_23_130901.png

・やり方がわかった。Net Properties の Connectivity を選択すればすべてのネットの配線遅延が見えた。
Vivado_FPGA_Editor_32_130901.png

Vivado では、回路図も参照しながら、FPGAの内部ロジックや配線の具合を見ることができることがわかった。FPGA Editor より使いやすくなったと思う。
  1. 2013年09月01日 04:28 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0