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

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

FPGAの部屋

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

Zybot のモーターの回転数と回転方向を取得する1

以前、Zybot を作成したのだが、モーター回転数を取得する部分はいい加減だった。

Digilent のZYBOt Tutorial
FPGAの部屋のブログのZybot カテゴリ


今回、Zybot のFPGA ボードをUltra96 に変更するにあって、ギアボックス付きモーター回転数と回転方向を取得したい。
以前、Vivado HLS で作ったのだが、いまいち精度が出ていない気がするので、HDL で作り直すことにした。
なお、SA, SB のタイミングチャートは、”Project 10: Frequency Measurement Using Input Capture”の 13 ページの”Figure 2. Screen capture of the seven monitored signals.”に載っている。

どうHDL で書くかを考えながら、いろいろとツィッターに書いていたところ、@izumitomonori さんにHDL のサンプルコードを教えていただいて、積分するんだよ。。。という貴重なご意見をいただいた。サンプルコードそのままでも良いのだが、私のアレンジ(悪くなっているかも知れないが。。。)を加えた久しぶりのVerilog HDL コードを書いてみた。ありがとうございました。

motor_count.v をここに載せるが、まだ全く検証していないので、バグはあると思う。これから検証していこう。
なお、@izumitomonori さんの名誉のために書いておきますが、元のコードはとってもシンプルな素晴らしいコードです。私がコードの切れ味をだいぶ鈍らせてしまった気がするが、どうもこんなふうに書かないと気がすまないもので。。。すみません。。。

// motor_count.v
// 2019/06/14 by marsee
// このファイルは、立命館大学の泉先生のVerilogコードを元に作成しました。泉先生ありがとうございました。
//

`default_nettype none

module moter_count #(
    parameter integer BW =32
)
(
   input  wire          sa,
   input  wire          sb,
   output wire [BW-1:0] count,
   input  wire          clk,
   input  wire          rst,
   input  wire          cnt_clr
);

    reg sa1b=1'b0, sa2b=1'b0;
    reg sb1b=1'b0, sb2b=1'b0;
    wire [1:0] sasb_state;
    reg [BW-1:0] count_r=0;
    
    parameter [3:0]     NOR_STATE_00 = 4'b0001,
                        NOR_STATE_10 = 4'b0010,
                        NOR_STATE_11 = 4'b0100,
                        NOR_STATE_01 = 4'b1000;

    reg [3:0] rev_rot, rev_rot_1b;
    parameter [3:0]     REV_STATE_00 = 4'b0001,
                        REV_STATE_01 = 4'b0010,
                        REV_STATE_11 = 4'b0100,
                        REV_STATE_10 = 4'b1000;
    reg [3:0] nor_rot=NOR_STATE_00, nor_rot_1b=REV_STATE_00;
    
    assign count = count_r;
    
    always @(posedge clk) begin
        sa1b <= sa;
        sb1b <= sb;
        sa2b <= sa1b;
        sb2b <= sb1b;

        nor_rot_1b <= nor_rot;
        rev_rot_1b <= rev_rot;
    end
    
    always @(posedge clk) begin
        if(rst)
            count_r <= 0;
        else if(cnt_clr)
            count_r <= 0;
        else begin
            if(nor_rot==NOR_STATE_00 && nor_rot_1b==NOR_STATE_01) begin
                if(rev_rot==REV_STATE_00 && rev_rot_1b==REV_STATE_10) begin
                    count_r <= count_r;
                end else begin
                    count_r <= count_r + 1;
                end
            end else begin
                if(rev_rot==REV_STATE_00 && rev_rot_1b==REV_STATE_10) begin
                    count_r <= count_r - 1;
                end else begin
                    count_r <= count_r;
                end
            end
        end
    end
    
    assign sasb_state = {sa2b, sb2b};
    
    // nor_rot state machine
    always @(posedge clk) begin
        if(rst)
            nor_rot <= NOR_STATE_00;
        else begin
            case(nor_rot)
                NOR_STATE_00:
                    if(sasb_state==2'b10)
                        nor_rot <= NOR_STATE_10;
                NOR_STATE_10:
                    if(sasb_state == 2'b11)
                        nor_rot <= NOR_STATE_11;
                NOR_STATE_11:
                    if(sasb_state == 2'b01)
                        nor_rot <= NOR_STATE_01;
                NOR_STATE_01:
                    if(sasb_state == 2'b00)
                        nor_rot <= NOR_STATE_00;
            endcase
        end
    end
    
    // rev_rot state machine
    always @(posedge clk) begin
        if(rst)
            rev_rot <= REV_STATE_00;
        else begin
            case(rev_rot)
                REV_STATE_00:
                    if(sasb_state==2'b01)
                        nor_rot <= NOR_STATE_01;
                REV_STATE_01:
                    if(sasb_state == 2'b11)
                        nor_rot <= NOR_STATE_11;
                REV_STATE_11:
                    if(sasb_state == 2'b10)
                        nor_rot <= NOR_STATE_10;
                REV_STATE_10:
                    if(sasb_state == 2'b00)
                        nor_rot <= NOR_STATE_00;
            endcase
        end
    end
endmodule

`default_nettype wire


