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

細々と開発を続けているMC68000エミュレータ

そろそろちょっとしたバグでも大きな影響が出始めてる…。

中には「え?!なんでこれで今まで動いてたの??」と思うような実装抜けも見つかって驚愕する(^^;

 

特に驚いたのがmove命令でのフラグ操作。コメントには巧妙にも「あとで実装する」と書かれており、すべてのフラグ操作が実装されていなかった…汗

なぜこれで動いてたのか…(-_-;

f:id:PocketGriffon:20211223105435j:plain

フラグ変化を修正して、ようやく57万ステップまで実行が進んだ。

テレビコントロールは必要なかろう…と思い、IOCS自体をスキップさせてしまった(先頭でRTSしてる)。

エラーで止まってるのはBSET命令の一部アドレッシングがまだ未実装ですよという意味だ。起動してから0.589秒相当まで実行できた。まだまだ先は長い…(T-T)

はたしてまともに動くようになるまでモチベーションは保てるのだろうか…汗

 

命令を実行してみる…準備!

さあ!ブログの方でもついに命令をエミュレーションしてみる!(^-^)

気合入れていくぜー!←カラ元気

 

ブログを書いてみてよく分かったが、言葉の説明がしきれない。

例えばアセンブラを使ったことのある人であればお馴染みの言葉である「アドレッシング」や「イミディエイト」などの説明はこのブログでは出来ない。申し訳ないが、意味が通じない方はそれっぽく読む検索などをしてもらいたい(^^;;

 

…いざ命令を実行しようとすると、その前にたくさんの準備が必要な事に気がつく。

まず、エミュレータとは言え、何かしらの動くプログラムが欲しい。そうでないと闇雲に命令を実行する事になってしまい、最初っからモチベーションが保てない(^-^;

 

ここは最終目的でもある、X68000のプログラムを実行させる。

手元にCompact XVIがあるので、そこからROMを抜き出す事にした。

実を言えば、私自身がX68000のメモリマップをちゃんと把握していないw

いつのもマイナーマシンであれば、まずは全メモリをホストマシンへ送り込んで解析を…とするところだけど、X68000は人気マシンということもあるので豊富な書籍を参考にする(^^)

f:id:PocketGriffon:20211223125620j:plain

どうやら……いわゆるROMプログラムは$FE0000からの128KB、そしてフォントデータが768KBあるらしい。フォントでかっ!(@_@;

 

このデータをどうやってホストマシンへ送り込もうと思ったが…先日、手元のマシンにRaSCSを繋げた。X68000本体でファイルに変換する事が出来れば、あとはネットワーク経由でホストマシンへ送り込める。実に簡単だ(^-^)

 

f:id:PocketGriffon:20211223130019j:plain

ここも数あるツールの力を借りることにした。

SCD(デバッガ)でメモリを書き出すだけでファイルにすることが出来た!

この2つのファイルをエミュレータ用のプログラムで読み込むようにしよう。

 

次にエミュレータ用のメモリを用意する。

ここは作りを単純化するために、68000で使えるメモリの最大容量(16MB)を用意する。

今の時代だったら16MBを確保する事にそんな罪悪感は沸かない(^^)

1990年頃、初めてワークステーションを利用した際に128KBのメモリをallocするコードを書いた事があったけど、その時の申し訳なさ感ときたらスゴイものがあった(^^;

far付けなくても128KB確保出来るのにも驚いたけど…汗

 

確保したメモリの$F00000からフォントROM、$FE0000からプログラムROMを読み込んでおき、X68000実機と同じメモリマップにしておく。他にVRAMやI/Oの領域があるが、今のところは放置で構わない(メモリは存在している)。

 

ここではて?と思った。

MC68000は$000000に書かれている8バイトをSSPとPCにコピーをして起動する。でも…メモリマップ的に$000000には何もない…。あれ?あれれ??

X68000ってどういう起動プロセスになってるんだ???(@_@;;

 

ここから……だいぶいろんな本やら資料をひっくり返してみたが、どうにも詳細を書いてる情報がない!ぐぬぬ、これは困ったぞ…(T-T)

  :

  :

そして答えはひょんなところから見つかった。

しかもついさっきまで見てた本から(^^;;

f:id:PocketGriffon:20211223132406j:plain

リセット直後のみ$FF0000〜と同じ内容が読める

ぐはっ!なんという仕様!(^^;

しかし……「リセット直後」は良いけれども、リセット後いつまで見えてるんだろ??こういう事がエミュレータを作っていると正確に知りたくなる。うむむ…(-_-;;

 

ともあれ、MC68000の起動に必要な情報は分かった!

SSP=$2000、PC=$00FF0010にレジスタを初期化し、実行開始となる。

他のレジスタ(AnやDnなど)については未初期化…なんだと思われれる…。

 

メモリアクセス

エミュレータを作る際、メモリアクセス、I/Oアクセスは特別扱いしている。

例えば、Z80マシンのエミュレータ作る際、CPUとしてのZ80は同じモノなんだけど、メモリのアクセス(メモリマップ)はマシンごとに違っている。

この違いをメモリアクセス関数を変えることで対応している。

f:id:PocketGriffon:20211223140338j:plain

↑図がわかりにくくて申し訳ないが…(^^;;

 

例えばPC-8801の場合、Z80コアとPC-8801ハードウェアは切り分けて開発をしている。

Z80モジュールの初期化をする際、メモリアクセス関数、I/Oアクセス関数も(関数へのポインタとして)一緒に渡す。Z80側はメモリやI/Oアクセスは必ずその関数を通して行うため、実装自体はPC-8801の都合で作る…というワケだ。

 

PC-8801の場合、バンク切り替えなど(例えばSR以降に備わっているALUなども)のハードウェアに密接に絡んだ部分は、この関数がすべてを受け持つ。

 

MC68000の場合、メモリアクセスが3種類ある。1バイト、2バイト、4バイトのそれぞれアクセスだ。実際の関数は1バイトと2バイトアクセスを用意し、4バイトアクセスは2バイト関数を2度アクセスするようにしている。

 

命令実行サイクル

命令を実行した時、実行に何サイクル掛かったのかを記録していく必要がある。

別に実行速度を気にしているわけではなく、どのタイミングで割り込み処理を挟み込んで良いのかを知るためだ。例えばタイマー割り込み、垂直同期、水平同期などはこのサイクルカウントから生成する。

 

68000の命令は、基本となるクロック数に加えてアドレッシングによるクロックの増加がある。この辺りを正確にカウントするのは骨が折れる(T-T)

 

今の時点ではそこまで正確なクロック数は必要ないが、ちゃんとカウントするようにしないと水平同期によるラスタースクロールなどの実現が難しくなる。そういう機能をきちんと実装している世の中のエミュレータは本当にすごい(T-T)

 

1命令を実行する

メモリ16MBの確保をした!

MC68000エミュレータの初期化もした!

SSPとPCも初期化した!

ここまできたらあとはもう命令実行を開始するだけだ!(^-^)

#特定のマシンを実行する場合は、ハードウェアの初期化も必要になる

 

f:id:PocketGriffon:20211223145812j:plain

PCの位置から1命令(2バイト)の情報を読み出す

その命令が「何を意味しているのか」を解析する

実行する

その結果、メモリやフラグに反映させる

文章で書くととても単純だ(^-^)

 

00FF0010: 46FC 2700 4FF9 0000 2000 4E70 91C8 203C

 

まず命令コード(ここではメモリの$00FF0010の位置(PC)にある$46FC)に注目をする。このコードが何なのかを調べる事となるが、これの役に立つのがシリーズ「その2」に出てきた以下の図だ。

f:id:PocketGriffon:20211212125043j:plain

これらを聞いてピンと来ない方は「2進数16進数」という言葉を元にググって欲しい(^^)

これで見ると$46FCというコードは「MOVE to SR」という命令だと分かる。

この命令は、SR(ステータスレジスタ)へ値を代入する命令だ。

そしてこの命令の意味はこんな感じだ。

f:id:PocketGriffon:20211223151045j:plain

慣れていかないと、この図を読み解くのは少しむずかしい。

MOVE.{W} <ea>, SR

この<ea>というのが写真下の方にある「実効アドレス(データ・モード)」に置き換わる。

$46FCは2進数で「0100 0110 1111 1100」となり、下位6ビットに注目すると「111 100」、つまり実効アドレスの表に置き換えると「#Imm」となる。

68000CPUでのプログラミング経験がないと、おそらくここの文章の意味は通じない(^^;;

 

MC68000エミュレータで面倒な事のひとつは、この実効アドレスを解決するところかもしれない。ここで指定されたのがDnの場合はレジスタの中身を指すが、(An)などはレジスタの指すメモリの中身を指す。アドレッシングによる解釈がかなり面倒だ(T-T)

 

実を言えば、この実効アドレスを場合分けするのがエレガントには実装出来て無く、ものすごくベタにプログラムを書いている。もう少し書き進めてみて、抜けなくキレイに書ける方法が見いだせたら書き直してみたい(実行速度も気になるし)。

 

SRが16bit長であることから、イミディエイトの値は自動的にWORDとなる。

この命令(2バイト)の後ろにある2バイトがイミディエイト値となり、それは$2700だ。

アセンブラ的には「move #$2700,SR」となる。

 

f:id:PocketGriffon:20211223160526j:plain

ステータスレジスタは↑こんな感じの意味となっている。

$2700は2進数で「0010 0111 0000 0000」となるので、MC68000CPUの状態はスーパーバイザーモード、割り込み禁止という意味となる(^^)

 

今回はここまで

なーんとなく命令を実行するプロセスが伝わるだろうか(^^)

この1命令実行のプロセスを、すべての命令で実装していくのだ。おそらく読んだ方も「えーそれは面倒!」となるだろうが、実際に作ってみてもそう思う(^^;

エミュレータの作成はとても地味な作業が続き、かつ根気が必要だと思う。

 

こんな感じで全命令の説明をしていくわけにはいかないので、ブログをどうやって収めていくのか課題ですな…(-_-;

まだもう少し書かねばならない事もあるので、あと何回かは続くよ(^^)

 

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