FP-1100のサブCPU乗っ取りについて
>>>>>>>こちらにサンプルコードをまとめてみました。
FP-1100は画面表示・キー入力・BEEP音その他の入出力関係がサブCPU側に切り離されていて、本来メインCPUからはBASICルーチンを介してしかアクセス出来ない。用意されたサブCPUとのやりとりルーチンは、送るパラメータの数によって4種類ある。
実行アドレス | 内容 | |
タイプ1 | &H0AFB | パラメータなし。Aregにコマンドコードを入れてコール。 |
タイプ2 | &H0B00 | パラメータがひとつ。Aregにコマンドコード、Bregにパラメータを入れてコール。 |
タイプ3 | &H0B09 | パラメータがふたつ。Aregにコマンドコード、BCregにパラメータを入れてコール。 |
タイプ4 | &H0B16 | パラメータが三つ以上。Aregにコマンドコード、Bregにパラメータ個数、HLregにパラメータ先頭アドレスを入れてコール。 |
コマンドコードは&H01〜&H33まで用意されていて、画面表示やキー入力などの入出力関係にマシン語からアクセスすることが出来る。が、結局BASICで使っているルーチンそのままなのでBASIC以上の表現は無理。これは初心者が使いやすいシステムではあるのだが、スピードがとてつもなく遅い上、各ルーチンのチューンも不可能でリアルタイムゲームには不向き。また、BASICルーチンからではリアルタイムキー入力は出来ないしBEEP音も決められた2種類の長さしか出せない。上記以外にも直接実行番地をコールしてBASICルーチンを利用する手段もある。サブCPUを直接制御する場合、最低限以下のものを覚えておけば事足りるかと。
■主なコマンドコードとBASICルーチン
コマンドコード | 内容 | タイプ |
&H21 | 画面サイズ変更 / Breg=1で80字モード、0だと40字モード | 2 |
&H12 | LOCATE / BCregにXY座標 | 3 |
&H13 | COLOR / BCregにカラーコード | 3 |
直接コール | DEFCHR$ / HLregにデータ先頭アドレスを入れて&H077Eをコール | × |
さて、このサブとメインのやり取りだが、サブCPUはコマンドコードを元にテーブルを参照してそれぞれの処理先にジャンプするという仕組み。では、&H33を越えた想定外のコマンドコードをサブ側に送るとどうなるか? サブCPUはメモリ内のでたらめな数値を参照してジャンプしてしまう。つまり大半は暴走してしまうのだが、偶然(?)にもジャンプ先がVRAM内を指すコマンドコードが存在する。ということは、画面上にグラフィックとしてサブCPU用のコードを表示させておき、うまく処理をそこに移せばサブCPUを乗っ取ることが出来る。
以上がFP-1100におけるサブCPU乗っ取りの大まかなストーリー。なかなか感動的。
具体的な例として、&H6Bをコマンドコードとして送ると、サブCPUは&H3A60番地をコールする。このアドレスは、40字モードでLOCATE 4,21(80字だとlocate 44,10)のキャラクタ位置、青のVRAMに相当する。なのでその位置にサブCPUプログラムを書き込んでおけばよい。
10 SCREEN 0:WIDTH 40 20 LOCATE 4,21:COLOR 1,0 30 DEFCHR$(255)="2400E44A304A2008" 40 PRINT CHR$(255); 50 'HACK SUB CPU 60 CALL &H0AFB,&H6B 70 GOTO 60
↑BASICによる単純なサブCPU乗っ取りの例。高速でBEEPのオンオフを繰り返すだけ。プチプチ音がする。サブCPU側のコードは
24 00 E4 LVI DE ,$E400 4A 30 MVIX (DE),$30 ;BEEP ON 4A 20 MVIX (DE),$20 ;BEEP OFF 08 RET
こんな感じである(BEEPのポートについては後述)。
簡単な検証やユーティリティプログラムではVRAM上にプログラムを置きっぱなしにして毎回コールしても良いが、リアルタイムゲームに応用した過去の例を見るとサブCPUのワークエリアにプログラムを転送してそこで実行させるのが一般的だったようである。
しかしサブCPUのワークは&HFF80〜のみ。とても小さい。VRAMはRGB三枚分あるし、40字モードにすればまるまる半分が画面外(スクロールエリア)になるのでここを使わない手はない! と思いきや、サブCPUに積まれたVRAMは世にも奇妙な反転型RAM(&H00 を書き込むと&HFFになる)であり、さらに水平同期中にVRAMにアクセスがあると次の水平同期までWAITがかかる。つまり1byte書き込むのに約64μSecかかる。ちなみに並び方もキャラクタ単位に8byte縦に並んで次は横に移るという、ちょっと変則的な並び。(確かパソピア7もそんな並びだった?)
■サブCPUメモリマップ
0000H〜 | CPU内部ROM |
1000H〜 | 外部ROM |
2000H〜 | 青VRAM |
6000H〜 | 赤VRAM |
A000H〜 | 緑VRAM |
E000H〜 | I/O |
F400H〜 | 外部ROM/CPU内部RAM |
F800H〜 | フリーエリア |
サブCPUはμPD7801G。約2MHz。こちらのリンク(http://www.st.rim.or.jp/~nkomatsu/nec/uPD7807.html)はuPD7807に関するものだが、これは7801の上位互換なので参考になる。こちらのリンク(http://www.disgruntleddesigner.com/chrisc/GamePokekon/files/uPD78c06_Instruction_Set.txt)はインスタラクションセット。8bitプログラムの経験者なら上の2つの資料で問題なくコードのほうは書けるだろう。
メイン<>サブのやりとりポートは以下の通り。
アドレス | 内容 | 詳細 |
サブCPU &HE800 | メインCPUとのやりとり | メインCPUポート&HFFC0にwriteしたデータがセットされる。書き込むとメンCPUの&HFF80からreadできる。メモリマップドIOなので単に読み書きすればOK |
サブCPU INT2 | メインCPUからの割り込み信号 | 1で割り込みあり SKIT F2で待てる |
メインCPU ポート&HFFC0 | (write)サブCPUとのやりとりポート | サブCPUの&HE800へ。使用例 : LD BC,&HFFC0 / OUT(C),A |
メインCPU ポート&HFF80 | (write)割り込み制御ポート | 最上位ビットがサブCPUへの割り込み信号INTS |
メインCPU ポート&HFF80 | (read)サブCPUとのやりとりポート | サブCPUの&HE800の内容がセットされる。 |
具体的には
LD BC,0xFF80 OUT (C),C ;INTS ON
↑これでサブCPUに割り込み信号を送る。FPユーザーのバイブル100%活用法によれば、INTSは4μs以上1にしてから0に戻さねば上手く割り込みがかからないことがあるとのこと。4μsはメインCPUの16クロックサイクル相当。でもハドソンのゲームのソースを読んでみると
LD BC,0xFF80 OUT (C),A ;INTS ON LD A,0x00 OUT (C),A ;INTS OFF
こんな感じでちゃんと動いているようなのでよくわからない。
また、サブCPU側では
SKIT F2 ;LOOP JR LOOP
こんな感じで割り込み信号を待てばよい。
結局、グラフィックを表示させようとすればメインCPUから1byteづつデータを送ってあげるしかない。(少なくとも当時雑誌に発表されたものはそうしていた)そのたびにメインとサブはREADYフラグを立てたりコマンド待ちをしたりしなくてはならず、さらにVRAM書き込み時のWAITもあるため思ったようにスピードは出せない。それでもサブCPUを乗っ取ったゲームプログラムはそうでないものに比べて格段に速かった。
ちなみにFPは完全なグラフィックマシン。テキストRAMは持っていなくてすべてグラフィック画面に描かれる。FM-7と同じような構成。BASIC上から見るとPCGがあるように見えるが、これは単にVRAMに送る文字データを書き換えてるだけ。PCG搭載機種でよく使われていたキャラパターン書き換えを使った擬似スクロールなどのテクニックは使えない。ちなみにメイン側&H9000〜のテキストRAMっぽく見える通称キャラクタバッファも単なる仮想テキストRAM。
■キーボード
サブCPU、&HE400の下位4ビットにラインNoを書き込んでから少し待つと、ポートBにKI1~KI8のビットがセットされる。KIが反転信号なのに注意。すこしというのがどのくらいなのかは未検証。&HE400の6bit目がキーボード読み込み許可。以下、キーボードマトリクス。
LINE 11 | LINE 10 | LINE 9 | LINE 8 | LINE 7 | LINE 6 | LINE 5 | LINE 4 | LINE 3 | LINE 2 | LINE 1 | LINE 0 | |
KI1(neg) | : | ; | L | K | J | H | G | F | D | S | A | SHIFT |
KI2(neg) | 0 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | ESC | CTRL |
KI3(neg) | ^ | RETURN | [7] | [4] | [5] | [8] | [9] | [6] | [3] | [+] | [-] | GRAPH |
KI4(neg) | @ | P | O | I | U | Y | T | R | E | W | Q | CAPS |
KI5(neg) | { | / | . | , | M | N | B | V | C | X | Z | KANA |
KI6(neg) | \ | RUBOUT | [←] | [CLS] | [↑] | [↓] | [INS] | [→] | [DEL] | [/] | [*] | |
KI7(neg) | - | [ | ] | [1] | [2] | [0] | SPACE | [000] | [.] | [,] | [ENTER] | |
KI8(neg) | STOP | PF9 | PF8 | PF7 | PF6 | PF5 | PF4 | PF3 | PF2 | PF1 | PF0 | BREAK |
また、通常時サブCPUはキーボードスキャンで割り込みがかかっているため、スピードダウンしている。サブCPUを乗っ取る場合、まずDIで割り込みを禁止するのがセオリー?
■BEEP音
サブCPU、&HE400の5bit目でBEEPのオンオフ。
サブCPUの&HE400のOUTはこんな感じ
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
KEY ENABLE | BEEP ON/OFF | KEY LINE NO | KEY LINE NO | KEY LINE NO | KEY LINE NO |
以上、FP-1100でのサブCPU乗っ取りに関する情報をまとめてみたがひとつ問題が。FP-1100エミュレータのeFP-1100、とても良い出来で重宝しているのだが、サブCPUを乗っ取ったプログラムで動かないものがいくつかあるようだ(2015.6.23現在)。ちゃんと動くプログラムもあり、どこ原因がちょっと究明できていないためなんとも言えないのだが。まぁこんな複雑な構成のコンピュータをクロックタイミングまでバッチリ実機に合わせるのは相当大変だと思うので、どこかでサブとメインのタイミングがずれているんだとは思うけど。。。
エミュレータでもちゃんと動く汎用の入出力ルーチンを制作中なので、次回はソースコードつきでそれを解説できたらいいな。