とても久しぶりにVerilog HDL のコードを書いた。いろいろと忘れている。。。大丈夫かな?あとで修正します。
次は、検証用の回路をVivado HLS で作っていくことにする。
  1. 2019年06月17日 23:17 |
  2. Zybot
  3. | トラックバック:0
  4. | コメント:0

ZYBOtを家のコースで走らせてみた

ZYBOt の白線間走行用CNNを含んだVivado 2018.2 プロジェクト2”でビットファイルが生成できた。
生成されたビットファイルを”白線追従用CNNを使用したZYBOtの白線追従走行1(準備編)”を参考にして、fpga-bit-to-bin.py を使用して、ZYBO_0_wrapper.bit を ZYBO_0_wrapper.bin に変換した。
python fpga-bit-to-bin.py --flip ZYBO_0_wrapper.bit ZYBO_0_wrapper.bin
ZYBOt_Keras_69_181008

ZYBOt のZYBO にFTP して書き換えた ZYBO_0_wrapper.bin をアップロードした。

sudo cp ZYBO_0_wrapper.bin /lib/firmware/
で、ZYBO_0_wrapper.bin を /lib/firmware/ ディレクトリにコピペした。

まずは、ホーム・ディレクトリから cd zybot/wl_tracing_cnn を実行して、wl_tracing_cnn ディレクトリに移動した。

参照ブログ”白線追従用CNNを使用したZYBOtの白線追従走行2(準備編2)
sudo dtbocfg.rb -i --dts devicetree.dts wl_tracing_cnn
を実行し、デバイスツリーをロードして、ビットファイルのダウンロード、クロックの設定も行った。
次に、/dev/uio* と /dev/udmabuf0 をすべての人がR/W できるように設定した。
sudo chmod 666 /dev/uio*
sudo chmod 666 /dev/udmabuf0

これで、すべてのユーザーが書き込みできるので、ユーザーモードで wl_tracing_cnn を起動できる。
cd build
./wl_tracing_cnn

これで ZYBOt が走り出した。コースを走らせてみた。
1つ目はバックグランド音楽を付けてみたので、音量注意。




それなりにコースを走れてはいるが、コースを外れてしまう。学習が良くなかったかもしれない?手前から曲がるように学習させてしまったかもしれない?
1回カーブを曲がるようなときはごまかしが効くが、複雑なコースを走るときにはごまかしが効かない。ごまかしというのは
私が操縦してラジコンのように走れせて、それをZYBOt に学習させたほうが良いのではないか?と思う。
  1. 2018年10月09日 05:29 |
  2. Zybot
  3. | トラックバック:0
  4. | コメント:0

ZYBOt の白線間走行用CNNを含んだVivado 2018.2 プロジェクト2

ZYBOt の白線間走行用CNNを含んだVivado 2018.2 プロジェクト1”の続き。

前回は、白線間走行用CNNを course_conv_nn2_axi3 に変更してVivado プロジェクトを論理合成、インプリメンテーションしたが、タイミングエラーになってしまった。今回は、Vivado HLS に戻ってIP を再合成して、タイミングエラーを解消しよう。

Vivado HLS 2018.2 の course_conv_nn2_axi3_k に戻って、Solution Settings -> Synthesis の Uncertainty を 8 ns まで、 1 ns 刻みに変更していったが、Export RTL の CP achieved post-implementation は 9.847 ns で変わらなかった。つまり回路が変わらなかったようだ。下にUncertainty が 6 ns のときの結果を示す。
ZYBOt_Keras_57_181006
ZYBOt_Keras_58_181006

Vivado HLS 2017.4 でも同様にSolution Settings -> Synthesis の Uncertainty を 2 ns から 8 ns まで、 1 ns 刻みに変更していったが、Export RTL の CP achieved post-implementation は 8.221 ns で変化がなかった。Uncertainty がデフォルト値の下に示す IP をVivado で論理合成、インプリメンテーションしてみたが、やはりタイミングエラーだった。
ZYBOt_Keras_60_181006
ZYBOt_Keras_61_181006

ZYBOt_Keras_62_181006

