SDカードドライバの設計(4)

| 5 Comments | No TrackBacks
高速読み出しの工夫
第1回で説明したように、ジョイスティックポートからのデータ入力はかなり手間がかかります。

高速化は最もたくさん実行する部分から行うのが定石なので、心臓部であるバイト入力をする部分を最適化するのが最も効果的です。
(ポート0xa0)<-0x0f	レジスタ0x0fをラッチ
(ポート0xa1)<-0x10	DIを書き込み
(ポート0xa1)<-0x11	クロックを反転
(ポート0xa0)<-0x0e	レジスタ0x0eをラッチ
A<-(ポート0xa2)	データ読みこみ、ビット0にデータが入る
    
これで1ビット。1バイト入力するにも8倍かかります。これを限界まで高速にする挑戦をします。

最初のコード

まず、Z80の入出力は

in	a, (n)
out	(n), a
    
がそれぞれ11 T-state(以下11Tと表記します)で最速ですので、これを使います。
同じポートから連続してバイト列を読み取りたい場合などは
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サイクルにはウェイトがあるらしいです。
海外のMSX関連の資料(たとえばこことか)を見たところ、やはりM1に1サイクル追加されるようです。よって、すべてのオペレーションのT-stateはZ80の仕様のドキュメントにあるT-stateから1増加させた数値になります。おそらくPC-6001も同じだと思われます。
ただし、この記事の中の数値はM1ウェイトを入れていない数値です。
これにセクタリード用のコマンド送信やらループ管理やらを入れて、1セクタ(512バイト)のリードにかかる時間はおよそ580,000T、実効速度2MHzとすると290msecということになります。実際にはさらにFATの管理コードの実行時間があるので、もっとかかります。

最適化

ここで、時間が短縮できるものがあります。それが以下の3種類です。
  1. Aレジスタのセットアップ
    上記のソースでは、いちいちAレジスタに即値代入をしていましたが、これは7Tかかります。あらかじめ別のレジスタに値を準備しておけばレジスタ間転送で4Tで済みます。
  2. Eレジスタの値の代入
    最初はandを使ってゼロフラグ経由で入力を判断していました。しかしうまいことに、入力がアキュームレータの第0ビットに入ってくるので、4Tのrrca一発でキャリーフラグに結果を追い出せます。また、8回シフトするのでEレジスタへ最初の0代入は実は不要です。
  3. ループ
    ループを展開すればサイズは大きくなりますが、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する必要がなく、レジスタの再ラッチが不要なためです。

もしプリンタポートで入出力したとすれば、やはり同じ理由で入出力が少し高速になるでしょう。ただし、プリンタポートには電源が来ていないので、ジョイスティックポートよりも物理的な接続が面倒になります。

これがバイト入力に関して私が考え付いた限界です。もし上記でもっと最適化、高速化できる方法があったら教えてください。


No TrackBacks

TrackBack URL: http://www.markn.org/cgi-bin/mt/mt-tb.cgi/754

5 Comments

1バイト691ですか…これは結構厳しいですね。
読み込み部をぱっと見てもこれ以上は私には無理そうでした。

強いて挙げるとするならば
8回目を
RLCA
OR E
とすると-4Clockになりそうとか考えたのですが。

前提として
最終的な出力 E を A に変更
IN A が bit0 の入力値以外 0 であること
E の bit0 が 0 で初期化されていること
が必要になってしまいますね。うーむ。

余談ですがプリンタポート版も見てみたいところです。

う、ローテートの向きを勘違いしていました(汗
どのみち E にビット順に入れなければいけないということは
8回シフトしないとダメなんですよね。うーむ。

ほんの少しですが速くなったような気がします。
机上でしか検証してないので間違ってたらスミマセン。
Cregに$A1を入れるパターンでも同じ‥かな?

    LD      HL,$0E0F
    LD      BC,$10A0
_MAIN:
    OUT     (C),L
    LD      A,B
    OUT     ($A1),A
    INC     A
    OUT     ($A1),A
    OUT     (C),H
    IN      A,($A2)
    RRCA
    RL      E

Leave a comment