導入メモその1に続き、今度はCP/M BIOSを自分で書いてみました。既にI/O掲載版があるわけで車輪の再発明もいいところですが、機能が全く同じでもないですし、複数の実装があってもいいんではないかと。また、引き続きいくつかのCP/Mアプリケーションを6601上で動かしてみたので、その紹介もしたいと思います。
(※2013.1.27追記:SR専用版も作ってみました)
スクリーン4版でRogue。ドットが細かいせいか録画したら色が変になってしまったのでエミュレータ画面で。
ビデオ出力+ブラウン管テレビだと流石に文字の判別は無理でした。
導入メモその1ではZorkにRogueと思考タイプのゲームを動かしたので、今回はリアルタイムゲームを試してみます。以下三つのゲームはすべて80桁用ですが、残念ながらROM/RAMカートリッジ無しのスクリーン4版では一つ目のゲームしか動きませんでした。スクリーン1版では、width 80としてから、ファイル名 (qtitris等) を入力すると起動します。F1キーでオートフォーカスを切った方が遊びやすいでしょう。それと、三つともゲーム前にレベル (スピード) を選べるので、ある程度スピードを上げた方が面白いです。
さて、ゲームの入ったディスクイメージを用意するために、前回はエミュレータのモニタモードでアプリケーションをメモリに読み込み、CP/M上のSAVEコマンドでディスクイメージに書き込むという手順を踏みました。今回は、CP/M用D88ファイルに直接アクセスしてファイルを追加するプログラムを書いてみたので、その使い方を説明します。配布BIOSファイルを展開した中にある、cpmadd.exe (Windows用バイナリ) というファイルを使います。Windows以外の場合は、src/cpmadd.cをコンパイルして使ってください。コマンドプロンプト上で、
cpmadd input.d88 input1.bin [input2.bin ...] output.d88
のようにすると、元のCP/M用D88イメージに対して、新たにファイルが追加された状態のD88ファイルができます。ディスクイメージの作成の項で用意したcpm.d88を元ファイルとして使うと良いでしょう。
既に存在するファイルを出力D88ファイルとして指定することもできますが、問答無用で上書きするので注意してください。(出力ファイルの指定を忘れると、最後の入力ファイルが破壊されます!) 入力と出力のD88ファイル名を同じにすることもできます。ひたすら追記するだけのプログラムなので、ディスクが一杯になったら別のイメージを用意するか、CP/M上でERAコマンドを使って要らないファイルを消去するなどしてください。ちなみに、空いているブロックを先頭から順に使用するので、消したり追加したりを繰り返すと断片化が進むかもしれません。CP/M用のデフラグツールってあるんでしょうか?また、テキストファイルを追加する場合は、改行コードがCR+LF(0Dh+0Ah)になっていること、最後がCtrl-Z(1Ah)で終わっていることを確認してください。
前回はZork Iのファイルをコピーするのに一旦分割してPIPコマンドで結合しましたが、これを使うと
cpmadd cpm.d88 zork1.* zork1.d88
のようにしてZork Iの入ったディスクイメージを作成することができます。
PC-6601でQuatris
テトリス的ゲームです。Retrocomputing Archiveの/cpm/cdrom/ENTERPRS/CPM/GAME/よりQTITRIS.COMをダウンロードし、コマンドプロンプトで
cpmadd cpm.d88 qtitris.com quatris.d88
とすると、QTITRIS.COMが書き込まれたquatris.d88というファイルが生成されます。ゲーム名とコマンド名が違って紛らわしいので、名前を変えておいた方がいいかもしれませんね。導入メモその1でやったように、
P6VW>loadmem qtitris.com 0x100
a>save 57 qtitris.com
のようにしてもOKです。操作は13で左右移動、2で回転。スクリーン1版だと次のブロック表示が画面内に収まらないので、スクリーン4版でやった方がいいでしょう。
PC-6601でCatChum。スクリーン4版で動かすにはROM/RAMカートリッジが必要です。
ばぐごん的ゲームです。Retrocomputing ArchiveのMiscellaneous Softwareより、CATCHUM.ZIPをダウンロードして展開し、中のファイルCATCHUM.COMとCATCHUM.DATの両方をディスクイメージに書き込む必要があります。
cpmadd cpm.d88 catchum.* catchum.d88
でOKです。自キャラがちゃんとパクパクしているように見えるのがいいですね。残念ながら、ROM/RAMカートリッジ無しのスクリーン4版では動きませんでした。
PC-6601でLadder。スクリーン4版で動かすにはROM/RAMカートリッジが必要です。
スパイパニックや沙悟浄的ゲームです。Javaで移植されていたり、結構人気があるみたいですね。Retrocomputing ArchiveのMiscellaneous Softwareより、LADDER.ZIPをダウンロードして展開します。あとはCatChumと同様に、
cpmadd cpm.d88 ladder.* ladder.d88
として必要なファイルをコピーします。面のバリエーションがあってなかなか面白いです。残念ながら、ROM/RAMカートリッジ無しのスクリーン4版では動きませんでした。
PC-6601でZASM
ここまでゲームばかり動かしてきましたが、CP/Mと言えば充実した開発環境なので、次は各種言語が動くかどうか試してみました。まずはアセンブラから。でも使い方を覚えるの面倒だなあ……というわけで、個人的にDOS版を使い慣れているZASMのCP/M版を。作者のサイトから、zasm164c.lzhをダウンロードして展開し、zasm.comをディスクにコピーします。当然かもしれませんが、DOS版と同じソースリストが同じようにアセンブルできるようです。BIOSのソースの最後にCtrl-Zだけ加えてコピーし、ZASMにかけてみたら、ちゃんと通りました。素晴らしい。
PC-6601でBDS-C。ファイル表示するプログラムをコンパイル後実行して、自分のソースを表示してみたところ
次はC言語に挑戦してみます。BDS-Cというのを試してみました。The Unofficial CP/M Web SiteのCP/M Programming Languages and Developer's Toolsより、bdsc-all.zipをダウンロードして展開し、以下のファイルをディスクにコピーします。BDSCIO.Hはファイル入出力なんかの時に要るようです。stdio.hみたいなものですかね。
まずCCコマンドでコンパイル。中間ファイルができるので、CLINKコマンドでリンクすると.COMファイルができます。今のCと若干文法が違ったり、サブセットということで機能に多少の制限があったりもするようです。
CCしてCLINKで実行ファイルができる
PC-6601でTurbo Pascal。エディタ画面
次はTurbo Pascalです。Retrocomputing ArchiveのProgramming Languages/Environmentsより、TP_301A.ZIPをダウンロードして展開し、以下のファイルをディスクにコピーします。
TINSTは最初に1回だけ、設定のため起動する必要があります。以下のように進めてください。http://ldlabo.hishaku.com/NO32/main.htmに参考画像があります。
(※ROM/RAMカートリッジ無しのスクリーン4版ではTINSTは動きませんでしたが、実行しなくてもさほど問題は無いような気もします。)
一度済めばTINST.*は消しても構いません。次いで、a>turboとしてプログラム本体を立ち上げると、まずエラーメッセージを出すかどうか聞かれます。YかNで答えると、以下のようなコマンドが使えるモードになります。
コマンド | 内容 |
---|---|
O | オプション変更 (実行ファイルを作りたい場合はここでCを選択、Qで戻る) |
E | エディタモードに |
S | ソースをセーブ |
C | コンパイル |
R | 実行 |
Q | 終了 |
コマンド | 内容 |
---|---|
Ctrl-E | カーソルを上に |
Ctrl-S | カーソルを左に |
Ctrl-D | カーソルを右に |
Ctrl-X | カーソルを下に |
Ctrl-G | カーソル上の文字を消去 |
Ctrl-N | 1行挿入 |
Ctrl-Y | 1行消去 |
Ctrl-K Ctrol-D | 終了 |
エディタのキーバインドは、CP/Mでは一般的なもののようです。上の表ぐらいを覚えれば何とか使えるんじゃないでしょうか。当時としては高性能なエディタでかつ安価だったため、エディタ目当てにTurbo Pascalを買う人もいたんだとか。
コンパイル中
.COMファイルの実行も成功
メモリマップは以下の通りです。ROM/RAMカートリッジ無しスクリーン1版のCCP/BDOS/BIOSの位置関係はI/O掲載版と同じ、スクリーン4版はそれより6KB少ないユーザエリアとなっています。VRAMがネックになっていて、あまり選択の余地がありません。
ROM/RAMカートリッジ使用版では、外部RAM領域をTPAに充てることによって使える領域を増やしました。CP/Mアプリケーションからは表RAMとして、0000h〜7FFFhとC000h〜FFFFhの領域が内蔵RAM、8000h〜BFFFhの領域が外部RAMで見えており、BIOS内で必要な時のみ8000h〜BFFFhを裏RAM (内蔵RAM側) に切り替える仕組みです。スクリーン1版では、バッファエリアやBIOSプログラムの大部分を裏RAMに移動させることで、更にユーザエリアを確保しています。
「○○K CP/M」という表現については、どうやら「BIOS以降のサイズが1.5KBの時に64K CP/Mと称する」という定義になっているようです。じゃあBIOSが512バイトなら65Kになるのかよ!その定義おかしいだろ!よしやってみよう!ということで、ROM/RAMカートリッジ使用スクリーン1版では限界に挑戦してみました。ただ実際は、ジャンプテーブルやBDOS用ワークエリアなど、CP/Mの仕様上必ずBIOS内に確保しなければいけない領域があるため、512バイト全部を使ってBIOSのプログラムを書くことはできません。領域の大きさはFDDの仕様によりますが、1Dでブロックサイズ1024バイト、4ドライブ構成という条件だと、約400バイトが最低限必要です。残りは100バイト程度で、ここにプログラムを収めるのは結構キツかったです。可能な限りの処理を裏RAM側に押し込みましたが、本当にカツカツで、現在表RAM側に余っているメモリはたったの1バイト。何かバグが発覚したらたぶんもう直せません。どうしよう。
アドレス | 内容 |
---|---|
0000h〜 | CP/Mワークエリア |
0100h〜 | TPA (ユーザエリア) |
A800h〜 | CCP (CP/M シェル部分) |
B006h〜 | BDOS (CP/M OS部分) |
BE00h〜 | BIOSジャンプテーブル、ワークエリア |
C000h〜 | VRAM |
C800h〜 | BIOSプログラム |
CE00h〜 | ディスクリード用バッファ |
D200h〜 | ディスクライト用バッファ |
D600h〜 | 仮想画面 |
DE00h〜 | 空き、BIOS一時スタック |
F900h〜 | ブート用 |
FA00h〜 | 割り込みテーブル |
アドレス | 内容 |
---|---|
0000h〜 | CP/Mワークエリア |
0100h〜 | TPA (ユーザエリア) |
9000h〜 | CCP (CP/M シェル部分) |
9806h〜 | BDOS (CP/M OS部分) |
A600h〜 | BIOS |
AF00h〜 | 割り込みテーブル、BIOS一時スタック |
B000h〜 | ディスクリード用バッファ |
B400h〜 | ディスクライト用バッファ |
B800h〜 | 仮想画面 |
C000h〜 | VRAM |
アドレス | 内蔵RAM | 外部RAM |
---|---|---|
0000h〜 | CP/Mワークエリア | 無し |
0100h〜 | TPA (ユーザエリア) | |
8000h〜 | VRAM | TPA |
8800h〜 | ディスクリード用バッファ | |
8C00h〜 | ディスクライト用バッファ | |
9000h〜 | 仮想画面 | |
9800h〜 | BIOS (裏) | |
9F00h〜 | DMA用バッファ | |
9F80h〜 | 空き、BIOS一時スタック | |
C000h〜 | TPA | 無し |
E800h〜 | CCP (CP/M シェル部分) | |
F006h〜 | BDOS (CP/M OS部分) | |
FE00h〜 | BIOS (表) |
アドレス | 内蔵RAM | 外部RAM |
---|---|---|
0000h〜 | CP/Mワークエリア | 無し |
0100h〜 | TPA (ユーザエリア) | |
8000h〜 | VRAM | TPA |
C000h〜 | TPA | 無し |
D000h〜 | CCP (CP/M シェル部分) | |
D806h〜 | BDOS (CP/M OS部分) | |
E600h〜 | BIOS | |
EE00h〜 | ディスクリード用バッファ | |
F200h〜 | ディスクライト用バッファ | |
F600h〜 | 仮想画面 | |
FE00h〜 | 割り込みテーブル、BIOS一時スタック等 |
制御コード、エスケープシーケンスはI/O掲載版と同じものに対応したつもりですが、動作の詳細についてはもしかしたら違っているかもしれません。対応コードは以下の通りです。
コード | 機能 | |
---|---|---|
08h,1dh | カーソル左へ | |
09h,0ch,1ch | カーソル右へ | |
0bh | カーソル上へ | |
0ah | 改行+復帰 | |
0dh | 復帰 | |
1ah | 画面全体クリア、カーソル原点へ | |
1eh | カーソル原点へ | |
1bh | エスケープシーケンス | |
ESC * | 画面全体クリア、カーソル原点へ | |
ESC T | カーソル位置から行末までクリア | |
ESC Y | カーソル位置から画面終わりまでクリア | |
ESC =yx | カーソルを(x-32,y-32)に移動 |
スクリーン4版では、80×25文字表示を行うため、自前のフォントを使っています。スクリーン4は画面全体で320×200ドットなので、1文字あたり4×8ドット、縦横1ドットずつ隙間を空けているため、実質3×7ドットのフォントです。多少無理のある文字もありますが、P6ではかつて、スパイパニック、ピラミッド、ハイドライドなど、横3ドットのフォントが良く使われていたので、これもまたP6らしさだとも言えるんではないでしょうか。
CP/Mでは基本的には7ビット文字だけ表示できればいいのですが、実はフォントを作った時に調子に乗って他の文字にも手をつけて、そして見事壁にぶち当たってしまったので、おまけとして苦闘の様子を紹介します。最後はかなりヤケクソ気味にドットを打ったのですが、それでも仮名や漢字は全部埋めることすらできませんでした。横3ドットで平仮名はやはりハードルが高い……。ヤケクソで作ってみたら案外読めるものになった、という字も中にはあるんですけどね。小さい「ゅ」なんかお気に入りです。ちなみに、ご覧の通り表計算ソフトでCtrl-C/Ctrl-Vを叩きまくって作成しました。黒背景のマスに数字の1を入れておいてデータ変換しています。
挫折の跡
2012.9.22版より、簡易的な代替ROMファイルを同梱してみました。本来のROMとある程度の互換性があり、エミュレータでBASICROM.66の代用品として使えます。とは言っても、拙作のCP/M BIOSとS-OS"SWORD"が動くための最低限の機能のみの実装です。
実装されているサブルーチンは以下の4個です。
2012.11.24版よりSRに対応しました。ただし、「SRでも起動する」という程度の対応なので、あくまで1D版で、そのためSRにおいては以下のような制限があります。
以前のバージョンではそもそもSRで起動できなかったのですが、その経緯について説明します。これまで、6601やmkIISRは持っていたのですが外付けFDDが無いため、実機確認は6601のみ、SRとしての動作確認はエミュレータでのみ行っていました。まあ6601で動けばたぶんSRでも大丈夫だろうと高を括っていたのですが、いざ66SRに触らせて頂く機会を得て試してみたところ、全然動きません。その後自分でも66SRを入手して、予想外の問題点が色々あることがわかったので、どういった点を修正して動くようにしたのか、参考までに書き留めておきます。
拙作のBIOSでは、ブート後接続ドライブ数を確認するために、ROM内ルーチンをコールしています。mkII/6601では42B9番地をコールしているのですが、SRでは4292番地を呼ばないといけないようです。どうやら42B9hのルーチンもそのまま残っているようで、エミュレータでの確認では気付くことができませんでした。もしかして、1D用ドライブと1DD用ドライブとで担当ルーチンが違うんでしょうか?ともあれ66SRでの動作が確認できたので、とりあえず良しとしたいと思います。
対処方法としては、SR/非SR共にROM内ルーチンの同じ部分からこのルーチンが呼ばれているので (601Ch)、そこに書いてある番地を呼び出すという処理にしました。また、ディスクリードルーチン (4274h) にも変更があるようで、ワークエリアとして使う領域が増えています (FE80h-FE84h)。そこを設定するためにもやはり4292hの方をコールする必要があるようです。BIOS側で使用するエリアと重なっていたりしたので、退避などして対応しました。また、この変更に伴い代替ROMファイルも更新しました。
SRのVRAMまわりは非SRとはかなり違うのですが、それはSR独自の画面モードの話です。非SRの画面モードであればmkII/6601と全く同じ……そう思い込んでいたのが落とし穴で、微妙な仕様の違いに引っかかっていました。
非SR画面モードは更に2種類に分けられます。初代機画面モード (32x16のテキストと256x192のグラフィック) とmkII画面モード (40x20のテキストと320x200のグラフィック) です。ポートC1hで画面モードを選び、ポートB0hでRAMのどのエリアをVRAMに割り当てるかを選ぶのですが、B0hに書き込む値とVRAMアドレスの対応は、モードによって異なります。以下に簡単にまとめてみました。
B0hの値 | 初代機画面モード | mkII画面モード |
---|---|---|
0 | C000h | 8000h |
2 | E000h | C000h |
4 | 8000h | 0000h |
6 | A000h | 4000h |
さて、ここで問題です。初代機画面モードでポートB0hに0を書き込んだあと、ポートC1hでmkII画面モードに切り替えました。この時、VRAMアドレスはどうなるでしょう?
答え:SRではC000h、非SRでは8000h
モード切り替え時、SRでは切り替え前の「VRAMアドレス」が維持されるのに対し、非SRでは切り替え前に「B0hに書き込んだ値」が維持されてVRAMアドレスが決まるようです。切り替え後に改めて0を書き込むと、どちらも同じく8000hになります。以下にテストプログラムを示します。モード5ページ3 (スクリーン1のVRAM=8000h) 用です。
10 OUT &HC1,6:REM 32x16
20 OUT &HB0,0:REM VRAM=C000h
30 OUT &HC1,4:REM 40x20
非SRでは一瞬画面がチラつくだけですが、SRでは画面が見えなくなってしまいます。非SRの場合40x20モードになった時点でVRAMアドレスが8000hに再設定されますが、SRではC000hのままだからです。手探りで「OUT &HB0,0」と打ち込むと、VRAMアドレスが正しく再設定されて元に戻ります。また、20行目を変更して以下のようにすると、今度はSR/非SRで逆の動作になります。
10 OUT &HC1,6:REM 32x16
20 OUT &HB0,4:REM VRAM=8000h
30 OUT &HC1,4:REM 40x20
エミュレータでの対応は現状では分かれており、PC6001VW ver 307a2ではSR/非SR共に非SR的な動作を、iP6 Plus Rel.4.7ではSR/非SR共にSR的な動作をするようです。
スクリーン1版の仮想80桁モードでは、GRAPH+カーソルという (ちょっと変な) 操作方法を使います。66SRではこれがうまく動きませんでした。
PC-6001シリーズでは、キー入力はサブCPUが処理し、ユーザプログラムは割り込みによってそれを知るという形になっています。通常文字が入力されると要求番号02hの割り込みが、グラフィック文字が入力されると要求番号14hの割り込みがかかり、次いでキーコードがサブCPUから送られます。また、グラフィック文字が割り振られていないキーとGRAPHキーを同時に押した場合にも同様に割り込みがかかります。この場合のキーコードは0です。
一方、カーソルキー/スペース/シフト/STOPに関しては、サブCPUに調査コマンドを送ることで、「現在押されているかどうか」をユーザプログラム側が能動的に調べることが可能です。そこで、拙作のBIOSでは「14hの割り込みがかかった時に、割り込みルーチン内で調査コマンドを発行する」という手順を踏むことで、GRAPH+カーソルを検出しています。キーを押してから離されるまで普通何十ミリ秒かかかるので、その間にキーの状態を調べようというわけです。(ちなみに、このロジックだとカーソル+カナキーなんてのも拾ってしまいます)
しかし66SRでは、2/3くらいの確率でしかカーソルキーを検出できませんでした。キーを押しっ放しにしておいても、返ってきた結果がしばしば「何も押されていない」という風になってしまいます。どうやら、キー割り込みがかかった直後、しばらく調査コマンドを受け付けない状態になることがあるようです。一方、同様のプログラムをmkIISRで走らせると100%検出できるので、恐らく66SRのセパレートキーボード特有の挙動なのではないかと思います。
更に困ったことに、検出できる時とできない時の判断方法がわかりません。ポート92hを使い、CPUとサブCPUの橋渡しをしている8255の状態を調べても同じなので、やはりそこから先、サブCPUとキーボード側の問題のように思われます。しょうがないので、調査コマンド発行前に空ループで適当に時間を潰すようにしたところ、無事動くようになりました。
CP/M移植にあたって参考にした資料、今後CP/Mを移植する人にとって参考になりそうな資料などを挙げておきます。
トップ > 雑多なもの > PC-6001mkII/6601 CP/M導入メモ・その2