FC2カウンター FPGAの部屋 テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する5(Vitis 編)
FC2ブログ

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

FPGAの部屋

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

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する5(Vitis 編)

テンプレートで書いた畳み込みニューラルネットワークをRTLカーネルとしてVitisで実装する4(Vivado HLS 編 4)”の続き。

前回は、白線走行用畳み込みニューラルネットワークをVivado HLS 2019.2 で IP にした。今回は白線走行用畳み込みニューラルネットワークの xo ファイルを使用して Vitis 2019.2 でアプリケーション・プロジェクトを作成し、実機確認する。

最初に、ホスト・アプリケーションの all_layers_template_host.cpp を示す。

// all_layers_template_host.cpp
// 2019/12/25 by marsee
//

// Vitis-Tutorials/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp のコードを引用します
// https://github.com/Xilinx/Vitis-Tutorials/blob/master/docs/mixing-c-rtl-kernels/reference-files/src/host/host_step1.cpp
#define CL_HPP_CL_1_2_DEFAULT_BUILD
#define CL_HPP_TARGET_OPENCL_VERSION 120
#define CL_HPP_MINIMUM_OPENCL_VERSION 120
#define CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY 1
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <CL/cl2.hpp>
#include <iostream>
#include <fstream>
#include <CL/cl_ext_xilinx.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

#include "layer_general.h"

#include "curve_data_0_100.h"
//#include "curve_data_2500_2600.h"
//#include "curve_data_5000_5100.h"

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

#define NUM_ITERATIONS  300 // C Simulation
//#define NUM_ITERATIONS    1 // C/RTL CoSimulation 2

typedef ap_uint<2> output_type;
typedef ap_fixed<12,7,AP_TRN,AP_WRAP> out_affine_type;

void all_layers_dnn(volatile uint32_t *inm, volatile uint32_t *output,
        volatile int32_t *dot2, int32_t x_size, int32_t y_size);

int all_layers_soft(hls::stream<ap_axiu<32,1,1,1> >& ins, output_type& output,
        float dot2[NUM_OF_OUTPUT]);

static const std::string error_message =
    "Error: Result mismatch:\n"
    "i = %d CPU result = %d Device result = %d\n";

//Some Library functions to be used.
template <typename T>
struct aligned_allocator
{
  using value_type = T;
  T* allocate(std::size_t num)
  {
    void* ptr = nullptr;
    if (posix_memalign(&ptr,4096,num*sizeof(T)))
      throw std::bad_alloc();
    return reinterpret_cast<T*>(ptr);
  }
  void deallocate(T* p, std::size_t num)
  {
    free(p);
  }
};


#define OCL_CHECK(error,call)                                       \
    call;                                                           \
    if (error != CL_SUCCESS) {                                      \
      printf("%s:%d Error calling " #call ", error code is: %d\n",  \
              __FILE__,__LINE__, error);                            \
      exit(EXIT_FAILURE);                                           \
    }

namespace xcl {
std::vector<cl::Device> get_devices(const std::string& vendor_name) {

    size_t i;
    cl_int err;
    std::vector<cl::Platform> platforms;
    OCL_CHECK(err, err = cl::Platform::get(&platforms));
    cl::Platform platform;
    for (i  = 0 ; i < platforms.size(); i++){
        platform = platforms[i];
        OCL_CHECK(err, std::string platformName = platform.getInfo<CL_PLATFORM_NAME>(&err));
        if (platformName == vendor_name){
            std::cout << "Found Platform" << std::endl;
            std::cout << "Platform Name: " << platformName.c_str() << std::endl;
            break;
        }
    }
    if (i == platforms.size()) {
        std::cout << "Error: Failed to find Xilinx platform" << std::endl;
        exit(EXIT_FAILURE);
    }

    //Getting ACCELERATOR Devices and selecting 1st such device
    std::vector<cl::Device> devices;
    OCL_CHECK(err, err = platform.getDevices(CL_DEVICE_TYPE_ACCELERATOR, &devices));
    return devices;
}

std::vector<cl::Device> get_xil_devices() {
    return get_devices("Xilinx");
}

char* read_binary_file(const std::string &xclbin_file_name, unsigned &nb)
{
    std::cout << "INFO: Reading " << xclbin_file_name << std::endl;

    if(access(xclbin_file_name.c_str(), R_OK) != 0) {
        printf("ERROR: %s xclbin not available please build\n", xclbin_file_name.c_str());
        exit(EXIT_FAILURE);
    }
    //Loading XCL Bin into char buffer
    std::cout << "Loading: '" << xclbin_file_name.c_str() << "'\n";
    std::ifstream bin_file(xclbin_file_name.c_str(), std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);
    return buf;
}
};