どうやら、このC ソースコードでは、Uncertainty を変更しても回路に変更が無いようだ。こんな時に状況を打開できるのは指示子だと思う。積極的に指示子を入れていって回路を変更してみよう。とりあえずは影響の少なそうなところにPIPELINE 指示子を入れた。
ZYBOt_Keras_63_181008

Uncertainty が 7 ns のときを示す。
ZYBOt_Keras_64_181008
ZYBOt_Keras_65_181008

Latency は約 3.09 ms だが、問題ない。

ZYBOt_Keras_66_181008

Export RTL の CP achieved post-implementation は 7.670 ns なので、良さそう。
この IP をVivado プロジェクトの IP と入れ替えた。
論理合成、インプリメンテーション、ビットストリームの生成を行った。
ZYBOt_Keras_67_181008

Project Summary を示す。
ZYBOt_Keras_68_181008

Timing も問題ない。
  1. 2018年10月08日 06:22 |
  2. Zybot
  3. | トラックバック:0
  4. | コメント:0

ZYBOt の白線間走行用CNNを含んだVivado 2018.2 プロジェクト1

ZYBOt の白線間走行用CNNをVivado HLS 2018.2 でIP化2”で作製した白線間走行用CNN IP を”Kerasで学習した重みとバイアスを使用した白線間走行用CNNのVivado プロジェクト1”の curve_conv_nn2_axis3 _0 を消去して、今回作成した course_conv_nn2_axi3 を Add IP した。

ZYBO_0_182_9_k ディレクトリの ZYBO_0_153 プロジェクトを示す。
ZYBOt_Keras_54_181006

camera_interface モジュールで、curve_conv_nn2_axis3 _0 を消去して、今回作成した course_conv_nn2_axi3 を Add IP した。
ZYBOt_Keras_55_181006

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

Timing を見ると Worst Negative Slace (WNS) が -0.297 ns で、タイミング・エラーになっていることがわかった。Total Negative Slack (TNS) も -1.752 ns なので、他の所でもタイミングエラーになっているのが分かる。

Open Implemented Design をクリックして、Timing を見ると course_conv_nn2_axis3_0 の内部の Intra-clock Path がエラーになっているのが分かった。
ZYBOt_Keras_53_181006

これは、Vivado HLS での合成結果が 100 MHz で動作しないということなので、Vivado HLS に戻って再合成をしてみようと思う。
  1. 2018年10月07日 11:37 |
  2. Zybot
  3. | トラックバック:0
  4. | コメント:0

ZYBOt の白線間走行用CNNをVivado HLS 2018.2 でIP化2

ZYBOt の白線間走行用CNNをVivado HLS 2018.2 でIP化1”の続き。

前回は、DMA 付きのZYBOt の白線間走行用CNNのC シミュレーションとC コードの合成を行った。今回は、C/RTL 協調シミュレーションとExport RTL を行って、IP 化する。

(2018/10/06 : 修正)画像を増やすスクリプトが間違っていたので、間違いを修正してやり直しました。

まずは、C/RTL 協調シミュレーションを行った。
ZYBOt_Keras_36_181004

Latency は 178925 クロックだった。100 MHz クロックだとすると約 1.79 ms となる。

C/RTL 協調シミュレーションの波形を見てみよう。
まずは全体波形を見てみよう。
ZYBOt_Keras_37_181004

ins(axis) の ins_TREADY は最初のころ1 - 0 を行き来しているようだ。ここを拡大する。
ZYBOt_Keras_38_181004

細かく 1 になっているのが分かる。まあ、ここのスループットを改善するには、リソースが大量に必要だし、処理速度は 1.79 ms と十分なので、問題ない。60 fps でも 16.7 ms の処理時間で十分間に合う。

最後に Export RTL を行った。
ZYBOt_Keras_52_181006

LUT は 786 個、FF が 690 個、DSP は 4 個、BRAM は 15 個だった。
CP achieved post-implementation は 9.847 ns だった。
  1. 2018年10月05日 04:49 |
  2. Zybot
  3. | トラックバック:0
  4. | コメント:0

ZYBOt の白線間走行用CNNをVivado HLS 2018.2 でIP化1

ZYBOt の白線間走行用CNNをVivado HLS 2018.2 で試しに実装した”で、ZYBOt の白線間走行用CNNの精度が検証できたので、今度はIP化を行う。今回はC シミュレーションとC コードの合成を行う。

(2018/10/06 : 修正)画像を増やすスクリプトが間違っていたので、間違いを修正してやり直しました。

まずは、”Kerasで学習した重みとバイアスを使用した白線間走行用CNNをIPにする1”と”Kerasで学習した重みとバイアスを使用した白線間走行用CNNをIPにする2”を参照して、Vivado HLS 2018.2 の course_conv_nn2_axis3_k プロジェクトを作成した。
ZYBOt_Keras_32_181004

