RISC-V 32bitコンパイラ環境をDockerfileを用いて構築する

## はじめに
以前、RISCVのコンパイラ環境を構築するのに、Linux初学者だった身が32ビット環境のみの構築に対して時間をかけてしまったのでこちらですぐにできる方法を共有できればと思います。

32ビットのみのコンパイラを構築する方がストレージ消費量が少なく、コンパイラのビルド時間も大幅に短縮されます。

開発において、何度も32ビットコンパイラ構築を行う方には、[riscv-gnu-toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain)の multilib ではなくて32ビットのみだとありがたいですね。（そんな人あんまいない気するけど）
※ multilib は64ビットと32ビットのクロスコンパイラの両方です。構築するのにかなり時間がかかります。

### 構築するDockerイメージの環境について
RISCVのレジスタ長は128bit、64bit、32bitがあります。32ビットのRISCVはレジスタ長が32ビットのRISCVということです。
RV32GCというのは、32ビットRISCV用の命令セットです。
こちらはG; General Purpose ISA(I, M, A, F, D の集合)とC; Compressedの命令セットを含みます。

今回は、DockerでUbuntu18.04環境上でRV32開発用のコンパイラを含むイメージを作成して、コンテナを起動するという形です。

作成するのは以下をすでにインストールされている環境です。
- RV32GC用クロスコンパイラ (参考: [riscv-gnu-toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain))
- [Spike](https://github.com/riscv-software-src/riscv-isa-sim)
- [riscv-pk](https://github.com/riscv-software-src/riscv-pk)

その為Dockerイメージが大きくなってしまうので、ストレージが圧迫されてしまうことにお気をつけください。(約15GB)

※もちろんこの記事の方法ではなく、Dockerfileを用いずにubuntu18.04環境をdocker pullして構築する方法でも構いません

## 方法

まず[Docker](https://docs.docker.com/get-docker/)をインストールしてください。
私はMacだった為、Homebrewを用いてインストールを行いました。
→ 参考: [DockerをHomebrewでMac OSに導入する方法](https://qiita.com/nemui_/items/ed753f6b2eb9960845f7)

Dockerのインストールができましたら、以下の記述のDockerfileを適当なフォルダ内においてください。
``` docker:Dockerfile
FROM ubuntu:18.04

LABEL version="1.0"
LABEL description="RV32GC Compiler Environment"

WORKDIR /tmp/

SHELL ["/bin/bash", "-c"]

RUN apt -y update
RUN apt -y install git autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev

RUN git clone --recursive https://github.com/riscv-collab/riscv-gnu-toolchain.git
ENV RV32 /opt/rv32gc
RUN cd riscv-gnu-toolchain && \
    mkdir build && cd build && \
    ../configure --prefix=$RV32/cross --with-arch=rv32gc --with-abi=ilp32d && \
    make -j$(nproc)

RUN echo 'export PATH=$PATH:$RV32/cross/bin' >> ~/.bashrc

RUN apt install device-tree-compiler
RUN git clone https://github.com/riscv-software-src/riscv-isa-sim.git
RUN cd riscv-isa-sim && \
    mkdir build && cd build && \
    ../configure --prefix=$RV32/spike --with-isa=rv32gc && \
    make && \
    make install
    
RUN echo 'export PATH=$PATH:$RV32/spike/bin' >> ~/.bashrc

RUN git clone https://github.com/riscv-software-src/riscv-pk.git
RUN cd riscv-pk && \
    git reset --hard 423801e35d048187d88fcbff55e20c4c34d27bee && \
    mkdir build && cd build && \
    export PATH=$PATH:$RV32/cross/bin && \
    ../configure --prefix=$RV32/pk --host=riscv32-unknown-elf && \
    make && \
    make install

RUN echo 'export PATH=$PATH:$RV32/pk/riscv32-unknown-elf/bin' >> ~/.bashrc

WORKDIR /workdir

```
今回は「rv32」フォルダ内にそのDockerfileを置いていることを想定しているので、cdコマンドで移動します。
```
cd rv32
```

そして、適当な名前とタグをつけて、Dockerイメージを作成します。
```
docker build -t ubuntu/rv32:1.0 .
```
かなりの時間がかかるので、一旦休んで別のことをして1時間後に戻りましょう。

**余談。。。**

最後の「.」と指定することでイメージの　build　時、デフォルトではフォルダー内にある Dockerfile を実行させます。

（一般的にはDockerfileという名前を使用するのだと思ってますが、何かしらの理由で別ファイルでイメージをビルドしたいときは -f オプションを使用するらしいです。
そこの辺りは、 コマンド ```docker build --help``` とかで確認してください。）

**余談ここまで**


戻ってきたら以下のコマンドを打って、コンテナを作成します。
```
docker run -it ubuntu/rv32:1.0
```
```ubuntu/rv32:1.0``` のかわりに ```[IMAGE ID]``` というものでも大丈夫です。
イメージIDは ``` docker images ``` コマンドで確認ができます。


コンテナに入れたかと思いますので、次に適当なCプログラムをクロスコンパイル＆シミュレーションしていきます。

```
user@computer_name % docker run -it ubuntu/rv32:1.0
root@15ea6a0ef57c:/workdir#
```

## クロスコンパイル＆シミュレーション

適当なCプログラムをとってきます。
```hello.c
#include <stdio.h>

int main()
{
  printf("Hello World\n");
}

```


以下のコマンドでRISCV用の実行ファイル a.out を生成します。
```
riscv32-unknown-elf-gcc hello.c
```

Spikeとriscv-pkを用いてシミュレーションを行ってみましょう。
この際、pkは、フルパスでpkを指定する（[方法1](https://qiita.com/drafts/8571080cb49f2e6735b8/edit#:~:text=%E3%81%BE%E3%81%99%EF%BC%88%E6%96%B9%E6%B3%952%EF%BC%89%E3%80%82-,%E6%96%B9%E6%B3%95,-%EF%BC%91)）か、pk実行ファイルをディレクトリ内にコピーする必要があります（[方法2](https://qiita.com/drafts/8571080cb49f2e6735b8/edit#:~:text=%E6%96%B9%E6%B3%95-,2,-root%4015ea6a0ef57c%3A/workdir)）。

**pk のパスが通っていても [方法1](https://qiita.com/drafts/8571080cb49f2e6735b8/edit#:~:text=%E3%81%BE%E3%81%99%EF%BC%88%E6%96%B9%E6%B3%952%EF%BC%89%E3%80%82-,%E6%96%B9%E6%B3%95,-%EF%BC%91) では pk をフルパスで指定する必要があります。**

ちなみに```--isa```オプションでどの命令セットでシミュレーションするか指定してます。

#### 方法１
```
root@15ea6a0ef57c:/workdir# spike --isa=RV32GC /opt/rv32gc/pk/riscv32-unknown-elf/bin/pk a.out
bbl loader
Hello World
```
一応コンパイラのパスの確認は、例えば```which pk```コマンドでできるようにしてあります。
(コンテナ起動時、必要なパスをすでに通すようにしているため)


```
root@15ea6a0ef57c:/workdir# which pk
/opt/rv32gc/pk/riscv32-unknown-elf/bin/pk
```


#### 方法2
```
root@15ea6a0ef57c:/workdir# cp /opt/rv32gc/pk/riscv32-unknown-elf/bin/pk .
root@15ea6a0ef57c:/workdir# spike --isa=RV32GC pk a.out
bbl loader
Hello World
```

シミュレーションできましたね。

＊ちなみに ```bbl loader```というのはBerkeley Boot Loaderのことです。
気になる方はお調べください。([参考](https://lowrisc.org/docs/build-berkeley-boot-loader/#:~:text=A%20simple%20summary%20of%20BBL,to%20handle%20directly%20in%20hardware.))

### コンテナの出方と入り方
コンテナを出る際には```exit```コマンドを打ってください。
```
root@15ea6a0ef57c:/workdir# exit
```

コンテナをまた起動するには1つとして以下の方法があります。
```
user@computer_name % docker ps -a //コンテナIDの確認
CONTAINER ID   IMAGE             COMMAND       CREATED          STATUS                     PORTS     NAMES
a6fed20a4325   ubuntu/rv32:1.0   "bash"        12 minutes ago   Exited (0) 3 seconds ago             blissful_dhawan
user@computer_name % docker start a6fed20a4325 // [CONTAINER ID]の代わりに[NAMES]値でもOK
user@computer_name % docker attach a6fed20a4325 // [CONTAINER ID]の代わりに[NAMES]でもOK
root@15ea6a0ef57c:/workdir#
```

あとは、VSCodeをお使いになられている場合、コンテナ用の拡張機能を使用する方法がおすすめです。
参考: [VSCode Remote Containerが良い](https://qiita.com/d0ne1s/items/d2649801c6f804019db7)

## 経緯
ご指摘やご質問などございましたらお気軽にお尋ねください。

## 参考

こちらの記事([RISC-Vを使用したアセンブリ言語入門 〜1. 環境構築編〜](https://qiita.com/widedream/items/c135df3277599fa8ebba)) に、各コンパイラについて(riscv-gnu-toolchain, Spike, riscv-pkなど)とても分かりやすく説明がのっておりました。
私も今回この記事を書くときに参考にさせていただきました。