第６回AIEDGEコンテスト KV260でRISC-Vを動かせなかったメモ

# はじめに

2022/08/17〜2023/01/29の期間で開催されていた第6回AIエッジコンテストに参加しました。

https://signate.jp/competitions/732

KV-260も提供いただき、なんとか提出まで行きたかったのですが、RISC-Vの実行部分でつまづいてしまい動作させることができませんでした。
同様に動かせなかった人の問題解決のヒントになればと思い、試したことを共有します。

# 試したこと

## OpenCLインターフェースを用いたriscvカーネルの呼び出し

今回のリファレンスカーネルではRISC-VをVitisアクセラレーターとして追加しているようだったので、OpenCLで呼び出しプログラムを作成し実行しました。
プログラムは以下のXilinxのサンプルをもとに作成しました。なお、RISC-Vに実行させるプログラムは[lp6mさんのコード](https://qiita.com/lp6m/items/fc8414998ac35d96ca0b)を参考にさせていただきました。



``` cpp: RISC-Vに実行させるプログラム
    iBus[0] = 0xA0002437; //  0: lui s0,0x41000
    iBus[1] = 0x00040413; //  4: mv  s0,s0
    iBus[2] = 0x00042603; //  8: lw  a2,0(s0) # 0x41000000
    iBus[3] = 0x00442683; //  C: lw  a3,4(s0)
    iBus[4] = 0x00d60733; // 10: add a4,a2,a3
    iBus[5] = 0x00e42423; // 14: sw  a4,0(s0) # 0x41000000
    iBus[6] = 0x0000006f; // 18: j   0x18
```

- 参考にしたOpenCLのサンプル

https://github.com/Xilinx/Vitis_Accel_Examples/tree/master/hello_world

<details><summary>作成したコード（長いので折りたたみにしています）</summary>

``` cpp:host.cpp
#include "xcl2.hpp"
#include <algorithm>
#include <vector>

int main(int argc, char** argv) {
    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " <XCLBIN File>" << std::endl;
        return EXIT_FAILURE;
    }

    std::string binaryFile = argv[1];
    cl_int err;
    cl::Context context;
    cl::Kernel krnl_riscv;
    cl::CommandQueue q;

    auto devices = xcl::get_xil_devices();
    auto fileBuf = xcl::read_binary_file(binaryFile);
    cl::Program::Binaries bins{{fileBuf.data(), fileBuf.size()}};
    bool valid_device = false;
    for (unsigned int i = 0; i < devices.size(); i++) {
        auto device = devices[i];
        OCL_CHECK(err, context = cl::Context(device, nullptr, nullptr, nullptr, &err));
        OCL_CHECK(err, q = cl::CommandQueue(context, device, CL_QUEUE_PROFILING_ENABLE, &err));
        std::cout << "Trying to program device[" << i << "]: " << device.getInfo<CL_DEVICE_NAME>() << std::endl;
        cl::Program program(context, {device}, bins, nullptr, &err);
        if (err != CL_SUCCESS) {
            std::cout << "Failed to program device[" << i << "] with xclbin file!\n";
        } else {
            std::cout << "Device[" << i << "]: program successful!\n";
            OCL_CHECK(err, krnl_riscv = cl::Kernel(program, "riscv", &err));
            valid_device = true;
            break; // we break because we found a valid device
        }
    }
    if (!valid_device) {
        std::cout << "Failed to program any device found, exit!\n";
        exit(EXIT_FAILURE);
    }

    // 変更①iBus, dBusのメモリを用意して、iBusにRISC-V用のプログラムを書き込み
    std::vector<int, aligned_allocator<int> > iBus(7);
    std::vector<int, aligned_allocator<int> > dBus(3);

    iBus[0] = 0xA0002437; //  0: lui s0,0x41000
    iBus[1] = 0x00040413; //  4: mv  s0,s0
    iBus[2] = 0x00042603; //  8: lw  a2,0(s0) # 0x41000000
    iBus[3] = 0x00442683; //  C: lw  a3,4(s0)
    iBus[4] = 0x00d60733; // 10: add a4,a2,a3
    iBus[5] = 0x00e42423; // 14: sw  a4,0(s0) # 0x41000000
    iBus[6] = 0x0000006f; // 18: j   0x18
    
    OCL_CHECK(err, cl::Buffer buffer_iBus(context, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY, sizeof(int) * 7, iBus.data(), &err));
    OCL_CHECK(err, cl::Buffer buffer_dBus(context, CL_MEM_USE_HOST_PTR | CL_MEM_WRITE_ONLY, sizeof(int) * 3, dBus.data(), &err));

    std::cout << "Buffer OK" << std::endl;

    // 変更②カーネル引数をriscvカーネル用に変更
    uint reset_riscv = 0;
    uint interrupt_riscv = 0;
    uint ABS_ADDRESS = 0;
    uint SAMPLE = 0;
    OCL_CHECK(err, err = krnl_riscv.setArg(0, reset_riscv)); 		// uint reset_riscv
    OCL_CHECK(err, err = krnl_riscv.setArg(1, interrupt_riscv)); 	// uint interrupt_riscv
    OCL_CHECK(err, err = krnl_riscv.setArg(2, ABS_ADDRESS)); 		// uint ABS_ADDRESS
    OCL_CHECK(err, err = krnl_riscv.setArg(3, SAMPLE)); 		// uint SAMPLE
    OCL_CHECK(err, err = krnl_riscv.setArg(4, buffer_dBus)); 		// int* dBus
    OCL_CHECK(err, err = krnl_riscv.setArg(5, buffer_iBus)); 		// int* iBus

    std::cout << "Set Kernel OK" << std::endl;

    OCL_CHECK(err, err = q.enqueueMigrateMemObjects({buffer_iBus}, 0));
    q.finish();

    std::cout << "Set Mem OK" << std::endl;

    OCL_CHECK(err, err = q.enqueueTask(krnl_riscv));
    q.finish();

    std::cout << "q.enqueueTask(krnl_riscv) OK" << std::endl;

    OCL_CHECK(err, err = q.enqueueMigrateMemObjects({buffer_dBus}, CL_MIGRATE_MEM_OBJECT_HOST));
    std::cout << "q.enqueueMigrateMemObjects({buffer_dBus}, CL_MIGRATE_MEM_OBJECT_HOST) OK" << std::endl;

    for (int i = 0; i < dBus.size(); i++) {
            std::cout << i << ": " << dBus.at(i) << ", ";
    }
    std::cout << std::endl;

    return (EXIT_SUCCESS);
}
```

</details>

上記コードをビルド・実行したところ「Set Mem OK」までは実行されるのですが、`q.finish()`の部分でカーネルから完了通知が来ず、プログラムが停止してしまいました。

## RISC-Vに送ったプログラム内容の確認

以下のページなどでRISC-Vに送ったプログラム内容を改めて確認したところ、以下に問題がありそうでした。

https://progrunner.hatenablog.jp/entry/2017/12/03/221829

``` cpp: RISC-Vに実行させるプログラム(コメント追加)
    iBus[0] = 0xA0002437; //  0: lui s0,0x41000            // s0 = 0x41000000
    iBus[1] = 0x00040413; //  4: mv  s0,s0                 // s0 = s0 //?
    iBus[2] = 0x00042603; //  8: lw  a2,0(s0) # 0x41000000 // a2 = mem[0x41000000]
    iBus[3] = 0x00442683; //  C: lw  a3,4(s0)              // a3 = mem[0x41000000+4]
    iBus[4] = 0x00d60733; // 10: add a4,a2,a3              // a4 = a2+a3
    iBus[5] = 0x00e42423; // 14: sw  a4,0(s0) # 0x41000000 // mem[0x41000000] = a4
    iBus[6] = 0x0000006f; // 18: j   0x18                  // jump(L18)
```

- 計算の入出力に使用するdBusのアドレスが0x41000000になっているが、今回作成したIPでは異なっている可能性がある
  - 今回dBusはAXI経由でホストのDDRとつながっている
    - HPC0とHP1につながっているが、どちらもアドレス0x0? 
  - dBusのアドレスはRISC-Vからは何番に見えるのか？

![Screenshot from 2023-02-05 05-32-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319245/2731e4d6-8189-0d4d-cb40-576d71e5ffb4.png)

![Screenshot from 2023-02-05 05-34-38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319245/e949a369-73fd-f9f8-9e5e-32b9007b17c0.png)


- 実行後一定時間でdBusの値を読み込む形にしているためか、無限ループで待機してしまう

以上の調査を行ったところで、時間切れになってしまいました。

# 課題と感想

今回は時間切れになってしまいましたが、時間があれば以下の点について確認し、なんとかRISC-Vを動かしたかったです。

- [リファレンスのインターフェース定義が書かれたVerilog](https://github.com/basaro/AIEDGE6/blob/main/VexRiscv/riscv_example.sv)の解析
  - RTLカーネルとして動作するようにverilogで書かれた部分の動作
  - ABS_ADDRESSやSAMPLEの意味、使い方
- VexRiscvに関する詳しい実装の調査
  - RISC-VからdBusのアドレスは何番に見えるのか？

今回は残念な結果に終わってしまいましたが、FPGAやRISC-Vの良い勉強になりました。
次回があればぜひリベンジしたいと思います。