PC-6601のディスク制御

■ PC-6601のオートスタートの仕組みと実践

PC-6601は電源ONの直後にBASICを立ち上げずにフロッピーディスクから起動することが出来ます(※参考までに、mkIIやSRでもオートスタートが出来ますが、PC-6001初代機+拡張BASIC+フロッピーディスクドライブでは出来ません)。

BASIC ROMは本体の起動時、BASICを立ち上げる前にフロッピーディスクの接続とフロッピーディスクの挿入を確認すると、トラック0/セクタ1の内容256ByteをRAM領域のF900Hに読み込みます。読み込んだ後にF900Hからの3バイトが"SYS"というASCIIコードの場合には、F903HをCALLします。256Byte以内の簡単なプログラムであれば、F900Hに配置される前提でプログラムを作成して、フロッピーディスクのトラック0/セクタ1に書き込めばよいことになりますから、まずはこれを確認してみます。

BASIC ROMはある程度、ハード周りを初期化してからフロッピーディスクにアクセスすると思われますので、テスト用のプログラムも必要最低限に収めてみます。テスト用のプログラムは単純に画面にテキストを表示する処理にしてみましょうか。SCREEN MODE1で"HELLO WORLD"と表示するだけの面白みのないプログラムにしましょう。

まず、テスト用にBASICからF900Hにプログラムを書き込んでみて、F903HをEXECしてみます。BASICには戻らない前提なのでBASICのワークエリアの事は考えません。オートスタート処理時のメモリの状態はこのようになっています。

8000HからFFFFHはRAMが表に出ているので、F900Hからのメモリを読み書きをするためにメモリコントローラを操作する必要はなさそうです。

表示するページの切り替えはI/OポートB0HのBit1/2へ表示したいページ番号を2bitで書き込みます(Bit0とBit3は別の用途でも使っているので、注意が必要です)。

x

グラフィック周りの制御には、もう一つ、I/OポートC1Hでグラフィックモードとテキストモードの切り替えを設定する必要があり、テキストモードにするには、I/Oポート C1Hに04Hを書き込みます。

テキストモードにした時のVRAM構造は、C000HからC31FHがアトリビュートで、C400Hからが文字情報です。アトリビュートは背景色と文字色の組み合わせが7bitで格納されます。色の組み合わせはBASICのマニュアルに書かれているので、そちらを参考にしてください。今回は背景が黒で文字色を青にしてみましょう。この色の組み合わせは06Hですから、この値をC000HからC31Fに書き込みます。

続いて、C400Hに文字情報を書き込めば文字が画面に表示されるので、"HELLO WORLD"の文字列をC400Hに転送します。

ところでF900Hからの領域ですが、マニュアルによるとFA00H直前の領域はBASICのワークエリアとして使われているようです。でもまぁ、プログラムを実行したらBASICには戻ってこないので気にせずに書き込んでしまいましょう。

完成したのがこちらのプログラムです。

	ORG 0F900H

	DB	"SYS"

	DI
	LD	SP,0F900H

	LD	A,04H
	OUT	(C1H),A
	LD	A,02H
	OUT	(B0H),A

	LD	HL,0C000H
	LD	DE,0C001H
	LD	BC,0320H-1
	LD	(HL),06H
	LDIR

	LD	HL,HELLO_STR
	LD	DE,0C400H
	LD	BC,HELLO_END - HELLO_STR
	LDIR

	EI
LOOP2:
	JP	LOOP2

HELLO_STR:
	DB	"HELLO WORLD"
HELLO_END:

	END

アセンブラにかけてバイナリファイルを作成して、BASICのDATA文にしてからPOKEで書き込むも良し、エミュレータにバイナリファイルを読み込ませてF900HからのRAMに配置するも良しです。今回は、PC-6001VWでバイナリファイルを読み込ませてみます。BASICをMODE5のPAGE4で起動してから、ALT+F6でデバッグモードに入ります。setbinでバイナリファイルを読み込むことができますから、アセンブラで作成したバイナリファイル(main.bin)を0xf900に読み込むには、

setbin main.bin 0xf900

とします。

gと入力するとBASICに戻るので、CLS:EXEC &HF903として実行してみると、左上にHELLO WORLDと表示されます。

このプログラムのバイナリサイズは54Byteでした。これを、ディスクのトラック0/セクター1に書き込めば、HELLO WORLDと表示するだけのオートスタートディスクの完成です。

