少ないリソースを酷使する

低レイヤーとレトロPC(PC98,MSX)が好きな情報学生

RaspberryPiのCPUを起動せずGPUだけでLチカ

以下のツイートがTwitterでプチバズったので簡単な解説

先駆者様

あとから知りましたが、RaspiのGPUのみでLチカする試みは私が最初ではないようです。 以下の人たちがGPU Lチカをしています。

github.com

qiita.com

アセンブラが好きな人、環境構築がめんどい人は「Herman H Hermitage」さんのGitリポジトリを見ると良さそうです。(簡単なアセンブラが入ってる)

GCCC言語が好きな人、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のGPUvideocore4だと勘違いしていましたが、正しくはvideocore6のようです。
いろいろ調べているとRaspi4のGPUはvideocore4であると記述している記事も混ざってたりして混乱しました...
どうやらローマ数字のIVVIが似ているので、間違えている人もいるようです。ややこしや。

ちなみにVC4VC6の互換性は微妙そう

Pi 4 - full specification of VideoCore 6 - Raspberry Pi Forums

ブートプロセス

次に、Raspiのブートプロセスについてですが、
RaspberryPiに電源を入れるとBCMチップはGPUから起動します。で、彼が簡単なブートプロセスを行ってからARM CPUを起こすというわけです。これが特徴的です。
簡単なブートプロセスの流れはこの記事がわかりやすいです。

qiita.com

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 rev2GPIO16
Pi B+, Pi2 BGPIO47
に割り当てられています。
RaspberryPi 3からはなぜかGPIO割当がなくなったのでLチカできません・・・一応 i2c の信号線とつながっているらしいので i2c をベアメタルでセットアップすればLチカできますが少しめんどくさそうです。

Lチカの解説

個人的に「Herman H Hermitage」さんのGitリポジトリがわかりやすかったので、この中のコードを例にさらっと解説していきます。 リポジトリの「blinker01」が初代Raspi 1でLチカするコードで、「blinker01-beeplus」がRaspi 1 B+ から Raspi zeroRaspi 2 B (ver1.1 ?)でLチカするコードです。
実際やることは「blinker01」の中に書いてあるとおりなのですが、簡単な日本語訳としてここに書き残しておきます。

環境

今回私はRaspi zeroをターゲットにしたので、見るサンプルは「blinker01-beeplus」です。
コンパイルに必要なのはgccmakeですが、コンパイル済みバイナリが入っているのですぐ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ビットの幅を持ったレジスタです。
使い方としては、

  1. GPFSELの各ピンのI/O設定3bitにinputなら000、outputなら001を書き込む。その他は111(GND)とかにしとく
  2. GPSETで出力したいピンのbitに1を立てる
  3. 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はGPFSEL423-21 bitで設定できます。

よって、r0をANDで11111111 11111111 00011111 11111111にして、設定するbitsだけ一度0にします。
で、ORで21 bit目だけに1を立ててあげれば23-21 bit001になります。

あとはそれをGPFSEL4のアドレスにセットします。

メインループ

メインループで必要な値をレジスタに持ちます。

  movi(r1, GPSET1);
  movi(r2, GPCLR1);
  movi(r3, 1<<15);

GPSET1orGPSCLR1GPIO32~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.binbootcode.binにリネームしてSDカードにそのまま放り込めばOKです。

ポエムと宣伝

これがGPGPUと呼べるかはわかりませんが、GPGPUはとても面白い技術だし、AIとか機械学習に関連して注目度も高いです。
私はOSの研究室に所属していながらも、去年からGPGPUの活用研究を行っていて、Raspi GPUの活用で論文出したりしてますので興味ある方は気軽に声をかけてくださると嬉しいです!(色々答えれたり、答えられなかったりすることもありますが)

あと、技術書典7に「RaspberryPiのGPUを使い倒す本(仮)」で申請してるので、もしサークル当選してたらチェックしていただけると嬉しいです!