とうとう、mkIIを落札してしまいました...
実際に入手できるのは、次回の日本出張時なので9月中旬になりますが.
なんだか深みにはまっていくような...これでよかったのだろうか....
実際に入手できるのは、次回の日本出張時なので9月中旬になりますが.
なんだか深みにはまっていくような...これでよかったのだろうか....
カードリーダをプリンタポートに接続する場合のコードを考えてみました。昔のパソコンの大部分は入力がBUSY信号のみですが、幸いなことにSPIで必要とする入力信号は1本なので、これで間に合います。
PC-6001のプリンタ関連のポートは以下のとおりです。0x91 data output 0xc0 busy(bit1)なお、回路では出力の際、インバータを通しているので、ビットは反転出力する必要があります。
では、早速コードを見てみましょう。例により、レジスタEを出力としています。-- ld d, #0xff _main: ld a, d out (#0x91), a dec a out (#0x91), a in a, (#0xc0) add a, d rl e ; _mainからを8回繰り返す ret --ここではハードウェアの設定として、以下を仮定しています。ここでのトリックは、
- CLKはビット0とします。
- DIはビット1~7のどこでもかまいません。
add a, dです。ポート0xc0からの入力は、ビット1以外は常に0なので、これで入力がある(a=0x02)場合は0xffを足すとキャリーが立ちます。しかも、レジスタDを使いまわせるので、利用するレジスタがA, F, D, Eの4つのみです。
結構これは重要で、このルーチンの外側でループを使ってバッファに書き込みを行うので、
ld (hl), eのようにバッファへのポインタ(この場合HL)とループカウンタを利用します。ジョイスティックの場合はバイト入力でAFDEHLを使ってしまい、残りのレジスタは2つ。ループカウンタ用のレジスタをBにしてdjnzでまわしても、ぎりぎりレジスタが足りないので、実は裏レジスタを利用しています。惜しい...。
プリンタポートだとその必要がないのでexxと裏表のレジスタ転送の分が浮きます。バイトごとにこの処理が必要なので、1セクタあたり12T*512回分。合計6,144Tで、意外に馬鹿になりません。
もし、SDカードからの入力信号が1のときにポート0xc0の値が0になるようであれば、上記の代わりにld c, #0x02を準備しておいて、
sub cとするとよいでしょう。
T-stateは1bitあたり53、1バイトで441となります。裏レジスタを利用しなくて済む分も考慮して、1セクタあたりの時間は約260,000T、期待できる転送レートは3kB/s(VDG on)または6kB/s(VDG off)となります。前回よりさらに1.5倍のスピードになりますね。
なお、これは現在のSDカードアダプタを、ケーブル配線のみ変更すれば適用可能です。ただ、テストはしていませんのでご注意ください。
第1回で説明したように、ジョイスティックポートからのデータ入力はかなり手間がかかります。
高速化は最もたくさん実行する部分から行うのが定石なので、心臓部であるバイト入力をする部分を最適化するのが最も効果的です。
(ポート0xa0)<-0x0f レジスタ0x0fをラッチ (ポート0xa1)<-0x10 DIを書き込み (ポート0xa1)<-0x11 クロックを反転 (ポート0xa0)<-0x0e レジスタ0x0eをラッチ A<-(ポート0xa2) データ読みこみ、ビット0にデータが入る
これで1ビット。1バイト入力するにも8倍かかります。これを限界まで高速にする挑戦をします。最初のコード
まず、Z80の入出力はがそれぞれ11 T-state(以下11Tと表記します)で最速ですので、これを使います。in a, (n) out (n), a
同じポートから連続してバイト列を読み取りたい場合などはでブロック転送するのがいいのですが、ここでは残念ながら適しません。ini
inir
outi
otir
入力結果はレジスタeに入れると仮定します。
-- ld e, #0x00 ; 結果 ld b, #0x08 ; ループカウンタ _loop: sla e ld a, #0x0f out (#0xa0), a ld a, #0x10 out (#0xa1), a ld a, #0x11 out (#0xa1), a ld a, #0x0e out (#0xa0), a in a, (#0xa2) and a, #0x01 jr nz, _zero set 0, e _zero: djnz _loop ret --
最初は上記のような感じでした。実際には入力ポートの判別を動的にやっていたので、もっと複雑だったのですが。最適化
上記のコードのT-stateを計算すると、1bitあたり123T~126Tで、1バイトあたり1,003T~1,027Tとなります。PC-6001のM1サイクルにはウェイトがあるらしいです。これにセクタリード用のコマンド送信やらループ管理やらを入れて、1セクタ(512バイト)のリードにかかる時間はおよそ580,000T、実効速度2MHzとすると290msecということになります。実際にはさらにFATの管理コードの実行時間があるので、もっとかかります。
海外のMSX関連の資料(たとえばこことか)を見たところ、やはりM1に1サイクル追加されるようです。よって、すべてのオペレーションのT-stateはZ80の仕様のドキュメントにあるT-stateから1増加させた数値になります。おそらくPC-6001も同じだと思われます。
ただし、この記事の中の数値はM1ウェイトを入れていない数値です。
ここで、時間が短縮できるものがあります。それが以下の3種類です。というわけで、次のようなコードになりました。
- Aレジスタのセットアップ
上記のソースでは、いちいちAレジスタに即値代入をしていましたが、これは7Tかかります。あらかじめ別のレジスタに値を準備しておけばレジスタ間転送で4Tで済みます。- Eレジスタの値の代入
最初はandを使ってゼロフラグ経由で入力を判断していました。しかしうまいことに、入力がアキュームレータの第0ビットに入ってくるので、4Tのrrca一発でキャリーフラグに結果を追い出せます。また、8回シフトするのでEレジスタへ最初の0代入は実は不要です。- ループ
ループを展開すればサイズは大きくなりますが、djnzの分の時間は浮きますね。
-- ld hl, #0x0e0f ld d, #0x10 _main: ld a, l out (#0xa0), a ld a, d out (#0xa1), a inc a out (#0xa1), a ld a, h out (#0xa0), a in a, (#0xa2) rrca rl e ; _mainからを8回繰り返す ret --
inでデータを読み取ったあと、rrcaでビット0をキャリーに追い出して、それをeのビット0から入れています。
これでT-stateは1bitあたり83、1バイトあたり691と、最初のコードの70%ほどになります。さらに外部でも工夫して、1セクタあたり約390,000T、2MHzで200msec以下となりました。
計算上ではなくて実測してみると、およそ2kB/s出ているので、そちらから考えれば1セクタ250msです。FAT管理などのオーバヘッドを考えるとおよそ妥当なところでしょう。
なお、よく知られているようにVDGからのBUSREQを止めると、画面は乱れますが速度は約2倍、約4kB/sとなります。
ちなみに、バイト出力はもっと高速です。inする必要がなく、レジスタの再ラッチが不要なためです。
もしプリンタポートで入出力したとすれば、やはり同じ理由で入出力が少し高速になるでしょう。ただし、プリンタポートには電源が来ていないので、ジョイスティックポートよりも物理的な接続が面倒になります。
これがバイト入力に関して私が考え付いた限界です。もし上記でもっと最適化、高速化できる方法があったら教えてください。