FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

LLVMのバックエンドを作るための第一歩 (1. LLVMバックエンドの仕組みとRISC-V基本情報)

f:id:msyksphinz:20190425001356p:plain
LLVM Compiler Infrastructure

LLVMのバックエンドに焦点を当て、オリジナルのアーキテクチャターゲットをLLVMに追加する。 オリジナルのアーキテクチャといっても、一から作ると仕様の部分まで作る必要があるし、またそういう部分で本書の説明を策必要が生じてしまうため、RISC-Vをベースにしたオリジナルのアーキテクチャを作成する。

LLVMにはRISC-Vのバックエンド実装はすでに入っている。 しかしそれをパックてしまっては意味がないので、オリジナルのバックエンドを作成するために、RISC-Vと似たようなアーキテクチャとして"MYRISCVX"というターゲットを定義する。

LLVMは、このようなオリジナルのアーキテクチャを追加するための機構が備わっている。 まず、MYRISCVXを追加するにあたり、基本的な用語から押さえていくことにする。

  • TargetMachine : ターゲットのアーキテクチャそのものを示する。LLVMにはTargetMachineクラスがあり、ターゲットアーキテクチャのすべての情報を管理する役割の担いう。以下の図は、TargetMachineに関連するクラスの相関図を示している。TargetMachineを親クラスとして、派生クラスに各バックエンドアーキテクチャのクラスが設計されていることが分かる。 MYRISCVXアーキテクチャLLVMに新規追加する場合は、MYRISCVXTargetMachineクラスを派生させることになる。

    • lib/Target/MYRISCVX/MYRISCVXTargetMachine.h
    ...
    namespace llvm {
    class formatted_raw_ostream;
    class MYRISCVXRegisterInfo;
    
    // MYRISCVXターゲットマシンは、LLVMTargetMachine(ひいてはTargetMachine)クラスから派生しています。
    class MYRISCVXTargetMachine : public LLVMTargetMachine {
        ...
  • Target Description File : tdファイル : その名の通り、LLVMバックエンドのターゲットマシンの情報を記述する。CPUには様々な情報が含まれています。レジスタのサイズ、名前、数、そして命令フォーマット、命令名、その命令がどのような意味を持つのか。その命令はどのような場合に使われるのか。。。これらのことをTarget Descriptionファイルに記述すると、LLVMはより詳細なバックエンドの実装を手助けするためのファイルを生成してくれる。

    MYRISCVXInstrInfo.td, MYRISCVXRegisterInfo.td というtdファイルをtblgenが処理し、その結果サポートファイルが生成される。より詳細な命令の制御を行いたい場合、そのサポートファイルを使って実装を行う。LLVMでのバックエンドの追加は、tdファイルの記述とその詳細設計にほとんどの時間が費やされる。 このtdファイルに関してはネットを調べてもあまり情報が出て来ないので、サンプル(というか既存のアーキテクチャに書いてある実装)を見ながら進めていくことになる。

データレイアウト

ターゲットアーキテクチャには、データレイアウトというものが定義されている。データレイアウトとは、おおざっぱに言えばそのアーキテクチャがどのようなアドレス空間を想定し、どの大きさの型をサポートしているのか、アライメントはどうするのか、デフォルトの型は何を使うのか、などと言うものを指定する。

  • lib/Target/Mips/MipsTargetMachine.cpp
// ※ MIPSには、o32, n32, n64の3つのABIがあるのでちょっと複雑。
// o32 : 32-bit MIPSのABI。int型は32ビット、long型も32ビット、ポインタは32ビット
// n64 : 64-bit MIPSのABI。int型は32ビット、long型は64ビット、ポインタは64ビット
// n32 : 64-bit MIPSのABI。int型は32ビット、long型は32ビット、ポインタは32ビット。

static std::string computeDataLayout(const Triple &TT, StringRef CPU,
                                     const TargetOptions &Options,
                                     bool isLittle) {
  std::string Ret;
  MipsABIInfo ABI = MipsABIInfo::computeTargetABI(TT, CPU, Options.MCOptions);

  // MIPSにはビッグエンディアンとリトルエンディアンのモードがある
  if (isLittle)
    Ret += "e";
  else
    Ret += "E";

  // マングリングの設定。詳細は省略だが、アセンブリ内でのラベル等の表現方法の指定。
  if (ABI.IsO32())
    Ret += "-m:m";
  else
    Ret += "-m:e";

  // ポインタサイズの指定。64bitモードでない場合は32ビットサイズを使用する。
  if (!ABI.IsN64())
    Ret += "-p:32:32";

  // 8bitと16bitの整数は、ABIとしては8ビット、16ビットアライメントである。
  // しかし、どちらも32ビットアライメントに調整しようとする。
  // 64ビットデータは、64ビットアライメントである。
  Ret += "-i8:8:32-i16:16:32-i64:64";

  // ネイティブな整数のサイズ。N64とN32では32ビット64ビットと、128ビットもサポートする。
  // それ以外は、32ビットと64ビットをサポートする。
  if (ABI.IsN64() || ABI.IsN32())
    Ret += "-n32:64-S128";
  else
    Ret += "-n32-S64";

  return Ret;
}

RISC-V の ABIにはどんなものがある?

ABIというのはApplication Binary Interfaceのことで、アプリケーションとシステム(OSやライブラリなど)の間で取り決められるインタフェースのこと。 例えば、関数呼び出しの際の引数の取り扱い。データ型、データのアライメント、システムコールの規約、実行ファイルのフォーマット、ライブラリのフォーマットなどを規定する。

このABIが同一のシステム間では、コンパイル済みのアプリケーションはリコンパイルすることなく動かすことができるものである。 したがって、CPUアーキテクチャにおいてABIの決定は非常に重要となる。

RISC-Vには、ilp32, ilp32f, ilp32d, lp64, lp64f, lp64d が定義されている。

ABI ilp32 ilp32f ilp32d lp64 lp64f lp64d
char型のサイズ 8 8 8 8 8 8
short型のサイズ 16 16 16 16 16 16
int型のサイズ 32-bit 32-bit 32-bit 32-bit 32-bit 32-bit
long型のサイズ 32-bit 32-bit 32-bit 64-bit 64-bit 64-bit
ポインタのサイズ 32-bit 32-bit 32-bit 64-bit 64-bit 64-bit
浮動小数点命令の取り扱い 浮動小数点の引数はレジスタ経由で渡されない 32ビット以下の浮動小数点数の引数はレジスタ渡しが行われる。 64ビット以下の浮動小数点数の引数はレジスタ渡しが行われる。 浮動小数点の引数はレジスタ経由で渡されない 32ビット以下の浮動小数点数の引数はレジスタ渡しが行われる。 64ビット以下の浮動小数点数の引数はレジスタ渡しが行われる。

ターゲットのTriple xxx

よく、GCCなどで`riscv64-unknown-elfなどという接頭語を見るが、これはどういう並びになっているのかというと、

ARCHITECTURE-VENDOR-OPERATING_SYSTEM
ARCHITECTURE-VENDOR-OPERATING_SYSTEM-ENVIRONMENT

という並びになって、LLVMで定義されている。

SiFive社のRISC-Vボード HiFive Unleashedを使ってみる (3. Debianのビルド試行)

f:id:msyksphinz:20190419223924p:plain
HiFive Unleashed 評価ボード

HiFive UnleashedはBuildrootだけでなく、それ以外にもDebian / Fedora Linuxをサポートしている。 Debianの起動は、Makefileの記述によると簡単に実行できるようだ。

cd freedom-u-sdk
sudo make DISK=/dev/sdd format-demo-image -j$(nproc)

これでしばらく待っていると、Debianのディスクイメージをダウンロードしてビルドが行われる。 CPU数にもよるが、数分~数10分でビルドが完了する。 DebianのディスクイメージはマイクロSDカードのパーティション2に展開される。 BuildrootでLinuxを起動後、パーティション2をマウントし、Debianの環境に入る必要がある。

マイクロSDカードを挿入し、HiFive Unleashedを立ち上げるとBuildrootが立ち上がり、Linuxにログインする。 その後、パーティション2をマウントする。

udhcpc: sending discover
udhcpc: sending select for 192.168.11.17
udhcpc: lease of 192.168.11.17 obtained, lease time 172800
deleting routers
adding dns 192.168.11.1
OK
Starting dropbear sshd: [    7.030000] random: dropbear: uninitialized urandom r                                         ead (32 bytes read)
OK

Welcome to Buildroot
buildroot login: root
Password: [sifive]
# mount /dev/mmcblk0p2 /mnt
# chroot /mnt

/etc/debian_versionを確認する。

# cat /etc/debian_version
buster/sid

procをマウントする。

mount proc -t proc /proc

パッケージシステムを更新して、Debianでパッケージを管理できるようにする。

export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true
export LC_ALL=C LANGUAGE=C LANG=C`dash.preinst`

マニュアルにはdash.preinstを実行するように指示がある、これはなぜか存在しなかったので、スキップする。

/var/lib/dpkg/info/dash.preinst install
dpkg --configure -a
apt -f install
Reading package lists... Done
Building dependency tree
Reading state information... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

これで、aptなども使えるようになっているはずです。試してみる。

apt install build-essential ccache gawk texinfo bison flex libmpfr-dev libgmp-dev libmpc-dev zlib1g-dev bc unzip libssl-dev python wget gdisk libncurses5-dev

ネットワークに接続されていなかった。一応LANケーブルは接続しているはずだが?確認する。

Err:1 http://deb.debian.org/debian-ports unstable InRelease
  Temporary failure resolving 'deb.debian.org'
Err:2 http://deb.debian.org/debian-ports unreleased InRelease
  Temporary failure resolving 'deb.debian.org`

RISC-VでZephyr OSを動作させる (3. HiFive1のZephyrの上でPhiloshperのプログラムを動かす)

f:id:msyksphinz:20190415004633p:plain

Zephyrとは、Linux Foundationのプロジェクトの一つでもあり、RTOS(Real Time Operating System)の一つだ。

次は、SiFive社から販売されている32bit RISC-VボードHiFive1を使ってリアルタイムOSであるZephyrを動かしてみる。

f:id:msyksphinz:20190416020805p:plain

参考にするのは、RISC-VのGetting Started GuideのZephyrの章(https://risc-v-getting-started-guide.readthedocs.io/en/latest/zephyr-hifive1.html#flashing)だ。 こちらの章に従って進めていくと、HiFive1ボードの上でZephyr OSを動作させ、アプリケーションを動かすことができる。

Philoshper問題のプログラムを動かす

次は、もう少し大きめのプログラムを動かしたい。サンプルプログラムの中で、今度はphilosopherを動かしてみることにした。 これは哲学者の食事問題のプログラムだ。うまく動くと、プロセス間でリソースの取得状況がリアルタイムに確認できる。

サンプルプログラムは、${ZEPHYR_BASE}/samples/philosopherを使用する。

まずは作業環境を整える。

mkdir build-philosopher
cd build-philosopher
cmake -DBOARD=hifive1 $ZEPHYR_BASE/samples/philosopher
make -j $(nproc)
cd ..

これでZephyrとアプリケーションのコンパイルができる。HiFive1にダウンロードして実行してみよう。

コンパイルしたプログラムをHiFive1にダウンロードするためには、Freedom-E-SDK環境を使用する。 まずは、HiFive1のコンフィグレーションを使用してopenocdを立ち上げ、HiFive1と接続してみる。

cd freedom-e-sdk
sudo openocd/bin/openocd -f bsp/sifive-hifive1/openocd.cfg

次に別のターミナルを開き、GDBを立ち上げる。 そして、ZephyrのバイナリをHiFive1に書き込んでみよう。 GDBを立ち上げた後、下記リストの先頭が(gdb)と記述されている行のみ入力していけばよい。

./freedom-e-sdk/riscv-gnu-toolchain/bin/riscv64-unknown-elf-gdb
(gdb) set remotetimeout 240
(gdb) target extended-remote localhost:3333
Remote debugging using localhost:3333
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x20401244 in ?? ()
(gdb) monitor reset halt
JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (SiFive, Inc.), part: 0x0e31, ver: 0x1)
halted at 0x20401244 due to debug interrupt
(gdb) monitor flash protect 0 64 last off
cleared protection for sectors 64 through 255 on flash bank 0
(gdb) load /home/msyksphinz/work/riscv/zephyr/hifive1/build-philosopher/zephyr/zephyr.elf
Loading section vector, size 0x10 lma 0x20400000
Loading section exceptions, size 0x268 lma 0x20400010
Loading section text, size 0x3ac8 lma 0x20400278
Loading section sw_isr_table, size 0x200 lma 0x20403d40
Loading section devconfig, size 0x84 lma 0x20403f40
Loading section rodata, size 0x574 lma 0x20403fc4
Loading section datas, size 0xc4 lma 0x20404538
Loading section initlevel, size 0x84 lma 0x204045fc
Loading section _k_mutex_area, size 0x14 lma 0x20404680
Start address 0x20400000, load size 18068
Transfer rate: 8 KB/sec, 2007 bytes/write.
(gdb) monitor resume
halted at 0x20400004 due to step

ここまででエラーが生じなければ、Zephyrのアップロードは完了だ。 シリアルコンソールを立ち上げて動作を確認みる。

以下を入力する。 /dev/ttyUSB1の部分は、お使いの環境でHiFive1のシリアルコンソールが認識されているデバイスファイルに適宜書き換えること。

sudo picocom -b 115200 /dev/ttyUSB1

HiFive1のリセットボタンを押すと以下のように表示された。

上手く動いたようだ。

f:id:msyksphinz:20190423003533g:plain
HiFive1でPhiloshperを動かした結果

SiFive社のRISC-Vボード HiFive Unleashedを使ってみる (2. Buildroot Linuxのビルド)

f:id:msyksphinz:20190419223924p:plain
HiFive Unleashed 評価ボード

HiFive Unleashedは、特に何もすることなくデフォルトで付属しているBuildroot Linuxを使ってLinuxを起動することができる。 しかし、これではあまり面白くないので、RISC-VのSDKを使って自分でLinuxをビルドし、カスタマイズするための環境を作ってみる。

RISC-V Linuxのカスタマイズを行うためには、SiFive社が提供しているFreedom-u-sdkを使用する。 このSDKGitHub上で配布されており、HiFive Unleashedで動作セする64ビットLinuxをビルドする環境も入っている。

# リポジトリのすべてをダウンロードしてしまうととても重いので、最新のリリースのみダウンロードする。
git clone https://github.com/sifive/freedom-u-sdk --depth 1 -b hifiveu-2.0-alpha --recurse-submodules
cd freedom-u-sdk
unset LD_LIBRARY_PATH
make -j$(nprocs)

必要に応じてMakeには並列数を挿入する。コンパイルにはしばらく時間がかかった。

ビルドが完了すると、マイクロSDカードにLinuxのイメージを書き込む。 マイクロSDカードを用意して、まずはフォーマットする。 マイクロSDカードをPCに差し込むと、まずは認識されているデバイスを確認する。

lsusb
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
...
sda      8:0    0   500G  0 disk
└─sda1   8:1    0   500G  0 part /
sdc      8:32   1  14.8G  0 disk
├─sdc1   8:33   1    31M  0 part /mnt/sdcard
├─sdc2   8:34   1   5.5G  0 part
├─sdc3   8:35   1 460.5K  0 part
└─sdc4   8:36   1    38K  0 part

マイクロSDカードが/dev/sdcに認識されていることが分かる。そこで、gdiskを使ってSDカードの初期化を行う。

sudo gdisk /dev/sdc
$ sudo gdisk /dev/sdc
[sudo] password for msyksphinz:
GPT fdisk (gdisk) version 1.0.3

Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present

Found valid GPT with protective MBR; using GPT.
Command (? for help): p  # デフォルトのパーティション情報を表示。次にパーティションをすべて削除する。
Disk /dev/sdc: 30965760 sectors, 14.8 GiB
Model: Mini SD Reader
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 81BDE1E5-C39D-4976-94A4-56C3AEA1E352
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 30965726
Partitions will be aligned on 4-sector boundaries
Total free space is 19446682 sectors (9.3 GiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048           65502   31.0 MiB    0700  Vfat Boot
   2          264192        11718750   5.5 GiB     8300  root
   3            1100            2020   460.5 KiB   FFFF  uboot
   4            1024            1099   38.0 KiB    FFFF  uboot-env
   
Command (? for help): d  # パーティション2を削除
Partition number (1-4): 2

Command (? for help): d  # パーティション3を削除
Partition number (1-4): 3

Command (? for help): d  # パーティション4を削除
Partition number (1-4): 4

Command (? for help): o  # すべてのパーティションの情報を削除
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): Y # Yesを選択する

Command (? for help): w  # ディスクへパーティション情報を書き込む。

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): Y
OK; writing new GUID partition table (GPT) to /dev/sdc.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.

SDカードへのフォーマットが完了すると、次にLinuxの情報をSDカードに書き込む。 これはmakeコマンドで実行できる。 以下のようにして書き込んだ。。

sudo make DISK=/dev/sdc format-boot-loader
$ sudo make DISK=/dev/sdc format-boot-loader
/sbin/sgdisk --clear  \
        --new=1:2048:65502  --change-name=1:"Vfat Boot" --typecode=1:EBD0A0A2-B9E5-4433-87C0-68B6B72699C7   \
        --new=2:264192:11718750 --change-name=2:root    --typecode=2:0FC63DAF-8483-4772-8E79-3D69D8477DE4 \
        --new=3:1100:2020   --change-name=3:uboot       --typecode=3:5B193300-FC78-40CD-8002-E86C45580B47 \
        --new=4:1024:1099  --change-name=4:uboot-env    --typecode=4:a09354ac-cd63-11e8-9aff-70b3d592f0fa \
        /dev/sdc
Setting name!
partNum is 0
Setting name!
partNum is 1
Setting name!
partNum is 2
Setting name!
partNum is 3
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.
/sbin/partprobe
dd if=/home/msyksphinz/work/riscv/freedom-u-sdk/work/HiFive_U-Boot/u-boot.bin of=/dev/sdc3 bs=4096
113+1 records in
113+1 records out
464973 bytes (465 kB, 454 KiB) copied, 0.310722 s, 1.5 MB/s
dd if=/home/msyksphinz/work/riscv/freedom-u-sdk/work/hifive-unleashed-vfat.part of=/dev/sdc1 bs=4096
7931+1 records in
7931+1 records out
32488448 bytes (32 MB, 31 MiB) copied, 0.0255919 s, 1.3 GB/s

マイクロSDカードの構築が完了しました。 HiFive Unleashedのボードに差し込んで起動する。このとき、HiFive Unleashedのディップス一致を1011に設定する必要があった。

U-Boot 2018.09-gca05d26 (Apr 20 2019 - 01:40:29 +0900)

DRAM:  2 GiB
MMC:
In:    serial
Out:   serial
Err:   serial
Net:   gmac0
Hit any key to stop autoboot:  0
MMC_SPI: 0 at 0:1 hz 20000000 mode 0

Partition Map for MMC device 0  --   Partition Type: EFI

Part    Start LBA       End LBA         Name
        Attributes
        Type GUID
...
## Starting application at 0x80000000 ...
bbl loader

                SIFIVE, INC.

         5555555555555555555555555
        5555                   5555
       5555                     5555
      5555                       5555
     5555       5555555555555555555555
    5555       555555555555555555555555
   5555                             5555
  5555                               5555
 5555                                 5555
5555555555555555555555555555          55555
 55555           555555555           55555
   55555           55555           55555
     55555           5           55555
       55555                   55555
         55555               55555
           55555           55555
             55555       55555
               55555   55555
                 555555555
                   55555
                     5

           SiFive RISC-V Core IP
[    0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000
[    0.000000] Linux version 4.19.0-sifive-1+ (msyksphinz@msyksphinz) (gcc version 8.3.0 (Buildroot 2019.02-07449-g4eddd28f99))
...
[    2.080000] Run /init as init process
[    2.090000] mmc0: new SDHC card on SPI
[    2.100000] mmcblk0: mmc0:0000 SPCC 14.8 GiB
[    2.130000]  mmcblk0: p1 p2 p3 p4
Starting syslogd: OK
Starting klogd: OK
Starting mdev...
modprobe: can't change directory to '/lib/modules': No such file or directory
Initializing random number generator... [    3.020000] random: dd: uninitialized urandom read (512 bytes read)
done.
Starting network: [    3.090000] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready
udhcpc: started, v1.29.3
udhcpc: sending discover
[    6.240000] macb 10090000.ethernet eth0: link up (100/Full)
[    6.240000] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
udhcpc: sending discover
udhcpc: sending select for 192.168.11.17
udhcpc: lease of 192.168.11.17 obtained, lease time 172800
deleting routers
adding dns 192.168.11.1
OK
Starting dropbear sshd: [    6.540000] random: dropbear: uninitialized urandom read (32 bytes read)
OK

Welcome to Buildroot
buildroot login: root
Password:
#

ログイン画面が現れた。ブート成功。

f:id:msyksphinz:20190420140522p:plain
Freedom-u-sdkからビルドしたLinuxを、HiFive Unleashedで立ち上げる。

SiFive社のRISC-Vボード HiFive Unleashedを使ってみる (1. ボードのセットアップとBuildroot Linuxの起動)

f:id:msyksphinz:20190419223924p:plain
HiFive Unleashed 評価ボード

RISC-Vの評価ボードは、数は少ないですが様々なものがリリースされている。 中でも、RISC-VプロセッサのIP開発を手掛けるSiFive社は、個人でも購入できるRISC-V評価ボードを提供している。

2018年の2月にCrowd Supplyというクラウドファンディングで発表された"HiFive Unleashed"という評価ボードは、本格的なマルチコアのRISC-Vボードとしてかなり注目だ。

基本的な仕様は、以下の通りになっている。

ボード全体の構成としては、

  • SiFive U540 SoC (U540の正式な仕様書はまだ存在していない。 U500の仕様書はこちらから入手できる)
  • ECC付き 8GB DDR4-SDRAM
  • ギガビット Ethernet ポート
  • 32MB Quad SPI フラッシュメモリ
  • MicroSDカードストレージ
  • アドインカード接続用のFMCコネクタ

と、かなり本格的だ。実際 Linuxもブートすることができ、様々なベンチマークプログラムを動かしたり、はてはデスクトップPCとして使う事例もある、非常に面白い、将来性のあるボードとなっている。

HiFive Unleashedを立ち上げてLinuxをブートさせるまで

HiFive UnleashdにはマイクロSDカードが付属しており、デフォルトでBuildrootのLinuxが立ち上がる。 HiFive UnleashdをWindows PCにUSBで接続し、USB経由でシリアルコンソールを立ち上げた。 Tera Termで接続し、ボーレートは115200bpsで接続する。 電源を入れると、SiFiveのロゴが表示され、Linuxがブートする。

f:id:msyksphinz:20190419224118p:plain
f:id:msyksphinz:20190419224258p:plain ユーザ名`root`、パスワード`sifive`でログインできる。

/proc/cpuinfoでCPUの情報を確認する。

# cat /proc/cpuinfo
hart    : 1
isa     : rv64imafdc
mmu     : sv39
uarch   : sifive,rocket0

hart    : 2
isa     : rv64imafdc
mmu     : sv39
uarch   : sifive,rocket0

hart    : 3
isa     : rv64imafdc
mmu     : sv39
uarch   : sifive,rocket0

hart    : 4
isa     : rv64imafdc
mmu     : sv39
uarch   : sifive,rocket0

本当にRISC-Vだ。少し感動。

オリジナルLLVM Backendを追加しよう (29. LLVMリグレッションテストの書き方)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181123/20181123225150.png

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。

jonathan2251.github.io

LLVMでのテスト記述とリグレッション

LLVMのオリジナルのバックエンドを実装してきたが、いろんなソースコードコンパイルして、いろんなテストプログラムを動かしてきた。 これだけ多くのテストパタンを実行するのなら、テスト環境とリグレッションテストを用意しておくのが良い。LLVMは、テストパタンとリグレッションテストの環境が用意されいる。 詳細なドキュメントはここを参照してほしいのだが、ここでは、これまで作成したオリジナルバックエンドをテストするための環境について調査する。

llvm-lit

llvm-litは"LLVM Integrated Tester"で、LLVMとClangのテストスイートを実行するためのツールである。 テストセットを記述してLLVMに食わせ、その結果が想定したものかどうかまでチェックしてくれる。

llvm-litは以下のようにして使用する。

$ ./bin/llvm-lit -h
usage: llvm-lit [-h] [--version] [-j N] [--config-prefix NAME] [-D NAME=VAL]
                [-q] [-s] [-v] [-vv] [-a] [-o PATH] [--no-progress-bar]
                [--show-unsupported] [--show-xfail] [--path PATH] [--vg]
                [--vg-leak] [--vg-arg ARG] [--time-tests] [--no-execute]
                [--xunit-xml-output XUNIT_OUTPUT_FILE]
                [--timeout MAXINDIVIDUALTESTTIME] [--max-failures MAXFAILURES]
                [--max-tests N] [--max-time N] [--shuffle] [-i]
                [--filter REGEX] [--num-shards M] [--run-shard N] [--debug]
                [--show-suites] [--show-tests] [--single-process]
                [test_paths [test_paths ...]]

では、コードジェネレータ(LLVMバックエンド)のテストを見てみる。 LLVMリポジトリ内に、test/CodeGen内にテストが入っている。

tree -L 1 test/CodeGen
test/CodeGen
├── AArch64
├── AMDGPU
├── ARC
├── ARM
├── AVR
├── BPF
├── Generic
├── Hexagon
├── Inputs
├── Lanai
├── MIR
├── MSP430
├── Mips
├── NVPTX
├── Nios2
├── PowerPC
├── RISCV
├── SPARC
├── SystemZ
├── Thumb
├── Thumb2
├── WebAssembly
├── WinCFGuard
├── WinEH
├── X86
└── XCore

26 directories, 0 files

ターゲットアーキテクチャ毎に、テストパタンが並んでいる。 さらに、RISCVの中身をのぞいてみる。

tree -L 1 test/CodeGen/RISCV
test/CodeGen/RISCV
├── addc-adde-sube-subc.ll
├── align.ll
├── alloca.ll
├── alu32.ll
├── analyze-branch.ll
├── arith-with-overflow.ll
...
├── tail-calls.ll
├── vararg.ll
├── wide-mem.ll
└── zext-with-load-is-free.ll

0 directories, 76 files

Clangで処理された中間形式LLVM IRのファイル群が格納されている。さらに、test/CodeGen/RISCV/alu32.llを見てみる。

; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc -mtriple=riscv32 -verify-machineinstrs < %s \
; RUN:   | FileCheck %s -check-prefix=RV32I

; These tests are each targeted at a particular RISC-V ALU instruction. Other
; files in this folder exercise LLVM IR instructions that don't directly match a
; RISC-V instruction

; Register-immediate instructions

define i32 @addi(i32 %a) nounwind {
; RV32I-LABEL: addi:
; RV32I:       # %bb.0:
; RV32I-NEXT:    addi a0, a0, 1
; RV32I-NEXT:    ret
  %1 = add i32 %a, 1
  ret i32 %1
}

RUN:の行に、テストの内容が書かれている。 このテストでは、llc -mtriple=riscv32 -verify-machineinstrs < [テストファイル名] | FileCheck [テストファイル名] -check-prefix=RV32Iが実行される、ということになる。

llcで生成したターゲットアセンブリコードを、FileCheckコマンドで結果を比較する、ということになる。 実行してみる。

$ ./bin/llvm-lit ../llvm-myriscvx/test/CodeGen/RISCV/alu32.ll
-- Testing: 1 tests, 1 threads --
PASS: LLVM :: CodeGen/RISCV/alu32.ll (1 of 1)
Testing Time: 0.87s
  Expected Passes    : 1

LLVM IRのコード:

define i32 @addi(i32 %a) nounwind {
  %1 = add i32 %a, 1
  ret i32 %1
}

に対して、以下のコードが生成されることを想定しています。 これが一致すると、テストがPassとなる。

addi:
# %bb.0:
    addi a0, a0, 1
    ret

Cpu0のテストを改造してMYRISCVX用にする

Cpu0のテストを改造して、MYRISCVX用に移植してみる。

  • lbdex/regression-test/Cpu0/seteq.ll
; RUN: llc  -march=cpu0 -mcpu=cpu032I  -relocation-model=pic %s -o - | FileCheck %s -check-prefix=cpu032I
; RUN: llc  -march=cpu0 -mcpu=cpu032II  -relocation-model=pic %s -o - | FileCheck %s -check-prefix=cpu032II
; terminal command (not work): llc  -march=cpu0 -mcpu=cpu032II  -relocation-model=pic %s < %s | FileCheck %s

@i = global i32 1, align 4
@j = global i32 10, align 4
@k = global i32 1, align 4
@r1 = common global i32 0, align 4
@r2 = common global i32 0, align 4

define void @test() nounwind {
entry:
  %0 = load i32, i32* @i, align 4
  %1 = load i32, i32* @k, align 4
  %cmp = icmp eq i32 %0, %1
  %conv = zext i1 %cmp to i32
  store i32 %conv, i32* @r1, align 4

; cpu032I:  cmp $sw, ${{[0-9]+|t9}}, ${{[0-9]+|t9}}
; cpu032I:  andi        $[[T1:[0-9]+|t9]], $sw, 2
; cpu032I:  shr ${{[0-9]+|t9}}, $[[T1]], 1
; cpu032II:  xor        $[[T0:[0-9]+|t9]], ${{[0-9]+|t9}}, ${{[0-9]+|t9}}
; cpu032II:  sltiu      ${{[0-9]+|t9}}, $[[T0]], 1

  ret void
}

どのレジスタアサインされるかわからないので、そこは柔軟に書いてある。${{[0-9]+|t9}} がそれに当たる。これをMYRISCVX用に改造する。 テストコードが配置されているディレクトリにtest/CodeGen/MYRISCVX/ディレクトリを作成する。

  • test/CodeGen/MYRISCVX/seteq.ll
; RUN: llc -march=myriscvx32  -relocation-model=pic %s -o - \
; RUN: | FileCheck %s -check-prefix=myriscvx32

@i = global i32 1, align 4
@j = global i32 10, align 4
@k = global i32 1, align 4
@r1 = common global i32 0, align 4
@r2 = common global i32 0, align 4

define void @test() nounwind {
entry:
  %0 = load i32, i32* @i, align 4
  %1 = load i32, i32* @k, align 4
  %cmp = icmp eq i32 %0, %1
  %conv = zext i1 %cmp to i32
  store i32 %conv, i32* @r1, align 4

; myriscvx32: lui     $[[REGISTER:[0-9A-Ba-b_]+]], %hi(_gp_disp)
; myriscvx32: addi    $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], %lo(_gp_disp)
; myriscvx32: lui     $[[REGISTER:[0-9A-Ba-b_]+]], %got_hi(k)
; myriscvx32: add     $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], $3
; myriscvx32: lw      $[[REGISTER:[0-9A-Ba-b_]+]], %got_lo(k)($[[REGISTER:[0-9A-Ba-b_]+]])
; myriscvx32: lw      $[[REGISTER:[0-9A-Ba-b_]+]], 0($[[REGISTER:[0-9A-Ba-b_]+]])
; myriscvx32: lui     $[[REGISTER:[0-9A-Ba-b_]+]], %got_hi(i)
; myriscvx32: add     $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], $3
; myriscvx32: lw      $[[REGISTER:[0-9A-Ba-b_]+]], %got_lo(i)($[[REGISTER:[0-9A-Ba-b_]+]])
; myriscvx32: lw      $[[REGISTER:[0-9A-Ba-b_]+]], 0($[[REGISTER:[0-9A-Ba-b_]+]])
; myriscvx32: xor     $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]]
; myriscvx32: sltiu   $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], 1
; myriscvx32: lui     $[[REGISTER:[0-9A-Ba-b_]+]], %got_hi(r1)
; myriscvx32: add     $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]]
; myriscvx32: lw      $[[REGISTER:[0-9A-Ba-b_]+]], %got_lo(r1)($[[REGISTER:[0-9A-Ba-b_]+]])
; myriscvx32: sw      $[[REGISTER:[0-9A-Ba-b_]+]], 0($[[REGISTER:[0-9A-Ba-b_]+]])

  ret void
}

