ファミコンエミュレータを写経してみるお話1【CPU】
はじめに
タイトル通りです。不定期に進めるつもりですが、最後まで進むかはまだ分からないです。
モチベーション
なぜ突然始めようと思ったか。 - 自分で調べてみたいゲームがある(秘密) - 低レイヤーに強くなりたい - Rustに興味がある
巷では自作OSが流行っていますが、みんなと同じだと芸がないので若干遠いものを選んでみました。
参考にするもの
- コード周りはこちらのコードを観させてもらおうと思います。また
Rust
の書き方の勉強としても頼りにさせてもらいます。
ファミコンエミュレータの創り方 - Speaker Deck
- こちらに有志の皆さんが
NES
の情報をまとめて下さっているので、利用させてもらいます。特に、コードを写経しているだけでは意味がないので、まずこちらを見て自分で実装を考えます。
- こちらの本も参考にさせてもらいます。1つ目のリンクの方の実装と見比べる事で、実装にマストな部分を見出すために使いました。また、こちらの方の実装順番も参考にしてます。
読み始める前に
- ファミコンの仕様についてはネット上にたくさん情報が上がっているので、ここでは詳しい事は書かないです。(自分も分からない) しかし、実装手順の再現性があるサイトは個人的に少ないなと感じたので、自分の試行錯誤をここにまとめて、うまく踏み台にしてもらえたらなと考えています。言語は
Rust
を使っており参考サイトもRust
が中心のものが多いです。 - また、解説力も乏しいので、適宜実装が完了したと思われる
commit
にジャンプできるGithub
のリンクをそれぞれに置いておくので、そこからコミット履歴などを参照していただいてもらう形にします。申し訳ないです。
始めにやること
まず、ファミコンを構成する全体像を把握していくと良いと思います。CPU
やPPU
がどうつながっているのかなどなど。
ファミコンエミュレータの創り方 - Speaker Deck
色んなサイトがそれぞれの図で説明しているので、目を通した方が掴みやすいかもしれません。
CPUから作り始めてみる
僕はCPU
から作り始めてみることにしました。理由は以下です。
- ここの命令を駆使してコンピュータが動いている
- 割と他の機器に依存しておらず、コードが読み始めやすい
- 同じ理由で、とりあえず命令が1つずつ動くように作れば大丈夫そう
命令を1つずつ実装する
実装が必要なCPU
の命令は参考サイトに一覧で載っているので、簡単に言えばこれらを呼び出して動くようにできればokなはず。まず、大まかな流れを作りながら、LDA imm
関数(中身未実装)が呼べるまでを作ってみました。(リンク先はその時のコミットに飛びます)
ここでcargo run
を実行すれば以下のような出力が得られ、とりあえず呼び出せた事がわかりました。
足りないもの
命令だけ実装していくつもりでしたが、CPU レジスタ
とBus
まわりの実装も必要そうだと感じてきました。命令による状態の変化であったり、アドレスの指す他の場所のデータを読んでくるといった処理を命令に実装しなければならないためです。特にレジスタ周りはプログラムカウンタ
など特に不可欠な要素が多いので、命令系よりも先に実装しておく必要がありそうです。
レジスタの実装
簡易的に必要となりうる機能を参考サイト様から引っ張って実装してみました。実際に操作はしていないものの、操作を呼び出せるようにCPU
に接続してみたつもりです。
github.com
テストを走らせるために、データを他とやり取りするbus
も必要そうです。テストだけであれば細かな実装はいらなそうなので、うまいことモックテストができるように実装してみます。
busの仮実装と初めての命令の実装
はじめに仮実装としてBus
と思われるものを実装しました。機能は適当ですが、読み書きができるメソッドが呼び出し可能なようにしておき、周辺が充実してきたら機能を実装してく形にします。
実際にBusをCPU
のなかに組み込み、LDA imm
命令を実装、テストを追加してみたのもがこちらです。これだとまだモックは必要ありませんでした。
LDA系の命令を一通り実装する
だんだん説明が少なってますが、こちらがLDA系を網羅した際のコミットです。それぞれある程度のテストも実装しています。mod
でのテストはBus
により実際に命令を読んできて、その命令を実行させるという、大分本番に近い感じで動いていると思います。楽しいです。
はじめはLDA
の命令を網羅的に実装していたのですが、「読んだ値をそのままAへロード」か「読んだアドレスの指す場所の値をAへロード」の2パターンしかないので、結果的にInstruction
には2つしかメソッドが実装されていません。
全ての命令を網羅する
あとは全ての実装をしていくだけです。基本的に下の2つのサイトを確認しながら、命令の挙動を把握して行きます。
pgate1.at-ninja.jp
一通り網羅しました。これにより基本的なCPU周りの実装はひとまず完了したと思われるので、プルリクにしてあります。
割り込み系の追加実装
上ではソフトウェアからのBRK
の割り込みは実装したはずですが、ハードウェアからなどの割り込みは未実装でしたので追加します。下のリンク先では、追加実装時のプルリクを紹介しています。
ここまで
一通りテストも書いたはずなので、ポカはないと思いますが、間違っているとしたら自分の解釈部分で違っていた故のミスかと思います。ひとまずCPU
の実装(写経)はこれで一段落つけます。もし追加で必要なものや修正があれば、後の実装で適宜直していこうと思います。このエントリがいつまで続くか分からないですが、引き続き頑張ります。
つづき↓
追記
後日rom
を起動させながらデバッグを行い修正が加わったので、CPU
の該当コードあたりは直しておいてください。修正する記事で後々出てくるコードなので今じゃなくても大丈夫です。