C シミュレーションを実行した。
ZYBOt_Keras_48_181006

レポートを示す。

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
   Compiling ../../../course_conv_nn2_axis3_tb.cpp in debug mode
   Compiling ../../../course_conv_nn2_axis3.cpp in debug mode
   Generating csim.exe
test/straight_test/bmp_file231.bmp
test/straight_test/bmp_file185.bmp
test/straight_test/bmp_file238.bmp
test/straight_test/bmp_file184.bmp
test/straight_test/bmp_file101.bmp
test/straight_test/bmp_file198.bmp
test/straight_test/bmp_file72.bmp
test/straight_test/bmp_file115.bmp
test/straight_test/bmp_file65.bmp
test/straight_test/bmp_file100.bmp
test/straight_test/bmp_file102.bmp
test/straight_test/bmp_file117.bmp
test/straight_test/bmp_file74.bmp
test/straight_test/bmp_file113.bmp
test/straight_test/bmp_file73.bmp
test/straight_test/bmp_file76.bmp
test/straight_test/bmp_file103.bmp
test/straight_test/bmp_file68.bmp
test/straight_test/bmp_file75.bmp
test/straight_test/bmp_file240.bmp
test/straight_test/bmp_file232.bmp
test/straight_test/bmp_file235.bmp
test/straight_test/bmp_file104.bmp
test/straight_test/bmp_file187.bmp
test/straight_test/bmp_file201.bmp
test/straight_test/bmp_file233.bmp
test/straight_test/bmp_file197.bmp
test/straight_test/bmp_file199.bmp
test/straight_test/bmp_file116.bmp
test/straight_test/bmp_file239.bmp
test/straight_test/bmp_file186.bmp
test/straight_test/bmp_file188.bmp
test/straight_test/bmp_file234.bmp
test/straight_test/bmp_file67.bmp
test/straight_test/bmp_file114.bmp
Straight error is 0

test/left_test/bmp_file203.bmp
test/left_test/bmp_file94.bmp
correct data = 0, outs = 1
dot2[0] = -2.724609 dot2[1] = 3.486328 dot2[2] = -3.287109 
test/left_test/bmp_file89.bmp
test/left_test/bmp_file214.bmp
test/left_test/bmp_file84.bmp
test/left_test/bmp_file112.bmp
test/left_test/bmp_file105.bmp
test/left_test/bmp_file96.bmp
correct data = 0, outs = 2
dot2[0] = 1.197266 dot2[1] = -7.605469 dot2[2] = 9.322266 
test/left_test/bmp_file109.bmp
test/left_test/bmp_file97.bmp
test/left_test/bmp_file125.bmp
test/left_test/bmp_file123.bmp
test/left_test/bmp_file78.bmp
test/left_test/bmp_file82.bmp
test/left_test/bmp_file107.bmp
test/left_test/bmp_file85.bmp
test/left_test/bmp_file122.bmp
test/left_test/bmp_file124.bmp
test/left_test/bmp_file237.bmp
test/left_test/bmp_file108.bmp
test/left_test/bmp_file70.bmp
test/left_test/bmp_file95.bmp
test/left_test/bmp_file215.bmp
test/left_test/bmp_file88.bmp
test/left_test/bmp_file87.bmp
test/left_test/bmp_file79.bmp
test/left_test/bmp_file120.bmp
test/left_test/bmp_file111.bmp
test/left_test/bmp_file121.bmp
test/left_test/bmp_file80.bmp
test/left_test/bmp_file99.bmp
test/left_test/bmp_file86.bmp
test/left_test/bmp_file213.bmp
test/left_test/bmp_file118.bmp
test/left_test/bmp_file98.bmp
Left error is 2

