PlayStationエミュレータ作りに取り組んだ
最近暇だったのでPlayStationのエミュレータ作りに取り組みました。そのまとめをしたいと思います。
PlayStationエミュレータ作りと聞くと難しそうに聞こえますが、実はかなり分かりやすいガイドブックが存在し、これに従うことであまり詰まることなく実装できました。
結果として5日ほどで、懐かしいオレンジのロゴが見れる程度の必要最低限の実装が行えたので、紹介したいと思います。
※テクスチャは未実装なのでロゴが赤い四角になってる
The ガイドブック
以下のPDFは、CPUの仕組みの簡単な説明から入り、0からBIOSのオレンジのロゴが表示できることろまで網羅した神ガイドブックです。言語は英語とRustです。
https://svkt.org/~simias/guide.pdf
普段のエミュレータ作りで時間のかかる作業は:
- 地獄のデバッグ
- PCのタイミング調整(パイプラインがある場合)
- 各種命令の仕様把握と実装
あたりがパッと浮かぶのですが、こういった躓きがちな罠をすべてガイドブックではちゃんと解説してくれてるので1/100ぐらいの時間で進められます。神。
PlayStationのハードウェア構成
PlayStationは以下のようなハードウェアで構成されています:
- CPU: Sony CXD8530AQ 33.87 MHz (R3000A互換のCW33000をベースにしているらしい)
- ISA: MIPS I
- データバス: 32bit
- アドレスバス: 32bit
- パイプライン: 5段
- コプロセッサ: CP0(割り込みとかキャッシュフラグの管理), CP2(GTE, Sonyの3Dグラフィックスエンジン)
- GPU: Sony CXD8514Q (東芝の設計らしい)
- 2Dの描画のみ(3DはGTEで2Dに投影されてからGPUに送られる)
- SPU: Sony CXD2922Q
- その他、メモリ、CD-ROMドライブ、動画デコーダー、タイマ、DMAなどなど…
プレイステーション歴代モデルの基板から見えてくるPS進化ヒストリー - mitok(ミトク)
PlayStation Architecture | A Practical Analysis
ガイドブックで実装するのは、CPU、GPUの二つがメインで、そのうちでもBIOSのオレンジロゴを表示できる必要最低限の部分のみです。(なので割り込みも実装しませんし、GTEも作りません。GPUも三角形を描画するコマンドぐらいです)
BIOSの用意
ここから暫くBIOSを用意するための手順を書きます。既にお持ちの方はシュッと飛ばして頂いてOKです。
どんなエミュレータを作るにもBIOSの用意は初めの壁として立ちはだかってきます。
非合法な入手の仕方はごにょごにょごにょ…ですが、合法的にBIOSを用意するには、実機を用意して吸い出す必要があります。
私の調べた限り実機吸出しで今ある手順は:
- ディスクスワップやMODチップなどを使って、homebrew(非公式ROM)を動かす
- メモリーカード経由、またはシリアルポート経由でPCに転送する
でした。
2.の転送手段ですが、メモリーカード経由だとプレミアの付いたメモリーカードリーダー(8000円ぐらい)を購入し何往復もしてデータを転送しなくてはならないため、非効率的でやめました。
(一応、メモリーカードリーダー自作も試したのですが、うまく動かなかった…SPIを使えば読めるらしいです。力尽きたやつ:
https://github.com/mj-hd/ps-memory-reader)
代わりに、Raspberry Pi Zero W (2000円ぐらい)と自作シリアルケーブルを使って転送を行いました。
シリアルケーブルの自作
まずは、PSとRaspberry Piを接続するシリアルケーブルを自作しましょう。
流石に端子は自作できませんので、メルカリなどで「SCPH-1040」というPSの対戦ケーブルをポチります。
このケーブルは、PSのシリアルポートに接続することができるため、半分に切断して線を剥いてラズパイとつなぎます。
USB to Serial PSX cable - Unirom
Pinoutをよく確認して、テスタで結線を確認しながら、GND/RXD/TXDを適切につなぎます。
Pinouts - PlayStation Specifications - psx-spx
非公式ROMを動かす
BIOSをダンプするための非公式ROMを、実機で実行する必要があります。
PSで非公式ROMを動かすには、いくつかの方法がありますが、その中でも「ディスクスワップ」と呼ばれる方法が一番手軽です。
公式ROM(適当なPSゲームソフト)を起動し、検証が終わった直後に非公式ROMと入れ替えることでブートさせる荒業です。
まずは、UniROMという多機能ROMを以下からダウンロードし、その辺のCD-Rに焼きます。
そして、以下の手順で頑張ってブートしましょう。(匠の技です、うまく動かすためには練習あるのみ)
(私の手持ちPSはSCPH-5500というディスクスワップの難易度が上がったバージョンでした。もっと古いバージョンであればサクッと起動できるかもしれません)
無事ブートしたら、手持ちのメモリーカードなどに起動用のイメージを書き込んでおくととても便利です。PSの脆弱性を使って、次回以降ディスクスワップをせずに直接UniROMを起動できるようになります。(INSTALLというメニューから選べます)
シリアル接続する
ここまで出来たら、あとはシリアル接続してBIOSをダンプします。
Raspberry PiにSSH経由で接続し、以下のプログラムを実行します:
GitHub - JonathanDotCel/NOTPSXSerial
そして、以下のようにBIOSのダンプコマンドを実行することで、無事Raspberry Pi上にBIOSが転送されます。(/slowでないと、うまく転送できませんでした。注意です)
sudo mono nops.exe /dump 0xBFC00000 0x80000 BIOS.ROM /slow
お疲れさまでした…。
ここまでできれば、シリアル接続で任意のプログラムをブートできるので、自作ゲームを作るのも面白いかもしれません。
エミュレータを作る
エミュレータ作りは基本ガイドブックに従っていきます。
ガイドブックは初代海外版のSCPH-1001をベースにしてるため、持っているBIOSによって多少ステップは変わってしまうかもしれませんが、一通り実装を終えれば動くはずと思います。
数か所、罠にハマったのでメモしておきます:
- ガイドブックはSDL2を使っているが、手元の環境だとどうしても画面が描画されなかった…wgpuを使って書き直して一旦解決(根本原因は不明…)
- あともう一か所ぐらい、最後の方にそのままでは動かないコードがあった気がした…ごめんなさい記憶が…
動かしてみる
という訳で、ここまで実装ができればオレンジのロゴがニュウン…と描画されます。
ログを見る限り、SPUへたくさんの書き込みをしており、「ブォオオーン…キランキラン…」という音がなっているのではないかなと思います。SPU実装してないので無音ですが。
デバッガをつける
ガイドブックでは、デバッガの実装がきちんと解説されていませんでした。
今後エミュレータを育てていくためにも、デバッガは必須になってきますので、実装が必要です。
Rustにはgdbstubという、traitを実装すればgdbと繋がるよ、という便利なクレートがあります。
これをつなぎこむことで、gdb (gdb-multiarchというMIPSに対応した版)を使ってデバッグや、逆アセンブルができるようになりました。
今後の作業
ガイドブックを最後まで実行し終えると、BIOSを抜けてShell(ROMのチェックやお馴染みのメモリーカード管理などができる画面)に入ります。
ROMチェックを通らないと画面が切り替わらないので、前述のデバッガを駆使して現在はCD-ROMドライブを実装中です。
何か簡単なROMが動かせるところまで持っていけたら良いですね。
実装の所感
今まで、低クオリティではありますがGB, NES, GBAのエミュレータを作っているので比較をしてみると、MIPS Iはシンプルなアーキテクチャだなと思いました。
デコードはかなりキレイに書けますし、命令数もそんなに多くない印象です。命令サイクルも全命令1サイクルで、遅延テーブルなど用意しなくて良さそうです。
パイプラインがあるので、GBAのようにPCの調整業はどうしても必要になってきますが、それでも分岐が起こってもパイプラインを破棄せず分岐の次の命令を実行する(!)という仕様のため、多少楽だったように思えます。(遅延分岐、遅延スロットと呼ぶらしいです)
その他、参考にした資料
- PSX-SPX (no$psxを見やすくした版): https://psx-spx.consoledev.net/
- 神PDFの作者が作ったPSエミュ実装(Rust): https://github.com/simias/rustation
- BIOSの逆コンパイル: https://github.com/pcsxr/PCSX-Reloaded/blob/master/pcsxr/libpcsxcore/psxbios.c
Discussion