ブロック転送
1~100 を足すプログラムで、先に改良が進んだのは 6502 でした。
一時期 6502 の方が速そうに思えたので、「Z80 に花を持たせようと 16bit 演算&ループを使う題材にしたのだけど」と書いたところ、「Z80 に花を持たせるなら、ブロック転送はどうでしょう」という意見がありました。
目次
Z80 vs 6502 トップページ(別ページ)
概要
ブロック転送か…ちょっとずるい気もしますが、実用的な例題でもあります。
ブロック転送とは、連続したメモリ内容を、別のアドレスにコピーすることを言います。
別記事に書きましたが、ゲームを作る際には「画面表示タイミング」を気にしながら画面を書き変える必要がありました。
実際の VRAM と同じサイズのメモリを用意しておき、そこを仮想画面としてゲームの画面を作っておきます。
そして、画面が垂直帰線期間(何も表示しない時間)になった時に、そのメモリ内容を VRAM に転送するのです。
ファミコンでは、スプライトの設定がちょうど 256byte に収まる設計だったため、256バイトを一気に転送したりしました。
6502 は 256 バイト以上のメモリを一括して扱うのが苦手なのですが、ファミコンのハードはうまく設計されていて、256バイトの転送で済むようにしてあるのですね。
MSX では Z80 から直接 VRAM を操作することはできなかったのですが、例題ですので
・400H から 200H byte を、600H にブロック転送する
を考えてみます。
改良案は、ページ下部のコメント欄や、メールで送って下さい。待っています。(SPAM対策アドレス表記使用)
・ローカルルール
ループ展開は、1~100を足すプログラムと同様、Z80 は30バイト、6502 は 40バイト以内に収めるものとします。
Z80基本
Z80ではこんな感じ。
LD HL,400H ; 10 (11)
LD BC,200H ; 10 (11)
LD DE,600H ; 10 (11)
LDIR ; 21 / 16 ( 23 / 18)
LDIR は、ブロック転送命令です。HL から BC バイトを DE に転送します。
1バイト転送ごとに、 21 (23) クロックかかりますが、最後の1バイトだけは 16 (18) バイトで終わります。
速度は、前処理が 33クロック、LDIR が…1命令で 511*23 + 18 = 11771クロック、合計 11804 クロックですね。
これは、おそらく改良の余地のないプログラム。
(実質的に LDIR 命令1つで終わるプログラムで、他の部分はその前処理にすぎないため)
ループ展開
2014.8.5午前追記
改良の余地がない、と思っていたら、しっかり改良版が投稿されました…。
LD HL,400H ; 10 (11)
LD BC,200H ; 10 (11)
LD DE,600H ; 10 (11)
LOOP:
LDI ; 16 (18)
LDI ; 16 (18)
LDI ; 16 (18)
LDI ; 16 (18)
LDI ; 16 (18)
LDI ; 16 (18)
LDI ; 16 (18)
LDI ; 16 (18)
JP PE,LOOP ; 10 / 10 (11 / 11)
LDIR は、「自分自身へのジャンプ」を含んだ命令です。JR による条件分岐を見るとわかりますが、分岐する場合としない場合の時間差は 5クロック。LDIR でも、繰り返している間と最後の1回で、5クロックの差があります。
LDI は、「自分自身に分岐しない」LDIR です。なので、常に「最後の1回」の実行時間しかかかりませんが、LDIR と同じ動作をしています。
これを並べてループ展開すると、LDIR より速くなる、という構造。
LDI は、BC レジスタを減算していて、0 になるまで P フラグを 1 にしています。JP PE では、この時だけジャンプします。
LDI と JP PE を使うより、LDIR の方が速いから…と思っていたのだけど、ループ展開すれば速い、という当然のことに気付いてませんでしたね。
前処理が 33クロック、ループ中は 18クロックの命令を 8 個並べ、最後に 11 クロックのジャンプがあります。これが 64 回ループ。
(18*8+11)*64 = 9920 クロックです。
総計は 9953 クロック。ちなみに、プログラムサイズは 28バイトです。
6502基本
6502ではこうです。
LDX #0 ; 2
LOOP:
LDA $400,X ; 4
STA $600,X ; 5
LDA $500,X ; 4
STA $700,X ; 5
DEX ; 2
BNE LOOP ; 3 / 2
6502 にはブロック転送命令もないですし、256 を超えるアドレスの扱いも苦手です。
そこで、512バイトを 256バイト 2つにわけて、ループの中で同時に転送しています。
転送方法も、A レジスタに読み出して、改めて別の個所に書き込むだけ。地味な作業です。
ループ内が 23 クロックで、最後だけ1クロック少ないです。ループ回数は 256回。前処理は2クロックです。
2 + 23*256 - 1 = 5889 クロック。
ループ展開
2014.8.5午前追記
Z80 にループ展開版が来ましたので、6502 にも作ってみましょう。…といっても、元々微妙にループ展開したような構造だったので、ループの回数を減らして内部を増やすだけ。
LDX #7F ; 2
LOOP:
LDA $400,X ; 4
STA $600,X ; 5
LDA $480,X ; 4
STA $680,X ; 5
LDA $500,X ; 4
STA $700,X ; 5
LDA $580,X ; 4
STA $780,X ; 5
DEX ; 2
BPL LOOP ; 3 / 2
合計 29 バイト。40バイトをループ展開の制限としていますが、もう1段階の展開はできません。
LDA と STA で9クロック、の組み合わせが4回。そして、X のデクリメント(DEX)とジャンプで、5 クロック。これを 128回繰り返します。
(9*4+5)*128 -1 = 5247 クロック
最後に 1 引いているのは、ループ末尾のジャンプ命令は実行されないためです。