[プログラム一式]

ディスクイメージの作成と、アセンブルして作成されたバイナリファイルの書き込みには、Bookwormさんの「落ちものパズルをつくる」に含まれているd88toolを使わせて頂きました。作成方法は、

d88tool testimg.d88
d88tool testimg.d88 main.bin 0 0 1

です。ディスクイメージを作成して、PC-6001VWに読み込ませると...

できました!

「月」と表示されていますが、VRAM上のゴミデータがそのまま表示されてしまっているのでしょう。

■ 256Byteの壁を突破する

オートスタートで読み込まれるデータは1セクタ分の256Byteです。さすがにマシン語256バイトでは大したことが出来ないので、より大きなプログラムを読み込めるようにしてみます。そのためにはディスクからの読み込みと、読み込んだデータをRAM上に配置する処理が必要になります。

ディスクの読み込み処理はPC-6601のBASICにサブルーチン化されているので、利用させてもらいましょう。ちなみにPC-6601ではディスク制御用ICであるμPD765Aを直接操作して、さらにドライブからのデータをディスク用メモリに読み込み、さらにメインメモリに転送していることもあって、処理量がそれなりにあり、ディスクの読み書き一式で約1024Byteのプログラム量です。試しに1セクタ分だけを読み取るルーチンだけを切り出してみたのですが、それでも364Byteでしたので、オートスタート用領域の256Byteに収めることは厳しいサイズです。エラーチェックなどを省けばなんとかなるかもしれませんが、実機では動かなくなると思われます。また、少々面倒なことに、ディスクアクセスルーチンは6601リセット時にメモリ空間の表に出ているBASIC ROMには実装されておらず、VOICE ROM領域の空き領域に埋め込まれています。

VOICE ROMを表に出すためにI/Oポートを経由してメモリコントローラを操作する必要があります。OUT命令一発なので簡単な話なのですが、BASIC ROMには、ディスクアクセスルーチンをより汎用的に扱うためのルーチンが用意されているので、それを利用しましょう。

ディスク読み取りルーチンはBASIC ROM内の4274Hにあります。正しくはディスクの入出力ルーチンで、パラメータは次の通りです。

[引数]
Areg: 入出力するセクタ数
Breg: トラック番号
Creg: セクタ番号
DEreg: データ格納先のRAMアドレス
IXreg+0: ドライブ番号-1
IXreg+27: エラーカウント

[戻り値]
DEreg:次のアドレス(DEreg + Areg×256)
なんらかのエラーが発生した場合、CY = 1

引数は更に、ディスクの入力か出力か、または実際に読み書きせずにチェックのみなのかで引数を取ります。

出力処理:CY=1
入力:CY=0, Z=0
チェック:CY = 0, Z=1

入出力するセクタ数は1バイトの値を設定できるので最大255セクタまで指定できる事になるのですが、トラックをまたぐ処理が不完全らしく、同じトラック内からしか連続したアクセスはできないようです(まったくできないわけではないのですが)。

IXreg+27のエラーカウントはディスクアクセス時に何らかのエラーが発生した場合にIXreg+27に設定した値だけリトライします。IXレジスタはPC-6601起動後のオートスタート処理実行時にはC400Hが設定されているので、適切な空き領域を設定しなければなりません。IXレジスタを経由したワークエリアアクセスはIX+32Dまで利用するので、33バイトの領域が必要になります。また、IXレジスタを経由してワークエリアを読み書きするようですので、ワークエリアがRAM領域になっている必要があります。(ちなみにBASICが起動して、それからモードやページ数を設定したあとはC400Hではなく、モード/ページ数別に異なった固定値が設定されるのですが、ここでの説明は省略します)

	ORG	0F900H

	DB	"SYS"

	DI

	LD	SP,0F900H

	LD	IX,0F000H
	XOR	A
	LD	(IX+0),A
	LD	(IX+27),A

	LD	B,1			; track
	LD	C,1			; sector
	LD	DE,08000H
	XOR	A			; CY = 0
	INC	A			; Z = 0, sector数 = 1

	CALL	04274H
	JR	C,ERROR

	EI
	JP	08000H

ERROR:
	JP	ERROR

	END

オートスタートプログラムをトラック0/セクタ1に書き込みます。