生成されるコードをざっと書き下してみた。同じようにコードが生成されると成功だ。

$ ./bin/llvm-lit ../llvm-myriscvx/test/CodeGen/MYRISCVX/seteq.ll
-- Testing: 1 tests, 1 threads --
PASS: LLVM :: CodeGen/MYRISCVX/seteq.ll (1 of 1)
Testing Time: 0.29s
  Expected Passes    : 1

RISC-VでZephyr OSを動作させる (2. HiFive1でZephyr OSを動作させる)

f:id:msyksphinz:20190415004633p:plain

Zephyrとは、Linux Foundationのプロジェクトの一つでもあり、RTOS(Real Time Operating System)の一つだ。

次は、SiFive社から販売されている32bit RISC-VボードHiFive1を使ってリアルタイムOSであるZephyrを動かしてみる。

f:id:msyksphinz:20190416020805p:plain

参考にするのは、RISC-VのGetting Started GuideのZephyrの章(https://risc-v-getting-started-guide.readthedocs.io/en/latest/zephyr-hifive1.html#flashing)だ。 こちらの章に従って進めていくと、HiFive1ボードの上でZephyr OSを動作させ、アプリケーションを動かすことができる。

GDBとOpenOCDのダウンロード

まずは、HiFive1ボードへのZephyrのアップロードとデバッグに必要なGDB, OpenOCDをダウンロードする。ここでは、すべての作業を${HOME}/riscv/zephyr/ディレクトリ上で行う。 まずは、以下のように入力し、GDBとOpenOCDをダウンロードする。

curl -L https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-2018.07.0-x86_64-linux-ubuntu14.tar.gz | tar xz
curl -L https://static.dev.sifive.com/dev-tools/riscv-openocd-2018.7.0-x86_64-linux-ubuntu14.tar.gz | tar xz

これらの環境を、Freedom-E-SDKの上に配置しなければならない。 Freedom-E-SDKは、SiFive社が配布しているRISC-Vボードやデザインを開発するためのSDKで、HiFive1のサンプルプログラムや、フラッシュへのデータをアップロードする環境なども一通り揃っている。

git clone https://github.com/sifive/freedom-e-sdk
mkdir freedom-e-sdk/riscv-gnu-toolchain
mkdir freedom-e-sdk/openocd
mv riscv64-unknown-elf-gcc-2018.07.0-x86_64-linux-ubuntu14/* freedom-e-sdk/riscv-gnu-toolchain
mv riscv-openocd-2018.7.0-x86_64-linux-ubuntu14/* freedom-e-sdk/openocd

余談だがZephyrのビルドに使用するDevice-Tree-Compilerも最新のものを導入してかなければならない。 私の実験環境であるUbuntu 18.04でインストールされているdtcはバージョンが古くビルド時にエラーとなったので、dtcの最新バージョンをインストールしておく。

curl -L https://git.kernel.org/pub/scm/utils/dtc/dtc.git/snapshot/dtc-1.5.0.tar.gz | tar xz
cd dtc-1.5.0
make
sudo make install PREFIX=/usr/

サンプルプログラムのコンパイル

ここまでで、Zephyrの環境構築は完了した。 次に、サンプルプログラムのコンパイルを行う。 サンプルプログラムは、${ZEPHYR_BASE}/samples/hello_worldを使用する。

  • samples/hello_world/src/main.c
/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <misc/printk.h>

void main(void)
{
        printk("Hello World! %s\n", CONFIG_BOARD);
}

たったこれだけだが、うまく実行できるとシリアルコンソール上にprintfkの結果が表示されるはずだ。

まずは作業環境を整える。

mkdir build-example
cd build-example
cmake -DBOARD=hifive1 $ZEPHYR_BASE/samples/hello_world
make -j $(nproc)
cd ..
  • cmake -DBOARD=hifive1 $ZEPHYR_BASE/samples/hello_worldの実行結果
Zephyr version: 1.14.0
-- Selected BOARD hifive1
-- Loading /home/msyksphinz/work/riscv/zephyr/boards/riscv32/hifive1/hifive1.dts as base
-- Overlaying /home/msyksphinz/work/riscv/zephyr/dts/common/common.dts
Parsing Kconfig tree in /home/msyksphinz/work/riscv/zephyr/Kconfig
Loading /home/msyksphinz/work/riscv/zephyr/hifive1/build-example/zephyr/.config as base
Configuration written to '/home/msyksphinz/work/riscv/zephyr/hifive1/build-example/zephyr/.config'
-- Cache files will be written to: /home/msyksphinz/.cache/zephyr
-- Configuring done
-- Generating done
-- Build files have been written to: /home/msyksphinz/work/riscv/zephyr/hifive1/build-example
  • make -j $(nproc)の実行結果
[ 81%] Building C object zephyr/kernel/CMakeFiles/kernel.dir/atomic_c.c.obj
[ 82%] Building C object zephyr/CMakeFiles/zephyr.dir/drivers/timer/sys_clock_init.c.obj
[ 83%] Building C object zephyr/CMakeFiles/zephyr.dir/drivers/timer/riscv_machine_timer.c.obj
[ 84%] Linking C static library libkernel.a
[ 85%] Linking C static library libzephyr.a
[ 86%] Built target kernel
[ 92%] Built target zephyr
Scanning dependencies of target zephyr_prebuilt
[ 93%] Linking C executable zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
             ROM:       18068 B        12 MB      0.14%
             RAM:        4272 B        16 KB     26.07%
        IDT_LIST:         553 B         2 KB     27.00%
[ 94%] Built target zephyr_prebuilt
Scanning dependencies of target linker_pass_final_script_target
[ 95%] Generating linker_pass_final.cmd
[ 95%] Built target linker_pass_final_script_target
[ 96%] Generating isr_tables.c
Scanning dependencies of target kernel_elf
[ 97%] Building C object zephyr/CMakeFiles/kernel_elf.dir/isr_tables.c.obj
[ 98%] Linking C executable zephyr.elf
Generating files from zephyr.elf for board: hifive1
[100%] Built target kernel_elf

これでZephyrとアプリケーションのコンパイルができる。HiFive1にダウンロードして実行してみよう。

サンプルプログラムをHiFive1のフラッシュメモリに書き込む

コンパイルしたプログラムをHiFive1にダウンロードするためには、先ほどのFreedom-E-SDK環境を使用する。 まずは、HiFive1のコンフィグレーションを使用してopenocdを立ち上げ、HiFive1と接続してみる。

cd freedom-e-sdk
sudo openocd/bin/openocd -f bsp/sifive-hifive1/openocd.cfg

次に別のターミナルを開き、GDBを立ち上げる。 そして、ZephyrのバイナリをHiFive1に書き込んでみよう。 GDBを立ち上げた後、下記リストの先頭が(gdb)と記述されている行のみ入力していけばよい。

./freedom-e-sdk/riscv-gnu-toolchain/bin/riscv64-unknown-elf-gdb
(gdb) set remotetimeout 240
(gdb) target extended-remote localhost:3333
Remote debugging using localhost:3333
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x20401244 in ?? ()
(gdb) monitor reset halt
JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (SiFive, Inc.), part: 0x0e31, ver: 0x1)
halted at 0x20401244 due to debug interrupt
(gdb) monitor flash protect 0 64 last off
cleared protection for sectors 64 through 255 on flash bank 0
(gdb) load /home/msyksphinz/work/riscv/zephyr/hifive1/build-example/zephyr/zephyr.elf
Loading section vector, size 0x10 lma 0x20400000
Loading section exceptions, size 0x268 lma 0x20400010
Loading section text, size 0x3ac8 lma 0x20400278
Loading section sw_isr_table, size 0x200 lma 0x20403d40
Loading section devconfig, size 0x84 lma 0x20403f40
Loading section rodata, size 0x574 lma 0x20403fc4
Loading section datas, size 0xc4 lma 0x20404538
Loading section initlevel, size 0x84 lma 0x204045fc
Loading section _k_mutex_area, size 0x14 lma 0x20404680
Start address 0x20400000, load size 18068
Transfer rate: 8 KB/sec, 2007 bytes/write.
(gdb) monitor resume
halted at 0x20400004 due to step

ここまででエラーが生じなければ、Zephyrのアップロードは完了だ。 シリアルコンソールを立ち上げて動作を確認みる。

以下を入力する。 /dev/ttyUSB1の部分は、お使いの環境でHiFive1のシリアルコンソールが認識されているデバイスファイルに適宜書き換えること。

sudo picocom -b 115200 /dev/ttyUSB1

HiFive1のリセットボタンを押すと以下のように表示された。

***** Booting Zephyr OS v1.14.0-rc3-246-g10327e59c622 *****
Hello World! hifive1

HiFive1ボードで、Zephyrのサンプルアプリケーションを動作せさせることができた。