一覧が欲しくなったので作りました。
Raspberry Pi 3 model B オーディオ周りの話のまとめ。
何気なく使っていた Raspberry Pi 3 のアナログオーディオ出力ですが、少し調べてみたところ、かなり変な動きでした。音は出ますが、まともなオーディオ用の回路には見えません。変な点はとりあえず 3つ見つけました。
変な点 1つ目。サンプリング周波数(fs)の半分、つまり 1/2 fs の Sin 波(fs = 48kHz なら 24kHz)が出ません。無音です。オーディオ用の DAC ならば、1/2 fs の Sin 波を出すときに波形が崩れることはあろうとも「無音」はあり得ません。この時点で RasPi 3 のオーディオ出力は変です。どこかで音が出なくなる周波数がありそうですが、上限の周波数までは調べ切れていません。
変な点 2つ目。サンプリング周波数(fs)が 44kHz 系でも 48kHz 系でもありません。1/4 fs の矩形波を入力すると出力は Sin 波になります(フーリエ変換が理想的だった場合、2014年 11月 20日の日記参照)。他の波形と違い歪みがわかりやすいので、私がオーディオ機器をテストする際によく使います。
本来は Sin 波になるはずなのに fs = 44kHz でも 48kHz でも、Sin 波とは程遠い歪んだ波形になります。同じデータを Creative Sound Blaster X-Fi Go! Pro で出力するときれいな Sin 波になります。やっぱり RasPi 3 のオーディオ出力はおかしいです。
fs = 44kHz, 1/4 fs = 11kHz Sin 波の波形(Raspberry Pi 3)
fs = 48kHz, 1/4 fs = 12kHz Sin 波の波形(Raspberry Pi 3)
fs = 44kHz, 1/4 fs = 11kHz Sin 波の波形(Sound Blaster X-Fi Go! Pro)
変な点 3つ目。なぜか GPU を制御しています。RasPi 3 は Broadcom BCM2837 という SoC を使っています。サウンドドライバは linux/drivers/staging/vc04_services/bcm2835-audio/ の下にあるんですけど、このドライバの実装を見ると VideoCore 4 と呼ばれている GPU にデータを投げつける実装になっています。
GPU のはずの VideoCore 4 にサウンドデータを投げつけると音が再生される、謎の仕組みです。Video じゃないのに……??
引き続き Raspberry Pi 3 のオーディオ出力波形がグニャグニャしている謎を調べます。サンプリング周波数の 1/4 の矩形波を再生(理想的な周波数変換がなされると Sin 波になるはず)を出力した際の周波数スペクトルを見ます。パワーを合わせるために波形の最大値を 3.2V くらいに合わせて測ったつもりです。が、RasPi 3 の波形は歪んでいてピークがわからないので、合っているか自信ありません。参考程度です。
Raspberry Pi 3 のオーディオ出力です。ピークの 11kHz 以外に 15.5kHz や 37kHz など、多数の高調波が出現します。高調波は安定して出現するので、波形の崩れは外乱やノイズではなさそうです。
fs = 44kHz, 1/4 fs = 11kHz Sin 波の周波数スペクトル(Raspberry Pi 3)
続いて fs = 48kHz で 12kHz の Sin 波を再生した場合です。12kHz、36.5kHz、60kHz、85kHz 付近、つまり 3次、5次、7次……のように奇数倍の高調波が出ます。なんだこれ?
fs = 48kHz, 1/4 fs = 12kHz Sin 波の周波数スペクトル(Raspberry Pi 3)
Creative Sound Blaster X-Fi Go! Pro の fs = 44kHz で 11kHz Sin 波出力です。きれいに「11kHz のみ」にピークが出ます。オーディオ機器ならこれが普通ですよね。
fs = 44kHz, 1/4 fs = 11kHz Sin 波の周波数スペクトル(Sound Blaster X-Fi Go! Pro)
矩形波を周波数領域に変換すると 1f, 3f, 5f, ... のように奇数次にスペクトルが出ます。Raspberry Pi 3 のオーディオ出力も同じ傾向がありますから、おそらく PWM 波形をフィルタしてオーディオ出力を作っているのだろうという予想が立ちます。
The World is Your Weapon をコンプリートしました。2018年にふりーむ!で公開されたフリーゲーム「ぶきあつめ」の Steam 移植版です。ふりーむ!版でも十二分に遊べますが、作者さんを応援したいってことで購入しました。
Steam 版はダンジョン探索ゲームが追加実装されていますが、本筋と関係ないうえ非常に長いです。私はほとんどやってません……。
一応、ふりーむ!へのリンクも貼っておきましょうか。
ぶきあつめ 〜なんでも武器になる RPG〜 - ふりーむ!
ぶきあつめでは、近づけるものはほぼ何でも(モノ、人、モンスターなど)武器にできます。RPG をやりなれている人は、スルーしてしまいそうな壁や床といった意外なものまで武器にできます。常識を捨ててプレイしましょう。私のお気に入りは地面の穴、ヘイワ村、海ですね。「ええっ!?それ取れるんだ??」と思わず笑ってしまいます。しかも案外強いです。海なんてほぼ最強ランクじゃないですかね?
このゲームは剣や杖など普通の意味での武器の形をしているものを除けば、基本的に大きなものほど強い傾向があります。ただし大きなものは上側の視界を塞ぐので非常にゲームがやりづらいトレードオフがあります。特にヘイワ村や海などは上半分がほぼ見えなくなるため、気づかないうちに上からモンスターに殴られることがあります。
エンディングは 4つ(通常)+1つ(シークレット)ですが、Steam 版はシークレットエンドが実装されていないのか、条件を満たしても発生しませんでした。ちょっと残念。
メモ: 技術系?の話は Facebook から転記しておくことにした。大幅に追記。
一覧が欲しくなってきたので作りました。
Raspberry Pi 3 のオーディオ出力はハードウェア PWM を使っているみたいです。適当に PWM の FIFO レジスタにデータを書き込むと、オーディオ出力に変なスパイクが出ました。さらに PWM の FIFO モードを解除して各 PWM チャネルを直叩きすると、Left が PWM1 で Right が PWM0 と対応していることもわかります。Raspberry Pi の Schematics とも一致しますし、おそらく間違いないでしょう。
RasPi の Audio Out と HW PWM の出している信号を同時に見たかったので、GPIO Function Select を直接弄って GPIO13 ピンに PWM 信号を同時に出して(※)みました。黄色の Ch1 が Audio Out で、水色の Ch2 が PWM の信号です。
fs = 44kHz, 30Hz Sin 波の周波数スペクトル(Raspberry Pi 3)
(※)やり方は GPFSEL1 の FSEL13(bit 11 - 9)を 0b100 にするだけです。GPFSEL1 はアドレス 3f200004 にあるので、例えば元の値が 0x24000 だとしたら 0x24800 に変えれば OK です。
30Hz の Sin 波を再生していますが、頭が欠けたような変な波形になっています。最初は理由がわからなかったのですが Facebook で色々コメントいただいたりして調べているうちに原因がわかりました。ボリュームが大きすぎでした。
先程の波形を取得したときのボリューム設定では 0.4dB のゲインが掛かっています。この設定だと Sin 波の頂上付近がクリッピングされて PWM の出力が 100%固定になっています。
fs = 44kHz, 30Hz Sin 波の周波数スペクトル(Raspberry Pi 3)0.4dB ゲインあり
次がゲインを解除した設定です。PWM 波形が 100%に張り付いている時間が大幅に減って、オーディオ出力も割と綺麗なSin 波になります(実は拡大するとうねってますけど…)。
fs = 44kHz, 30Hz Sin 波の周波数スペクトル(Raspberry Pi 3)ゲインなし
色々な周波数を試したところ 1kHz, 2kHz くらいまでは割ときれいです。
fs = 44kHz, 1kHz Sin 波の周波数スペクトル(Raspberry Pi 3)
fs = 44kHz, 2kHz Sin 波の周波数スペクトル(Raspberry Pi 3)
ところが 4kHz, 8kHz 辺りになってくると波形がウニョウニョし始めます。
fs = 44kHz, 4kHz Sin 波の周波数スペクトル(Raspberry Pi 3)
fs = 44kHz, 8kHz Sin 波の周波数スペクトル(Raspberry Pi 3)
11kHz になるともはや Sin 波には見えません。
Raspberry Pi 3 のオーディオ出力は HW PWM の出力(矩形波)を何らかのフィルタ回路を通しているのではないか?という疑問を解決するため、オーディオ出力回路を調べます。
会社のみなさまに回路をシミュレーションする LTspice というツールを教えていただいた(ありがとうございます!)ので、Raspberry Pi 3 の Schematics 通りに回路を組んで回路シミュレーションをしました。Spice という名前は聞いたことありましたけど、使うのは初めてです。
私が使っているのは Raspberry Pi 3 model B rev 1.2 ですので、rpi_SCH_3b_1p2_reduced.pdf という回路図が該当します。オーディオ出力回路だけ抜粋すると下記のとおりです。
Raspberry Pi 3 model B rev 1.2 のオーディオ出力回路
(5/17 訂正)
オーディオ出力回路が微妙に違う回路図だった(C58, C60の番号が違う)ので上げ直しました。
前段が RC ローパスフィルタ回路、後段が RC ハイパスフィルタ回路と思われます。この回路の 1ch 分を LTspice で組んで AC 特性をシミュレーションしますと、こんな感じになりました。
参考までにゲインが -3dB になる周波数(フィルタのカットオフ周波数)に線を足しています。低いほうが 1.7〜1.8Hz 辺り、高い方が 21〜22KHz くらいに見えます。素人目には fs = 48KHz のとき再生帯域 24KHz ですから、発生してはならない不要な高周波を落としているフィルタに見えます。
フィルタの AC 特性はわかったので、矩形波を入れたときにどんな波形になるかも見ておきたいと思います。主に回路の入力やシミュレーション方法をミスっていないか確認のためです。初めて使うので、これで合っているのか?と何かと不安なんです……。
V1 の設定を変更して 12.5kHz の矩形波を出す設定に変えます。RasPi の PWM は変調速度が少なくとも 50MHz はありそう(※)なので、立ち上がり立ち下がりは 20ns にしています。
(※)PWM で 0 と 1 が交互に出力されるであろう Duty 比 1/2 にすると 50MHz とオシロ様がおっしゃっているので、変調速度は 50MHz だと思うのですが、本当はもっと速いけど我が家のオシロ Tektronix TBS 1052B の測定限界(50MHz モデル)を超えていて、正しく測れていないだけかもしれません。
我が家のオシロは信号の周波数 20MHz 辺りから波形がミミズみたいになって、何が表示されているのかわからなくなるので、HW PWM 信号を直接見るのは無理です。仮に正しく表示できても波形から値を読み取るのは困難ですし、波形の正確性は気にしなくて良いのです。
対して実際の出力がこんな感じです。PWM が矩形波に近くなっている中央部分の波形を見ると、オシロの波形とシミュレーションの波形がほぼ同じ波形になっています……よね?
fs = 48kHz, 12kHz Sin 波の周波数スペクトル(Raspberry Pi 3)
Rasberry Pi 3 の HW PWM はレジスタに設定した値を Duty 比だと思ってずっと出力し続けます。最大値(0x800、設定で変えられる)を書き込むとずっと 3.3V、0 を書き込むとずっと 0V、半分の値を書くと 3.3V と 0V を交互に出力します。特に不思議なところはありません。
Raspberry Pi 3 のオーディオ出力は PWM の Duty 比を 100%に固定すると、なぜオーディオ出力が減衰していくのか?という点が良くわからないままです。以前、ボリューム設定をミスっていたときの波形がわかりやすいです。数カ所で PWM 出力が Duty 比 100%に張り付いていますが、なぜかオーディオ出力は上限値に張り付かず減衰します。
fs = 44kHz, 30Hz Sin 波の周波数スペクトル(Raspberry Pi 3)0.4dB ゲインあり
LTspice の過渡応答シミュレーションの結果では Duty 比 100%が続いたら 0.9V に張り付いており、減衰するような波形にはなりません。何かシミュレーションをミスっているんでしょうか?それともまだ隠し要素があるんでしょうか……?
Raspberry Pi 3 の HW PWM は意外と変調速度が速く、最も ON/OFF が頻発する Duty 比 50%にするとこのような波形になります。PWM 信号はもっとエッジが急峻だと思われますが、我が家のオシロスコープが測れる帯域(50MHz)の限界に達しており、波形がすっかり丸くなっています。
RasPi 3 の PWM 信号波形(Duty 比 50%)
RasPi 3 の PWM 信号波形(Duty 比 25%)
もっと性能の良いオシロを買えば良いだけですけど、値段が高すぎるんですよね。200MHz を測れるオシロはエントリーモデル TBS 1202C でも 20万円!高すぎでしょ!?10万円超えはホビー向けは高すぎだと思いますし、企業や研究所向けとしては TBS 1202C のスペックでは足りないでしょう。誰向けでしょうね?学校かなあ?Tektronix の値付け方針がいまいち理解できません。
メーカーにこだわらなければ同じ予算でもっと良いオシロが買えることは知っています。最近だと RIGOL や OWON が人気なんですかね?テック系の YouTuber が使っているのをたまに見かけます。でも憧れの Agilent や Tektronix のオシロ欲しいじゃないですか……。そんなことないですか?
Raspberry Pi 3 の Audio Out の残された謎がいくつかあります。
一部は解決しましたが、最後の謎が良くわからないままです。
端的に言えばシミュレーション時間が不足していました。シミュレーション時間を 0s〜0.05s ではなく 1s〜1.05s にしたところ、-500mV〜500mV の間で振れるようになりました
0s〜1s のシミュレーション結果を見ると、数百 ms かけて負の電圧側に移行していくようですね。
オシロの設定ミスでプローブを繋ぐときの設定(感度 x10)になっていて、実測の電圧が 10倍に見えていただけでした。実測(400mV)とシミュレーション(500mV)のスケールは合いましたが、電圧は微妙に合っていない点が気になりますね。うーむ。
これはまだ真相がわかりません。未解決です。PWM の Duty 比 100%を維持したときに減衰する速度が異なります。シミュレーションでも減衰はします(500mV → 450mV 程度)。
Audio Out 回路のシミュレーション結果(125Hz 矩形波を入力に設定)
ですが実測の方がはるかに速く減衰します(400mV → 248mV)です。なんでですの……??
Raspberry Pi 3 model B rev 1.2 の Schematics が間違っていて、ハイパスフィルタ側のコンデンサが 47uF ではなく 4.7uF が実装されていると仮定すると、シミュレーションと実測の辻褄が割と合うことに気づきました。4.7uF でシミュレーションすると AC 特性はこのような感じになります。拡大図も載せておきます。
Audio Out 回路の AC 特性(コンデンサ容量 4.7uF)
Audio Out 回路の AC 特性(4.7uF、縦軸方向に拡大)
もし Raspberry Pi 3 の Audio Out 回路のコンデンサ容量が 4.7uF だとすると、30Hz Sin 波と 100Hz Sin 波にゲインの差が生じるはずです。グラフを見た限り 30Hz だと約 1.2〜1.3dB 程度カットされてしまうはずです。
Sound Blaster で 30Hz と 100Hz の Sin 波を同時に鳴らした出力の周波数スペクトルを見ると、30Hz と 100Hz にゲインの差はほとんどないです。
Sound Blaster X-Fi Go! Pro にて 30Hz, 100Hz 同時再生時の周波数スペクトル
Raspberry Pi 3 で 30Hz と 100Hz の Sin 波を同時に鳴らすと、30Hz, 100Hz 同時再生時の周波数スペクトル30Hz と 100Hz にゲイン 1.2dB ほどの差が生じています。
RasPi 3 にて 30Hz, 100Hz 同時再生時の周波数スペクトル
RasPi 3 で 30Hz の Sin 波と 100Hz の Sin 波をそれぞれ単独で鳴らしても 30Hz の方がピーク電圧が低いですし、ハイパスフィルターに引っかかってカットされてるような動きに見えます。
フィルタの AC 特性だけでなくて、出力波形もかなり実測に近くなります。
Audio Out 回路のシミュレーション結果(125Hz 矩形波)
Audio Out 回路のシミュレーション結果(125Hz 矩形波、コンデンサ容量 4.7uF に変更)
RasPi 3 の実測値(黄色 Audio Out、水色 PWM 信号 125Hz 矩形波)
かなり良い線行ってますよね。やっぱりコンデンサ容量 4.7uF じゃないか?と思うんですけど。
ボードに実装されたコンデンサの容量を測れたら一発でわかるんですけど、ボードを壊してコンデンサを剥がす以外に何か良い方法はないのかなあ。うーん……??
この推測は間違っていて、ケーブルに抵抗が入っていたことが原因でした。その 8(2021年 6月 17日の日記参照)をご覧ください。
Raspberry Pi は何も苦労せず動くので最高に便利ですけど、今回みたいにハードを直叩きしたいケースや、もう少し発展させてカーネルやドライバ開発用に使うことを考えると最高とは言えないですね。ちょっとボードと SoC の仕様がわかりにくいです。
ボードの仕様については Schematics は公開されているものの、コネクタ近辺しか記述がなく GPIO ピンと信号名の関係がわかりません。SoC(BCM283x)の仕様は歯抜け(Broadcom が一般人に見せたくない部分を削っている?)で意味不明な点がチラホラあります。
ちょっとイマイチ感はあるものの Broadcom 並の情報公開をしてくれるのは、ありがたいことです。他のベンダーですと AllWinner, Amlogic, Rockchip 辺りはセットトップボックス(STB)向け SoC の仕様を公開しています。非常にありがたいです。
仕様がオープンなだけあって、シングルボードコンピュータ(SBC)ではほぼこの 3社の SoC が採用されます。あとは Samsung が採用されるくらいかな?
我らが日本ベンダーは SoC の情報をほぼ公開しません。SBC に日本の SoC が採用されることもほとんどありません。一般開発者が日本の SoC に触れる機会はあまりないです。仕様書がないのか、仕様公開するメリットを感じないのか、SBC のような小規模顧客を相手する余裕がないのか……、真相は知りませんけどちょっと残念ですね。
その 1 - 波形観察(2021年 5月 2日の日記参照)で Raspberry Pi 3 では高い音が出ない現象が発生していました。この原因について調べたメモです。結論だけ言えば、PWM のハード的には出せるはず、でも根本原因はわかりませんでした。
RasPi 3 の HW PWM は出力 Duty 比を約 20us ごとにしか変更できないようです。確認方法は下記のようにしました。
これにより最速で PWM Duty 比を変更することができます。下記は設定例です。
3f20c000 00002121 00000032 00000303 70776d30 3f20c010 00000800 00000000 70776d30 70776d30 3f20c020 00000800 00000800 70776d30 70776d30
Duty 比は何でも良いですけど、0% 100%が一番波形が見やすいと思います。この設定例で言えば 0x000, 0x000 と 0x800, 0x800 を交互に書きます。
最高速度で PWM の Duty 比を切り替えたときの波形(黄色 1ch オーディオ出力、水色 2ch PWM 出力)
上記操作をしたときの PWM の波形を見ると約 20us に一度しか波形が変わっていません。従って RasPi 3 のオーディオ出力は 25kHz 程度が上限です。
なぜ 20us に一度しか変更できないか気になりますが、BCM2835 の仕様書には一切記載がありません。試行錯誤してみた結果、clk_pwm_domain のクロック周波数を早くすると変更間隔も速くなるみたいです。
PWM のクロック設定は PWMCTL(アドレス 3f1010a0)と PWMDIV(3f1010a4)にあるようです。これも仕様書には載っていませんので、Linux のクロックドライバ drivers/clk/bcm/clk-bcm2835.c から読み取るしかありません。イマイチな仕様書ですね……。
3f1010a0 00000096 00005000 00000200 00000000 3f1010b0 00000000 00000000 0000636d 0000636d
クロックドライバの実装をみるに、CTL レジスタのビット 0〜3 がクロック源です。6 なので PLLD_CORE というクロック源が選択されているようです。ビット 9 が分周するかどうかの設定です。上記の設定だと有効でも無効でもクロックの速度が変わりませんでした。デフォルトが 5分周なのか?どこかでデフォルトの分周比を設定しているのか?どちらなのかは良くわかりません。
DIV レジスタは分周比を表すようです。仕様がイマイチわかりませんが固定小数点のようで、上位ビットが整数部分、下位 12ビットが小数点以下を表しているようです。上記の設定だと 5分周を意味しているはず。
最高速度で PWM の Duty 比を切り替えたときの波形(黄色 1ch オーディオ出力、水色 2ch PWM 出力)分周比設定 0x4000
分周比を 0x4000 に変更すると Duty 比の変更速度も 5/4 倍になって、16us 間隔になるので、上位ビットが整数部分であることはほぼ間違いなさそうです。ちなみにクロック制御部のレジスタを変更する際は、上位 16ビットに 0x5a00 を OR して write しないといけません。
クロック源の周波数を debugfs から確認すると 500MHz みたいです。5分周だから 100MHz 駆動でしょうか?
# cat /sys/kernel/debug/clk/plld_core/clk_rate 500000006
チャネル数は 2ch、FIFO 幅は 32bit ですから、100MHz / 2 / 32 = 1.5625MHz つまり 0.64us 間隔ならまだわかるんですが、どうしてその 30倍もある 20us 間隔になるんでしょう。さっぱりです。
PWM の能力的にはギリギリ 24kHz も再生できるはずですが、以前示したように通常のオーディオ出力経路(CPU → GPU → DMA → PWM の経路と思われる)を使うと、24kHz が再生できません。24kHz どころか fs = 32kHz での 16kHz でさえ再生不可能です。
PWM 以外に、例えば GPU 側で何か PCM データを弄っているなど、高音を再生する際の制約があるのかもしれません。
艦これが流行ったあとくらいでしたか、Google で「赤城」を検索すると空母やプラモデルの写真ではなく、艦これの絵がたくさん出てきて空母が出てこなくなりました。
ところが最近(?)Google 検索結果の傾向が変わったと Twitter で見たので、試してみました。少なくとも「赤城」は空母が出てくるようになっています。
検索結果が擬人化キャラで埋め尽くされようと、世の中のトレンドの一部ですし間違いとはいえません。Google はそんなこと気にしないのかと思っていましたが、あえて変えたってことは、内心ダメだこりゃって思ってたんですかね……?
赤城だけ特別扱いか?全部擬人化を排除したか?どちらか気になったので、試しに歴代空母を Google 画像検索で探して、擬人化率を調べました。結構違っていて面白いです。
以下はカウントに使った検索結果のキャプチャ画像です。
赤城だけ特異的に擬人化の絵の結果が少ないです。何か限られた対象だけ特殊処理が働いているとかですかね?
検索結果を見たら一目瞭然なんですが、擬人化絵と空母というか艦船の画像のアスペクト比が明らかに違います。
このような傾向があって 1画面の表示結果が多い=艦これ優勢、とわかります。その他にも、
ヒストグラムも割と特徴的です。この件に限ってはあまり難しいことを考えなくても、アスペクト比 1:1 を境界にして、色の特徴を見たら擬人化画像を分離できるなあなんてことを思いました。手元で分類するくらいなら十分ではないでしょうか。
ハックしようと思えばいくらでもできちゃうんで、検索エンジンの結果としてはダメですけど。
最近 OpenCL のオープンソース実装 pocl について調べています。わかったことのメモです。
OpenCL は clGetDeviceIDs() を呼ぶときにデバイスの種類を指定します。Khronos の API ドキュメント(clGetDeviceIDs(3) Manual Page)を見ると、デバイスの種類は 4つ定義されています(DEFAULT と ALL はデバイスの種類ではないので除外)。
これらのうち pocl がサポートしているのは CPU と GPU です。GPU は NVIDIA の CUDA と AMD の HSA に対応しているようです。全部 LLVM がビルドしてくれるわけで、すごいよ LLVM さん。ACCELERATOR はテンプレート実装のみで、そのままでは動作しないので、注意が必要です。
CPU(pthread 版)、GPU(CUDA 版)、ACCELERATOR を有効にした pocl のビルド、インストール方法は下記のとおりです。実際に動かすときは ACCELERATOR を無効にしてください。でないと初期化時にエラーが発生して動かないです。
$ cmake -G Ninja \ -DCMAKE_INSTALL_PREFIX=`pwd`/_install \ -DENABLE_CUDA=ON \ -DENABLE_ACCEL_DEVICE=ON \ ../ $ ninja $ ninja install
基本的には必要なオプションがあれば ON にするだけですから、ビルドとインストールはそんなに難しくないはずです。
実行方法はややクセがあります。OpenCL は実装がたくさんあるので、直接 OpenCL ライブラリをリンクするのではなく、ICD Loader と呼ばれるライブラリ(2020年 7月 14日の日記参照)を間に噛ませることが多いです。
ソースコードは Oak Ridge 大学のサイトとほぼ同じです。行数を減らしたのと、OpenCL 2.2 に合わせて使う API を一部変えている程度です。
#define CL_TARGET_OPENCL_VERSION 220
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <CL/opencl.h>
// OpenCL kernel. Each work item takes care of one element of c
const char *kernelSource = "" \
"#pragma OPENCL EXTENSION cl_khr_fp64 : enable \n" \
"__kernel void vecAdd(__global double *a, \n" \
" __global double *b, \n" \
" __global double *c, \n" \
" const unsigned int n) \n" \
"{ \n" \
" // Get our global thread ID \n" \
" int id = get_global_id(0); \n" \
" \n" \
" // Make sure we do not go out of bounds \n" \
" if (id < n) \n" \
" c[id] = a[id] + b[id]; \n" \
"} \n" \
"\n" ;
int main(int argc, char *argv[])
{
// Length of vectors
int n = 100000;
size_t bytes = n * sizeof(double);
cl_platform_id cpPlatform;
cl_device_id device_id;
cl_context context;
cl_command_queue queue;
cl_program program;
cl_kernel kernel;
// Device input/output buffers
cl_mem d_a, d_b;
cl_mem d_c;
// Allocate memory for each vector on host
double *h_a = (double *)malloc(bytes);
double *h_b = (double *)malloc(bytes);
double *h_c = (double *)malloc(bytes);
// Initialize vectors on host
for (int i = 0; i < n; i++) {
h_a[i] = sinf(i) * sinf(i);
h_b[i] = cosf(i) * cosf(i);
}
// Number of work items in each local work group
size_t localSize = 64;
// Number of total work items - localSize must be devisor
size_t globalSize = ceil(n / (float)localSize) * localSize;
cl_int err;
// Bind to platform
err = clGetPlatformIDs(1, &cpPlatform, NULL);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
// Get ID for the device
err = clGetDeviceIDs(cpPlatform, CL_DEVICE_TYPE_CPU, 1, &device_id, NULL);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
// Create a context
context = clCreateContext(0, 1, &device_id, NULL, NULL, &err);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
queue = clCreateCommandQueueWithProperties(context, device_id, 0, &err);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
// Create the compute program from the source buffer
program = clCreateProgramWithSource(context, 1,
(const char **)&kernelSource, NULL, &err);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
// Build the program executable
err = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
// Create the compute kernel in the program we wish to run
kernel = clCreateKernel(program, "vecAdd", &err);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
// Create the input and output arrays in device memory for our calculation
d_a = clCreateBuffer(context, CL_MEM_READ_ONLY, bytes, NULL, NULL);
d_b = clCreateBuffer(context, CL_MEM_READ_ONLY, bytes, NULL, NULL);
d_c = clCreateBuffer(context, CL_MEM_WRITE_ONLY, bytes, NULL, NULL);
// Write our data set into the input array in device memory
err = clEnqueueWriteBuffer(queue, d_a, CL_TRUE, 0,
bytes, h_a, 0, NULL, NULL);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
err = clEnqueueWriteBuffer(queue, d_b, CL_TRUE, 0,
bytes, h_b, 0, NULL, NULL);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
// Set the arguments to our compute kernel
err = clSetKernelArg(kernel, 0, sizeof(cl_mem), &d_a);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
err = clSetKernelArg(kernel, 1, sizeof(cl_mem), &d_b);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
err = clSetKernelArg(kernel, 2, sizeof(cl_mem), &d_c);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
err = clSetKernelArg(kernel, 3, sizeof(unsigned int), &n);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
// Execute the kernel over the entire range of the data set
err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL,
&globalSize, &localSize, 0, NULL, NULL);
if (err != CL_SUCCESS) {
printf("%s:%d err:%d\n", __func__, __LINE__, err);
return 0;
}
// Wait for the command queue to get serviced before reading back results
clFinish(queue);
// Read the results from the device
clEnqueueReadBuffer(queue, d_c, CL_TRUE, 0,
bytes, h_c, 0, NULL, NULL);
//Sum up vector c and print result divided by n, this should equal 1 within error
double sum = 0;
for (int i = 0; i < n; i++) {
sum += h_c[i];
}
printf("final result: %f\n", sum / n);
clReleaseMemObject(d_a);
clReleaseMemObject(d_b);
clReleaseMemObject(d_c);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(queue);
clReleaseContext(context);
free(h_a);
free(h_b);
free(h_c);
return 0;
}
$ gcc a.c -g -O0 -Wall -lOpenCL -lm ★ ICD Loader に pocl のライブラリ名を教える必要がある $ cat /etc/OpenCL/vendors/pocl_test.icd libpocl.so.2.7.0 $ LD_LIBRARY_PATH=/path/to/pocl/build/_install/lib \ ./a.out final result: 1.000000
動作しました。良かった。
OpenCL の API は ICD Loader が提供し、OpenCL の実装は各 ICD が提供します。App → ICD Loader → ICD という呼び出し関係です(詳しくは 2020年 7月 14日の日記参照)。一見すると煩雑ですが、アプリケーションは ICD のことは知らなくても良いのが利点です。今回の例でいえばアプリケーションは libpocl.so をリンクせずとも、pocl の実装を使うことができます。
これだけだと面白くないし、何が動いているかすらわからないので、デバッグ出力を全開にして観察します。POCL_DEBUG という環境変数を使います。その他のデバッグ方法は PoCL のドキュメント(Debugging OpenCL applications with PoCL)が参考になります。
$ LD_LIBRARY_PATH=/path/to/pocl/build/_install/lib \ POCL_DEBUG=all \ ./a.out ** Final POCL_DEBUG flags: FFFFFFFFFFFFFFFF [2021-03-13 07:05:14.074169726]POCL: in fn pocl_init_devices at line 571: | GENERAL | Installing SIGFPE handler... [2021-03-13 07:05:14.128006157]POCL: in fn pocl_cuda_init at line 287: | GENERAL | [CUDA] GPU architecture = sm_61 [2021-03-13 07:05:14.128050269]POCL: in fn findLibDevice at line 560: | CUDA | looking for libdevice at '/usr/lib/nvvm/libdevice/libdevice.10.bc' ...
動作しました。よきかな。タイムスタンプの日付がかなりズレますね……?ま、実害はないし良いか。
一覧が欲しくなってきたので作りました。
Quora のとある項目なぜTRON OSが「非常に優れていたが外圧で潰された」とか「組み込みで世界標準OSだ」とかいう誇張された伝説をいまだに信じている人が大勢いるのですか? - Quora が話題になっていました。そんな話を信じている人が居るんですね。TRON が世界標準……私の知らない世界線で TRON が覇権を獲ったのでしょうか……。
松下電器(おそらく日本一の TRON 推しの会社でした)に居た自分すら、そんなこと思ったことありませんでした。
その松下電器でさえ BTRON はもちろん iTRON すらギブアップです。いまやレコーダーやテレビの OS は Linux/BSD カーネルを採用しています。iTRON アプリも残ってはいますが、過去資産の作り直しは面倒&旨味がないのが理由だったと思います。
メモ: 技術系の話は Facebook から転記しておくことにした。
目次: RISC-V - まとめリンク
SiFive の HiFive Unmatched を購入しました。現状、世界最速の Linux が動作する RISC-V 64bit SoC とのことです。
ボードには SD カードが付属しており Freedom USDK という独自の環境がインストールされています。ボード上には USB 接続のシリアル端子があり、電源を入れれば Linux が起動し、ユーザ root、パスワード sifive でログインできるようになっています。
ぱっと見は PC と同じ mini-ITX マザーボードですけど、バックパネルを見ると SD カードの差し込み口、USB シリアル用の microB 端子が出ていて、どちらかというと SBC(シングルボードコンピュータ)です。PC っぽさがありません。
本当はグラフィックカードを装着して GUI を使うべきですが、昨今のグラフィックカード品薄&異常な値上がりのおかげで全く買う気が起きないので、しばらくシリアルコンソールで使おうと思います。
インストールされているカーネルは、
Linux unmatched 5.11.10 #1 SMP Wed Apr 7 17:37:34 UTC 2021 riscv64 riscv64 riscv64 GNU/Linux
でした。5.11 は Stable カーネルではあるものの、既に EOL です。まあ、開発用ボードだしこんなもんか。
Crowd Supply から購入しました。本体 $679, 消費税が 7,100円、合計で 7万円くらいでした。HiFive Unleashed ほどではないにせよ、SBC にしては良いお値段です。
UPS が米国→日本まで持ってきて、国内はクロネコヤマトが運びます。受け取りの際に、消費税を着払いでクロネコに払う必要があります。私は消費税のことを忘れていて、何だこの金は??と混乱しました。Unleashed のときと全く同じでした。海外からものを買うことがほとんどなくて、消費税の存在をすぐ忘れちゃうんですよね……。
(参考)コード一式は GitHub に置きました(GitHub へのリンク)
Linux が動く RISC-V ボードを買ったので、RISC-V 64 でも memset をやってみました。環境はボードが SiFive HiFive Unmatched で、SoC が SiFive Freedom U740 で、コアが U74-MC という名前です。動作周波数は書いてないですね。OS は Freedom USDK という SiFive 独自?の環境です。メモリは DDR4-2400 のようです(Schematics hifive-unmatched-schematics-v3.pdf より)。
特徴的な点は、
あと個人的に残念だった点としては、U74 コアの速度です。前世代の HiFive Unleashed に搭載されていた U54 コアは Cortex-A53 の足下にも及びませんでした(2019年 5月 27日の日記参照)。
U74 は Cortex-A72 レベルとまでは言いませんが、Cortex-A53 は超えてくると期待していましたが、少なくとも memset に関しては負けています。半分くらいの速度しか出ていません……。
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2021.
Powered by PHP 5.2.17.
using GD bundled (2.0.34 compatible)(png support.)