RaspberryPiのCPUを起動せずGPUだけでLチカ
以下のツイートがTwitterでプチバズったので簡単な解説
ラズパイでCPUを起動せずにGPUだけでLチカする妙技を手に入れた pic.twitter.com/6NltHod9FM
— 大将 (@T_taisyou) 2019年7月6日
先駆者様
あとから知りましたが、RaspiのGPUのみでLチカする試みは私が最初ではないようです。 以下の人たちがGPU Lチカをしています。
アセンブラが好きな人、環境構築がめんどい人は「Herman H Hermitage」さんのGitリポジトリを見ると良さそうです。(簡単なアセンブラが入ってる)
GCCやC言語が好きな人、GPUプログラミング環境を構築したい人は@stkchpさんのQiita記事が良さそうです。
前提知識
"CPUを起動せずに"とはどういうことか理解してもらうためには、BroadcomのBCMチップとRaspberryPiの簡単な理解が必要です。
Raspiの頭脳
Raspiに乗っている頭脳はBroadcomのチップで、ARM CPUとVideocore4(VC4)というGPUが一体となったSoC的なものです。
以下は、Raspiのチップ表です
RaspberryPi SoC's
Raspberry Pi 1系 zero系 |
Raspberry Pi 2 Model B |
Raspberry Pi 3系 | Raspberry Pi 4系 | |
---|---|---|---|---|
チップ | BCM2835 | BCM2836 BCM283 |
BCM2837 BCM2837B0 |
BCM2711 |
GPU | VideoCore IV | 〃 | 〃 | VideoCore VI |
CPU | ARM 1176JZF-S | (ARM Cortex-A7) ARM Cortex-A53 |
ARM Cortex-A53 | ARM Cortex-A72 |
追記:2019/7/8
Raspberry Pi 4のGPUはvideocore4
だと勘違いしていましたが、正しくはvideocore6
のようです。
いろいろ調べているとRaspi4のGPUはvideocore4である
と記述している記事も混ざってたりして混乱しました...
どうやらローマ数字のIV
とVI
が似ているので、間違えている人もいるようです。ややこしや。
ラズパイ4はvc4からvc6に進化してるようですね!
— しお@深圳 (@o_sio) July 8, 2019
ちなみにVC4
とVC6
の互換性は微妙そう
Pi 4 - full specification of VideoCore 6 - Raspberry Pi Forums
ブートプロセス
次に、Raspiのブートプロセスについてですが、
RaspberryPiに電源を入れるとBCMチップはGPUから起動します。で、彼が簡単なブートプロセスを行ってからARM CPUを起こすというわけです。これが特徴的です。
簡単なブートプロセスの流れはこの記事がわかりやすいです。
VC4には小さいOSが乗っているようです(VCOS)
GPUブートコード(bootcode.bin)について
SDカードから最初に読み込まれるGPUプログラムですが、ソースコードは公開されていません。バイナリはARM CPUの命令ではなくVC4の命令で記述されています。
このプログラムが実行される段階ではCPUは停止しているので、ここをHackしてLチカしようというのが今回の試みです。
注意点
RaspberryPi 4では「bootcode.bin」は工場出荷時にROMに焼き込まれるようなので今回のような手法は使えません。
RaspberryPiのACT LEDについて
RaspberryPiには表面に「ACT」と書かれたLEDがついてます。
これは、
・Pi B rev1, Pi B rev2はGPIO16
・Pi B+, Pi2 BはGPIO47
に割り当てられています。
RaspberryPi 3からはなぜかGPIO割当がなくなったのでLチカできません・・・一応 i2c の信号線とつながっているらしいので i2c をベアメタルでセットアップすればLチカできますが少しめんどくさそうです。
Lチカの解説
個人的に「Herman H Hermitage」さんのGitリポジトリがわかりやすかったので、この中のコードを例にさらっと解説していきます。
リポジトリの「blinker01」が初代Raspi 1でLチカするコードで、「blinker01-beeplus」がRaspi 1 B+ から Raspi zero、Raspi 2 B (ver1.1 ?)でLチカするコードです。
実際やることは「blinker01」の中に書いてあるとおりなのですが、簡単な日本語訳としてここに書き残しておきます。
環境
今回私はRaspi zeroをターゲットにしたので、見るサンプルは「blinker01-beeplus」です。
コンパイルに必要なのはgcc
とmake
ですが、コンパイル済みバイナリが入っているのですぐLチカできます。
FAT32でフォーマットされたSDカードも必要です。
コードとバイナリ
GPUのブートルーチンでは最初の0x200 byte
は使わないらしいので0埋めしときます。
以下で説明するGPIO制御はBCM2835のリファレンスを読めば大体書いてます。(pp.90あたり) https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
GPIOの制御レジスタ
equ(GPFSEL4, 0x7e200010); /* input or outputを決める 90p */ equ(GPSET1, 0x7e200020); /* GPIOに出力(High) 90p */ equ(GPCLR1, 0x7e20002c); /* GPIOをクリア(Low) 90p */
それぞれ32ビットの幅を持ったレジスタです。
使い方としては、
GPFSEL
の各ピンのI/O設定3bit
にinputなら000
、outputなら001
を書き込む。その他は111
(GND)とかにしとくGPSET
で出力したいピンのbitに1
を立てるGPCLR
でクリアしたいピンのbitに1
を立てる
みたいな使い方をします。
GPIOの設定
movi(r1, GPFSEL4); ld(r0, r1); andi(r0, ~(7<<21)); ori(r0, 1<<21); st(r0, r1);
Raspi zero の ACT LEDはGPIO47
です。
GPIO47はGPFSEL4
の23-21 bit
で設定できます。
よって、r0
をANDで11111111 11111111 00011111 11111111
にして、設定するbitsだけ一度0
にします。
で、ORで21 bit
目だけに1
を立ててあげれば23-21 bit
が001
になります。
あとはそれをGPFSEL4
のアドレスにセットします。
メインループ
メインループで必要な値をレジスタに持ちます。
movi(r1, GPSET1); movi(r2, GPCLR1); movi(r3, 1<<15);
GPSET1
orGPSCLR1
はGPIO32~53
を扱います。
よって[制御したいGPIO] - 32
のビットに1
を立てれば出力が制御できます。
st(r3, r1); // GPSET1(15bit) High st(r3, r2); // GPCLR1(15bit) High
ちなみにRaspi zeroとか2のACT LEDは、SET
で消灯、CLR
で点灯するようです。ほかは検証してません。
適当なディレイ
適当な回数ラベルジャンプループをして点灯・消灯の間隔を空けます。
movi(r0, 0); label(delayloop1); addi(r0, 1); cmpi(r0, 0x100000); bne(delayloop1);
メインループ
最後に消灯→点灯の処理をラベルジャンプで囲めばLチカループの完成です。
label(loop); ・・・ bra(loop);
実行
makeしてできたblinker01.bin
をbootcode.bin
にリネームしてSDカードにそのまま放り込めばOKです。
ポエムと宣伝
これがGPGPUと呼べるかはわかりませんが、GPGPUはとても面白い技術だし、AIとか機械学習に関連して注目度も高いです。
私はOSの研究室に所属していながらも、去年からGPGPUの活用研究を行っていて、Raspi GPUの活用で論文出したりしてますので興味ある方は気軽に声をかけてくださると嬉しいです!(色々答えれたり、答えられなかったりすることもありますが)
あと、技術書典7に「RaspberryPiのGPUを使い倒す本(仮)」で申請してるので、もしサークル当選してたらチェックしていただけると嬉しいです!