NES(ファミコン)のCPUはRicoh 2A03というチップ。これはMOS 6502という、Apple II にも使われていたチップの互換品。互換品といっても、機能が少しだけ違っていて、6502に比べて①サウンド機能の追加 ②10進数の演算命令の削除 という変更がされていることが特徴。余談だけどアメリカで大ブレイクしたコモドール64っていうコンピューターも6502の互換品である6510を使っていたみたい。コモドール64とかの時代のコンピューターメーカーの熾烈な競争はwikipediaとかで調べるとめっちゃ面白くて時が経つのを忘れる(笑)。
さて、 前回 と 前々回 の記事を書くことでだいぶファミコンのCPUの扱い方が分かってきた。そろそろこのへんでCPU関連についてまとめておきたいと思う。
とは言ったものの、 壁は通り抜けられませんよ 様のサイトにだいたい書いてある。 また、ほかのサイトとして6502の研究部屋 様のサイトが素人でも理解しやすいように大変かみ砕いて解説なさっている。素人の私がこの方々以上に詳しく書くことはできないので、網羅的に書くことはやめて、CPUのポイントや躓いたところ、参考にしたサイトなどを書き残そうと思う。
まず、私みたいに素人で新しく学ぶ方はNES(ファミコン)のCPU 2A03の元となる6502 について調べると良いだろう。
というのも、2A03は6502から変更されている機能(上述)をのぞいて6502と動作は全く一緒であり、どちらを学んでも大差はないからだ。さらに6502の方が参考資料が圧倒的に多い。
さて、
CPUの動きをを超ざっくりと簡単に書いてみる。CPUはレジスタとアドレス空間というものを持っている。レジスタもアドレス空間もまあやってることはそんなに変わらない。結局、CPUの動作っていうのは1クロックあたり1Byteのデータを取り出したり入れたりする命令(転送する命令)がメインで、その取り出したり入れたりするデータの保存元や保存先がレジスタとかアドレス空間。
(この辺は「CPUの創り方」などの本を読む方が良い。私はこの本を何回でも紹介し布教するつもりです笑)
レジスタとアドレス空間にある1byteのデータの保存先(よく住所に例えられる)には名前がある。レジスタは、Aレジスタ、Xレジスタ、Sレジスタ、Pレジスタ、PCレジスタ、のように「(アルファベット1~2文字)+レジスタ」という命名。アドレス空間の名前は、$0000~$FFFFというように、16進数の番号で、これを「アドレス」という。私たちはこの名前を使ってCPUに命令を下す。例えば、「$0300というアドレス に入っている1ByteのデータをAレジスタに転送してくれ」とか。
さて、CPUはレジスタやアドレス空間のデータを転送するが、どこから何をどこに転送するか、これは私たち人間側が命令してやらなければならない。 しかし、もちろん命令を日本語で下したとしてもCPU君がそれを理解できはしない。
ではどのように命令するか、実はこれは命令を1Byteの数字のデータとしてアドレス空間の中に入れるだけでいい。このような1byteのような長さの数字で表された命令(オペコード)や、その命令の引数となる数値(オペランド)を、CPUという機械が読み取れるという意味で機械語と呼ばれる。例えばファミコンだったら$8000~$FFFFの中に人間からCPUに向けた命令が保存されている。これは前回 の記事でも確認したのでよかったら覗いてみてほしい。この機械語の命令をCPU君はクロックが刻まれるたびに読んでいくわけだ。
ちょっとぼかして言っていたけど、「転送命令がメイン」ってことにも触れておく。 とはいってもこれは「CPUの創り方」の受け売りなんだけど。
例えば転送命令以外にどんな命令があるか。NES研究室 様のサイトや、lsluk様 作の命令の一覧表 など、6502の資料はネットに沢山落ちているので調べてみるといい。算術命令、論理演算命令、ジャンプやコールやリターンの命令、比較命令など様々な種類があるようだ。でも、例えば算術命令だったらレジスタの値をALUという電子回路に転送し、その結果をALUからレジスタに転送しなおす、という命令と考えられる。ほかのものも同じようなもので、私たちが命令することは”どこからどこに転送するか”ということであり、その転送先では算術演算や論理演算のような処理が電子回路によって自動的に行われる。(そもそも機械語の”命令”自体もデータであり、その命令が入っているアドレスから命令翻訳機(デコーダ)に転送されている)。このように考えると私たちが下す”命令”は転送命令がメインであると言える。私たちの作るプログラム、つまり”命令”の役割について、このようなざっくりとした理解をしておくとCPUの動きをとらえやすくなる。
…つまり何が言いたいのかっていうと、CPUの内部回路について細かいことは知らなくてもCPUの動きを学ぶ上では問題ないということ。そういう詳しいことは専門の人か暇な人に任せようね。
以上のように考えると、CPUのざっくりとした動きを知るために学ぶことは絞られる。①レジスタとメモリのアドレス空間について ②命令について
この2つだけでよい。
①レジスタとメモリのアドレス空間について
なぜレジスタとメモリのアドレス空間を同時に語ろうとしているかというと、どちらも1byteのデータを保存するメモリであり、機能がほとんど同じだからである。というかどちらも原理を言うとフリップフロップという同じ回路だし(あくまで原始的な、原理のことを言っているので6502のメモリに使われているのがフリップフロップ回路だけだとは言ってないよ)。実際、6502にはゼロページといって、メモリの$00~$FF という2桁(1byteであらわされる)のアドレスをレジスタと同じように使える機能がある。
レジスタとメモリの役割の違いは、それぞれがつながっている回路が違うってイメージ。例えばメモリにおいてゼロページ以外のアドレスのデータは直接数値の足し算をしたり引き算をしたりすることはできない。これは、メモリのゼロページ以外のアドレスからALUのような演算装置に回路が直接つながっていないからであると考えられる。でも、レジスタには繋がっているから、
メモリのゼロページ以外のアドレス → Aレジスタ → 演算回路 → Aレジスタ
というようにレジスタを仲介して計算を行うことができる。 ほかにも、メモリの異なるアドレス同士でデータの受け渡しができないのも、この異なるアドレス同士がデータを受け渡せるように回路がつながっていないからである(もしつながっていたら天文学的な数の配線が必要。インターネットで個人の持つすべてのパソコンが互いに1対1でつながっていると考えるとわかりやすいと思う。恐ろしい)。 前々回 でいちいちAレジスタを経由して計算したり、データを様々なところに転送したのは、これが理由である。
ということで、レジスタとメモリについて考える。同じような機能であっても、つながっている回路は違うのでやっぱり役割は微妙に違う。その役割を抑える。
レジスタにはA、X、Y、S、P、PC という6種のレジスタがある。それぞれの役割は壁は通り抜けられませんよ 様のサイトや、6502の研究部屋 様のサイトなどを参考にするとよい。繰り返して言うが、インターネットには6502の資料は大量に転がっている。
メモリはアドレスの名前の付け方が$0000 ~ $FFFF という番号であるというせいで、どのアドレスも同じ役割を持てそうに思えるかもしれない。でも、じつはアドレスによってその役割は違う。先程の説明に沿う言い方をするならば、異なる番号のアドレスはつながっている回路が違うと考えるのが分かりやすい(あくまでイメージ)。例えば$0000~$00FFという1byteで表せるアドレスはゼロページといって演算回路と直接つながっているので、レジスタを仲介しなくても直接演算ができる。例えば$2000~$2007はグラフィックの司令塔であるPPUという回路とつながっているのでPPUとデータのやり取りをするときに使う。というようなイメージ。
では、それぞれのアドレスがどんな役割を持つのかということは、メモリマップを見れば分かる。例えば、 nesdevの資料 や、 I/Oレジスタマップ といったサイトなどだ。繰り返して何度でも言うが6502の資料はインターネットのそこら中から拾ってくることができる。ブックマークしたサイトが多すぎてほしい情報をブックマークバーからは逆に探し辛くなったりする(笑)。
①のまとめ: 6種のレジスタと、メモリのそれぞれのアドレスについて、与えられている役割を知るとCPUに何ができるのかが分かる。
②命令について
命令は、上述のとおりレジスタやメモリに保存されているデータを「どこからどこに何を」転送するのかを説明するものだと考えると分かりやすい。
例えば 「lda $2002」という命令は、機械語に直すと「AD 02 20」である( 前回 の記事参照)。これは、「$2002 から Aレジスタ に $2002に格納されていた8bitのデータ を転送する」という命令。この命令では「どこに何を」の部分が「AD」で分かり、「どこから」の部分が「02 20」で分かるようになっている(このような命令の解読はCPUが命令デコーダで行う)。
さて、命令の書き方はこんな感じであるため、あとは 命令の一覧表 でも見ながらプログラムを作ればよい。
注意することは、オペコードに対してオペランドが正しいかということくらい。
例えば「lda $FF」を機械語で書くとどうなるかを考える。ldaという命令だからさっきと同じように「AD 20」 で書くのは正しだろうか。次のプログラムを機械語に直して考えてみるとわかる。
lda $FF
lda $2002
このプログラムを今の考えで機械語に翻訳すると、「AD FF AD 02 20 」になる。・・・よく見るとこれは実は、①$ADFFのアドレスに入っている1byteデータをAレジスタに転送 ②オペコード「02」を実行
というプログラムである。つまり意図した動きをしない。
正しい動きをするためには、実はこの「lda $FF」を「A5 FF」と翻訳しなくてはならない。
つまり、オペランドがどのような長さであるかとか、どのような種類のものがあるかとかで、アセンブリ言語では同じ「lda」であっても機械語のオペコードは異なる。このような、オペランドの種類によって変化するオペコードをアドレッシングモードという。6502はアドレッシングモードが多く最初は「命令が多すぎる!」と混乱するかもしれないが、オペランドの種類(バイト長、数、ゼロページか否かなど)で分けられているから多いだけであって、実際に下せる命令はそんなに多くない。NES研究室 様のサイトをじっくり読んでどんな命令やアドレッシングモードがあるのかを確認してみてほしい。
②のまとめ:オペコード+オペランドの組み合わせは合理的に決まっており、このような命令のしくみを理解すればCPUに対して適切に命令を下せるようになる。
ということで、6502を学ぶうえでポイントとなることを書いたつもり。
次は躓いたところ。
1.ゼロページとは?
レジスタを仲介せずに直接計算できるため、あたかもレジスタのようにつかえる。ゼロページ以外のアドレスよりも必要なサイクル数(何クロック必要か)が少ないため高速に処理することができる。
2.比較命令とかステータスレジスタとかその辺
壁は通り抜けられませんよ 様のサイトを見れ。
1) まず比較演算は引き算。結果をどこにも転送しない引き算。比較じゃなくて引き算。これを頭に叩き込む。
2) そしてステータスレジスタ(Sレジスタ)はなにか計算したときなどの結果によって自動的に値が変わる。自動的に。この自動的にってことを頭に練りこむ。
3) 叩き込んで練りこんだら、”比較演算をすると自動的にステータスレジスタの値が変わること”を閃くことができる。
4) 命令一覧表を調べてみるとステータスレジスタの値によってCPUの動作を分岐できる命令があることを知る。beq ( branch if equal ) とか bne ( branch if not equal ) とか。
5) あとは比較演算してからステータスレジスタの値を使ってジャンプ命令とかの好きな命令を行う。
おわり。
コメント