RISC-V実機でDebianを動かしてみる(&ちょっとアセンブリ比較)
こんにちは。LWisteriaです。
以前から何度かブログにも登場している通り、フィックスターズでは社内有志で勉強会を半年区切りぐらいで開催しています。
そして今期は「RISC-V勉強会」になりまして、早速『RISC-V原典』の輪読をしているのですが、「やっぱり実機もほしいよね」ということで、HiFive Unleashedを会社で購入しました。
この記事は、そのHiFive UnleashedでDebianを動かしてみるところまでの軌跡という名の手順のご紹介です。
RISC-Vとは
RISC-Vとは、近年とても注目されている命令セット・アーキテクチャー(ISA)で、詳細な説明は先述の『RISC-V原典』に譲るとして、ここでは簡単な特徴を紹介しておきます
- 比較的最近作られたISAであり、従来のプロセッサーが抱えていた問題を教訓としている
- オープン標準な仕様であり、x86やARM等と違い、権利者の一存で気まぐれに変わることがない
- 目指すのは組み込み機器からスパコンのような大規模計算機まで、あらゆる分野で使えるもの
なぜフィックスターズでRISC-V?
目的を書いておかないと「なんで?」となる箇所も多いかと思うので、最初に少し書いておきます。
端的に言えば、このRISC-V、まさに「組み込みSoCからスパコンまで」お客さまのご要望とあらばどんなプロセッサー上でもソフトウェアを高速化するフィックスターズでもとても重要なものだから、ということです。
現時点で眼前のお仕事でRISC-Vプロセッサーを使うというのは(少なくとも私は)見かけていませんが、ポスト京を筆頭に、独自プロセッサー・ISAから広く普及しているものへ移行しようとするのが時代の流れのようです。
ですので、それほど遠くない将来において、それらのうち少なくともいくつかはRISC-Vを適用することが容易に予想されます。
加えて、単純に、フィックスターズには「普通じゃないプロセッサー・アーキテクチャーを面白いと思うエンジニア」が多く集まっているので、みんなの関心注目度も高いです。
また、既に世の中の多くの方がRISC-Vに注目し、既に多くの技術情報・ブログを書いておられますが、その多くは現状ではやはり「RISC-Vプロセッサーをどのように作るか・実装するか」に着目しているように見えます。
一方、我々の多くはやはり「ソフトウェア」屋さんなので、ハードウェア的にどう実装するかというよりは、「そのISAやチップの上でどのようにソフトウェア・アプリケーションを実装するのが良いか」が多くの関心を引くところです。
というわけで、現時点で入手できる実機のRISC-V開発環境のうち、おそらく最強の環境であるHiFive Unleashedを使って、いまのうちにRISC-Vについて学んでおこうというのが本勉強会の趣旨です。
HiFive Unleashedとは
先述の通り、HiFive Unleashedは、本記事執筆時点で入手できるRISC-Vの実機のうち、おそらく世界最強の開発環境となっています。その特徴は
- 規格を管理しているRISC-V財団の創設メンバーであるSiFiveによって開発・販売されている
- 64bitプロセッサーである(他社のほとんどはまだ32bitしかない)
- マルチコア(4コア)であり、並列処理が可能(他社のほとんどはまだシングルコア)
- 具体的な命令セットはRV64IMAFDCをサポートしており、基本命令・整数乗除算、アトミック操作、単精度&倍精度浮動小数演算、圧縮命令という、普通のCPUと遜色ないことが可能
- その結果、Linuxが動作する(他社のほとんどはまだベアメタル)
で、まさに「ソフトウェア屋さんがRISC-Vを学ぶための環境」としてはとても最適です。
ということで、最後の「Linuxが動作する」を具体的にするため、実際にDebianを動かしてみました。
(本当は勉強会で発表してからにしようかとも思ったんですが、5月大型連休中に試そうとしている方々の参考になればということで、先にブログ記事にしました)
HiFive Unleashedを動かそう
Debianを動かす前に、まずは普通に動かしてみましょう。
この手順の詳細は、公式のGetting Startedにあります。本記事はv1p1に基づいています。
電源を入れて初回起動する
- まずはHiFive Unleashedを入手してください。croud supplyから買うと(送料込みで$1039)、1ヶ月程度で届きます。
- ボードを取り出し、SDカードがちゃんと入っていることを確認してください
- DIPスイッチを全部1(左側)にします
- 電源コードを挿します。ファンが回り始めます
- 電源横の赤いスイッチ(電源スイッチ)を押し込みます
- 30秒ぐらい待ちます
- Linuxが起動します。マイクロUSB横のLEDが点滅します

