MC68000エミュレータの開発 その4

f:id:PocketGriffon:20211217112618j:plain

今回は68000固有というよりは、私が行っているエミュレータ開発全般的な話を書こうと思う。相変わらず概念的な話にはなってもうしわけがない…。

 

あくまでも私の作り方であり、他のアプローチを否定するものではない事を強くお伝えしたい。参考程度で読んでいただけると助かります!(^^;;

出先でテキストを更新しているので写真少なめです。

あとでちょこちょこ追加するかも(^^;

 

現状のMC68000エミュレータ開発進捗

まずは先に開発中の68000エミュレータの進捗報告だ。

現在はリセットから398,348ステップ、命令グループは31まで実装した。

前回から出てたその数字の意味はなんだ…と言われそう(^^;

 

ステップというのは「リセットしてから何回命令を実行したか」という数字。

命令グループというのがとても説明しにくい…。例えばmove命令を1つのグループとして見立てている。move命令には数多くのアドレッシングが存在するが、そのうちの一部(の必要なアドレッシング)しか実装していない。命令としては未完なのだが登録はしてある…という感じで表現してる(分かりづらい…)。

 

実装している命令グループの割にステップ数が進んでいるのは、メモリクリアなどのループがたくさんあったからだ。テキストVRAMをクリアするだけで大量のループがある(^^;

 

私はエミュレータ開発中は、このステップ数を見ながらモチベーションを保ったりしてる。命令を追加するとどーんと進み、バグを修正するとどーんと縮んだりする(^^;

 

今くらいの開発状況だとバグを見つけるためには「プログラムの文脈を感じる」のがとても大事。なーんとなくプログラムの流れが変だと思ったら、そのまま進めるのではなく、プログラムを見直すのが大事な時期だ。納得出来ない事を放置してはいけない(^^)

 

バグを修正すると、それまで進んでいたステップ数がズドムと減る事があり、モチベーションも同時にドガンと減る(^^;; そんな事を繰り返しながら地道に根気良く作り込んでいくのがエミュレータ開発だ。

 

この数日間もエミュレータのバグのみならず逆アセンブラのバグなども見つかり、一進一退を繰り返している(^-^;

 

エミュレータの構造

エミュレータは大きく分けて2つの要素で成り立っていて、CPUそれ以外のハードウェアに分別している。おそらくほとんどの場合はCPUを先に開発していき、途中からハードウェアも同時に開発対応が進む。

マシンによっては「それ以外のハードウェア」がてんこ盛りの場合があり、X68000はまさにソレだ(^^;; 

 

今の私は68000CPUのエミュレーションを開発しているだけで、ハードウェア周りは完全な未実装状態。ハードウェアの大半はCPUと密接に繋がっているので、先にCPUエミュレーションを実装ししまわないてと話が始まらない(^^)

 

何度も書くが、今回のMC68000エミュレータCPUに限ったお話(^-^)

 

ちなみに……X68000の画像関係は実装がかなり大変だと思う。多彩なグラフィックモードも面倒だけど、グラフィックモードによるメモリアクセスの違いやBGやスプライトなどの対応、さらにラスタースクロールなどのタイミングがシビアなモノを作るのは想像しただけで厄介だ(T-T)

 

しかしX68000ではないが過去に似たようなものを作ったことがあり、ソコに対する技術的な興味がすでにない。画像関係とか作らないよーって言ってるのはいくつか理由があるが、コレも大きな理由のひとつだ(^^)

そもそも私自身がゲームで遊ぶ人ではないってのもあるんだけど(-_-;

 

CPU構造体

CPUの構成を超大雑把に言えば、レジスタ、フラグ、メモリを扱いながら与えられた命令を実行していく機械だ。これらの要素を言語的に定義をし、真似するプログラムを書いていく。

私はCPUをエミュレーションするための構造体を用意している。

C++ならばclassにしちゃうのが良いと思う。

その構造体の中に「扱うメモリ」「CPUのレジスタ」「メモリ読み込み関数」「メモリ書き込み関数」「I/O読み込み」「I/O書き込み」「その他」を定義している。

 

CPU構成を構造体にしている理由は、マシンによっては複数のCPUを持つ場合があるからだ。FM-7などはこの構造体が2つあり、メインCPU/サブCPUとして使っている。

PC-8801もメインCPUと、ディスク側にもうひとつZ80CPUが載っている。私が良く作っているアルフォスエミュレータではディスクをサポートしていないので、CPUは1つだ。

 

CPUレジスタ

CPUのエミュレーションをするためには、そのCPUについて詳しく調べておかなくてならない。可能ならば、そのCPUでのプログラミング経験もオススメしたい。

過去に、そのCPUでのプログラミング経験が無いままエミュレータを書いたことが(何度か)あったけど、思い違いなどがたくさん含まれてしまい、結構難儀した(T-T)

このブログでもPC-1350エミュレータは経験無しで開発している。

pocketgriffon.hatenablog.com

 

これが↓レジスタの定義。

f:id:PocketGriffon:20211217084540j:plain

MC68000ではDレジスタが8つ、Aレジスタが8つあり、どちらも32ビット長だ。型定義はそれっぽく読んでもらえるとありがたい。

 

CPU命令を実装していく流れで、これらのメンバを読んだり書き換えたりしながらCPUのマネをさせていく。エミュレータとは直訳すれば……真似?模倣?

 

実はユーザースタックポインタとシステムスタックポインタ、それとA7レジスタの関係性が分かっていない。A7レジスタがアクセスされた時は、ステータスレジスタのSビットを見て、どっちのスタックをアクセスしたのかを判断している。これであってるのかな…汗

 

メモリ読み込み、メモリ書き込み

メモリに対する操作をする関数を定義する。

f:id:PocketGriffon:20211217090554j:plain

いつもだと1バイト読み込み/書き込みの2つの関数を定義しているが、68000の場合は3つ(1バイト、2バイト、4バイト)のアクセスパターンがあったので、3つずつ定義した。

 

エミュレータのメモリアクセスは、この関数以外では絶対にしない。アクセスを関数に集中させる事で、バンク切り替えなどの構造定義を簡単にしている(^^) その分、速度的な犠牲はあると思うが、それはもう必要悪だ(悪?)。

 

現状のエミュレータではバスエラーを実装していない。

68000はメモリがない空間をアクセスしたり、奇数アドレスからのワードアクセスしたり不正にスーパーバイザーエリアをアクセスしたりするとバスエラーが出る。X68000のROMやHuman68kはバスエラーを起こさず実行ができてるのでであろう…という前提の元、チェックを省いてしまっている(^^;

最終的には実装しておきたい。

 

I/O読み込み、I/O書き込み

いわゆるハードウェアとのやりとりをするポートのアクセス関数だ。Z80などの場合はOUT/INなどのCPU命令でアクセスされるが、68系ではメモリマップドI/Oが多い。メモリにアクセスをするだけでハードウェアとのやりとりが出来るので便利だ!

 

今回の68000ではメモリに配置されたI/Oが並んでいるため、I/Oの読み書きは上記の「メモリ読み書き」から呼び出される構造になるだろう(まだ実装していない)。

 

この辺りを拡張していくと豪華なグラフィックやサウンド、キーボード周りが実装できるようになるはずだ。

 

CPUの初期化

一番最初にCPUの初期化から始めるんだけど、ここでネタを1つ。

 

CPUがメモリに置かれた命令を1つずつ解釈して実行しているのはご存知だと思う。そのための「プログラムの位置」をプログラムカウンタ(PCだったりIPだったり)が保持しているのもご存知と思う。

そのPC、リセット時にはどこのアドレスを指すのかまで知ってる人は多くない(^^)

エミュレータを作るためには、まずはそれが重要だ。

 

マシン語入門と書かれた本には、その手の情報はほぼ書かれていない!

多くはCPUのデータシート的な役割をしている資料に書かれている。

 

ちなみに

Z80:0000h

8086:FFFF:0000h(CS=FFFFh、IP=0000h)

6809:$FFFEに書かれた2バイトをスタートアドレス

というようになっている。

 

MC68000は$00000000に書かれている8バイトをSSPとPCにコピーをして起動する。

CPUそれぞれの起動方法があって実に面白い(^-^)

 

なんかだらだらと書いてしまった…汗

出先では調べられる事も限りがあて大した事が書けないな…(^^;

次回はもう少し具体的な実装方法をご紹介出来たらな…と思う。

 

ではまた次回!(^-^)ノ