test/right_test/bmp_file210.bmp
correct data = 2, outs = 0
dot2[0] = 3.501953 dot2[1] = 3.076172 dot2[2] = -9.761719 
test/right_test/bmp_file93.bmp
test/right_test/bmp_file196.bmp
test/right_test/bmp_file192.bmp
test/right_test/bmp_file208.bmp
test/right_test/bmp_file207.bmp
test/right_test/bmp_file219.bmp
test/right_test/bmp_file204.bmp
test/right_test/bmp_file77.bmp
test/right_test/bmp_file119.bmp
correct data = 2, outs = 1
dot2[0] = -6.904297 dot2[1] = 4.335938 dot2[2] = -1.699219 
test/right_test/bmp_file236.bmp
test/right_test/bmp_file221.bmp
test/right_test/bmp_file229.bmp
test/right_test/bmp_file71.bmp
correct data = 2, outs = 1
dot2[0] = -4.986328 dot2[1] = 2.332031 dot2[2] = 0.953125 
test/right_test/bmp_file205.bmp
test/right_test/bmp_file242.bmp
test/right_test/bmp_file206.bmp
test/right_test/bmp_file225.bmp
test/right_test/bmp_file195.bmp
test/right_test/bmp_file226.bmp
test/right_test/bmp_file228.bmp
test/right_test/bmp_file209.bmp
test/right_test/bmp_file211.bmp
correct data = 2, outs = 1
dot2[0] = -1.001953 dot2[1] = 2.556641 dot2[2] = -2.494141 
test/right_test/bmp_file216.bmp
test/right_test/bmp_file202.bmp
test/right_test/bmp_file217.bmp
test/right_test/bmp_file106.bmp
test/right_test/bmp_file246.bmp
test/right_test/bmp_file193.bmp
test/right_test/bmp_file194.bmp
test/right_test/bmp_file223.bmp
test/right_test/bmp_file230.bmp
test/right_test/bmp_file191.bmp
test/right_test/bmp_file218.bmp
test/right_test/bmp_file241.bmp
Right error is 4

INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


2 つ同じファイル名が続いているところがある。どうもこのフォルダのファイルをリストするコードが良くないのかもしれない?
とりあえず、精度を確認してみると、(105 - 6) / 105 * 100 = 94.3 % だった。

C コードの合成を行った。レポートを示す。
ZYBOt_Keras_49_181006
ZYBOt_Keras_50_181006

Estimated は 8.313 ns でLatency は 178833 クロックだった。100 MHz クロックだと、約 1.79 ms となる。
リソース使用量は BRAM_18K が 15 個、DSP48E が 8 個、FF が 970 個、LUT が 2805 個となった。

curve_conv_nn2_axis3.cpp を貼っておく。

// curve_conv_nn2_axis3.cpp
// 2017/09/09 by marsee
// 畳み込み層のカーネル数 2
// AXI4 Stream入力 番号出力
// 2017/09/18 : dot2[3]の出力も追加
// 2017/12/13 : 直線に加えてカーブのデータも使用して学習した
// 2018/10/03 : 家のZYBOtコースでKerasを使用して学習した
//

#include <ap_fixed.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

#include "conv1_weight.h"
#include "conv1_bias.h"
#include "af1_weight.h"
#include "af1_bias.h"
#include "af2_weight.h"
#include "af2_bias.h"

#define REDUSED_ROW     45
#define REDUSED_COULMN  60
#define NUM_OF_KERNELS  2
#define COULMN_PIXELS   56
#define ROW_PIXELS      10
#define ALL_PIXELS      560
#define NUM_OF_OUTPUT   3

int max_ap_fixed(ap_fixed<16, 7, AP_TRN_ZERO, AP_SAT> out[NUM_OF_OUTPUT], ap_uint<2> &out_num);

