FC2カウンター FPGAの部屋 DNN

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

FPGAの部屋

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

白線追従走行用畳み込みニューラルネットワークの製作1(概要)

MNIST手書き数字認識用ニューラルネットワークが完成したので、今度は本命のミニ・ロボットカーを白線間を走らせるための白線追従走行用畳み込みニューラルネットワーク(白線追従走行用CNN)を製作しよう。

白線追従走行用CNNを作るにあたって、最初は直線の白線間を追従走行するための畳み込みニューラルネットワーク(CNN)を作ろうと思う。具体的には、多少外れた位置にいても、直線の白線間に戻れるCNNを学習させようと思っている。まずは小手調べとして、直線を走行するためだけのCNNを学習させてみよう。とは言っても外れた場合も戻ってくるように異常状態を考えるといくらでもありそうだ。しかし、ある程度のシチュエーションで白線間に戻れるようにすれば良いかと思うので、そうしようと思う。白線が全く見えない状況で探索する必要は無いということだ。

具体的には、Zybot のカメラで撮影した写真を使って、どの程度の画像サイズでやってみるかを検討してみよう。
カメラで撮影した白線の様子を示す。サムネイルを示すが、真っ直ぐ、右旋回が必要、左旋回が必要な画像に分けてある。
wlt_cnn_5_170723.jpg

画像サイズは 800 x 600 ピクセルだ。なお、白線は塗装コンパネの上に白線を引いてある。すぐに撤去できるようにするためのアイデアなのだが、黄色は結構明るい色なので、心配だ。
これをPaint.net を使用して、試しに加工してみることにした。
wlt_cnn_1_170723.jpg

まずは、20分の1 に縮小してみた。サイズは、40 x 30 だ。
wlt_cnn_2_170723.jpg

白線の引かれている下 1/3 を切り取ってみよう。(画像サイズは 40 x 10 ピクセル)
wlt_cnn_3_170723.jpg

そして、これを白黒画像にする。
wlt_cnn_4_170723.jpg

これで 40 x 10 ピクセルの白線の画像ができた。これでデータセットを作って、学習させてみよう。とりあえず、30枚しかないので、状況を見て、1~数ピクセルを x または y 方向にずらした画像を追加してみよう。あまり画像を回転をすると、曲がらなければならない画像と一致してしまうかもしれない?なかなか難しそうだ。。。
  1. 2017年07月23日 11:19 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

手書き数字認識用畳み込みニューラルネットワーク回路の製作9(Linuxでの動作2)

手書き数字認識用畳み込みニューラルネットワーク回路の製作8(Linuxでの動作1)”の続き。

前回はPYNQボードのDebian 上でカメラ画像を表示するアプリケーションソフトを作成して、カメラ画像を表示した。今回は、PYNQボードのDebian 上でピンクの四角枠を表示して、その中の手書き数字を認識した。

最初に、デバイスツリー・オーバーレイ、クロックの設定、FPGAのコンフィギュレーションを行う。all_settings_bat ファイルを作成した。all_settings_bat ファイルの中身を示す。

./devtov pynq_mnist_cnn
./clock_settings.sh
./fpgamag pynq_fastx_wrapper.bit


まずは、su になる必要があるので、
su コマンドを入力して、ルートのパスワード admin を入れてから、./all_settings_bat を実行して、su から exitする。
hand_draw_num_109_170721.png

./cam_disp を実行して、カメラ画像を表示してから、build ディレクトリに移動して、./pynq_mnist_cnn を実行する。
hand_draw_num_110_170721.png

手書き数字の 4 を認識した。
hand_draw_num_108_170721.jpg

hand_draw_num_107_170720.png

gcc で pynq_mnist_cnn.c をコンパイルすると浮動小数点数演算でのmnist_cnn は約 60.8 ms かかった。
次に、gcc の -O2 オプションを付けたところ、最初は 7 ms 程度だったが、キャッシュがフィルしたであろう 2 回目以降は約 6.13 ms で実行できているので、ハードウェアの2倍速くなった。
hand_draw_num_106_170720.png

ハードウェアの方が遅くなって残念な結果になったが、DSP をほとんど使用していないので、仕方ないのかもしれない?
本格的にリソースを使用するようにチューニングすると、PYNQボードには入らなくなってしまうと思うので、とりあえずは、今のままやっていくことにする。

build ディレクトリの構成を示す。重みやバイアス、Vivado HLS のドライバ・ファイルと pynq_mnist_cnn.c がある。
hand_draw_num_111_170721.png

Makefile を示す。

#Makefile
# Referred to http://www.ie.u-ryukyu.ac.jp/~e085739/c.makefile.tuts.html

PROGRAM = pynq_mnist_cnn
OBJS = pynq_mnist_cnn.o xmnist_conv_nn.o xmnist_conv_nn_linux.o xsquare_frame_gen.o xsquare_frame_gen_linux.o

CC = gcc
CFLAGS = -Wall -O2

.SUFFIXES: .c .o

.PHONY: all

all: pynq_mnist_cnn

pynq_mnist_cnn: $(OBJS)
    $(CC) -Wall -o $@ $(OBJS)
    
.c.o:
    $(CC) $(CFLAGS) -c $<

    
.PHONY: clean
clean:
    $(RM) $(PROGRAM) $(OBJS)


pynq_mnist_cnn.c を貼っておく。

//
// pynq_mnist_cnn.c
// 2017/07/17 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/time.h>

#include "xmnist_conv_nn.h"
#include "xsquare_frame_gen.h"
#include "af1_bias_float.h"
#include "af1_weight_float.h"
#include "af2_bias_float.h"
#include "af2_weight_float.h"
#include "conv1_bias_float.h"
#include "conv1_weight_float.h"

#define HORIZONTAL_PIXELS   800
#define VERTICAL_LINES      600
#define PIXEL_NUM_OF_BYTES  4
#define ALL_DISP_ADDRESS    (HORIZONTAL_PIXELS*VERTICAL_LINES*PIXEL_NUM_OF_BYTES)

int max_int(int out[10]);
int max_float(float out[10]);
int mnist_conv_nn_float(int in[22400], int addr_offset, float out[10]);
float conv_rgb2y_soft(int rgb);

