RISC-V実機でDebianを動かしてみる(&ちょっとアセンブリ比較)

2019年4月26日

以前から何度かブログにも登場している通り、フィックスターズでは社内有志で勉強会を半年区切りぐらいで開催しています。
そして今期は「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に基づいています。

電源を入れて初回起動する

  1. まずはHiFive Unleashedを入手してください。croud supplyから買うと(送料込みで$1039)、1ヶ月程度で届きます。
  2. ボードを取り出し、SDカードがちゃんと入っていることを確認してください
  3. DIPスイッチを全部1(左側)にします
  4. 電源コードを挿します。ファンが回り始めます
  5. 電源横の赤いスイッチ(電源スイッチ)を押し込みます
  6. 30秒ぐらい待ちます
  7. Linuxが起動します。マイクロUSB横のLEDが点滅します

コンソールからLinuxを操作する

HDMI等の画面出力端子はありませんので、操作はCUIコンソールでやります。sshでつなぐこともできますが、DHCPで割り当てられたIPアドレスを調べるのがちょっと面倒なので、初回はシリアル接続で試すことをオススメします。
また、ここではWindowsから接続する方法を紹介します。MacやLinux等の場合についてはGetting Startedの5.2に書かれていますので、その通りやってください。

  1. HiFive Unleashed側のマイクロUSBとWindows機のUSB端子を接続します
  2. デバイスマネージャーを起動して、「ポート(COMとLPT)」に新しくCOMポートが2つ増えていることを確認してください。多くの環境ではCOM3とCOM4になっていると思います。もしなければ、FDTIのドライバーを入れてください
  3. TeraTermをインストールし、起動します
  4. 「設定→シリアルポート→ボー・レート」を115200に設定します
  5. 「ファイル→新しい接続→シリアル」からポートを選択します。おそらく増えた番号のうち大きい方(COM4)のはずです
  6. エンターキーを押すとbuildroot login:と聞かれます。idはroot、初期パスワードはsifiveでログインしましょう
  7. BuildrootというLinuxが使えるようになりました!

注意点として、(当然ですが)先に電源を入れてからTeraTermを接続しないと、画面に何も出てきません。間違えた場合or再起動したあとは、接続し直してください。

終了方法

  1. haltコマンドを実行します(※shutdownコマンドはありません)
  2. Power offが出るまで待ちます
  3. 電源ボタンを押してオフにします
  4. 電源コードを抜きます

Debianで動かそう

前述の通り、初期出荷時のままでBuildrootが入っているので、Linuxとしてはそのまま使うことができます。
しかし、この環境はかなりの最小環境で、例えばGCC等のコンパイラはありませんし、当然パッケージマネージャーなんてものはありません。
公式フォーラムでもSiFiveの人が開発等したかったらまともなOSを入れてねと回答しています。

ということで、普段使っているのに近いLinux distroを入れましょう。本記事執筆時点ではFedoraDebianがしっかり使えそうですが、ここではDebianを選択することにします(社内ではUbuntuが多く使われているため)。
他のdistroもありそうなので、興味がある方はぜひ試してみてください(そしてブログ記事等でぜひ教えてください!)

自力でBuildrootを書き換える方法を試す

Debianを入れる前に、まず、標準のBuildrootを自力でビルドし、SDカードを書き換えられるか試してみることをオススメします。

また、操作ホスト環境はUbuntu(16.04)を推奨します(Getting Startedによる)。他の環境でもできなくはないでしょうが、少なくとも調べた限りWindows/WSLからでは少し厄介そうですので、Windowsしかない場合はVMを立ち上げたほうが良さそうです。

  1. 電源を落とし、マイクロSDカードを取り出します
  2. SDカードリーダーをUbuntuが動いているホスト機(≠SiFive Unleashed)に挿します。この時、SDカードが/dev/sdX(Xは環境による…他になければsdbが多そう)に見えているのですが、これは普通にmountできません。が、問題ないです。
  3. 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”と書かれていましたが(少なくとも私の環境では)必須でした。

  4. Getting Startedの7.2.4に従って、SDカードの中身を完全消去します。$ sudo gdisk /dev/sdXを実行後、dで全パーティションを消去、oで新しくMBRを作って、wで書き込んでください。
  5. $ sudo make DISK=/dev/sdX format-boot-loaderでSDカードに書き込みます。この時、Getting Startedにある通り、失敗することがあります(私の環境では必ず初回は失敗しました)。成功して完了するまで何度かやってください。
  6. マイクロSDカードをホストから抜いて、HiFive Unleashedに挿しなおします
  7. DIPスイッチのMSEL2を0(右)にしてください。Getting Startedには書いてありませんが、freedom-u-sdkのREADMEに書いてあります(アップデートで変わったようです)。
  8. あとは通常通り起動します

これでちゃんとBuildrootが起動し操作できるようになっていれば、とりあえずSDカードの中身を書き換える操作はできました。

Debianを入れて動かす

さてここからが本題のDebianを動かすところです。ここでは、Debian wikiに従って、第2パーティションに入れる方法をとります。

  1. 前節のBuildrootを書き換える方法のうち、4.パーティション完全消去までをやります
  2. $ sudo make DISK=/dev/sdX format-demo-imageを実行します(format-boot-loader→format-demo-imageに変更)。Debianのビルド済みイメージがダウンロードされ、自動でSDカードに展開されます。少し時間がかかります(20分ぐらい?)。
  3. 通常通り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
  4. これで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 RISC-V(RV64IMAFDC)
アセンブリ出力
	.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
	.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]
1344 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 RISC-V(RV64IMAFDC)
アセンブリ出力
	.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
	.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]
1616 1288

a_columnの型が、doubleではなく正しくはsize_tであるべきでした。

本文中の修正後と比べると、x86_64は1616→1344と小さくなり、行数もかなり減りました。差分を見ても、多くの箇所で変更があります。

一方、RISV-Vの方はほとんど変化せず、24行目のfldがld(倍精度ロードが普通のロード)になり、27行目のfcvt(double→ulong)変換がなくなっただけで「型を変更しただけ」というのが素直に現れていて、分かりやすいですね。

Tags

About Author

YOSHIFUJI Naoki

yoshifujiです。計算力学的なプログラムを高速化することが得意です。プログラミング自体はチョットダケワカリマス。 Twitter: https://twitter.com/LWisteria

1件のコメント

  • Thanks for the acknowledgement. Glad to help anytime 🙂

Leave a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

Recent Comments

Social Media