MC68000エミュレータを作りながら昔のエミュレータ開発を思い出した。
今回はちょっと脱線して、過去に動かしたエミュレータを紹介してみたい(^^)
何年か前の年末年始お楽しみ作業で、PC-9801CV21(V30 10MHzマシン)でPC-8801エミュレータを動かした事がある。
そもそものはじまりは…
そもそも…なんでPC-9801でPC-8801を動かしてみようと思ったのか。
それは友人からの「PC-9801CV21の小さなブラウン管で、アルフォスが動いてたら感動するね!」という一言から始まった!
PC-9801CV21のCPUは、V30の10MHz。
単純構造のエミュレータを作るのならば、エミュレーションする元マシンのCPUよりはせめて2〜30倍以上は速くないと厳しい。
経験則から考えても、V30でZ80を(現実的な速度で)エミュレーション出来る気がしない。
時間がたっぷりあるのならばJITコンパイラとか試してみたいけど、今回はそういう感じでもなく、とりあえず動かしてみたらどーなるのか…という遊び心から来てる(^^)
「無理」と断るのは簡単だけど、ちゃんと検討した上で「無理」を突きつけたい!
だけど…ちょっとしたアイディアが浮かんでしまい、それを元に「もしかしたら作れるのでは?」という目線を元に、両機種を比較してみた。
都合の良い材料
グラフィックフォーマットが同じ
PC-8801とPC-9801では、絵を表示するためのデータフォーマットが同じだ。VRAMが3プレーンあるのも同じ(PC-9801は機種によって4プレーン)。これはとても有利に働く。
VRAMのメモリアクセスさえ解決してやれば、書き込みの速度は高速で行えそうだ!
もひとつ、カラーパレットの設定データも両者は同じだ!(^^)
バイトオーダー(エンディアン)が一緒
これも両者ともリトルエンディアンなので2バイトアクセスなどで有利に働く。
メモリがたくさん(640KB)ある
現実的な速度でエミュレーションを動かすために、贅沢なメモリの使い方をするケースもあるだろう。余裕があればあるほどアイディアは実現しやすい(^^)
フラグの生成が簡単
8086のフラグ生成と、Z80のフラグ生成はタイミングも結果もほとんど同じだ!
フラグの保存が簡単
8086には「SAHF、LAHF」という命令があり、フラグの状態をAHレジスタに退避、復帰する事が出来る。これをエミュレーションの実装で上手に活用が出来ればとても便利だ!
都合の悪い材料
CPUが違う
これはいわずもがなの事実!
ただ8086は、Z80(というよりは8080)の後継アーキテクチャという事もあり、命令自体が良く似ている。勝機があるとすればここかも!
マシンアーキテクチャが違う
これももう仕方がない!
仕方ないけど、幸運な事に「PC-8801を進化させたらPC-9801になる」と思えるほど似ている部分もある。しかも今回はアルフォスが動けば良いと割り切れば…!
速度が足りない
初代PC-8801の速度はZ80の2.3MHzくらいと言われている。これはDMAが使われる関係でCPUが停止するタイミングがあるから…らしい。命令コードは最低で4クロック。
片やV30は10MHzノーウェイトの動作。命令コードは最低2クロックで実行できる。
概算で7.5倍くらいの速度差があるようだ。
しかしソフトウェアでCPUをエミュレーションするためには、まだ足りない。この速度差はどうしようもないので覚悟の上でチャレンジするしか無い!(T-T)
バンク切り替えをどう実現するか
PC-8801にはバンク切り替えという概念がある。VRAM、ROM、メインメモリ、それぞれをバンク切り替えしながらメモリアクセスを行う。これをPC-9801で実現するためにどうしたら良いのか…
実はこれが最初に思いついた「こうすれば…」というアイディアだった(^^)
8086にはセグメントという概念がある。これを上手に利用すればバンク切り替えっぽい事が出来るのでは…という考えが頭に浮かんだ。つまりこういう事だ。
メインメモリをアクセスした際のDS(データセグメント)
VRAMをアクセスした時のDS
ROMをアクセスした時のDS
それぞれのメモリをアクセスした時、DSの値を8086側の都合良い値にセットすれば、複雑なアドレス変換などを行わずにメモリアクセスが出来るはずだ!
説明が簡略的で申し訳ないが…
↑PC-8801のメモリマップを、
8086側↓ではこんな感じにした。
フラグ生成が微妙に違う
エミュレータの開発では、計算結果のフラグを生成するのが意外にも厄介だ。いつもだったら計算結果を元にソフトウェアでフラグ情報を作り出す。
これが…8086とZ80の関係性だとちょっとラクする事が出来る。
SAHF ← フラグ情報のロード
ADD AL,CH ← 通常の計算
LAHF ← フラグ情報のセーブ
こうしておく事で、素の演算結果のフラグ状態をAHレジスタに退避する事が出来る。この仕組みを上手に活用する事で、エミュレータ上でフラグを作り出す作業をパスする事が出来る!(^-^)
だけど……各命令でフラグの変化を詳しく調べてみたところ、一部の計算でZ80と8086の互換性がない事が分かった。もはやどのコードがどうだったか…はソースを見ないと思い出せない…。
フラグの違いをちゃんと把握した上で実装していかないといけないので、かなーり気の抜けない作業となった。
もう少し検討
そもそもV30にはハードウェアで8080エミュレーション機能が存在する。これを上手に使うことで高速にZ80エミュレーションが作れないのか??
未定義命令を実行した時に、何らかの方法で検知する事ができれば目的は果たせそうだ。
…で、調べてみたんだけど、どうやらそういう機能はないみたいだった(T-T)
この方法はダメそうだ!
8086は典型的なレジスタマシンであり、メモリをアクセスすると途端に遅くなる。そのため、Z80エミュレーションを動かす時に、可能な限り実メモリをアクセスしない方法で実装しなければならない。
レジスタ構成を考えてみて、以下のようにしてみた。
表の見方は、Z80のAFレジスタはV30側でCXレジスタに割り当てた…という感じだ。
Z80のIX/IYなどはメモリに置かれる。プログラム的にIX/IYは出現が多くないので優先度を下げた。R/Iレジスタも同じ。SPだけはレジスタに置けないのが気になるが、レジスタ数の関係でどうにもならなかった。
表が間違っていたので訂正をば。PCがBPに割り当たっているが、SIの間違いだ(T-T)
そしてレジスタ割り当てはイッパツで決まらなかった。
一度PC-8801が起動するまで作り込んだが、どうにもしっくり来なくて作り直した!
その結果、上記の割り当てとなった(^^;
開発作業
これまで検討した事を考慮しつつ、エミュレータを開発していった。
実行速度を重視するため、大半のコードをアセンブラで書く事となった。ファイルの読み込みなどのアセンブラで書くと面倒な部分はC言語で記述したが、それ以外は初期化も含めてアセンブラとなった。
最終的にはZ80+PC-8801ハードの実現で、約5700行のアセンブラコードとなった。
開発はMacで編集、Mac上のDOSBoxでビルド、出来上がったファイルをフロッピーディスク経由でPC-9801NX/Cへ送り込んで動作確認を行った。
PC-9801NX/Cはアクセラレータを積んでいて486SX 40MHzくらいの速度があったので、動作確認もだいぶ楽に出来た。
最終的にはPC-9801CV21で動かしたが、実際に動かした回数は数えるほどで済んだ(^^)
まずはPC-9801上で、Z80で動くN88-BASICが動くようになった。
言葉が似てて説明がややこしいけど頑張ってついてきてw
BASICのプログラムがちゃんと動いた!
この段階ではPC-9801のハードウェアには依存していなかったので、試しにHP200LXでも動かしてみた。
これは……多分、絵面的にとても面白い!(^-^;;
ちなみに実行速度はびっくりするくらい遅かった。
速度の問題は置いておくとしても、まずは動かす事が大事だ!
どんどん作業を進めていくと、ようやくPC-9801NX/C上でアルフォスが動き始めた!
最初の画面がこちら!
…なにこれ?とか思わないで!(^^;;
テキスト画面を非表示にしていないのと、カラーパレットを合わせていないだけだ。
画面の右下辺りをじっくり見てみると「Morita」「NAMCO」という文字列が見える!
テキスト画面がなければある程度見える。
この当時は写真の撮り方も雑で、手と携帯が写っちゃってる(^^;;
カラーパレットを合わせてやればPC-8801と同じ画面となる!
PC-9801では標準で640x400ドットだが、アルフォス起動時に640x200ドットモードに切り替えている。ソフトウェア的に頑張らなくてもPC-8801と同じ画面が出た。
この状態でPC-9801CV21で動かしてみたのだが……
めっちゃくちゃ遅い!!!!
データのロードに2分、タイトル表示→ゲームまでも2分。
ゲーム画面は秒間2コマくらいしか出ない状態だった!!
動いたよ…動いたけど、これは動いたと言って良いのだろうか…(-_-;;
鬼の高速化!!
ここからお楽しみの高速化を始める!
なんとなく8086マシンでも動かす事を考えていたので、V30特有の命令を使わないでいた。しかしそうも言ってられなくなったので、この先は積極的に使う事にした!
垂直同期割り込みの要因を作るために各命令を実行した時に掛かったサイクル数をカウントしていたが、これを全廃止。
そして割り込みタイミングを固定化してしまった!
さらにゲーム開始時にテキストVRAMを非更新、非表示とした!
これでもまだ速度が足りない!!!
LDI、PUSH命令が連続する場合には特別処理をするようにした!
スタックポインタが更新されるまでスタックセグメントは変更しない、同じく長距離ジャンプがない限りはコードセグメントを更新しない、などなど細かい対応もしてみた!
最後はZ80の命令を拡張する暴挙に出た!(^^;
LD A,(HL) : INC HLなどの典型的に出てくる命令を新設1命令に置き換え!
もっと行くぜと32x16ドットのクリア、32x16ドットの描画を行う専用命令を新設!(^^;
よーし、これでだいぶPC-9801CV21でも動くようになった!
なったけど…命令を新設する時点でもうエミュレータじゃない(^^;;
この辺りが潮時…とばかり、作業終了とした。
ここまでの開発期間は、途中の作り直しも含めて20日間でした(^^)
先日ツイートした、PC-9801CV21で動く、PC-8801アルフォスの動画が残ってた!
— PocketGriffon (@GriffonPocket) 2021年12月15日
ブラウン管を撮影するとチラついちゃうけど、実機ではチラチラしないよ!
今見ると実機よりもかなり遅いね…(・・;) pic.twitter.com/MVAMcKBtmH
番外編
せっかくV30で動くZ80コアが出来上がったので、他にも何か動かしたいな…と。
なんとなく資料を見ていて気がついたPC-9801のテキスト画面のセミグラフィック。
これってPC-8001のグラフィックと同じフォーマットじゃん…と!
そんな事情で作ってみたPC-9801で動くPC-8001エミュレータ。
一見してグラフィックを表示しているように見えるが、これは全部PC-9801のテキスト画面で表示している。PC-9801とPC-8001ではアトリビュート情報の互換性がないため、その部分のみPC-9801の都合に合わせた変換をした。
全体的な処理が軽いので、PC-9801NX/Cでは実機よりもかなり速く動く!
PC-9801で動く、PC-8001エミュレータ。
— PocketGriffon (@GriffonPocket) 2021年12月15日
テキスト画面のセミグラフィックで表示してる事もあって処理が軽い。486SXの40MHzで実機より速く動くよ!(^^) pic.twitter.com/rIn9xEuPbS
テキストでPC-8001が動くという事で、↑ネタ画像ではあるけどこんな絵も出せる!
テキスト画面でPC-8001、グラフィックス画面にPC-8801を表示してる。この状態で両方が動くわけではないが、こんな表示も可能ですよって事で(^-^)
終わりに
今回のブログは、何年も前に作ったもののご紹介でした!
以前、別のアカウント名で公開してたので、見た覚えがある人もいるだろう。
当時はブログを書いてなかったので詳しい説明が出来なかったが、今回ようやくこうして文章として残せる事になった!(^-^)
PC-9801CV21で動いてるアルフォスを見たい…という、些細な話から始まった制作だったけれど、私個人としてはV30でZ80のエミュレーションコードを書くチャンスともなった。開発当初は半信半疑ではあったけど、ちゃんと動いたので大満足だ!(^^)
こういう無茶なチャレンジは本当に楽しいね!(^o^)/
ではまた次回!(^-^)ノ