int main(int argc, char* argv[]){
    hls::stream<ap_axiu<32,1,1,1> > ins_soft;
    output_type output_soft;
    float dot2_soft[NUM_OF_OUTPUT];
    ap_axiu<32,1,1,1> pix;
    int hw_err_cnt = 0;
    int sw_err_cnt = 0;
    const char* xclbinFilename;

    if (argc==2) {
        xclbinFilename = argv[1];
        std::cout <<"Using FPGA binary file specfied through the command line: " << xclbinFilename << std::endl;
    }
    else {
        xclbinFilename = "../lap_filter_axim.xclbin";
        std::cout << "No FPGA binary file specified through the command line, using:" << xclbinFilename <<std::endl;
    }

    // t_train256[][]を入れるメモリをアロケート
    std::vector<int32_t,aligned_allocator<int32_t>> pixel(ROW_PIXELS*COULMN_PIXELS);
    size_t pixel_in_bytes = (ROW_PIXELS*COULMN_PIXELS) * sizeof(int32_t);

    std::vector<uint32_t,aligned_allocator<uint32_t>> output(1);
    size_t output_in_bytes = sizeof(uint32_t);

    std::vector<int32_t,aligned_allocator<int32_t>> dot2(NUM_OF_OUTPUT);
    size_t dot2_in_bytes = (NUM_OF_OUTPUT * sizeof(int32_t));

    std::vector<cl::Device> devices = xcl::get_xil_devices();
    cl::Device device = devices[0];
    devices.resize(1);

    // Creating Context and Command Queue for selected device
    cl::Context context(device);
    cl::CommandQueue q(context, device, CL_QUEUE_PROFILING_ENABLE);

    // Load xclbin
    std::cout << "Loading: '" << xclbinFilename << "'\n";
    std::ifstream bin_file(xclbinFilename, std::ifstream::binary);
    bin_file.seekg (0, bin_file.end);
    unsigned nb = bin_file.tellg();
    bin_file.seekg (0, bin_file.beg);
    char *buf = new char [nb];
    bin_file.read(buf, nb);

    // Creating Program from Binary File
    cl::Program::Binaries bins;
    bins.push_back({buf,nb});
    cl::Program program(context, devices, bins);

    // This call will get the kernel object from program. A kernel is an
    // OpenCL function that is executed on the FPGA.
    cl::Kernel krnl_all_layers_dnn(program,"all_layers_dnn");
        
    // These commands will allocate memory on the Device. The cl::Buffer objects can
    // be used to reference the memory locations on the device.
    cl::Buffer pixel_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            pixel_in_bytes, pixel.data());
    cl::Buffer output_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
            output_in_bytes, output.data());
    cl::Buffer dot2_buf(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY,
            dot2_in_bytes, dot2.data());

    //set the kernel Arguments
    krnl_all_layers_dnn.setArg(0,pixel_buf);
    krnl_all_layers_dnn.setArg(1,output_buf);
    krnl_all_layers_dnn.setArg(2,dot2_buf);
    krnl_all_layers_dnn.setArg(3,COULMN_PIXELS);
    krnl_all_layers_dnn.setArg(4,ROW_PIXELS);

    for(int i=0; i<NUM_ITERATIONS; i++){
        for(int y=0; y<ROW_PIXELS; y++){
            for(int x=0; x<COULMN_PIXELS; x++){
                // 1 画面分のデータを ins、ins_soft に入力する
                pix.data = ap_uint<32>(t_train_256[i][y*COULMN_PIXELS+x]);

                if (x==0 && y==0)    // 最初のデータの時に TUSER を 1 にする
                    pix.user = 1;
                else
                    pix.user = 0;

                if (x == COULMN_PIXELS-1) // 行の最後でTLASTをアサートする
                    pix.last = 1;
                else
                    pix.last = 0;

                ins_soft << pix;

                pixel[y*COULMN_PIXELS+x] = uint32_t(t_train_256[i][y*COULMN_PIXELS+x]);
            }
        }

        // Data will be transferred from system memory over PCIe to the FPGA on-board
        // DDR memory.
        q.enqueueMigrateMemObjects({pixel_buf},0/* 0 means from host*/);

        //Launch the Kernel
        q.enqueueTask(krnl_all_layers_dnn);
        //q.enqueueTask(krnl_all_layers_dnn);

        // The result of the previous kernel execution will need to be retrieved in
        // order to view the results. This call will transfer the data from FPGA to
        // source_results vector

        q.enqueueMigrateMemObjects({output_buf, dot2_buf},CL_MIGRATE_MEM_OBJECT_HOST);

        all_layers_soft(ins_soft, output_soft, dot2_soft);

        int t_test_num = 0;
        for(int m=0; m<NUM_OF_OUTPUT; m++){
            if(t_test[i][m] == 1.0f){
                t_test_num = m;
                break;
            }
        }
        // out と out_soft を比較する
        /* cout << "output" << " = " << int(output) << " output_soft = " << int(output_soft) << endl;
        for(int j=0; j<NUM_OF_OUTPUT; j++){
            cout << "dot2[" << j << "] = " << float(dot2[j]) << " dot2_soft[" << j << "] = " << dot2_soft[j] << endl;
        } */

        if(int(output[0]) != t_test_num){
            std::cout << "hw_error: i = " << i << " output = " << int(output[0]) << " t_test_num = " << t_test_num << std::endl;
            hw_err_cnt++;
            //return(1);
        }
        if(int(output_soft) != t_test_num){
            std::cout << "sw_error: i = "<< i << " output_soft = " << int(output_soft) << " t_test_num" " = " << t_test_num << std::endl;
            sw_err_cnt++;
            //return(1);
        }
        if(int(output[0]) != t_test_num || int(output_soft) != t_test_num){
            for(int j=0; j<NUM_OF_OUTPUT; j++){
                std::cout << "dot2[" << j << "] = " << std::fixed << std::setprecision(8) << float(dot2[j])/float(256.0) << "   dot2_soft[" << j << "] = " << dot2_soft[j] << std::endl;
            }
            std::cout << std::endl;
        }
    }
    q.finish();

    std::cout << "hw_err_cnt = " << hw_err_cnt << " sw_err_cnt = " << sw_err_cnt << std::endl;

    return(0);
}


