PythonによるNESエミュレータ開発5
もうすぐ引越しです。頑張って部屋を片付けないと・・・
しばらくドタバタすると思し、これ以上作りこむモチベーションもないので、ここまで作ったものをあげておこうと思いました。
ダウンロード
pynes-0-0-1.zip
試し方
インストールはダウンロードしたzipファイルを展開するだけです。
必要なライブラリは
です。
両方ともeasy_install psyco、easy_install pygameでインストールできたはずです。
roms/以下に最低1つ以上ロムファイルを置いてください。現状、マッパーに対応してませんので、マッパー0のしか動く可能性はありません。現在動作を確認してるのは、前回あげさせていただいたTkShootくらいです。市販のはほとんど動かないんじゃないでしょうか。
一応参考までにあげておくと、動く可能性があるのはGolf,DonkeyKongなどです。
bin/pynesi.pyが起動用スクリプトです。コマンドラインから起動してください。起動したら、romファイルを番号で選択してください。
キーバーインドは
- 十字キー : カーソル
- スタート : テンキーの0
- セレクト : テンキーのEnter
- A : テンキーの3
- B : テンキーの2
になってます。キーバーインドを代えたい方はsrc/pynes/pad.pyを適当に書き換えてください。
- self.keymap1 = {
- K_UP : NES_PAD_UP,
- K_DOWN : NES_PAD_DOWN,
- K_LEFT : NES_PAD_LEFT,
- K_RIGHT : NES_PAD_RIGHT,
- K_KP0 : NES_PAD_START,
- K_KP_ENTER : NES_PAD_SELECT,
- K_KP2 : NES_PAD_B,
- K_KP3 : NES_PAD_A
- }
ここです。
とにかく、めちゃくちゃ遅いので、固まったと思ってもしばらくすると画面がちゃんと切り替わったりします。
よもや話
かなり適当です。前回(PythonによるNESエミュレータ開発4)から変わってません。マッパーっぽいのが用意してありますが、これはダミーです。他のエミュのソースを参考に必要そうな部分に適当にいれただけです。
一応、速度を重視しているものの、わかりやすく書いてるつもりなんで、Pythonが分かっていてかつ、エミュレータの基本的な構造が知りたい人には参考になるかもしれません。
PythonによるNESエミュレータ開発4