続けて、オートスタートプログラムから読み込まれるプログラムは先ほどのプログラムを少しだけ改編したものです。実験用のプログラムなので、なんの面白みもないどころか実用性もまったくありません。

	ORG	08000H

	LD	A,04H
	OUT	(0C1H),A
	LD	A,02H
	OUT	(0B0H),A

	LD	HL,0C000H
	LD	DE,0C001H
	LD	BC,0320H-1
	LD	(HL),06H
	LDIR

	LD	HL,HELLO_STR
	LD	DE,0C400H
	LD	BC,HELLO_END - HELLO_STR
	LDIR

LOOP2:
	JP	LOOP2

HELLO_STR:
	DB	"I am PC-6601."
HELLO_END:

	END

特に問題もなく動作しました。

[プログラム一式]

■ 32KByteの壁を突破する

指定セクタ数分のディスク読み込みができるようになりましたが、PC-6601の初期Z80メモリ空間は前半32KbyteがBASIC ROMで後半32KByteがRAM領域に割り当てられています。PC-6601は64KByteのRAMが搭載されていて、Z80空間全てをRAMにすることが出来ます。ただ、BASIC ROMが扱えなくなるので、全ての処理を自前で実装するか、小まめにメモリ空間を切り替える必要があります。PC-6601のメモリバンク切り替えは原則的に16KByte単位ですから、フロッピーディスク周りの処理を使うためには、Z80メモリ空間の16KByteを割り当てることになります。

ディスクアクセスルーチンの裏に配置されたRAMに、ディスクから読み取ったデータを配置するには、まず、別の領域にデータを読み込んだ後にデータのコピーを行わなければならず、非常に効率が悪くなります。

となるともうこれはディスクアクセスルーチンをRAM領域に作った方がよいという結論になります。ディスクアクセスルーチンは初期化・読み取り・書き込み含めて1KByte程度ですから、RAM領域の1KByteを使ってでも実装した方がよいでしょう。ディスクアクセスルーチンの実装方法ですが、まず、音声合成ROM内に記述されているフロッピーディスクルーチンをRAM領域にコピーする方法を考えました。ただ、残念ながらコードがリロケータブルではないので、ROM上のアドレスとコピー先のRAMアドレスを一致させなければならないのですが、フロッピーディスクルーチンは7800Hというメモリ空間のど真ん中にあるため使い勝手が悪いのです。であれば、逆アセンブルしたコードをごっそりと再アセンブルしてしまいましょう。今回はディスクの読み込みだけで充分ですので、その処理だけをゴッソリといただきます。

[プログラム一式]

色々と無駄な処理が含まれていますが、動作確認優先ということで見逃して下さい。注意点としては、VOICE ROM内のディスクアクセスルーチンはEI/DI命令が使われています。なので、事前にDIしていてもディスク処理中に割り込みが解除されてしまいます。Z80メモリ空間をALL RAM化するのであれば、当然ならがら、事前に割り込み処理のジャンプ先アドレスを書き換えておかないと、割り込み発生時にBASIC ROM領域のアドレスへとジャンプするつもりがRAM領域に飛んでしまって暴走となります。
※追記 PC-6601のメモリは読み込みと書き込みそれぞれでメモリバンクの切り替えが出来る事に後で気がつきました。つまり、同じメモリアドレスに対して、書き込みはRAM、読み込みはROMとすることができます。BASIC ROMを表に出しつつ、書き込みはRAMにする事ができるということですから、フロッピーディスクからのデータを書き込む分にはBASIC ROMの領域をバンク切り替えする必要はありませんでした。もちろん読み込む(=プログラムを実行する)にはバンク切り替えが必要です。

さて、そろそろメモリの使い方が複雑になってきたのでまとめます。無駄な空きが結構あるのですが、切り詰めるのは後でもいいでしょう。

今回のテストでは、トラック2、セクタ1からの1セクタ分を4000Hに読み込んで、4000Hから実行するというものです。

(1) 電源ONでBASIC ROMがトラック0/セクター1の1セクタをF900Hに読み込む

(2) F900Hからのプログラムは、トラック1/セクター1のフロッピーディスクルーチンをF000Hに読み込む

(3) メモリバンク切り替えによりZ80メモリ空間を全てRAMにする

(4) F000Hからのフロッピーディスクルーチンを利用して、トラック2/セクター1の1セクタ分を4000Hに読み込む

(5) 4000Hへジャンプ

これも問題なく動作しました。