アクセラレーション用 all_layers_template アプリケーション・プロジェクトを作成した。

ultra96v2_min2 プラットフォームを使用している。

Hardware Functions に all_layers_template を指定した。

xclbin ファイル名を all_layters_dnn に変更した。

Assistant ウインドウで all_layers_template_system/all_layers_template/Hardware を右クリックし右クリックメニューからBuild を選択してビルドし、成功した。
template_cnn_2019_02_19_200113.png

Ultra96-V2 を電源ON して、Linux を起動した。
root でログインして、zocl ドライバを insmod した。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko
template_cnn_2019_02_20_200113.png

Hardware を右クリックし右クリックメニューから Run -> Run Configurations... を選択した。
Single Application Debug をダブルクリックして、Debugger _all_layers_template を作成した。
template_cnn_2019_02_23_200113.png

template_cnn_2019_02_24_200113.png

Run ボタンをクリックして、アプリケーション・ソフトを起動したところ、アプリケーション・ソフトが実行されて結果が出力された。
どうも dot2[0] の値が dot2[2] の値と同じになっているようだ。output の値は問題ないようで、 hw_err_cnt = 8 になっている。
職場のパソコンで、同じコードでやってみたところ、hw_err_cnt = 200 となっていた。パソコンによって違うのだろうか?
template_cnn_2019_02_21_200113.png

シリアル・コンソールの様子を示す。
template_cnn_2019_02_22_200113.png
  1. 2020年01月14日 03:34 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


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

トラックバック URL
https://marsee101.blog.fc2.com/tb.php/4763-b4fcac2d
この記事にトラックバックする(FC2ブログユーザー)