見出し画像

RaspberryPi Zero 2W の限界を探る

現在、RaspberryPi Zeroのベアメタル環境で動作する自作ゲーム機VGS-Zeroを開発中です。

VGS-Zeroは、メインコアにZ80を搭載しています。

Z80のクロックレートとしては3,579,545Hz(Z80A)が有名ですが、VGS-Zeroは通常のエミュレータ開発とは異なり過去のソフトウェアとの互換性を気にする必要が無いので、敢えてそのクロックレートに拘る必要がありません。

そこで、VGS-ZeroのZ80はRaspberryPi Zeroのベアメタル環境で動作できる限界性能までクロックアップすることで、メインコアで動作するゲーム本編の処理性能を限界まで上げることにしました。

それが今回の記事のお題です。


検証プログラムの準備

まずは、VGS-Zeroのエミュレータ側の負荷を最大化するヒートランプログラム(Z80)を作成しました。

https://github.com/suzukiplan/vgszero/tree/master/example/04_heavy

このプログラムは、

  • 背景(BG)と前景(FG)の全体にグラフィックスを表示&スクロール

  • 256個のスプライトを描画して動かす

  • 256個の効果音を鳴らす

  • BGMをループ再生

を実行する形のものです。

このプログラムをRaspberryPi Zero(無印)で動かしてみたところ、残念ながら処理落ちが発生して再生中のBGMが音飛びしてしまいました。つまり、RaspberryPi Zero(無印)では既に限界なので、Z80のクロックアップの余地がありません・・・

RaspberryPi Zero(無印)の切り捨て

RaspberryPi Zero(無印)は、シングルコアのARMv6 1GHzなので、大幅な性能改善は難しいです。

しかし、RaspberryPi Zero 2Wであれば、クアッドコア(4コア)のARMv8 1GHzなので、複数コアを活用した形で作れば大幅な性能改善をすることが可能です。

という訳で、若干迷いましたが無印のRaspberryPi Zero(W、WHを含む)への対応はdeprecatedとして、RaspberryPi Zero 2W専用にしてマルチコアを活用して性能確保をすることにしました。

Circleでのマルチコア対応

RaspberryPi Zeroベアメタル版VGS-Zeroのカーネルコア実装には Circle という OSS を使っていますが、Circle を使えばマルチコア対応も簡単にできました。

まず、Circle でマルチコア対応をするには、Config.mkに次のような定義を追加してCircleをリビルドする必要があります。

DEFINE += -DARM_ALLOW_MULTI_CORE

そして、CMultiCoreSupportを継承したサブクラスを作成し、そのサブクラスをCKernelのサブクラスのメンバ変数に追加して初期化処理のキックを行い、CMultiCoreSupportでコア毎の処理実装を行います。

詳しくは以下のドキュメントに書かれています。

処理を並列動作したいタイミングでスレッドを作成するマルチスレッド・プログラミングとは若干雰囲気が異なりますね。

VGS-Zeroのマルチコア対応

VGS-Zeroの初期化後のメイン処理(ループ処理)の処理内容を簡潔に纏めると、次のようなシーケンスで処理を実行しています。

  1. ペリフェラル通信(USBゲームパッド入力)

  2. VGS-Zeroエミュレーション処理(映像と音声がバッファリングされる)

  3. HDMIへの映像出力(バックバッファ)

  4. バッファフリップと垂直同期信号(60Hz)の待機

  5. HDMIへの音声出力

上記の内、1と4と5の処理(外部デバイス通信処理)は必ずプライマリCPU(CPU0)で実行しなければなりませんが、2と3の処理であればサブCPU(CPU1〜3)へ分散させることが可能です。

そこで、2と3の処理を次のように分割して、

  • 処理2-1+3: CPUエミュレーション + HDMIへの映像出力

  • 処理2-2: VGSエミュレーション(BGMと効果音のバッファリング)

  • 処理2-3: VDPエミュレーション(映像のバッファリング)

処理2-1+3処理2-2は、CPU0からの割り込みを契機にフレーム単位で実行、処理2-31スキャンライン描画すべきタイミングで処理2-1+3からの割り込みにより実行という形で処理することでマルチコアに対応しました。

単位時間あたりの割り込み実行回数は、処理2-3がやや多めなので少し心配でしたが、特に問題なく実行できていました。

RaspberryPi Zero 2Wの限界を探る

マルチコア対応により先述のヒートラン・プログラムはRaspberryPi Zero 2Wで処理落ちすることなく動作できるようになったので、Z80のクロックアップの限界値を探りました。

単純に「処理2-1+3」の処理時間を15ms以下に収まるCPUクロックレートを探る形で調査したところ、16MHzの時の処理時間が約14.9ms程度に収束したので約3.4MHz→16MHz(約4.7倍)にクロックアップすることにしました。

Z80が16MHzの時の「処理2-1+3」の処理時間
(※単位: マイクロ秒)

1〜2ms程度の余力を残していますが、これはDMA転送機能用のバッファです。(DMAはネイティブで実行するのでZ80とは別にCPUリソースを喰います)

16MHzのZ80

8ビット16MHzのZ80なんて実在するのかな?と思って一応調べてみたところありましたw(Z84C1516FSG)

https://www.digikey.jp/ja/products/detail/zilog/Z84C1516FSG/929037

追加命令の有無と下位互換性チェックはしていないので、実際にZ84C1516FSGでVGS-Zeroが動作できるかは未確認ですが、一応CPUの脱エミュレーションという方向性も残すことができたかもしれません。

ちなみにメガドライブのMC68000は7.67MHz(※NTSCの場合)なので、クロックレートだけならメガドライブの倍ですね。(8ビットCPUですがw)

なかなか面白くなってきました。

この記事が気に入ったらサポートをしてみませんか?