コンソールからLinuxを操作する
HDMI等の画面出力端子はありませんので、操作はCUIコンソールでやります。sshでつなぐこともできますが、DHCPで割り当てられたIPアドレスを調べるのがちょっと面倒なので、初回はシリアル接続で試すことをオススメします。
また、ここではWindowsから接続する方法を紹介します。MacやLinux等の場合についてはGetting Startedの5.2に書かれていますので、その通りやってください。
- HiFive Unleashed側のマイクロUSBとWindows機のUSB端子を接続します
- デバイスマネージャーを起動して、「ポート(COMとLPT)」に新しくCOMポートが2つ増えていることを確認してください。多くの環境ではCOM3とCOM4になっていると思います。もしなければ、FDTIのドライバーを入れてください
- TeraTermをインストールし、起動します
- 「設定→シリアルポート→ボー・レート」を115200に設定します
- 「ファイル→新しい接続→シリアル」からポートを選択します。おそらく増えた番号のうち大きい方(COM4)のはずです
- エンターキーを押すと
buildroot login:と聞かれます。idはroot、初期パスワードはsifiveでログインしましょう - BuildrootというLinuxが使えるようになりました!
注意点として、(当然ですが)先に電源を入れてからTeraTermを接続しないと、画面に何も出てきません。間違えた場合or再起動したあとは、接続し直してください。

終了方法
-
haltコマンドを実行します(※shutdownコマンドはありません) -
Power offが出るまで待ちます - 電源ボタンを押してオフにします
- 電源コードを抜きます
Debianで動かそう
前述の通り、初期出荷時のままでBuildrootが入っているので、Linuxとしてはそのまま使うことができます。
しかし、この環境はかなりの最小環境で、例えばGCC等のコンパイラはありませんし、当然パッケージマネージャーなんてものはありません。
公式フォーラムでもSiFiveの人が開発等したかったらまともなOSを入れてねと回答しています。
ということで、普段使っているのに近いLinux distroを入れましょう。本記事執筆時点ではFedoraとDebianがしっかり使えそうですが、ここではDebianを選択することにします(社内ではUbuntuが多く使われているため)。
他のdistroもありそうなので、興味がある方はぜひ試してみてください(そしてブログ記事等でぜひ教えてください!)
自力でBuildrootを書き換える方法を試す
Debianを入れる前に、まず、標準のBuildrootを自力でビルドし、SDカードを書き換えられるか試してみることをオススメします。
また、操作ホスト環境はUbuntu(16.04)を推奨します(Getting Startedによる)。他の環境でもできなくはないでしょうが、少なくとも調べた限りWindows/WSLからでは少し厄介そうですので、Windowsしかない場合はVMを立ち上げたほうが良さそうです。
-
電源を落とし、マイクロSDカードを取り出します
-
SDカードリーダーをUbuntuが動いているホスト機(≠SiFive Unleashed)に挿します。この時、SDカードが
/dev/sdX(Xは環境による…他になければsdbが多そう)に見えているのですが、これは普通にmountできません。が、問題ないです。 -
Getting Startedの7.2.1 7.2.2, 7.2.3を実行し、freedom-u-sdkをビルドします。
$ sudo apt-get install device-tree-compiler autoconf automake autotools-dev bc bison build-essential curl flex gawk gdisk git gperf libgmp-dev libmpc-dev libmpfr-dev libncurses-dev libssl-dev libtool patchutils python screen texinfo unzip zlib1g-dev $ git clone https://github.com/sifive/freedom-u-sdk.git $ cd freedom-u-sdk $ git submodule update --init --recursive $ make -j8注意点として、submodule updateとmakeにはとても時間がかかるので気長に待つことと、かなり重いビルドがかかるのでmake -jの並列ビルド数を多くしすぎないようにすること、あと、ビルド前の依存パッケージに"device-tree-compiler"が"Additional"と書かれていましたが(少なくとも私の環境では)必須でした。
-
Getting Startedの7.2.4に従って、SDカードの中身を完全消去します。
$ sudo gdisk /dev/sdXを実行後、dで全パーティションを消去、oで新しくMBRを作って、wで書き込んでください。 -
$ sudo make DISK=/dev/sdX format-boot-loaderでSDカードに書き込みます。この時、Getting Startedにある通り、失敗することがあります(私の環境では必ず初回は失敗しました)。成功して完了するまで何度かやってください。 -
マイクロSDカードをホストから抜いて、HiFive Unleashedに挿しなおします
-
DIPスイッチのMSEL2を0(右) にしてください。Getting Startedには書いてありませんが、freedom-u-sdkのREADMEに書いてあります(アップデートで変わったようです)。
-
あとは通常通り起動します
これでちゃんとBuildrootが起動し操作できるようになっていれば、とりあえずSDカードの中身を書き換える操作はできました。
Debianを入れて動かす
さてここからが本題のDebianを動かすところです。ここでは、Debian wikiに従って、第2パーティションに入れる方法をとります。
-
前節のBuildrootを書き換える方法のうち、4.パーティション完全消去までをやります
-
$ sudo make DISK=/dev/sdX format-demo-imageを実行します(format-boot-loader→format-demo-imageに変更)。Debianのビルド済みイメージがダウンロードされ、自動でSDカードに展開されます。少し時間がかかります(20分ぐらい?)。 -
通常通りBuildrootに入り、以下のコマンドでmmcblk0p2以下に入っているdebianにchrootします
# mount /dev/mmcblk0p2 /mnt # mount -t proc /proc /mnt/proc # cp /etc/resolv.conf /mnt/etc/resolv.conf # chroot /mnt/ /bin/bash -
これでDebianの中に入れたので、更に以下を実行してパッケージマネージャーを使えるようにします
# ntpdate ntp.nict.jp # export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true # export LC_ALL=C LANGUAGE=C LANG=C # dpkg --configure -a # apt update # apt upgrade
cp resolv.conf(DNSの設定)とntpdate(時刻の更新)を忘れないようにしてください。これがないとaptが動きません(特に、時刻は電源を落とす度に毎回消えて1970年に戻るので、忘れないように)。
これで普通のDebianとほぼ同じように使えるようになりました!