パッド入力部分を書いたので、動くゲームも出てきました。といってもまだほとんどのゲームが動かないんですけど。画面はブルジョアソフトウェア研究所さんのTkShoot 1.00が動作している様子です。
さて、ここまできたので基本的にはこの企画も終了かなー、という感じがします。目的はPythonのパフォーマンスについて知ることだったので。
作成の過程でかなりPythonのパフォーマンス関連について勉強ができてよかったと思います。
速度
サウンドは作っていないので、それを除くと1frameだいたい0.4秒弱くらいで動きます(もちろんpsycoを導入して)。変にベタ書きしたりはしていません。わりとメソッドはちゃんと分割しています。ただ、LDA $ssss(absolute addressingのLDA)だけ、実行回数が多いので完全にベタ書きしました。
んで感じたのは
- プリプロセッサでマクロを使えばそこそこ実用的な速度になりえるのではないか
- bytecodehacksでインライン化すれば結構いけるんじゃないか。ただし、bytecodehacksがオブジェクトのメソッドには対応していないので、classを使わずに書くことになる。
- 部分的にCで拡張モジュールを書けば、わりといけそう
ってことでしょうか。
まぁただ、JAVAとかC系全く分かりません><っていう人以外は、素直にC系かJAVAで書いたほうがいいと思います。
高速化のためにオブジェクトのプロパティをローカルにだしたり
- read = self.memory.read
- write = self.memory.write
- # .
- # .
- for i in xrange(foo):
- code = read(addr)
- # .
- # .
- # .
するので、無駄に行数も増えますし、ぶっちゃけ読みにくいです。それでも普段使い慣れてるLLで書けるってのは大きな利点だとは思います。
10年くらいして、マシンがもっと速くなることに期待しましょう、ということで(笑
その他雑感
現時点でも単純なゲームくらいなら、そこそこ動くのでコンピューターの仕組みの基礎を学ぶには、もしかしたらLLでエミュレータってのはいいかも。当然ですけど、デバッグの段階ではアセンブリ言語を書くことになりますし、そのアセンブリ言語の内容も完全に自分で処理するわけですから、単に本で読むよりは格段CPUやメモリについて詳しくなれると思います。ただ、CPUの仕組みを勉強しながら書くのはきついものがあるかもしれませんが・・・
他の利点としては「俺エミュレータ書いたんだぜ」と自慢できる(笑)、自分の書いたエミュレータでゲームが動くと結構感動できる、というくらいでしょうか。
というわけで、とりあえず動くようになりました。今のところソースをアップする気はないです。(してもほとんどのゲームは動かないし意味無い)
確実にいないと思いますが、もし、「俺もLLでエミュレータ書いてみるんだぜ!だからお前のしょーもないソースも参考にしてやるから見せるんだぜ!ついでにエミュってどうやって作るのか教えるんだぜ!」というような方や「おめーソースがないのに信用できるか!」という方がおられましたら、この記事のコメント欄やはてブなんかのコメントに、「うp」とか書いてください。適当にソースまとめてうpして、それをネタに簡単なエミュの書き方でも記事にします(笑
まぁとにかく書いてて楽しかったです。チャレンジ精神旺盛な方、そして時間があまっている方は是非LLでエミュにチャレンジしてみてください。
#追記
アップしました。コチラの記事へドウゾ。
PythonによるNESエミュレータ開発3
時間があればちょっとずつ続けてます。
とりあえず画面がでるようにはなりました。速度は全然追いつかないですけど。

CPU
基本的には変化なし。細かいバグが多くて大変・・・・
わずかでも速くしたいところなので、あまり構造を壊さず速度を上げられないかな、と思ってPrefetch cueを実装してみました。Prefetch cueはCPUの非常に基本的な最適化で、基本的故に単純、実装しても負荷になることはないだろう、ってことですな。
エミュレーターでは(とくにPythonでは)ハードウェアでいうメモリが遅いうんぬんとは別の理由で、メモリアクセスはかなり重い処理になります。
- def memory(self, addr):
- if addr <"RAMの範囲":
- self.ram[addr]
- elif addr <"メモリマップドIOの範囲":
- self.io.read(addr)
- #else:
- # .
- # .
- # .
さらにページングされている場合、C言語ならポインタでいけるんですけど、Pythonではそうはいかないので、いちいち長ったらしく書くか、ページング用にメソッドを書いてそれをはさむことになります。
そう、関数の呼び出しが非常に多い部分なのです。実際、profileモジュールでデータを取ってみてもメモリ読み込み書き込みが結構なウェイトをしめてました。
そこでPrefetch用のクラスを作って、別スレッドで現在のPC+いくらかをとるようにしてみました。これは現在のオペコードを実行している間に実行されます。これで次のオペコードを実行する前にオペコードとオペランドを取得でき、メモリ読み込み関数を呼ぶ回数が減ります。ただし、本物のPrefetch cueと同じでジャンプ命令が多いとあんまり効果がありません。
Prefetch-cueを入れてみると、まぁコードの内容によりますが、psycoを入れて1frame 0.075くらいまではいきました。PPUをいれると全然なんですけど(笑
PPU
なんとか表示できるまでにきました。正直つらいです(笑 特にスクロールに関してはloopyの文書の文書が重要、ということをしらなかったのではじめはサッパリでした。
描画部分はpygameです。pygameで描画する場合、更新したRectだけupdateするってのが常套手段なわけですが、エミュの場合はどうも・・・。というわけで今は毎回全体を描画しています。これも結構重い処理になるなあ。
ほとんどスクロールしないゲームの場合、もしかしたら32x32くらいに区切ってスプライトが動いたところだけ更新するようにしたら、結構軽いのかもなあ。
というわけで、PPUをつめてパッド入力あたりを書けばマッパー0のゲームなら動き出しそうな感じがします。ちなみにマッパーは全然です。とりあえずPythonで書いてみることが目的なので実際に使うことは想定して無いですし。
卒論の試問会も終わり、家探しの旅も終わり、つかの間の落ち着きが戻ってきました。なんか資格とかをとらないといけないらしいので、それをちょっとずつ勉強しつつ、こっちもちょっとずつ進めていきたいなーと思ってます。