int course_conv_nn2_axis3(hls::stream<ap_axiu<32,1,1,1> >& ins, ap_uint<2> &outs,
        ap_fixed<16, 7, AP_TRN_ZERO, AP_SAT> dot2[NUM_OF_OUTPUT]){
#pragma HLS INTERFACE s_axilite port=dot2
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE s_axilite port=outs
#pragma HLS INTERFACE axis register both port=ins
    ap_ufixed<8, 0, AP_TRN_ZERO, AP_SAT> buf[ROW_PIXELS][COULMN_PIXELS];
    ap_fixed<13, 6, AP_TRN_ZERO, AP_SAT> conv_out[NUM_OF_KERNELS][ROW_PIXELS-4][COULMN_PIXELS-4];
    ap_fixed<13, 6, AP_TRN_ZERO, AP_SAT> pool_out[NUM_OF_KERNELS][(ROW_PIXELS-4)/2][(COULMN_PIXELS-4)/2];
    ap_fixed<16, 7, AP_TRN_ZERO, AP_SAT> dot1[100];
    ap_axiu<32,1,1,1> pix;

    do {
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
    // user が 1になった時にフレームがスタートする
        ins >> pix;
    } while(pix.user == 0);

    // 10 x 56 に整形
    buf_copy1: for(int i=0; i<REDUSED_ROW; i++){
        buf_copy2: for(int j=0; j<REDUSED_COULMN; j++){
            if (!(i==0 && j==0))    // 最初の入力はすでに入力されている
                ins >> pix; // AXI4-Stream からの入力

            if((i>=33 && i<33+ROW_PIXELS) && (j>=2 && j<2+COULMN_PIXELS)){
                buf[i-33][j-2] = (ap_ufixed<8, 0, AP_TRN_ZERO, AP_SAT>)((ap_ufixed<16, 8, AP_TRN_ZERO, AP_SAT>)(pix.data & 0xff) / 256);
            }
        }
    }

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<NUM_OF_KERNELS; i++){ // カーネルの個数
        CONV2: for(int j=0; j<ROW_PIXELS-4; j++){
            CONV3: for(int k=0; k<COULMN_PIXELS-4; 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_weight[i][0][m][n];
                    }
                }
                conv_out[i][j][k] += conv1_bias[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<NUM_OF_KERNELS; i++){
        POOL2: for(int j=0; j<ROW_PIXELS-4; j += 2){
            POOL3: for(int k=0; k<COULMN_PIXELS-4; 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<NUM_OF_KERNELS; i++){
            af1_dot3: for(int j=0; j<(ROW_PIXELS-4)/2; j++){
                af1_dot4: for(int k=0; k<(COULMN_PIXELS-4)/2; k++){
                    dot1[col] += pool_out[i][j][k]*af1_weight[i*((ROW_PIXELS-4)/2)*((COULMN_PIXELS-4)/2)+j*((COULMN_PIXELS-4)/2)+k][col];
                }
            }
        }
        dot1[col] += af1_bias[col];

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

    af2_dot1: for(int col=0; col<NUM_OF_OUTPUT; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<100; row++){
            dot2[col] += dot1[row]*af2_weight[row][col];
        }
        dot2[col] += af2_bias[col];
    }

    max_ap_fixed(dot2, outs);

    return(0);
}

int max_ap_fixed(ap_fixed<16, 7, AP_TRN_ZERO, AP_SAT> out[NUM_OF_OUTPUT], ap_uint<2> &out_num){
    int max_id;
    ap_fixed<16, 7, AP_TRN_ZERO, AP_SAT> max;

    for(int i=0; i<NUM_OF_OUTPUT; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    out_num = (ap_uint<2>)max_id;

    return(0);
}



course_conv_nn2_axis3_tb.cpp を貼っておく。(2018/10/06:変更、バグフィックス)

// course_conv_nn2_axis3_tb.cpp
// 2018/10/03 by marsee
// Keras を使用して学習した
//

#include <iostream>
#include "hls_opencv.h"
#include "ap_axi_sdata.h"
#include "hls_video.h"
#include <dirent.h>

#define MAX_HEIGHT  600
#define MAX_WIDTH   800

typedef hls::stream<ap_axiu<32,1,1,1> > AXI_STREAM;
typedef hls::Mat<MAX_HEIGHT, MAX_WIDTH, HLS_8UC3> RGB_IMAGE;
typedef hls::Mat<MAX_HEIGHT, MAX_WIDTH, HLS_8UC1> GRAY_IMAGE;

using namespace cv;

#define NUM_OF_OUTPUT   3

//#define LOOP_COUNT        35  // test_images
#define LOOP_COUNT    1    // for C/RTL Co-Simulation

//#define STRAIGHT_IMAGE_NAME     "train/straight"
//#define LEFT_IMAGE_NAME    "train/left"
//#define RIGHT_IMAGE_NAME   "train/right"
#define STRAIGHT_IMAGE_NAME     "test/straight_test"
#define LEFT_IMAGE_NAME    "test/left_test"
#define RIGHT_IMAGE_NAME   "test/right_test"

const char CUR_DIR[] = ".";
const char PAR_DIR[] = "..";

int course_conv_nn2_axis3(hls::stream<ap_axiu<32,1,1,1> >& ins, ap_uint<2> &outs,
        ap_fixed<16, 7, AP_TRN_ZERO, AP_SAT> dot2[NUM_OF_OUTPUT]);
int resize_gray(AXI_STREAM& ins, AXI_STREAM& outs);
int main_output_loop(char *buf, int loop_count, int correct_data);

int main () {
    char buf[200];

    sprintf(buf, "%s", STRAIGHT_IMAGE_NAME);
    main_output_loop(buf, LOOP_COUNT, 1);

    sprintf(buf, "%s", LEFT_IMAGE_NAME);
    main_output_loop(buf, LOOP_COUNT, 0);

    sprintf(buf, "%s", RIGHT_IMAGE_NAME);
    main_output_loop(buf, LOOP_COUNT, 2);

    return(0);
}

int main_output_loop(char *buf, int loop_count, int correct_data){
    char bmp_file_name[200];
    ap_uint<2> outs;
    AXI_STREAM src_axi, dst_axi;
    Mat src;
    ap_fixed<16, 7, AP_TRN_ZERO, AP_SAT> dot2[NUM_OF_OUTPUT];
    int err_num = 0;
    DIR *dir;
    struct dirent *dp;

    if((dir=opendir(buf)) == NULL){
        fprintf(stderr, "%s Open Error\n", buf);
        exit(1);
    }

    for(int i=0; i<loop_count;){
        if((dp=readdir(dir)) == NULL){
            fprintf(stderr, "readdir(dir) == NULL\n");
            exit(1);
        }

        if(strcmp(CUR_DIR, dp->d_name)!=0 && strcmp(PAR_DIR, dp->d_name)!=0){
            sprintf(bmp_file_name, "%s/%s", buf, dp->d_name);
        }else{
            continue;
        }
        printf("%s\n", bmp_file_name);

        // OpenCV で 画像を読み込む
        src = imread(bmp_file_name);

        // BGR から RGBへ変換
        Mat src_rgb;
        cvtColor(src, src_rgb, CV_BGR2RGB);

        // Mat フォーマットから AXI4 Stream へ変換
        cvMat2AXIvideo(src_rgb, src_axi);

        // resize_gray() 関数をコール
        resize_gray(src_axi, dst_axi);

        course_conv_nn2_axis3(dst_axi, outs, dot2);

        if((int)outs != correct_data){
            //printf("*%s\n", bmp_file_name);
            printf("correct data = %d, outs = %d\n", correct_data, (int)outs);
            for(int i=0; i<NUM_OF_OUTPUT; i++)
                printf("dot2[%d] = %f ", i, (float)dot2[i]);
            printf("\n");
            err_num++;
        }
        i++;
    }
    if(correct_data == 1)
        printf("Straight error is %d\n\n", err_num);
    else if(correct_data == 0)
        printf("Left error is %d\n\n", err_num);
    else // if(correct_data == 2)
        printf("Right error is %d\n\n", err_num);

    closedir(dir);
    return(0);
}

int resize_gray(AXI_STREAM& ins, AXI_STREAM& outs){

    RGB_IMAGE org_img(600, 800);
    GRAY_IMAGE org_img_g(600, 800);
    GRAY_IMAGE resize_img_g(45, 60);
    RGB_IMAGE resize_img(45, 60);

    hls::AXIvideo2Mat(ins, org_img);
    hls::CvtColor<HLS_RGB2GRAY>(org_img, org_img_g);
    hls::Resize(org_img_g, resize_img_g);
    hls::CvtColor<HLS_GRAY2RGB>(resize_img_g, resize_img);
    hls::Mat2AXIvideo(resize_img, outs);

    return(0);
}

  1. 2018年10月04日 05:33 |
  2. Zybot
  3. | トラックバック:0
  4. | コメント:0

ZYBOt の白線間走行用CNNをVivado HLS 2018.2 で試しに実装した

ZYBOt のコースをKeras で学習した”の続き。

(2018/10/03:変更)
(2018/10/06 : 修正)画像を増やすスクリプトが間違っていたので、間違いを修正してやり直しました。


前回は、今まで作ってきたトレーニング・ファイルやラベル・ファイル、テスト・ファイルやラベル・ファイルを使用して、Keras で学習させ、重みやバイアスをC ヘッダ・ファイルに出力した。今回は、その重みやバイアスのC ヘッダ・ファイルを使用してVivado HLS 2018.2 でプロジェクトを作成して、量子化ビット長を設定し、C シミュレーションとC コードの合成を行う。

まずは、Vivado HLS 2018.2 の course_conv_nn2 プロジェクトを示す。
ZYBOt_Keras_28_180930

course_conv_nn2.cpp を貼っておく。

// course_conv_nn2.cpp
// 2018/09/30 by marsee
// 畳み込み層のカーネル数 2
//

#include <ap_fixed.h>

#include "conv1_weight.h"
#include "conv1_bias.h"
#include "af1_weight.h"
#include "af1_bias.h"
#include "af2_weight.h"
#include "af2_bias.h"

#define NUM_OF_KERNELS 2
#define COULMN_PIXELS 56
#define ROW_PIXELS 10
#define ALL_PIXELS 560
#define NUM_OF_OUTPUT 3

int course_conv_nn(ap_ufixed<8, 0, AP_TRN_ZERO, AP_SAT> in[ALL_PIXELS], ap_fixed<12, 7, AP_TRN_ZERO, AP_SAT> out[NUM_OF_OUTPUT]){
    ap_ufixed<8, 0, AP_TRN_ZERO, AP_SAT> buf[ROW_PIXELS][COULMN_PIXELS];
    ap_fixed<13, 3, AP_TRN_ZERO, AP_SAT> conv_out[NUM_OF_KERNELS][ROW_PIXELS-4][COULMN_PIXELS-4];
    ap_fixed<13, 3, AP_TRN_ZERO, AP_SAT> pool_out[NUM_OF_KERNELS][(ROW_PIXELS-4)/2][(COULMN_PIXELS-4)/2];
    ap_fixed<16, 5, AP_TRN_ZERO, AP_SAT> dot1[100];
    ap_fixed<16, 7, AP_TRN_ZERO, AP_SAT> dot2[NUM_OF_OUTPUT];

    buf_copy1: for(int i=0; i<ROW_PIXELS; i++)
        buf_copy2: for(int j=0; j<COULMN_PIXELS; j++)
            buf[i][j] = in[i*COULMN_PIXELS+j];

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<NUM_OF_KERNELS; i++){ // カーネルの個数
        CONV2: for(int j=0; j<ROW_PIXELS-4; j++){
            CONV3: for(int k=0; k<COULMN_PIXELS-4; 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_weight[i][0][m][n];
                    }
                }
                conv_out[i][j][k] += conv1_bias[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<NUM_OF_KERNELS; i++){
        POOL2: for(int j=0; j<ROW_PIXELS-4; j += 2){
            POOL3: for(int k=0; k<COULMN_PIXELS-4; 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<NUM_OF_KERNELS; i++){
            af1_dot3: for(int j=0; j<(ROW_PIXELS-4)/2; j++){
                af1_dot4: for(int k=0; k<(COULMN_PIXELS-4)/2; k++){
                    dot1[col] += pool_out[i][j][k]*af1_weight[i*((ROW_PIXELS-4)/2)*((COULMN_PIXELS-4)/2)+j*((COULMN_PIXELS-4)/2)+k][col];
                }
            }
        }
        dot1[col] += af1_bias[col];

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

    af2_dot1: for(int col=0; col<NUM_OF_OUTPUT; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<100; row++){
            dot2[col] += dot1[row]*af2_weight[row][col];
        }
        dot2[col] += af2_bias[col];

        out[col] = dot2[col];
    }

    return(0);
}


C シミュレーションを行った。
ZYBOt_Keras_45_181006

ハードウェアのエラーは 98.9 % 、ソフトウェアのエラーは 99.0 % だった。これは 1000 個のデータでの精度なので、他の部分ではもっと精度が悪いことが考えられる。というのも、”ZYBOt のコースをKeras で学習した”では、テスト・データでの精度は、約 92.4 % だからだ。
すべてのログを示す。

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
   Compiling ../../../course_conv_nn2_tb.cpp in debug mode
   Compiling ../../../course_conv_nn2.cpp in debug mode
   Generating csim.exe
id = 38, max_id_ref = 0, max_id_hw = 2
id = 38, max_id_ref = 0, max_id_sw = 2
id = 45, max_id_ref = 2, max_id_hw = 1
id = 45, max_id_ref = 2, max_id_sw = 1
id = 47, max_id_ref = 0, max_id_hw = 1
id = 47, max_id_ref = 0, max_id_sw = 1
id = 74, max_id_ref = 0, max_id_hw = 1
id = 74, max_id_ref = 0, max_id_sw = 1
id = 114, max_id_ref = 2, max_id_hw = 1
id = 114, max_id_ref = 2, max_id_sw = 1
id = 117, max_id_ref = 0, max_id_hw = 1
id = 117, max_id_ref = 0, max_id_sw = 1
id = 124, max_id_ref = 0, max_id_hw = 2
id = 124, max_id_ref = 0, max_id_sw = 2
id = 133, max_id_ref = 2, max_id_hw = 1
id = 133, max_id_ref = 2, max_id_sw = 1
id = 137, max_id_ref = 2, max_id_hw = 1
id = 137, max_id_ref = 2, max_id_sw = 1
id = 139, max_id_ref = 1, max_id_hw = 2
id = 142, max_id_ref = 2, max_id_hw = 1
id = 142, max_id_ref = 2, max_id_sw = 1

hw_err_cnt = 11, sw_err_cnt = 10
hw accuracy = 98.900002%, sw accuracy = 99.000001%

INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************



次に C コードの合成を行った。ここでは、あくまで目安の値を確認することを目的としている。実際に使用するIP はDMA を付ける必要があるので、後で実装する。
ZYBOt_Keras_46_181006
ZYBOt_Keras_47_181006

Estimated は 8.25 ns だった。Latency は 171774 クロックで、100 MHz クロックでは、約 1.72 ms となる。
リソース使用量は、BRAM_18K が 13 個、DSP48E が 8 個、FF が 726 個、LUT が 2531 個だった。
  1. 2018年10月02日 05:01 |
  2. Zybot
  3. | トラックバック:0
  4. | コメント:0
»