■ VRAMに直接読み込んでみる

先ほどは4000Hからのメモリにユーザプログラムを読み込み、4000Hから実行するというものでした。これを、VRAMの先頭アドレス0000HからのメモリにFDDからのデータを読み込むようにすれば、FDD内の画像データを表示する事ができることになります。

そのためにはまず、画像データを用意しないといけないのですが、ドアドアmkIIのロード中画面のVRAMをエミュレータのデバッグモードからファイルに落とし込んでしまいましょう。ドアドアmkIIのテープをロードしている途中で画面が整ったらデバッグモードに入り、次のコマンドでVRAMの内容をファイルに書き出します。

savemem door2capture.bin 0x0000 0x3ffff


この時のVRAMのアドレスと、I/Oポートの値も記録しておきます。オートスタートのプログラムでI/Oポートを操作してグラフィックモードを設定すれば、ディスクから読み込まれたデータがVRAMに展開されて絵が表示されます。

[プログラム一式]

■ テープ版ソフトをディスク化してみる

さて本題です。PC-6001mkII以降対応のテープ版ザ・キャッスルを6601のディスクからオートスタートできるようにしてみます。

まずはテープ版キャッスルの解析ですが、CLOADで読み込まれるBASIC+マシン語のローダーがあり、続いてマシン語部がテープに記録されていました。 マシン語部はヘッダは無く暗号化も無く、ローダーに続くマシン語部をRAMの4000Hから8000H分だけ読み込んで、4000Hから実行するというシンプルなものでした。 もう少し詳しく書くと、ザ・キャッスルのローダーはBASIC ROMのテープロードルーチンを使用していて、 メモリバンク切り替えをせずにテープから読み込んだデータを4000Hからの領域に順に書き込んでいました。 テープからの読み込みが完了したら、DI命令を実行してからバンク切り替えを行い、4000Hへとジャンプします。

ディスク化にあたっては、そのままテープイメージ化されたファイルからローダ部分を除いたマシン語部8000Hバイト分を用意します。

これを加工せずにそのままフロッピーディスクに書き込みます。オートスタートプログラムでは、フロッピーディスクから読み込んだマシン語部のデータをメモリに書き込んで4000Hにジャンプするようにします。 ここまでに作成したテスト用のプログラムをちょっと書き換えただけで実現出来ました。 ただ、BASICのCLOADされるローダー部分で次のようなBASICプログラムが実行されるので、これをマシン語に置き換える必要がありました。

CONSOLE,,0,0:SCREEN 3,2,2:CLS

BASICのローダー部ではファンクションキーの書き換えもしていたのですが、動作に影響がなさそうな書き換え内容でしたから無視しました。

[プログラム一式]

■ まとめ

テープソフトのディスク化はタイトル毎に色々と手を加えないといけないので一苦労ですが、 テープ読み込みの手間やモードやページ数を入力する手間が省けるのでとても便利ですし、PC-6601の電源を入れただけでソフトが起動する様をみるのは楽しいです。

今回作成したディスクアクセス用のルーチンは無駄が多く、まだまだ改良の余地があるので、他のテープソフトをディスク化する際に修正しようと思っています。

■ 追記

キャッスルのBASICのローダーで、ファンクションキーを定義している箇所を処理しませんでしたが、「定義しないとゲーム中のセーブ・ロード等ができなくなります」という指摘がありました。

ローダー部は

30 KEY1,CHR$(&H81):KEY2,CHR$(&H82):KEY3,CHR$(&H83):KEY4,CHR$(&H84)
40 KEY5,CHR$(&H85)

このような処理になっています。これをマシン語で処理しなければなりません。ちなみに背景のイラストがジャマでちょっと読みにくいマニュアルによると、F1キーで自殺、F2キーでゲームオーバー、F3でロード、F4でセーブです。F5については書かれていませんが一時停止のようです。これらファンクションキー設定をF900Hから配置されるプログラムの最初の方に追加します。

; ファンクションキー定義
XOR	A
LD	(0FB3EH),A
LD	(0FB46H),A
LD	(0FB4EH),A
LD	(0FB56H),A
LD	(0FB5EH),A
LD	A,081H
LD	(0FB3DH),A
INC	A
LD	(0FB45H),A
INC	A
LD	(0FB4DH),A
INC	A
LD	(0FB55H),A
INC	A
LD	(0FB5DH),A