int main()
{
    int fd0, fd3, fd4;
    int fd_udmabuf, fd_paddr;
    volatile unsigned int *axis_switch_0, *axis_switch_1;
    volatile unsigned int *axi_gpio_0;
    volatile unsigned int *frame_buffer;
    unsigned char  attr[1024];
    unsigned long  phys_addr;
    struct termios save_settings;
    struct termios settings;
    int xval, yval;
    int inbyte_in;
    int result_disp = 0;
    unsigned int conv_addr;
    int max_id_float;
    struct timeval start_time, end_time;
    XMnist_conv_nn mcnn;
    XSquare_frame_gen sf_gen;
    int i, res;
    int max_id;
    int result[10];
    float result_float[10];

    // Reffered to http://d.hatena.ne.jp/mFumi/20101002/1286003738
    tcgetattr(0,&save_settings);
    settings = save_settings;

    settings.c_lflag &= ~(ECHO|ICANON);
    settings.c_cc[VTIME] = 0;
    settings.c_cc[VMIN] = 1;
    tcsetattr(0,TCSANOW,&settings);
    fcntl(0,F_SETFL,O_NONBLOCK);

    // axi_gpio_0 (uio0)
    fd0 = open("/dev/uio0", O_RDWR); // axi_iic_0
    if (fd0 < 1){
        fprintf(stderr, "/dev/uio0 (axi_gpio_0) open errorn");
        exit(-1);
    }
    axi_gpio_0 = (volatile unsigned int *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd0, 0);
    if (axi_gpio_0 == MAP_FAILED){
        fprintf(stderr, "axi_gpio_0 mmap errorn");
        exit(-1);
    }
    
    // axis_switch_0 (uio3)
    fd3 = open("/dev/uio3", O_RDWR); // axis_switch_0
    if (fd3 < 1){
        fprintf(stderr, "/dev/uio3 (axis_switch_0) open errorn");
        exit(-1);
    }
    axis_switch_0 = (volatile unsigned int *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd3, 0);
    if (axis_switch_0 == MAP_FAILED){
        fprintf(stderr, "axis_switch_0 mmap errorn");
        exit(-1);
    }
    
    // axis_switch_1 (uio4)
    fd4 = open("/dev/uio4", O_RDWR); // axis_switch_1
    if (fd4 < 1){
        fprintf(stderr, "/dev/uio4 (axis_switch_1) open errorn");
        exit(-1);
    }
    axis_switch_1 = (volatile unsigned int *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd4, 0);
    if (axis_switch_1 == MAP_FAILED){
        fprintf(stderr, "axis_switch_1 mmap errorn");
        exit(-1);
    }
       
    // udmabuf4
    fd_udmabuf = open("/dev/udmabuf4", O_RDWR | O_SYNC); // frame_buffer, The chache is disabled. 
    if (fd_udmabuf == -1){
        fprintf(stderr, "/dev/udmabuf4 open errorn");
        exit(-1);
    }
    frame_buffer = (volatile unsigned int *)mmap(NULL, (ALL_DISP_ADDRESS*3), PROT_READ|PROT_WRITE, MAP_SHARED, fd_udmabuf, 0);
    if (frame_buffer == MAP_FAILED){
        fprintf(stderr, "frame_buffer mmap errorn");
        exit(-1);
    }

    // phys_addr of udmabuf4
    fd_paddr = open("/sys/devices/soc0/amba/amba:udmabuf4/udmabuf/udmabuf4/phys_addr", O_RDONLY);
    if (fd_paddr == -1){
        fprintf(stderr, "/sys/devices/soc0/amba/amba:udmabuf4/udmabuf/udmabuf4/phys_addr open errorn");
        exit(-1);
    }
    read(fd_paddr, attr, 1024);
    sscanf((const char *)attr, "%lx", &phys_addr);  
    close(fd_paddr);
    printf("phys_addr = %x\n", (unsigned int)phys_addr);

    // Mnist_conv_nn, Square_frame_gen Initialize
    if (XMnist_conv_nn_Initialize(&mcnn, "mnist_conv_nn_0") != XST_SUCCESS){
        fprintf(stderr,"mnist_conv_nn_0 open error\n");
        exit(-1);
    }
    if (XSquare_frame_gen_Initialize(&sf_gen, "square_frame_gen_0") != XST_SUCCESS){
        fprintf(stderr,"square_frame_gen_0 open error\n");
        exit(-1);
    }

    // square_frame_gen initialize
    XSquare_frame_gen_Set_x_pos(&sf_gen, HORIZONTAL_PIXELS/2);
    xval = HORIZONTAL_PIXELS/2;
    XSquare_frame_gen_Set_y_pos(&sf_gen, VERTICAL_LINES/2);
    yval = VERTICAL_LINES/2;
    XSquare_frame_gen_Set_width(&sf_gen, 28);
    XSquare_frame_gen_Set_height(&sf_gen, 28);
    XSquare_frame_gen_Set_off_on(&sf_gen, 1); // on

    // XSquare_frame_gen start
    XSquare_frame_gen_DisableAutoRestart(&sf_gen);
    while(!XSquare_frame_gen_IsIdle(&sf_gen)) ;
    XSquare_frame_gen_Start(&sf_gen);
    XSquare_frame_gen_EnableAutoRestart(&sf_gen);

    // mnist_conv_nn initialize
    XMnist_conv_nn_Set_addr_offset(&mcnn, HORIZONTAL_PIXELS/2);
    XMnist_conv_nn_Set_in_r(&mcnn, (unsigned int)phys_addr+HORIZONTAL_PIXELS*(VERTICAL_LINES/2)*sizeof(int));
 
    // axis_switch_1, 1to2 ,Select M00_AXIS
    // Refer to http://marsee101.blog19.fc2.com/blog-entry-3177.html
    axis_switch_1[16] = 0x80000000// 0x40 = 0x80000000, disable
    axis_switch_1[17] = 0x0// 0x44 = 0
    axis_switch_1[0] = 0x2// Comit registers
    
    // axis_switch_0, 2to1, Select S00_AXIS
    // Refer to http://marsee101.blog19.fc2.com/blog-entry-3177.html
    axis_switch_0[16] = 0x1// 0x40 = 1;
    axis_switch_0[0] = 0x2// Comit registers
  
    axi_gpio_0[0] = 0// LED 0 clear

    printf("mnist_conv_nn_test, <h> : left, <k> : up, <j> : down, <l> : right, <r> : result, <d> : help, <q> : exit\n");
    
    while(1){   // main loop
        inbyte_in = getchar(); fflush(stdin);
        if (inbyte_in != EOF){
            if (inbyte_in == 'q')
                break;
            else if (inbyte_in < 'A' || inbyte_in > 'z')
                usleep(10000);
        }
        switch(inbyte_in) {
            case 'h' : // left
            case 'H' : // left -5
                if(inbyte_in == 'h' && xval > 0)
                    --xval;
                else if(inbyte_in == 'H' && xval >= 5)
                    xval -= 5;
                XSquare_frame_gen_Set_x_pos(&sf_gen, xval);
                XMnist_conv_nn_Set_addr_offset(&mcnn, xval);
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'l' : // right
            case 'L' : // right +5
                if(inbyte_in == 'l' && xval < HORIZONTAL_PIXELS-28)
                    xval++;
                else if(inbyte_in == 'L' && xval <= HORIZONTAL_PIXELS-28-5)
                    xval += 5;
                XSquare_frame_gen_Set_x_pos(&sf_gen, xval);
                XMnist_conv_nn_Set_addr_offset(&mcnn, xval);
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'k' : // up
            case 'K' : // up -5
                if(inbyte_in == 'k' && yval > 0)
                    --yval;
                else if(inbyte_in == 'K' && yval >= 5)
                    yval -= 5;
                XSquare_frame_gen_Set_y_pos(&sf_gen, yval);
                XMnist_conv_nn_Set_in_r(&mcnn, (unsigned int)phys_addr+HORIZONTAL_PIXELS*yval*sizeof(int));
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'j' : // down
            case 'J' : // down +5
                if(inbyte_in == 'j' && yval < VERTICAL_LINES-28)
                    yval++;
                else if(inbyte_in == 'J' && yval <= VERTICAL_LINES-28-5)
                    yval += 5;
                XSquare_frame_gen_Set_y_pos(&sf_gen, yval);
                XMnist_conv_nn_Set_in_r(&mcnn, (unsigned int)phys_addr+HORIZONTAL_PIXELS*yval*sizeof(int));
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'r' : // result check
                result_disp = 1;
                break;
            case 'd' : // help
            case 'D' :
                printf("mnist_conv_nn_test, <h> : left, <k> : up, <j> : down, <l> : right, <r> : result, <d> : help, <q> : exit\n");
                break;
            case 'q' : // exit
                return(0);
        }

        if(result_disp){
            printf("\nHardware\n");
            // XMnist_conv_nn start
            XMnist_conv_nn_DisableAutoRestart(&mcnn);
            while(!XMnist_conv_nn_IsIdle(&mcnn));
            gettimeofday(&start_time, NULL);
            XMnist_conv_nn_Start(&mcnn);
            while(!XMnist_conv_nn_IsIdle(&mcnn));
            gettimeofday(&end_time, NULL);
            if (end_time.tv_usec < start_time.tv_usec) {
                printf("conv_time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec - 11000000 + end_time.tv_usec - start_time.tv_usec);
            } else {
                printf("conv_time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec, end_time.tv_usec - start_time.tv_usec);
            }

            // mnist cnn result check
            for(i=0; i<5; i++){
                XMnist_conv_nn_Read_out_V_Words(&mcnn, i, &res, 1);
                result[i*2] = res & 0x0fff;
                if(result[i*2] & 0x800// minus
                    result[i*2] = 0xfffff000 | result[i*2]; // Sign extension

                result[i*2+1] = (res & 0x0fff0000) >> 16;
                if(result[i*2+1] & 0x800// minus
                    result[i*2+1] = 0xfffff000 | result[i*2+1]; // Sign extension
            }

            max_id = max_int(result);
            axi_gpio_0[0] = max_id;

            for(i=0; i<10; i++){
                printf("result[%d] = %x\n", i, result[i]);
            }
            printf("max_id = %d\n", max_id);

            printf("\nSoftware\n");
            conv_addr = (unsigned int)frame_buffer+HORIZONTAL_PIXELS*yval*sizeof(int);
            gettimeofday(&start_time, NULL);
            mnist_conv_nn_float((int *)conv_addr, xval, result_float);
            gettimeofday(&end_time, NULL);
            max_id_float = max_float(result_float);

           if (end_time.tv_usec < start_time.tv_usec) {
                printf("conv_time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec - 11000000 + end_time.tv_usec - start_time.tv_usec);
            } else {
                printf("conv_time = %ld.%06ld sec\n", end_time.tv_sec - start_time.tv_sec, end_time.tv_usec - start_time.tv_usec);
            }

            for(i=0; i<10; i++){
                printf("result_float[%d] = %f\n", i, result_float[i]);
            }
            printf("max_id_float = %d\n", max_id_float);
            printf("\n");

            result_disp = 0;
        }
    }
            
    munmap((void *)axi_gpio_0, 0x10000);
    munmap((void *)axis_switch_0, 0x10000);
    munmap((void *)axis_switch_1, 0x10000);
    munmap((void *)frame_buffer, (ALL_DISP_ADDRESS*3));
    
    close(fd0);
    close(fd3);
    close(fd4);
    close(fd_udmabuf);
    
    // Reffered to http://d.hatena.ne.jp/mFumi/20101002/1286003738
    tcsetattr(0,TCSANOW,&save_settings);
    
    return(0);
}

int max_int(int out[10]){
    int max_id;
    int max = 0, i;

    for(i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

int mnist_conv_nn_float(int in[22400], int addr_offset, float out[10]){
    
    int i, j, k, m, n, col, row;
    float buf[28][28];
    float conv_out[10][24][24];
    float pool_out[10][12][12];
    float dot1[100];
    float dot2[10];
   
    // 
    /*for (i=0; i<28; i++){        for (j=0; j<800; j++){            if (j>=addr_offset && j<addr_offset+28)                printf("%2x, ", (int)(conv_rgb2y_soft(in[i*800+j])*256.0));        }        printf("\n");    } */
    for(i=0; i<28; i++){
        for(j=0; j<800; j++){
            if (j>=addr_offset && j<addr_offset+28){
                buf[i][j-addr_offset] = (float)0.99609375 - (float)conv_rgb2y_soft(in[i*800+j]);
            }
        }
    }

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    for(i=0; i<10; i++){    //
        for(j=0; j<24; j++){
            for(k=0; k<24; k++){
                conv_out[i][j][k] = 0;
                for(m=0; m<5; m++){
                    for(n=0; n<5; n++){
                        conv_out[i][j][k] += buf[j+m][k+n] * conv1_fweight[i][0][m][n];
                    }
                }
                conv_out[i][j][k] += conv1_fbias[i];

                if(conv_out[i][j][k]<0// ReLU
                    conv_out[i][j][k] = 0;
            }
        }
    }

    // Pooling Kernel = 2 x 2, Stride = 2
    for(i=0; i<10; i++){
        for(j=0; j<24; j += 2){
            for(k=0; k<24; k += 2){
                for(m=0; m<2; m++){
                    for(n=0; n<2; n++){
                        if(m==0 && n==0){
                            pool_out[i][j/2][k/2] = conv_out[i][j][k];
                        } else if(pool_out[i][j/2][k/2] < conv_out[i][j+m][k+n]){
                            pool_out[i][j/2][k/2] = conv_out[i][j+m][k+n];
                        }
                    }
                }
            }
        }
    }

    for(col=0; col<100; col++){
        dot1[col] = 0;
        for(i=0; i<10; i++){
            for(j=0; j<12; j++){
                for(k=0; k<12; k++){
                    dot1[col] += pool_out[i][j][k]*af1_fweight[i*12*12+j*12+k][col];
                }
            }
        }
        dot1[col] += af1_fbias[col];

        if(dot1[col] < 0)   // ReLU
            dot1[col] = 0;
    }

    for(col=0; col<10; col++){
        dot2[col] = 0;
        for(row=0; row<100; row++){
            dot2[col] += dot1[row]*af2_fweight[row][col];
        }
        dot2[col] += af2_fbias[col];

        out[col] = dot2[col];
    }

    return(0);
}

int max_float(float out[10]){
    int max_id, i;
    float max = 0;

    for(i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

float conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;
    float y_float;

    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;
    y = y_f >> 8// /256

    if (y >= 256)
        y = 255;

    y_float = (float)y/256.0;

    return(y_float);
}

  1. 2017年07月21日 04:20 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

手書き数字認識用畳み込みニューラルネットワーク回路の製作8(Linuxでの動作1)

手書き数字認識用畳み込みニューラルネットワーク回路の製作7(ハードとソフトの比較)”の続き。

前回はSDKを使用してベアメタル・アプリケーションでハードウェアでのMNIST手書き数字認識とソフトウェアでのMNIST手書き数字認識の実行時間を比較した。今回は、それをLinuxのアプリケーションソフトを作成してやってみようと思う。まずは、カメラ画像の表示を目指す。

なお、PYNQボードにikwzmさんのLinux システムを載せて、デバイスツリー・オーバーロード、FPGA Manager を使用して、動作させることを目指す。
過去にやったことを思い出しながらやってみよう。このためにブログのまとめをやったようなものだ。
FPGAの部屋のWeb サイトの「PYNQ」の「”FPGA+SoC+Linux+Device Tree Overlay+FPGA Manager(PYNQ-Z1対応)”を試してみる1(FPGA-SoC-Linux のクローン)」からのブログを参考にやってみた。ほとんど「
”FPGA+SoC+Linux+Device Tree Overlay+FPGA Manager(PYNQ-Z1対応)”を使用してカメラの画像を表示」を見ながらやってみた。
最初にデバイスツリーのpynq_mnist_cnn.dts を作成した。pynq_mnist_cnn.dts を示す。

/dts-v1/;
/ {
    fragment@0 {
        target-path = "/amba";
        __overlay__ {
            #address-cells = <0x1>;
            #size-cells = <0x1>;
            axi_gpio_0@41200000 {
                compatible = "generic-uio";
                reg = <0x41200000 0x10000>;
                #interrupts = <0x0 0x1d 0x4>;
            };
            axi_iic_0@41600000 {
                compatible = "generic-uio";
                reg = <0x41600000 0x10000>;
                #interrupts = <0x0 0x1d 0x4>;
            };
            axi_vdma_0@43000000 {
                compatible = "generic-uio";
                reg = <0x43000000 0x10000>;
                #interrupts = <0x0 0x1d 0x4>;
            };
            axis_switch_0@43C10000 {
                compatible = "generic-uio";
                reg = <0x43C10000 0x10000>;
                #interrupts = <0x0 0x1d 0x4>;
            };
            axis_switch_1@43C20000 {
                compatible = "generic-uio";
                reg = <0x43C20000 0x10000>;
                #interrupts = <0x0 0x1d 0x4>;
            };
            bitmap_disp_cntrler_axi_master_0@43C00000 {
                compatible = "generic-uio";
                reg = <0x43C00000 0x10000>;
                #interrupts = <0x0 0x1d 0x4>;
            };
            mnist_conv_nn_0@43C50000 {
                compatible = "generic-uio";
                reg = <0x43C50000 0x10000>;
                #interrupts = <0x0 0x1d 0x4>;
            };
            mt9d111_inf_axis_0@43C40000 {
                compatible = "generic-uio";
                reg = <0x43C40000 0x10000>;
                #interrupts = <0x0 0x1d 0x4>;
            };
            square_frame_gen_0@43C30000 {
                compatible = "generic-uio";
                reg = <0x43C30000 0x10000>;
                #interrupts = <0x0 0x1d 0x4>;
            };
            udmabuf4 {
                compatible = "ikwzm,udmabuf-0.10.a";
                minor-number = <4>;
                size = <0x00600000>;
            };
            fclk0 {
                compatible  = "ikwzm,fclkcfg-0.10.a";
                clocks      = <1 15>;
            };
            fclk1 {
                compatible  = "ikwzm,fclkcfg-0.10.a";
                clocks      = <1 16>;
            };
            fclk2 {
                compatible  = "ikwzm,fclkcfg-0.10.a";
                clocks      = <1 17>;
            };
        };
    };
};


pynq_mnist_cnn.dts をコンパイルして pynq_mnist_cnn.dtbo を生成しよう。
dtc -I dts -O dtb -o pynq_mnist_cnn.dtbo pynq_mnist_cnn.dts
pynq_mnist_cnn.dtbo が生成できたので、スーパーユーザーになってからdevtov コマンドでデバイスツリー・オーバーレイでデバイスツリーを設定しよう。(devtov, fpgamag, rmdevtov はこのディレクトリにコピーしてあります)
su
./devtov pynq_mnist_cnn

hand_draw_num_101_170716.png

次にクロックの周波数を設定する clock_settings.sh を作成した。なお、fclk0はデフォルトで100MHzに設定されているので、そのままとした。つまり設定は行っていない。

#!/bin/sh
# clock_settings.sh

echo 25000000 > /sys/class/fclkcfg/fclk1/rate
cat /sys/class/fclkcfg/fclk1/rate

echo 72000000 > /sys/class/fclkcfg/fclk2/rate
cat /sys/class/fclkcfg/fclk2/rate


これをスーパーユーザー・モードで実行した。
hand_draw_num_102_170716.png

fpgamag コマンドでビット・ファイルをPYNQのZynq にダウンロードする。
./fpgamag pynq_fastx_wrapper.bit
実行するとDONE ランプが点灯した。
hand_draw_num_103_170716.png

”FPGA+SoC+Linux+Device Tree Overlay+FPGA Manager(PYNQ-Z1対応)”を使用してカメラの画像を表示」の cam_disp.c を改造してコンパイルし、実行したところ、ディスプレイにカメラ画像を表示することができた。
exit
gcc -o cam_disp cam_disp.c
./cam_disp

hand_draw_num_104_170716.png

hand_draw_num_105_170716.jpg

cam_disp.c を示す。

// cam_disp.c
// 2017/07/15 by marsee
//
// cam_disp for pynq_mnist_cnn
//

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/mman.h>
#include <fcntl.h>

#define NUMBER_OF_WRITE_FRAMES    3 // Note: If not at least 3 or more, the image is not displayed in succession.

#define HORIZONTAL_PIXELS    800
#define VERTICAL_LINES        600
#define PIXEL_NUM_OF_BYTES    4
#define ALL_DISP_ADDRESS    (HORIZONTAL_PIXELS*VERTICAL_LINES*PIXEL_NUM_OF_BYTES)

#define FASTX_THRESHOLD        20

void cam_i2c_init(volatile unsigned *mt9d111_i2c_axi_lites) {
    mt9d111_i2c_axi_lites[64] = 0x2// reset tx fifo ,address is 0x100, i2c_control_reg
    mt9d111_i2c_axi_lites[64] = 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(volatile unsigned *mt9d111_i2c_axi_lites, unsigned int device_addr, unsigned int write_addr, unsigned int write_data){
    mt9d111_i2c_axi_lites[66] = 0x100 | (device_addr & 0xfe);   // Slave IIC Write Address, address is 0x108, i2c_tx_fifo
    mt9d111_i2c_axi_lites[66] = write_addr;
    mt9d111_i2c_axi_lites[66] = (write_data >> 8)|0xff;         // first data
    mt9d111_i2c_axi_lites[66] = 0x200 | (write_data & 0xff);        // second data
    cam_i2x_write_sync();
}

int main()
{
    int fd1, fd2, fd3, fd4, fd5, fd7;
    int fd_udmabuf, fd_paddr;
    volatile unsigned int *axi_iic_0, *axi_vdma_0, *axis_switch_0, *axis_switch_1;
    volatile unsigned int *bitmap_disp_cntrler_axim_0, *fastx_corner_det_0;
    volatile unsigned int *mt9d111_inf_axis_0;
    volatile unsigned int *frame_buffer;
    unsigned char  attr[1024];
    unsigned long  phys_addr;

    // axi_iic_0 (uio1)
    fd1 = open("/dev/uio1", O_RDWR); // axi_iic_0
    if (fd1 < 1){
        fprintf(stderr, "/dev/uio1 (axi_iic_0) open errorn");
        exit(-1);
    }
    axi_iic_0 = (volatile unsigned int *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0);
    if (axi_iic_0 == MAP_FAILED){
        fprintf(stderr, "axi_iic_0 mmap errorn");
        exit(-1);
    }
    
    // axi_vdma_0 (uio2)
    fd2 = open("/dev/uio2", O_RDWR); // axi_vdma_0
    if (fd2 < 1){
        fprintf(stderr, "/dev/uio2 (axi_vdma_0) open errorn");
        exit(-1);
    }
    axi_vdma_0 = (volatile unsigned int *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd2, 0);
    if (axi_vdma_0 == MAP_FAILED){
        fprintf(stderr, "axi_vdma_0 mmap errorn");
        exit(-1);
    }

    // axis_switch_0 (uio3)
    fd3 = open("/dev/uio3", O_RDWR); // axis_switch_0
    if (fd3 < 1){
        fprintf(stderr, "/dev/uio3 (axis_switch_0) open errorn");
        exit(-1);
    }
    axis_switch_0 = (volatile unsigned int *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd3, 0);
    if (axis_switch_0 == MAP_FAILED){
        fprintf(stderr, "axis_switch_0 mmap errorn");
        exit(-1);
    }
    
    // axis_switch_1 (uio4)
    fd4 = open("/dev/uio4", O_RDWR); // axis_switch_1
    if (fd4 < 1){
        fprintf(stderr, "/dev/uio4 (axis_switch_1) open errorn");
        exit(-1);
    }
    axis_switch_1 = (volatile unsigned int *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd4, 0);
    if (axis_switch_1 == MAP_FAILED){
        fprintf(stderr, "axis_switch_1 mmap errorn");
        exit(-1);
    }
    
    // bitmap_disp_cntrler_axim_0 (uio5)
    fd5 = open("/dev/uio5", O_RDWR); // bitmap_disp_cntrler_axim_0
    if (fd5 < 1){
        fprintf(stderr, "/dev/uio5 (bitmap_disp_cntrler_axim_0) open errorn");
        exit(-1);
    }
    bitmap_disp_cntrler_axim_0 = (volatile unsigned int *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd5, 0);
    if (bitmap_disp_cntrler_axim_0 == MAP_FAILED){
        fprintf(stderr, "bitmap_disp_cntrler_axim_0 mmap errorn");
        exit(-1);
    }
    
    // mt9d111_inf_axis_0 (uio7)
    fd7 = open("/dev/uio7", O_RDWR); // mt9d111_inf_axis_0
    if (fd7 < 1){
        fprintf(stderr, "/dev/uio7 (mt9d111_inf_axis_0) open errorn");
        exit(-1);
    }
    mt9d111_inf_axis_0 = (volatile unsigned int *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd7, 0);
    if (mt9d111_inf_axis_0 == MAP_FAILED){
        fprintf(stderr, "mt9d111_inf_axis_0 mmap errorn");
        exit(-1);
    }
    
    // udmabuf4
    fd_udmabuf = open("/dev/udmabuf4", O_RDWR | O_SYNC); // frame_buffer, The chache is disabled. 
    if (fd_udmabuf == -1){
        fprintf(stderr, "/dev/udmabuf4 open errorn");
        exit(-1);
    }
    frame_buffer = (volatile unsigned int *)mmap(NULL, (ALL_DISP_ADDRESS*3), PROT_READ|PROT_WRITE, MAP_SHARED, fd_udmabuf, 0);
    if (frame_buffer == MAP_FAILED){
        fprintf(stderr, "frame_buffer mmap errorn");
        exit(-1);
    }

    // phys_addr of udmabuf4
    fd_paddr = open("/sys/devices/soc0/amba/amba:udmabuf4/udmabuf/udmabuf4/phys_addr", O_RDONLY);
    if (fd_paddr == -1){
        fprintf(stderr, "/sys/devices/soc0/amba/amba:udmabuf4/udmabuf/udmabuf4/phys_addr open errorn");
        exit(-1);
    }
    read(fd_paddr, attr, 1024);
    sscanf(attr, "%lx", &phys_addr);  
    close(fd_paddr);
    printf("phys_addr = %x\n", (unsigned int)phys_addr);
    
    // axis_switch_1, 1to2 ,Select M00_AXIS
    // Refer to http://marsee101.blog19.fc2.com/blog-entry-3177.html
    axis_switch_1[16] = 0x0// 0x40 = 0
    axis_switch_1[17] = 0x80000000// 0x44 = 0x80000000, disable
    axis_switch_1[18] = 0x80000000// 0x48 = 0x80000000, disable
    axis_switch_1[19] = 0x80000000// 0x4C = 0x80000000, disable
    axis_switch_1[0] = 0x2// Comit registers
    
    // axis_switch_0, 2to1, Select S00_AXIS
    // Refer to http://marsee101.blog19.fc2.com/blog-entry-3177.html
    axis_switch_0[16] = 0x0// 0x40 = 0;
    axis_switch_0[0] = 0x2// Comit registers

    // AXI VDMA Initialization sequence (axi_vdma_0)
    axi_vdma_0[12] = 0x4// S2MM_VDMACR (S2MM VDMA Control Register  Offset 30h) is 0x4 
    while ((axi_vdma_0[12] & 0x4) == 0x4) ; // Reset is progress
    axi_vdma_0[12] = 0x4// S2MM_VDMACR (S2MM VDMA Control Register  Offset 30h) is 0x4 
    while ((axi_vdma_0[12] & 0x4) == 0x4) ; // Reset is progress
    axi_vdma_0[18] = NUMBER_OF_WRITE_FRAMES; // S2MM_FRMSTORE (0x48) register
    axi_vdma_0[12] = 0x00010002// S2MM_VDMACR(IRQFrameCount = 0x1, Circular_Park = 1)
    axi_vdma_0[41] = HORIZONTAL_PIXELS*PIXEL_NUM_OF_BYTES; // S2MM Horizontal Size Register(S2MM_HSIZE)0xc80 = 3200dec = 800 x 4
    axi_vdma_0[42] = HORIZONTAL_PIXELS*PIXEL_NUM_OF_BYTES; // S2MM Frame Delay and Stride Register(S2MM_FRMDLY_STRIDE)0xc80 = 3200dec = 800 x 4
    axi_vdma_0[43] = (unsigned)phys_addr; // S2MM Start Address (1 to 16) Start Address 1
    axi_vdma_0[44] = (unsigned)phys_addr; // S2MM Start Address (1 to 16) Start Address 2
    axi_vdma_0[45] = (unsigned)phys_addr; // S2MM Start Address (1 to 16) Start Address 3
    axi_vdma_0[12] = 0x00010003// S2MM_VDMACR(IRQFrameCount = 0x1, Circular_Park = 1, Run/stop = 1)
    while((axi_vdma_0[13] & 0x1) == 0x1) ; // Halt? (S2MM_VDMASR 0x34)
    axi_vdma_0[40] = VERTICAL_LINES; // S2MM Vertical Size (S2MM_VSIZE  Offset 0xA0) 0x258 = 600dec

    // CMOS Camera initialize, MT9D111
    cam_i2c_init(axi_iic_0);
    
    cam_i2c_write(axi_iic_0, 0xba, 0xf00x1);        // Changed regster map to IFP page 1
    cam_i2c_write(axi_iic_0, 0xba, 0x970x20);    // RGB Mode, RGB565

    mt9d111_inf_axis_0[1] = 0;
    mt9d111_inf_axis_0[0] = (unsigned int)phys_addr;

    bitmap_disp_cntrler_axim_0[0] = (unsigned int)phys_addr;
        
    munmap((void *)axi_iic_0, 0x10000);
    munmap((void *)axi_vdma_0, 0x10000);
    munmap((void *)axis_switch_0, 0x10000);
    munmap((void *)axis_switch_1, 0x10000);
    munmap((void *)bitmap_disp_cntrler_axim_0, 0x10000);
    munmap((void *)mt9d111_inf_axis_0, 0x10000);
    munmap((void *)frame_buffer, (ALL_DISP_ADDRESS*3));
    
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    close(fd5);
    close(fd7);
    close(fd_udmabuf);
}

  1. 2017年07月16日 04:00 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

手書き数字認識用畳み込みニューラルネットワーク回路の製作7(ハードとソフトの比較)

手書き数字認識用畳み込みニューラルネットワーク回路の製作6(PYNQボードで動作確認)”の続き。

前回は、mnist_conv_nn_test.c を作成して、手書き数字を認識してみたところ、今のところ 100 % の精度で手書き数字を認識することができた。今回は、ハードウェアのMNISTの手書き数字を認識する時間とソフトウェアでMNISTの手書き数字を認識する時間を比較してみようと思う。

ハードウェアの手書き数字を認識時間とソフトウェアの手書き数字を認識時間を比べるためにmnist_conv_soft_test.c を作成した。ソフトウェアの手書き数字の認識は、float で演算している。更に、カーソルを動かすのが面倒なため、それぞれの方向キーをシフトキーを押したときに、5ピクセルずつ移動できるように改良した。なお、時間の計測には、”SDKのベアメタル・アプリケーションで時間を計測する方法(Zynq)”で試したXTime_GetTime() を使用している。
hand_draw_num_100_170708.png

mnist_conv_soft_test.elf を起動して、1 を認識してみた。
hand_draw_num_90_170706.png

ハードウェアの実行時間は 12.9 ms 程度、ソフトウェアの実行時間は 34.2 ms 程度だった。ハードウェアの方がソフトウェアより、約 2.66 倍速い。

2 を認識してみた。
hand_draw_num_91_170706.png

3 を認識してみた。
hand_draw_num_92_170706.png

4 を認識してみた。
hand_draw_num_93_170706.png

5 を認識してみた。
hand_draw_num_94_170706.png

6 を認識してみた。
hand_draw_num_95_170706.png

7 を認識してみた。
hand_draw_num_96_170706.png

8 を認識してみた。
hand_draw_num_97_170706.png

9 を認識してみた。
hand_draw_num_98_170706.png

0 を認識してみた。
hand_draw_num_99_170706.png

ハードウェアの実行時間はほとんど変動はない。ソフトウェアの実行時間にもそれほど変動はないので、約 2.7 倍ハードウェアの方が速いという結果だった。ハードウェア、ソフトウェア共に精度は今のところ 100 % だった。
ソフトウェアの方が float というハンデはあるが、ハードウェアの方が速かった。だが、ソフトウェアもチューニングするとどうなるかわからないと思う。まあ、ハードウェアにオフロードして、仕様を満足できたので良かったと思う。

最後に、mnist_conv_soft_test.c を貼っておく。
なお、

float buf[28][28];
float conv_out[10][24][24];
float pool_out[10][12][12];
float dot1[100];
float dot2[10];

をグローバル変数にしたのは、ローカル変数にしていては動作がおかしかったからだ。スタックに積むことができる大きさに制限があるのかもしれない?

/* * mnist_conv_soft_test.c * *  Created on: 2017/07/06 *      Author: ono */

#include <stdio.h>
#include <stdlib.h>
#include "xaxivdma.h"
#include "xil_io.h"
#include "xparameters.h"
#include "sleep.h"
#include "xgpio.h"
#include "xtime_l.h"

#include "xmnist_conv_nn.h"
#include "xsquare_frame_gen.h"
#include "af1_bias_float.h"
#include "af1_weight_float.h"
#include "af2_bias_float.h"
#include "af2_weight_float.h"
#include "conv1_bias_float.h"
#include "conv1_weight_float.h"

#define FRAME_BUFFER_ADDRESS 0x10000000
#define NUMBER_OF_WRITE_FRAMES    3 // Note: If not at least 3 or more, the image is not displayed in succession.

#define HORIZONTAL_PIXELS    800
#define VERTICAL_LINES        600
#define PIXEL_NUM_OF_BYTES    4

int max_int(int out[10]);
int max_float(float out[10]);
int mnist_conv_nn_float(int in[22400], int addr_offset, float out[10]);
float conv_rgb2y_soft(int rgb);

static XAxiVdma_DmaSetup Vdma0_WriteCfg;
float buf[28][28];
float conv_out[10][24][24];
float pool_out[10][12][12];
float dot1[100];
float dot2[10];

void cam_i2c_init(volatile unsigned *mt9d111_i2c_axi_lites) {
    mt9d111_i2c_axi_lites[64] = 0x2// reset tx fifo ,address is 0x100, i2c_control_reg
    mt9d111_i2c_axi_lites[64] = 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(volatile unsigned *mt9d111_i2c_axi_lites, unsigned int device_addr, unsigned int write_addr, unsigned int write_data){
    mt9d111_i2c_axi_lites[66] = 0x100 | (device_addr & 0xfe);   // Slave IIC Write Address, address is 0x108, i2c_tx_fifo
    mt9d111_i2c_axi_lites[66] = write_addr;
    mt9d111_i2c_axi_lites[66] = (write_data >> 8)|0xff;         // first data
    mt9d111_i2c_axi_lites[66] = 0x200 | (write_data & 0xff);        // second data
    cam_i2x_write_sync();
}

int main(){
    XMnist_conv_nn mcnn;
    XSquare_frame_gen sf_gen;
    int inbyte_in;
    int xval, yval;
    int i, res;
    int result[10];
    float result_float[10];
    static XGpio GPIOInstance_Ptr;
    int XGpio_Status;
    int max_id;
    XAxiVdma_Config *XAxiVdma0_Config;
    XAxiVdma XAxiVdma0;
    int XAxiVdma0_Status;
    int result_disp = 0;
    int conv_addr;
    int max_id_float;
    XTime start_time, end_time;

    // AXI VDMA Initialization sequence
    XAxiVdma0_Config = XAxiVdma_LookupConfig(XPAR_CAMERA_INTERFACE_AXI_VDMA_0_DEVICE_ID); // Look up the hardware configuration for a device instance
    if (XAxiVdma0_Config == NULL){
        fprintf(stderr, "No AXI VDMA found\n");
        return(-1);
    }

    XAxiVdma0_Status = XAxiVdma_CfgInitialize(&XAxiVdma0, XAxiVdma0_Config, XAxiVdma0_Config->BaseAddress); // Initialize the driver with hardware configuration
    if (XAxiVdma0_Status != XST_SUCCESS){
        fprintf(stderr, "XAxiVdma_CfgInitialize() failed\n");
        return(-1);
    }

    XAxiVdma_Reset(&XAxiVdma0, XAXIVDMA_WRITE);
    while(XAxiVdma_ResetNotDone(&XAxiVdma0, XAXIVDMA_WRITE)) ;

    XAxiVdma0_Status = XAxiVdma_SetFrmStore(&XAxiVdma0, NUMBER_OF_WRITE_FRAMES, XAXIVDMA_WRITE); // Set the number of frame store buffers to use.

    Vdma0_WriteCfg.VertSizeInput = VERTICAL_LINES;
    Vdma0_WriteCfg.HoriSizeInput = HORIZONTAL_PIXELS * PIXEL_NUM_OF_BYTES;
    Vdma0_WriteCfg.Stride = HORIZONTAL_PIXELS * PIXEL_NUM_OF_BYTES; // Indicates the number of address bytes between the first pixels of each video line.
    Vdma0_WriteCfg.FrameDelay = 0// Indicates the minimum number of frame buffers the Genlock slave is to be behind the locked master. This field is only used if the channel is enabled for Genlock Slave operations. This field has no meaning in other Genlock modes.
    Vdma0_WriteCfg.EnableCircularBuf = 1// Indicates frame buffer Circular mode or frame buffer Park mode.  1 = Circular Mode Engine continuously circles through frame buffers.
    Vdma0_WriteCfg.EnableSync = 0// Enables Genlock or Dynamic Genlock Synchronization. 0 = Genlock or Dynamic Genlock Synchronization disabled.
    Vdma0_WriteCfg.PointNum = 0// No Gen-Lock
    Vdma0_WriteCfg.EnableFrameCounter = 0// Endless transfers
    Vdma0_WriteCfg.FixedFrameStoreAddr = 0// We are not doing parking

    XAxiVdma0_Status = XAxiVdma_DmaConfig(&XAxiVdma0, XAXIVDMA_WRITE, &Vdma0_WriteCfg);
    if (XAxiVdma0_Status != XST_SUCCESS){
        fprintf(stderr, "XAxiVdma_DmaConfig() failed\n");
        return(-1);
    }

    // Frame buffer address set
    unsigned int frame_addr = (unsigned int)FRAME_BUFFER_ADDRESS;
    for (i=0; i<NUMBER_OF_WRITE_FRAMES; i++){
        Vdma0_WriteCfg.FrameStoreStartAddr[i] = frame_addr;
        //frame_addr += HORIZONTAL_PIXELS * PIXEL_NUM_OF_BYTES * VERTICAL_LINES;
    }

    XAxiVdma0_Status = XAxiVdma_DmaSetBufferAddr(&XAxiVdma0, XAXIVDMA_WRITE, Vdma0_WriteCfg.FrameStoreStartAddr);
    if (XAxiVdma0_Status != XST_SUCCESS){
        fprintf(stderr, "XAxiVdma_DmaSetBufferAddr() failed\n");
        return(-1);
    }

    // Mnist_conv_nn, Square_frame_gen Initialize
    XMnist_conv_nn_Initialize(&mcnn, 0);
    XSquare_frame_gen_Initialize(&sf_gen, 0);

    // square_frame_gen initialize
    XSquare_frame_gen_Set_x_pos(&sf_gen, HORIZONTAL_PIXELS/2);
    xval = HORIZONTAL_PIXELS/2;
    XSquare_frame_gen_Set_y_pos(&sf_gen, VERTICAL_LINES/2);
    yval = VERTICAL_LINES/2;
    XSquare_frame_gen_Set_width(&sf_gen, 28);
    XSquare_frame_gen_Set_height(&sf_gen, 28);
    XSquare_frame_gen_Set_off_on(&sf_gen, 1); // on

    // XSquare_frame_gen start
    XSquare_frame_gen_DisableAutoRestart(&sf_gen);
    while(!XSquare_frame_gen_IsIdle(&sf_gen)) ;
    XSquare_frame_gen_Start(&sf_gen);
    XSquare_frame_gen_EnableAutoRestart(&sf_gen);

    // mnist_conv_nn initialize
    XMnist_conv_nn_Set_addr_offset(&mcnn, HORIZONTAL_PIXELS/2);
    XMnist_conv_nn_Set_in_r(&mcnn, FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*(VERTICAL_LINES/2)*sizeof(int));

    // axis_switch_1, 1to2 ,Select M00_AXIS
    // Refer to http://marsee101.blog19.fc2.com/blog-entry-3177.html
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_1_BASEADDR+0x40), 0x80000000); // disable
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_1_BASEADDR+0x44), 0x0); // square_frame_gen enable
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_1_BASEADDR), 0x2); // Commit registers

    // axis_switch_0, 2to1, Select S00_AXIS
    // Refer to http://marsee101.blog19.fc2.com/blog-entry-3177.html
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_0_BASEADDR+0x40), 0x1);
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_0_BASEADDR), 0x2); // Commit registers

    // VDMA start
    XAxiVdma0_Status = XAxiVdma_DmaStart(&XAxiVdma0, XAXIVDMA_WRITE);
    if (XAxiVdma0_Status != XST_SUCCESS){
        fprintf(stderr, "XAxiVdma_DmaStart() failed\n");
        return(-1);
    }

    // mt9d111_inf_axis_0, axi_iic_0, bitmap_disp_cntrler_axi_master_0
    volatile unsigned int *bmdc_axi_lites;
    volatile unsigned int *mt9d111_axi_lites;
    volatile unsigned int *mt9d111_i2c_axi_lites;

    bmdc_axi_lites = (volatile unsigned *)XPAR_BITMAP_DISP_CNTRLER_AXI_MASTER_0_BASEADDR;
    mt9d111_axi_lites = (volatile unsigned *)XPAR_CAMERA_INTERFACE_MT9D111_INF_AXIS_0_BASEADDR;
    mt9d111_i2c_axi_lites = (volatile unsigned *)XPAR_CAMERA_INTERFACE_AXI_IIC_0_BASEADDR;

    bmdc_axi_lites[0] = (volatile unsigned int)FRAME_BUFFER_ADDRESS; // Bitmap Display Controller start
    mt9d111_axi_lites[0] = (volatile unsigned int)FRAME_BUFFER_ADDRESS; // Camera Interface start (Address is dummy)

    // CMOS Camera initialize, MT9D111
    cam_i2c_init(mt9d111_i2c_axi_lites);

    cam_i2c_write(mt9d111_i2c_axi_lites, 0xba, 0xf00x1);      // Changed regster map to IFP page 1
    cam_i2c_write(mt9d111_i2c_axi_lites, 0xba, 0x970x20);        // RGB Mode, RGB565

    mt9d111_axi_lites[1] = 0// One_shot_mode is disabled

    // AXI GPIO Initialization
    XGpio_Status = XGpio_Initialize(&GPIOInstance_Ptr,XPAR_AXI_GPIO_0_DEVICE_ID);
    if(XST_SUCCESS != XGpio_Status)
        print("GPIO INIT FAILED\n\r");
    // AXI GPIO Set the Direction(output setting)
    XGpio_SetDataDirection(&GPIOInstance_Ptr, 10);

    while(1){
        printf("mnist_conv_nn_test, <h> : left, <k> : up, <j> : down, <l> : right, <q> : exit\n");
        inbyte_in = inbyte();
        switch(inbyte_in) {
            case 'h' : // left
            case 'H' : // left -5
                if(inbyte_in == 'h' && xval > 0)
                    --xval;
                else if(inbyte_in == 'H' && xval >= 5)
                    xval -= 5;
                XSquare_frame_gen_Set_x_pos(&sf_gen, xval);
                XMnist_conv_nn_Set_addr_offset(&mcnn, xval);
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'l' : // right
            case 'L' : // right +5
                if(inbyte_in == 'l' && xval < HORIZONTAL_PIXELS-28)
                    xval++;
                else if(inbyte_in == 'L' && xval <= HORIZONTAL_PIXELS-28-5)
                    xval += 5;
                XSquare_frame_gen_Set_x_pos(&sf_gen, xval);
                XMnist_conv_nn_Set_addr_offset(&mcnn, xval);
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'k' : // up
            case 'K' : // up -5
                if(inbyte_in == 'k' && yval > 0)
                    --yval;
                else if(inbyte_in == 'K' && yval >= 5)
                    yval -= 5;
                XSquare_frame_gen_Set_y_pos(&sf_gen, yval);
                XMnist_conv_nn_Set_in_r(&mcnn, FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*yval*sizeof(int));
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'j' : // down
            case 'J' : // down +5
                if(inbyte_in == 'j' && xval < VERTICAL_LINES-28)
                    yval++;
                else if(inbyte_in == 'J' && xval <= VERTICAL_LINES-28-5)
                    yval += 5;
                XSquare_frame_gen_Set_y_pos(&sf_gen, yval);
                XMnist_conv_nn_Set_in_r(&mcnn, FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*yval*sizeof(int));
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'r' : // result check
                result_disp = 1;
                break;
            case 'q' : // exit
                return(0);
        }

        if(result_disp){
            printf("\nHardware\n");
            // XMnist_conv_nn start
            XMnist_conv_nn_DisableAutoRestart(&mcnn);
            while(!XMnist_conv_nn_IsIdle(&mcnn));
            XTime_GetTime(&start_time);
            XMnist_conv_nn_Start(&mcnn);
            while(!XMnist_conv_nn_IsIdle(&mcnn));
            XTime_GetTime(&end_time);
            printf("conv_time = %f ms\n", (float)((long)end_time-(long)start_time)/325000.0);

            // mnist cnn result check
            for(i=0; i<5; i++){
                XMnist_conv_nn_Read_out_V_Words(&mcnn, i, &res, 1);
                result[i*2] = res & 0x0fff;
                if(result[i*2] & 0x800// minus
                    result[i*2] = 0xfffff000 | result[i*2]; // Sign extension

                result[i*2+1] = (res & 0x0fff0000) >> 16;
                if(result[i*2+1] & 0x800// minus
                    result[i*2+1] = 0xfffff000 | result[i*2+1]; // Sign extension
            }

            max_id = max_int(result);
            XGpio_DiscreteWrite(&GPIOInstance_Ptr, 1, max_id);

            for(i=0; i<10; i++){
                printf("result[%d] = %x\n", i, result[i]);
            }
            printf("max_id = %d\n", max_id);

            printf("\nSoftware\n");
            conv_addr = FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*yval*sizeof(int);
            XTime_GetTime(&start_time);
            mnist_conv_nn_float((int *)conv_addr, xval, result_float);
            XTime_GetTime(&end_time);
            max_id_float = max_float(result_float);
            printf("conv_time = %f ms\n", (float)((long)end_time-(long)start_time)/325000.0);
            for(i=0; i<10; i++){
                printf("result_float[%d] = %f\n", i, result_float[i]);
            }
            printf("max_id_float = %d\n", max_id_float);

            result_disp = 0;
        }
    }
}

int max_int(int out[10]){
    int max_id;
    int max, i;

    for(i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

int mnist_conv_nn_float(int in[22400], int addr_offset, float out[10]){

    // 手書き数字の値を表示
    /*for (int i=0; i<28; i++){        for (int j=0; j<800; j++){            if (j>=addr_offset && j<addr_offset+28)                printf("%2x, ", (int)(conv_rgb2y_soft(in[i*800+j])*256.0));        }        printf("\n");    } */

    buf_copy1: for(int i=0; i<28; i++){
        buf_copy2: for(int j=0; j<800; j++){
            if (j>=addr_offset && j<addr_offset+28)
                buf[i][j-addr_offset] = (float)0.99609375 - (float)conv_rgb2y_soft(in[i*800+j]);
        }
    }

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<10; i++){    // カーネルの個数
        CONV2: for(int j=0; j<24; j++){
            CONV3: for(int k=0; k<24; k++){
                conv_out[i][j][k] = 0;
                CONV4: for(int m=0; m<5; m++){
                    CONV5: for(int n=0; n<5; n++){
                        conv_out[i][j][k] += buf[j+m][k+n] * conv1_fweight[i][0][m][n];
                    }
                }
                conv_out[i][j][k] += conv1_fbias[i];

                if(conv_out[i][j][k]<0)    // ReLU
                    conv_out[i][j][k] = 0;
            }
        }
    }

    // Pooling Kernel = 2 x 2, Stride = 2
    POOL1: for(int i=0; i<10; i++){
        POOL2: for(int j=0; j<24; j += 2){
            POOL3: for(int k=0; k<24; k += 2){
                POOL4: for(int m=0; m<2; m++){
                    POOL5: for(int n=0; n<2; n++){
                        if(m==0 && n==0){
                            pool_out[i][j/2][k/2] = conv_out[i][j][k];
                        } else if(pool_out[i][j/2][k/2] < conv_out[i][j+m][k+n]){
                            pool_out[i][j/2][k/2] = conv_out[i][j+m][k+n];
                        }
                    }
                }
            }
        }
    }

    af1_dot1: for(int col=0; col<100; col++){
        dot1[col] = 0;
        af1_dot2: for(int i=0; i<10; i++){
            af1_dot3: for(int j=0; j<12; j++){
                af1_dot4: for(int k=0; k<12; k++){
                    dot1[col] += pool_out[i][j][k]*af1_fweight[i*12*12+j*12+k][col];
                }
            }
        }
        dot1[col] += af1_fbias[col];

        if(dot1[col] < 0)    // ReLU
            dot1[col] = 0;
    }

    af2_dot1: for(int col=0; col<10; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<100; row++){
            dot2[col] += dot1[row]*af2_fweight[row][col];
        }
        dot2[col] += af2_fbias[col];

        out[col] = dot2[col];
    }

    return(0);
}

int max_float(float out[10]){
    int max_id;
    float max;

    for(int i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}


// 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 にした
// 2017/06/30 : retval を float にした
float conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;
    float y_float;

    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で割る

    if (y >= 256)
        y = 255;

    y_float = (float)y/256.0;

    return(y_float);
}

  1. 2017年07月08日 04:50 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

手書き数字認識用畳み込みニューラルネットワーク回路の製作6(PYNQボードで動作確認)

手書き数字認識用畳み込みニューラルネットワーク回路の製作5(SDK)”の続き。

前回は、カメラで撮影した画像の内の四角枠の領域の手書き数字を認識するPYNQボード用のVivado 2017.2プロジェクトで、SDKを使用してカメラ画像を表示した。今回は、mnist_conv_nn_test.c を作成して、手書き数字を認識してみよう。

まずは、SDK でベアメタル・アプリケーションソフトの mnist_conv_nn_test.c を作成した。
hand_draw_num_89_170706.png

PYNQボードにMT9D111カメラモジュールを搭載したシステムで、手書きの数字を撮影した。
hand_draw_num_78_170705.jpg

画像にピンクの四角枠が表示され、PYNQボードのシリアルインターフェイスに接続されたTera Term の k キーを押すと1ピクセル左に、j キーを押すと1ピクセル下に、k キーを押すと1ピクセル上に、l キーを押すと1ピクセル右に移動する。 r キーを押すと、0 ~ 10 までの値が表示され、最大値 (max_id) の値が表示される。 q キーでexit する。
更に、GPIOに接続されたLEDが、最大値 (max_id) の値を2進数で表示する。

さて、キー操作で、ピンクの四角枠を移動して、数字を指定する。
まずは、1 を認識してみよう。
hand_draw_num_79_170705.jpg

パソコンで立ち上げたTera Term の画面を示す。result[0] ~ result[9] はそれぞれの値の手書き数字の値を示す。その最大値 (max_id) は 1 なので、1 と判定された。なお、max_id はint で 2 の補数表示となっている。
hand_draw_num_68_170705.png

つぎは、2 を認識してみよう。
hand_draw_num_80_170705.jpg

パソコンで立ち上げたTera Term の画面を示す。 2 と認識されている。
hand_draw_num_69_170705.png

3 を認識してみよう。
hand_draw_num_81_170705.jpg

パソコンで立ち上げたTera Term の画面を示す。 3 と認識されている。
hand_draw_num_70_170705.png

4 を認識してみよう。
hand_draw_num_82_170705.jpg

パソコンで立ち上げたTera Term の画面を示す。 4 と認識されている。
hand_draw_num_71_170705.png

5 を認識してみよう。
hand_draw_num_83_170705.jpg

パソコンで立ち上げたTera Term の画面を示す。 5 と認識されている。
hand_draw_num_72_170705.png

6 を認識してみよう。
hand_draw_num_84_170705.jpg

パソコンで立ち上げたTera Term の画面を示す。 6 と認識されている。
hand_draw_num_73_170705.png

7 を認識してみよう。
hand_draw_num_85_170705.jpg

パソコンで立ち上げたTera Term の画面を示す。 7 と認識されている。
hand_draw_num_74_170705.png

8 を認識してみよう。
hand_draw_num_86_170705.jpg

パソコンで立ち上げたTera Term の画面を示す。 8 と認識されている。
hand_draw_num_75_170705.png

9 を認識してみよう。
hand_draw_num_87_170705.jpg

パソコンで立ち上げたTera Term の画面を示す。 9 と認識されている。
hand_draw_num_76_170705.png

0 を認識してみよう。
hand_draw_num_88_170705.jpg

パソコンで立ち上げたTera Term の画面を示す。 0 と認識されている。
hand_draw_num_77_170705.png

今のところ精度は 100 % だった。うまく行ったようだ。
手書き数字を認識する領域であるピンクの四角枠をキーで移動できるので、カメラの位置を細かく合わせる必要がなく、とってもやりやすい。

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

/* * mnist_conv_nn_test.c * *  Created on: 2017/07/03 *      Author: Masaaki */

#include <stdio.h>
#include <stdlib.h>
#include "xaxivdma.h"
#include "xil_io.h"
#include "xparameters.h"
#include "sleep.h"
#include "xgpio.h"

#include "xmnist_conv_nn.h"
#include "xsquare_frame_gen.h"

#define FRAME_BUFFER_ADDRESS 0x10000000
#define NUMBER_OF_WRITE_FRAMES    3 // Note: If not at least 3 or more, the image is not displayed in succession.

#define HORIZONTAL_PIXELS    800
#define VERTICAL_LINES        600
#define PIXEL_NUM_OF_BYTES    4

int max_int(int out[10]);

static XAxiVdma_DmaSetup Vdma0_WriteCfg;

void cam_i2c_init(volatile unsigned *mt9d111_i2c_axi_lites) {
    mt9d111_i2c_axi_lites[64] = 0x2// reset tx fifo ,address is 0x100, i2c_control_reg
    mt9d111_i2c_axi_lites[64] = 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(volatile unsigned *mt9d111_i2c_axi_lites, unsigned int device_addr, unsigned int write_addr, unsigned int write_data){
    mt9d111_i2c_axi_lites[66] = 0x100 | (device_addr & 0xfe);   // Slave IIC Write Address, address is 0x108, i2c_tx_fifo
    mt9d111_i2c_axi_lites[66] = write_addr;
    mt9d111_i2c_axi_lites[66] = (write_data >> 8)|0xff;         // first data
    mt9d111_i2c_axi_lites[66] = 0x200 | (write_data & 0xff);        // second data
    cam_i2x_write_sync();
}

int main(){
    XMnist_conv_nn mcnn;
    XSquare_frame_gen sf_gen;
    int inbyte_in;
    int xval, yval;
    int i, res;
    int result[10];
    static XGpio GPIOInstance_Ptr;
    int XGpio_Status;
    int max_id;
    XAxiVdma_Config *XAxiVdma0_Config;
    XAxiVdma XAxiVdma0;
    int XAxiVdma0_Status;
    int result_disp = 0;

    // AXI VDMA Initialization sequence
    XAxiVdma0_Config = XAxiVdma_LookupConfig(XPAR_CAMERA_INTERFACE_AXI_VDMA_0_DEVICE_ID); // Look up the hardware configuration for a device instance
    if (XAxiVdma0_Config == NULL){
        fprintf(stderr, "No AXI VDMA found\n");
        return(-1);
    }

    XAxiVdma0_Status = XAxiVdma_CfgInitialize(&XAxiVdma0, XAxiVdma0_Config, XAxiVdma0_Config->BaseAddress); // Initialize the driver with hardware configuration
    if (XAxiVdma0_Status != XST_SUCCESS){
        fprintf(stderr, "XAxiVdma_CfgInitialize() failed\n");
        return(-1);
    }

    XAxiVdma_Reset(&XAxiVdma0, XAXIVDMA_WRITE);
    while(XAxiVdma_ResetNotDone(&XAxiVdma0, XAXIVDMA_WRITE)) ;

    XAxiVdma0_Status = XAxiVdma_SetFrmStore(&XAxiVdma0, NUMBER_OF_WRITE_FRAMES, XAXIVDMA_WRITE); // Set the number of frame store buffers to use.

    Vdma0_WriteCfg.VertSizeInput = VERTICAL_LINES;
    Vdma0_WriteCfg.HoriSizeInput = HORIZONTAL_PIXELS * PIXEL_NUM_OF_BYTES;
    Vdma0_WriteCfg.Stride = HORIZONTAL_PIXELS * PIXEL_NUM_OF_BYTES; // Indicates the number of address bytes between the first pixels of each video line.
    Vdma0_WriteCfg.FrameDelay = 0// Indicates the minimum number of frame buffers the Genlock slave is to be behind the locked master. This field is only used if the channel is enabled for Genlock Slave operations. This field has no meaning in other Genlock modes.
    Vdma0_WriteCfg.EnableCircularBuf = 1// Indicates frame buffer Circular mode or frame buffer Park mode.  1 = Circular Mode Engine continuously circles through frame buffers.
    Vdma0_WriteCfg.EnableSync = 0// Enables Genlock or Dynamic Genlock Synchronization. 0 = Genlock or Dynamic Genlock Synchronization disabled.
    Vdma0_WriteCfg.PointNum = 0// No Gen-Lock
    Vdma0_WriteCfg.EnableFrameCounter = 0// Endless transfers
    Vdma0_WriteCfg.FixedFrameStoreAddr = 0// We are not doing parking

    XAxiVdma0_Status = XAxiVdma_DmaConfig(&XAxiVdma0, XAXIVDMA_WRITE, &Vdma0_WriteCfg);
    if (XAxiVdma0_Status != XST_SUCCESS){
        fprintf(stderr, "XAxiVdma_DmaConfig() failed\n");
        return(-1);
    }

    // Frame buffer address set
    unsigned int frame_addr = (unsigned int)FRAME_BUFFER_ADDRESS;
    for (i=0; i<NUMBER_OF_WRITE_FRAMES; i++){
        Vdma0_WriteCfg.FrameStoreStartAddr[i] = frame_addr;
        //frame_addr += HORIZONTAL_PIXELS * PIXEL_NUM_OF_BYTES * VERTICAL_LINES;
    }

    XAxiVdma0_Status = XAxiVdma_DmaSetBufferAddr(&XAxiVdma0, XAXIVDMA_WRITE, Vdma0_WriteCfg.FrameStoreStartAddr);
    if (XAxiVdma0_Status != XST_SUCCESS){
        fprintf(stderr, "XAxiVdma_DmaSetBufferAddr() failed\n");
        return(-1);
    }

    // Mnist_conv_nn, Square_frame_gen Initialize
    XMnist_conv_nn_Initialize(&mcnn, 0);
    XSquare_frame_gen_Initialize(&sf_gen, 0);

    // square_frame_gen initialize
    XSquare_frame_gen_Set_x_pos(&sf_gen, HORIZONTAL_PIXELS/2);
    xval = HORIZONTAL_PIXELS/2;
    XSquare_frame_gen_Set_y_pos(&sf_gen, VERTICAL_LINES/2);
    yval = VERTICAL_LINES/2;
    XSquare_frame_gen_Set_width(&sf_gen, 28);
    XSquare_frame_gen_Set_height(&sf_gen, 28);
    XSquare_frame_gen_Set_off_on(&sf_gen, 1); // on

    // XSquare_frame_gen start
    XSquare_frame_gen_DisableAutoRestart(&sf_gen);
    while(!XSquare_frame_gen_IsIdle(&sf_gen)) ;
    XSquare_frame_gen_Start(&sf_gen);
    XSquare_frame_gen_EnableAutoRestart(&sf_gen);

    // mnist_conv_nn initialize
    XMnist_conv_nn_Set_addr_offset(&mcnn, HORIZONTAL_PIXELS/2);
    XMnist_conv_nn_Set_in_r(&mcnn, FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*(VERTICAL_LINES/2)*sizeof(int));

    // axis_switch_1, 1to2 ,Select M00_AXIS
    // Refer to http://marsee101.blog19.fc2.com/blog-entry-3177.html
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_1_BASEADDR+0x40), 0x80000000); // disable
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_1_BASEADDR+0x44), 0x0); // square_frame_gen enable
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_1_BASEADDR), 0x2); // Commit registers

    // axis_switch_0, 2to1, Select S00_AXIS
    // Refer to http://marsee101.blog19.fc2.com/blog-entry-3177.html
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_0_BASEADDR+0x40), 0x1);
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_0_BASEADDR), 0x2); // Commit registers

    // VDMA start
    XAxiVdma0_Status = XAxiVdma_DmaStart(&XAxiVdma0, XAXIVDMA_WRITE);
    if (XAxiVdma0_Status != XST_SUCCESS){
        fprintf(stderr, "XAxiVdma_DmaStart() failed\n");
        return(-1);
    }

    // mt9d111_inf_axis_0, axi_iic_0, bitmap_disp_cntrler_axi_master_0
    volatile unsigned int *bmdc_axi_lites;
    volatile unsigned int *mt9d111_axi_lites;
    volatile unsigned int *mt9d111_i2c_axi_lites;

    bmdc_axi_lites = (volatile unsigned *)XPAR_BITMAP_DISP_CNTRLER_AXI_MASTER_0_BASEADDR;
    mt9d111_axi_lites = (volatile unsigned *)XPAR_CAMERA_INTERFACE_MT9D111_INF_AXIS_0_BASEADDR;
    mt9d111_i2c_axi_lites = (volatile unsigned *)XPAR_CAMERA_INTERFACE_AXI_IIC_0_BASEADDR;

    bmdc_axi_lites[0] = (volatile unsigned int)FRAME_BUFFER_ADDRESS; // Bitmap Display Controller start
    mt9d111_axi_lites[0] = (volatile unsigned int)FRAME_BUFFER_ADDRESS; // Camera Interface start (Address is dummy)

    // CMOS Camera initialize, MT9D111
    cam_i2c_init(mt9d111_i2c_axi_lites);

    cam_i2c_write(mt9d111_i2c_axi_lites, 0xba, 0xf00x1);      // Changed regster map to IFP page 1
    cam_i2c_write(mt9d111_i2c_axi_lites, 0xba, 0x970x20);        // RGB Mode, RGB565

    mt9d111_axi_lites[1] = 0// One_shot_mode is disabled

    // XMnist_conv_nn start
    XMnist_conv_nn_DisableAutoRestart(&mcnn);
    while(!XMnist_conv_nn_IsIdle(&mcnn));
    XMnist_conv_nn_Start(&mcnn);
    XMnist_conv_nn_EnableAutoRestart(&mcnn);

    // AXI GPIO Initialization
    XGpio_Status = XGpio_Initialize(&GPIOInstance_Ptr,XPAR_AXI_GPIO_0_DEVICE_ID);
    if(XST_SUCCESS != XGpio_Status)
        print("GPIO INIT FAILED\n\r");
    // AXI GPIO Set the Direction(output setting)
    XGpio_SetDataDirection(&GPIOInstance_Ptr, 10);

    while(1){
        printf("mnist_conv_nn_test, <h> : left, <k> : up, <j> : down, <l> : right, <q> : exit\n");
        inbyte_in = inbyte();
        switch(inbyte_in) {
            case 'h' : // left
                if(xval > 0){
                    --xval;
                    XSquare_frame_gen_Set_x_pos(&sf_gen, xval);
                    XMnist_conv_nn_Set_addr_offset(&mcnn, xval);
                    printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                }
                break;
            case 'l' : // right
                if(xval < HORIZONTAL_PIXELS-28){
                    xval++;
                    XSquare_frame_gen_Set_x_pos(&sf_gen, xval);
                    XMnist_conv_nn_Set_addr_offset(&mcnn, xval);
                    printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                }
                break;
            case 'k' : // up
                if(yval > 0){
                    --yval;
                    XSquare_frame_gen_Set_y_pos(&sf_gen, yval);
                    XMnist_conv_nn_Set_in_r(&mcnn, FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*yval*sizeof(int));
                    printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                }
                break;
            case 'j' : // down
                if(yval < VERTICAL_LINES-28){
                    yval++;
                    XSquare_frame_gen_Set_y_pos(&sf_gen, yval);
                    XMnist_conv_nn_Set_in_r(&mcnn, FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*yval*sizeof(int));
                    printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                }
                break;
            case 'r' : // result check
                result_disp = 1;
                break;
            case 'q' : // exit
                return(0);
        }

        // mnist cnn result check
        for(i=0; i<5; i++){
            XMnist_conv_nn_Read_out_V_Words(&mcnn, i, &res, 1);
            result[i*2] = res & 0x0fff;
            if(result[i*2] & 0x800// minus
                result[i*2] = 0xfffff000 | result[i*2]; // Sign extension

            result[i*2+1] = (res & 0x0fff0000) >> 16;
            if(result[i*2+1] & 0x800// minus
                result[i*2+1] = 0xfffff000 | result[i*2+1]; // Sign extension
        }

        max_id = max_int(result);
        XGpio_DiscreteWrite(&GPIOInstance_Ptr, 1, max_id);

        if(result_disp){
            for(i=0; i<10; i++){
                printf("result[%d] = %x\n", i, result[i]);
            }
            printf("max_id = %d\n", max_id);
            result_disp = 0;
        }
    }
}

int max_int(int out[10]){
    int max_id;
    int max, i;

    for(i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

  1. 2017年07月06日 05:03 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

手書き数字認識用畳み込みニューラルネットワーク回路の製作5(SDK)

手書き数字認識用畳み込みニューラルネットワーク回路の製作4(Vivadoプロジェクト)”の続き。

前回は、手書き数字認識用四角枠表示回路(IP)と、手書き数字を認識する畳み込みニューラルネットワークIP を使用してカメラで撮影した画像の内の四角枠の領域の手書き数字を認識するPYNQボード用のVivado 2017.2プロジェクトを作成した。今回は、SDKを使用してカメラ画像を表示してみよう。

Vivado プロジェクトのインプリメントが成功したので、ハードウェアをエクスポートして、SDK を立ち上げた。

FASTX コーナー検出の時に使用していた cam_disp_axis.c はそのまま使用できるので、cam_disp_axis アプリケーション・プロジェクトを作成して、cam_disp_axis.c をソースコードとして、src フォルダにコピーした。
hand_draw_num_66_170702.png

SDK のXilinx Tools メニューから、Program FPGAを選択して、Zynq をコンフィギュレーションした。
cam_disp_axis.elf を起動したらカメラ画像が表示された。
hand_draw_num_67_170704.jpg

次回は、アプリケーションソフトを作成して、四角枠を表示して、その枠内の手書き数字の値をLED に表示してみたい。
  1. 2017年07月04日 05:03 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

手書き数字認識用畳み込みニューラルネットワーク回路の製作4(Vivadoプロジェクト)

手書き数字認識用畳み込みニューラルネットワーク回路の製作3(畳み込みNN)”の続き。

前回は、カメラのフレームバッファから直接DMA で画像データを持ってきて手書き数字を認識する畳み込みニューラルネットワークで手書き数字を認識するIP を作成した。
今回は、手書き数字認識用四角枠表示回路(IP)と、手書き数字を認識する畳み込みニューラルネットワークIP を使用してカメラで撮影した画像の内の四角枠の領域の手書き数字を認識するPYNQボード用のVivado 2017.2プロジェクトを作成する。

ペースとなるVivado プロジェクトとしては、PYNQ用のFASTX コーナー検出プロジェクトを使用する。その画像フィルタの代わりに、手書き文字認識用四角枠表示回路を挿入する。手書き数字を認識する畳み込みニューラルネットワークIPはAXI_HP0 のAXI Interconnect に接続することにしよう。

PYNQ_MNIST_CMM_172 フォルダのVivado 2017.2 プロジェクトを示す。プロジェクト名は、FASTX コーナー検出jのPYNQ_FASTX_164 のままだ。
hand_draw_num_62_170702.png

ブロックデザインを示す。mnist_conv_nn_0 が中央付近にあるのが分かる。
hand_draw_num_63_170702.png

camera_interface モジュールを示す。
hand_draw_num_64_170702.png

論理合成、インプリメント、ビットストリームの生成を行った。
hand_draw_num_65_170702.png

成功した。
ちなみに、Linux版Vivado HLS 2016.4 のIP を使用しているが、Estimated が 10.07 ns のWindowsのVivado HLS 2016.4 では、タイミングがメットしなかった。論理合成のストラテジーを変えてもだめだった。いろいろと微妙のようだ。結構、Vivado HLS のタイミングの予測が正しいみたいだ。
  1. 2017年07月03日 04:52 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0
»