せっかくなのでx86_64と少し比較
せっかく動くようになったので、少しソフトウェアの比較をしてみたいと思います。とは言え、普通のIntel CPUと実行性能を比較しても仕方ないので(どう考えてもIntel CPUの方が強いに決まってる)、ここでは以下の単純な疎行列ベクトル積(ELL形式)のコードをコンパイルした時のアセンブリの比較してみましょう。
※2019-05-07追記:以下のコードは、Twitterでのご指摘を受けて修正されたものです。結果的にx86_64の方は修正前に比べて少し短く出力されました。参考に、修正前のコードは付録に残してあります。また、ここでの比較は単純かつ素朴なものであり、厳密さを求めたものではないことに注意してください。あらゆる条件を考慮し正しい手法を用いた比較は今後の勉強会でなされてそのうちまたブログ記事になると期待しています。
// gcc -std=c11 -O2 -S main.c
#include<stddef.h>
#define MAX_NONZERO (32)
void SpMV(double y[restrict], const double a_data[restrict], const size_t a_column[restrict], const size_t a_nonzero[restrict], const double x[restrict], const size_t n)
{
for(size_t i = 0; i < n; ++i)
{
double y_i = 0;
const size_t nnz = a_nonzero[i];
for(size_t idx = 0; idx < nnz; ++idx)
{
const double a_ij = a_data[i*MAX_NONZERO + idx];
const size_t j = a_column[i*MAX_NONZERO + idx];
const double x_j = x[j];
const double ax = a_ij * x_j;
y_i += ax;
}
y[i] = y_i;
}
}
x86_64とRISC-V(RV64IMAFDC)のアセンブリ比較
x86_64
アセンブリ出力
.file "main.c"
.text
.p2align 4,,15
.globl SpMV
.type SpMV, @function
SpMV:
.LFB0:
.cfi_startproc
testq %r9, %r9
je .L13
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
xorl %ebx, %ebx
.p2align 4,,10
.p2align 3
.L5:
movq (%rcx,%rbx,8), %r10
testq %r10, %r10
je .L6
movq %rbx, %r11
movq %rbx, %rax
pxor %xmm1, %xmm1
salq $5, %r11
salq $8, %rax
addq %r10, %r11
salq $3, %r11
.p2align 4,,10
.p2align 3
.L4:
movq (%rdx,%rax), %r10
movsd (%r8,%r10,8), %xmm0
mulsd (%rsi,%rax), %xmm0
addq $8, %rax
addsd %xmm0, %xmm1
cmpq %rax, %r11
jne .L4
movsd %xmm1, (%rdi,%rbx,8)
addq $1, %rbx
cmpq %rbx, %r9
jne .L5
.L17:
popq %rbx
.cfi_remember_state
.cfi_def_cfa_offset 8
ret
.p2align 4,,10
.p2align 3
.L6:
.cfi_restore_state
pxor %xmm1, %xmm1
movsd %xmm1, (%rdi,%rbx,8)
addq $1, %rbx
cmpq %rbx, %r9
jne .L5
jmp .L17
.L13:
.cfi_def_cfa_offset 8
.cfi_restore 3
ret
.cfi_endproc
.LFE0:
.size SpMV, .-SpMV
.ident "GCC: (Ubuntu 8.1.0-5ubuntu1~16.04) 8.1.0"
.section .note.GNU-stack,"",@progbits
オブジェクト(.o)サイズ[byte]
1344
RISC-V(RV64IMAFDC)
.file "main.c"
.option pic
.text
.align 1
.globl SpMV
.type SpMV, @function
SpMV:
beqz a5,.L1
slli t4,a5,3
li t3,0
.L5:
add a5,a3,t3
ld a5,0(a5)
beqz a5,.L6
slli t1,t3,2
add t1,t1,a5
fmv.d.x fa4,zero
slli a6,t3,5
slli t1,t1,3
add a6,a6,a1
add t1,t1,a1
mv a7,a2
.L4:
ld a5,0(a7)
fld fa3,0(a6)
addi a6,a6,8
slli a5,a5,3
add a5,a4,a5
fld fa5,0(a5)
addi a7,a7,8
fmul.d fa5,fa5,fa3
fadd.d fa4,fa4,fa5
bne t1,a6,.L4
add a5,a0,t3
fsd fa4,0(a5)
addi t3,t3,8
addi a2,a2,256
bne t3,t4,.L5
.L1:
ret
.L6:
fmv.d.x fa4,zero
add a5,a0,t3
addi t3,t3,8
fsd fa4,0(a5)
addi a2,a2,256
bne t3,t4,.L5
j .L1
.size SpMV, .-SpMV
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
オブジェクト(.o)サイズ[byte]
1288
GCCバージョンがちょっと古い(freedom-u-sdk推奨のUbuntu 16.04を使っている)ので少し不公平とはいえ、RISC-Vの方がオブジェクトサイズも小さく、かつアセンブリも短く読みやすいという結果になりました。
終わりに
というわけで、RISC-V実機であるHiFive UnleashedでDebianを動かすことができました。
今後、今期の勉強会で私を含めて多くのエンジニアが色々触ったり実行したりしてみる予定です。またそれらの結果もぜひ紹介したいと思いますので、お楽しみに!
謝辞:本記事に含まれている内容には、フィックスターズのエンジニア各位からの助言・協力してもらった内容が多く含まれています。
特に、aptを動かすためのresolv.confとntp周りの設定はyuki-itoによるものです。
また、Fixstars SolutionsのFarhanには、RISC-V North America Roadshow 2019にて勉強会に適した開発ボードを調査してもらい、最終的にHiFive Unleashedに決めたのは彼の報告によるものです。
付録:2019-05-07修正前のコード
// gcc -std=c11 -O2 -S main.c
#include<stddef.h>
#define MAX_NONZERO (32)
void SpMV(double y[restrict], const double a_data[restrict], const double a_column[restrict], const size_t a_nonzero[restrict], const double x[restrict], const size_t n)
{
for(size_t i = 0; i < n; ++i)
{
double y_i = 0;
const size_t nnz = a_nonzero[i];
for(size_t idx = 0; idx < nnz; ++idx)
{
const double a_ij = a_data[i*MAX_NONZERO + idx];
const size_t j = a_column[i*MAX_NONZERO + idx];
const double x_j = x[j];
const double ax = a_ij * x_j;
y_i += ax;
}
y[i] = y_i;
}
}
x86_64とRISC-V(RV64IMAFDC)のアセンブリ比較
x86_64
アセンブリ出力
.file "main.c"
.text
.p2align 4,,15
.globl SpMV
.type SpMV, @function
SpMV:
.LFB0:
.cfi_startproc
testq %r9, %r9
je .L15
movsd .LC1(%rip), %xmm2
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movabsq $-9223372036854775808, %rbp
pushq %rbx
.cfi_def_cfa_offset 24
.cfi_offset 3, -24
xorl %ebx, %ebx
.p2align 4,,10
.p2align 3
.L7:
movq (%rcx,%rbx,8), %rax
testq %rax, %rax
je .L8
movq %rbx, %r11
movq %rbx, %r10
pxor %xmm1, %xmm1
salq $5, %r11
salq $8, %r10
addq %rax, %r11
salq $3, %r11
jmp .L6
.p2align 4,,10
.p2align 3
.L19:
cvttsd2siq %xmm0, %rax
.L5:
movsd (%r8,%rax,8), %xmm0
mulsd (%rsi,%r10), %xmm0
addq $8, %r10
addsd %xmm0, %xmm1
cmpq %r10, %r11
je .L3
.L6:
movsd (%rdx,%r10), %xmm0
comisd %xmm2, %xmm0
jb .L19
subsd %xmm2, %xmm0
cvttsd2siq %xmm0, %rax
xorq %rbp, %rax
jmp .L5
.p2align 4,,10
.p2align 3
.L8:
pxor %xmm1, %xmm1
.p2align 4,,10
.p2align 3
.L3:
movsd %xmm1, (%rdi,%rbx,8)
addq $1, %rbx
cmpq %rbx, %r9
jne .L7
popq %rbx
.cfi_def_cfa_offset 16
popq %rbp
.cfi_def_cfa_offset 8
ret
.L15:
.cfi_restore 3
.cfi_restore 6
ret
.cfi_endproc
.LFE0:
.size SpMV, .-SpMV
.section .rodata.cst8,"aM",@progbits,8
.align 8
.LC1:
.long 0
.long 1138753536
.ident "GCC: (Ubuntu 8.1.0-5ubuntu1~16.04) 8.1.0"
.section .note.GNU-stack,"",@progbits
オブジェクトサイズ[byte]
1616
RISC-V(RV64IMAFDC)
.file "main.c"
.option pic
.text
.align 1
.globl SpMV
.type SpMV, @function
SpMV:
beqz a5,.L1
slli t4,a5,3
li t3,0
.L5:
add a5,a3,t3
ld a5,0(a5)
beqz a5,.L6
slli t1,t3,2
add t1,t1,a5
fmv.d.x fa4,zero
slli a6,t3,5
slli t1,t1,3
add a6,a6,a1
add t1,t1,a1
mv a7,a2
.L4:
fld fa5,0(a7)
fld fa3,0(a6)
addi a6,a6,8
fcvt.lu.d a5,fa5,rtz
addi a7,a7,8
slli a5,a5,3
add a5,a4,a5
fld fa5,0(a5)
fmul.d fa5,fa5,fa3
fadd.d fa4,fa4,fa5
bne t1,a6,.L4
add a5,a0,t3
fsd fa4,0(a5)
addi t3,t3,8
addi a2,a2,256
bne t3,t4,.L5
.L1:
ret
.L6:
fmv.d.x fa4,zero
add a5,a0,t3
addi t3,t3,8
fsd fa4,0(a5)
addi a2,a2,256
bne t3,t4,.L5
j .L1
.size SpMV, .-SpMV
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
オブジェクトサイズ[byte]
1288
a_columnの型が、doubleではなく正しくはsize_tであるべきでした。
本文中の修正後と比べると、x86_64は1616→1344と小さくなり、行数もかなり減りました。差分を見ても、多くの箇所で変更があります。
一方、RISV-Vの方はほとんど変化せず、24行目のfldがld(倍精度ロードが普通のロード)になり、27行目のfcvt(double→ulong)変換がなくなっただけで「型を変更しただけ」というのが素直に現れていて、分かりやすいですね。
Discussion