M5Stack Core2で動かすエミュレータ第3弾!
みんな大好きPC-8001エミュレータを動かしてみたよ(^-^)
前半はガチガチの技術ブログになってしまった…汗
なんとなく集大成
これまでPC-8801(アルフォス専用)、FM-7とエミュレータを移植し続けてきたけれども、今回のPC-8001はM5Stackでのプログラム集大成なイメージで作り上げた。
M5Stackでどのようにプログラムをしたら効率の良いものが作れるのかを模索した前の2機種だったけれど、そこでまとまったアイディアがPC-8001で全部入った感じ(^^)
使ったことがないCPU、何種類もあるメモリ、何をすると遅くなるのかがさっぱり分からないハードでプログラムをする場合、気にしないといけないポイントを見極めるのが難しい。
以下、自分なりのM5Stackでプログラムを作る上で気にしたいポイント!
・速度を要する処理のメモリをどこに割り当てるか
M5Stack(私はCore2ユーザー)はメモリがたくさん載っている。容量が大きいのはとても助かるのだが、何種類ものタイプが違うメモリが存在するのだ。
プログラムの処理負荷と、必要とするメモリサイズを意識して、どこの領域からメモリを確保するのかを決めていきたい。効果もはっきり出るので、ここはじっくりと見極めが必要。
以下、独自に調べた結果だけど、結果が信じがたい数字になってしまっているため、半信半疑、目安程度に見ておいて欲しい。
32KBのメモリを2つ確保し、32bitアクセスで「メモリクリア」「値を加工しつつメモリコピー」「単純にメモリコピー」の処理を1000回繰り返した時の速度比。
時間 | 速度比 | |
グローバル変数 | 36,133 | 1.000 |
MALLOC_CAP_EXEC | 306,030 | 8.470 |
MALLOC_CAP_32BIT | 195,998 | 5.424 |
MALLOC_CAP_8BIT | 110,036 | 3.045 |
MALLOC_CAP_DMA | 110,036 | 3.045 |
MALLOC_CAP_SPIRAM | 2,476,450 | 68.537 |
MALLOC_CAP_INTERNAL | 195,998 | 5.424 |
MALLOC_CAP_DEFAULT | 2,471,492 | 68.400 |
この結果を見る限り、グローバル領域のアクセス速度が一番速い。
なかなかC++を利用していてグローバル領域を使う…という事がピンと来ないかも知れないが、ここは単純に性能と比較したら良いと思う(^-^)
グローバル変数のアクセス速度を1とした場合、SPIRAMは68倍以上も遅いという結果が出てしまった…。これホントかな…(-_-;; ちょっと信じがたい。これを信じる限り、SPIRAMは速度を要さないけれども大きさが欲しい部分でのみ利用するのが良さそうだ。
あと32BITと8BITで、32BITの方が遅いという結果も「?」だ。32BITの方はおそらくだけど、32bitアライメントが保証されたメモリで、32bit単位にアクセスしてもエラーが出ませんよって意味だと思う。単純にその違いなだけだと思うのだが…汗
このテストはESP32(M5StackのCPU)にとって最悪な環境だ。キャッシュメモリが32KBのESP32に対してバッファサイズを32KBx2にしているので、キャッシュミスを頻繁に起こすための悪意がある。実際のプログラムではキャッシュが有効に働くであろうから、こんな極端な例は滅多に起こらない。あくまでも最悪のケースとして見てもらうのが良い。
あと、グローバル変数はあまり大きく取れないので注意。
空のスケッチ状態であれば約103KBほど確保出来るようだ。
PC-8001のプロジェクトではメインメモリ(64KB)をグローバル変数にしちゃえ!と思っていたが、後半足りなくなって仕方なくINTERNALから確保した。
SDカードからの一時読み込みバッファなどはDEFAULTを使っている。
・画面表示はLovyanGFXにおまかせ
もう私の中ではLovyanGFX一択!!まじめに速いです!(^-^)
最初、自分で似たようなライブラリを作ろうと考えていた事があったけれど、LovyanGFXを知ってからはそんな気持ちが吹き飛んでしまった(^^;
sprite.getBuffer()でバッファのアドレスを得て、その中身をプログラムで書き換える。
その後、sprite.pushSprite(...)で一気に書き込むのがお気に入り。
おそらく内部でDMA転送していると思われるので、複数のspriteを用意して、転送中のバッファを壊さないようにしている。
FM-7では320x1のスプライトを4つ、PC-8001では320x8 or 10のスプライトを3つ用意して使い回している。DMA転送よりも画像生成のプログラムが遅いのでこの数で十分だ(^^)
・その他もろもろ
他にも細かい事を書いていたが、さすがに細かすぎるか…と思い、全部消してしまった(^^;
書いていたのは
・キャッシュメモリの有効活用
・Write-back-cacheはあるんだろうか?
・パイプラインを乱さないために
・乗算、除算、SHIFTしてOR
……などなど、プログラム大好きな人が見たら楽しそうな事柄だったけど、さすがにマニアック過ぎると思った。
gccで使える__builtin_prefetchなども使ってみたが、出力されたアセンブラファイルを見る限り、それっぽいコードは出力されていないようだった。
likely / unlikelyはもう身体の一部になってるので普通に指定してた(^^)
M5Stackが楽しくて難しいハードなので、私自身もプログラマとしての経験を最大限に生かすような感じでプログラムに取り組めている。良いマシンだよ、M5Stack!(^-^)
PC-8001移植の話
PC-8001は、何度となくエミュレータを作ってるマシンだ。
私はいろんな事情によりソースコードを公開しない(出来ない)人なので、自身で楽しむためだけに作っている。他の方が開発されたエミュレータを使う事も無い。
今回利用したのはLinux用に開発していたPC-8001のエミュレータ。Z80部分は自作のCP/M80エミュレータで利用していたものと同じで、PC-8801アルフォスでも使われている。
M5Stackでアーカイブを利用する方法が分からなかったため、そのままソースコードをスケッチに追加してしまった(^^;; 他のみなさんはどうしてるの???
メモリ確保、フレームバッファの生成部分、キーボード入力部分のみを変更しただけで動いてしまった。もうM5Stackで何度と無くプログラムを書いているので、悩むところが少ない。
デバッグ中に知りたい情報はLcdに表示するか、またはSerialに出力すれば簡単に見られる。かつキーボードが繋がった事で、好きなタイミングで出力する事が出来るようになった。
プログラムを作る環境としては最強に近いんじゃないだろーか??
他のエミュレータとの比較
PC-8801ではメモリ容量が大きい(&プログラムの工夫が足りない)事で、DEFAULT(つまり68倍遅いメモリ!)に主要なデータを入れてあった。これによりCPUコアの実行速度が出なかったんだと思う。
FM-7では2つあるCPUのメモリ空間はINTERNALから確保出来たが、そもそもCPUを2つ駆動しなくてはならないという現実がつきまとう。せっかくの高速メモリが使えても帳消しになってしまった格好だ。逆にINTERNALが使えなかったら、FM-7 on M5Stackは実現しなかった可能性もある。
ちなみにFM-7では2つのCPUの駆動タイミングによって、FM-7全体の速度が大きく変わってくる。これはお互いのCPUが「互いに止めあって」協調動作するからだ。この辺りは実装ノウハウありそう(^^;
そんな実験を経て、PC-8001ではメインメモリをINTERNALから確保、必要なバッファはグローバル領域に置き、描画にはLovyanGFXで高速化!おかげで初めて処理時間が余った!
このエミュレータは1/20秒を基準として動作している。1回の処理50msに対して、平均して28〜30msは遊んでいる(^^;; すごいぞ、M5Stack!!
画面に関しても書き換わった部分のみを再描画しているPC-8801とFM-7に比べ、PC-8001では何も考えずに全面書き換えが出来てしまった。作業に必要なワークをグローバル領域に置き、面倒なパレット変換などの処理が入らないPC-8001ならではかも知れない。
エミュレータシリーズ完結!
ここまで3つのエミュレータを、実に1日単位で作るという荒業をご披露したw
こんなの、元のエミュレータがあったからこそ出来たワザだ(^^;
手元にはまだMZ-80、MZ-80B、PC-1350などのエミュレータが残ってるが、カラーも出ないのでもういいかなって思ってる。
もう少しM5Stackは使っていこうと思うけれど、こんなハイペースでは作らないよ!
おじさんの寿命が縮まっちゃうw
これからはじっくりと構えてプログラミングしていきます。
さて、次は何をやろうかね(^^)
ではまた次回!(^-^)ノ